diff --git a/.gas-report b/.gas-report index be25979..019569a 100644 --- a/.gas-report +++ b/.gas-report @@ -15,71 +15,71 @@ | src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | | |------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 2529128 | 11658 | | | | | +| 2510128 | 11575 | | | | | | Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 294 | 294 | 294 | 294 | 23 | -| MAX_MULTIPLIER | 295 | 295 | 295 | 295 | 30 | +| MAX_LOCKUP_PERIOD | 272 | 272 | 272 | 272 | 23 | +| MAX_MULTIPLIER | 251 | 251 | 251 | 251 | 30 | | MIN_LOCKUP_PERIOD | 274 | 274 | 274 | 274 | 11 | | MP_RATE_PER_YEAR | 253 | 253 | 253 | 253 | 3 | -| SCALE_FACTOR | 251 | 251 | 251 | 251 | 41 | +| SCALE_FACTOR | 272 | 272 | 272 | 272 | 41 | | STAKING_TOKEN | 2428 | 2428 | 2428 | 2428 | 262 | -| accountedRewards | 350 | 939 | 350 | 2350 | 78 | -| emergencyModeEnabled | 2398 | 2398 | 2398 | 2398 | 7 | -| enableEmergencyMode | 2506 | 19413 | 24698 | 24698 | 8 | +| accountedRewards | 395 | 984 | 395 | 2395 | 78 | +| emergencyModeEnabled | 2399 | 2399 | 2399 | 2399 | 7 | +| enableEmergencyMode | 2462 | 19369 | 24654 | 24654 | 8 | | getAccount | 1639 | 1639 | 1639 | 1639 | 72 | -| getStakedBalance | 2607 | 2607 | 2607 | 2607 | 1 | -| getUserTotalMP | 9208 | 9208 | 9208 | 9208 | 1 | -| getUserTotalMaxMP | 3166 | 3166 | 3166 | 3166 | 1 | -| getUserTotalStakedBalance | 15140 | 15140 | 15140 | 15140 | 1 | -| getUserVaults | 5245 | 5245 | 5245 | 5245 | 4 | -| initialize | 137854 | 137854 | 137854 | 137854 | 53 | +| getStakedBalance | 2629 | 2629 | 2629 | 2629 | 1 | +| getUserTotalMP | 11497 | 11497 | 11497 | 11497 | 1 | +| getUserTotalMaxMP | 5412 | 5412 | 5412 | 5412 | 1 | +| getUserTotalStakedBalance | 17407 | 17407 | 17407 | 17407 | 1 | +| getUserVaults | 6769 | 7894 | 6769 | 11269 | 4 | +| initialize | 160076 | 160076 | 160076 | 160076 | 53 | | isTrustedCodehash | 563 | 563 | 563 | 563 | 207 | -| leave | 62621 | 62621 | 62621 | 62621 | 1 | -| lock | 12041 | 35954 | 16370 | 79452 | 3 | -| proxiableUUID | 354 | 354 | 354 | 354 | 3 | -| registerVault | 55844 | 72698 | 72944 | 72944 | 209 | -| rewardIndex | 350 | 375 | 350 | 2350 | 78 | -| setTrustedCodehash | 26227 | 26227 | 26227 | 26227 | 53 | -| stake | 137282 | 172935 | 178872 | 199351 | 64 | -| totalMP | 351 | 351 | 351 | 351 | 81 | -| totalMaxMP | 395 | 395 | 395 | 395 | 81 | +| leave | 62642 | 62642 | 62642 | 62642 | 1 | +| lock | 17522 | 41435 | 21851 | 84933 | 3 | +| proxiableUUID | 331 | 331 | 331 | 331 | 3 | +| registerVault | 63444 | 80298 | 80544 | 80544 | 209 | +| rewardIndex | 394 | 419 | 394 | 2394 | 78 | +| setTrustedCodehash | 26249 | 26249 | 26249 | 26249 | 53 | +| stake | 142720 | 178377 | 184316 | 204795 | 64 | +| totalMP | 373 | 373 | 373 | 373 | 81 | +| totalMaxMP | 350 | 350 | 350 | 350 | 81 | | totalStaked | 374 | 374 | 374 | 374 | 82 | -| unstake | 66758 | 87497 | 66758 | 122819 | 13 | -| updateAccountMP | 15442 | 17680 | 17944 | 17944 | 19 | -| updateGlobalState | 11066 | 41321 | 30542 | 63493 | 28 | -| upgradeToAndCall | 3181 | 9357 | 10905 | 10905 | 5 | +| unstake | 72193 | 92932 | 72193 | 128254 | 13 | +| updateAccountMP | 15464 | 17702 | 17966 | 17966 | 19 | +| updateGlobalState | 11044 | 41299 | 30520 | 63471 | 28 | +| upgradeToAndCall | 3250 | 9415 | 10954 | 10964 | 5 | | src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | | |------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 278710 | 1263 | | | | | +| 301321 | 1295 | | | | | | Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 721 | 1503 | 721 | 5221 | 23 | -| MAX_MULTIPLIER | 722 | 1622 | 722 | 5222 | 30 | +| MAX_LOCKUP_PERIOD | 699 | 1481 | 699 | 5199 | 23 | +| MAX_MULTIPLIER | 678 | 1578 | 678 | 5178 | 30 | | MIN_LOCKUP_PERIOD | 701 | 3973 | 5201 | 5201 | 11 | | MP_RATE_PER_YEAR | 680 | 680 | 680 | 680 | 3 | -| SCALE_FACTOR | 678 | 678 | 678 | 678 | 41 | +| SCALE_FACTOR | 699 | 699 | 699 | 699 | 41 | | STAKING_TOKEN | 7355 | 7355 | 7355 | 7355 | 262 | -| accountedRewards | 777 | 1366 | 777 | 2777 | 78 | -| emergencyModeEnabled | 7325 | 7325 | 7325 | 7325 | 7 | -| enableEmergencyMode | 28501 | 45402 | 50686 | 50686 | 8 | +| accountedRewards | 822 | 1411 | 822 | 2822 | 78 | +| emergencyModeEnabled | 7326 | 7326 | 7326 | 7326 | 7 | +| enableEmergencyMode | 28457 | 45358 | 50642 | 50642 | 8 | | getAccount | 2093 | 2093 | 2093 | 2093 | 72 | -| getStakedBalance | 7537 | 7537 | 7537 | 7537 | 1 | -| getUserTotalMP | 9638 | 9638 | 9638 | 9638 | 1 | -| getUserTotalMaxMP | 3596 | 3596 | 3596 | 3596 | 1 | -| getUserTotalStakedBalance | 15570 | 15570 | 15570 | 15570 | 1 | -| getUserVaults | 5681 | 6806 | 5681 | 10181 | 4 | +| getStakedBalance | 7559 | 7559 | 7559 | 7559 | 1 | +| getUserTotalMP | 11927 | 11927 | 11927 | 11927 | 1 | +| getUserTotalMaxMP | 5842 | 5842 | 5842 | 5842 | 1 | +| getUserTotalStakedBalance | 17837 | 17837 | 17837 | 17837 | 1 | +| getUserVaults | 7205 | 9455 | 7205 | 16205 | 4 | | implementation | 343 | 840 | 343 | 2343 | 350 | | isTrustedCodehash | 993 | 993 | 993 | 993 | 207 | -| rewardIndex | 777 | 802 | 777 | 2777 | 78 | -| setTrustedCodehash | 52873 | 52873 | 52873 | 52873 | 53 | -| totalMP | 778 | 778 | 778 | 778 | 81 | -| totalMaxMP | 822 | 822 | 822 | 822 | 81 | +| rewardIndex | 821 | 846 | 821 | 2821 | 78 | +| setTrustedCodehash | 52895 | 52895 | 52895 | 52895 | 53 | +| totalMP | 800 | 800 | 800 | 800 | 81 | +| totalMaxMP | 777 | 777 | 777 | 777 | 81 | | totalStaked | 801 | 801 | 801 | 801 | 82 | -| updateAccountMP | 41801 | 44039 | 44303 | 44303 | 19 | -| updateGlobalState | 37054 | 67309 | 56530 | 89481 | 28 | -| upgradeToAndCall | 29824 | 35995 | 37541 | 37541 | 5 | +| updateAccountMP | 41823 | 44061 | 44325 | 44325 | 19 | +| updateGlobalState | 37032 | 67287 | 56508 | 89459 | 28 | +| upgradeToAndCall | 29893 | 36052 | 37590 | 37600 | 5 | | src/StakeVault.sol:StakeVault contract | | | | | | @@ -88,16 +88,26 @@ | 1420425 | 6695 | | | | | | Function Name | min | avg | median | max | # calls | | STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 | -| emergencyExit | 36353 | 48857 | 48091 | 65191 | 7 | -| leave | 33507 | 132714 | 63186 | 370978 | 4 | -| lock | 33245 | 62013 | 50779 | 113249 | 4 | +| emergencyExit | 36354 | 48858 | 48092 | 65192 | 7 | +| leave | 33507 | 132718 | 63195 | 370978 | 4 | +| lock | 33245 | 66124 | 56260 | 118730 | 4 | | owner | 2339 | 2339 | 2339 | 2339 | 209 | -| register | 86993 | 103847 | 104093 | 104093 | 209 | -| stake | 33411 | 244157 | 253376 | 273903 | 65 | +| register | 94593 | 111447 | 111693 | 111693 | 209 | +| stake | 33411 | 249516 | 258820 | 279347 | 65 | | stakeManager | 368 | 368 | 368 | 368 | 209 | | trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 | -| unstake | 33282 | 119756 | 116285 | 159183 | 14 | -| withdraw | 42267 | 42267 | 42267 | 42267 | 1 | +| unstake | 33282 | 124648 | 121720 | 164618 | 14 | +| withdraw | 42289 | 42289 | 42289 | 42289 | 1 | + + +| src/StakeVaultRegistry.sol:StakeVaultRegistry contract | | | | | | +|--------------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 478735 | 2129 | | | | | +| Function Name | min | avg | median | max | # calls | +| setStakeManager | 46008 | 46008 | 46008 | 46008 | 53 | +| vaultOwners | 2558 | 2558 | 2558 | 2558 | 80 | +| vaultsOf | 1868 | 5148 | 5109 | 11868 | 7 | | src/XPNFTToken.sol:XPNFTToken contract | | | | | | diff --git a/.gas-snapshot b/.gas-snapshot index a04a0d1..f114e0c 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,20 +1,20 @@ -EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 89804) -EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 298634) -EmergencyExitTest:test_EmergencyExitBasic() (gas: 401399) -EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 835587) -EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 544250) -EmergencyExitTest:test_EmergencyExitWithLock() (gas: 390381) -EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 535878) -EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39445) -IntegrationTest:testStakeFoo() (gas: 1591343) -LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 2990489) -LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 295799) -LeaveTest:test_TrustNewStakeManager() (gas: 3040270) -LockTest:test_LockFailsWithInvalidPeriod() (gas: 310862) -LockTest:test_LockFailsWithNoStake() (gas: 63576) -LockTest:test_LockWithoutPriorLock() (gas: 408566) -MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1769831) -MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 737870) +EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 89716) +EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 304079) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 406866) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 846537) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 549629) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 395782) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 541323) +EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39401) +IntegrationTest:testStakeFoo() (gas: 1619039) +LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 2977098) +LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 301243) +LeaveTest:test_TrustNewStakeManager() (gas: 3026735) +LockTest:test_LockFailsWithInvalidPeriod() (gas: 321787) +LockTest:test_LockFailsWithNoStake() (gas: 69057) +LockTest:test_LockWithoutPriorLock() (gas: 419423) +MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1775413) +MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 761058) NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 85934) NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 58332) NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35804) @@ -22,42 +22,42 @@ NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 102512) NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 49555) NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35979) RewardsStreamerTest:testStake() (gas: 869181) -StakeTest:test_StakeMultipleAccounts() (gas: 517926) -StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 674056) -StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 885563) -StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 532405) -StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 554294) -StakeTest:test_StakeOneAccount() (gas: 296366) -StakeTest:test_StakeOneAccountAndRewards() (gas: 452536) -StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 543338) -StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 539785) -StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 317004) -StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 316949) -StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 317060) +StakeTest:test_StakeMultipleAccounts() (gas: 528874) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 685048) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 896599) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 543305) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 565172) +StakeTest:test_StakeOneAccount() (gas: 301876) +StakeTest:test_StakeOneAccountAndRewards() (gas: 458090) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 548936) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 545360) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 322468) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 322435) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 322546) StakingTokenTest:testStakeToken() (gas: 10422) -UnstakeTest:test_StakeMultipleAccounts() (gas: 517925) -UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 674100) -UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 885540) -UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 532404) -UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 554338) -UnstakeTest:test_StakeOneAccount() (gas: 296389) -UnstakeTest:test_StakeOneAccountAndRewards() (gas: 452580) -UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 543382) -UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 539787) -UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 316961) -UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 316949) -UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 317060) -UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 555105) -UnstakeTest:test_UnstakeMultipleAccounts() (gas: 739113) -UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 1086217) -UnstakeTest:test_UnstakeOneAccount() (gas: 516093) -UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 536982) -UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 621421) -UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 569408) -UpgradeTest:test_RevertWhenNotOwner() (gas: 2605396) -UpgradeTest:test_UpgradeStakeManager() (gas: 2897185) -VaultRegistrationTest:test_VaultRegistration() (gas: 62234) -WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 311479) +UnstakeTest:test_StakeMultipleAccounts() (gas: 528873) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 685092) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 896576) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 543304) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 565216) +UnstakeTest:test_StakeOneAccount() (gas: 301899) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 458134) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 548980) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 545362) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 322425) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 322435) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 322546) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 565912) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 760997) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 1111538) +UnstakeTest:test_UnstakeOneAccount() (gas: 530431) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 548059) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 632476) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 580413) +UpgradeTest:test_RevertWhenNotOwner() (gas: 2586437) +UpgradeTest:test_UpgradeStakeManager() (gas: 2883781) +VaultRegistrationTest:test_VaultRegistration() (gas: 72830) +WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 316945) 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 afbef15..bc86278 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", "RewardsStreamerMP:REWARD_TOKEN=ERC20A" ], diff --git a/certora/specs/EmergencyMode.spec b/certora/specs/EmergencyMode.spec index c00856e..4cec244 100644 --- a/certora/specs/EmergencyMode.spec +++ b/certora/specs/EmergencyMode.spec @@ -22,6 +22,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.getPendingRewards(address).selector || @@ -31,9 +32,7 @@ definition isViewFunction(method f) returns bool = ( 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 = ( @@ -47,7 +46,7 @@ definition isTrustedCodehashAccessFunction(method f) returns bool = ( ); definition isInitializerFunction(method f) returns bool = ( - f.selector == sig:streamer.initialize(address,address,address).selector + f.selector == sig:streamer.initialize(address,address,address,address).selector ); definition isUUPSUpgradeableFunction(method f) returns bool = ( diff --git a/src/RewardsStreamerMP.sol b/src/RewardsStreamerMP.sol index fbc0be1..46e8597 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 @@ -23,6 +24,8 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc error StakingManager__AlreadyLocked(); error StakingManager__EmergencyModeEnabled(); + IStakeVaultRegistry public stakeVaultRegistry; + IERC20 public STAKING_TOKEN; IERC20 public REWARD_TOKEN; @@ -50,8 +53,6 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc 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() { @@ -72,11 +73,20 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc _disableInitializers(); } - function initialize(address _owner, address _stakingToken, address _rewardToken) public initializer { + function initialize( + address _owner, + address _stakeVaultRegistry, + address _stakingToken, + address _rewardToken + ) + public + initializer + { __TrustedCodehashAccess_init(_owner); __UUPSUpgradeable_init(); __ReentrancyGuard_init(); + stakeVaultRegistry = IStakeVaultRegistry(_stakeVaultRegistry); STAKING_TOKEN = IERC20(_stakingToken); REWARD_TOKEN = IERC20(_rewardToken); lastMPUpdatedTime = block.timestamp; @@ -92,7 +102,7 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc * @return true if the vault is registered, false otherwise */ function isVaultRegistered(address vault) public view returns (bool) { - return vaultOwners[vault] != address(0); + return stakeVaultRegistry.vaultOwners(vault) != address(0); } /** @@ -102,25 +112,14 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc 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); + stakeVaultRegistry.register(owner, vault); } /** * @notice Get all vaults owned by an address */ function getUserVaults(address owner) external view returns (address[] memory) { - return vaults[owner]; + return stakeVaultRegistry.vaultsOf(owner); } /** @@ -130,7 +129,7 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc * @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++) { @@ -147,7 +146,7 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc * @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++) { @@ -163,7 +162,7 @@ contract RewardsStreamerMP is UUPSUpgradeable, IStakeManager, TrustedCodehashAcc * @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 9a6de28..381716d 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"; @@ -28,13 +29,18 @@ contract RewardsStreamerMPTest is Test { function setUp() public virtual { rewardToken = new MockToken("Reward Token", "RT"); stakingToken = new MockToken("Staking Token", "ST"); + address stakeVaultRegistry = address(new StakeVaultRegistry(address(this))); - bytes memory initializeData = - abi.encodeCall(RewardsStreamerMP.initialize, (address(this), address(stakingToken), address(rewardToken))); + bytes memory initializeData = abi.encodeCall( + RewardsStreamerMP.initialize, + (address(this), stakeVaultRegistry, address(stakingToken), address(rewardToken)) + ); address impl = address(new RewardsStreamerMP()); address proxy = address(new StakeManagerProxy(impl, initializeData)); streamer = RewardsStreamerMP(proxy); + StakeVaultRegistry(stakeVaultRegistry).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; 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