-
-
Save zhegic/85f0befd1da2e6b90a75a6be8434423f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
contract LotManager is ILotManager { | |
using SafeERC20 for IERC20; | |
using Address for address; | |
using SafeMath for uint256; | |
using EnumerableSet for EnumerableSet.AddressSet; | |
uint256 FEE_PRECISION = 10000; | |
uint256 MAX_FEE = 100 * FEE_PRECISION; | |
uint256 public constant LOT_PRICE = 888_000e18; | |
uint256 fee = 10 * FEE_PRECISION; | |
address public uniswapV2 = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); | |
IHegicStaking public HegicStakingETH = IHegicStaking(0x1Ef61E3E5676eC182EED6F052F8920fD49C7f69a); | |
IHegicStaking public HegicStakingWBTC = IHegicStaking(0x840a1AE46B7364855206Eb5b7286Ab7E207e515b); | |
address public weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); | |
address public wbtc = address(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); | |
IHegicPool public pool = IHegicPool(0x7656C0D5CaaD1aA7b30b141c0426647e1AE0F3c0); | |
IERC20 public token; | |
address public governance; | |
address public pendingGovernance; | |
uint256 public desiredETHLots = 0; | |
uint256 public desiredWBTCLots = 0; | |
EnumerableSet.AddressSet private protocolTokens; | |
constructor( | |
address _pool | |
) public { | |
// Check underlying | |
if (_pool != address(0)) { | |
pool = IHegicPool(_pool); | |
} | |
token = IERC20(pool.getToken()); | |
// Add tokens to protocol | |
protocolTokens.add(address(pool)); | |
protocolTokens.add(address(token)); | |
protocolTokens.add(weth); | |
protocolTokens.add(wbtc); | |
protocolTokens.add(address(HegicStakingETH)); | |
protocolTokens.add(address(HegicStakingWBTC)); | |
governance = msg.sender; | |
} | |
function getName() external pure returns (string memory) { | |
return 'LotManager'; | |
} | |
function isLotManager() external override pure returns (bool) { | |
return true; | |
} | |
function lotPrice() external override view returns (uint256) { | |
return LOT_PRICE; | |
} | |
/** Underlying getters and checks | |
* | |
*/ | |
function balaceOfUnderlying() public override virtual view returns (uint256 _underlyingBalance) { | |
uint256 _ethHegic = HegicStakingETH.balanceOf(address(this)).mul(LOT_PRICE); | |
uint256 _wbtcHegic = HegicStakingWBTC.balanceOf(address(this)).mul(LOT_PRICE); | |
return _ethHegic.add(_wbtcHegic); | |
} | |
function balaceOfLots() public override view returns (uint256 _eth, uint256 _wbtc) { | |
return ( | |
HegicStakingETH.balanceOf(address(this)).mul(LOT_PRICE), | |
HegicStakingWBTC.balanceOf(address(this)).mul(LOT_PRICE) | |
); | |
} | |
/** Governance Control | |
* Set fee | |
* Set lotCost | |
* Sell Lots | |
* | |
*/ | |
function setFee(uint256 _fee) external onlyGovernance { | |
require(fee <= MAX_FEE, 'lot-manager/max-fee'); | |
fee = _fee; | |
emit FeeSet(_fee); | |
} | |
function setDesiredLots(uint256 _eth, uint256 _wbtc) public onlyGovernance returns (bool) { | |
desiredETHLots = _eth; | |
desiredWBTCLots = _wbtc; | |
emit DesiredLotsSet(_eth, _wbtc); | |
} | |
function sellLots(uint256 _eth, uint256 _wbtc) public override onlyGovernance returns (bool) { | |
// Sells Lot(s) used for unwinding/rebalancing | |
(uint256 _ethOwned, uint256 _wbtcOwned) = balaceOfLots(); | |
require (_eth <= _ethOwned && _wbtc <= _wbtcOwned, 'lot-manager/not-enough-lots'); | |
if (_eth > 0) _sellETHLots(_eth); | |
if (_wbtc > 0) _sellWBTCLots(_wbtc); | |
// Transfer all underlying back to pool | |
token.transfer(address(pool), token.balanceOf(address(this))); | |
return true; | |
} | |
/** BuyLot | |
* Pools calls stake to invest underlying | |
* | |
*/ | |
function buyLot() external override virtual onlyPool returns (bool) { | |
// Get allowance | |
uint256 allowance = token.allowance(address(pool), address(this)); | |
// Check if Allowance exceeds lot contract cost | |
require (allowance >= LOT_PRICE, 'lot-manager/not-enough-allowance'); | |
// Buy lot by transfering tokens | |
token.transferFrom(address(pool), address(this), LOT_PRICE); | |
(, uint256 _wbtc) = balaceOfLots(); | |
// Buys Lot(s) (defaults buys ETH lot) | |
if (_wbtc < desiredWBTCLots) { | |
_buyWBTCLots(1); | |
} else { | |
_buyETHLots(1); | |
} | |
// Transfer unused tokens(underlying) back to the pool | |
token.transfer(address(pool), token.balanceOf(address(this))); | |
return true; | |
} | |
function _buyETHLots(uint256 _eth) internal { | |
// Allow HegicStakingETH to spend allowance | |
token.approve(address(HegicStakingETH), 0); | |
token.approve(address(HegicStakingETH), _eth * LOT_PRICE); | |
HegicStakingETH.buy(_eth); | |
emit ETHLotBought(_eth); | |
} | |
function _buyWBTCLots(uint256 _wbtc) internal { | |
// Allow HegicStakingWBTC to spend allowance | |
token.approve(address(HegicStakingWBTC), 0); | |
token.approve(address(HegicStakingWBTC), _wbtc * LOT_PRICE); | |
HegicStakingWBTC.buy(_wbtc); | |
emit WBTCLotBought(_wbtc); | |
} | |
/** SellLot / Unwind | |
* Pools calls to sell lots | |
* | |
*/ | |
function sellLot() external override virtual onlyPool returns (bool) { | |
// check lots available to sell | |
(uint256 _eth, uint256 _wbtc) = balaceOfLots(); | |
require (_eth > 0 || _wbtc > 0, 'lot-manager/no-lots-avilable-to-sell'); | |
bool freeETH = HegicStakingETH.lastBoughtTimestamp(address(this)).add(HegicStakingETH.lockupPeriod()) <= block.timestamp; | |
bool freeWBTC = HegicStakingWBTC.lastBoughtTimestamp(address(this)).add(HegicStakingWBTC.lockupPeriod()) <= block.timestamp; | |
require (freeETH || freeWBTC, 'lot-manager/no-free-lots'); | |
if (freeETH && _eth > 0) { | |
_sellETHLots(1); | |
return true; | |
} | |
if (freeWBTC && _wbtc > 0) { | |
_sellWBTCLots(1); | |
return true; | |
} | |
return false; | |
} | |
function _sellETHLots(uint256 _eth) internal { | |
HegicStakingETH.sell(_eth); | |
emit ETHLotSold(_eth); | |
} | |
function _sellWBTCLots(uint256 _wbtc) internal { | |
HegicStakingWBTC.sell(_wbtc); | |
emit WBTCLotSold(_wbtc); | |
} | |
function claimRewards() public override virtual onlyPool returns (bool) { | |
uint _hegicStakingWBTCProfit = HegicStakingWBTC.profitOf(address(this)); | |
uint _hegicStakingETHProfit = HegicStakingETH.profitOf(address(this)); | |
require(_hegicStakingWBTCProfit > 0 || _hegicStakingETHProfit > 0, 'lot-manager/should-have-profits-to-claim-rewards'); | |
// Claim x888 Lot Rewards in ETH if there is profit | |
if (_hegicStakingETHProfit > 0) { | |
HegicStakingETH.claimProfit(); | |
// Swaps eth for weth | |
uint256 _eth = address(this).balance; | |
payable(weth).transfer(_eth); | |
// Swaps them to HEGIC | |
_swapRewardForToken(weth, IERC20(weth).balanceOf(address(this))); | |
} | |
// Claim x888 Lot Rewards in WBTC if there is profit | |
if (_hegicStakingWBTCProfit > 0) { | |
HegicStakingWBTC.claimProfit(); | |
// Swaps them to HEGIC | |
_swapRewardForToken(wbtc, IERC20(wbtc).balanceOf(address(this))); | |
} | |
// Gets amount of tokens as rewards | |
uint256 _rewards = token.balanceOf(address(this)); | |
// Take fee in HEGIC | |
uint256 _fee = _rewards.mul(fee).div(FEE_PRECISION).div(100); | |
_rewards = _rewards.sub(_fee); | |
// Deposit fee in Pool to get zHEGIC | |
token.approve(address(pool), 0); | |
token.approve(address(pool), _fee); | |
IHegicPool(address(pool)).deposit(_fee); | |
// Transfer zHegic to governance | |
pool.transfer(governance, pool.balanceOf(address(this))); | |
// Transfer HEGIC _rewards to pool (do this last) | |
token.transfer(address(pool), _rewards); | |
// Emit event | |
return true; // TODO Change to return _rewards ? | |
} | |
/** Swap [Not sure if needed, might be better to give rewards in ETH/WBTC ?] | |
* Swaps rewards for more HEGIC | |
* | |
*/ | |
function _swapRewardForToken(address _reward, uint256 _amount) internal { | |
if (_amount == 0) return; | |
require(_reward == weth || _reward == wbtc, 'lot-manager/only-weth-or-wbtc-rewards-allowed'); | |
// Check amount of _reward | |
IERC20(_reward).balanceOf(address(this)); | |
// Approve _amount to uniswapV2 on _reward | |
IERC20(_reward).safeApprove(uniswapV2, 0); | |
IERC20(_reward).safeApprove(uniswapV2, _amount); | |
address[] memory path; | |
if (_reward == weth) { | |
path = new address[](2); | |
path[0] = weth; | |
path[1] = pool.getToken(); | |
} else if (_reward == wbtc) { | |
path = new address[](3); | |
path[0] = wbtc; | |
path[1] = weth; | |
path[2] = pool.getToken(); | |
} | |
// Swap _reward for token | |
IUniswapV2(uniswapV2).swapExactTokensForTokens(_amount, uint256(0), path, address(this), now.add(1800)); | |
} | |
// Governance setters | |
function setPendingGovernance(address _pendingGovernance) external onlyGovernance { | |
pendingGovernance = _pendingGovernance; | |
emit PendingGovernanceSet(_pendingGovernance); | |
} | |
function acceptGovernance() external onlyPendingGovernance { | |
governance = msg.sender; | |
emit GovernanceAccepted(); | |
} | |
modifier onlyGovernance { | |
require(msg.sender == governance, 'lot-manager/only-governance'); | |
_; | |
} | |
modifier onlyPendingGovernance { | |
require(msg.sender == pendingGovernance, 'lot-manager/only-pending-governance'); | |
_; | |
} | |
// Pool modifier | |
modifier onlyPool { | |
require(msg.sender == address(pool), 'lot-manager/only-pool'); | |
_; | |
} | |
/** Util | |
* Governance Dust Collection | |
* | |
*/ | |
function collectDust(address _token, uint256 _amount) public onlyGovernance { | |
// Check if token is not part of the protocol | |
require(!protocolTokens.contains(_token), 'lot-manager/token-is-part-of-protocol'); | |
if (_token == address(0)) { | |
payable(governance).transfer(_amount); | |
} else { | |
IERC20(_token).transfer(governance, _amount); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment