Skip to content

Commit

Permalink
🐛 staking: fix claim on deposit
Browse files Browse the repository at this point in the history
  • Loading branch information
itofarina committed Aug 8, 2024
1 parent 729de5c commit dae2b41
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 73 deletions.
121 changes: 61 additions & 60 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -461,90 +461,91 @@ 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: 43884)
StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1216598, ~: 1249206)
StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1203004, ~: 1235612)
StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 355442, ~: 362486)
StakedEXATest:testAvgIndex(uint256[3],uint256[2]) (runs: 256, μ: 1216267, ~: 1249579)
StakedEXATest:testAvgStartTime(uint256[3],uint256[2]) (runs: 256, μ: 1202673, ~: 1235985)
StakedEXATest:testBalanceOfDeposit(uint80) (runs: 256, μ: 355406, ~: 362451)
StakedEXATest:testBalanceOfWithdraw(uint256) (runs: 256, μ: 60558, ~: 60565)
StakedEXATest:testCanChangeRewardsDurationWhenDisabled() (gas: 168917)
StakedEXATest:testClaimAfterHarvest() (gas: 840843)
StakedEXATest:testClaimAndUnstake() (gas: 1486220)
StakedEXATest:testClaimAndWithdrawAfterRefTime() (gas: 1091230)
StakedEXATest:testClaimBeforeFirstHarvest() (gas: 539901)
StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 364348, ~: 364057)
StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 824806, ~: 742146)
StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1746056, ~: 1809047)
StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 35420, ~: 35711)
StakedEXATest:testClaimAfterHarvest() (gas: 840853)
StakedEXATest:testClaimAndUnstake() (gas: 1486466)
StakedEXATest:testClaimAndWithdrawAfterRefTime() (gas: 1091565)
StakedEXATest:testClaimBeforeFirstHarvest() (gas: 539888)
StakedEXATest:testDepositClaimsRewardsToReceiver() (gas: 1050445)
StakedEXATest:testDepositEvent(uint256) (runs: 256, μ: 364333, ~: 364044)
StakedEXATest:testDepositShouldClaim(uint256[2],uint32) (runs: 256, μ: 819344, ~: 742180)
StakedEXATest:testDepositWithdrawAvgStartTimeAndIndex(uint256[3],uint256,uint256[5]) (runs: 256, μ: 1748331, ~: 1809795)
StakedEXATest:testEarnedWithTime(uint256) (runs: 256, μ: 35436, ~: 35711)
StakedEXATest:testEmergencyAdminCanPauseNotUnpause() (gas: 159039)
StakedEXATest:testFinishDistributionEmitEvent() (gas: 402739)
StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1516928)
StakedEXATest:testFinishDistributionStopsEmission() (gas: 1496514)
StakedEXATest:testFinishDistributionThatAlreadyFinished() (gas: 436027)
StakedEXATest:testFinishDistributionEmitEvent() (gas: 402683)
StakedEXATest:testFinishDistributionLetsClaimUnclaimed() (gas: 1516914)
StakedEXATest:testFinishDistributionStopsEmission() (gas: 1496500)
StakedEXATest:testFinishDistributionThatAlreadyFinished() (gas: 436014)
StakedEXATest:testFinishDistributionTransfersRemainingToSavings() (gas: 112201)
StakedEXATest:testGrantRevokeEmergencyAdmin() (gas: 107216)
StakedEXATest:testGrantRevokePauser() (gas: 107191)
StakedEXATest:testHandlerClaim(uint8) (runs: 256, μ: 302723, ~: 302723)
StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 799412, ~: 810018)
StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 343765, ~: 342656)
StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 127301, ~: 123310)
StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 145620, ~: 164475)
StakedEXATest:testHandlerClaim(uint8) (runs: 256, μ: 302903, ~: 302903)
StakedEXATest:testHandlerDeposit(uint80) (runs: 256, μ: 798727, ~: 810004)
StakedEXATest:testHandlerHarvest(uint64) (runs: 256, μ: 343431, ~: 342608)
StakedEXATest:testHandlerNotifyRewardAmount(uint64) (runs: 256, μ: 127022, ~: 123274)
StakedEXATest:testHandlerSetDuration(uint32) (runs: 256, μ: 145941, ~: 164475)
StakedEXATest:testHandlerSetMarket() (gas: 157950)
StakedEXATest:testHandlerWithdraw(uint256) (runs: 256, μ: 69995, ~: 70002)
StakedEXATest:testHarvest() (gas: 197956)
StakedEXATest:testHarvestAmountWithReducedAllowance() (gas: 215375)
StakedEXATest:testHarvestEffectOnRewardData() (gas: 190979)
StakedEXATest:testHarvestEmitsRewardAmountNotified() (gas: 188932)
StakedEXATest:testHarvestZero() (gas: 236758)
StakedEXATest:testHandlerWithdraw(uint256) (runs: 256, μ: 69973, ~: 69980)
StakedEXATest:testHarvest() (gas: 197944)
StakedEXATest:testHarvestAmountWithReducedAllowance() (gas: 215363)
StakedEXATest:testHarvestEffectOnRewardData() (gas: 190945)
StakedEXATest:testHarvestEmitsRewardAmountNotified() (gas: 188920)
StakedEXATest:testHarvestZero() (gas: 236746)
StakedEXATest:testInitialValues() (gas: 89269)
StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 64231, ~: 64353)
StakedEXATest:testMaxRewardsGasConsumption() (gas: 138550598)
StakedEXATest:testMultipleClaimsVsOne() (gas: 26036290)
StakedEXATest:testMultipleHarvests() (gas: 461995)
StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1569892, ~: 1576164)
StakedEXATest:testInsufficientBalanceError(uint256) (runs: 256, μ: 64211, ~: 64353)
StakedEXATest:testMaxRewardsGasConsumption() (gas: 138555933)
StakedEXATest:testMultipleClaimsVsOne() (gas: 26048842)
StakedEXATest:testMultipleHarvests() (gas: 461971)
StakedEXATest:testNoRewardsAfterPeriod(uint256) (runs: 256, μ: 1569438, ~: 1576150)
StakedEXATest:testNotPausingRoleError() (gas: 39481)
StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 129980, ~: 129930)
StakedEXATest:testNotifyRewardWithUnderlyingAsset() (gas: 497622)
StakedEXATest:testOnlyAdminEnableReward() (gas: 1199188)
StakedEXATest:testNotifyRewardAmount(uint256,uint256) (runs: 256, μ: 129968, ~: 129918)
StakedEXATest:testNotifyRewardWithUnderlyingAsset() (gas: 497597)
StakedEXATest:testOnlyAdminEnableReward() (gas: 1199166)
StakedEXATest:testOnlyAdminFinishDistribution() (gas: 191108)
StakedEXATest:testOnlyAdminNotifyRewardAmount() (gas: 203509)
StakedEXATest:testOnlyAdminNotifyRewardAmount() (gas: 203497)
StakedEXATest:testOnlyAdminSetProvider() (gas: 143746)
StakedEXATest:testOnlyAdminSetProviderRatio() (gas: 143405)
StakedEXATest:testOnlyAdminSetRewardsDuration() (gas: 153043)
StakedEXATest:testOnlyAdminSetSavings() (gas: 141186)
StakedEXATest:testPausable() (gas: 1154779)
StakedEXATest:testPausableClaim() (gas: 639349)
StakedEXATest:testPausableHarvest() (gas: 344638)
StakedEXATest:testPauserCanPauseUnpause() (gas: 157789)
StakedEXATest:testPausable() (gas: 1154861)
StakedEXATest:testPausableClaim() (gas: 639455)
StakedEXATest:testPausableHarvest() (gas: 344626)
StakedEXATest:testPauserCanPauseUnpause() (gas: 157877)
StakedEXATest:testPenaltyGrowthRange() (gas: 67233)
StakedEXATest:testPenaltyThresholdRange() (gas: 37156)
StakedEXATest:testPermitAndDeposit() (gas: 373099)
StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1020703, ~: 1020403)
StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 104690, ~: 105555)
StakedEXATest:testPermitAndDeposit() (gas: 373086)
StakedEXATest:testResetDepositAfterRefTime(uint256) (runs: 256, μ: 1020687, ~: 1020389)
StakedEXATest:testRewardAmountNotifiedEvent(uint256) (runs: 256, μ: 104741, ~: 105543)
StakedEXATest:testRewardNotListedError() (gas: 1109565)
StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 810710, ~: 752450)
StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1577010, ~: 1576599)
StakedEXATest:testRewardsDurationSetEvent(uint40) (runs: 256, μ: 52074, ~: 52056)
StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 58982, ~: 59226)
StakedEXATest:testRewardPaidEvent(uint256,uint256) (runs: 256, μ: 813598, ~: 862521)
StakedEXATest:testRewardsAmounts(uint256) (runs: 256, μ: 1576995, ~: 1576585)
StakedEXATest:testRewardsDurationSetEvent(uint40) (runs: 256, μ: 52072, ~: 52056)
StakedEXATest:testSetDuration(uint256,uint40) (runs: 256, μ: 58965, ~: 59226)
StakedEXATest:testSetMarketAddressZero() (gas: 37124)
StakedEXATest:testSetMarketOnlyAdmin() (gas: 1300351)
StakedEXATest:testSetMaxRewardsTokensExceeded() (gas: 104946678)
StakedEXATest:testSetMinTime() (gas: 81960)
StakedEXATest:testSetPenaltyGrowth() (gas: 82125)
StakedEXATest:testSetPenaltyGrowth() (gas: 82148)
StakedEXATest:testSetPenaltyThreshold() (gas: 81925)
StakedEXATest:testSetProviderRatioOverOneError() (gas: 37156)
StakedEXATest:testSetProviderZeroAddressError() (gas: 37175)
StakedEXATest:testSetSavingsZeroAddressError() (gas: 37284)
StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 354935, ~: 361979)
StakedEXATest:testSetProviderRatioOverOneError() (gas: 37179)
StakedEXATest:testSetProviderZeroAddressError() (gas: 37131)
StakedEXATest:testSetSavingsZeroAddressError() (gas: 37240)
StakedEXATest:testTotalSupplyDeposit(uint80) (runs: 256, μ: 354921, ~: 361966)
StakedEXATest:testTotalSupplyWithdraw(uint256) (runs: 256, μ: 62006, ~: 62013)
StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 374588, ~: 382531)
StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 520341, ~: 520046)
StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1073469, ~: 1143214)
StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 879758, ~: 879463)
StakedEXATest:testUntransferable(uint80) (runs: 256, μ: 374574, ~: 382518)
StakedEXATest:testWithdrawEvent(uint256) (runs: 256, μ: 520326, ~: 520033)
StakedEXATest:testWithdrawSameAmountRewardsShouldEqual(uint256,uint256) (runs: 256, μ: 1076739, ~: 1143249)
StakedEXATest:testWithdrawWithRewards(uint256) (runs: 256, μ: 879743, ~: 879450)
StakedEXATest:testZeroRateError() (gas: 58047)
StakingPreviewerTest:testAllClaimable() (gas: 431264)
StakingPreviewerTest:testAllClaimed() (gas: 632779)
StakingPreviewerTest:testAllEarned() (gas: 316370)
StakingPreviewerTest:testAllRewards() (gas: 485771)
StakingPreviewerTest:testStaking() (gas: 528491)
StakingPreviewerTest:testAllClaimable() (gas: 431263)
StakingPreviewerTest:testAllClaimed() (gas: 633003)
StakingPreviewerTest:testAllEarned() (gas: 316369)
StakingPreviewerTest:testAllRewards() (gas: 485770)
StakingPreviewerTest:testStaking() (gas: 528490)
SwapperTest:testSwapBasic() (gas: 216831)
SwapperTest:testSwapWithAllowance() (gas: 481530)
SwapperTest:testSwapWithInaccurateSlippageSendsETHToAccount() (gas: 297968)
Expand Down
26 changes: 13 additions & 13 deletions contracts/StakedEXA.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ contract StakedEXA is
if (balance != 0) claimWithdraw(reward, to, balance);
avgIndexes[to][reward] = rewards[reward].index;
} else {
if (balance != 0) claim_(reward);
if (balance != 0) claim_(reward, to);
uint256 numerator = avgIndexes[to][reward] * balance + rewards[reward].index * amount;
avgIndexes[to][reward] = numerator == 0 ? 0 : (numerator - 1) / total + 1;
}
Expand Down Expand Up @@ -367,44 +367,44 @@ contract StakedEXA is

