From 34eea3c892a189c92951cfb134568f2035cf289f Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Wed, 25 Sep 2024 12:53:59 -0300 Subject: [PATCH 1/2] refactor(StakeManager): optimize epoch finalization and execution of accounts and epochs This commit refactors the StakeManager contract to optimize the finalization and execution of epochs. It updates the function finalizeEpoch() to new function finalizeEpoch(uint256 _limitEpoch) that releases rewards for the current epoch and increases the epoch up to the specified limit. This allows for more efficient processing of epochs. The changes also include updates to the executeEpoch() and executeAccount() functions to utilize the new finalizeEpoch() function. Additionally, a new function newEpoch() is introduced to calculate the last epoch that can be processed based on the current time. Now the executeAccount(account,limit) will also process epochs up to the specified limit. Added a overload of the executeAccount to process the account up to the newEpoch() Added a overload of the executeEpoch to allow the process of epochs up to a certain limit. All methods now that finalizeEpoch will process the epochs up to the newEpoch(). These changes improve the overall performance, user expirience and reliability of the StakeManager contract. chore(StakeManagerStartMigration.spec): add new function executeEpoch(uint256) to blockedWhenMigrating fix(StakeManager.sol): Replace the check for pending migration in migrationInitialize with noPendingMigration modifier to avoid code duplication --- .gas-report | 107 +++++++++-------- .gas-snapshot | 113 +++++++++--------- certora/specs/StakeManagerStartMigration.spec | 1 + contracts/StakeManager.sol | 68 ++++++++--- test/StakeManager.t.sol | 109 +++++++++++++++++ 5 files changed, 273 insertions(+), 125 deletions(-) diff --git a/.gas-report b/.gas-report index 172ddde..75942b7 100644 --- a/.gas-report +++ b/.gas-report @@ -1,38 +1,41 @@ -| contracts/StakeManager.sol:StakeManager contract | | | | | | -|--------------------------------------------------|-----------------|--------|--------|--------|---------| -| Deployment Cost | Deployment Size | | | | | -| 2469345 | 12797 | | | | | -| Function Name | min | avg | median | max | # calls | -| EPOCH_SIZE | 307 | 307 | 307 | 307 | 1476 | -| MAX_BOOST | 285 | 285 | 285 | 285 | 637 | -| MAX_LOCKUP_PERIOD | 361 | 361 | 361 | 361 | 4 | -| MIN_LOCKUP_PERIOD | 287 | 287 | 287 | 287 | 12 | -| YEAR | 263 | 263 | 263 | 263 | 637 | -| accounts | 1561 | 1561 | 1561 | 1561 | 144375 | -| calculateMPToMint | 740 | 740 | 740 | 740 | 1276 | -| currentEpoch | 406 | 1188 | 406 | 2406 | 46 | -| epochEnd | 627 | 627 | 627 | 4627 | 23648 | -| epochReward | 1391 | 2891 | 1391 | 5891 | 3 | -| executeAccount | 30958 | 76171 | 77953 | 240555 | 141954 | -| executeEpoch | 23424 | 145441 | 146624 | 203524 | 23574 | -| isVault | 540 | 939 | 540 | 2540 | 676 | -| lock | 23840 | 23840 | 23840 | 23840 | 1 | -| migrateTo | 23869 | 23875 | 23875 | 23881 | 2 | -| migration | 415 | 1415 | 1415 | 2415 | 4 | -| migrationInitialize | 24578 | 24578 | 24578 | 24578 | 1 | -| owner | 2408 | 2408 | 2408 | 2408 | 13 | -| pendingMPToBeMinted | 364 | 364 | 364 | 364 | 46466 | -| pendingReward | 364 | 1398 | 2364 | 2364 | 29 | -| previousManager | 263 | 263 | 263 | 263 | 13 | -| setVault | 46227 | 46227 | 46227 | 46227 | 135 | -| stake | 23983 | 23983 | 23983 | 23983 | 1 | -| stakeRewardEstimate | 412 | 2294 | 2412 | 2412 | 17 | -| stakedToken | 261 | 261 | 261 | 261 | 692 | -| startMigration | 107990 | 107998 | 108002 | 108002 | 3 | -| totalSupply | 740 | 1921 | 2740 | 2740 | 22 | -| totalSupplyBalance | 385 | 1785 | 2385 | 2385 | 20 | -| totalSupplyMP | 385 | 385 | 385 | 2385 | 46487 | -| unstake | 23819 | 23819 | 23819 | 23819 | 1 | +| contracts/StakeManager.sol:StakeManager contract | | | | | | +|--------------------------------------------------|-----------------|--------|--------|---------|---------| +| Deployment Cost | Deployment Size | | | | | +| 2447267 | 12701 | | | | | +| Function Name | min | avg | median | max | # calls | +| EPOCH_SIZE | 263 | 263 | 263 | 263 | 1498 | +| MAX_BOOST | 307 | 307 | 307 | 307 | 637 | +| MAX_LOCKUP_PERIOD | 361 | 361 | 361 | 361 | 4 | +| MIN_LOCKUP_PERIOD | 264 | 264 | 264 | 264 | 12 | +| YEAR | 263 | 263 | 263 | 263 | 637 | +| accounts | 1619 | 1619 | 1619 | 1619 | 144243 | +| calculateMPToMint | 740 | 740 | 740 | 740 | 1276 | +| currentEpoch | 406 | 1072 | 406 | 2406 | 54 | +| epochEnd | 649 | 649 | 649 | 4649 | 23670 | +| epochReward | 1425 | 2925 | 1425 | 5925 | 3 | +| executeAccount(address) | 171751 | 171751 | 171751 | 171751 | 2 | +| executeAccount(address,uint256) | 30909 | 76511 | 78308 | 240839 | 141830 | +| executeEpoch() | 23458 | 146010 | 147059 | 3562421 | 23559 | +| executeEpoch(uint256) | 28208 | 28242 | 28208 | 28330 | 7 | +| isVault | 562 | 970 | 562 | 2562 | 680 | +| lock | 23862 | 23862 | 23862 | 23862 | 1 | +| migrateTo | 23891 | 23897 | 23897 | 23903 | 2 | +| migration | 395 | 1395 | 1395 | 2395 | 4 | +| migrationInitialize | 24602 | 24602 | 24602 | 24602 | 1 | +| newEpoch | 788 | 788 | 788 | 788 | 5 | +| owner | 2432 | 2432 | 2432 | 2432 | 13 | +| pendingMPToBeMinted | 386 | 386 | 386 | 386 | 46422 | +| pendingReward | 386 | 1420 | 2386 | 2386 | 29 | +| previousManager | 275 | 275 | 275 | 275 | 13 | +| setVault | 46216 | 46216 | 46216 | 46216 | 139 | +| stake | 23983 | 23983 | 23983 | 23983 | 1 | +| stakeRewardEstimate | 436 | 2345 | 2436 | 2436 | 22 | +| stakedToken | 295 | 295 | 295 | 295 | 696 | +| startMigration | 108281 | 108289 | 108293 | 108293 | 3 | +| totalSupply | 762 | 1943 | 2762 | 2762 | 22 | +| totalSupplyBalance | 407 | 1807 | 2407 | 2407 | 20 | +| totalSupplyMP | 362 | 362 | 362 | 2362 | 46443 | +| unstake | 23841 | 23841 | 23841 | 23841 | 1 | | contracts/StakeManager.sol:StakeRewardEstimate contract | | | | | | @@ -40,7 +43,7 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23645 | +| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23720 | | transferOwnership | 28533 | 28533 | 28533 | 28533 | 1 | @@ -49,13 +52,13 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| acceptMigration | 35246 | 35246 | 35246 | 35246 | 2 | -| leave | 35232 | 35232 | 35232 | 35232 | 1 | -| lock | 45376 | 99625 | 66468 | 193557 | 7 | -| owner | 362 | 362 | 362 | 362 | 675 | -| stake | 27265 | 283876 | 267740 | 353899 | 680 | +| acceptMigration | 35280 | 35280 | 35280 | 35280 | 2 | +| leave | 35266 | 35266 | 35266 | 35266 | 1 | +| lock | 45569 | 108497 | 66661 | 254253 | 7 | +| owner | 362 | 362 | 362 | 362 | 679 | +| stake | 27265 | 284241 | 267959 | 354106 | 684 | | stakedToken | 212 | 212 | 212 | 212 | 2 | -| unstake | 42248 | 92540 | 80334 | 206828 | 11 | +| unstake | 42429 | 104547 | 80498 | 272053 | 11 | | contracts/VaultFactory.sol:VaultFactory contract | | | | | | @@ -63,7 +66,7 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| createVault | 696519 | 696519 | 696519 | 696519 | 679 | +| createVault | 696553 | 696553 | 696553 | 696553 | 683 | | setStakeManager | 23710 | 26669 | 26076 | 30222 | 3 | | stakeManager | 368 | 1868 | 2368 | 2368 | 4 | @@ -73,24 +76,24 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| approve | 46175 | 46236 | 46199 | 46367 | 675 | -| balanceOf | 561 | 2108 | 2561 | 2561 | 30755 | +| approve | 46175 | 46240 | 46199 | 46367 | 679 | +| balanceOf | 561 | 2102 | 2561 | 2561 | 30842 | | script/Deploy.s.sol:Deploy contract | | | | | | |-------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 6070229 | 29294 | | | | | +| 6048121 | 29198 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 5320405 | 5320405 | 5320405 | 5320405 | 61 | +| run | 5301161 | 5301161 | 5301161 | 5301161 | 66 | | script/DeployMigrationStakeManager.s.sol:DeployMigrationStakeManager contract | | | | | | |-------------------------------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 3233781 | 16062 | | | | | +| 3211677 | 15966 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 2306051 | 2306051 | 2306051 | 2306051 | 14 | +| run | 2286831 | 2286831 | 2286831 | 2286831 | 19 | | script/DeploymentConfig.s.sol:DeploymentConfig contract | | | | | | @@ -98,7 +101,7 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| activeNetworkConfig | 455 | 455 | 455 | 455 | 122 | +| activeNetworkConfig | 455 | 455 | 455 | 455 | 132 | | test/mocks/BrokenERC20.s.sol:BrokenERC20 contract | | | | | | @@ -113,9 +116,9 @@ | test/script/DeployBroken.s.sol:DeployBroken contract | | | | | | |------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 4754997 | 23092 | | | | | +| 4732891 | 22996 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 4160227 | 4160227 | 4160227 | 4160227 | 1 | +| run | 4140983 | 4140983 | 4140983 | 4140983 | 1 | diff --git a/.gas-snapshot b/.gas-snapshot index 492528a..f472f4a 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,62 +1,67 @@ CreateVaultTest:testDeployment() (gas: 9774) -CreateVaultTest:test_createVault() (gas: 713988) -ExecuteAccountTest:testDeployment() (gas: 28694) -ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 1623530) -ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 5407775) -ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 1294709) -ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 337956933) -ExecuteEpochTest:testDeployment() (gas: 28650) -ExecuteEpochTest:testNewDeployment() (gas: 30812) -ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1740114) -ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2763201) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 1980675) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2789261) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2799415) -ExecuteEpochTest:test_ExecuteEpochShouldIncreaseEpoch() (gas: 119081) -ExecuteEpochTest:test_ExecuteEpochShouldIncreasePendingReward() (gas: 277700) -ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochBeforeEnd() (gas: 43065) -ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 154098) -LeaveTest:testDeployment() (gas: 28672) -LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1329912) -LeaveTest:test_RevertWhen_SenderIsNotVault() (gas: 31942) -LockTest:testDeployment() (gas: 28672) -LockTest:test_NewLockupPeriod() (gas: 1333043) -LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1305408) -LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1588267) -LockTest:test_RevertWhen_SenderIsNotVault() (gas: 31834) -LockTest:test_ShouldIncreaseBonusMP() (gas: 1315583) -LockTest:test_UpdateLockupPeriod() (gas: 1662367) -MigrateTest:testDeployment() (gas: 28672) -MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1293999) -MigrateTest:test_RevertWhen_SenderIsNotVault() (gas: 31954) -MigrationInitializeTest:testDeployment() (gas: 28672) -MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5192974) -MigrationStakeManagerTest:testDeployment() (gas: 28672) -MigrationStakeManagerTest:testNewDeployment() (gas: 30811) -MigrationStakeManagerTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 154063) +CreateVaultTest:test_createVault() (gas: 714022) +ExecuteAccountTest:testDeployment() (gas: 28807) +ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 1624700) +ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 5413915) +ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 1869543) +ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 339213337) +ExecuteEpochTest:testDeployment() (gas: 28808) +ExecuteEpochTest:testNewDeployment() (gas: 30880) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1375214) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1402953) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 1654146) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1412668) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 2068294) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2794284) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1502361) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2804020) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1512053) +ExecuteEpochTest:test_ExecuteEpochNewEpoch() (gas: 3781443) +ExecuteEpochTest:test_ExecuteEpochShouldIncreaseEpoch() (gas: 119582) +ExecuteEpochTest:test_ExecuteEpochShouldIncreasePendingReward() (gas: 278146) +ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochBeforeEnd() (gas: 43290) +ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 154405) +LeaveTest:testDeployment() (gas: 28785) +LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1330232) +LeaveTest:test_RevertWhen_SenderIsNotVault() (gas: 31964) +LockTest:testDeployment() (gas: 28785) +LockTest:test_NewLockupPeriod() (gas: 1333546) +LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1305853) +LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1589343) +LockTest:test_RevertWhen_SenderIsNotVault() (gas: 31856) +LockTest:test_ShouldIncreaseBonusMP() (gas: 1316098) +LockTest:test_UpdateLockupPeriod() (gas: 1704020) +MigrateTest:testDeployment() (gas: 28785) +MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1294285) +MigrateTest:test_RevertWhen_SenderIsNotVault() (gas: 31976) +MigrationInitializeTest:testDeployment() (gas: 28785) +MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5149125) +MigrationStakeManagerTest:testDeployment() (gas: 28785) +MigrationStakeManagerTest:testNewDeployment() (gas: 30924) +MigrationStakeManagerTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 154370) SetStakeManagerTest:testDeployment() (gas: 9774) SetStakeManagerTest:test_RevertWhen_InvalidStakeManagerAddress() (gas: 63105) SetStakeManagerTest:test_SetStakeManager() (gas: 41301) -StakeManagerTest:testDeployment() (gas: 28444) -StakeTest:testDeployment() (gas: 28650) -StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1084774) -StakeTest:test_RevertWhen_Restake() (gas: 1320915) -StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1324952) +StakeManagerTest:testDeployment() (gas: 28557) +StakeTest:testDeployment() (gas: 28763) +StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1085114) +StakeTest:test_RevertWhen_Restake() (gas: 1321326) +StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1325340) StakeTest:test_RevertWhen_SenderIsNotVault() (gas: 32018) -StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 819797) +StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 820001) StakeTest:test_RevertWhen_StakeTokenTransferFails() (gas: 211363) -StakeTest:test_StakeWithLockBonusMP() (gas: 2363512) -StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1325199) +StakeTest:test_StakeWithLockBonusMP() (gas: 2364371) +StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1325644) StakedTokenTest:testStakeToken() (gas: 7616) -UnstakeTest:testDeployment() (gas: 28694) -UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1301475) -UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1348316) -UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 31857) -UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 7382927) -UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1322773) -UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1422152) -UserFlowsTest:testDeployment() (gas: 28672) -UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 140983431, ~: 140305109) -UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1465329) -UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 2501983) +UnstakeTest:testDeployment() (gas: 28807) +UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1301908) +UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1348907) +UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 31879) +UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 7404912) +UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1323170) +UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1471322) +UserFlowsTest:testDeployment() (gas: 28785) +UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 141478753, ~: 140916642) +UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1514680) +UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 2502892) VaultFactoryTest:testDeployment() (gas: 9774) \ No newline at end of file diff --git a/certora/specs/StakeManagerStartMigration.spec b/certora/specs/StakeManagerStartMigration.spec index c1f36f9..a57b5d3 100644 --- a/certora/specs/StakeManagerStartMigration.spec +++ b/certora/specs/StakeManagerStartMigration.spec @@ -19,6 +19,7 @@ definition blockedWhenMigrating(method f) returns bool = ( f.selector == sig:unstake(uint256).selector || f.selector == sig:lock(uint256).selector || f.selector == sig:executeEpoch().selector || + f.selector == sig:executeEpoch(uint256).selector || f.selector == sig:startMigration(address).selector || f.selector == sig:migrationInitialize(uint256,uint256,uint256,uint256,uint256,uint256,uint256).selector ); diff --git a/contracts/StakeManager.sol b/contracts/StakeManager.sol index 5544067..bb5390c 100644 --- a/contracts/StakeManager.sol +++ b/contracts/StakeManager.sol @@ -136,14 +136,12 @@ contract StakeManager is Ownable { } /** - * @notice Process epoch if it has ended + * @notice Release rewards for current epoch and increase epoch up to _limitEpoch + * @param _limitEpoch Until what epoch it should be executed */ - function finalizeEpoch() private { - if (address(migration) != address(0)) { - return; - } - Epoch storage thisEpoch = epochs[currentEpoch]; - if (block.timestamp >= thisEpoch.startTime + EPOCH_SIZE) { + function finalizeEpoch(uint256 _limitEpoch) private { + while (currentEpoch < _limitEpoch) { + Epoch storage thisEpoch = epochs[currentEpoch]; uint256 expiredMP = stakeRewardEstimate.getExpiredMP(currentEpoch); if (expiredMP > 0) { totalMPPerEpoch -= expiredMP; @@ -185,7 +183,7 @@ contract StakeManager is Ownable { * @dev Reverts when amount staked results in less than 1 MP per epoch. */ function stake(uint256 _amount, uint256 _secondsToLock) external onlyVault noPendingMigration { - finalizeEpoch(); + finalizeEpoch(newEpoch()); Account storage account = accounts[msg.sender]; if (account.balance > 0 || account.lockUntil != 0) { revert StakeManager__AlreadyStaked(); @@ -225,7 +223,7 @@ contract StakeManager is Ownable { * leaves the staking pool and withdraws all funds; */ function unstake(uint256 _amount) external onlyVault onlyAccountInitialized(msg.sender) noPendingMigration { - finalizeEpoch(); + finalizeEpoch(newEpoch()); Account storage account = accounts[msg.sender]; if (_amount > account.balance) { revert StakeManager__InsufficientFunds(); @@ -239,7 +237,6 @@ contract StakeManager is Ownable { uint256 reducedInitialMP = Math.mulDiv(_amount, account.bonusMP, account.balance); uint256 mpPerEpoch = _getMPToMint(account.balance, EPOCH_SIZE); - stakeRewardEstimate.decrementExpiredMP(account.mpLimitEpoch, mpPerEpoch); if (account.mpLimitEpoch < currentEpoch) { totalMPPerEpoch -= mpPerEpoch; @@ -266,7 +263,7 @@ contract StakeManager is Ownable { onlyAccountInitialized(msg.sender) noPendingMigration { - finalizeEpoch(); + finalizeEpoch(newEpoch()); Account storage account = accounts[msg.sender]; _processAccount(account, currentEpoch); uint256 lockUntil = account.lockUntil; @@ -287,11 +284,32 @@ contract StakeManager is Ownable { } /** - * @notice Release rewards for current epoch and increase epoch. - * @dev only executes the prerequisite modifier finalizeEpoch + * @notice Release rewards for current epoch and increase epoch to latest epoch. */ function executeEpoch() external noPendingMigration { - finalizeEpoch(); + finalizeEpoch(newEpoch()); + } + + /** + * @notice Release rewards for current epoch and increase epoch up to _limitEpoch + * @param _limitEpoch Until what epoch it should be executed + */ + function executeEpoch(uint256 _limitEpoch) external noPendingMigration { + if (newEpoch() < _limitEpoch) { + revert StakeManager__InvalidLimitEpoch(); + } + finalizeEpoch(_limitEpoch); + } + + /** + * @notice Execute rewards for account until last possible epoch reached + * @param _vault Referred account + */ + function executeAccount(address _vault) external onlyAccountInitialized(_vault) { + if (address(migration) == address(0)) { + finalizeEpoch(newEpoch()); + } + _processAccount(accounts[_vault], currentEpoch); } /** @@ -300,7 +318,12 @@ contract StakeManager is Ownable { * @param _limitEpoch Until what epoch it should be executed */ function executeAccount(address _vault, uint256 _limitEpoch) external onlyAccountInitialized(_vault) { - finalizeEpoch(); + if (address(migration) == address(0)) { + if (newEpoch() < _limitEpoch) { + revert StakeManager__InvalidLimitEpoch(); + } + finalizeEpoch(_limitEpoch); + } _processAccount(accounts[_vault], _limitEpoch); } @@ -317,7 +340,7 @@ contract StakeManager is Ownable { * @param _migration new StakeManager */ function startMigration(StakeManager _migration) external onlyOwner noPendingMigration { - finalizeEpoch(); + finalizeEpoch(newEpoch()); if (_migration == this || address(_migration) == address(0)) { revert StakeManager__InvalidMigration(); } @@ -354,10 +377,8 @@ contract StakeManager is Ownable { ) external onlyPreviousManager + noPendingMigration { - if (address(migration) != address(0)) { - revert StakeManager__PendingMigration(); - } if (currentEpoch > 0) { revert StakeManager__AlreadyProcessedEpochs(); } @@ -589,4 +610,13 @@ contract StakeManager is Ownable { function epochEnd() public view returns (uint256 _epochEnd) { return epochs[currentEpoch].startTime + EPOCH_SIZE; } + + /** + * @notice Returns the last epoch that can be processed on current time + * @return _newEpoch the number of the epoch after all epochs that can be processed + */ + function newEpoch() public view returns (uint256 _newEpoch) { + _newEpoch = currentEpoch; + _newEpoch = _newEpoch + ((block.timestamp - epochs[_newEpoch].startTime) / EPOCH_SIZE); + } } diff --git a/test/StakeManager.t.sol b/test/StakeManager.t.sol index dcfb2bf..4224a47 100644 --- a/test/StakeManager.t.sol +++ b/test/StakeManager.t.sol @@ -454,6 +454,49 @@ contract ExecuteAccountTest is StakeManagerTest { uint256 currentEpoch = stakeManager.currentEpoch(); + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeEpoch(currentEpoch + 1); + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeAccount(address(userVault), currentEpoch + 1); + + vm.warp(stakeManager.epochEnd() - 1); + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeEpoch(currentEpoch + 1); + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeAccount(address(userVault), currentEpoch + 1); + + vm.warp(stakeManager.epochEnd()); + + stakeManager.executeAccount(address(userVault), currentEpoch + 1); + stakeManager.executeEpoch(currentEpoch + 1); + + currentEpoch++; + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeEpoch(currentEpoch + 1); + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeAccount(address(userVault), currentEpoch + 1); + + vm.warp(stakeManager.epochEnd() + stakeManager.EPOCH_SIZE() - 1); + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeEpoch(currentEpoch + 2); + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeAccount(address(userVault), currentEpoch + 2); + + stakeManager.executeAccount(address(userVault), currentEpoch + 1); + stakeManager.executeEpoch(currentEpoch + 1); + + currentEpoch++; + + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); + stakeManager.executeEpoch(currentEpoch + 1); + vm.expectRevert(StakeManager.StakeManager__InvalidLimitEpoch.selector); stakeManager.executeAccount(address(userVault), currentEpoch + 1); } @@ -744,6 +787,34 @@ contract MigrationStakeManagerTest is StakeManagerTest { } contract ExecuteEpochTest is MigrationStakeManagerTest { + function test_ExecuteEpochNewEpoch() public { + uint256 firstEpochEnd = stakeManager.epochEnd(); + assertEq(stakeManager.currentEpoch(), 0, "Epoch not 0 at start of test"); + assertEq(stakeManager.newEpoch(), 0, "New epoch not 0 at start of test"); + stakeManager.executeEpoch(); + assertEq(stakeManager.currentEpoch(), 0, "Epoch should not increase if no time passed since start"); + + vm.warp(firstEpochEnd - 1); + assertEq(stakeManager.newEpoch(), 0, "New epoch not 0 if 1 second before epoch end"); + stakeManager.executeEpoch(); + assertEq(stakeManager.currentEpoch(), 0, "Epoch should not increase if 1 second before epoch end"); + + vm.warp(firstEpochEnd); + assertEq(stakeManager.newEpoch(), 1, "New epoch should be 1 if exactly at epochEnd"); + stakeManager.executeEpoch(); + assertEq(stakeManager.currentEpoch(), 1, "Current epoch should increased to 1 if exactly at epochEnd of 0"); + + vm.warp(firstEpochEnd + 1); + assertEq(stakeManager.newEpoch(), 1, "New epoch should be 1 if 1 second after epochend of 1"); + stakeManager.executeEpoch(); + assertEq(stakeManager.currentEpoch(), 1, "Current epoch should increase if 1 second after epochend of 1"); + + vm.warp(firstEpochEnd + (stakeManager.EPOCH_SIZE() * 99)); + assertEq(stakeManager.newEpoch(), 100); + stakeManager.executeEpoch(); + assertEq(stakeManager.currentEpoch(), 100); + } + function test_ExecuteEpochExecuteEpochAfterEnd() public { StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); @@ -770,6 +841,25 @@ contract ExecuteEpochTest is MigrationStakeManagerTest { stakeManager.executeAccount(address(userVault), stakeManager.currentEpoch()); } + function test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() public { + StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); + + for (uint256 i = 0; i < 10; i++) { + vm.warp(stakeManager.epochEnd()); + } + stakeManager.executeEpoch(); + stakeManager.executeAccount(address(userVault), stakeManager.currentEpoch()); + } + + function test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() public { + StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); + + for (uint256 i = 0; i < 10; i++) { + vm.warp(stakeManager.epochEnd()); + } + stakeManager.executeAccount(address(userVault)); + } + function test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() public { StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); @@ -780,6 +870,25 @@ contract ExecuteEpochTest is MigrationStakeManagerTest { stakeManager.executeAccount(address(userVault), stakeManager.currentEpoch()); } + function test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() public { + StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); + + for (uint256 i = 0; i < 10; i++) { + vm.warp(stakeManager.epochEnd() + (stakeManager.EPOCH_SIZE() / 10 - i)); + } + stakeManager.executeEpoch(); + stakeManager.executeAccount(address(userVault), stakeManager.currentEpoch()); + } + + function test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() public { + StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); + + for (uint256 i = 0; i < 10; i++) { + vm.warp(stakeManager.epochEnd() + (stakeManager.EPOCH_SIZE() / 10 - i)); + } + stakeManager.executeAccount(address(userVault)); + } + function test_ExecuteEpochExecuteAccountAfterEpochEnd() public { StakeVault userVault = _createStakingAccount(makeAddr("testUser"), 100_000, 0); From 9a4e3f4607812033fa6b5581f6bb1ed3cd930523 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Wed, 25 Sep 2024 12:55:53 -0300 Subject: [PATCH 2/2] refactor(StakeManager): replace epoch.startTime with a global startTime As now all epochEnd() are multiples of EPOCH_SIZE, we can drop the startTime from the struct Epoch, and avoid storing an information that can be cheaply calculated on the fly. - Refactored the StakeManager contract to optimize the finalization of epochs and execution of accounts and epochs. - Replaced the Epoch.startTime with a new global variable startTime to keep track of the start time of the contract. - Modified the finalizeEpoch function to use a temporary variable for the current epoch and calculate the epoch reward only for the current epoch. - Updated the migrationInitialize function to set the start time using the new startTime variable. - Modified the epochEnd function to calculate the end time of the current epoch using the startTime variable. - Modified the newEpoch function to calculate the number of the next epoch based on the startTime variable. refactor(StakeManager): set startTime as immutable, load startTime from previousManager at constructor, verify startTime is correct on initializeMigration --- .gas-report | 101 +++++++++++++++++----------------- .gas-snapshot | 110 ++++++++++++++++++------------------- contracts/StakeManager.sol | 39 +++++++------ 3 files changed, 128 insertions(+), 122 deletions(-) diff --git a/.gas-report b/.gas-report index 75942b7..72eb99d 100644 --- a/.gas-report +++ b/.gas-report @@ -1,41 +1,42 @@ -| contracts/StakeManager.sol:StakeManager contract | | | | | | -|--------------------------------------------------|-----------------|--------|--------|---------|---------| -| Deployment Cost | Deployment Size | | | | | -| 2447267 | 12701 | | | | | -| Function Name | min | avg | median | max | # calls | -| EPOCH_SIZE | 263 | 263 | 263 | 263 | 1498 | -| MAX_BOOST | 307 | 307 | 307 | 307 | 637 | -| MAX_LOCKUP_PERIOD | 361 | 361 | 361 | 361 | 4 | -| MIN_LOCKUP_PERIOD | 264 | 264 | 264 | 264 | 12 | -| YEAR | 263 | 263 | 263 | 263 | 637 | -| accounts | 1619 | 1619 | 1619 | 1619 | 144243 | -| calculateMPToMint | 740 | 740 | 740 | 740 | 1276 | -| currentEpoch | 406 | 1072 | 406 | 2406 | 54 | -| epochEnd | 649 | 649 | 649 | 4649 | 23670 | -| epochReward | 1425 | 2925 | 1425 | 5925 | 3 | -| executeAccount(address) | 171751 | 171751 | 171751 | 171751 | 2 | -| executeAccount(address,uint256) | 30909 | 76511 | 78308 | 240839 | 141830 | -| executeEpoch() | 23458 | 146010 | 147059 | 3562421 | 23559 | -| executeEpoch(uint256) | 28208 | 28242 | 28208 | 28330 | 7 | -| isVault | 562 | 970 | 562 | 2562 | 680 | -| lock | 23862 | 23862 | 23862 | 23862 | 1 | -| migrateTo | 23891 | 23897 | 23897 | 23903 | 2 | -| migration | 395 | 1395 | 1395 | 2395 | 4 | -| migrationInitialize | 24602 | 24602 | 24602 | 24602 | 1 | -| newEpoch | 788 | 788 | 788 | 788 | 5 | -| owner | 2432 | 2432 | 2432 | 2432 | 13 | -| pendingMPToBeMinted | 386 | 386 | 386 | 386 | 46422 | -| pendingReward | 386 | 1420 | 2386 | 2386 | 29 | -| previousManager | 275 | 275 | 275 | 275 | 13 | -| setVault | 46216 | 46216 | 46216 | 46216 | 139 | -| stake | 23983 | 23983 | 23983 | 23983 | 1 | -| stakeRewardEstimate | 436 | 2345 | 2436 | 2436 | 22 | -| stakedToken | 295 | 295 | 295 | 295 | 696 | -| startMigration | 108281 | 108289 | 108293 | 108293 | 3 | -| totalSupply | 762 | 1943 | 2762 | 2762 | 22 | -| totalSupplyBalance | 407 | 1807 | 2407 | 2407 | 20 | -| totalSupplyMP | 362 | 362 | 362 | 2362 | 46443 | -| unstake | 23841 | 23841 | 23841 | 23841 | 1 | +| contracts/StakeManager.sol:StakeManager contract | | | | | | +|--------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 2465702 | 13041 | | | | | +| Function Name | min | avg | median | max | # calls | +| EPOCH_SIZE | 307 | 307 | 307 | 307 | 1498 | +| MAX_BOOST | 285 | 285 | 285 | 285 | 637 | +| MAX_LOCKUP_PERIOD | 361 | 361 | 361 | 361 | 4 | +| MIN_LOCKUP_PERIOD | 264 | 264 | 264 | 264 | 12 | +| YEAR | 263 | 263 | 263 | 263 | 637 | +| accounts | 1597 | 1597 | 1597 | 1597 | 144273 | +| calculateMPToMint | 740 | 740 | 740 | 740 | 1276 | +| currentEpoch | 384 | 1050 | 384 | 2384 | 54 | +| epochEnd | 627 | 627 | 627 | 2627 | 23675 | +| epochReward | 1425 | 2925 | 1425 | 5925 | 3 | +| executeAccount(address) | 151152 | 151152 | 151152 | 151152 | 2 | +| executeAccount(address,uint256) | 26562 | 72616 | 74122 | 217879 | 141860 | +| executeEpoch() | 23458 | 120800 | 121956 | 938985 | 23564 | +| executeEpoch(uint256) | 23861 | 24497 | 23861 | 26090 | 7 | +| isVault | 540 | 948 | 540 | 2540 | 680 | +| lock | 23862 | 23862 | 23862 | 23862 | 1 | +| migrateTo | 23891 | 23897 | 23897 | 23903 | 2 | +| migration | 395 | 1395 | 1395 | 2395 | 4 | +| migrationInitialize | 24602 | 24602 | 24602 | 24602 | 1 | +| newEpoch | 441 | 441 | 441 | 441 | 5 | +| owner | 2432 | 2432 | 2432 | 2432 | 13 | +| pendingMPToBeMinted | 364 | 364 | 364 | 364 | 46432 | +| pendingReward | 386 | 1420 | 2386 | 2386 | 29 | +| previousManager | 275 | 275 | 275 | 275 | 13 | +| setVault | 46239 | 46239 | 46239 | 46239 | 139 | +| stake | 23983 | 23983 | 23983 | 23983 | 1 | +| stakeRewardEstimate | 436 | 2345 | 2436 | 2436 | 22 | +| stakedToken | 295 | 295 | 295 | 295 | 696 | +| startMigration | 103580 | 103588 | 103592 | 103592 | 3 | +| startTime | 306 | 306 | 306 | 306 | 21 | +| totalSupply | 762 | 1943 | 2762 | 2762 | 22 | +| totalSupplyBalance | 407 | 1807 | 2407 | 2407 | 20 | +| totalSupplyMP | 362 | 362 | 362 | 2362 | 46453 | +| unstake | 23819 | 23819 | 23819 | 23819 | 1 | | contracts/StakeManager.sol:StakeRewardEstimate contract | | | | | | @@ -43,7 +44,7 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23720 | +| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23725 | | transferOwnership | 28533 | 28533 | 28533 | 28533 | 1 | @@ -54,11 +55,11 @@ | Function Name | min | avg | median | max | # calls | | acceptMigration | 35280 | 35280 | 35280 | 35280 | 2 | | leave | 35266 | 35266 | 35266 | 35266 | 1 | -| lock | 45569 | 108497 | 66661 | 254253 | 7 | +| lock | 43329 | 96146 | 64421 | 204745 | 7 | | owner | 362 | 362 | 362 | 362 | 679 | -| stake | 27265 | 284241 | 267959 | 354106 | 684 | +| stake | 27265 | 282079 | 265719 | 351866 | 684 | | stakedToken | 212 | 212 | 212 | 212 | 2 | -| unstake | 42429 | 104547 | 80498 | 272053 | 11 | +| unstake | 40167 | 95818 | 78688 | 233549 | 11 | | contracts/VaultFactory.sol:VaultFactory contract | | | | | | @@ -76,24 +77,24 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| approve | 46175 | 46240 | 46199 | 46367 | 679 | -| balanceOf | 561 | 2102 | 2561 | 2561 | 30842 | +| approve | 46175 | 46239 | 46199 | 46367 | 679 | +| balanceOf | 561 | 2107 | 2561 | 2561 | 30744 | | script/Deploy.s.sol:Deploy contract | | | | | | |-------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 6048121 | 29198 | | | | | +| 6118940 | 29538 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 5301161 | 5301161 | 5301161 | 5301161 | 66 | +| run | 5316305 | 5316305 | 5316305 | 5316305 | 66 | | script/DeployMigrationStakeManager.s.sol:DeployMigrationStakeManager contract | | | | | | |-------------------------------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 3211677 | 15966 | | | | | +| 3282471 | 16306 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 2286831 | 2286831 | 2286831 | 2286831 | 19 | +| run | 2302633 | 2302633 | 2302633 | 2302633 | 19 | | script/DeploymentConfig.s.sol:DeploymentConfig contract | | | | | | @@ -116,9 +117,9 @@ | test/script/DeployBroken.s.sol:DeployBroken contract | | | | | | |------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 4732891 | 22996 | | | | | +| 4803693 | 23336 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 4140983 | 4140983 | 4140983 | 4140983 | 1 | +| run | 4156127 | 4156127 | 4156127 | 4156127 | 1 | diff --git a/.gas-snapshot b/.gas-snapshot index f472f4a..b013b9b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,67 +1,67 @@ CreateVaultTest:testDeployment() (gas: 9774) CreateVaultTest:test_createVault() (gas: 714022) -ExecuteAccountTest:testDeployment() (gas: 28807) -ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 1624700) -ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 5413915) -ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 1869543) -ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 339213337) -ExecuteEpochTest:testDeployment() (gas: 28808) -ExecuteEpochTest:testNewDeployment() (gas: 30880) -ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1375214) -ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1402953) -ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 1654146) -ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1412668) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 2068294) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2794284) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1502361) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2804020) -ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1512053) -ExecuteEpochTest:test_ExecuteEpochNewEpoch() (gas: 3781443) -ExecuteEpochTest:test_ExecuteEpochShouldIncreaseEpoch() (gas: 119582) -ExecuteEpochTest:test_ExecuteEpochShouldIncreasePendingReward() (gas: 278146) -ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochBeforeEnd() (gas: 43290) -ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 154405) -LeaveTest:testDeployment() (gas: 28785) -LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1330232) +ExecuteAccountTest:testDeployment() (gas: 28785) +ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 1580255) +ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 5297777) +ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 1788412) +ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 321397553) +ExecuteEpochTest:testDeployment() (gas: 28786) +ExecuteEpochTest:testNewDeployment() (gas: 30858) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1366211) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1385045) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 1629507) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1395200) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 1936210) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2523553) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1479491) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2533729) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1489623) +ExecuteEpochTest:test_ExecuteEpochNewEpoch() (gas: 1122339) +ExecuteEpochTest:test_ExecuteEpochShouldIncreaseEpoch() (gas: 92413) +ExecuteEpochTest:test_ExecuteEpochShouldIncreasePendingReward() (gas: 256446) +ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochBeforeEnd() (gas: 38984) +ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 149638) +LeaveTest:testDeployment() (gas: 28763) +LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1327993) LeaveTest:test_RevertWhen_SenderIsNotVault() (gas: 31964) -LockTest:testDeployment() (gas: 28785) -LockTest:test_NewLockupPeriod() (gas: 1333546) -LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1305853) -LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1589343) +LockTest:testDeployment() (gas: 28763) +LockTest:test_NewLockupPeriod() (gas: 1329045) +LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1301374) +LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1545884) LockTest:test_RevertWhen_SenderIsNotVault() (gas: 31856) -LockTest:test_ShouldIncreaseBonusMP() (gas: 1316098) -LockTest:test_UpdateLockupPeriod() (gas: 1704020) -MigrateTest:testDeployment() (gas: 28785) -MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1294285) +LockTest:test_ShouldIncreaseBonusMP() (gas: 1311575) +LockTest:test_UpdateLockupPeriod() (gas: 1634540) +MigrateTest:testDeployment() (gas: 28763) +MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1292046) MigrateTest:test_RevertWhen_SenderIsNotVault() (gas: 31976) -MigrationInitializeTest:testDeployment() (gas: 28785) -MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5149125) -MigrationStakeManagerTest:testDeployment() (gas: 28785) -MigrationStakeManagerTest:testNewDeployment() (gas: 30924) -MigrationStakeManagerTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 154370) +MigrationInitializeTest:testDeployment() (gas: 28763) +MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5181388) +MigrationStakeManagerTest:testDeployment() (gas: 28763) +MigrationStakeManagerTest:testNewDeployment() (gas: 30902) +MigrationStakeManagerTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 149603) SetStakeManagerTest:testDeployment() (gas: 9774) SetStakeManagerTest:test_RevertWhen_InvalidStakeManagerAddress() (gas: 63105) SetStakeManagerTest:test_SetStakeManager() (gas: 41301) -StakeManagerTest:testDeployment() (gas: 28557) -StakeTest:testDeployment() (gas: 28763) -StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1085114) -StakeTest:test_RevertWhen_Restake() (gas: 1321326) -StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1325340) +StakeManagerTest:testDeployment() (gas: 28535) +StakeTest:testDeployment() (gas: 28741) +StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1080635) +StakeTest:test_RevertWhen_Restake() (gas: 1316847) +StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1320861) StakeTest:test_RevertWhen_SenderIsNotVault() (gas: 32018) -StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 820001) +StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 817762) StakeTest:test_RevertWhen_StakeTokenTransferFails() (gas: 211363) -StakeTest:test_StakeWithLockBonusMP() (gas: 2364371) -StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1325644) +StakeTest:test_StakeWithLockBonusMP() (gas: 2357564) +StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1322003) StakedTokenTest:testStakeToken() (gas: 7616) -UnstakeTest:testDeployment() (gas: 28807) -UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1301908) -UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1348907) -UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 31879) -UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 7404912) -UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1323170) -UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1471322) -UserFlowsTest:testDeployment() (gas: 28785) -UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 141478753, ~: 140916642) -UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1514680) -UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 2502892) +UnstakeTest:testDeployment() (gas: 28785) +UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1297407) +UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1342144) +UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 31857) +UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 6526945) +UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1319573) +UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1440205) +UserFlowsTest:testDeployment() (gas: 28763) +UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 131402731, ~: 130856521) +UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1481301) +UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 2494771) VaultFactoryTest:testDeployment() (gas: 9774) \ No newline at end of file diff --git a/contracts/StakeManager.sol b/contracts/StakeManager.sol index bb5390c..696bed9 100644 --- a/contracts/StakeManager.sol +++ b/contracts/StakeManager.sol @@ -74,6 +74,7 @@ contract StakeManager is Ownable { uint256 public currentEpoch; uint256 public pendingReward; + uint256 public immutable startTime; uint256 public pendingMPToBeMinted; uint256 public totalSupplyMP; @@ -140,30 +141,33 @@ contract StakeManager is Ownable { * @param _limitEpoch Until what epoch it should be executed */ function finalizeEpoch(uint256 _limitEpoch) private { - while (currentEpoch < _limitEpoch) { - Epoch storage thisEpoch = epochs[currentEpoch]; - uint256 expiredMP = stakeRewardEstimate.getExpiredMP(currentEpoch); + uint256 tempCurrentEpoch = currentEpoch; + while (tempCurrentEpoch < _limitEpoch) { + Epoch storage thisEpoch = epochs[tempCurrentEpoch]; + uint256 expiredMP = stakeRewardEstimate.getExpiredMP(tempCurrentEpoch); if (expiredMP > 0) { totalMPPerEpoch -= expiredMP; - stakeRewardEstimate.deleteExpiredMP(currentEpoch); + stakeRewardEstimate.deleteExpiredMP(tempCurrentEpoch); } thisEpoch.estimatedMP = totalMPPerEpoch - currentEpochTotalExpiredMP; delete currentEpochTotalExpiredMP; pendingMPToBeMinted += thisEpoch.estimatedMP; //finalize current epoch - thisEpoch.epochReward = epochReward(); + if (tempCurrentEpoch == currentEpoch) { + thisEpoch.epochReward = epochReward(); + pendingReward += thisEpoch.epochReward; + } thisEpoch.totalSupply = totalSupply(); - pendingReward += thisEpoch.epochReward; //create new epoch - currentEpoch++; - epochs[currentEpoch].startTime = thisEpoch.startTime + EPOCH_SIZE; + tempCurrentEpoch++; } + currentEpoch = tempCurrentEpoch; } constructor(address _stakedToken, address _previousManager) { - epochs[0].startTime = block.timestamp; + startTime = (_previousManager == address(0)) ? block.timestamp : StakeManager(_previousManager).startTime(); previousManager = StakeManager(_previousManager); stakedToken = ERC20(_stakedToken); if (address(previousManager) != address(0)) { @@ -351,7 +355,7 @@ contract StakeManager is Ownable { currentEpoch, totalSupplyMP, totalSupplyBalance, - epochs[currentEpoch].startTime, + startTime, totalMPPerEpoch, pendingMPToBeMinted, currentEpochTotalExpiredMP @@ -364,13 +368,13 @@ contract StakeManager is Ownable { * @param _currentEpoch epoch of old manager * @param _totalSupplyMP MP supply on old manager * @param _totalSupplyBalance stake supply on old manager - * @param _epochStartTime epoch start time of old manager + * @param _startTime start time of old manager */ function migrationInitialize( uint256 _currentEpoch, uint256 _totalSupplyMP, uint256 _totalSupplyBalance, - uint256 _epochStartTime, + uint256 _startTime, uint256 _totalMPPerEpoch, uint256 _pendingMPToBeMinted, uint256 _currentEpochExpiredMP @@ -382,10 +386,12 @@ contract StakeManager is Ownable { if (currentEpoch > 0) { revert StakeManager__AlreadyProcessedEpochs(); } + if (_startTime != startTime) { + revert StakeManager__InvalidMigration(); + } currentEpoch = _currentEpoch; totalSupplyMP = _totalSupplyMP; totalSupplyBalance = _totalSupplyBalance; - epochs[currentEpoch].startTime = _epochStartTime; totalMPPerEpoch = _totalMPPerEpoch; pendingMPToBeMinted = _pendingMPToBeMinted; currentEpochTotalExpiredMP = _currentEpochExpiredMP; @@ -458,7 +464,7 @@ contract StakeManager is Ownable { while (userEpoch < _limitEpoch) { Epoch storage iEpoch = epochs[userEpoch]; //mint multiplier points to that epoch - _mintMP(account, iEpoch.startTime + EPOCH_SIZE, iEpoch); + _mintMP(account, startTime + (EPOCH_SIZE * (userEpoch + 1)), iEpoch); uint256 userSupply = account.balance + account.totalMP; uint256 userEpochReward = Math.mulDiv(userSupply, iEpoch.epochReward, iEpoch.totalSupply); userReward += userEpochReward; @@ -608,7 +614,7 @@ contract StakeManager is Ownable { * @return _epochEnd end time of current epoch */ function epochEnd() public view returns (uint256 _epochEnd) { - return epochs[currentEpoch].startTime + EPOCH_SIZE; + return startTime + (EPOCH_SIZE * (currentEpoch + 1)); } /** @@ -616,7 +622,6 @@ contract StakeManager is Ownable { * @return _newEpoch the number of the epoch after all epochs that can be processed */ function newEpoch() public view returns (uint256 _newEpoch) { - _newEpoch = currentEpoch; - _newEpoch = _newEpoch + ((block.timestamp - epochs[_newEpoch].startTime) / EPOCH_SIZE); + _newEpoch = (block.timestamp - startTime) / EPOCH_SIZE; } }