From f2444a68b5d6c2078bacbd8b7369ca837f4b1cd2 Mon Sep 17 00:00:00 2001 From: itofarina Date: Tue, 6 Aug 2024 20:25:46 -0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=94=20staking:=20disable=20provider=20?= =?UTF-8?q?when=20distribution=20finishes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gas-snapshot | 82 ++++++++++++++++++++--------------------- contracts/StakedEXA.sol | 3 +- test/StakedEXA.t.sol | 29 ++++++++++++--- 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 6d36936c..94473e41 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -461,75 +461,76 @@ StakedEXATest:invariantNoDuplicatedReward() (runs: 10, calls: 5000, reverts: 0) StakedEXATest:invariantRewardsUpOnly() (runs: 10, calls: 5000, reverts: 0) StakedEXATest:invariantShareValueIsOne() (runs: 10, calls: 5000, reverts: 0) StakedEXATest:testAlreadyListedError() (gas: 43862) -StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1247332, ~: 1305416) -StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1233419, ~: 1291503) -StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 343891, ~: 350615) +StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1246078, ~: 1280202) +StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1232165, ~: 1266289) +StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 343956, ~: 350680) StakedEXATest:testBalanceOfWithdraw(uint256) (runs: 256, μ: 60559, ~: 60566) -StakedEXATest:testCanChangeRewardsDurationWhenDisabled() (gas: 168895) +StakedEXATest:testCanChangeRewardsDurationWhenDisabled() (gas: 174277) StakedEXATest:testClaimAfterHarvest() (gas: 854153) StakedEXATest:testClaimAndUnstake() (gas: 1579631) StakedEXATest:testClaimAndWithdrawAfterRefTime() (gas: 1079773) StakedEXATest:testClaimBeforeFirstHarvest() (gas: 528009) -StakedEXATest:testDepositClaimsRewardsToReceiver() (gas: 1111861) -StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 352568, ~: 352286) -StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 836904, ~: 758252) +StakedEXATest:testDepositClaimsRewardsToReceiver() (gas: 1111839) +StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 352573, ~: 352286) +StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 831538, ~: 758192) StakedEXATest:testDepositToAnotherWithAllowance() (gas: 397264) StakedEXATest:testDepositToAnotherWithoutAllowanceShouldFail() (gas: 122813) -StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1783788, ~: 1842268) -StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 35410, ~: 35690) +StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1778484, ~: 1842223) +StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 35418, ~: 35690) StakedEXATest:testEmergencyAdminCanPauseNotUnpause() (gas: 159237) -StakedEXATest:testFinishDistributionEmitEvent() (gas: 389881) -StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1584790) -StakedEXATest:testFinishDistributionStopsEmission() (gas: 1564575) -StakedEXATest:testFinishDistributionThatAlreadyFinished() (gas: 424222) -StakedEXATest:testFinishDistributionTransfersRemainingToSavings() (gas: 112135) +StakedEXATest:testFinishDistributionEmitEvent() (gas: 392671) +StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1590217) +StakedEXATest:testFinishDistributionStopsEmission() (gas: 1569957) +StakedEXATest:testFinishDistributionThatAlreadyFinished() (gas: 429610) +StakedEXATest:testFinishDistributionTransfersRemainingToSavings() (gas: 117517) StakedEXATest:testGrantRevokeEmergencyAdmin() (gas: 107506) StakedEXATest:testGrantRevokePauser() (gas: 107235) StakedEXATest:testHandlerClaim(uint8) (runs: 256, μ: 302617, ~: 302617) -StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 811983, ~: 823294) -StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 331432, ~: 329775) -StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 127473, ~: 123299) -StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 149216, ~: 164431) +StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 811316, ~: 823222) +StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 332326, ~: 329795) +StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 127838, ~: 123277) +StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 152223, ~: 169813) StakedEXATest:testHandlerSetMarket() (gas: 124946) -StakedEXATest:testHandlerWithdraw(uint256) (runs: 256, μ: 70130, ~: 70137) +StakedEXATest:testHandlerWithdraw(uint256) (runs: 256, μ: 70108, ~: 70115) StakedEXATest:testHarvest() (gas: 185142) StakedEXATest:testHarvestAmountWithReducedAllowance() (gas: 202998) -StakedEXATest:testHarvestEffectOnRewardData() (gas: 178166) +StakedEXATest:testHarvestEffectOnRewardData() (gas: 178144) StakedEXATest:testHarvestEmitsRewardAmountNotified() (gas: 176185) StakedEXATest:testHarvestFailDoesntDoSDeposits() (gas: 360543) +StakedEXATest:testHarvestWhenFinished() (gas: 308477) StakedEXATest:testHarvestZero() (gas: 255193) StakedEXATest:testInitialValues() (gas: 89248) -StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 64179, ~: 64309) +StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 64173, ~: 64309) StakedEXATest:testMaxRewardsGasConsumption() (gas: 138537892) -StakedEXATest:testMultipleClaimsVsOne() (gas: 25444683) +StakedEXATest:testMultipleClaimsVsOne() (gas: 25444749) StakedEXATest:testMultipleHarvests() (gas: 436412) -StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1581196, ~: 1587799) +StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1581090, ~: 1587799) StakedEXATest:testNotPausingRoleError() (gas: 39589) -StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 129951, ~: 129896) +StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 129954, ~: 129896) StakedEXATest:testNotifyRewardWithUnderlyingAsset() (gas: 485760) -StakedEXATest:testOnlyAdminEnableReward() (gas: 1199012) -StakedEXATest:testOnlyAdminFinishDistribution() (gas: 190976) +StakedEXATest:testOnlyAdminEnableReward() (gas: 1198990) +StakedEXATest:testOnlyAdminFinishDistribution() (gas: 196358) StakedEXATest:testOnlyAdminNotifyRewardAmount() (gas: 203387) -StakedEXATest:testOnlyAdminSetProvider() (gas: 143637) +StakedEXATest:testOnlyAdminSetProvider() (gas: 143602) StakedEXATest:testOnlyAdminSetProviderRatio() (gas: 143295) StakedEXATest:testOnlyAdminSetRewardsDuration() (gas: 152933) StakedEXATest:testOnlyAdminSetSavings() (gas: 141076) StakedEXATest:testPausable() (gas: 1168567) StakedEXATest:testPausableClaim() (gas: 627593) -StakedEXATest:testPausableHarvest() (gas: 331866) -StakedEXATest:testPauserCanPauseUnpause() (gas: 157897) +StakedEXATest:testPausableHarvest() (gas: 331844) +StakedEXATest:testPauserCanPauseUnpause() (gas: 157875) StakedEXATest:testPenaltyGrowthRange() (gas: 67233) StakedEXATest:testPenaltyThresholdRange() (gas: 37134) StakedEXATest:testPermitAndDeposit() (gas: 361227) StakedEXATest:testRemoveDepositAllowance() (gas: 489733) -StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1027838, ~: 1027558) +StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1027844, ~: 1027558) StakedEXATest:testResetStake() (gas: 1017606) -StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 104616, ~: 105458) +StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 104653, ~: 105458) StakedEXATest:testRewardNotListedError() (gas: 1109543) -StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 800832, ~: 742909) -StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1588693, ~: 1588333) +StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 799853, ~: 741748) +StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1588702, ~: 1588333) StakedEXATest:testRewardsDurationSetEvent(uint40) (runs: 256, μ: 52028, ~: 52012) -StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 58997, ~: 59226) +StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 58976, ~: 59226) StakedEXATest:testSetMarketAddressZero() (gas: 37102) StakedEXATest:testSetMarketOnlyAdmin() (gas: 1272469) StakedEXATest:testSetMaxRewardsTokensExceeded() (gas: 104944694) @@ -537,17 +538,16 @@ StakedEXATest:testSetMinTime() (gas: 82116) StakedEXATest:testSetPenaltyGrowth() (gas: 82126) StakedEXATest:testSetPenaltyThreshold() (gas: 81992) StakedEXATest:testSetProviderRatioOverOneError() (gas: 37157) -StakedEXATest:testSetProviderZeroAddressError() (gas: 37087) -StakedEXATest:testSetSavingsZeroAddressError() (gas: 37240) -StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 343493, ~: 350217) +StakedEXATest:testSetSavingsZeroAddressError() (gas: 37218) +StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 343471, ~: 350195) StakedEXATest:testTotalSupplyWithdraw(uint256) (runs: 256, μ: 62073, ~: 62080) -StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 363143, ~: 370769) -StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 508606, ~: 508325) +StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 363144, ~: 370769) +StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 508611, ~: 508325) StakedEXATest:testWithdrawRewardUnderlyingAsset() (gas: 466609) StakedEXATest:testWithdrawRewardsOnlyAdmin() (gas: 227940) StakedEXATest:testWithdrawRewardsOnlyReward() (gas: 1109495) -StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1063400, ~: 1131612) -StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 867913, ~: 867632) +StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1062717, ~: 1131564) +StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 867896, ~: 867610) StakedEXATest:testZeroRateError() (gas: 58048) StakingPreviewerTest:testAllClaimable() (gas: 452427) StakingPreviewerTest:testAllClaimed() (gas: 655179) diff --git a/contracts/StakedEXA.sol b/contracts/StakedEXA.sol index 10deff60..7b5096d0 100644 --- a/contracts/StakedEXA.sol +++ b/contracts/StakedEXA.sol @@ -444,6 +444,8 @@ contract StakedEXA is reward.safeTransfer(savings, (finishAt - block.timestamp) * rewards[reward].rate); } + if (reward == IERC20(address(market.asset()))) setProvider(address(0)); + emit DistributionFinished(reward, msg.sender); } @@ -482,7 +484,6 @@ contract StakedEXA is /// @notice Sets the provider address. /// @param provider_ The new provider address. function setProvider(address provider_) public onlyRole(DEFAULT_ADMIN_ROLE) { - if (provider_ == address(0)) revert ZeroAddress(); provider = provider_; emit ProviderSet(provider_, msg.sender); } diff --git a/test/StakedEXA.t.sol b/test/StakedEXA.t.sol index a0b656f3..ee3dbd8e 100644 --- a/test/StakedEXA.t.sol +++ b/test/StakedEXA.t.sol @@ -8,6 +8,7 @@ import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.so import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import { ERC20, ERC4626, IERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import { IERC20Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; import { @@ -426,7 +427,7 @@ contract StakedEXATest is Test { uint256 savings = stEXA.market().maxWithdraw(SAVINGS); try stEXA.harvest() {} catch {} // solhint-disable-line no-empty-blocks (uint256 rDuration, , , , ) = stEXA.rewards(asset); - if (rDuration != 0 && assets.mulWadDown(providerRatio) >= rDuration) { + if (rDuration != 0 && assets.mulWadDown(providerRatio) >= rDuration && stEXA.provider() != address(0)) { assertEq(stEXA.market().balanceOf(PROVIDER), 0, "assets left"); assertEq( stEXA.market().maxWithdraw(SAVINGS), @@ -1410,11 +1411,6 @@ contract StakedEXATest is Test { assertEq(stEXA.savings(), newSavings); } - function testSetProviderZeroAddressError() external { - vm.expectRevert(ZeroAddress.selector); - stEXA.setProvider(address(0)); - } - function testSetSavingsZeroAddressError() external { vm.expectRevert(ZeroAddress.selector); stEXA.setSavings(address(0)); @@ -1845,6 +1841,27 @@ contract StakedEXATest is Test { assertEq(exa.balanceOf(SAVINGS), initialAmount); } + function testHarvestWhenFinished() external { + skip(duration / 2); + stEXA.harvest(); + + uint256 timestamp = block.timestamp; + + (, uint256 finishAt, , , ) = stEXA.rewards(providerAsset); + assertNotEq(finishAt, timestamp); + + stEXA.finishDistribution(providerAsset); + + (, finishAt, , , ) = stEXA.rewards(providerAsset); + assertEq(finishAt, timestamp, "finishAt != timestamp"); + assertEq(stEXA.provider(), address(0)); + + vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidApprover.selector, address(0))); + stEXA.harvest(); + (, finishAt, , , ) = stEXA.rewards(providerAsset); + assertEq(finishAt, timestamp); + } + function minMaxWithdrawAllowance() internal view returns (uint256) { return Math.min(market.convertToAssets(market.allowance(PROVIDER, address(stEXA))), market.maxWithdraw(PROVIDER)); }