/// @notice Internal function to claim rewards.
/// @param reward The reward token.
function claim_(IERC20 reward) internal whenNotPaused {
uint256 time = block.timestamp * 1e18 - avgStart[msg.sender];
function claim_(IERC20 reward, address account) internal whenNotPaused {
uint256 time = block.timestamp * 1e18 - avgStart[account];
if (time <= minTime * 1e18) return;

uint256 claimedAmount = claimed[msg.sender][reward];
uint256 claimedAmount = claimed[account][reward];
// due to excess exposure
uint256 claimableAmount = Math.max(rawClaimable(reward, msg.sender, balanceOf(msg.sender)), claimedAmount);
uint256 claimableAmount = Math.max(rawClaimable(reward, account, balanceOf(account)), claimedAmount);
uint256 claimAmount = claimableAmount - claimedAmount;

if (claimAmount != 0) claimed[msg.sender][reward] = claimedAmount + claimAmount;
if (claimAmount != 0) claimed[account][reward] = claimedAmount + claimAmount;

if (time > refTime * 1e18) {
uint256 rawEarned = earned(reward, msg.sender, balanceOf(msg.sender));
uint256 savedAmount = saved[msg.sender][reward];
uint256 rawEarned = earned(reward, account, balanceOf(account));
uint256 savedAmount = saved[account][reward];
uint256 maxClaimed = Math.min(rawEarned, claimableAmount);
uint256 saveAmount = rawEarned > maxClaimed + savedAmount ? rawEarned - maxClaimed - savedAmount : 0;

if (saveAmount != 0) {
saved[msg.sender][reward] = savedAmount + saveAmount;
saved[account][reward] = savedAmount + saveAmount;
reward.safeTransfer(savings, saveAmount);
}
}
if (claimAmount != 0) {
reward.safeTransfer(msg.sender, claimAmount);
emit RewardPaid(reward, msg.sender, claimAmount);
reward.safeTransfer(account, claimAmount);
emit RewardPaid(reward, account, claimAmount);
}
}

/// @notice Claims rewards for a specific reward token.
/// @param reward The reward token.
function claim(IERC20 reward) external {
claim_(reward);
claim_(reward, msg.sender);
}

/// @notice Claims rewards for all reward tokens.
function claimAll() external {
for (uint256 i = 0; i < rewardsTokens.length; ++i) {
claim_(rewardsTokens[i]);
claim_(rewardsTokens[i], msg.sender);
}
}

Expand Down
20 changes: 20 additions & 0 deletions test/StakedEXA.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1711,6 +1711,26 @@ contract StakedEXATest is Test {
assertLt(vm.lastCallGas().gasTotalUsed, 10_000_000);
}

function testDepositClaimsRewardsToReceiver() external {
uint256 assets = 1_000e18;
exa.mint(address(this), assets);
stEXA.deposit(assets, address(this));

skip(minTime + 1);

uint256 claimableAmount = claimable(rA, address(this));
uint256 balanceBefore = rA.balanceOf(address(this));

exa.mint(BOB, assets);
vm.startPrank(BOB);
exa.approve(address(stEXA), assets);
stEXA.deposit(assets, address(this));
vm.stopPrank();

assertEq(claimableAmount, rA.balanceOf(address(this)) - balanceBefore);

}

function minMaxWithdrawAllowance() internal view returns (uint256) {
return Math.min(market.convertToAssets(market.allowance(PROVIDER, address(stEXA))), market.maxWithdraw(PROVIDER));
}
Expand Down

0 comments on commit dae2b41

Please sign in to comment.