Skip to content

Instantly share code, notes, and snippets.

@zhegic
Created October 14, 2020 17:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zhegic/85f0befd1da2e6b90a75a6be8434423f to your computer and use it in GitHub Desktop.
Save zhegic/85f0befd1da2e6b90a75a6be8434423f to your computer and use it in GitHub Desktop.
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