From c8ea850f720593d443f376a771fe1a6d0cb04eca Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Sun, 1 Dec 2024 11:15:59 +0100 Subject: [PATCH] refactor: move vault registrations into dedicated registry contract --- .gas-report | 140 +++++++++++++------------ .gas-snapshot | 116 ++++++++++---------- certora/confs/RewardsStreamerMP.conf | 2 + certora/specs/EmergencyMode.spec | 9 +- src/RewardsStreamerMP.sol | 45 ++------ src/StakeVaultRegistry.sol | 48 +++++++++ src/interfaces/IStakeVaultRegistry.sol | 8 ++ test/RewardsStreamerMP.t.sol | 10 +- test/StakeVault.test.sol | 11 +- 9 files changed, 220 insertions(+), 169 deletions(-) create mode 100644 src/StakeVaultRegistry.sol create mode 100644 src/interfaces/IStakeVaultRegistry.sol diff --git a/.gas-report b/.gas-report index 74dbf57..9b249ec 100644 --- a/.gas-report +++ b/.gas-report @@ -15,99 +15,107 @@ | src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | | |------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 2506514 | 11550 | | | | | +| 2603716 | 12006 | | | | | | Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 249 | 249 | 249 | 249 | 23 | -| MAX_MULTIPLIER | 273 | 273 | 273 | 273 | 30 | +| MAX_LOCKUP_PERIOD | 294 | 294 | 294 | 294 | 23 | +| MAX_MULTIPLIER | 251 | 251 | 251 | 251 | 30 | | MIN_LOCKUP_PERIOD | 252 | 252 | 252 | 252 | 11 | | MP_RATE_PER_YEAR | 253 | 253 | 253 | 253 | 3 | -| SCALE_FACTOR | 295 | 295 | 295 | 295 | 41 | -| STAKING_TOKEN | 2428 | 2428 | 2428 | 2428 | 292 | -| emergencyModeEnabled | 2420 | 2420 | 2420 | 2420 | 7 | -| enableEmergencyMode | 2485 | 19392 | 24677 | 24677 | 8 | -| getAccount | 1661 | 1661 | 1661 | 1661 | 72 | -| getStakedBalance | 2586 | 2586 | 2586 | 2586 | 1 | -| getUserTotalMP | 9186 | 9186 | 9186 | 9186 | 1 | -| getUserTotalMaxMP | 3145 | 3145 | 3145 | 3145 | 1 | -| getUserTotalStakedBalance | 15118 | 15118 | 15118 | 15118 | 1 | -| getUserVaults | 5201 | 5201 | 5201 | 5201 | 4 | -| initialize | 115633 | 115633 | 115633 | 115633 | 59 | +| SCALE_FACTOR | 273 | 273 | 273 | 273 | 41 | +| STAKING_TOKEN | 2404 | 2404 | 2404 | 2404 | 292 | +| emergencyModeEnabled | 2398 | 2398 | 2398 | 2398 | 7 | +| enableEmergencyMode | 2439 | 19346 | 24631 | 24631 | 8 | +| getAccount | 1615 | 1615 | 1615 | 1615 | 72 | +| getStakedBalance | 2562 | 2562 | 2562 | 2562 | 1 | +| getUserTotalMP | 11307 | 11307 | 11307 | 11307 | 1 | +| getUserTotalMaxMP | 5266 | 5266 | 5266 | 5266 | 1 | +| getUserTotalStakedBalance | 17261 | 17261 | 17261 | 17261 | 1 | +| initialize | 137804 | 137804 | 137804 | 137804 | 59 | | isTrustedCodehash | 541 | 541 | 541 | 541 | 231 | -| lastRewardTime | 395 | 1395 | 1395 | 2395 | 2 | -| leave | 56568 | 56568 | 56568 | 56568 | 1 | -| lock | 12063 | 34234 | 16392 | 74247 | 3 | -| proxiableUUID | 353 | 353 | 353 | 353 | 3 | -| registerVault | 55888 | 72767 | 72988 | 72988 | 233 | +| lastRewardTime | 351 | 1351 | 1351 | 2351 | 2 | +| leave | 56601 | 56601 | 56601 | 56601 | 1 | +| lock | 17430 | 39599 | 21759 | 79608 | 3 | +| proxiableUUID | 319 | 319 | 319 | 319 | 3 | +| registerVault | 63452 | 80331 | 80552 | 80552 | 233 | | rewardEndTime | 395 | 1395 | 1395 | 2395 | 2 | | rewardStartTime | 374 | 1374 | 1374 | 2374 | 2 | -| rewardsBalanceOf | 1316 | 1316 | 1316 | 1316 | 4 | -| setReward | 2583 | 50897 | 60278 | 102595 | 7 | -| setTrustedCodehash | 26198 | 26198 | 26198 | 26198 | 59 | -| stake | 131233 | 170386 | 178050 | 198529 | 66 | -| totalMP | 395 | 395 | 395 | 395 | 81 | -| totalMaxMP | 372 | 372 | 372 | 372 | 81 | -| totalRewardsAccrued | 373 | 373 | 373 | 373 | 3 | -| totalRewardsSupply | 960 | 1921 | 1724 | 6700 | 30 | -| totalStaked | 351 | 351 | 351 | 351 | 82 | -| unstake | 60728 | 61276 | 60728 | 64291 | 13 | -| updateAccountMP | 15420 | 18498 | 17922 | 35022 | 21 | -| updateGlobalState | 11088 | 28116 | 25337 | 110317 | 21 | -| upgradeToAndCall | 3180 | 9356 | 10903 | 10903 | 5 | +| rewardsBalanceOf | 1292 | 1292 | 1292 | 1292 | 4 | +| setReward | 2582 | 50896 | 60277 | 102594 | 7 | +| setTrustedCodehash | 26174 | 26174 | 26174 | 26174 | 59 | +| stake | 136594 | 175747 | 183411 | 203890 | 66 | +| totalMP | 373 | 373 | 373 | 373 | 81 | +| totalMaxMP | 350 | 350 | 350 | 350 | 81 | +| totalRewardsAccrued | 351 | 351 | 351 | 351 | 3 | +| totalRewardsSupply | 981 | 1942 | 1745 | 6721 | 30 | +| totalStaked | 374 | 374 | 374 | 374 | 82 | +| unstake | 66111 | 66659 | 66111 | 69674 | 13 | +| updateAccountMP | 15440 | 18518 | 17942 | 35042 | 21 | +| updateGlobalState | 11066 | 28094 | 25315 | 110295 | 21 | +| upgradeToAndCall | 3188 | 9307 | 10832 | 10854 | 5 | | src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | | |------------------------------------------------------|-----------------|-------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 256101 | 1231 | | | | | +| 278660 | 1263 | | | | | | Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 676 | 1458 | 676 | 5176 | 23 | -| MAX_MULTIPLIER | 700 | 1600 | 700 | 5200 | 30 | +| MAX_LOCKUP_PERIOD | 721 | 1503 | 721 | 5221 | 23 | +| MAX_MULTIPLIER | 678 | 1578 | 678 | 5178 | 30 | | MIN_LOCKUP_PERIOD | 679 | 3951 | 5179 | 5179 | 11 | | MP_RATE_PER_YEAR | 680 | 680 | 680 | 680 | 3 | -| SCALE_FACTOR | 722 | 722 | 722 | 722 | 41 | -| STAKING_TOKEN | 7355 | 7355 | 7355 | 7355 | 292 | -| emergencyModeEnabled | 7347 | 7347 | 7347 | 7347 | 7 | -| enableEmergencyMode | 28480 | 45381 | 50665 | 50665 | 8 | -| getAccount | 2115 | 2115 | 2115 | 2115 | 72 | -| getStakedBalance | 7516 | 7516 | 7516 | 7516 | 1 | -| getUserTotalMP | 9616 | 9616 | 9616 | 9616 | 1 | -| getUserTotalMaxMP | 3575 | 3575 | 3575 | 3575 | 1 | -| getUserTotalStakedBalance | 15548 | 15548 | 15548 | 15548 | 1 | -| getUserVaults | 5637 | 6762 | 5637 | 10137 | 4 | +| SCALE_FACTOR | 700 | 700 | 700 | 700 | 41 | +| STAKING_TOKEN | 7331 | 7331 | 7331 | 7331 | 292 | +| emergencyModeEnabled | 7325 | 7325 | 7325 | 7325 | 7 | +| enableEmergencyMode | 28434 | 45335 | 50619 | 50619 | 8 | +| getAccount | 2069 | 2069 | 2069 | 2069 | 72 | +| getStakedBalance | 7492 | 7492 | 7492 | 7492 | 1 | +| getUserTotalMP | 11737 | 11737 | 11737 | 11737 | 1 | +| getUserTotalMaxMP | 5696 | 5696 | 5696 | 5696 | 1 | +| getUserTotalStakedBalance | 17691 | 17691 | 17691 | 17691 | 1 | | implementation | 343 | 808 | 343 | 2343 | 382 | | isTrustedCodehash | 971 | 971 | 971 | 971 | 231 | -| lastRewardTime | 822 | 1822 | 1822 | 2822 | 2 | +| lastRewardTime | 778 | 1778 | 1778 | 2778 | 2 | | rewardEndTime | 822 | 1822 | 1822 | 2822 | 2 | | rewardStartTime | 801 | 4051 | 4051 | 7301 | 2 | -| rewardsBalanceOf | 1746 | 1746 | 1746 | 1746 | 4 | -| setReward | 28863 | 77211 | 86636 | 128881 | 7 | -| setTrustedCodehash | 52844 | 52844 | 52844 | 52844 | 59 | -| totalMP | 822 | 822 | 822 | 822 | 81 | -| totalMaxMP | 799 | 799 | 799 | 799 | 81 | -| totalRewardsAccrued | 800 | 800 | 800 | 800 | 3 | -| totalRewardsSupply | 1387 | 2498 | 2151 | 11627 | 30 | -| totalStaked | 778 | 778 | 778 | 778 | 82 | -| updateAccountMP | 41779 | 44857 | 44281 | 61381 | 21 | -| updateGlobalState | 37076 | 54104 | 51325 | 136305 | 21 | -| upgradeToAndCall | 29823 | 35993 | 37539 | 37539 | 5 | +| rewardsBalanceOf | 1722 | 1722 | 1722 | 1722 | 4 | +| setReward | 28862 | 77210 | 86635 | 128880 | 7 | +| setTrustedCodehash | 52820 | 52820 | 52820 | 52820 | 59 | +| totalMP | 800 | 800 | 800 | 800 | 81 | +| totalMaxMP | 777 | 777 | 777 | 777 | 81 | +| totalRewardsAccrued | 778 | 778 | 778 | 778 | 3 | +| totalRewardsSupply | 1408 | 2519 | 2172 | 11648 | 30 | +| totalStaked | 801 | 801 | 801 | 801 | 82 | +| updateAccountMP | 41799 | 44877 | 44301 | 61401 | 21 | +| updateGlobalState | 37054 | 54082 | 51303 | 136283 | 21 | +| upgradeToAndCall | 29819 | 35933 | 37456 | 37478 | 5 | | src/StakeVault.sol:StakeVault contract | | | | | | |----------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 1420425 | 6695 | | | | | +| 1420401 | 6695 | | | | | | Function Name | min | avg | median | max | # calls | | STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 | -| emergencyExit | 36375 | 48879 | 48113 | 65213 | 7 | -| leave | 33507 | 131504 | 60765 | 370978 | 4 | -| lock | 33245 | 60723 | 50801 | 108044 | 4 | +| emergencyExit | 36353 | 48857 | 48091 | 65191 | 7 | +| leave | 33507 | 131510 | 60778 | 370978 | 4 | +| lock | 33245 | 64746 | 56168 | 113405 | 4 | | owner | 2339 | 2339 | 2339 | 2339 | 233 | -| register | 87037 | 103916 | 104137 | 104137 | 233 | -| stake | 33411 | 241673 | 252554 | 273081 | 67 | +| register | 94601 | 111480 | 111701 | 111701 | 233 | +| stake | 33411 | 246953 | 257915 | 278442 | 67 | | stakeManager | 368 | 368 | 368 | 368 | 233 | -| trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 | -| unstake | 33282 | 96951 | 102442 | 110255 | 14 | -| withdraw | 42246 | 42246 | 42246 | 42246 | 1 | +| trustStakeManager | 28941 | 28941 | 28941 | 28941 | 1 | +| unstake | 33282 | 101796 | 107825 | 115638 | 14 | +| withdraw | 42222 | 42222 | 42222 | 42222 | 1 | + + +| src/StakeVaultRegistry.sol:StakeVaultRegistry contract | | | | | | +|--------------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 478735 | 2129 | | | | | +| Function Name | min | avg | median | max | # calls | +| setStakeManager | 46008 | 46008 | 46008 | 46008 | 59 | +| vaultOwners | 2558 | 2558 | 2558 | 2558 | 82 | +| vaultsOf | 1868 | 5148 | 5109 | 11868 | 7 | | src/XPNFTToken.sol:XPNFTToken contract | | | | | | diff --git a/.gas-snapshot b/.gas-snapshot index bf58655..a55ed00 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,69 +1,69 @@ -EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92690) -EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 297868) -EmergencyExitTest:test_EmergencyExitBasic() (gas: 384605) -EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 659510) -EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 392525) -EmergencyExitTest:test_EmergencyExitWithLock() (gas: 392081) -EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 377466) -EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39430) -IntegrationTest:testStakeFoo() (gas: 1179859) -LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 2941805) -LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 294977) -LeaveTest:test_TrustNewStakeManager() (gas: 3019253) -LockTest:test_LockFailsWithInvalidPeriod() (gas: 310062) -LockTest:test_LockFailsWithNoStake() (gas: 63620) -LockTest:test_LockWithoutPriorLock() (gas: 391268) -MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1745438) -MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 717206) +EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92598) +EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 303185) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 389831) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 670073) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 397772) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 397352) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 382738) +EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39384) +IntegrationTest:testStakeFoo() (gas: 1205695) +LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 3044358) +LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 300338) +LeaveTest:test_TrustNewStakeManager() (gas: 3121863) +LockTest:test_LockFailsWithInvalidPeriod() (gas: 320790) +LockTest:test_LockFailsWithNoStake() (gas: 69009) +LockTest:test_LockWithoutPriorLock() (gas: 401855) +MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1750751) +MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 739653) 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: 670943) -RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160368) -RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39345) -RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39368) -RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39381) -RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 610886) +RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 676244) +RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160279) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39409) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39345) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39358) +RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 616724) RewardsStreamerTest:testStake() (gas: 869181) -StakeTest:test_StakeMultipleAccounts() (gas: 494743) -StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500680) -StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 831250) -StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517691) -StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539574) -StakeTest:test_StakeOneAccount() (gas: 277083) -StakeTest:test_StakeOneAccountAndRewards() (gas: 283051) -StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499980) -StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 496361) -StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 301892) -StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 301881) -StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 301948) +StakeTest:test_StakeMultipleAccounts() (gas: 505375) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 511291) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 841637) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 528373) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 550256) +StakeTest:test_StakeOneAccount() (gas: 282377) +StakeTest:test_StakeOneAccountAndRewards() (gas: 288302) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 505138) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 501475) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 307256) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 307200) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 307311) StakingTokenTest:testStakeToken() (gas: 10422) -UnstakeTest:test_StakeMultipleAccounts() (gas: 494765) -UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500680) -UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 831227) -UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517713) -UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539596) -UnstakeTest:test_StakeOneAccount() (gas: 277106) -UnstakeTest:test_StakeOneAccountAndRewards() (gas: 283073) -UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 500002) -UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 496341) -UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 301892) -UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 301881) -UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 301992) -UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 543052) -UnstakeTest:test_UnstakeMultipleAccounts() (gas: 693591) -UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 787431) -UnstakeTest:test_UnstakeOneAccount() (gas: 473557) -UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 495171) -UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 404616) -UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 531627) -UpgradeTest:test_RevertWhenNotOwner() (gas: 2582744) -UpgradeTest:test_UpgradeStakeManager() (gas: 2856354) -VaultRegistrationTest:test_VaultRegistration() (gas: 62035) -WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 310613) +UnstakeTest:test_StakeMultipleAccounts() (gas: 505374) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 511268) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 841614) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 528372) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 550300) +UnstakeTest:test_StakeOneAccount() (gas: 282400) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 288346) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 505182) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 501477) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 307278) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 307200) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 307311) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 553550) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 714853) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 811813) +UnstakeTest:test_UnstakeOneAccount() (gas: 487377) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 505804) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 415205) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 542243) +UpgradeTest:test_RevertWhenNotOwner() (gas: 2680084) +UpgradeTest:test_UpgradeStakeManager() (gas: 2958999) +VaultRegistrationTest:test_VaultRegistration() (gas: 55423) +WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 315973) XPNFTTokenTest:testApproveNotAllowed() (gas: 10500) XPNFTTokenTest:testGetApproved() (gas: 10523) XPNFTTokenTest:testIsApprovedForAll() (gas: 10698) diff --git a/certora/confs/RewardsStreamerMP.conf b/certora/confs/RewardsStreamerMP.conf index 5d8cdfa..999bd86 100644 --- a/certora/confs/RewardsStreamerMP.conf +++ b/certora/confs/RewardsStreamerMP.conf @@ -1,9 +1,11 @@ { "files": [ + "src/StakeVaultRegistry.sol", "src/RewardsStreamerMP.sol", "certora/helpers/ERC20A.sol" ], "link" : [ + "RewardsStreamerMP:stakeVaultRegistry=StakeVaultRegistry", "RewardsStreamerMP:STAKING_TOKEN=ERC20A" ], "msg": "Verifying RewardsStreamerMP.sol", diff --git a/certora/specs/EmergencyMode.spec b/certora/specs/EmergencyMode.spec index 9fa2ab7..23a1737 100644 --- a/certora/specs/EmergencyMode.spec +++ b/certora/specs/EmergencyMode.spec @@ -20,6 +20,7 @@ definition isViewFunction(method f) returns bool = ( f.selector == sig:streamer.totalMP().selector || f.selector == sig:streamer.accounts(address).selector || f.selector == sig:streamer.emergencyModeEnabled().selector || + f.selector == sig:streamer.stakeVaultRegistry().selector || f.selector == sig:streamer.getStakedBalance(address).selector || f.selector == sig:streamer.getAccount(address).selector || f.selector == sig:streamer.rewardsBalanceOf(address).selector || @@ -33,11 +34,7 @@ definition isViewFunction(method f) returns bool = ( f.selector == sig:streamer.getUserTotalMP(address).selector || f.selector == sig:streamer.getUserTotalMaxMP(address).selector || f.selector == sig:streamer.getUserTotalStakedBalance(address).selector || - f.selector == sig:streamer.getUserVaults(address).selector || - f.selector == sig:streamer.isVaultRegistered(address).selector || - f.selector == sig:streamer.registerVault().selector || - f.selector == sig:streamer.vaultOwners(address).selector || - f.selector == sig:streamer.vaults(address,uint256).selector + f.selector == sig:streamer.registerVault().selector ); definition isOwnableFunction(method f) returns bool = ( @@ -52,7 +49,7 @@ definition isTrustedCodehashAccessFunction(method f) returns bool = ( ); definition isInitializerFunction(method f) returns bool = ( - f.selector == sig:streamer.initialize(address,address).selector + f.selector == sig:streamer.initialize(address,address,address).selector ); definition isUUPSUpgradeableFunction(method f) returns bool = ( diff --git a/src/RewardsStreamerMP.sol b/src/RewardsStreamerMP.sol index 266aa42..f047916 100644 --- a/src/RewardsStreamerMP.sol +++ b/src/RewardsStreamerMP.sol @@ -7,6 +7,7 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { IStakeManager } from "./interfaces/IStakeManager.sol"; import { IStakeVault } from "./interfaces/IStakeVault.sol"; +import { IStakeVaultRegistry } from "./interfaces/IStakeVaultRegistry.sol"; import { TrustedCodehashAccess } from "./TrustedCodehashAccess.sol"; // Rewards Streamer with Multiplier Points @@ -30,6 +31,8 @@ contract RewardsStreamerMP is error StakingManager__EmergencyModeEnabled(); error StakingManager__DurationCannotBeZero(); + IStakeVaultRegistry public stakeVaultRegistry; + IERC20 public STAKING_TOKEN; uint256 public constant SCALE_FACTOR = 1e18; @@ -61,12 +64,10 @@ contract RewardsStreamerMP is uint256 lockUntil; } - mapping(address owner => address[] vault) public vaults; - mapping(address vault => address owner) public vaultOwners; mapping(address vault => Account data) public accounts; modifier onlyRegisteredVault() { - if (!isVaultRegistered(msg.sender)) { + if (stakeVaultRegistry.vaultOwners(msg.sender) == address(0)) { revert StakingManager__VaultNotRegistered(); } _; @@ -83,11 +84,12 @@ contract RewardsStreamerMP is _disableInitializers(); } - function initialize(address _owner, address _stakingToken) public initializer { + function initialize(address _owner, address _stakeVaultRegistry, address _stakingToken) public initializer { __TrustedCodehashAccess_init(_owner); __UUPSUpgradeable_init(); __ReentrancyGuard_init(); + stakeVaultRegistry = IStakeVaultRegistry(_stakeVaultRegistry); STAKING_TOKEN = IERC20(_stakingToken); lastMPUpdatedTime = block.timestamp; } @@ -96,15 +98,6 @@ contract RewardsStreamerMP is _checkOwner(); } - /** - * @notice Check if a vault is registered - * @param vault The address of the vault to check - * @return true if the vault is registered, false otherwise - */ - function isVaultRegistered(address vault) public view returns (bool) { - return vaultOwners[vault] != address(0); - } - /** * @notice Registers a vault with its owner. Called by the vault itself during initialization. * @dev Only callable by contracts with trusted codehash @@ -112,25 +105,7 @@ contract RewardsStreamerMP is function registerVault() external onlyTrustedCodehash { address vault = msg.sender; address owner = IStakeVault(vault).owner(); - - if (vaultOwners[vault] != address(0)) { - revert StakingManager__VaultAlreadyRegistered(); - } - - // Verify this is a legitimate vault by checking it points to us - if (address(IStakeVault(vault).stakeManager()) != address(this)) { - revert StakingManager__InvalidVault(); - } - - vaultOwners[vault] = owner; - vaults[owner].push(vault); - } - - /** - * @notice Get all vaults owned by an address - */ - function getUserVaults(address owner) external view returns (address[] memory) { - return vaults[owner]; + stakeVaultRegistry.register(owner, vault); } /** @@ -140,7 +115,7 @@ contract RewardsStreamerMP is * @return The total multiplier points for the user */ function getUserTotalMP(address user) external view returns (uint256) { - address[] memory userVaults = vaults[user]; + address[] memory userVaults = stakeVaultRegistry.vaultsOf(user); uint256 userTotalMP = 0; for (uint256 i = 0; i < userVaults.length; i++) { @@ -157,7 +132,7 @@ contract RewardsStreamerMP is * @return The total maximum multiplier points for the user */ function getUserTotalMaxMP(address user) external view returns (uint256) { - address[] memory userVaults = vaults[user]; + address[] memory userVaults = stakeVaultRegistry.vaultsOf(user); uint256 userTotalMaxMP = 0; for (uint256 i = 0; i < userVaults.length; i++) { @@ -173,7 +148,7 @@ contract RewardsStreamerMP is * @return The total staked balance for the user */ function getUserTotalStakedBalance(address user) external view returns (uint256) { - address[] memory userVaults = vaults[user]; + address[] memory userVaults = stakeVaultRegistry.vaultsOf(user); uint256 userTotalStake = 0; for (uint256 i = 0; i < userVaults.length; i++) { diff --git a/src/StakeVaultRegistry.sol b/src/StakeVaultRegistry.sol new file mode 100644 index 0000000..fc0499b --- /dev/null +++ b/src/StakeVaultRegistry.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IStakeVaultRegistry } from "./interfaces/IStakeVaultRegistry.sol"; +import { IStakeVault } from "./interfaces/IStakeVault.sol"; + +contract StakeVaultRegistry is IStakeVaultRegistry, Ownable { + error StakeManagerVaultRegistry__NotAuthorized(); + error StakeManagerVaultRegistry__VaultAlreadyRegistered(); + error StakeManagerVaultRegistry__InvalidVault(); + + address public stakeManager; + + mapping(address owner => address[] vault) public vaults; + mapping(address vault => address owner) public vaultOwners; + + modifier onlyStakeManager() { + if (msg.sender != stakeManager) { + revert StakeManagerVaultRegistry__NotAuthorized(); + } + _; + } + + constructor(address _owner) Ownable(_owner) { } + + function setStakeManager(address _stakeManager) external onlyOwner { + stakeManager = _stakeManager; + } + + function register(address owner, address vault) external onlyStakeManager { + if (vaultOwners[vault] != address(0)) { + revert StakeManagerVaultRegistry__VaultAlreadyRegistered(); + } + + // Verify this is a legitimate vault by checking it points to stakeManager + if (address(IStakeVault(vault).stakeManager()) != stakeManager) { + revert StakeManagerVaultRegistry__InvalidVault(); + } + + vaultOwners[vault] = owner; + vaults[owner].push(vault); + } + + function vaultsOf(address owner) external view returns (address[] memory) { + return vaults[owner]; + } +} diff --git a/src/interfaces/IStakeVaultRegistry.sol b/src/interfaces/IStakeVaultRegistry.sol new file mode 100644 index 0000000..a304506 --- /dev/null +++ b/src/interfaces/IStakeVaultRegistry.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +interface IStakeVaultRegistry { + function register(address owner, address vault) external; + function vaultsOf(address owner) external view returns (address[] memory); + function vaultOwners(address vault) external view returns (address); +} diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index 9d65177..78858cc 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -7,6 +7,7 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; +import { StakeVaultRegistry } from "../src/StakeVaultRegistry.sol"; import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; import { StakeManagerProxy } from "../src/StakeManagerProxy.sol"; import { MockToken } from "./mocks/MockToken.sol"; @@ -15,6 +16,7 @@ import { StackOverflowStakeManager } from "./mocks/StackOverflowStakeManager.sol contract RewardsStreamerMPTest is Test { MockToken stakingToken; RewardsStreamerMP public streamer; + StakeVaultRegistry public vaultRegistry; address admin = makeAddr("admin"); address alice = makeAddr("alice"); @@ -26,12 +28,16 @@ contract RewardsStreamerMPTest is Test { function setUp() public virtual { stakingToken = new MockToken("Staking Token", "ST"); + vaultRegistry = new StakeVaultRegistry(address(this)); - bytes memory initializeData = abi.encodeCall(RewardsStreamerMP.initialize, (admin, address(stakingToken))); + bytes memory initializeData = + abi.encodeCall(RewardsStreamerMP.initialize, (admin, address(vaultRegistry), address(stakingToken))); address impl = address(new RewardsStreamerMP()); address proxy = address(new StakeManagerProxy(impl, initializeData)); streamer = RewardsStreamerMP(proxy); + vaultRegistry.setStakeManager(address(streamer)); + // Create a temporary vault just to get the codehash StakeVault tempVault = new StakeVault(address(this), IStakeManagerProxy(address(streamer))); bytes32 vaultCodeHash = address(tempVault).codehash; @@ -167,7 +173,7 @@ contract VaultRegistrationTest is RewardsStreamerMPTest { function test_VaultRegistration() public view { address[4] memory accounts = [alice, bob, charlie, dave]; for (uint256 i = 0; i < accounts.length; i++) { - address[] memory userVaults = streamer.getUserVaults(accounts[i]); + address[] memory userVaults = vaultRegistry.vaultsOf(accounts[i]); assertEq(userVaults.length, 1, "wrong number of vaults"); assertEq(userVaults[0], vaults[accounts[i]], "wrong vault address"); } diff --git a/test/StakeVault.test.sol b/test/StakeVault.test.sol index ca32530..50e4e14 100644 --- a/test/StakeVault.test.sol +++ b/test/StakeVault.test.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; +import { StakeVaultRegistry } from "../src/StakeVaultRegistry.sol"; import { StakeManagerProxy } from "../src/StakeManagerProxy.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; @@ -30,13 +30,20 @@ contract StakeVaultTest is Test { function setUp() public virtual { rewardToken = new MockToken("Reward Token", "RT"); stakingToken = new MockToken("Staking Token", "ST"); + StakeVaultRegistry stakeVaultRegistry = new StakeVaultRegistry(address(this)); address impl = address(new RewardsStreamerMP()); bytes memory initializeData = abi.encodeWithSelector( - RewardsStreamerMP.initialize.selector, address(this), address(stakingToken), address(rewardToken) + RewardsStreamerMP.initialize.selector, + address(this), + address(stakeVaultRegistry), + address(stakingToken), + address(rewardToken) ); address proxy = address(new StakeManagerProxy(impl, initializeData)); streamer = RewardsStreamerMP(proxy); + stakeVaultRegistry.setStakeManager(address(streamer)); + stakingToken.mint(alice, 10_000e18); // Create a temporary vault just to get the codehash