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);