diff --git a/.gas-report b/.gas-report index 72eb99d..af54826 100644 --- a/.gas-report +++ b/.gas-report @@ -1,51 +1,42 @@ | contracts/StakeManager.sol:StakeManager contract | | | | | | |--------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 2465702 | 13041 | | | | | +| 2489660 | 13152 | | | | | | 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 | +| EPOCH_SIZE | 263 | 263 | 263 | 263 | 1498 | +| MAX_BOOST | 264 | 264 | 264 | 264 | 637 | +| MAX_LOCKUP_PERIOD | 383 | 383 | 383 | 383 | 4 | | MIN_LOCKUP_PERIOD | 264 | 264 | 264 | 264 | 12 | | YEAR | 263 | 263 | 263 | 263 | 637 | -| accounts | 1597 | 1597 | 1597 | 1597 | 144273 | +| accounts | 1616 | 1616 | 1616 | 1616 | 144249 | | 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 | +| epochEnd | 649 | 649 | 649 | 2649 | 23671 | +| epochReward | 1381 | 2881 | 1381 | 5881 | 3 | +| executeAccount(address) | 151167 | 151167 | 151167 | 151167 | 2 | +| executeAccount(address,uint256) | 26562 | 72617 | 74122 | 217855 | 141836 | +| executeEpoch() | 23480 | 120821 | 121978 | 939007 | 23560 | | 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 | +| expiredStakeStorage | 437 | 2346 | 2437 | 2437 | 22 | +| isTrustedCodehash | 541 | 949 | 541 | 2541 | 680 | +| lock | 23818 | 23818 | 23818 | 23818 | 1 | +| migrateTo | 23922 | 23928 | 23928 | 23934 | 2 | +| migration | 417 | 1417 | 1417 | 2417 | 4 | +| migrationInitialize | 24624 | 24624 | 24624 | 24624 | 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 | +| pendingMPToBeMinted | 363 | 363 | 363 | 363 | 46424 | +| pendingReward | 408 | 1442 | 2408 | 2408 | 29 | | previousManager | 275 | 275 | 275 | 275 | 13 | -| setVault | 46239 | 46239 | 46239 | 46239 | 139 | +| setTrustedCodehash | 47960 | 47960 | 47960 | 47960 | 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 | +| stakedToken | 272 | 272 | 272 | 272 | 696 | +| startMigration | 103602 | 103610 | 103614 | 103614 | 3 | | startTime | 306 | 306 | 306 | 306 | 21 | -| totalSupply | 762 | 1943 | 2762 | 2762 | 22 | +| totalSupply | 784 | 1965 | 2784 | 2784 | 22 | | totalSupplyBalance | 407 | 1807 | 2407 | 2407 | 20 | -| totalSupplyMP | 362 | 362 | 362 | 2362 | 46453 | -| unstake | 23819 | 23819 | 23819 | 23819 | 1 | - - -| contracts/StakeManager.sol:StakeRewardEstimate contract | | | | | | -|---------------------------------------------------------|-----------------|-------|--------|-------|---------| -| Deployment Cost | Deployment Size | | | | | -| 0 | 0 | | | | | -| Function Name | min | avg | median | max | # calls | -| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23725 | -| transferOwnership | 28533 | 28533 | 28533 | 28533 | 1 | +| totalSupplyMP | 384 | 384 | 384 | 2384 | 46445 | +| unstake | 23841 | 23841 | 23841 | 23841 | 1 | | contracts/StakeVault.sol:StakeVault contract | | | | | | @@ -53,13 +44,13 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| acceptMigration | 35280 | 35280 | 35280 | 35280 | 2 | -| leave | 35266 | 35266 | 35266 | 35266 | 1 | -| lock | 43329 | 96146 | 64421 | 204745 | 7 | +| acceptMigration | 35311 | 35311 | 35311 | 35311 | 2 | +| leave | 35297 | 35297 | 35297 | 35297 | 1 | +| lock | 43285 | 96103 | 64379 | 204703 | 7 | | owner | 362 | 362 | 362 | 362 | 679 | -| stake | 27265 | 282079 | 265719 | 351866 | 684 | +| stake | 27265 | 282007 | 265700 | 351859 | 684 | | stakedToken | 212 | 212 | 212 | 212 | 2 | -| unstake | 40167 | 95818 | 78688 | 233549 | 11 | +| unstake | 40180 | 97074 | 78700 | 233556 | 11 | | contracts/VaultFactory.sol:VaultFactory contract | | | | | | @@ -67,34 +58,43 @@ | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| createVault | 696553 | 696553 | 696553 | 696553 | 683 | +| createVault | 696530 | 696530 | 696530 | 696530 | 683 | | setStakeManager | 23710 | 26669 | 26076 | 30222 | 3 | | stakeManager | 368 | 1868 | 2368 | 2368 | 4 | +| contracts/storage/ExpiredStakeStorage.sol:ExpiredStakeStorage contract | | | | | | +|------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| getExpiredMP | 2427 | 2427 | 2427 | 2427 | 23721 | +| transferOwnership | 28533 | 28533 | 28533 | 28533 | 1 | + + | lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol:ERC20 contract | | | | | | |---------------------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| approve | 46175 | 46239 | 46199 | 46367 | 679 | -| balanceOf | 561 | 2107 | 2561 | 2561 | 30744 | +| approve | 46175 | 46238 | 46199 | 46367 | 679 | +| balanceOf | 561 | 2107 | 2561 | 2561 | 30740 | | script/Deploy.s.sol:Deploy contract | | | | | | |-------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 6118940 | 29538 | | | | | +| 6142896 | 29649 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 5316305 | 5316305 | 5316305 | 5316305 | 66 | +| run | 5338557 | 5338557 | 5338557 | 5338557 | 66 | | script/DeployMigrationStakeManager.s.sol:DeployMigrationStakeManager contract | | | | | | |-------------------------------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 3282471 | 16306 | | | | | +| 3306430 | 16417 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 2302633 | 2302633 | 2302633 | 2302633 | 19 | +| run | 2324886 | 2324886 | 2324886 | 2324886 | 19 | | script/DeploymentConfig.s.sol:DeploymentConfig contract | | | | | | @@ -117,9 +117,9 @@ | test/script/DeployBroken.s.sol:DeployBroken contract | | | | | | |------------------------------------------------------|-----------------|---------|---------|---------|---------| | Deployment Cost | Deployment Size | | | | | -| 4803693 | 23336 | | | | | +| 4827656 | 23447 | | | | | | Function Name | min | avg | median | max | # calls | -| run | 4156127 | 4156127 | 4156127 | 4156127 | 1 | +| run | 4178379 | 4178379 | 4178379 | 4178379 | 1 | diff --git a/.gas-snapshot b/.gas-snapshot index b013b9b..ff826a0 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,67 +1,67 @@ CreateVaultTest:testDeployment() (gas: 9774) -CreateVaultTest:test_createVault() (gas: 714022) -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: 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: 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: 28763) -MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5181388) -MigrationStakeManagerTest:testDeployment() (gas: 28763) -MigrationStakeManagerTest:testNewDeployment() (gas: 30902) -MigrationStakeManagerTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 149603) +CreateVaultTest:test_createVault() (gas: 713999) +ExecuteAccountTest:testDeployment() (gas: 28828) +ExecuteAccountTest:test_ExecuteAccountLimit() (gas: 1582018) +ExecuteAccountTest:test_ExecuteAccountMintMP() (gas: 5299985) +ExecuteAccountTest:test_RevertWhen_InvalidLimitEpoch() (gas: 1790139) +ExecuteAccountTest:test_ShouldNotMintMoreThanCap() (gas: 321470949) +ExecuteEpochTest:testDeployment() (gas: 28829) +ExecuteEpochTest:testNewDeployment() (gas: 30901) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterEpochEnd() (gas: 1367896) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1386983) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 1630994) +ExecuteEpochTest:test_ExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1396698) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochAfterEnd() (gas: 1937948) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochs() (gas: 2525682) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsJumoMany() (gas: 1481437) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTime() (gas: 2535418) +ExecuteEpochTest:test_ExecuteEpochExecuteEpochExecuteAccountAfterManyEpochsWithBrokenTimeJumpMany() (gas: 1491129) +ExecuteEpochTest:test_ExecuteEpochNewEpoch() (gas: 1122427) +ExecuteEpochTest:test_ExecuteEpochShouldIncreaseEpoch() (gas: 92457) +ExecuteEpochTest:test_ExecuteEpochShouldIncreasePendingReward() (gas: 256428) +ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochBeforeEnd() (gas: 39028) +ExecuteEpochTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 149748) +LeaveTest:testDeployment() (gas: 28806) +LeaveTest:test_RevertWhen_NoPendingMigration() (gas: 1329762) +LeaveTest:test_RevertWhen_SenderIsNotVault() (gas: 31995) +LockTest:testDeployment() (gas: 28806) +LockTest:test_NewLockupPeriod() (gas: 1330751) +LockTest:test_RevertWhen_InvalidNewLockupPeriod() (gas: 1303059) +LockTest:test_RevertWhen_InvalidUpdateLockupPeriod() (gas: 1547562) +LockTest:test_RevertWhen_SenderIsNotVault() (gas: 31812) +LockTest:test_ShouldIncreaseBonusMP() (gas: 1313344) +LockTest:test_UpdateLockupPeriod() (gas: 1636200) +MigrateTest:testDeployment() (gas: 28806) +MigrateTest:test_RevertWhen_NoPendingMigration() (gas: 1293784) +MigrateTest:test_RevertWhen_SenderIsNotVault() (gas: 32007) +MigrationInitializeTest:testDeployment() (gas: 28806) +MigrationInitializeTest:test_RevertWhen_MigrationPending() (gas: 5229403) +MigrationStakeManagerTest:testDeployment() (gas: 28806) +MigrationStakeManagerTest:testNewDeployment() (gas: 30945) +MigrationStakeManagerTest:test_ExecuteEpochShouldNotIncreaseEpochInMigration() (gas: 149713) SetStakeManagerTest:testDeployment() (gas: 9774) SetStakeManagerTest:test_RevertWhen_InvalidStakeManagerAddress() (gas: 63105) SetStakeManagerTest:test_SetStakeManager() (gas: 41301) -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) +StakeManagerTest:testDeployment() (gas: 28578) +StakeTest:testDeployment() (gas: 28784) +StakeTest:test_RevertWhen_InvalidLockupPeriod() (gas: 1082353) +StakeTest:test_RevertWhen_Restake() (gas: 1318545) +StakeTest:test_RevertWhen_RestakeWithLock() (gas: 1322559) StakeTest:test_RevertWhen_SenderIsNotVault() (gas: 32018) -StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 817762) +StakeTest:test_RevertWhen_StakeIsTooLow() (gas: 819467) StakeTest:test_RevertWhen_StakeTokenTransferFails() (gas: 211363) -StakeTest:test_StakeWithLockBonusMP() (gas: 2357564) -StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1322003) +StakeTest:test_StakeWithLockBonusMP() (gas: 2359257) +StakeTest:test_StakeWithoutLockUpTimeMintsMultiplierPoints() (gas: 1314761) StakedTokenTest:testStakeToken() (gas: 7616) -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) +UnstakeTest:testDeployment() (gas: 28828) +UnstakeTest:test_RevertWhen_AmountMoreThanBalance() (gas: 1299127) +UnstakeTest:test_RevertWhen_FundsLocked() (gas: 1343877) +UnstakeTest:test_RevertWhen_SenderIsNotVault() (gas: 31879) +UnstakeTest:test_UnstakeShouldBurnMultiplierPoints() (gas: 6529254) +UnstakeTest:test_UnstakeShouldReturnFund_NoLockUp() (gas: 1321289) +UnstakeTest:test_UnstakeShouldReturnFund_WithLockUp() (gas: 1441917) +UserFlowsTest:testDeployment() (gas: 28806) +UserFlowsTest:test_PendingMPToBeMintedCannotBeGreaterThanTotalSupplyMP(uint8,uint128) (runs: 106, μ: 131427031, ~: 130900495) +UserFlowsTest:test_StakeWithLockUpTimeLocksStake() (gas: 1483026) +UserFlowsTest:test_StakedSupplyShouldIncreaseAndDecreaseAgain() (gas: 2496473) VaultFactoryTest:testDeployment() (gas: 9774) \ No newline at end of file diff --git a/README.md b/README.md index 54aaa4f..d16ed0a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ To install the system on the blockchain, follow these steps: 1. **Deploy StakeManager**: Begin by deploying the `StakeManager` contract. 2. **Deploy Sample VaultManager**: Next, deploy a sample `VaultManager` contract on any chain (this can be on a development network or a testnet). -3. **Configure Codehash**: Once the `VaultManager` is deployed, retrieve its codehash and configure it in the `StakeManager` using the `setVault(bytes32)` function. +3. **Configure Codehash**: Once the `VaultManager` is deployed, retrieve its codehash and configure it in the `StakeManager` using the `setTrustedCodehash(bytes32, bool)` function. 4. **Optional - Use VaultFactory**: The `VaultFactory` contract can be optionally used to optimize gas costs during deployments and to facilitate the creation of valid vaults. ### Staking Tokens diff --git a/certora/confs/StakeManager.conf b/certora/confs/StakeManager.conf index 6fd4b76..9144da4 100644 --- a/certora/confs/StakeManager.conf +++ b/certora/confs/StakeManager.conf @@ -1,12 +1,12 @@ { "files": ["contracts/StakeManager.sol", - "certora/helpers/StakeRewardEstimateA.sol", + "certora/helpers/ExpiredStakeStorageA.sol", "certora/helpers/ERC20A.sol" ], "link" : [ "StakeManager:stakedToken=ERC20A", - "StakeManager:stakeRewardEstimate=StakeRewardEstimateA" + "StakeManager:expiredStakeStorage=ExpiredStakeStorageA" ], "msg": "Verifying StakeManager.sol", "rule_sanity": "basic", diff --git a/certora/confs/StakeManagerProcess.conf b/certora/confs/StakeManagerProcess.conf index c070703..6b51c28 100644 --- a/certora/confs/StakeManagerProcess.conf +++ b/certora/confs/StakeManagerProcess.conf @@ -2,11 +2,11 @@ "files": ["contracts/StakeManager.sol", "certora/helpers/ERC20A.sol", - "certora/helpers/StakeRewardEstimateA.sol" + "certora/helpers/ExpiredStakeStorageA.sol" ], "link" : [ "StakeManager:stakedToken=ERC20A", - "StakeManager:stakeRewardEstimate=StakeRewardEstimateA" + "StakeManager:expiredStakeStorage=ExpiredStakeStorageA" ], "msg": "Verifying StakeManager ProcessAccount", "rule_sanity": "basic", diff --git a/certora/confs/StakeManagerStartMigration.conf b/certora/confs/StakeManagerStartMigration.conf index ba1524d..33a5e0e 100644 --- a/certora/confs/StakeManagerStartMigration.conf +++ b/certora/confs/StakeManagerStartMigration.conf @@ -2,12 +2,12 @@ "files": [ "contracts/StakeManager.sol", "certora/harness/StakeManagerNew.sol", - "certora/helpers/StakeRewardEstimateA.sol", + "certora/helpers/ExpiredStakeStorageA.sol", "certora/helpers/ERC20A.sol" ], "link" : [ "StakeManager:stakedToken=ERC20A", - "StakeManager:stakeRewardEstimate=StakeRewardEstimateA", + "StakeManager:expiredStakeStorage=ExpiredStakeStorageA", ], "msg": "Verifying StakeManager.sol", "rule_sanity": "basic", diff --git a/certora/confs/StakeVault.conf b/certora/confs/StakeVault.conf index 088e1f2..2aa686c 100644 --- a/certora/confs/StakeVault.conf +++ b/certora/confs/StakeVault.conf @@ -2,13 +2,13 @@ "files": [ "contracts/StakeManager.sol", "contracts/StakeVault.sol", - "certora/helpers/StakeRewardEstimateA.sol", + "certora/helpers/ExpiredStakeStorageA.sol", "certora/helpers/ERC20A.sol" ], "link" : [ "StakeVault:STAKED_TOKEN=ERC20A", "StakeManager:stakedToken=ERC20A", - "StakeManager:stakeRewardEstimate=StakeRewardEstimateA", + "StakeManager:expiredStakeStorage=ExpiredStakeStorageA", "StakeVault:stakeManager=StakeManager" ], "msg": "Verifying StakeVault.sol", diff --git a/certora/helpers/ExpiredStakeStorageA.sol b/certora/helpers/ExpiredStakeStorageA.sol new file mode 100644 index 0000000..9787836 --- /dev/null +++ b/certora/helpers/ExpiredStakeStorageA.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import { ExpiredStakeStorage } from "./../../contracts/storage/ExpiredStakeStorage.sol"; + +contract ExpiredStakeStorageA is ExpiredStakeStorage { } diff --git a/certora/helpers/StakeRewardEstimateA.sol b/certora/helpers/StakeRewardEstimateA.sol deleted file mode 100644 index 981a646..0000000 --- a/certora/helpers/StakeRewardEstimateA.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.19; - -import { StakeRewardEstimate } from "./../../contracts/StakeManager.sol"; - -contract StakeRewardEstimateA is StakeRewardEstimate {} - diff --git a/certora/specs/StakeManager.spec b/certora/specs/StakeManager.spec index 8b49a56..28b6c06 100644 --- a/certora/specs/StakeManager.spec +++ b/certora/specs/StakeManager.spec @@ -198,7 +198,7 @@ rule epochOnlyIncreases(method f) { } -//TODO codehash / isVault +//TODO codehash / isTrustedCodehash /* ghost mapping(address => bytes32) codehash; @@ -211,7 +211,7 @@ rule checksCodeHash(method f) filtered { } { env e; - bool isWhitelisted = isVault(codehash[e.msg.sender]); + bool isWhitelisted = isTrustedCodehash(codehash[e.msg.sender]); f(e); assert isWhitelisted; diff --git a/contracts/StakeManager.sol b/contracts/StakeManager.sol index 696bed9..34b33ed 100644 --- a/contracts/StakeManager.sol +++ b/contracts/StakeManager.sol @@ -3,33 +3,13 @@ pragma solidity ^0.8.18; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { TrustedCodehashAccess } from "./access/TrustedCodehashAccess.sol"; +import { ExpiredStakeStorage } from "./storage/ExpiredStakeStorage.sol"; import { StakeVault } from "./StakeVault.sol"; -contract StakeRewardEstimate is Ownable { - mapping(uint256 epochId => uint256 balance) public expiredMPPerEpoch; - - function getExpiredMP(uint256 epochId) public view returns (uint256) { - return expiredMPPerEpoch[epochId]; - } - - function incrementExpiredMP(uint256 epochId, uint256 amount) public onlyOwner { - expiredMPPerEpoch[epochId] += amount; - } - - function decrementExpiredMP(uint256 epochId, uint256 amount) public onlyOwner { - expiredMPPerEpoch[epochId] -= amount; - } - - function deleteExpiredMP(uint256 epochId) public onlyOwner { - delete expiredMPPerEpoch[epochId]; - } -} - -contract StakeManager is Ownable { - error StakeManager__SenderIsNotVault(); +contract StakeManager is TrustedCodehashAccess { error StakeManager__FundsLocked(); error StakeManager__InvalidLockTime(); error StakeManager__NoPendingMigration(); @@ -70,7 +50,6 @@ contract StakeManager is Ownable { mapping(address index => Account value) public accounts; mapping(uint256 index => Epoch value) public epochs; - mapping(bytes32 codehash => bool approved) public isVault; uint256 public currentEpoch; uint256 public pendingReward; @@ -81,7 +60,7 @@ contract StakeManager is Ownable { uint256 public totalSupplyBalance; uint256 public totalMPPerEpoch; - StakeRewardEstimate public stakeRewardEstimate; + ExpiredStakeStorage public expiredStakeStorage; uint256 public currentEpochTotalExpiredMP; @@ -89,16 +68,6 @@ contract StakeManager is Ownable { StakeManager public immutable previousManager; ERC20 public immutable stakedToken; - /** - * @notice Only callable by vaults - */ - modifier onlyVault() { - if (!isVault[msg.sender.codehash]) { - revert StakeManager__SenderIsNotVault(); - } - _; - } - modifier onlyAccountInitialized(address account) { if (accounts[account].lockUntil == 0) { revert StakeManager__AccountNotInitialized(); @@ -144,10 +113,10 @@ contract StakeManager is Ownable { uint256 tempCurrentEpoch = currentEpoch; while (tempCurrentEpoch < _limitEpoch) { Epoch storage thisEpoch = epochs[tempCurrentEpoch]; - uint256 expiredMP = stakeRewardEstimate.getExpiredMP(tempCurrentEpoch); + uint256 expiredMP = expiredStakeStorage.getExpiredMP(tempCurrentEpoch); if (expiredMP > 0) { totalMPPerEpoch -= expiredMP; - stakeRewardEstimate.deleteExpiredMP(tempCurrentEpoch); + expiredStakeStorage.deleteExpiredMP(tempCurrentEpoch); } thisEpoch.estimatedMP = totalMPPerEpoch - currentEpochTotalExpiredMP; delete currentEpochTotalExpiredMP; @@ -171,9 +140,9 @@ contract StakeManager is Ownable { previousManager = StakeManager(_previousManager); stakedToken = ERC20(_stakedToken); if (address(previousManager) != address(0)) { - stakeRewardEstimate = previousManager.stakeRewardEstimate(); + expiredStakeStorage = previousManager.expiredStakeStorage(); } else { - stakeRewardEstimate = new StakeRewardEstimate(); + expiredStakeStorage = new ExpiredStakeStorage(); } } @@ -186,7 +155,7 @@ contract StakeManager is Ownable { * @dev Reverts when account has already staked funds. * @dev Reverts when amount staked results in less than 1 MP per epoch. */ - function stake(uint256 _amount, uint256 _secondsToLock) external onlyVault noPendingMigration { + function stake(uint256 _amount, uint256 _secondsToLock) external onlyTrustedCodehash noPendingMigration { finalizeEpoch(newEpoch()); Account storage account = accounts[msg.sender]; if (account.balance > 0 || account.lockUntil != 0) { @@ -219,14 +188,19 @@ contract StakeManager is Ownable { totalSupplyBalance += _amount; currentEpochTotalExpiredMP += currentEpochExpiredMP; totalMPPerEpoch += mpPerEpoch; - stakeRewardEstimate.incrementExpiredMP(mpLimitEpoch, lastEpochAmountToMint); - stakeRewardEstimate.incrementExpiredMP(mpLimitEpoch + 1, mpPerEpoch - lastEpochAmountToMint); + expiredStakeStorage.incrementExpiredMP(mpLimitEpoch, lastEpochAmountToMint); + expiredStakeStorage.incrementExpiredMP(mpLimitEpoch + 1, mpPerEpoch - lastEpochAmountToMint); } /** * leaves the staking pool and withdraws all funds; */ - function unstake(uint256 _amount) external onlyVault onlyAccountInitialized(msg.sender) noPendingMigration { + function unstake(uint256 _amount) + external + onlyTrustedCodehash + onlyAccountInitialized(msg.sender) + noPendingMigration + { finalizeEpoch(newEpoch()); Account storage account = accounts[msg.sender]; if (_amount > account.balance) { @@ -241,7 +215,7 @@ 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); + expiredStakeStorage.decrementExpiredMP(account.mpLimitEpoch, mpPerEpoch); if (account.mpLimitEpoch < currentEpoch) { totalMPPerEpoch -= mpPerEpoch; } @@ -263,7 +237,7 @@ contract StakeManager is Ownable { */ function lock(uint256 _secondsToIncreaseLock) external - onlyVault + onlyTrustedCodehash onlyAccountInitialized(msg.sender) noPendingMigration { @@ -331,14 +305,6 @@ contract StakeManager is Ownable { _processAccount(accounts[_vault], _limitEpoch); } - /** - * @notice Enables a contract class to interact with staking functions - * @param _codehash bytecode hash of contract - */ - function setVault(bytes32 _codehash) external onlyOwner { - isVault[_codehash] = true; - } - /** * @notice starts migration to new StakeManager * @param _migration new StakeManager @@ -350,7 +316,7 @@ contract StakeManager is Ownable { } migration = _migration; stakedToken.transfer(address(migration), epochReward()); - stakeRewardEstimate.transferOwnership(address(_migration)); + expiredStakeStorage.transferOwnership(address(_migration)); migration.migrationInitialize( currentEpoch, totalSupplyMP, @@ -410,7 +376,7 @@ contract StakeManager is Ownable { */ function migrateTo(bool _acceptMigration) external - onlyVault + onlyTrustedCodehash onlyAccountInitialized(msg.sender) onlyPendingMigration returns (StakeManager newManager) diff --git a/contracts/access/TrustedCodehashAccess.sol b/contracts/access/TrustedCodehashAccess.sol new file mode 100644 index 0000000..3caac30 --- /dev/null +++ b/contracts/access/TrustedCodehashAccess.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @title TrustedCodehashAccess + * @author Ricardo Guilherme Schmidt + * @notice Ensures that only specific contract bytecode hashes are trusted to + * interact with the functions using the `onlyTrustedCodehash` modifier. + */ +contract TrustedCodehashAccess is Ownable { + error TrustedCodehashAccess__UnauthorizedCodehash(); + + event TrustedCodehashUpdated(bytes32 indexed codehash, bool trusted); + + mapping(bytes32 codehash => bool permission) private trustedCodehashes; + + /** + * @notice Restricts access based on the codehash of the caller. + * Only contracts with trusted codehashes can execute functions using this modifier. + */ + modifier onlyTrustedCodehash() { + bytes32 codehash = msg.sender.codehash; + if (!trustedCodehashes[codehash]) { + revert TrustedCodehashAccess__UnauthorizedCodehash(); + } + _; + } + + /** + * @notice Allows the owner to set or update the trust status for a contract's codehash. + * @dev Emits the `TrustedCodehashUpdated` event whenever a codehash is updated. + * @param _codehash The bytecode hash of the contract. + * @param _trusted Boolean flag to designate the contract as trusted or not. + */ + function setTrustedCodehash(bytes32 _codehash, bool _trusted) external onlyOwner { + trustedCodehashes[_codehash] = _trusted; + emit TrustedCodehashUpdated(_codehash, _trusted); + } + + /** + * @notice Checks if a contract's codehash is trusted to interact with protected functions. + * @param _codehash The bytecode hash of the contract. + * @return bool True if the codehash is trusted, false otherwise. + */ + function isTrustedCodehash(bytes32 _codehash) external view returns (bool) { + return trustedCodehashes[_codehash]; + } +} diff --git a/contracts/storage/ExpiredStakeStorage.sol b/contracts/storage/ExpiredStakeStorage.sol new file mode 100644 index 0000000..656effa --- /dev/null +++ b/contracts/storage/ExpiredStakeStorage.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +contract ExpiredStakeStorage is Ownable { + mapping(uint256 epochId => uint256 balance) public expiredMPPerEpoch; + + function getExpiredMP(uint256 epochId) public view returns (uint256) { + return expiredMPPerEpoch[epochId]; + } + + function incrementExpiredMP(uint256 epochId, uint256 amount) public onlyOwner { + expiredMPPerEpoch[epochId] += amount; + } + + function decrementExpiredMP(uint256 epochId, uint256 amount) public onlyOwner { + expiredMPPerEpoch[epochId] -= amount; + } + + function deleteExpiredMP(uint256 epochId) public onlyOwner { + delete expiredMPPerEpoch[epochId]; + } +} diff --git a/test/StakeManager.t.sol b/test/StakeManager.t.sol index 4224a47..339234d 100644 --- a/test/StakeManager.t.sol +++ b/test/StakeManager.t.sol @@ -7,7 +7,7 @@ import { Test, console } from "forge-std/Test.sol"; import { Deploy } from "../script/Deploy.s.sol"; import { DeployMigrationStakeManager } from "../script/DeployMigrationStakeManager.s.sol"; import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; -import { StakeManager, StakeRewardEstimate } from "../contracts/StakeManager.sol"; +import { TrustedCodehashAccess, StakeManager, ExpiredStakeStorage } from "../contracts/StakeManager.sol"; import { StakeVault } from "../contracts/StakeVault.sol"; import { VaultFactory } from "../contracts/VaultFactory.sol"; @@ -42,9 +42,9 @@ contract StakeManagerTest is Test { vm.prank(owner); vault = vaultFactory.createVault(); - if (!stakeManager.isVault(address(vault).codehash)) { + if (!stakeManager.isTrustedCodehash(address(vault).codehash)) { vm.prank(deployer); - stakeManager.setVault(address(vault).codehash); + stakeManager.setTrustedCodehash(address(vault).codehash, true); } } @@ -83,7 +83,7 @@ contract StakeManagerTest is Test { contract StakeTest is StakeManagerTest { function test_RevertWhen_SenderIsNotVault() public { - vm.expectRevert(StakeManager.StakeManager__SenderIsNotVault.selector); + vm.expectRevert(TrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); stakeManager.stake(100, 1); } @@ -169,8 +169,8 @@ contract StakeTest is StakeManagerTest { } function test_StakeWithoutLockUpTimeMintsMultiplierPoints() public { - uint256 stakeAmount = 100; - StakeVault userVault = _createStakingAccount(testUser, stakeAmount, 0, stakeAmount * 10); + uint256 stakeAmount = 54; + StakeVault userVault = _createStakingAccount(testUser, stakeAmount, 0, stakeAmount); (,, uint256 totalMP,,,,,) = stakeManager.accounts(address(userVault)); assertEq(stakeManager.totalSupplyMP(), stakeAmount, "total multiplier point supply"); @@ -187,7 +187,7 @@ contract StakeTest is StakeManagerTest { contract UnstakeTest is StakeManagerTest { function test_RevertWhen_SenderIsNotVault() public { - vm.expectRevert(StakeManager.StakeManager__SenderIsNotVault.selector); + vm.expectRevert(TrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); stakeManager.unstake(1); } @@ -289,7 +289,7 @@ contract UnstakeTest is StakeManagerTest { contract LockTest is StakeManagerTest { function test_RevertWhen_SenderIsNotVault() public { - vm.expectRevert(StakeManager.StakeManager__SenderIsNotVault.selector); + vm.expectRevert(TrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); stakeManager.lock(100); } @@ -371,7 +371,7 @@ contract LockTest is StakeManagerTest { contract LeaveTest is StakeManagerTest { function test_RevertWhen_SenderIsNotVault() public { - vm.expectRevert(StakeManager.StakeManager__SenderIsNotVault.selector); + vm.expectRevert(TrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); stakeManager.migrateTo(false); } @@ -393,7 +393,7 @@ contract LeaveTest is StakeManagerTest { contract MigrateTest is StakeManagerTest { function test_RevertWhen_SenderIsNotVault() public { - vm.expectRevert(StakeManager.StakeManager__SenderIsNotVault.selector); + vm.expectRevert(TrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); stakeManager.migrateTo(true); } @@ -423,7 +423,7 @@ contract MigrationInitializeTest is StakeManagerTest { vm.stopPrank(); // first, ensure `secondStakeManager` is in migration mode itself - StakeRewardEstimate db = stakeManager.stakeRewardEstimate(); + ExpiredStakeStorage db = stakeManager.expiredStakeStorage(); vm.prank(address(stakeManager)); db.transferOwnership(address(secondStakeManager));