From ddee257bce9b53edd66c7d85aaf2cf912f9b17df Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Sun, 28 Jul 2024 22:45:49 +0200 Subject: [PATCH] Implement plenty ebip tests in foundry, fix some app storage merge mistakes --- .../silo/SiloFacet/SiloGettersFacet.sol | 2 +- .../contracts/interfaces/IMockFBeanstalk.sol | 2 + .../contracts/libraries/Silo/LibFlood.sol | 2 +- .../contracts/libraries/Silo/LibGerminate.sol | 1 + protocol/contracts/libraries/Silo/LibSilo.sol | 5 +- protocol/test/foundry/sun/Flood.t.sol | 250 +++++++++++++++++- 6 files changed, 252 insertions(+), 10 deletions(-) diff --git a/protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol b/protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol index 0745a422f2..ac6401fed3 100644 --- a/protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol +++ b/protocol/contracts/beanstalk/silo/SiloFacet/SiloGettersFacet.sol @@ -512,7 +512,7 @@ contract SiloGettersFacet is ReentrancyGuard { } function totalRainRoots() external view returns (uint256) { - return s.sys.silo.roots; + return s.sys.rain.roots; } //////////////////////// STEM //////////////////////// diff --git a/protocol/contracts/interfaces/IMockFBeanstalk.sol b/protocol/contracts/interfaces/IMockFBeanstalk.sol index d84708d5bd..49f3841a20 100644 --- a/protocol/contracts/interfaces/IMockFBeanstalk.sol +++ b/protocol/contracts/interfaces/IMockFBeanstalk.sol @@ -1676,6 +1676,8 @@ interface IMockFBeanstalk { function totalPods(uint256 fieldId) external view returns (uint256); + function totalRainRoots() external view returns (uint256); + function totalRealSoil() external view returns (uint256); function totalRoots() external view returns (uint256); diff --git a/protocol/contracts/libraries/Silo/LibFlood.sol b/protocol/contracts/libraries/Silo/LibFlood.sol index 69656d046d..50c43ba728 100644 --- a/protocol/contracts/libraries/Silo/LibFlood.sol +++ b/protocol/contracts/libraries/Silo/LibFlood.sol @@ -307,7 +307,7 @@ library LibFlood { * ownership when the Farm became Oversaturated. Also, at the beginning of the * Flood, all Pods that were minted before the Farm became Oversaturated Ripen * and become Harvestable. - * For more information On Oversaturation see {Weather.handleRain}. + * For more information On Oversaturation see {handleRain}. */ function sopWell(WellDeltaB memory wellDeltaB) private { AppStorage storage s = LibAppStorage.diamondStorage(); diff --git a/protocol/contracts/libraries/Silo/LibGerminate.sol b/protocol/contracts/libraries/Silo/LibGerminate.sol index 3726118c62..e1457d6ae1 100644 --- a/protocol/contracts/libraries/Silo/LibGerminate.sol +++ b/protocol/contracts/libraries/Silo/LibGerminate.sol @@ -191,6 +191,7 @@ library LibGerminate { ); germinatingStalk = firstStalk; totalRootsFromGermination = roots; + firstGerminatingRoots = roots; emit FarmerGerminatingStalkBalanceChanged( account, -int256(uint256(germinatingStalk)), diff --git a/protocol/contracts/libraries/Silo/LibSilo.sol b/protocol/contracts/libraries/Silo/LibSilo.sol index 358213ac3b..e8203e4343 100644 --- a/protocol/contracts/libraries/Silo/LibSilo.sol +++ b/protocol/contracts/libraries/Silo/LibSilo.sol @@ -259,7 +259,8 @@ library LibSilo { if (s.accts[account].sop.rainRoots > s.accts[account].roots) { uint256 deltaRoots = s.accts[account].sop.rainRoots.sub(s.accts[account].roots); s.accts[account].sop.rainRoots = s.accts[account].roots; - s.sys.silo.roots = s.sys.silo.roots.sub(deltaRoots); + // decrease system rain roots + s.sys.rain.roots = s.sys.rain.roots.sub(deltaRoots); } // emit event. @@ -316,7 +317,7 @@ library LibSilo { if (s.accts[sender].sop.rainRoots > s.accts[sender].roots) { uint256 deltaRoots = s.accts[sender].sop.rainRoots.sub(s.accts[sender].roots); s.accts[sender].sop.rainRoots = s.accts[sender].roots; - s.sys.silo.roots = s.sys.silo.roots.sub(deltaRoots); + s.sys.rain.roots = s.sys.rain.roots.sub(deltaRoots); } // Add Stalk and Roots to the 'recipient' balance. diff --git a/protocol/test/foundry/sun/Flood.t.sol b/protocol/test/foundry/sun/Flood.t.sol index cb67d02703..7da35233af 100644 --- a/protocol/test/foundry/sun/Flood.t.sol +++ b/protocol/test/foundry/sun/Flood.t.sol @@ -28,6 +28,7 @@ contract FloodTest is TestHelper { // test accounts address[] farmers; + int96 depositStemBean; event SeasonOfPlentyWell(uint256 indexed season, address well, address token, uint256 amount); event SeasonOfPlentyField(uint256 toField); @@ -55,16 +56,250 @@ contract FloodTest is TestHelper { season.siloSunrise(0); season.siloSunrise(0); + depositStemBean = bs.stemTipForToken(C.BEAN); + // users 1 and 2 deposits 1000 beans into the silo. address[] memory depositUsers = new address[](2); depositUsers[0] = users[1]; depositUsers[1] = users[2]; - depostBeansForUsers(depositUsers, 1_000e6, 10_000e6); + depostBeansForUsers(depositUsers, 1_000e6, 10_000e6, true); // give user2 some eth vm.deal(users[2], 10 ether); } + function testBugReportLostPlenty2() public { + season.rainSunrise(); + bs.mow(users[1], C.BEAN); + season.rainSunrise(); + bs.mow(users[1], C.BEAN); + + // set reserves so next season plenty is accrued + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); // 1st actual sop + bs.mow(users[1], C.BEAN); + + season.rainSunrise(); + season.rainSunrise(); + + season.droughtSunrise(); + season.droughtSunrise(); + + // withdraw deposit + vm.prank(users[1]); + bs.withdrawDeposit(C.BEAN, depositStemBean, 1_000e6, 0); + + season.rainSunrise(); + bs.mow(users[1], C.BEAN); + + uint256 userPlenty = bs.balanceOfPlenty(users[1], C.BEAN_ETH_WELL); + assertEq(userPlenty, 25595575914848452999); + } + + function testReducesRainRootsUponWithdrawal() public { + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); + season.rainSunrise(); + + bs.mow(users[1], C.BEAN); + + uint256 rainRoots = bs.balanceOfRainRoots(users[1]); + + assertEq(rainRoots, 10004000000000000000000000); + + vm.prank(users[1]); + bs.withdrawDeposit(C.BEAN, depositStemBean, 1_000e6, 0); + + rainRoots = bs.balanceOfRainRoots(users[1]); + + assertEq(rainRoots, 0); + } + + function testStopsRainingAndWithdraw() public { + int96 stem = bs.stemTipForToken(C.BEAN); + address[] memory testUsers = new address[](1); + + testUsers[0] = users[3]; + depostBeansForUsers(testUsers, 50_000e6, 100_000e6, false); + + // set reserves so that a sop will occur + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); + bs.mow(users[3], C.BEAN); + + uint256 rainRoots = bs.balanceOfRainRoots(users[3]); + assertEq(rainRoots, 500000000000000000000000000); + + season.droughtSunrise(); + + // withdraw + vm.prank(users[3]); + bs.withdrawDeposit(C.BEAN, stem, 50_000e6, 0); + rainRoots = bs.balanceOfRainRoots(users[3]); + assertEq(rainRoots, 0); + + // start raining again + season.rainSunrise(); + season.rainSunrise(); + bs.mow(users[3], C.BEAN); + rainRoots = bs.balanceOfRainRoots(users[3]); + assertEq(rainRoots, 0); + + // measure actual roots + uint256 roots = bs.balanceOfRoots(users[3]); + assertEq(roots, 0); + } + + function testBurnsRainRootsUponTransfer() public { + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); // start raining + season.rainSunrise(); // sop + + bs.mow(users[1], C.BEAN); + + uint256 rainRoots = bs.balanceOfRainRoots(users[1]); + + assertEq(rainRoots, 10004000000000000000000000); + + vm.prank(users[1]); + bs.transferDeposit(users[1], users[3], C.BEAN, depositStemBean, 1_000e6); + bs.mow(users[1], C.BEAN); + + // user[1] should have 0 rain roots + assertEq(bs.balanceOfRainRoots(users[1]), 0); + // user[3] should have 0 rain roots, none transferred + assertEq(bs.balanceOfRainRoots(users[3]), 0); + } + + function testBurnsHalfOfRainRootsUponHalfTransfer() public { + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); // start raining + season.rainSunrise(); // sop + + bs.mow(users[1], C.BEAN); + + uint256 rainRoots = bs.balanceOfRainRoots(users[1]); + + assertEq(rainRoots, 10004000000000000000000000); + + vm.prank(users[1]); + bs.transferDeposit(users[1], users[3], C.BEAN, depositStemBean, 500e6); + bs.mow(users[1], C.BEAN); + + // user[1] should be down by 500 rain roots + assertEq(bs.balanceOfRainRoots(users[1]), 5004000000000000000000000); + } + + function testDoesNotBurnRainRootsUponTransferIfExtraRootsAvailable() public { + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); // start raining + season.rainSunrise(); // sop + + bs.mow(users[1], C.BEAN); + + uint256 rainRoots = bs.balanceOfRainRoots(users[1]); + + assertEq(rainRoots, 10004000000000000000000000); + + // do another deposit + vm.prank(users[1]); + bs.deposit(C.BEAN, 1_000e6, 0); + + // pass germination + season.siloSunrise(0); + season.siloSunrise(0); + + // verify roots went up + assertEq(bs.balanceOfRoots(users[1]), 20008000000000000000000000); + + // verify rain roots stayed the same + assertEq(bs.balanceOfRainRoots(users[1]), 10004000000000000000000000); + + vm.prank(users[1]); + bs.transferDeposit(users[1], users[3], C.BEAN, depositStemBean, 500e6); + bs.mow(users[1], C.BEAN); + + // user should have full rain roots, since they had non-rain roots that could be removed before + assertEq(bs.balanceOfRainRoots(users[1]), 10004000000000000000000000); + } + + function testGerminationRainRoots() public { + C.bean().mint(users[3], 50_000e6); + vm.prank(users[3]); + C.bean().approve(BEANSTALK, type(uint256).max); + vm.prank(users[3]); + bs.deposit(C.BEAN, 50_000e6, 0); + + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); + + season.rainSunrise(); + bs.mow(users[3], C.BEAN); + + uint256 totalRainRoots = bs.totalRainRoots(); + uint256 userRainRoots = bs.balanceOfRainRoots(users[3]); + // expect user rain roots to be less than total rain roots + assertLt(userRainRoots, totalRainRoots); + + // also rain roots should be zero + assertEq(userRainRoots, 0); + } + + function testSecondGerminationRainRoots() public { + // not raining + + season.rainSunrise(); // start raining + + uint256 totalRainRootsBefore = bs.totalRainRoots(); + + C.bean().mint(users[3], 50_000e6); + vm.prank(users[3]); + C.bean().approve(BEANSTALK, type(uint256).max); + vm.prank(users[3]); + bs.deposit(C.BEAN, 50_000e6, 0); + // set reserves so we'll sop + setReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + setInstantaneousReserves(C.BEAN_ETH_WELL, 1000000e6, 1100e18); + + season.rainSunrise(); // sop + bs.mow(users[3], C.BEAN); + + uint256 totalRainRootsAfter = bs.totalRainRoots(); + // rain roots before should equal rain roots after, anything deposited after raining doesn't count + assertEq( + totalRainRootsBefore, + totalRainRootsAfter, + "total rain roots before and after should be equal" + ); + + uint256 userRainRoots = bs.balanceOfRainRoots(users[3]); + + // assert that user rain roots are zero + assertEq(userRainRoots, 0, "user rain roots should be zero"); + + // shouldn't be a way for a user to get more rain roots than total rain roots + // couldn't find a way to do lessThan without importing something else that supports BigNumber from chai + assertLt( + userRainRoots, + totalRainRootsAfter, + "user rain roots should be less than total rain roots" + ); + } + function testNotRaining() public view { Season memory s = seasonGetters.time(); assertFalse(s.raining); @@ -615,7 +850,8 @@ contract FloodTest is TestHelper { function depostBeansForUsers( address[] memory users, uint256 beansDeposit, - uint256 beansMint + uint256 beansMint, + bool mow ) public { for (uint i = 0; i < users.length; i++) { C.bean().mint(users[i], beansMint); @@ -629,10 +865,12 @@ contract FloodTest is TestHelper { season.siloSunrise(0); season.siloSunrise(0); - for (uint i = 0; i < users.length; i++) { - // mow, so that lastUpdated has been called at least once - vm.prank(users[i]); - bs.mow(users[i], C.BEAN); + if (mow) { + for (uint i = 0; i < users.length; i++) { + // mow, so that lastUpdated has been called at least once + vm.prank(users[i]); + bs.mow(users[i], C.BEAN); + } } }