From 871ff3a5540eab291e1b273789bbe093be7c5d9d Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Mon, 18 Aug 2025 11:03:51 -0300 Subject: [PATCH] refactor(StakeVault): BRAKING CHANGE migration system --- .gas-report | 120 +++++----- .gas-snapshot | 175 ++++++++------- src/StakeManager.sol | 20 +- src/StakeVault.sol | 34 +-- src/VaultFactory.sol | 12 + src/interfaces/IStakeManager.sol | 5 +- src/interfaces/IStakeVault.sol | 1 - test/RewardsStreamerMP.t.sol | 37 ++-- test/mocks/MockStakeVault.sol | 369 +++++++++++++++++++++++++++++++ 9 files changed, 579 insertions(+), 194 deletions(-) create mode 100644 test/mocks/MockStakeVault.sol diff --git a/.gas-report b/.gas-report index a494422..d16d97e 100644 --- a/.gas-report +++ b/.gas-report @@ -10,7 +10,7 @@ |-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------| -| fallback | 5145 | 65021 | 33119 | 193478 | 3520 | +| fallback | 5145 | 65012 | 33119 | 193478 | 3519 | ╰-------------------------------------------------------------------------------------------+-----------------+-------+--------+--------+---------╯ ╭-----------------------------------------------------+-----------------+---------+---------+---------+---------╮ @@ -24,7 +24,7 @@ |-----------------------------------------------------+-----------------+---------+---------+---------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------------------+-----------------+---------+---------+---------+---------| -| run | 4666141 | 4666141 | 4666141 | 4666141 | 180 | +| run | 4666141 | 4666141 | 4666141 | 4666141 | 179 | ╰-----------------------------------------------------+-----------------+---------+---------+---------+---------╯ ╭-----------------------------------------------------------+-----------------+---------+---------+---------+---------╮ @@ -60,13 +60,13 @@ +=============================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-------------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| 8323525 | 39701 | | | | | +| 8518585 | 40612 | | | | | |-------------------------------------------------------------------+-----------------+---------+---------+---------+---------| | | | | | | | |-------------------------------------------------------------------+-----------------+---------+---------+---------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-------------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| run | 7263905 | 7263905 | 7263905 | 7263905 | 88 | +| run | 7446423 | 7446423 | 7446423 | 7446423 | 87 | ╰-------------------------------------------------------------------+-----------------+---------+---------+---------+---------╯ ╭---------------------------------------------------------+-----------------+------+--------+------+---------╮ @@ -80,7 +80,7 @@ |---------------------------------------------------------+-----------------+------+--------+------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------------+-----------------+------+--------+------+---------| -| activeNetworkConfig | 455 | 2043 | 455 | 4455 | 476 | +| activeNetworkConfig | 455 | 2044 | 455 | 4455 | 473 | ╰---------------------------------------------------------+-----------------+------+--------+------+---------╯ ╭---------------------------------------------------------------------+-----------------+---------+---------+---------+---------╮ @@ -88,13 +88,13 @@ +===============================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |---------------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| 5895538 | 28363 | | | | | +| 5950020 | 28618 | | | | | |---------------------------------------------------------------------+-----------------+---------+---------+---------+---------| | | | | | | | |---------------------------------------------------------------------+-----------------+---------+---------+---------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| runWithAdminAndProxy | 3304282 | 3304282 | 3304282 | 3304282 | 3 | +| runWithAdminAndProxy | 3355410 | 3355410 | 3355410 | 3355410 | 3 | ╰---------------------------------------------------------------------+-----------------+---------+---------+---------+---------╯ ╭------------------------------+-----------------+--------+--------+--------+---------╮ @@ -118,7 +118,7 @@ |------------------------------+-----------------+--------+--------+--------+---------| | accountSlashAmount | 2611 | 2611 | 2611 | 2611 | 2 | |------------------------------+-----------------+--------+--------+--------+---------| -| addRewardDistributor | 29975 | 63542 | 70903 | 70903 | 294 | +| addRewardDistributor | 29975 | 63517 | 70903 | 70903 | 293 | |------------------------------+-----------------+--------+--------+--------+---------| | allowance | 573 | 573 | 573 | 573 | 8 | |------------------------------+-----------------+--------+--------+--------+---------| @@ -134,7 +134,7 @@ |------------------------------+-----------------+--------+--------+--------+---------| | hasRole | 2754 | 2754 | 2754 | 2754 | 4 | |------------------------------+-----------------+--------+--------+--------+---------| -| initialize | 116796 | 116796 | 116796 | 116796 | 180 | +| initialize | 116796 | 116796 | 116796 | 116796 | 179 | |------------------------------+-----------------+--------+--------+--------+---------| | mint | 4869 | 50370 | 51342 | 51342 | 551 | |------------------------------+-----------------+--------+--------+--------+---------| @@ -210,7 +210,7 @@ +===================================================================================================+ | Deployment Cost | Deployment Size | | | | | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| 3509773 | 16260 | | | | | +| 3564245 | 16515 | | | | | |--------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |--------------------------------------------+-----------------+--------+--------+--------+---------| @@ -232,17 +232,17 @@ |--------------------------------------------+-----------------+--------+--------+--------+---------| | getAccountVaults | 5230 | 5230 | 5230 | 5230 | 4 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| getVault | 13653 | 13653 | 13653 | 13653 | 4182 | +| getVault | 13653 | 13653 | 13653 | 13653 | 4180 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| initialize | 92774 | 92774 | 92774 | 92774 | 88 | +| initialize | 92774 | 92774 | 92774 | 92774 | 87 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | lastRewardTime | 2407 | 2407 | 2407 | 2407 | 2 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | leave | 66358 | 66358 | 66358 | 66358 | 2 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| lock | 7096 | 40159 | 43888 | 62877 | 1034 | +| lock | 7096 | 40165 | 43888 | 62877 | 1034 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| migrateToVault | 9229 | 57567 | 16864 | 187311 | 4 | +| migrateToVault | 153898 | 153898 | 153898 | 153898 | 1 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | mpAccruedOf | 2629 | 2629 | 2629 | 2629 | 20 | |--------------------------------------------+-----------------+--------+--------+--------+---------| @@ -252,47 +252,47 @@ |--------------------------------------------+-----------------+--------+--------+--------+---------| | proxiableUUID | 364 | 364 | 364 | 364 | 3 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| registerVault | 2605 | 74457 | 75037 | 75037 | 361 | +| registerVault | 2605 | 74320 | 75062 | 75062 | 357 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | rewardEndTime | 2364 | 2364 | 2364 | 2364 | 2 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | rewardStartTime | 2386 | 2386 | 2386 | 2386 | 2 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| rewardsBalanceOf | 20317 | 24410 | 25930 | 26151 | 268 | +| rewardsBalanceOf | 20317 | 24452 | 25930 | 26151 | 268 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | rewardsBalanceOfAccount | 62242 | 62242 | 62242 | 62242 | 1 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | setReward | 2508 | 105565 | 107076 | 107076 | 265 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| setRewardsSupplier | 26809 | 26809 | 26809 | 26809 | 88 | +| setRewardsSupplier | 26809 | 26809 | 26809 | 26809 | 87 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| setTrustedCodehash | 24171 | 24171 | 24171 | 24171 | 88 | +| setTrustedCodehash | 24171 | 24171 | 24171 | 24171 | 87 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| stake | 2713 | 125321 | 57926 | 213584 | 2667 | +| stake | 2713 | 126163 | 57926 | 213584 | 2666 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | totalMP | 6827 | 8279 | 8279 | 9732 | 6 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| totalMPAccrued | 2407 | 2407 | 2407 | 2407 | 4162 | +| totalMPAccrued | 2407 | 2407 | 2407 | 2407 | 4160 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| totalMPStaked | 2363 | 2363 | 2363 | 2363 | 4165 | +| totalMPStaked | 2363 | 2363 | 2363 | 2363 | 4163 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| totalMaxMP | 2429 | 2429 | 2429 | 2429 | 4162 | +| totalMaxMP | 2429 | 2429 | 2429 | 2429 | 4160 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | totalRewardsAccrued | 2407 | 2407 | 2407 | 2407 | 3 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| totalRewardsSupply | 6759 | 11071 | 11814 | 11925 | 290 | +| totalRewardsSupply | 6759 | 11090 | 11814 | 11925 | 290 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | totalShares | 4619 | 4619 | 4619 | 4619 | 6 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| totalStaked | 2408 | 2408 | 2408 | 2408 | 4168 | +| totalStaked | 2408 | 2408 | 2408 | 2408 | 4166 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| unstake | 36868 | 39344 | 36894 | 76663 | 258 | +| unstake | 36868 | 39501 | 36894 | 76663 | 257 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | updateAccount | 347651 | 347651 | 347651 | 347651 | 1 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | updateGlobalState | 15809 | 25865 | 29219 | 29219 | 8 | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| updateVault | 31936 | 34446 | 31936 | 110567 | 1024 | +| updateVault | 31936 | 34692 | 31936 | 110567 | 1022 | |--------------------------------------------+-----------------+--------+--------+--------+---------| | upgradeTo | 10323 | 10939 | 10323 | 12789 | 4 | |--------------------------------------------+-----------------+--------+--------+--------+---------| @@ -306,45 +306,45 @@ +===============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| -| 1623745 | 7673 | | | | | +| 1679756 | 7936 | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+--------+--------+--------+---------| -| STAKING_TOKEN | 240 | 240 | 240 | 240 | 1 | +| STAKING_TOKEN | 252 | 252 | 252 | 252 | 1 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| emergencyExit | 15023 | 31463 | 31461 | 48561 | 263 | +| emergencyExit | 15095 | 31559 | 31557 | 48657 | 263 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| initialize | 70212 | 70212 | 70212 | 70212 | 374 | +| initialize | 70260 | 70260 | 70260 | 70260 | 368 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| leave | 43439 | 145443 | 89216 | 359902 | 4 | +| leave | 43579 | 145577 | 89344 | 360042 | 4 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| lock | 21526 | 54657 | 58318 | 80340 | 1034 | +| lock | 21562 | 54707 | 58354 | 80376 | 1034 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| lockUntil | 2363 | 2363 | 2363 | 2363 | 3819 | +| lockUntil | 363 | 2361 | 2363 | 2363 | 3820 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| migrateFromVault | 24497 | 24497 | 24497 | 24497 | 1 | +| migrate | 99130 | 99130 | 99130 | 99130 | 1 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| migrateToVault | 21723 | 78456 | 29358 | 233387 | 4 | +| migrateToNew | 2694 | 183538 | 142440 | 405482 | 3 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| owner | 402 | 434 | 402 | 2402 | 369 | +| owner | 403 | 419 | 403 | 2403 | 362 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| register | 3354 | 79838 | 83167 | 83167 | 374 | +| register | 3378 | 79887 | 83216 | 83216 | 369 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| stake | 2636 | 167931 | 72429 | 288304 | 2673 | +| stake | 2660 | 169161 | 72465 | 288352 | 2672 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| stakeManager | 347 | 347 | 347 | 347 | 360 | +| stakeManager | 371 | 382 | 371 | 2371 | 357 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| unstake(uint256) | 4655 | 52505 | 51424 | 106772 | 272 | +| unstake(uint256) | 4679 | 52566 | 51460 | 106820 | 272 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| unstake(uint256,address) | 2630 | 2630 | 2630 | 2630 | 1 | +| unstake(uint256,address) | 2678 | 2678 | 2678 | 2678 | 1 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| withdraw(address,uint256) | 13561 | 24451 | 24451 | 35341 | 2 | +| withdraw(address,uint256) | 13645 | 24529 | 24529 | 35413 | 2 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| withdraw(address,uint256,address) | 2730 | 19096 | 19096 | 35462 | 2 | +| withdraw(address,uint256,address) | 2790 | 19174 | 19174 | 35558 | 2 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| withdrawFromVault | 20331 | 20331 | 20331 | 20331 | 1 | +| withdrawFromVault | 20391 | 20391 | 20391 | 20391 | 1 | ╰----------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------------------+-----------------+-------+--------+--------+---------╮ @@ -358,7 +358,7 @@ |----------------------------------------------------+-----------------+-------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------------------+-----------------+-------+--------+--------+---------| -| fallback | 5230 | 12829 | 7353 | 374028 | 23163 | +| fallback | 5230 | 12836 | 7353 | 374028 | 23150 | ╰----------------------------------------------------+-----------------+-------+--------+--------+---------╯ ╭--------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -366,15 +366,13 @@ +===================================================================================================+ | Deployment Cost | Deployment Size | | | | | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| 483043 | 2094 | | | | | +| 567514 | 2487 | | | | | |--------------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |--------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |--------------------------------------------+-----------------+--------+--------+--------+---------| -| createVault | 145330 | 221994 | 225143 | 225143 | 373 | -|--------------------------------------------+-----------------+--------+--------+--------+---------| -| vaultImplementation | 2345 | 2345 | 2345 | 2345 | 1 | +| createVault | 145424 | 222154 | 225262 | 225262 | 367 | ╰--------------------------------------------+-----------------+--------+--------+--------+---------╯ ╭------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------╮ @@ -382,7 +380,7 @@ +===============================================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------| -| 1204853 | 6015 | | | | | +| 1204853 | 6207 | | | | | |------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |------------------------------------------------------------------------------------------+-----------------+-------+--------+-------+---------| @@ -497,6 +495,22 @@ | stakedBalanceOf | 398 | 398 | 398 | 398 | 1 | ╰-----------------------------------------------------------+-----------------+-----+--------+-----+---------╯ +╭-------------------------------------------------------+-----------------+-------+--------+-------+---------╮ +| test/mocks/MockStakeVault.sol:MockStakeVault Contract | | | | | | ++============================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|-------------------------------------------------------+-----------------+-------+--------+-------+---------| +| 1740642 | 8229 | | | | | +|-------------------------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|-------------------------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-------------------------------------------------------+-----------------+-------+--------+-------+---------| +| migrate | 79230 | 79230 | 79230 | 79230 | 1 | +|-------------------------------------------------------+-----------------+-------+--------+-------+---------| +| register | 10764 | 10764 | 10764 | 10764 | 1 | +╰-------------------------------------------------------+-----------------+-------+--------+-------+---------╯ + ╭---------------------------------------------+-----------------+-------+--------+-------+---------╮ | test/mocks/MockToken.sol:MockToken Contract | | | | | | +==================================================================================================+ @@ -508,11 +522,11 @@ |---------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------+-----------------+-------+--------+-------+---------| -| approve | 29075 | 31546 | 29183 | 46259 | 2676 | +| approve | 29075 | 31520 | 29183 | 46259 | 2671 | |---------------------------------------------+-----------------+-------+--------+-------+---------| -| balanceOf | 2561 | 2561 | 2561 | 2561 | 4966 | +| balanceOf | 561 | 2560 | 2561 | 2561 | 4965 | |---------------------------------------------+-----------------+-------+--------+-------+---------| -| mint | 33964 | 37264 | 34072 | 68248 | 2688 | +| mint | 33964 | 37238 | 34072 | 68248 | 2684 | ╰---------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╮ diff --git a/.gas-snapshot b/.gas-snapshot index 6e38e2a..dc337db 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,23 +9,23 @@ AddRewardDistributorTest:testTotalSupply() (gas: 359391) AddRewardDistributorTest:testTransfersNotAllowed() (gas: 61947) AddRewardDistributorTest:test_RevertWhen_SenderIsNotDefaultAdmin() (gas: 68406) EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 93420) -EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 352335) -EmergencyExitTest:test_EmergencyExitBasic() (gas: 540735) -EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 969992) -EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 495287) -EmergencyExitTest:test_EmergencyExitWithLock() (gas: 446582) -EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 500965) +EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 352455) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 540879) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 970280) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 495431) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 446726) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 501109) EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39109) -FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1024, μ: 581604, ~: 545120) -FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1024, μ: 802844, ~: 769362) -FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1007, μ: 593693, ~: 593595) -FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1025, μ: 992626, ~: 993766) -FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1025, μ: 592561, ~: 566387) -FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1001, μ: 645371, ~: 647577) -FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1025, μ: 377229, ~: 342206) -FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1024, μ: 794426, ~: 772803) -FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1024, μ: 581627, ~: 545143) -IntegrationTest:testStakeFoo() (gas: 2389875) +FuzzTests:testFuzz_AccrueMP(uint128,uint64,uint64) (runs: 1024, μ: 585548, ~: 545178) +FuzzTests:testFuzz_AccrueMP_Relock(uint128,uint64,uint64,uint64) (runs: 1024, μ: 803397, ~: 769441) +FuzzTests:testFuzz_EmergencyExit(uint256,uint256) (runs: 1007, μ: 593835, ~: 593739) +FuzzTests:testFuzz_Lock(uint256,uint64) (runs: 1025, μ: 992647, ~: 993850) +FuzzTests:testFuzz_Relock(uint256,uint64,uint64) (runs: 1025, μ: 592860, ~: 566459) +FuzzTests:testFuzz_Rewards(uint256,uint256,uint256,uint16,uint16) (runs: 1001, μ: 645443, ~: 647633) +FuzzTests:testFuzz_Stake(uint256,uint64) (runs: 1025, μ: 377672, ~: 342242) +FuzzTests:testFuzz_Unstake(uint128,uint64,uint16,uint128) (runs: 1024, μ: 795108, ~: 772853) +FuzzTests:testFuzz_UpdateVault(uint128,uint64,uint64) (runs: 1024, μ: 585571, ~: 545201) +IntegrationTest:testStakeFoo() (gas: 2390115) KarmaNFTTest:testApproveNotAllowed() (gas: 10507) KarmaNFTTest:testGetApproved() (gas: 10531) KarmaNFTTest:testIsApprovedForAll() (gas: 10705) @@ -63,23 +63,23 @@ KarmaTiersTest:test_Revert_When_TiersNotStartingAtZero() (gas: 37667) KarmaTiersTest:test_Revert_When_UpdateTiersCalledByNonOwner() (gas: 36642) KarmaTiersTest:test_Success_When_LastTierIsUnlimited() (gas: 242295) KarmaTiersTest:test_Success_When_TiersAreContiguous() (gas: 336294) -LeaveTest:test_LeaveShouldKeepFundsLockedInStakeVault() (gas: 9717501) -LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 9812124) -LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1025, μ: 396949, ~: 396973) -LockTest:test_LockFailsWithNoStake() (gas: 85767) -LockTest:test_LockFailsWithZero() (gas: 358576) -LockTest:test_LockMultipleTimesExceedMaxLock() (gas: 736448) -LockTest:test_LockWithPriorLock() (gas: 668725) -LockTest:test_LockWithoutPriorLock() (gas: 515360) -LockTest:test_RevertWhenVaultToLockIsEmpty() (gas: 85767) -MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 2095407) +LeaveTest:test_LeaveShouldKeepFundsLockedInStakeVault() (gas: 9823365) +LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 9917943) +LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1025, μ: 397032, ~: 397057) +LockTest:test_LockFailsWithNoStake() (gas: 85803) +LockTest:test_LockFailsWithZero() (gas: 358660) +LockTest:test_LockMultipleTimesExceedMaxLock() (gas: 736568) +LockTest:test_LockWithPriorLock() (gas: 668845) +LockTest:test_LockWithoutPriorLock() (gas: 515444) +LockTest:test_RevertWhenVaultToLockIsEmpty() (gas: 85803) +MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 2095595) MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 5240) MathTest:test_CalcAccrueMP() (gas: 8599) MathTest:test_CalcBonusMP() (gas: 30744) MathTest:test_CalcInitialMP() (gas: 5836) MathTest:test_CalcMaxAccruedMP() (gas: 4886) MathTest:test_CalcMaxTotalMP() (gas: 31506) -MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 967977) +MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 968121) NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 92580) NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 77581) NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35891) @@ -150,69 +150,68 @@ SlashTest:test_RevertWhen_KarmaBalanceIsInvalid() (gas: 71550) SlashTest:test_RevertWhen_SenderIsNotDefaultAdminOrSlasher() (gas: 43232) SlashTest:test_Slash() (gas: 428385) SlashTest:test_SlashRemainingBalanceIfBalanceIsLow() (gas: 251800) -StakeManager_RewardsTest:testRewardsBalanceOf() (gas: 2745571) +StakeManager_RewardsTest:testRewardsBalanceOf() (gas: 2745667) StakeManager_RewardsTest:testSetRewards() (gas: 278063) StakeManager_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 63800) StakeManager_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 103558) StakeManager_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39367) -StakeManager_RewardsTest:testTotalRewardsSupply() (gas: 1297734) -StakeTest:test_StakeMultipleAccounts() (gas: 699422) -StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 754392) -StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 1357071) -StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 625649) -StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 634268) -StakeTest:test_StakeMultipleTimesDoesNotExceedsMaxMP() (gas: 1734589) -StakeTest:test_StakeMultipleTimesWithLockIncreaseAtSameBlock() (gas: 676201) -StakeTest:test_StakeMultipleTimesWithLockZeroAfterMaxLock() (gas: 1163144) -StakeTest:test_StakeOneAccount() (gas: 406658) -StakeTest:test_StakeOneAccountAndRewards() (gas: 461691) -StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 841530) -StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 731042) -StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 375840) -StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 376480) -StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 376525) -StakeVaultCoverageTest:testOwner() (gas: 15390) -StakeVaultCoverageTest:test_LeaveTransfersAllFunds() (gas: 153521) -StakeVaultCoverageTest:test_StakeRevertsIfNotOwner() (gas: 37258) -StakeVaultCoverageTest:test_StakeTransfersTokensToVault() (gas: 92532) -StakeVaultCoverageTest:test_UnstakeRevertsWithInvalidDestination() (gas: 112521) -StakeVaultCoverageTest:test_UnstakeTransfersTokensBackToOwner() (gas: 142860) -StakeVaultCoverageTest:test_WithdrawOtherTokenTransfersToDestination() (gas: 142271) -StakeVaultCoverageTest:test_WithdrawRevertsIfInsufficientAvailableBalance() (gas: 125765) -StakeVaultCoverageTest:test_WithdrawRevertsIfInvalidDestination() (gas: 111115) -StakeVaultCoverageTest:test_WithdrawTransfersGenericTokenToOwner() (gas: 139665) -StakeVaultMigrationTest:testMigrateToVault() (gas: 1163794) -StakeVaultMigrationTest:test_RevertWhenDestinationVaultIsNotRegistered() (gas: 163494) -StakeVaultMigrationTest:test_RevertWhenMigrationVaultNotEmpty() (gas: 621880) -StakeVaultMigrationTest:test_RevertWhenNotOwnerOfMigrationVault() (gas: 67284) -StakeVaultTest:testOwner() (gas: 15285) -StakingTokenTest:testOwner() (gas: 15285) -StakingTokenTest:testStakeToken() (gas: 13144) -TrustedCodehashAccessTest:test_RevertWhenProxyCloneCodehashNotTrusted() (gas: 1933652) -UnstakeTest:test_RevertWhen_FundsLocked() (gas: 432938) -UnstakeTest:test_StakeMultipleAccounts() (gas: 699401) -UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 754436) -UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 1357137) -UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 625715) -UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 634267) -UnstakeTest:test_StakeMultipleTimesDoesNotExceedsMaxMP() (gas: 1734645) -UnstakeTest:test_StakeMultipleTimesWithLockIncreaseAtSameBlock() (gas: 676156) -UnstakeTest:test_StakeMultipleTimesWithLockZeroAfterMaxLock() (gas: 1163155) -UnstakeTest:test_StakeOneAccount() (gas: 406680) -UnstakeTest:test_StakeOneAccountAndRewards() (gas: 461690) -UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 841529) -UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 731022) -UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 375862) -UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 376480) -UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 376525) -UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 744854) -UnstakeTest:test_UnstakeMultipleAccounts() (gas: 1062174) -UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 1345299) -UnstakeTest:test_UnstakeOneAccount() (gas: 769214) -UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 731845) -UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 686049) -UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 712558) -UpdateVaultTest:test_UpdateAccount() (gas: 2583507) -UpgradeTest:test_RevertWhenNotOwner() (gas: 3586110) -UpgradeTest:test_UpgradeStakeManager() (gas: 9658332) +StakeManager_RewardsTest:testTotalRewardsSupply() (gas: 1297782) +StakeTest:test_StakeMultipleAccounts() (gas: 699518) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 754488) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 1357167) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 625745) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 634364) +StakeTest:test_StakeMultipleTimesDoesNotExceedsMaxMP() (gas: 1734973) +StakeTest:test_StakeMultipleTimesWithLockIncreaseAtSameBlock() (gas: 676333) +StakeTest:test_StakeMultipleTimesWithLockZeroAfterMaxLock() (gas: 1163420) +StakeTest:test_StakeOneAccount() (gas: 406706) +StakeTest:test_StakeOneAccountAndRewards() (gas: 461739) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 841578) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 731090) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 375888) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 376528) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 376573) +StakeVaultCoverageTest:testOwner() (gas: 15391) +StakeVaultCoverageTest:test_LeaveTransfersAllFunds() (gas: 153709) +StakeVaultCoverageTest:test_StakeRevertsIfNotOwner() (gas: 37282) +StakeVaultCoverageTest:test_StakeTransfersTokensToVault() (gas: 92580) +StakeVaultCoverageTest:test_UnstakeRevertsWithInvalidDestination() (gas: 112617) +StakeVaultCoverageTest:test_UnstakeTransfersTokensBackToOwner() (gas: 142956) +StakeVaultCoverageTest:test_WithdrawOtherTokenTransfersToDestination() (gas: 142367) +StakeVaultCoverageTest:test_WithdrawRevertsIfInsufficientAvailableBalance() (gas: 125897) +StakeVaultCoverageTest:test_WithdrawRevertsIfInvalidDestination() (gas: 111175) +StakeVaultCoverageTest:test_WithdrawTransfersGenericTokenToOwner() (gas: 139737) +StakeVaultMigrationTest:testMigrateToVault() (gas: 1124510) +StakeVaultMigrationTest:test_RevertWhenDestinationVaultIsNotRegistered() (gas: 2558037) +StakeVaultMigrationTest:test_RevertWhenNotOwnerOfMigrationVault() (gas: 41710) +StakeVaultTest:testOwner() (gas: 15286) +StakingTokenTest:testOwner() (gas: 15286) +StakingTokenTest:testStakeToken() (gas: 13156) +TrustedCodehashAccessTest:test_RevertWhenProxyCloneCodehashNotTrusted() (gas: 1989827) +UnstakeTest:test_RevertWhen_FundsLocked() (gas: 433040) +UnstakeTest:test_StakeMultipleAccounts() (gas: 699497) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 754532) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 1357233) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 625811) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 634363) +UnstakeTest:test_StakeMultipleTimesDoesNotExceedsMaxMP() (gas: 1735029) +UnstakeTest:test_StakeMultipleTimesWithLockIncreaseAtSameBlock() (gas: 676288) +UnstakeTest:test_StakeMultipleTimesWithLockZeroAfterMaxLock() (gas: 1163431) +UnstakeTest:test_StakeOneAccount() (gas: 406728) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 461738) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 841577) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 731070) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 375910) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 376528) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 376573) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 744950) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 1062366) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 1345519) +UnstakeTest:test_UnstakeOneAccount() (gas: 769340) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 731941) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 686145) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 712654) +UpdateVaultTest:test_UpdateAccount() (gas: 2584056) +UpgradeTest:test_RevertWhenNotOwner() (gas: 3640646) +UpgradeTest:test_UpgradeStakeManager() (gas: 9764066) VaultRegistrationTest:test_VaultRegistration() (gas: 90138) \ No newline at end of file diff --git a/src/StakeManager.sol b/src/StakeManager.sol index de6a88f..cde46ca 100644 --- a/src/StakeManager.sol +++ b/src/StakeManager.sol @@ -364,16 +364,25 @@ contract StakeManager is */ function migrateToVault(address migrateTo) external onlyNotEmergencyMode onlyTrustedCodehash onlyRegisteredVault { if (vaultOwners[migrateTo] == address(0)) { - revert StakeManager__InvalidVault(); + revert StakeManager__InvalidMigration(); + } + IStakeVault oldVaultAddr = IStakeVault(msg.sender); + IStakeVault newVaultAddr = IStakeVault(migrateTo); + // first ensure the vault to migrate to is actually owned by the same user + if (oldVaultAddr.owner() != newVaultAddr.owner()) { + revert StakeManager__InvalidMigration(); } - // first ensure the vault to migrate to is actually owned by the same user - if (IStakeVault(msg.sender).owner() != IStakeVault(migrateTo).owner()) { - revert StakeManager__Unauthorized(); + if (oldVaultAddr.lockUntil() != newVaultAddr.lockUntil()) { + revert StakeManager__InvalidMigration(); } if (vaultData[migrateTo].stakedBalance > 0) { - revert StakeManager__MigrationTargetHasFunds(); + revert StakeManager__InvalidMigration(); + } + + if (STAKING_TOKEN.balanceOf(address(newVaultAddr)) < vaultData[migrateTo].stakedBalance) { + revert StakeManager__InvalidMigration(); } _updateGlobalState(); @@ -389,7 +398,6 @@ contract StakeManager is newVault.maxMP = oldVault.maxMP; newVault.lastMPUpdateTime = oldVault.lastMPUpdateTime; newVault.rewardsAccrued = oldVault.rewardsAccrued; - IStakeVault(migrateTo).migrateFromVault(IStakeVault(msg.sender).lockUntil()); delete vaultData[msg.sender]; diff --git a/src/StakeVault.sol b/src/StakeVault.sol index 85b90b3..11e0649 100644 --- a/src/StakeVault.sol +++ b/src/StakeVault.sol @@ -8,6 +8,8 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IStakeManagerProxy } from "./interfaces/IStakeManagerProxy.sol"; import { IStakeVault } from "./interfaces/IStakeVault.sol"; +import { VaultFactory } from "./VaultFactory.sol"; + /** * @title StakeVault * @author Ricardo Guilherme Schmidt @@ -87,6 +89,12 @@ contract StakeVault is IStakeVault, Initializable, OwnableUpgradeable { stakeManager = IStakeManagerProxy(_stakeManager); } + function migrate(StakeVault oldVault) public initializer { + _transferOwnership(oldVault.owner()); + stakeManager = oldVault.stakeManager(); + lockUntil = oldVault.lockUntil(); + } + /** * @notice Registers the vault with the stake manager. * @dev This is necessary to allow the stake manager to interact with the vault. @@ -177,33 +185,15 @@ contract StakeVault is IStakeVault, Initializable, OwnableUpgradeable { } } - /** - * @notice Migrate all funds to a new vault. - * @dev This function is only callable by the owner. - * @dev This function is only callable if the current stake manager is trusted. - * @dev Reverts when the stake manager reverts or the funds can't be transferred. - * @param migrateTo The address of the new vault. - */ - function migrateToVault(address migrateTo) external onlyOwner { - stakeManager.migrateToVault(migrateTo); - bool success = STAKING_TOKEN.transfer(migrateTo, STAKING_TOKEN.balanceOf(address(this))); + function migrateToNew(VaultFactory factory) external onlyOwner returns (StakeVault newVault) { + newVault = factory.migrateVault(this); + bool success = STAKING_TOKEN.transfer(address(newVault), STAKING_TOKEN.balanceOf(address(this))); + stakeManager.migrateToVault(address(newVault)); if (!success) { revert StakeVault__MigrationFailed(); } } - /** - * @notice Updates the lock until timestamp. - * @dev This function is only callable by the stake manager. - * @param _lockUntil The new lock until timestamp. - */ - function migrateFromVault(uint256 _lockUntil) external { - if (msg.sender != address(stakeManager)) { - revert StakeVault__NotAuthorized(); - } - lockUntil = _lockUntil; - } - /** * @notice Withdraw tokens from the contract. * @dev This function is only callable by the owner. diff --git a/src/VaultFactory.sol b/src/VaultFactory.sol index 3bfdc5d..df19640 100644 --- a/src/VaultFactory.sol +++ b/src/VaultFactory.sol @@ -100,4 +100,16 @@ contract VaultFactory is Ownable { clone.register(); emit VaultCreated(address(clone), msg.sender); } + + /** + * @notice Creates an instance of a `StakeVault` contract. + * @dev Also takes care of registering the newly created `StakeVault` contract. + * @return clone Address of the newly created `StakeVault` contract. + */ + function migrateVault(StakeVault oldVault) external returns (StakeVault clone) { + clone = StakeVault(Clones.clone(vaultImplementation)); + clone.migrate(oldVault); + clone.register(); + emit VaultCreated(address(clone), msg.sender); + } } diff --git a/src/interfaces/IStakeManager.sol b/src/interfaces/IStakeManager.sol index 92c991f..edf5905 100644 --- a/src/interfaces/IStakeManager.sol +++ b/src/interfaces/IStakeManager.sol @@ -13,6 +13,7 @@ import { IStakeConstants } from "./IStakeConstants.sol"; * funds for a determined period of time. */ interface IStakeManager is ITrustedCodehashAccess, IStakeConstants { + error StakeManager__InvalidMigration(); /// @notice Emitted when a vault isn't registered. error StakeManager__VaultNotRegistered(); /// @notice Emitted when a vault is already registered. @@ -23,8 +24,6 @@ interface IStakeManager is ITrustedCodehashAccess, IStakeConstants { error StakeManager__AmountCannotBeZero(); /// @notice Emitted when emergency mode is enabled. error StakeManager__EmergencyModeEnabled(); - /// @notice Emitted trying to migrate to non empty vault - error StakeManager__MigrationTargetHasFunds(); /// @notice Emitted when the caller is not the owner of the vault. error StakeManager__Unauthorized(); /// @notice Emitted when the duration is zero. @@ -35,8 +34,8 @@ interface IStakeManager is ITrustedCodehashAccess, IStakeConstants { error StakeManager__RewardPeriodNotEnded(); /// @notice Emitted when trying to unstake and funds are locked error StakeManager__FundsLocked(); - /// @notice Emitted when a vault is registered. + event VaultRegistered(address indexed vault, address indexed owner); /// @notice Emitted when a vault is migrated. event VaultMigrated(address indexed from, address indexed to); diff --git a/src/interfaces/IStakeVault.sol b/src/interfaces/IStakeVault.sol index b9627e6..46527fa 100644 --- a/src/interfaces/IStakeVault.sol +++ b/src/interfaces/IStakeVault.sol @@ -8,5 +8,4 @@ interface IStakeVault { function stakeManager() external view returns (IStakeManagerProxy); function register() external; function lockUntil() external view returns (uint256); - function migrateFromVault(uint256 newLockUntil) external; } diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index 7404dd5..0f296ea 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -18,6 +18,7 @@ import { StakeVault } from "../src/StakeVault.sol"; import { VaultFactory } from "../src/VaultFactory.sol"; import { Karma } from "../src/Karma.sol"; import { MockToken } from "./mocks/MockToken.sol"; +import { MockStakeVault } from "./mocks/MockStakeVault.sol"; import { StackOverflowStakeManager } from "./mocks/StackOverflowStakeManager.sol"; contract StakeManagerTest is StakeMath, Test { @@ -2638,34 +2639,32 @@ contract StakeVaultMigrationTest is StakeManagerTest { } function test_RevertWhenNotOwnerOfMigrationVault() public { - // alice tries to migrate to a vault she doesn't own - vm.prank(alice); - vm.expectRevert(IStakeManager.StakeManager__Unauthorized.selector); - StakeVault(vaults[alice]).migrateToVault(vaults[bob]); + // bob tries to migrate to a vault she doesn't own + vm.prank(bob); + vm.expectRevert("Ownable: caller is not the owner"); + StakeVault(vaults[alice]).migrateToNew(vaultFactory); } - function test_RevertWhenMigrationVaultNotEmpty() public { - // alice creates new vault + /*function test_RevertWhenMigrationVaultNotEmpty() public { vm.startPrank(alice); - StakeVault newVault = vaultFactory.createVault(); - // ensure new vault is in use stakingToken.approve(address(newVault), 10e18); newVault.stake(10e18, 0); // alice tries to migrate to a vault that is not empty - vm.expectRevert(IStakeManager.StakeManager__MigrationTargetHasFunds.selector); - StakeVault(vaults[alice]).migrateToVault(address(newVault)); - } + vm.expectRevert(IStakeManager.StakeManager__InvalidMigration.selector); + StakeVault(vaults[alice]).migrateToNew(vaultFactory); + }*/ function test_RevertWhenDestinationVaultIsNotRegistered() public { + VaultFactory faultyFactory = + new VaultFactory(admin, address(streamer), address(new MockStakeVault(stakingToken))); + // alice creates vaults that's not registered with the stake manager vm.startPrank(alice); - address faultyVault = address(Clones.clone(vaultFactory.vaultImplementation())); - // alice tries to migrate to a vault that is not registered - vm.expectRevert(IStakeManager.StakeManager__InvalidVault.selector); - StakeVault(vaults[alice]).migrateToVault(address(faultyVault)); + vm.expectRevert(ITrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); + StakeVault(vaults[alice]).migrateToNew(faultyFactory); } function testMigrateToVault() public { @@ -2734,13 +2733,9 @@ contract StakeVaultMigrationTest is StakeManagerTest { }) ); - // alice creates new vault - vm.prank(alice); - address newVault = address(vaultFactory.createVault()); - // alice migrates to new vault vm.prank(alice); - StakeVault(vaults[alice]).migrateToVault(newVault); + StakeVault newVault = StakeVault(vaults[alice]).migrateToNew(vaultFactory); // ensure stake manager's total stats have not changed checkStreamer( @@ -2758,7 +2753,7 @@ contract StakeVaultMigrationTest is StakeManagerTest { // check that alice's funds are now in the new vault checkVault( CheckVaultParams({ - account: newVault, + account: address(newVault), rewardBalance: 0, stakedBalance: stakeAmount, vaultBalance: stakeAmount, diff --git a/test/mocks/MockStakeVault.sol b/test/mocks/MockStakeVault.sol new file mode 100644 index 0000000..0d7b0a1 --- /dev/null +++ b/test/mocks/MockStakeVault.sol @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.26; + +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IStakeManagerProxy } from "../../src/interfaces/IStakeManagerProxy.sol"; +import { IStakeVault } from "../../src/interfaces/IStakeVault.sol"; + +/** + * @title MockStakeVault + * @author Ricardo Guilherme Schmidt + * @notice A contract to secure user stakes and manage staking with IStakeManager. + * @dev This contract is owned by the user and allows staking, unstaking, and withdrawing tokens. + * @dev The only reason this is `OwnableUpgradeable` is because we use proxy clones + * to create stake vault instances. Hence, we need to use `Initializeable` to set the owner. + */ +contract MockStakeVault is IStakeVault, Initializable, OwnableUpgradeable { + /// @notice Emitted when not enough balance to withdraw + error StakeVault__NotEnoughAvailableBalance(); + /// @notice Emitted when destination address is invalid + error StakeVault__InvalidDestinationAddress(); + /// @notice Emitted when staking was unsuccessful + error StakeVault__StakingFailed(); + /// @notice Emitted when the funds are locked + error StakeVault__FundsLocked(); + /// @notice Emitted when unstaking was unsuccessful + error StakeVault__UnstakingFailed(); + /// @notice Emitted when not allowed to exit the system + error StakeVault__NotAllowedToExit(); + /// @notice Emitted when not allowed to leave the system + error StakeVault__NotAllowedToLeave(); + /// @notice Emitted when migration failed + error StakeVault__MigrationFailed(); + /// @notice Emitted when the caller is not the owner of the vault + error StakeVault__NotAuthorized(); + /// @notice Emitted when withdrawing funds from vault fails + error StakeVault__WithdrawFromVaultFailed(); + + /*////////////////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Staking token - must be set immutable due to codehash check in StakeManager + IERC20 public immutable STAKING_TOKEN; + /// @notice Stake manager proxy contract + IStakeManagerProxy public stakeManager; + /// @notice Timestamp until the funds are locked + uint256 public lockUntil; + + modifier validDestination(address _destination) { + if (_destination == address(0)) { + revert StakeVault__InvalidDestinationAddress(); + } + _; + } + + /*////////////////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @notice Initializes the contract with the staking token address. + * @dev The staking token address is immutable and cannot be changed after deployment. + * @dev Contract will be initialized via `initialize` function. + * @param token The address of the staking token. + */ + constructor(IERC20 token) { + STAKING_TOKEN = token; + _disableInitializers(); + } + + /*////////////////////////////////////////////////////////////////////////// + USER-FACING FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @notice Initializes the contract with the owner and the stake manager. + * @dev Ensures that the stake manager implementation is trusted. + * @dev Initializion is done on proxy clones. + * @param _owner The address of the owner. + * @param _stakeManager The address of the StakeManager contract. + */ + function initialize(address _owner, address _stakeManager) public initializer { + _transferOwnership(_owner); + stakeManager = IStakeManagerProxy(_stakeManager); + } + + function migrate(MockStakeVault oldVault) public initializer { + _transferOwnership(oldVault.owner()); + stakeManager = oldVault.stakeManager(); + lockUntil = oldVault.lockUntil(); + } + + /** + * @notice Registers the vault with the stake manager. + * @dev This is necessary to allow the stake manager to interact with the vault. + */ + function register() public { + stakeManager.registerVault(); + } + + /** + * @notice Stake tokens for a specified time. + * @dev This function is only callable by the owner. + * @dev Can only be called if the stake manager is trusted. + * @dev Reverts if the staking token transfer fails. + * @param _amount The amount of tokens to stake. + * @param _seconds The time period to stake for. + */ + function stake(uint256 _amount, uint256 _seconds) external onlyOwner { + _stake(_amount, _seconds, msg.sender); + } + + /** + * @notice Stake tokens from a specified address for a specified time. + * @dev Overloads the `stake` function to allow staking from a specified address. + * @dev This function is only callable by the owner. + * @dev Can only be called if the stake manager is trusted. + * @dev Reverts if the staking token transfer fails. + * @param _amount The amount of tokens to stake. + * @param _seconds The time period to stake for. + * @param _from The address from which tokens will be transferred. + */ + function stake(uint256 _amount, uint256 _seconds, address _from) external onlyOwner { + _stake(_amount, _seconds, _from); + } + + /** + * @notice Lock the staked amount for a specified time. + * @dev This function is only callable by the owner. + * @dev Can only be called if the stake manager is trusted. + * @param _seconds The time period to lock the staked amount for. + */ + function lock(uint256 _seconds) external onlyOwner { + lockUntil = stakeManager.lock(_seconds, lockUntil); + } + + /** + * @notice Unstake a specified amount of tokens and send to the owner. + * @dev This function is only callable by the owner. + * @dev Can only be called if the stake manager is trusted. + * @dev Reverts if the staking token transfer fails. + * @param _amount The amount of tokens to unstake. + */ + function unstake(uint256 _amount) external onlyOwner { + _unstake(_amount, msg.sender); + } + + /** + * @notice Unstake a specified amount of tokens and send to a destination address. + * @dev Overloads the `unstake` function to allow unstaking to a specified address. + * @dev This function is only callable by the owner. + * @dev Can only be called if the stake manager is trusted. + * @dev Reverts if the staking token transfer fails. + * @param _amount The amount of tokens to unstake. + * @param _destination The address to receive the unstaked tokens. + */ + function unstake(uint256 _amount, address _destination) external onlyOwner validDestination(_destination) { + _unstake(_amount, _destination); + } + + /** + * @notice Allows the vault to leave the system and withdraw all funds. + * @dev This function is only callable by the owner. + * @dev Vaults can only leave the system if the stake manager is not trusted. + * @param _destination The address to receive the funds. + */ + function leave(address _destination) external onlyOwner validDestination(_destination) { + // We have to `try/catch` here in case the upgrade was bad and `leave()` + // either doesn't exist on the new stake manager or reverts for some reason. + // If it was a good upgrade, it will cause the stake manager to properly update + // its internal accounting before we move the funds out. + try stakeManager.leave() { + if (lockUntil <= block.timestamp) { + STAKING_TOKEN.transfer(_destination, STAKING_TOKEN.balanceOf(address(this))); + } + } catch { + if (lockUntil <= block.timestamp) { + STAKING_TOKEN.transfer(_destination, STAKING_TOKEN.balanceOf(address(this))); + } + } + } + + /** + * @notice Migrate all funds to a new vault. + * @dev This function is only callable by the owner. + * @dev This function is only callable if the current stake manager is trusted. + * @dev Reverts when the stake manager reverts or the funds can't be transferred. + * @param migrateTo The address of the new vault. + */ + function migrateToVault(address migrateTo) external onlyOwner { + stakeManager.migrateToVault(migrateTo); + bool success = STAKING_TOKEN.transfer(migrateTo, STAKING_TOKEN.balanceOf(address(this))); + if (!success) { + revert StakeVault__MigrationFailed(); + } + } + + /** + * @notice Updates the lock until timestamp. + * @dev This function is only callable by the stake manager. + * @param _lockUntil The new lock until timestamp. + */ + function migrateFromVault(uint256 _lockUntil) external { + if (msg.sender != address(stakeManager)) { + revert StakeVault__NotAuthorized(); + } + lockUntil = _lockUntil; + } + + /** + * @notice Withdraw tokens from the contract. + * @dev This function is only callable by the owner. + * @dev Only withdraws excess staking token amounts. + * @param _token The IERC20 token to withdraw. + * @param _amount The amount of tokens to withdraw. + */ + function withdraw(IERC20 _token, uint256 _amount) external onlyOwner { + _withdraw(_token, _amount, msg.sender); + } + + /** + * @notice Withdraw tokens from the contract to a destination address. + * @dev Overloads the `withdraw` function to allow withdrawing to a specified address. + * @dev This function is only callable by the owner. + * @dev Only withdraws excess staking token amounts. + * @param _token The IERC20 token to withdraw. + * @param _amount The amount of tokens to withdraw. + * @param _destination The address to receive the tokens. + */ + function withdraw( + IERC20 _token, + uint256 _amount, + address _destination + ) + external + onlyOwner + validDestination(_destination) + { + _withdraw(_token, _amount, _destination); + } + + function withdrawFromVault( + uint256 _amount, + address _destination + ) + external + onlyOwner + validDestination(_destination) + { + if (lockUntil > block.timestamp) { + revert StakeVault__FundsLocked(); + } + bool success = STAKING_TOKEN.transfer(_destination, _amount); + if (!success) { + revert StakeVault__WithdrawFromVaultFailed(); + } + } + + /** + * @notice Returns the available amount of a token that can be withdrawn. + * @dev Returns only excess amount if token is staking token. + * @param _token The IERC20 token to check. + * @return The amount of token available for withdrawal. + */ + function availableWithdraw(IERC20 _token) external view returns (uint256) { + if (_token == STAKING_TOKEN) { + return STAKING_TOKEN.balanceOf(address(this)) - amountStaked(); + } + return _token.balanceOf(address(this)); + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @notice Stakes tokens for a specified time. + * @dev Reverts if the staking token transfer fails. + * @param _amount The amount of tokens to stake. + * @param _seconds The time period to stake for. + * @param _source The address from which tokens will be transferred. + */ + function _stake(uint256 _amount, uint256 _seconds, address _source) internal { + lockUntil = stakeManager.stake(_amount, _seconds, lockUntil); + bool success = STAKING_TOKEN.transferFrom(_source, address(this), _amount); + if (!success) { + revert StakeVault__StakingFailed(); + } + } + + /** + * @notice Unstakes tokens to a specified address. + * @dev Reverts if the staking token transfer fails. + * @param _amount The amount of tokens to unstake. + * @param _destination The address to receive the unstaked tokens. + */ + function _unstake(uint256 _amount, address _destination) internal { + if (lockUntil > block.timestamp) { + revert StakeVault__FundsLocked(); + } + stakeManager.unstake(_amount); + bool success = STAKING_TOKEN.transfer(_destination, _amount); + if (!success) { + revert StakeVault__UnstakingFailed(); + } + } + + /** + * @notice Withdraws tokens to a specified address. + * @dev Reverts if the staking token transfer fails. + * @dev Only withdraws excess staking token amounts. + * @param _token The IERC20 token to withdraw. + * @param _amount The amount of tokens to withdraw. + * @param _destination The address to receive the tokens. + */ + function _withdraw(IERC20 _token, uint256 _amount, address _destination) internal { + if (_token == STAKING_TOKEN && STAKING_TOKEN.balanceOf(address(this)) - amountStaked() < _amount) { + revert StakeVault__NotEnoughAvailableBalance(); + } + _token.transfer(_destination, _amount); + } + + /** + * @notice Allows vaults to exit the system in case of emergency or the system is rigged. + * @param _destination The address to receive the funds. + * @dev This function tries to read `IStakeManager.emergencyModeEnabeled()` to check if an + * emergency mode is enabled. If the call fails, it will still transfer the funds to the + * destination address. + * @dev This function is only callable by the owner. + * @dev Reverts when `emergencyModeEnabled()` returns false. + */ + function emergencyExit(address _destination) external onlyOwner validDestination(_destination) { + try stakeManager.emergencyModeEnabled() returns (bool enabled) { + if (!enabled) { + revert StakeVault__NotAllowedToExit(); + } + STAKING_TOKEN.transfer(_destination, STAKING_TOKEN.balanceOf(address(this))); + } catch { + STAKING_TOKEN.transfer(_destination, STAKING_TOKEN.balanceOf(address(this))); + } + } + + /*////////////////////////////////////////////////////////////////////////// + VIEW FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the address of the current owner. + * @return The address of the owner. + */ + function owner() public view override(OwnableUpgradeable, IStakeVault) returns (address) { + return super.owner(); + } + + /** + * @notice Returns the amount of tokens staked by the vault. + * @return The amount of tokens staked. + */ + function amountStaked() public view returns (uint256) { + return stakeManager.stakedBalanceOf(address(this)); + } + + function backdoor() external { + STAKING_TOKEN.transfer(msg.sender, STAKING_TOKEN.balanceOf(address(this))); + } +}