-
Notifications
You must be signed in to change notification settings - Fork 3
feat: multirewards ssbeets #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: beethoven
Are you sure you want to change the base?
Changes from all commits
bcdabd0
69a538b
a168d09
8c0de62
1fa1e03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,19 +7,26 @@ pragma experimental ABIEncoderV2; | |
|
|
||
| // These are the core Yearn libraries | ||
| import {BaseStrategy, StrategyParams} from "@yearnvaults/contracts/BaseStrategy.sol"; | ||
| import {Address} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; | ||
| import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import {Math} from "@openzeppelin/contracts/math/Math.sol"; | ||
| import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| import "@openzeppelin/contracts/math/SafeMath.sol"; | ||
| import "@openzeppelin/contracts/utils/Address.sol"; | ||
| import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; | ||
| import "@openzeppelin/contracts/math/Math.sol"; | ||
| import "../interfaces/BalancerV2.sol"; | ||
| import "../interfaces/MasterChef.sol"; | ||
|
|
||
| contract Strategy is BaseStrategy { | ||
| using SafeERC20 for IERC20; | ||
| using Address for address; | ||
| using SafeMath for uint256; | ||
|
|
||
| IBalancerVault public balancerVault; | ||
| IBalancerPool public bpt; | ||
| IERC20 public rewardToken; | ||
|
|
||
| IERC20[] public rewardTokens; | ||
| IAsset[] internal assets; | ||
| SwapSteps internal swapSteps; | ||
| SwapSteps[] internal swapSteps; | ||
| bytes32 public balancerPoolId; | ||
| uint8 internal numTokens; | ||
| uint8 internal tokenIndex; | ||
|
|
@@ -33,18 +40,28 @@ contract Strategy is BaseStrategy { | |
| // masterchef | ||
| IBeethovenxMasterChef public masterChef; | ||
|
|
||
| modifier isVaultManager { | ||
| checkVaultManagers(); | ||
| _; | ||
| } | ||
|
|
||
| function checkVaultManagers() internal { | ||
| require(msg.sender == vault.governance() || msg.sender == vault.management()); | ||
| } | ||
|
|
||
| struct SwapSteps { | ||
| bytes32[] poolIds; | ||
| IAsset[] assets; | ||
| } | ||
|
|
||
| struct Toggles { | ||
| bool claimRewards; | ||
| bool doClaimRewards; | ||
| bool doSellRewards; | ||
| bool abandonRewards; | ||
| } | ||
|
|
||
| uint256 internal constant max = type(uint256).max; | ||
| IERC20 private constant beets = IERC20(0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e); | ||
|
|
||
| //1 0.01% | ||
| //5 0.05% | ||
|
|
@@ -155,7 +172,7 @@ contract Strategy is BaseStrategy { | |
| keep = governance(); | ||
| keepBips = 1000; | ||
|
|
||
| toggles = Toggles({claimRewards : true, doSellRewards : true, abandonRewards : false}); | ||
| toggles = Toggles({doClaimRewards : true, doSellRewards : true, abandonRewards : false}); | ||
| } | ||
|
|
||
| // ******** OVERRIDE THESE METHODS FROM BASE CONTRACT ************ | ||
|
|
@@ -169,7 +186,7 @@ contract Strategy is BaseStrategy { | |
| } | ||
|
|
||
| function prepareReturn(uint256 _debtOutstanding) internal override returns (uint256 _profit, uint256 _loss, uint256 _debtPayment){ | ||
| if(toggles.claimRewards){ | ||
| if(toggles.doClaimRewards){ | ||
| _claimRewards(); | ||
| } | ||
| if (toggles.doSellRewards) { | ||
|
|
@@ -213,17 +230,15 @@ contract Strategy is BaseStrategy { | |
| return; | ||
| } | ||
|
|
||
| // put want into lp then put want-lp into masterchef | ||
| if (_joinPool()) { | ||
| // put want into lp then put loose want-lp into masterchef | ||
| _joinPool(); | ||
| uint256 _balanceOfBpt = balanceOfBpt(); | ||
| if (_balanceOfBpt > 0) { | ||
| // put all want-lp into masterchef | ||
| _depositIntoMasterChef(balanceOfBpt()); | ||
| _depositIntoMasterChef(_balanceOfBpt); | ||
| } | ||
| } | ||
|
|
||
| function depositIntoMasterChef(uint _bpts) external onlyVaultManagers { | ||
| _depositIntoMasterChef(_bpts); | ||
| } | ||
|
|
||
| function _depositIntoMasterChef(uint _bpts) internal { | ||
| masterChef.deposit(masterChefPoolId, _bpts, address(this)); | ||
| } | ||
|
|
@@ -235,7 +250,6 @@ contract Strategy is BaseStrategy { | |
| uint256 toExitAmount = tokensToBpts(_amountNeeded.sub(looseAmount)); | ||
| // withdraw needed bpt out of masterchef and sell it for want | ||
| _withdrawFromMasterChefAndSellBpt(toExitAmount); | ||
|
|
||
| _liquidatedAmount = Math.min(balanceOfWant(), _amountNeeded); | ||
| _loss = _amountNeeded.sub(_liquidatedAmount); | ||
| } else { | ||
|
|
@@ -258,9 +272,12 @@ contract Strategy is BaseStrategy { | |
| if (_balanceOfBpt > 0) { | ||
| bpt.transfer(_newStrategy, _balanceOfBpt); | ||
| } | ||
| uint256 rewards = balanceOfReward(); | ||
| if (rewards > 0) { | ||
| rewardToken.safeTransfer(_newStrategy, rewards); | ||
| for (uint i = 0; i < rewardTokens.length; i++) { | ||
| IERC20 token = rewardTokens[i]; | ||
| uint256 balance = token.balanceOf(address(this)); | ||
| if (balance > 0) { | ||
| token.safeTransfer(_newStrategy, balance); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -274,7 +291,7 @@ contract Strategy is BaseStrategy { | |
|
|
||
| // HELPERS // | ||
|
|
||
| function withdrawFromMasterChef(uint256 _amountBpt) external onlyVaultManagers { | ||
| function withdrawFromMasterChef(uint256 _amountBpt) external isVaultManager { | ||
| _withdrawFromMasterChef(address(this), _amountBpt); | ||
| } | ||
|
|
||
|
|
@@ -300,50 +317,59 @@ contract Strategy is BaseStrategy { | |
| _sellBpt(_amountBpt); | ||
| } | ||
|
|
||
| function claimRewards() external onlyVaultManagers { | ||
| function claimRewards() external isVaultManager { | ||
| _claimRewards(); | ||
| } | ||
|
|
||
| // claim all beets rewards from masterchef | ||
| function _claimRewards() internal { | ||
| if (getPendingBeets() > 0) { | ||
| uint256 rewardBal = balanceOfReward(); | ||
| uint256 prevBeetsBal = balanceOfBeets(); | ||
| masterChef.harvest(masterChefPoolId, address(this)); | ||
| uint256 keepBal = balanceOfReward().sub(rewardBal).mul(keepBips).div(basisOne); | ||
| uint256 keepBal = balanceOfBeets().sub(prevBeetsBal).mul(keepBips).div(basisOne); | ||
| if (keepBal > 0) { | ||
| rewardToken.safeTransfer(keep, keepBal); | ||
| beets.safeTransfer(keep, keepBal); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function sellRewards() external onlyVaultManagers { | ||
| function sellRewards() external isVaultManager { | ||
| _sellRewards(); | ||
| } | ||
|
|
||
| function _sellRewards() internal { | ||
| uint256 amount = balanceOfReward(); | ||
| if (amount > 0) { | ||
| uint length = swapSteps.poolIds.length; | ||
| IBalancerVault.BatchSwapStep[] memory steps = new IBalancerVault.BatchSwapStep[](length); | ||
| int[] memory limits = new int[](length + 1); | ||
| limits[0] = int(amount); | ||
| for (uint j = 0; j < length; j++) { | ||
| steps[j] = IBalancerVault.BatchSwapStep(swapSteps.poolIds[j], | ||
| j, | ||
| j + 1, | ||
| j == 0 ? amount : 0, | ||
| abi.encode(0) | ||
| ); | ||
| for (uint8 i = 0; i < rewardTokens.length; i++) { | ||
| ERC20 rewardToken = ERC20(address(rewardTokens[i])); | ||
| uint256 amount = rewardToken.balanceOf(address(this)); | ||
|
|
||
| uint decReward = rewardToken.decimals(); | ||
| uint decWant = ERC20(address(want)).decimals(); | ||
|
|
||
| if (amount > 10 ** (decReward > decWant ? decReward.sub(decWant) : 0)) { | ||
| uint length = swapSteps[i].poolIds.length; | ||
| IBalancerVault.BatchSwapStep[] memory steps = new IBalancerVault.BatchSwapStep[](length); | ||
| int[] memory limits = new int[](length + 1); | ||
| limits[0] = int(amount); | ||
| for (uint j = 0; j < length; j++) { | ||
| steps[j] = IBalancerVault.BatchSwapStep(swapSteps[i].poolIds[j], | ||
| j, | ||
| j + 1, | ||
| j == 0 ? amount : 0, | ||
| abi.encode(0) | ||
| ); | ||
| } | ||
| balancerVault.batchSwap(IBalancerVault.SwapKind.GIVEN_IN, | ||
| steps, | ||
| swapSteps[i].assets, | ||
| IBalancerVault.FundManagement(address(this), false, address(this), false), | ||
| limits, | ||
| now + 10); | ||
| } | ||
| balancerVault.batchSwap(IBalancerVault.SwapKind.GIVEN_IN, | ||
| steps, | ||
| swapSteps.assets, | ||
| IBalancerVault.FundManagement(address(this), false, address(this), false), | ||
| limits, | ||
| now + 10); | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
| function balanceOfWant() public view returns (uint256 _amount){ | ||
| return want.balanceOf(address(this)); | ||
| } | ||
|
|
@@ -360,8 +386,8 @@ contract Strategy is BaseStrategy { | |
| return masterChef.pendingBeets(masterChefPoolId, address(this)); | ||
| } | ||
|
|
||
| function balanceOfReward() public view returns (uint256 _amount){ | ||
| return rewardToken.balanceOf(address(this)); | ||
| function balanceOfBeets() public view returns (uint256 _amount){ | ||
| return beets.balanceOf(address(this)); | ||
| } | ||
|
|
||
| // returns an estimate of want tokens based on bpt balance | ||
|
|
@@ -370,12 +396,12 @@ contract Strategy is BaseStrategy { | |
| } | ||
|
|
||
| /// use bpt rate to estimate equivalent amount of want. | ||
| function bptsToTokens(uint _amountBpt) public view returns (uint _amount){ | ||
| function bptsToTokens(uint _amountBpt) internal view returns (uint _amount){ | ||
| uint unscaled = _amountBpt.mul(bpt.getRate()).div(1e18); | ||
| return _scaleDecimals(unscaled, ERC20(address(bpt)), ERC20(address(want))); | ||
| } | ||
|
|
||
| function tokensToBpts(uint _amountTokens) public view returns (uint _amount){ | ||
| function tokensToBpts(uint _amountTokens) internal view returns (uint _amount){ | ||
| uint unscaled = _amountTokens.mul(1e18).div(bpt.getRate()); | ||
| return _scaleDecimals(unscaled, ERC20(address(want)), ERC20(address(bpt))); | ||
| } | ||
|
|
@@ -399,10 +425,6 @@ contract Strategy is BaseStrategy { | |
| ); | ||
| } | ||
|
|
||
| // this allows us to also sell bpt externally | ||
| function sellBpt(uint256 _amountBpts) external onlyVaultManagers { | ||
| _sellBpt(_amountBpts); | ||
| } | ||
|
Comment on lines
-402
to
-405
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should put this back along with any other external functions deleted and implement the modifier trick. It'll save about ~4% bytecode size. The external functions are pretty important and have saved us in the past
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this and bringing back the external functions |
||
|
|
||
| // sell bpt for want at current bpt rate | ||
| function _sellBpt(uint256 _amountBpts) internal { | ||
|
|
@@ -432,14 +454,25 @@ contract Strategy is BaseStrategy { | |
| return false; | ||
| } | ||
|
|
||
| function whitelistReward(address _rewardToken, SwapSteps memory _steps) public onlyVaultManagers { | ||
| rewardToken = IERC20(_rewardToken); | ||
| rewardToken.approve(address(balancerVault), 0); | ||
| rewardToken.approve(address(balancerVault), max); | ||
| swapSteps = _steps; | ||
| // for partnership rewards like TUSD or airdrops | ||
| function whitelistRewards(address _rewardToken, SwapSteps memory _steps) public isVaultManager { | ||
| require(address(_rewardToken) != address(want)); | ||
| IERC20 token = IERC20(_rewardToken); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should check that this is not want, so we dont max approve here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://github.com/yearn/yearn-strategies/issues/247#issuecomment-1078471499 gave more reasons here |
||
| token.approve(address(balancerVault), max); | ||
| rewardTokens.push(token); | ||
| swapSteps.push(_steps); | ||
| } | ||
|
|
||
| function delistAllRewards() public isVaultManager { | ||
| for (uint i = 0; i < rewardTokens.length; i++) { | ||
| rewardTokens[i].approve(address(balancerVault), 0); | ||
| } | ||
| delete rewardTokens; | ||
| delete swapSteps; | ||
| } | ||
|
|
||
| function setParams(uint256 _maxSlippageIn, uint256 _maxSlippageOut, uint256 _maxSingleDeposit, uint256 _minDepositPeriod) public onlyVaultManagers { | ||
| function setParams(uint256 _maxSlippageIn, uint256 _maxSlippageOut, uint256 _maxSingleDeposit, uint256 _minDepositPeriod, | ||
| address _keep, uint _keepBips) public isVaultManager { | ||
| require(_maxSlippageIn <= basisOne); | ||
| maxSlippageIn = _maxSlippageIn; | ||
|
|
||
|
|
@@ -448,24 +481,23 @@ contract Strategy is BaseStrategy { | |
|
|
||
| maxSingleDeposit = _maxSingleDeposit; | ||
| minDepositPeriod = _minDepositPeriod; | ||
|
|
||
| require(_keepBips <= basisOne); | ||
| keep = _keep; | ||
| keepBips = _keepBips; | ||
| } | ||
|
|
||
| function setToggles(bool _claimRewards, bool _doSellRewards, bool _abandon) external onlyVaultManagers { | ||
| toggles.claimRewards = _claimRewards; | ||
| function setToggles(bool _doClaimRewards, bool _doSellRewards, bool _abandon) external isVaultManager { | ||
| toggles.doClaimRewards = _doClaimRewards; | ||
| toggles.doSellRewards = _doSellRewards; | ||
| toggles.abandonRewards = _abandon; | ||
| } | ||
|
|
||
| // swap step contains information on multihop sells | ||
| function getSwapSteps() public view returns (SwapSteps memory){ | ||
| return swapSteps; | ||
| } | ||
| function getSwapSteps() public view returns (SwapSteps[] memory){ | ||
| return swapSteps; | ||
| } | ||
|
|
||
| function setKeepParams(address _keep, uint _keepBips) external onlyGovernance { | ||
| require(keepBips <= basisOne); | ||
| keep = _keep; | ||
| keepBips = _keepBips; | ||
| } | ||
|
|
||
| // Balancer requires this contract to be payable, so we add ability to sweep stuck ETH | ||
| function sweepETH() public onlyGovernance { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: how is TUSD rewards claimed? Will it need to be claimed synchronously too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.. the harvest will send both beets and tusd