From a413f4cbbb8938101d02cca5476e083781b78022 Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Thu, 5 Dec 2024 18:29:43 +0100 Subject: [PATCH] feat(RewardsStreamerMP): rewardsBalanceOf uses account's accrued + pending MPs --- .gas-report | 92 ++++++++++----------- .gas-snapshot | 108 ++++++++++++------------- certora/specs/EmergencyMode.spec | 6 +- certora/specs/RewardsStreamerMP.spec | 4 +- src/RewardsStreamerMP.sol | 115 +++++++++++++++++++-------- test/RewardsStreamerMP.t.sol | 13 +-- 6 files changed, 192 insertions(+), 146 deletions(-) diff --git a/.gas-report b/.gas-report index e06b3d1..554bb00 100644 --- a/.gas-report +++ b/.gas-report @@ -1,9 +1,9 @@ | script/DeployRewardsStreamerMP.s.sol:DeployRewardsStreamerMPScript contract | | | | | | |-----------------------------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 6200489 | 29703 | | | | | +| 6303045 | 30177 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 5302330 | 5302330 | 5302330 | 5302330 | 63 | +| run | 5397428 | 5397428 | 5397428 | 5397428 | 63 | | script/DeploymentConfig.s.sol:DeploymentConfig contract | | | | | | @@ -17,9 +17,9 @@ | script/UpgradeRewardsStreamerMP.s.sol:UpgradeRewardsStreamerMPScript contract | | | | | | |-------------------------------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 2845624 | 14078 | | | | | +| 2948151 | 14552 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 2383214 | 2383214 | 2383214 | 2383214 | 3 | +| run | 2478266 | 2478266 | 2478266 | 2478266 | 3 | | src/RewardsStreamer.sol:RewardsStreamer contract | | | | | | @@ -39,77 +39,77 @@ | src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | | |------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 2525687 | 11639 | | | | | +| 2628212 | 12113 | | | | | | Function Name | min | avg | median | max | # calls | | MAX_LOCKUP_PERIOD | 349 | 349 | 349 | 349 | 6 | -| MAX_MULTIPLIER | 273 | 273 | 273 | 273 | 33 | +| MAX_MULTIPLIER | 251 | 251 | 251 | 251 | 33 | | MIN_LOCKUP_PERIOD | 297 | 297 | 297 | 297 | 15 | | MP_RATE_PER_YEAR | 253 | 253 | 253 | 253 | 9 | | STAKING_TOKEN | 428 | 2036 | 2428 | 2428 | 322 | | emergencyModeEnabled | 2420 | 2420 | 2420 | 2420 | 7 | | enableEmergencyMode | 2485 | 19392 | 24677 | 24677 | 8 | | getAccount | 1661 | 1661 | 1661 | 1661 | 72 | -| getStakedBalance | 2607 | 2607 | 2607 | 2607 | 1 | -| getUserTotalMaxMP | 3166 | 3166 | 3166 | 3166 | 1 | +| getStakedBalance | 2629 | 2629 | 2629 | 2629 | 1 | +| getUserTotalMaxMP | 3123 | 3123 | 3123 | 3123 | 1 | | getUserTotalStakedBalance | 15162 | 15162 | 15162 | 15162 | 1 | | getUserVaults | 5201 | 5201 | 5201 | 5201 | 4 | -| initialize | 115589 | 115589 | 115589 | 115589 | 65 | +| initialize | 115611 | 115611 | 115611 | 115611 | 65 | | lastRewardTime | 395 | 1395 | 1395 | 2395 | 2 | -| leave | 56244 | 56244 | 56244 | 56244 | 1 | -| lock | 12063 | 34172 | 16480 | 73975 | 3 | +| leave | 59897 | 59897 | 59897 | 59897 | 1 | +| lock | 12063 | 35390 | 16480 | 77628 | 3 | | mpBalanceOfUser | 9185 | 9185 | 9185 | 9185 | 1 | -| proxiableUUID | 376 | 376 | 376 | 376 | 3 | +| proxiableUUID | 353 | 353 | 353 | 353 | 3 | | registerVault | 55866 | 72766 | 72966 | 72966 | 257 | -| rewardEndTime | 351 | 1351 | 1351 | 2351 | 2 | -| rewardStartTime | 396 | 1396 | 1396 | 2396 | 2 | -| rewardsBalanceOf | 1362 | 1362 | 1362 | 1362 | 4 | -| setReward | 2583 | 50892 | 60278 | 102595 | 7 | -| setTrustedCodehash | 24221 | 24282 | 24221 | 26221 | 65 | -| stake | 131124 | 170244 | 177941 | 198274 | 66 | -| totalMPAccrued | 351 | 351 | 351 | 351 | 81 | -| totalMaxMP | 395 | 395 | 395 | 395 | 81 | +| rewardEndTime | 373 | 1373 | 1373 | 2373 | 2 | +| rewardStartTime | 352 | 1352 | 1352 | 2352 | 2 | +| rewardsBalanceOf | 3231 | 6627 | 7074 | 7341 | 8 | +| setReward | 2583 | 58371 | 86341 | 105731 | 7 | +| setTrustedCodehash | 24243 | 24304 | 24243 | 26243 | 65 | +| stake | 134799 | 172378 | 179192 | 199525 | 66 | +| totalMPAccrued | 395 | 395 | 395 | 395 | 81 | +| totalMaxMP | 350 | 350 | 350 | 350 | 81 | | totalRewardsAccrued | 351 | 351 | 351 | 351 | 3 | -| totalRewardsSupply | 960 | 1921 | 1724 | 6700 | 30 | +| totalRewardsSupply | 1025 | 1984 | 1806 | 6765 | 30 | | totalStaked | 374 | 374 | 374 | 374 | 82 | -| unstake | 60360 | 60897 | 60360 | 63855 | 13 | -| updateAccountMP | 15348 | 18429 | 17854 | 34954 | 21 | -| updateGlobalState | 11088 | 28045 | 25249 | 110284 | 21 | -| upgradeToAndCall | 3203 | 7892 | 8449 | 10914 | 5 | +| unstake | 64035 | 64572 | 64035 | 67530 | 13 | +| updateAccountMP | 15392 | 17634 | 17898 | 17898 | 19 | +| updateGlobalState | 14317 | 26623 | 28594 | 28594 | 19 | +| upgradeToAndCall | 3225 | 7901 | 8448 | 10936 | 5 | | src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | | |------------------------------------------------------|-----------------|-------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 256445 | 1263 | | | | | +| 256467 | 1263 | | | | | | Function Name | min | avg | median | max | # calls | | MAX_LOCKUP_PERIOD | 776 | 3776 | 5276 | 5276 | 6 | -| MAX_MULTIPLIER | 700 | 1927 | 700 | 5200 | 33 | +| MAX_MULTIPLIER | 678 | 1905 | 678 | 5178 | 33 | | MIN_LOCKUP_PERIOD | 724 | 3424 | 5224 | 5224 | 15 | | MP_RATE_PER_YEAR | 680 | 1180 | 680 | 5180 | 9 | | STAKING_TOKEN | 855 | 6083 | 7355 | 7355 | 322 | | emergencyModeEnabled | 7347 | 7347 | 7347 | 7347 | 7 | | enableEmergencyMode | 28480 | 45381 | 50665 | 50665 | 8 | | getAccount | 2115 | 2115 | 2115 | 2115 | 72 | -| getStakedBalance | 7537 | 7537 | 7537 | 7537 | 1 | -| getUserTotalMaxMP | 3596 | 3596 | 3596 | 3596 | 1 | +| getStakedBalance | 7559 | 7559 | 7559 | 7559 | 1 | +| getUserTotalMaxMP | 3553 | 3553 | 3553 | 3553 | 1 | | getUserTotalStakedBalance | 15592 | 15592 | 15592 | 15592 | 1 | | getUserVaults | 5637 | 6762 | 5637 | 10137 | 4 | | implementation | 343 | 775 | 343 | 2343 | 412 | | lastRewardTime | 822 | 1822 | 1822 | 2822 | 2 | | mpBalanceOfUser | 9615 | 9615 | 9615 | 9615 | 1 | -| rewardEndTime | 778 | 1778 | 1778 | 2778 | 2 | -| rewardStartTime | 823 | 4073 | 4073 | 7323 | 2 | -| rewardsBalanceOf | 1792 | 1792 | 1792 | 1792 | 4 | -| setReward | 28863 | 77206 | 86636 | 128881 | 7 | -| setTrustedCodehash | 52867 | 52867 | 52867 | 52867 | 2 | -| totalMPAccrued | 778 | 778 | 778 | 778 | 81 | -| totalMaxMP | 822 | 822 | 822 | 822 | 81 | +| rewardEndTime | 800 | 1800 | 1800 | 2800 | 2 | +| rewardStartTime | 779 | 4029 | 4029 | 7279 | 2 | +| rewardsBalanceOf | 3661 | 7057 | 7504 | 7771 | 8 | +| setReward | 28863 | 84685 | 112699 | 132089 | 7 | +| setTrustedCodehash | 52889 | 52889 | 52889 | 52889 | 2 | +| totalMPAccrued | 822 | 822 | 822 | 822 | 81 | +| totalMaxMP | 777 | 777 | 777 | 777 | 81 | | totalRewardsAccrued | 778 | 778 | 778 | 778 | 3 | -| totalRewardsSupply | 1387 | 2498 | 2151 | 11627 | 30 | +| totalRewardsSupply | 1452 | 2561 | 2233 | 11692 | 30 | | totalStaked | 801 | 801 | 801 | 801 | 82 | -| updateAccountMP | 41707 | 44788 | 44213 | 61313 | 21 | -| updateGlobalState | 37076 | 54033 | 51237 | 136272 | 21 | -| upgradeToAndCall | 29846 | 33698 | 33698 | 37550 | 2 | +| updateAccountMP | 41751 | 43993 | 44257 | 44257 | 19 | +| updateGlobalState | 40305 | 52611 | 54582 | 54582 | 19 | +| upgradeToAndCall | 29868 | 33720 | 33720 | 37572 | 2 | | src/StakeVault.sol:StakeVault contract | | | | | | @@ -119,15 +119,15 @@ | Function Name | min | avg | median | max | # calls | | STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 | | emergencyExit | 36375 | 48879 | 48113 | 65213 | 7 | -| leave | 33507 | 131439 | 60635 | 370978 | 4 | -| lock | 33245 | 60677 | 50845 | 107772 | 4 | +| leave | 33507 | 132169 | 62097 | 370978 | 4 | +| lock | 33245 | 61590 | 50845 | 111425 | 4 | | owner | 2339 | 2339 | 2339 | 2339 | 257 | | register | 87015 | 103915 | 104115 | 104115 | 257 | -| stake | 33411 | 241532 | 252445 | 272826 | 67 | +| stake | 33411 | 243707 | 253696 | 274077 | 67 | | stakeManager | 368 | 368 | 368 | 368 | 257 | | trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 | -| unstake | 33282 | 96610 | 102040 | 109887 | 14 | -| withdraw | 42267 | 42267 | 42267 | 42267 | 1 | +| unstake | 33282 | 99918 | 105715 | 113562 | 14 | +| withdraw | 42289 | 42289 | 42289 | 42289 | 1 | | src/XPNFTToken.sol:XPNFTToken contract | | | | | | @@ -201,7 +201,7 @@ | test/mocks/MockToken.sol:MockToken contract | | | | | | |---------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 625454 | 3260 | | | | | +| 625370 | 3260 | | | | | | Function Name | min | avg | median | max | # calls | | approve | 46330 | 46339 | 46342 | 46342 | 262 | | balanceOf | 558 | 989 | 558 | 2558 | 139 | diff --git a/.gas-snapshot b/.gas-snapshot index f90eaca..057a621 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,75 +1,75 @@ EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92690) -EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 297737) -EmergencyExitTest:test_EmergencyExitBasic() (gas: 384498) -EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 659361) -EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 392416) -EmergencyExitTest:test_EmergencyExitWithLock() (gas: 391804) -EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 377359) +EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 298988) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 385748) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 664285) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 393667) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 393055) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 378609) EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39426) -IntegrationTest:testStakeFoo() (gas: 1178590) -LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 5626489) -LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 294868) -LeaveTest:test_TrustNewStakeManager() (gas: 5699124) -LockTest:test_LockFailsWithInvalidPeriod() (gas: 309953) +IntegrationTest:testStakeFoo() (gas: 1212412) +LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 5827658) +LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 296119) +LeaveTest:test_TrustNewStakeManager() (gas: 5898100) +LockTest:test_LockFailsWithInvalidPeriod() (gas: 311204) LockTest:test_LockFailsWithNoStake() (gas: 63730) -LockTest:test_LockWithoutPriorLock() (gas: 385979) -MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1745354) -MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 18952) +LockTest:test_LockWithoutPriorLock() (gas: 390861) +MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1746626) +MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 18908) MathTest:test_CalcAccrueMP() (gas: 22207) MathTest:test_CalcBonusMP() (gas: 17713) MathTest:test_CalcInitialMP() (gas: 5395) -MathTest:test_CalcMaxAccruedMP() (gas: 15630) -MathTest:test_CalcMaxTotalMP() (gas: 23386) -MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 716945) +MathTest:test_CalcMaxAccruedMP() (gas: 15586) +MathTest:test_CalcMaxTotalMP() (gas: 23298) +MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 725502) NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 85934) NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 58332) NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35804) NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 102512) NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 49555) NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35979) -RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 670804) -RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160318) +RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 486254) +RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160703) RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39404) RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39340) RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39375) -RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 610638) +RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 618489) RewardsStreamerTest:testStake() (gas: 869181) -StakeTest:test_StakeMultipleAccounts() (gas: 494527) -StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500466) -StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 830567) -StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 510971) -StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 533014) -StakeTest:test_StakeOneAccount() (gas: 276976) -StakeTest:test_StakeOneAccountAndRewards() (gas: 282924) -StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499543) -StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 493483) -StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 296831) -StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 298542) -StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 298609) +StakeTest:test_StakeMultipleAccounts() (gas: 499452) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 505390) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 842312) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 515852) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 537895) +StakeTest:test_StakeOneAccount() (gas: 278226) +StakeTest:test_StakeOneAccountAndRewards() (gas: 284173) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 507547) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 502149) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 298081) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 299770) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 299837) StakingTokenTest:testStakeToken() (gas: 10422) -UnstakeTest:test_StakeMultipleAccounts() (gas: 494549) -UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500488) -UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 830589) -UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 510970) -UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 533036) -UnstakeTest:test_StakeOneAccount() (gas: 276999) -UnstakeTest:test_StakeOneAccountAndRewards() (gas: 282968) -UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499587) -UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 493485) -UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 296831) -UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 298542) -UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 298608) -UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 537916) -UnstakeTest:test_UnstakeMultipleAccounts() (gas: 692641) -UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 786267) -UnstakeTest:test_UnstakeOneAccount() (gas: 472866) -UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 494544) -UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 404145) -UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 523087) -UpgradeTest:test_RevertWhenNotOwner() (gas: 2601964) -UpgradeTest:test_UpgradeStakeManager() (gas: 5541264) +UnstakeTest:test_StakeMultipleAccounts() (gas: 499474) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 505412) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 842334) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 515851) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 537917) +UnstakeTest:test_StakeOneAccount() (gas: 278249) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 284217) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 507591) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 502151) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 298081) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 299770) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 299836) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 546206) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 704915) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 800743) +UnstakeTest:test_UnstakeOneAccount() (gas: 479994) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 502856) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 409068) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 531333) +UpgradeTest:test_RevertWhenNotOwner() (gas: 2704653) +UpgradeTest:test_UpgradeStakeManager() (gas: 5740239) VaultRegistrationTest:test_VaultRegistration() (gas: 62013) -WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 310548) +WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 311821) XPNFTTokenTest:testApproveNotAllowed() (gas: 10500) XPNFTTokenTest:testGetApproved() (gas: 10523) XPNFTTokenTest:testIsApprovedForAll() (gas: 10698) diff --git a/certora/specs/EmergencyMode.spec b/certora/specs/EmergencyMode.spec index b46b49e..d50adf1 100644 --- a/certora/specs/EmergencyMode.spec +++ b/certora/specs/EmergencyMode.spec @@ -18,19 +18,21 @@ definition isViewFunction(method f) returns bool = ( f.selector == sig:streamer.owner().selector || f.selector == sig:streamer.totalStaked().selector || f.selector == sig:streamer.totalMaxMP().selector || - f.selector == sig:streamer.totalMP().selector || + f.selector == sig:streamer.totalMPAccrued().selector || f.selector == sig:streamer.accounts(address).selector || f.selector == sig:streamer.emergencyModeEnabled().selector || f.selector == sig:streamer.getStakedBalance(address).selector || f.selector == sig:streamer.getAccount(address).selector || f.selector == sig:streamer.rewardsBalanceOf(address).selector || + f.selector == sig:streamer.rewardsBalanceOfUser(address).selector || + f.selector == sig:streamer.pendingRewardIndex().selector || f.selector == sig:streamer.totalRewardsSupply().selector || - f.selector == sig:streamer.calculateAccountRewards(address).selector || f.selector == sig:streamer.lastRewardTime().selector || f.selector == sig:streamer.rewardAmount().selector || f.selector == sig:streamer.totalRewardsAccrued().selector || f.selector == sig:streamer.rewardStartTime().selector || f.selector == sig:streamer.rewardEndTime().selector || + f.selector == sig:streamer.mpBalanceOf(address).selector || f.selector == sig:streamer.mpBalanceOfUser(address).selector || f.selector == sig:streamer.getUserTotalMaxMP(address).selector || f.selector == sig:streamer.getUserTotalStakedBalance(address).selector || diff --git a/certora/specs/RewardsStreamerMP.spec b/certora/specs/RewardsStreamerMP.spec index df6fc3f..83e128b 100644 --- a/certora/specs/RewardsStreamerMP.spec +++ b/certora/specs/RewardsStreamerMP.spec @@ -121,9 +121,9 @@ rule MPsOnlyDecreaseWhenUnstaking(method f) filtered { f -> f.selector != sig:up env e; calldataarg args; - uint256 totalMPBefore = totalMP(e); + uint256 totalMPBefore = totalMPAccrued(e); f(e, args); - uint256 totalMPAfter = totalMP(e); + uint256 totalMPAfter = totalMPAccrued(e); assert totalMPAfter < totalMPBefore => f.selector == sig:unstake(uint256).selector || f.selector == sig:leave().selector; } diff --git a/src/RewardsStreamerMP.sol b/src/RewardsStreamerMP.sol index daa4bf4..c5e4e3b 100644 --- a/src/RewardsStreamerMP.sol +++ b/src/RewardsStreamerMP.sol @@ -332,15 +332,28 @@ contract RewardsStreamerMP is } function updateGlobalMP() internal { - if (totalMaxMP == 0) { + (uint256 adjustedRewardIndex, uint256 newTotalMPAccrued) = _pendingTotalMPAccrued(); + if (newTotalMPAccrued > totalMPAccrued) { + totalMPAccrued = newTotalMPAccrued; lastMPUpdatedTime = block.timestamp; - return; + } + + if (adjustedRewardIndex != rewardIndex) { + rewardIndex = adjustedRewardIndex; + } + } + + function _pendingTotalMPAccrued() internal view returns (uint256, uint256) { + uint256 adjustedRewardIndex = rewardIndex; + + if (totalMaxMP == 0) { + return (adjustedRewardIndex, totalMPAccrued); } uint256 currentTime = block.timestamp; uint256 timeDiff = currentTime - lastMPUpdatedTime; if (timeDiff == 0) { - return; + return (adjustedRewardIndex, totalMPAccrued); } uint256 accruedMP = (timeDiff * totalStaked * MP_RATE_PER_YEAR) / YEAR; @@ -348,17 +361,16 @@ contract RewardsStreamerMP is accruedMP = totalMaxMP - totalMPAccrued; } + uint256 newTotalMPAccrued = totalMPAccrued + accruedMP; + // Adjust rewardIndex before updating totalMP uint256 previousTotalWeight = totalStaked + totalMPAccrued; - totalMPAccrued += accruedMP; - - uint256 newTotalWeight = totalStaked + totalMPAccrued; - + uint256 newTotalWeight = totalStaked + newTotalMPAccrued; if (previousTotalWeight != 0 && newTotalWeight != previousTotalWeight) { - rewardIndex = (rewardIndex * previousTotalWeight) / newTotalWeight; + adjustedRewardIndex = (rewardIndex * previousTotalWeight) / newTotalWeight; } - lastMPUpdatedTime = currentTime; + return (adjustedRewardIndex, newTotalMPAccrued); } function setReward(uint256 amount, uint256 duration) external onlyOwner { @@ -402,14 +414,35 @@ contract RewardsStreamerMP is return 0; } - uint256 accruedRewards = (timeElapsed * rewardAmount) / duration; + uint256 accruedRewards = Math.mulDiv(timeElapsed, rewardAmount, duration); return accruedRewards; } function updateRewardIndex() internal { - uint256 totalWeight = totalStaked + totalMPAccrued; + uint256 accruedRewards; + uint256 newRewardIndex; + + (accruedRewards, newRewardIndex) = _pendingRewardIndex(); + totalRewardsAccrued += accruedRewards; + + if (newRewardIndex > rewardIndex) { + rewardIndex = newRewardIndex; + lastRewardTime = block.timestamp < rewardEndTime ? block.timestamp : rewardEndTime; + } + } + + function pendingRewardIndex() external view returns (uint256) { + uint256 newRewardIndex; + (, newRewardIndex) = _pendingRewardIndex(); + return newRewardIndex; + } + + function _pendingRewardIndex() internal view returns (uint256, uint256) { + (uint256 adjustedRewardIndex, uint256 newTotalMPAccrued) = _pendingTotalMPAccrued(); + uint256 totalWeight = totalStaked + newTotalMPAccrued; + if (totalWeight == 0) { - return; + return (0, rewardIndex); } uint256 currentTime = block.timestamp; @@ -417,20 +450,17 @@ contract RewardsStreamerMP is uint256 elapsedTime = applicableTime - lastRewardTime; if (elapsedTime == 0) { - return; + return (0, rewardIndex); } - uint256 newRewards = _calculatePendingRewards(); - if (newRewards == 0) { - return; + uint256 accruedRewards = _calculatePendingRewards(); + if (accruedRewards == 0) { + return (0, rewardIndex); } - totalRewardsAccrued += newRewards; - uint256 indexIncrease = Math.mulDiv(newRewards, SCALE_FACTOR, totalWeight); - if (indexIncrease > 0) { - rewardIndex += indexIncrease; - lastRewardTime = block.timestamp < rewardEndTime ? block.timestamp : rewardEndTime; - } + uint256 newRewardIndex = adjustedRewardIndex + Math.mulDiv(accruedRewards, SCALE_FACTOR, totalWeight); + + return (accruedRewards, newRewardIndex); } function _calculateBonusMP(uint256 amount, uint256 lockPeriod) internal pure returns (uint256) { @@ -467,15 +497,6 @@ contract RewardsStreamerMP is _updateAccountMP(accountAddress); } - function calculateAccountRewards(address accountAddress) public view returns (uint256) { - Account storage account = accounts[accountAddress]; - - uint256 accountWeight = account.stakedBalance + account.mpAccrued; - uint256 deltaRewardIndex = rewardIndex - account.accountRewardIndex; - - return Math.mulDiv(accountWeight, deltaRewardIndex, SCALE_FACTOR); - } - function enableEmergencyMode() external onlyOwner { if (emergencyModeEnabled) { revert StakingManager__EmergencyModeEnabled(); @@ -495,7 +516,35 @@ contract RewardsStreamerMP is return totalRewardsAccrued + _calculatePendingRewards(); } - function rewardsBalanceOf(address accountAddress) external view returns (uint256) { - return calculateAccountRewards(accountAddress); + function rewardsBalanceOf(address accountAddress) public view returns (uint256) { + uint256 newRewardIndex; + (, newRewardIndex) = _pendingRewardIndex(); + + Account storage account = accounts[accountAddress]; + + uint256 accountWeight = account.stakedBalance + _mpBalanceOf(accountAddress); + uint256 deltaRewardIndex = newRewardIndex - account.accountRewardIndex; + + return (accountWeight * deltaRewardIndex) / SCALE_FACTOR; + } + + function rewardsBalanceOfUser(address user) external view returns (uint256) { + address[] memory userVaults = vaults[user]; + uint256 userTotalRewards = 0; + + for (uint256 i = 0; i < userVaults.length; i++) { + userTotalRewards += rewardsBalanceOf(userVaults[i]); + } + + return userTotalRewards; + } + + function _mpBalanceOf(address accountAddress) internal view returns (uint256) { + Account storage account = accounts[accountAddress]; + return account.mpAccrued + _getAccountPendingdMP(account); + } + + function mpBalanceOf(address accountAddress) external view returns (uint256) { + return _mpBalanceOf(accountAddress); } } diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index 9abfff6..a56d849 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -2107,8 +2107,6 @@ contract RewardsStreamerMP_RewardsTest is RewardsStreamerMPTest { function testRewardsBalanceOf() public { assertEq(streamer.totalRewardsSupply(), 0); - vm.warp(0); - uint256 initialTime = vm.getBlockTimestamp(); _stake(alice, 100e18, 0); @@ -2120,22 +2118,19 @@ contract RewardsStreamerMP_RewardsTest is RewardsStreamerMPTest { vm.warp(initialTime + 1 days); - // FIXME: this is needed to update the global state and account MP - // Later we should update the functions to use "real-time" values. - streamer.updateGlobalState(); - streamer.updateAccountMP(vaults[alice]); - + uint256 liveBalanceBeforeGlobalUpdate = streamer.rewardsBalanceOf(vaults[alice]); uint256 tolerance = 300; // 300 wei assertEq(streamer.totalRewardsSupply(), 100e18, "Total rewards supply mismatch"); + assertEq(streamer.rewardsBalanceOf(vaults[alice]), liveBalanceBeforeGlobalUpdate); assertApproxEqAbs(streamer.rewardsBalanceOf(vaults[alice]), 100e18, tolerance); vm.warp(initialTime + 10 days); - streamer.updateGlobalState(); - streamer.updateAccountMP(vaults[alice]); + uint256 secondLiveBalanceBeforeGlobalUpdate = streamer.rewardsBalanceOf(vaults[alice]); assertEq(streamer.totalRewardsSupply(), 1000e18, "Total rewards supply mismatch"); + assertEq(streamer.rewardsBalanceOf(vaults[alice]), secondLiveBalanceBeforeGlobalUpdate); assertApproxEqAbs(streamer.rewardsBalanceOf(vaults[alice]), 1000e18, tolerance); } }