Created
May 14, 2024 23:17
-
-
Save nican0r/2f57e8b31b19fe7dee87b55597c8d60b to your computer and use it in GitHub Desktop.
GlpStrategy Tests
This file contains hidden or 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
| // Specification to test: | |
| // 1. tsGLP passed in on deposit is staked for GlpStrategy [x] | |
| // 2. GLP bought with weth rewards is staked for GlpStrategy (WIP) | |
| // 3. harvesting uses all the weth rewards balance if it's nonzero [x] | |
| // 4. only YieldBox can withdraw and deposit [x] | |
| // 5. depositing sGLP directly to strategy should fail [x] | |
| // 6. calling harvest with 0 rewards accumulated doesn't revert [x] | |
| // 7. user balance of tsGLP increases by amount on call to withdraw [x] | |
| // 8. GlpStrategy balance of sGLP decreases on withdrawal [x] | |
| // 9. YieldBox can always withdraw up to the full amount of GLP + weth rewards in the GlpStrategy (WIP) | |
| // 10. User can only withdraw yield accumulated for their shares TODO | |
| // 1. tsGLP passed in on deposit is staked for GlpStrategy | |
| function test_stake_acccounting() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| // mints GLP and stakes it for binanceWallet account | |
| _buyGLPAndStake(ethBuyin); | |
| uint256 userGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| uint256 strategyGlpBefore = sGLP.balanceOf(address(glpStrategy)); | |
| // Wrap sGLP -> tsGLP | |
| _wrapSGLP(binanceWalletAddr, userGlpBefore); | |
| _depositIntoStrategy(userGlpBefore); | |
| // GlpStrategy's balance of sGLP should be increased by userGlpBefore | |
| uint256 strategyGlpAfter = sGLP.balanceOf(address(glpStrategy)); | |
| assertTrue( | |
| strategyGlpBefore + userGlpBefore == strategyGlpAfter, | |
| "strategy GLP balance doesn't increase" | |
| ); | |
| } | |
| // 2. GLP bought with weth rewards is staked for GlpStrategy | |
| // TODO: add compounding here for better testing | |
| // TODO: figure out why sGLP balance delta is greater than the rewards accumulated in harvesting | |
| function test_weth_rewards_staked() public isArbFork prankBinance { | |
| // buy sGLP | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| // wrap and deposit sGLP | |
| uint256 userGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| _wrapSGLP(binanceWalletAddr, userGlpBefore); | |
| _depositIntoStrategy(userGlpBefore); | |
| // warps time forward then harvests the rewards | |
| // @audit first testing for a simple case where rewards accumulate then get harvested | |
| uint256 interval = 525600; // using the interval from compound harvest test | |
| vm.warp(block.timestamp + interval); | |
| // this returns the reward amount in GLP | |
| uint256 rewardsBeforeHarvest = glpStrategy.pendingRewards(); | |
| uint256 strategyGLPBalanceBeforeHarvest = sGLP.balanceOf( | |
| address(glpStrategy) | |
| ); | |
| glpStrategy.harvest(); | |
| uint256 rewardsAfterHarvest = glpStrategy.pendingRewards(); | |
| uint256 strategyGLPBalanceAfterHarvest = sGLP.balanceOf( | |
| address(glpStrategy) | |
| ); | |
| console2.log( | |
| "glp balance delta: %e", | |
| strategyGLPBalanceAfterHarvest - strategyGLPBalanceBeforeHarvest | |
| ); | |
| console2.log("rewards before harvest: %e", rewardsBeforeHarvest); | |
| // check that call to harvest decreases weth rewards and increases the sGLP balance of GlpStrategy | |
| // reward balance of GlpStrategy is empty after harvesting | |
| // 3. harvesting uses all the weth rewards balance if it's nonzero | |
| assertTrue( | |
| rewardsAfterHarvest == 0, | |
| "reward balance of GlpStrategy nonzero after harvesting" | |
| ); | |
| // all harvested rewards have been staked for GlpStrategy | |
| // TODO: this can serve as a starting point for fuzzing to ensure sGLP balance always increases | |
| assertTrue( | |
| rewardsBeforeHarvest == | |
| strategyGLPBalanceAfterHarvest - | |
| strategyGLPBalanceBeforeHarvest, | |
| "GLP lost in harvest" | |
| ); | |
| } | |
| // 4. only YieldBox can withdraw and deposit | |
| function test_only_yieldBox_actions() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| uint256 userGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| _wrapSGLP(binanceWalletAddr, userGlpBefore); | |
| // deposit directly into strategy | |
| vm.expectRevert("Not YieldBox"); | |
| glpStrategy.deposited(userGlpBefore); | |
| // withdraw directly from strategy | |
| vm.expectRevert("Not YieldBox"); | |
| glpStrategy.withdraw(binanceWalletAddr, userGlpBefore); | |
| } | |
| // 5. depositing sGLP directly to strategy should fail | |
| function test_deposit_sGLP() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| uint256 userGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| // Deposit into strategy | |
| tsGLP.approve(address(yieldBox), userGlpBefore); | |
| vm.expectRevert("BoringERC20: TransferFrom failed"); | |
| yieldBox.depositAsset( | |
| glpStratAssetId, | |
| binanceWalletAddr, | |
| binanceWalletAddr, | |
| userGlpBefore, | |
| 0 | |
| ); | |
| } | |
| // 6. calling harvest with 0 rewards accumulated doesn't revert | |
| function test_harvest_zero() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| // wrap and deposit sGLP | |
| uint256 userGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| _wrapSGLP(binanceWalletAddr, userGlpBefore); | |
| _depositIntoStrategy(userGlpBefore); | |
| // assert that rewards accumulated by the strategy = 0 | |
| assertTrue(glpStrategy.pendingRewards() == 0); | |
| glpStrategy.harvest(); | |
| } | |
| // 7. user balance of sGLP increases by amount on call to withdraw | |
| // TODO: implement a sub-version of this where rewards get compounded | |
| function test_user_balance_increases() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| // wrap and deposit sGLP | |
| uint256 userSGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| _wrapSGLP(binanceWalletAddr, userSGlpBefore); | |
| _depositIntoStrategy(userSGlpBefore); | |
| // warps time forward then harvests the rewards | |
| uint256 interval = 525600; // using the interval from compound harvest test | |
| vm.warp(block.timestamp + interval); | |
| glpStrategy.harvest(); | |
| // withdrawing initially deposited amount in this test | |
| _withdrawFromStrategy(userSGlpBefore); | |
| // unwraps received tsGLP from | |
| tsGLP.unwrap( | |
| binanceWalletAddr, | |
| tsGLP.balanceOf(address(binanceWalletAddr)) | |
| ); | |
| uint256 userSGLPAfter = sGLP.balanceOf(address(binanceWalletAddr)); | |
| assertTrue( | |
| userSGlpBefore <= userSGLPAfter, | |
| "user loses sGLP on withdraw" | |
| ); | |
| } | |
| // 8. GlpStrategy balance of sGLP decreases on withdrawal | |
| // TODO: implement a sub-version where rewards are compounded and not fully withdrawn | |
| function test_strategy_balance_decreases() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| // wrap and deposit sGLP | |
| uint256 userSGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| _wrapSGLP(binanceWalletAddr, userSGlpBefore); | |
| _depositIntoStrategy(userSGlpBefore); | |
| // warps time forward then harvests the rewards | |
| uint256 interval = 525600; // using the interval from compound harvest test | |
| vm.warp(block.timestamp + interval); | |
| glpStrategy.harvest(); | |
| uint256 strategyBalanceBefore = sGLP.balanceOf(address(glpStrategy)); | |
| // withdraw the user's original balance from strategy | |
| _withdrawFromStrategy(userSGlpBefore); | |
| uint256 strategyBalanceAfter = sGLP.balanceOf(address(glpStrategy)); | |
| // if this is false, strategy gains sGLP from user | |
| assertTrue( | |
| strategyBalanceBefore - strategyBalanceAfter == userSGlpBefore, | |
| "strategy gains sGLP" | |
| ); | |
| } | |
| // 9. YieldBox can always withdraw up to the full amount of GLP + weth rewards in the GlpStrategy | |
| // @audit when trying to withdraw the full amount before the 15 minute cooldown period for sGLP this function reverts | |
| // NOTE: since any withdrawal triggers buying GLP with the rewards this means the full reward amount will never be withdrawable as new rewards will always accumulate in the cooldown period | |
| // unless the rewards accumulated in the cooldown period are less than the precision value of the sGLP token | |
| function test_always_withdrawable() public isArbFork prankBinance { | |
| uint256 ethBuyin = 1 ether; | |
| _buyGLPAndStake(ethBuyin); | |
| // wrap and deposit sGLP | |
| uint256 userSGlpBefore = sGLP.balanceOf(binanceWalletAddr); | |
| _wrapSGLP(binanceWalletAddr, userSGlpBefore); | |
| _depositIntoStrategy(userSGlpBefore); | |
| // warps time forward then harvests the rewards | |
| uint256 interval = 525600; // using the interval from compound harvest test | |
| vm.warp(block.timestamp + interval); | |
| glpStrategy.harvest(); | |
| uint256 strategyBalanceBeforeWithdraw = sGLP.balanceOf( | |
| address(glpStrategy) | |
| ); | |
| console2.log( | |
| "strategy balance before: %e", | |
| strategyBalanceBeforeWithdraw | |
| ); // 1.76230473101395713457e21 | |
| // withdraw full amount from strategy | |
| // vm.warp(20 minutes); // need to warp ahead to work around the cooldown period for minting sGLP | |
| _withdrawFromStrategy(strategyBalanceBeforeWithdraw); | |
| uint256 strategyBalanceAfterWithdraw = sGLP.balanceOf( | |
| address(glpStrategy) | |
| ); | |
| assertTrue( | |
| strategyBalanceAfterWithdraw == 0, | |
| "full rewards not withdrawable" | |
| ); | |
| } | |
| /** | |
| * Utils | |
| */ | |
| function compound(uint256 t, uint256 n) internal { | |
| glpStrategy.harvest(); | |
| uint256 r = t % n; | |
| uint256 interval = (t - r) / n; | |
| console2.log("interval: ", interval); | |
| // warps time forward then harvests the rewards | |
| for (uint256 i; i < r; i++) { | |
| vm.warp(block.timestamp + interval + 1); | |
| glpStrategy.harvest(); | |
| } | |
| for (uint256 i; i < n; i++) { | |
| vm.warp(block.timestamp + interval); | |
| glpStrategy.harvest(); | |
| } | |
| } | |
| // NOTE: this can be modified for fuzzing to test all tokens used to mint GLP | |
| function _buyGLPAndStake(uint256 _amountToBuy) internal { | |
| uint256 glpPrice = glpManager.getPrice(true) / 1e12; | |
| uint256 wethPrice = gmxVault.getMaxPrice(weth) / 1e12; | |
| // Buys GLP using ETH which is automatically staked by GMX | |
| uint256 minUsdg = (((wethPrice * _amountToBuy) / 1e18) * 99) / 100; // 1% slippage | |
| uint256 minGlp = (minUsdg * 1e18) / glpPrice; | |
| // mints GLP to the msg.sender | |
| glpRewardRouter.mintAndStakeGlpETH{value: _amountToBuy}( | |
| minUsdg, | |
| minGlp | |
| ); | |
| uint256 glpBal = sGLP.balanceOf(address(binanceWalletAddr)); | |
| assertGe(glpBal, minGlp, "user doesn't receive minimum sGLP amount"); | |
| } | |
| function _wrapSGLP(address _recipient, uint256 _balanceToWrap) internal { | |
| sGLP.approve(address(tsGLP), _balanceToWrap); | |
| tsGLP.wrap(_recipient, _recipient, _balanceToWrap); | |
| } | |
| function _depositIntoStrategy(uint256 _amount) internal { | |
| // Deposit into strategy | |
| tsGLP.approve(address(yieldBox), _amount); | |
| // yieldBox makes call to GlpStrategy::deposited | |
| yieldBox.depositAsset( | |
| glpStratAssetId, | |
| binanceWalletAddr, | |
| binanceWalletAddr, | |
| _amount, | |
| 0 | |
| ); | |
| } | |
| function _withdrawFromStrategy(uint256 _amount) internal { | |
| yieldBox.withdraw( | |
| glpStratAssetId, | |
| binanceWalletAddr, | |
| binanceWalletAddr, | |
| _amount, | |
| 0 | |
| ); | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment