Skip to content

Instantly share code, notes, and snippets.

@nican0r
Created May 14, 2024 23:17
Show Gist options
  • Select an option

  • Save nican0r/2f57e8b31b19fe7dee87b55597c8d60b to your computer and use it in GitHub Desktop.

Select an option

Save nican0r/2f57e8b31b19fe7dee87b55597c8d60b to your computer and use it in GitHub Desktop.
GlpStrategy Tests
// 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