refactor(RewardStreamerMP): extract MP and Stake mathematical formulas to abstract contracts

This commit is contained in:
Ricardo Guilherme Schmidt
2025-01-30 12:28:31 -03:00
committed by r4bbit
parent 9807f498e0
commit a22da253c3
10 changed files with 615 additions and 337 deletions

View File

@@ -1,119 +1,92 @@
| script/DeployRewardsStreamerMP.s.sol:DeployRewardsStreamerMPScript contract | | | | | |
|-----------------------------------------------------------------------------|-----------------|---------|---------|---------|---------|
| Deployment Cost | Deployment Size | | | | |
| 6351315 | 30400 | | | | |
| 6539451 | 31274 | | | | |
| Function Name | min | avg | median | max | # calls |
| run | 5443041 | 5443041 | 5443041 | 5443041 | 63 |
| run | 5618268 | 5618268 | 5618268 | 5618268 | 64 |
| script/DeploymentConfig.s.sol:DeploymentConfig contract | | | | | |
|---------------------------------------------------------|-----------------|-----|--------|-----|---------|
| Deployment Cost | Deployment Size | | | | |
| 0 | 0 | | | | |
| 0 | 7333 | | | | |
| Function Name | min | avg | median | max | # calls |
| activeNetworkConfig | 454 | 454 | 454 | 454 | 126 |
| activeNetworkConfig | 454 | 454 | 454 | 454 | 128 |
| script/UpgradeRewardsStreamerMP.s.sol:UpgradeRewardsStreamerMPScript contract | | | | | |
|-------------------------------------------------------------------------------|-----------------|---------|---------|---------|---------|
| Deployment Cost | Deployment Size | | | | |
| 2952925 | 14574 | | | | |
| 3140997 | 15448 | | | | |
| Function Name | min | avg | median | max | # calls |
| run | 2482684 | 2482684 | 2482684 | 2482684 | 3 |
| run | 2657933 | 2657933 | 2657933 | 2657933 | 3 |
| src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | |
|------------------------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 2632986 | 12135 | | | | |
| 2821054 | 13009 | | | | |
| Function Name | min | avg | median | max | # calls |
| MAX_LOCKUP_PERIOD | 371 | 371 | 371 | 371 | 6 |
| MAX_MULTIPLIER | 295 | 295 | 295 | 295 | 33 |
| MIN_LOCKUP_PERIOD | 252 | 252 | 252 | 252 | 15 |
| MP_RATE_PER_YEAR | 253 | 253 | 253 | 253 | 9 |
| STAKING_TOKEN | 428 | 2036 | 2428 | 2428 | 322 |
| MAX_LOCKUP_PERIOD | 382 | 382 | 382 | 382 | 4 |
| MAX_MULTIPLIER | 262 | 262 | 262 | 262 | 9 |
| MIN_LOCKUP_PERIOD | 308 | 308 | 308 | 308 | 15 |
| STAKING_TOKEN | 395 | 2003 | 2395 | 2395 | 327 |
| emergencyModeEnabled | 2398 | 2398 | 2398 | 2398 | 7 |
| enableEmergencyMode | 2507 | 19414 | 24699 | 24699 | 8 |
| getAccountTotalMaxMP | 3122 | 3122 | 3122 | 3122 | 1 |
| getAccountTotalStakedBalance | 15119 | 15119 | 15119 | 15119 | 1 |
| getAccountVaults | 5202 | 5202 | 5202 | 5202 | 4 |
| getStakedBalance | 2629 | 2629 | 2629 | 2629 | 1 |
| getVault | 1643 | 1643 | 1643 | 1643 | 72 |
| initialize | 115611 | 115611 | 115611 | 115611 | 65 |
| lastRewardTime | 373 | 1373 | 1373 | 2373 | 2 |
| leave | 59961 | 59961 | 59961 | 59961 | 1 |
| lock | 12041 | 35382 | 16458 | 77648 | 3 |
| mpBalanceOfAccount | 9228 | 9228 | 9228 | 9228 | 1 |
| proxiableUUID | 353 | 353 | 353 | 353 | 3 |
| registerVault | 55888 | 72788 | 72988 | 72988 | 257 |
| rewardEndTime | 373 | 1373 | 1373 | 2373 | 2 |
| rewardStartTime | 352 | 1352 | 1352 | 2352 | 2 |
| rewardsBalanceOf | 3231 | 6627 | 7074 | 7341 | 8 |
| setReward | 2561 | 58349 | 86319 | 105709 | 7 |
| setTrustedCodehash | 24243 | 24304 | 24243 | 26243 | 65 |
| stake | 134819 | 172398 | 179212 | 199545 | 66 |
| totalMPAccrued | 351 | 351 | 351 | 351 | 81 |
| totalMaxMP | 373 | 373 | 373 | 373 | 81 |
| totalRewardsAccrued | 373 | 373 | 373 | 373 | 3 |
| totalRewardsSupply | 1025 | 1984 | 1806 | 6765 | 30 |
| totalStaked | 374 | 374 | 374 | 374 | 82 |
| unstake | 64077 | 64613 | 64077 | 67567 | 13 |
| updateGlobalState | 14339 | 26645 | 28616 | 28616 | 19 |
| updateVaultMP | 12238 | 17353 | 17955 | 17955 | 19 |
| upgradeToAndCall | 3225 | 7901 | 8448 | 10936 | 5 |
| getAccountTotalMaxMP | 3133 | 3133 | 3133 | 3133 | 1 |
| getAccountTotalStakedBalance | 15173 | 15173 | 15173 | 15173 | 1 |
| getAccountVaults | 5225 | 5225 | 5225 | 5225 | 4 |
| getStakedBalance | 2618 | 2618 | 2618 | 2618 | 1 |
| getVault | 1621 | 1621 | 1621 | 1621 | 72 |
| initialize | 115654 | 115654 | 115654 | 115654 | 66 |
| lastRewardTime | 428 | 1428 | 1428 | 2428 | 2 |
| leave | 79955 | 79955 | 79955 | 79955 | 1 |
| lock | 14282 | 42729 | 42692 | 78446 | 259 |
| mpBalanceOfAccount | 10308 | 10308 | 10308 | 10308 | 1 |
| proxiableUUID | 387 | 387 | 387 | 387 | 3 |
| registerVault | 55866 | 72769 | 72966 | 72966 | 261 |
| rewardEndTime | 362 | 1362 | 1362 | 2362 | 2 |
| rewardStartTime | 407 | 1407 | 1407 | 2407 | 2 |
| rewardsBalanceOf | 2942 | 6975 | 7525 | 7746 | 8 |
| setReward | 2606 | 58415 | 86507 | 105754 | 7 |
| setTrustedCodehash | 24199 | 24259 | 24199 | 26199 | 66 |
| stake | 136333 | 179336 | 180726 | 201200 | 322 |
| totalMPAccrued | 384 | 384 | 384 | 384 | 81 |
| totalMaxMP | 406 | 406 | 406 | 406 | 81 |
| totalRewardsAccrued | 407 | 407 | 407 | 407 | 3 |
| totalRewardsSupply | 1036 | 1995 | 1817 | 6776 | 30 |
| totalStaked | 427 | 427 | 427 | 427 | 82 |
| unstake | 63857 | 64502 | 63857 | 68054 | 13 |
| updateGlobalState | 14339 | 26780 | 28759 | 28759 | 19 |
| updateVaultMP | 11707 | 17581 | 18273 | 18273 | 19 |
| upgradeToAndCall | 3181 | 7875 | 8438 | 10881 | 5 |
| src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | |
|------------------------------------------------------|-----------------|-------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 256467 | 1263 | | | | |
| Function Name | min | avg | median | max | # calls |
| MAX_LOCKUP_PERIOD | 798 | 3798 | 5298 | 5298 | 6 |
| MAX_MULTIPLIER | 722 | 1949 | 722 | 5222 | 33 |
| MIN_LOCKUP_PERIOD | 679 | 3379 | 5179 | 5179 | 15 |
| MP_RATE_PER_YEAR | 680 | 1180 | 680 | 5180 | 9 |
| STAKING_TOKEN | 855 | 6083 | 7355 | 7355 | 322 |
| emergencyModeEnabled | 7325 | 7325 | 7325 | 7325 | 7 |
| enableEmergencyMode | 28502 | 45403 | 50687 | 50687 | 8 |
| getAccountTotalMaxMP | 3552 | 3552 | 3552 | 3552 | 1 |
| getAccountTotalStakedBalance | 15549 | 15549 | 15549 | 15549 | 1 |
| getAccountVaults | 5638 | 6763 | 5638 | 10138 | 4 |
| getStakedBalance | 7559 | 7559 | 7559 | 7559 | 1 |
| getVault | 2097 | 2097 | 2097 | 2097 | 72 |
| implementation | 343 | 775 | 343 | 2343 | 412 |
| lastRewardTime | 800 | 1800 | 1800 | 2800 | 2 |
| mpBalanceOfAccount | 9658 | 9658 | 9658 | 9658 | 1 |
| rewardEndTime | 800 | 1800 | 1800 | 2800 | 2 |
| rewardStartTime | 779 | 4029 | 4029 | 7279 | 2 |
| rewardsBalanceOf | 3661 | 7057 | 7504 | 7771 | 8 |
| setReward | 28841 | 84663 | 112677 | 132067 | 7 |
| setTrustedCodehash | 52889 | 52889 | 52889 | 52889 | 2 |
| totalMPAccrued | 778 | 778 | 778 | 778 | 81 |
| totalMaxMP | 800 | 800 | 800 | 800 | 81 |
| totalRewardsAccrued | 800 | 800 | 800 | 800 | 3 |
| totalRewardsSupply | 1452 | 2561 | 2233 | 11692 | 30 |
| totalStaked | 801 | 801 | 801 | 801 | 82 |
| updateGlobalState | 40327 | 52633 | 54604 | 54604 | 19 |
| updateVaultMP | 38597 | 43712 | 44314 | 44314 | 19 |
| upgradeToAndCall | 29868 | 33720 | 33720 | 37572 | 2 |
| src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | |
|------------------------------------------------------|-----------------|------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 256510 | 1231 | | | | |
| Function Name | min | avg | median | max | # calls |
| fallback | 689 | 7187 | 2075 | 132112 | 790 |
| implementation | 343 | 1636 | 2343 | 2343 | 929 |
| src/StakeVault.sol:StakeVault contract | | | | | |
|----------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 1420425 | 6695 | | | | |
| 1420392 | 6695 | | | | |
| Function Name | min | avg | median | max | # calls |
| STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 |
| emergencyExit | 36353 | 48857 | 48091 | 65191 | 7 |
| leave | 33507 | 132182 | 62122 | 370978 | 4 |
| lock | 33245 | 61584 | 50823 | 111445 | 4 |
| owner | 2339 | 2339 | 2339 | 2339 | 257 |
| register | 87037 | 103937 | 104137 | 104137 | 257 |
| stake | 33411 | 243726 | 253716 | 274097 | 67 |
| stakeManager | 368 | 368 | 368 | 368 | 257 |
| leave | 33507 | 136181 | 70120 | 370978 | 4 |
| lock | 33245 | 79219 | 79302 | 112243 | 260 |
| owner | 2339 | 2339 | 2339 | 2339 | 261 |
| register | 87015 | 103918 | 104115 | 104115 | 261 |
| stake | 33411 | 253159 | 255230 | 275752 | 323 |
| stakeManager | 368 | 368 | 368 | 368 | 261 |
| trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 |
| unstake | 33282 | 99955 | 105755 | 113604 | 14 |
| withdraw | 42289 | 42289 | 42289 | 42289 | 1 |
| unstake | 33282 | 99858 | 105888 | 113384 | 14 |
| withdraw | 42278 | 42278 | 42278 | 42278 | 1 |
| src/XPNFTToken.sol:XPNFTToken contract | | | | | |
@@ -189,18 +162,18 @@
| Deployment Cost | Deployment Size | | | | |
| 625454 | 3260 | | | | |
| Function Name | min | avg | median | max | # calls |
| approve | 46330 | 46339 | 46342 | 46342 | 257 |
| approve | 46330 | 46339 | 46342 | 46342 | 261 |
| balanceOf | 558 | 926 | 558 | 2558 | 103 |
| mint | 51279 | 56407 | 51279 | 68379 | 270 |
| mint | 51279 | 56395 | 51279 | 68379 | 274 |
| test/mocks/StackOverflowStakeManager.sol:StackOverflowStakeManager contract | | | | | |
|-----------------------------------------------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost | Deployment Size | | | | |
| 1026739 | 4584 | | | | |
| 1031089 | 4604 | | | | |
| Function Name | min | avg | median | max | # calls |
| leave | 391 | 161316 | 161316 | 322322 | 334 |
| proxiableUUID | 341 | 341 | 341 | 341 | 1 |
| proxiableUUID | 330 | 330 | 330 | 330 | 1 |
| test/mocks/XPProviderMock.sol:XPProviderMock contract | | | | | |

View File

@@ -1,74 +1,75 @@
EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92734)
EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 299008)
EmergencyExitTest:test_EmergencyExitBasic() (gas: 385662)
EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 664160)
EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 393691)
EmergencyExitTest:test_EmergencyExitWithLock() (gas: 393030)
EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 378630)
EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39470)
IntegrationTest:testStakeFoo() (gas: 1212157)
LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 5836880)
LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 296161)
LeaveTest:test_TrustNewStakeManager() (gas: 5907277)
LockTest:test_LockFailsWithInvalidPeriod() (gas: 311224)
LockTest:test_LockFailsWithNoStake() (gas: 63663)
LockTest:test_LockWithoutPriorLock() (gas: 390931)
MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1746581)
MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 18995)
MathTest:test_CalcAccrueMP() (gas: 22229)
MathTest:test_CalcBonusMP() (gas: 17645)
MathTest:test_CalcInitialMP() (gas: 5330)
MathTest:test_CalcMaxAccruedMP() (gas: 15696)
MathTest:test_CalcMaxTotalMP() (gas: 23339)
MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 725540)
EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92757)
EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 300544)
EmergencyExitTest:test_EmergencyExitBasic() (gas: 387340)
EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 667427)
EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 395139)
EmergencyExitTest:test_EmergencyExitWithLock() (gas: 394708)
EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 380241)
EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39471)
IntegrationTest:testStakeFoo() (gas: 1218594)
LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 6214173)
LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 297675)
LeaveTest:test_TrustNewStakeManager() (gas: 6269901)
LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1002, μ: 344783, ~: 344801)
LockTest:test_LockFailsWithNoStake() (gas: 102637)
LockTest:test_LockFailsWithZero() (gas: 315022)
LockTest:test_LockWithoutPriorLock() (gas: 393335)
MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1752531)
MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 4996)
MathTest:test_CalcAccrueMP() (gas: 7990)
MathTest:test_CalcBonusMP() (gas: 18676)
MathTest:test_CalcInitialMP() (gas: 5352)
MathTest:test_CalcMaxAccruedMP() (gas: 4642)
MathTest:test_CalcMaxTotalMP() (gas: 19449)
MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 731369)
NFTMetadataGeneratorSVGTest:testGenerateMetadata() (gas: 85934)
NFTMetadataGeneratorSVGTest:testSetImageStrings() (gas: 58332)
NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35804)
NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 102512)
NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 49555)
NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35979)
RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 486274)
RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160637)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39317)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39340)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39375)
RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 618553)
StakeTest:test_StakeMultipleAccounts() (gas: 499457)
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 505374)
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 842563)
StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 515891)
StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 538001)
StakeTest:test_StakeOneAccount() (gas: 278207)
StakeTest:test_StakeOneAccountAndRewards() (gas: 284155)
StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 507692)
StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 499083)
StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 298124)
StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 299768)
StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 299857)
RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 490632)
RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160880)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39384)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39407)
RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39442)
RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 620722)
StakeTest:test_StakeMultipleAccounts() (gas: 502561)
StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 508596)
StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 847390)
StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517705)
StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539649)
StakeTest:test_StakeOneAccount() (gas: 279841)
StakeTest:test_StakeOneAccountAndRewards() (gas: 285896)
StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 510467)
StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 500009)
StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 300111)
StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 300696)
StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 300763)
StakingTokenTest:testStakeToken() (gas: 10422)
UnstakeTest:test_StakeMultipleAccounts() (gas: 499479)
UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 505396)
UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 842585)
UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 515935)
UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 537957)
UnstakeTest:test_StakeOneAccount() (gas: 278230)
UnstakeTest:test_StakeOneAccountAndRewards() (gas: 284133)
UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 507736)
UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 499040)
UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 298124)
UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 299768)
UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 299856)
UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 546251)
UnstakeTest:test_UnstakeMultipleAccounts() (gas: 704925)
UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 800718)
UnstakeTest:test_UnstakeOneAccount() (gas: 479941)
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 502893)
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 409031)
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 531430)
UpgradeTest:test_RevertWhenNotOwner() (gas: 2709437)
UpgradeTest:test_UpgradeStakeManager() (gas: 5749376)
VaultRegistrationTest:test_VaultRegistration() (gas: 62017)
WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 311841)
UnstakeTest:test_StakeMultipleAccounts() (gas: 502560)
UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 508640)
UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 847367)
UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517704)
UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539693)
UnstakeTest:test_StakeOneAccount() (gas: 279841)
UnstakeTest:test_StakeOneAccountAndRewards() (gas: 285874)
UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 510511)
UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 500008)
UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 300111)
UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 300718)
UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 300762)
UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 546541)
UnstakeTest:test_UnstakeMultipleAccounts() (gas: 707663)
UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 803659)
UnstakeTest:test_UnstakeOneAccount() (gas: 481480)
UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 505028)
UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 410671)
UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 530083)
UpgradeTest:test_RevertWhenNotOwner() (gas: 2897740)
UpgradeTest:test_UpgradeStakeManager() (gas: 6114750)
VaultRegistrationTest:test_VaultRegistration() (gas: 62154)
WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 313397)
XPNFTTokenTest:testApproveNotAllowed() (gas: 10500)
XPNFTTokenTest:testGetApproved() (gas: 10523)
XPNFTTokenTest:testIsApprovedForAll() (gas: 10698)

View File

@@ -9,7 +9,12 @@ definition isViewFunction(method f) returns bool = (
f.selector == sig:streamer.YEAR().selector ||
f.selector == sig:streamer.STAKING_TOKEN().selector ||
f.selector == sig:streamer.SCALE_FACTOR().selector ||
f.selector == sig:streamer.MP_RATE_PER_YEAR().selector ||
f.selector == sig:streamer.MP_APY().selector ||
f.selector == sig:streamer.MP_MPY().selector ||
f.selector == sig:streamer.MP_MPY_ABSOLUTE().selector ||
f.selector == sig:streamer.ACCRUE_RATE().selector ||
f.selector == sig:streamer.MIN_BALANCE().selector ||
f.selector == sig:streamer.MAX_BALANCE().selector ||
f.selector == sig:streamer.MIN_LOCKUP_PERIOD().selector ||
f.selector == sig:streamer.MAX_LOCKUP_PERIOD().selector ||
f.selector == sig:streamer.MAX_MULTIPLIER().selector ||

View File

@@ -10,6 +10,7 @@ import { IStakeManager } from "./interfaces/IStakeManager.sol";
import { IStakeVault } from "./interfaces/IStakeVault.sol";
import { IRewardProvider } from "./interfaces/IRewardProvider.sol";
import { TrustedCodehashAccess } from "./TrustedCodehashAccess.sol";
import { StakeMath } from "./math/StakeMath.sol";
// Rewards Streamer with Multiplier Points
contract RewardsStreamerMP is
@@ -18,7 +19,8 @@ contract RewardsStreamerMP is
IStakeManager,
TrustedCodehashAccess,
ReentrancyGuardUpgradeable,
IRewardProvider
IRewardProvider,
StakeMath
{
error StakingManager__InvalidVault();
error StakingManager__VaultNotRegistered();
@@ -26,7 +28,7 @@ contract RewardsStreamerMP is
error StakingManager__AmountCannotBeZero();
error StakingManager__TransferFailed();
error StakingManager__InsufficientBalance();
error StakingManager__InvalidLockingPeriod();
error StakingManager__LockingPeriodCannotBeZero();
error StakingManager__CannotRestakeWithLockedFunds();
error StakingManager__TokensAreLocked();
error StakingManager__AlreadyLocked();
@@ -36,12 +38,6 @@ contract RewardsStreamerMP is
IERC20 public STAKING_TOKEN;
uint256 public constant SCALE_FACTOR = 1e18;
uint256 public constant MP_RATE_PER_YEAR = 1;
uint256 public constant YEAR = 365 days;
uint256 public constant MIN_LOCKUP_PERIOD = 90 days;
uint256 public constant MAX_LOCKUP_PERIOD = 4 * YEAR;
uint256 public constant MAX_MULTIPLIER = 4;
uint256 public totalStaked;
uint256 public totalMPAccrued;
@@ -193,10 +189,6 @@ contract RewardsStreamerMP is
revert StakingManager__AmountCannotBeZero();
}
if (lockPeriod != 0 && (lockPeriod < MIN_LOCKUP_PERIOD || lockPeriod > MAX_LOCKUP_PERIOD)) {
revert StakingManager__InvalidLockingPeriod();
}
_updateGlobalState();
_updateVaultMP(msg.sender, true);
@@ -204,29 +196,23 @@ contract RewardsStreamerMP is
if (vault.lockUntil != 0 && vault.lockUntil > block.timestamp) {
revert StakingManager__CannotRestakeWithLockedFunds();
}
(uint256 _deltaMpTotal, uint256 _deltaMPMax, uint256 _newLockEnd) =
_calculateStake(vault.stakedBalance, vault.maxMP, vault.lockUntil, block.timestamp, amount, lockPeriod);
vault.stakedBalance += amount;
totalStaked += amount;
uint256 initialMP = amount;
uint256 potentialMP = amount * MAX_MULTIPLIER;
uint256 bonusMP = 0;
if (lockPeriod != 0) {
bonusMP = _calculateBonusMP(amount, lockPeriod);
vault.lockUntil = block.timestamp + lockPeriod;
vault.lockUntil = _newLockEnd;
} else {
vault.lockUntil = 0;
}
uint256 vaultMaxMP = initialMP + bonusMP + potentialMP;
uint256 vaultMP = initialMP + bonusMP;
vault.mpAccrued += _deltaMpTotal;
totalMPAccrued += _deltaMpTotal;
vault.mpAccrued += vaultMP;
totalMPAccrued += vaultMP;
vault.maxMP += vaultMaxMP;
totalMaxMP += vaultMaxMP;
vault.maxMP += _deltaMPMax;
totalMaxMP += _deltaMPMax;
vault.rewardIndex = rewardIndex;
}
@@ -238,33 +224,29 @@ contract RewardsStreamerMP is
onlyRegisteredVault
nonReentrant
{
if (lockPeriod < MIN_LOCKUP_PERIOD || lockPeriod > MAX_LOCKUP_PERIOD) {
revert StakingManager__InvalidLockingPeriod();
}
VaultData storage vault = vaultData[msg.sender];
if (vault.lockUntil > 0) {
revert StakingManager__AlreadyLocked();
}
if (vault.stakedBalance == 0) {
revert StakingManager__InsufficientBalance();
if (lockPeriod == 0) {
revert StakingManager__LockingPeriodCannotBeZero();
}
_updateGlobalState();
_updateVaultMP(msg.sender, true);
(uint256 deltaMp, uint256 newLockEnd) =
_calculateLock(vault.stakedBalance, vault.maxMP, vault.lockUntil, block.timestamp, lockPeriod);
uint256 additionalBonusMP = _calculateBonusMP(vault.stakedBalance, lockPeriod);
// Update vault state
vault.lockUntil = block.timestamp + lockPeriod;
vault.mpAccrued += additionalBonusMP;
vault.maxMP += additionalBonusMP;
// Update account state
vault.lockUntil = newLockEnd;
vault.mpAccrued += deltaMp;
vault.maxMP += deltaMp;
// Update global state
totalMPAccrued += additionalBonusMP;
totalMaxMP += additionalBonusMP;
totalMPAccrued += deltaMp;
totalMaxMP += deltaMp;
vault.rewardIndex = rewardIndex;
}
@@ -277,13 +259,6 @@ contract RewardsStreamerMP is
nonReentrant
{
VaultData storage vault = vaultData[msg.sender];
if (amount > vault.stakedBalance) {
revert StakingManager__InsufficientBalance();
}
if (block.timestamp < vault.lockUntil) {
revert StakingManager__TokensAreLocked();
}
_unstake(amount, vault, msg.sender);
}
@@ -291,18 +266,15 @@ contract RewardsStreamerMP is
_updateGlobalState();
_updateVaultMP(vaultAddress, true);
uint256 previousStakedBalance = vault.stakedBalance;
// solhint-disable-next-line
uint256 mpToReduce = Math.mulDiv(vault.mpAccrued, amount, previousStakedBalance);
uint256 maxMPToReduce = Math.mulDiv(vault.maxMP, amount, previousStakedBalance);
(uint256 _deltaMpTotal, uint256 _deltaMpMax) = _calculateUnstake(
vault.stakedBalance, vault.lockUntil, block.timestamp, vault.mpAccrued, vault.maxMP, amount
);
vault.stakedBalance -= amount;
vault.mpAccrued -= mpToReduce;
vault.maxMP -= maxMPToReduce;
vault.mpAccrued -= _deltaMpTotal;
vault.maxMP -= _deltaMpMax;
vault.rewardIndex = rewardIndex;
totalMPAccrued -= mpToReduce;
totalMaxMP -= maxMPToReduce;
totalMPAccrued -= _deltaMpTotal;
totalMaxMP -= _deltaMpMax;
totalStaked -= amount;
}
@@ -315,6 +287,8 @@ contract RewardsStreamerMP is
VaultData storage vault = vaultData[msg.sender];
if (vault.stakedBalance > 0) {
//updates lockuntil to allow unstake early
vault.lockUntil = block.timestamp;
// calling `_unstake` to update accounting accordingly
_unstake(vault.stakedBalance, vault, msg.sender);
@@ -358,7 +332,7 @@ contract RewardsStreamerMP is
return (adjustedRewardIndex, totalMPAccrued);
}
uint256 accruedMP = (timeDiff * totalStaked * MP_RATE_PER_YEAR) / YEAR;
uint256 accruedMP = _accrueMP(totalStaked, timeDiff);
if (totalMPAccrued + accruedMP > totalMaxMP) {
accruedMP = totalMaxMP - totalMPAccrued;
}
@@ -465,26 +439,19 @@ contract RewardsStreamerMP is
return (accruedRewards, newRewardIndex);
}
function _calculateBonusMP(uint256 amount, uint256 lockPeriod) internal pure returns (uint256) {
return Math.mulDiv(amount, lockPeriod, YEAR);
}
function _getVaultPendingMP(VaultData storage vault) internal view returns (uint256) {
if (block.timestamp == vault.lastMPUpdateTime) {
return 0;
}
if (vault.maxMP == 0 || vault.stakedBalance == 0) {
return 0;
}
uint256 timeDiff = block.timestamp - vault.lastMPUpdateTime;
if (timeDiff == 0) {
return 0;
}
uint256 deltaMpTotal = _calculateAccrual(
vault.stakedBalance, vault.mpAccrued, vault.maxMP, vault.lastMPUpdateTime, block.timestamp
);
uint256 accruedMP = Math.mulDiv(timeDiff * vault.stakedBalance, MP_RATE_PER_YEAR, YEAR);
if (vault.mpAccrued + accruedMP > vault.maxMP) {
accruedMP = vault.maxMP - vault.mpAccrued;
}
return accruedMP;
return deltaMpTotal;
}
function _updateVaultMP(address vaultAddress, bool forceMPUpdate) internal {

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import { ITrustedCodehashAccess } from "./ITrustedCodehashAccess.sol";
/**
* @title IStakeConstants
* @author Ricardo Guilherme Schmidt <ricardo3@status.im>
* @notice Interface for Stake Constants
* @dev This interface is necessary to linearize the inheritance of StakeMath and MultiplierPointMath
*/
interface IStakeConstants {
function MIN_LOCKUP_PERIOD() external view returns (uint256);
function MAX_LOCKUP_PERIOD() external view returns (uint256);
function MP_APY() external view returns (uint256);
function MAX_MULTIPLIER() external view returns (uint256);
}

View File

@@ -3,8 +3,9 @@ pragma solidity ^0.8.26;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ITrustedCodehashAccess } from "./ITrustedCodehashAccess.sol";
import { IStakeConstants } from "./IStakeConstants.sol";
interface IStakeManager is ITrustedCodehashAccess {
interface IStakeManager is ITrustedCodehashAccess, IStakeConstants {
error StakingManager__FundsLocked();
error StakingManager__InvalidLockTime();
error StakingManager__InsufficientFunds();
@@ -24,8 +25,4 @@ interface IStakeManager is ITrustedCodehashAccess {
function getStakedBalance(address _vault) external view returns (uint256 _balance);
function STAKING_TOKEN() external view returns (IERC20);
function MIN_LOCKUP_PERIOD() external view returns (uint256);
function MAX_LOCKUP_PERIOD() external view returns (uint256);
function MP_RATE_PER_YEAR() external view returns (uint256);
function MAX_MULTIPLIER() external view returns (uint256);
}

View File

@@ -0,0 +1,166 @@
// SPDX-License-Identifier: MIT-1.0
pragma solidity ^0.8.26;
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { IStakeConstants } from "../interfaces/IStakeConstants.sol";
/**
* @title MultiplierPointMath
* @author Ricardo Guilherme Schmidt <ricardo3@status.im>
* @notice Provides mathematical operations and utilities for managing multiplier points in the staking system.
*/
abstract contract MultiplierPointMath is IStakeConstants {
/// @notice One (mean) tropical year, in seconds.
uint256 public constant YEAR = 365 days;
/// @notice Accrued multiplier points maximum multiplier.
uint256 public constant MAX_MULTIPLIER = 4;
/// @notice Multiplier points annual percentage yield.
uint256 public constant MP_APY = 100;
/// @notice Multiplier points accrued maximum percentage yield.
uint256 public constant MP_MPY = MAX_MULTIPLIER * MP_APY;
/// @notice Multiplier points absolute maximum percentage yield.
uint256 public constant MP_MPY_ABSOLUTE = 100 + (2 * (MAX_MULTIPLIER * MP_APY));
/// @notice The accrue rate period of time over which multiplier points are calculated.
uint256 public constant ACCRUE_RATE = 1 seconds;
/// @notice Minimal value to generate 1 multiplier point in the accrue rate period (rounded up).
uint256 public constant MIN_BALANCE = (((YEAR * 100) - 1) / (MP_APY * ACCRUE_RATE)) + 1;
/// @notice Maximum value to not overflow unsigned integer of 256 bits.
uint256 public constant MAX_BALANCE = type(uint256).max / (MP_APY * ACCRUE_RATE);
/**
* @notice Calculates the accrued multiplier points (MPs) over a time period Δt, based on the account balance
* @param _balance Represents the current account balance
* @param _deltaTime The time difference or the duration over which the multiplier points are accrued, expressed in
* seconds
* @return accruedMP points accrued for given `_balance` and `_seconds`
*/
function _accrueMP(uint256 _balance, uint256 _deltaTime) internal pure returns (uint256 accruedMP) {
return Math.mulDiv(_balance, _deltaTime * MP_APY, YEAR * 100);
}
/**
* @notice Calculates the bonus multiplier points (MPs) earned when a balance Δa is locked for a specified duration
* t_lock.
* It is equivalent to the accrued multiplier points function but specifically applied in the context of a locked
* balance.
* @param _balance quantity of tokens
* @param _lockedSeconds time in seconds locked
* @return bonusMP bonus multiplier points for given `_balance` and `_lockedSeconds`
*/
function _bonusMP(uint256 _balance, uint256 _lockedSeconds) internal pure returns (uint256 bonusMP) {
return _accrueMP(_balance, _lockedSeconds);
}
/**
* @notice Calculates the initial multiplier points (MPs) based on the balance change Δa. The result is equal to
* the amount of balance added.
* @param _balance Represents the change in balance.
* @return initialMP Initial Multiplier Points
*/
function _initialMP(uint256 _balance) internal pure returns (uint256 initialMP) {
return _balance;
}
/**
* @notice Calculates the reduction in multiplier points (MPs) when a portion of the balance Δa `_reducedAmount` is
* removed from the total balance a_bal `_balance`.
* The reduction is proportional to the ratio of the removed balance to the total balance, applied to the current
* multiplier points $mp$.
* @param _balance The total account balance before the removal of Δa `_reducedBalance`
* @param _mp Represents the current multiplier points
* @param _reducedAmount reduced balance
* @return reducedMP Multiplier points to reduce from `_mp`
*/
function _reduceMP(
uint256 _balance,
uint256 _mp,
uint256 _reducedAmount
)
internal
pure
returns (uint256 reducedMP)
{
return Math.mulDiv(_mp, _reducedAmount, _balance);
}
/**
* @notice Calculates maximum stake a given `_balance` can be generated with `MAX_MULTIPLIER`
* @param _balance quantity of tokens
* @return maxMPAccrued maximum quantity of muliplier points that can be generated for given `_balance`
*/
function _maxAccrueMP(uint256 _balance) internal pure returns (uint256 maxMPAccrued) {
return Math.mulDiv(_balance, MP_MPY, 100);
}
/**
* @notice The maximum total multiplier points that can be generated for a determined amount of balance and lock
* duration.
* @param _balance Represents the current account balance
* @param _lockTime The time duration for which the balance is locked
* @return maxMP Maximum Multiplier Points that can be generated for given `_balance` and `_lockTime`
*/
function _maxTotalMP(uint256 _balance, uint256 _lockTime) internal pure returns (uint256 maxMP) {
return _balance + Math.mulDiv(_balance * MP_APY, (MAX_MULTIPLIER * YEAR) + _lockTime, YEAR * 100);
}
/**
* @notice The absolute maximum total multiplier points that some balance could have, which is the sum of the
* maximum
* lockup time bonus possible and the maximum accrued multiplier points.
* @param _balance quantity of tokens
* @return maxMPAbsolute Absolute Maximum Multiplier Points
*/
function _maxAbsoluteTotalMP(uint256 _balance) internal pure returns (uint256 maxMPAbsolute) {
return Math.mulDiv(_balance, MP_MPY_ABSOLUTE, 100);
}
/**
* @dev Caution: This value is estimated and can be incorrect due precision loss.
* @notice Calculates the remaining lock time available for a given `_mpMax` and `_balance`
* @param _balance Current balance used to calculate the maximum multiplier points.
* @param _mpMax Maximum multiplier points calculated from the current balance.
* @return lockTime Amount of lock time allowed to be increased
*/
function _lockTimeAvailable(uint256 _balance, uint256 _mpMax) internal pure returns (uint256 lockTime) {
return Math.mulDiv((_balance * MP_MPY_ABSOLUTE) - _mpMax, YEAR, _balance * 100);
}
/**
* @notice Calculates the time required to accrue a specific multiplier point value.
* @param _balance The current balance.
* @param _mp The target multiplier points to accrue.
* @return timeToReachMaxMP The time required to reach the specified multiplier points, in seconds.
*/
function _timeToAccrueMP(uint256 _balance, uint256 _mp) internal pure returns (uint256 timeToReachMaxMP) {
return Math.mulDiv(_mp * 100, YEAR, _balance * MP_APY);
}
/**
* @notice Calculates the bonus multiplier points based on the balance and maximum multiplier points.
* @param _balance The current balance.
* @param _maxMP The maximum multiplier points.
* @return bonusMP The calculated bonus multiplier points.
*/
function _retrieveBonusMP(uint256 _balance, uint256 _maxMP) internal pure returns (uint256 bonusMP) {
return _maxMP - (_balance + _maxAccrueMP(_balance));
}
/**
* @notice Retrieves the accrued multiplier points based on the total and maximum multiplier points.
* @param _balance The current balance.
* @param _totalMP The total multiplier points.
* @param _maxMP The maximum multiplier points.
* @return accruedMP The calculated accrued multiplier points.
*/
function _retrieveAccruedMP(
uint256 _balance,
uint256 _totalMP,
uint256 _maxMP
)
internal
pure
returns (uint256 accruedMP)
{
return _totalMP + _maxAccrueMP(_balance) - _maxMP;
}
}

182
src/math/StakeMath.sol Normal file
View File

@@ -0,0 +1,182 @@
// SPDX-License-Identifier: MIT-1.0
pragma solidity ^0.8.26;
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { MultiplierPointMath } from "./MultiplierPointMath.sol";
/**
* @title StakeMath
* @author Ricardo Guilherme Schmidt <ricardo3@status.im>
* @notice Provides mathematical operations and utilities for managing staking operations.
*/
abstract contract StakeMath is MultiplierPointMath {
error StakeMath__FundsLocked();
error StakeMath__InvalidLockingPeriod();
error StakeMath__StakeIsTooLow();
error StakeMath__InsufficientBalance();
error StakeMath__AccrueTimeNotReached();
error StakeMath__AbsoluteMaxMPOverflow();
event StakeMathTest(uint256 lockTime);
/// @notice Minimal lockup time
uint256 public constant MIN_LOCKUP_PERIOD = 90 days;
/// @notice Maximum lockup period
uint256 public constant MAX_LOCKUP_PERIOD = MAX_MULTIPLIER * YEAR;
/**
* @notice Calculates the bonus multiplier points earned when a balance Δa is increased an optionally locked for a
* specified duration
* @param _balance Account current balance
* @param _currentMaxMP Account current max multiplier points
* @param _currentLockEndTime Account current lock end timestamp
* @param _processTime Process current timestamp
* @param _increasedAmount Increased amount of balance
* @param _increasedLockSeconds Increased amount of seconds to lock
* @return _deltaMpTotal Increased amount of total multiplier points
* @return _deltaMpMax Increased amount of max multiplier points
* @return _newLockEnd Account new lock end timestamp
*/
function _calculateStake(
uint256 _balance,
uint256 _currentMaxMP,
uint256 _currentLockEndTime,
uint256 _processTime,
uint256 _increasedAmount,
uint256 _increasedLockSeconds
)
internal
pure
returns (uint256 _deltaMpTotal, uint256 _deltaMpMax, uint256 _newLockEnd)
{
uint256 newBalance = _balance + _increasedAmount;
_newLockEnd = Math.max(_currentLockEndTime, _processTime) + _increasedLockSeconds;
uint256 dt_lock = _newLockEnd - _processTime;
if (dt_lock != 0 && (dt_lock < MIN_LOCKUP_PERIOD || dt_lock > MAX_LOCKUP_PERIOD)) {
revert StakeMath__InvalidLockingPeriod();
}
uint256 deltaMpBonus;
if (dt_lock > 0) {
deltaMpBonus = _bonusMP(_increasedAmount, dt_lock);
}
if (_balance > 0 && _increasedLockSeconds > 0) {
deltaMpBonus += _bonusMP(_balance, _increasedLockSeconds);
}
_deltaMpTotal = _initialMP(_increasedAmount) + deltaMpBonus;
_deltaMpMax = _deltaMpTotal + _accrueMP(_increasedAmount, MAX_MULTIPLIER * YEAR);
if (_deltaMpMax + _currentMaxMP > MP_MPY_ABSOLUTE * newBalance) {
revert StakeMath__AbsoluteMaxMPOverflow();
}
}
/**
* @notice Calculates the bonus multiplier points earned when a balance Δa is locked for a specified duration
* @param _balance Account current balance
* @param _currentMaxMP Account current max multiplier points
* @param _currentLockEndTime Account current lock end timestamp
* @param _processTime Process current timestamp
* @param _increasedLockSeconds Increased amount of seconds to lock
* @return _deltaMp Increased amount of total and max multiplier points
* @return _newLockEnd Account new lock end timestamp
*/
function _calculateLock(
uint256 _balance,
uint256 _currentMaxMP,
uint256 _currentLockEndTime,
uint256 _processTime,
uint256 _increasedLockSeconds
)
internal
pure
returns (uint256 _deltaMp, uint256 _newLockEnd)
{
if (_balance == 0) {
revert StakeMath__InsufficientBalance();
}
_newLockEnd = Math.max(_currentLockEndTime, _processTime) + _increasedLockSeconds;
uint256 dt_lock = _newLockEnd - _processTime;
if (dt_lock != 0 && (dt_lock < MIN_LOCKUP_PERIOD || dt_lock > MAX_LOCKUP_PERIOD)) {
revert StakeMath__InvalidLockingPeriod();
}
_deltaMp = _bonusMP(_balance, _increasedLockSeconds);
if (_deltaMp + _currentMaxMP > MP_MPY_ABSOLUTE * _balance) {
revert StakeMath__AbsoluteMaxMPOverflow();
}
}
/**
*
* @param _balance Account current balance
* @param _currentLockEndTime Account current lock end timestamp
* @param _processTime Process current timestamp
* @param _currentTotalMP Account current total multiplier points
* @param _currentMaxMP Account current max multiplier points
* @param _reducedAmount Reduced amount of balance
* @return _deltaMpTotal Increased amount of total multiplier points
* @return _deltaMpMax Increased amount of max multiplier points
*/
function _calculateUnstake(
uint256 _balance,
uint256 _currentLockEndTime,
uint256 _processTime,
uint256 _currentTotalMP,
uint256 _currentMaxMP,
uint256 _reducedAmount
)
internal
pure
returns (uint256 _deltaMpTotal, uint256 _deltaMpMax)
{
if (_reducedAmount > _balance) {
revert StakeMath__InsufficientBalance();
}
if (_currentLockEndTime > _processTime) {
revert StakeMath__FundsLocked();
}
_deltaMpTotal = _reduceMP(_balance, _currentTotalMP, _reducedAmount);
_deltaMpMax = _reduceMP(_balance, _currentMaxMP, _reducedAmount);
}
/**
* @notice Calculates the accrued multiplier points for a given balance and seconds passed since last accrual
* @param _balance Account current balance
* @param _currentTotalMP Account current total multiplier points
* @param _currentMaxMP Account current max multiplier points
* @param _lastAccrualTime Account current last accrual timestamp
* @param _processTime Process current timestamp
* @return _deltaMpTotal Increased amount of total multiplier points
*/
function _calculateAccrual(
uint256 _balance,
uint256 _currentTotalMP,
uint256 _currentMaxMP,
uint256 _lastAccrualTime,
uint256 _processTime
)
internal
pure
returns (uint256 _deltaMpTotal)
{
uint256 dt = _processTime - _lastAccrualTime;
if (_currentTotalMP < _currentMaxMP) {
_deltaMpTotal = Math.min(_accrueMP(_balance, dt), _currentMaxMP - _currentTotalMP);
}
}
/**
* @dev Caution: This value is estimated and can be incorrect due precision loss.
* @notice Estimates the time an account set as locked time.
* @param _mpMax Maximum multiplier points calculated from the current balance.
* @param _balance Current balance used to calculate the maximum multiplier points.
*/
function _estimateLockTime(uint256 _mpMax, uint256 _balance) internal pure returns (uint256 _lockTime) {
return Math.mulDiv((_mpMax - _balance) * 100, YEAR, _balance * MP_APY, Math.Rounding.Ceil) - MAX_LOCKUP_PERIOD;
}
}

View File

@@ -9,13 +9,14 @@ import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol";
import { StakeMath } from "../src/math/StakeMath.sol";
import { StakeVault } from "../src/StakeVault.sol";
import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol";
import { StakeManagerProxy } from "../src/StakeManagerProxy.sol";
import { MockToken } from "./mocks/MockToken.sol";
import { StackOverflowStakeManager } from "./mocks/StackOverflowStakeManager.sol";
contract RewardsStreamerMPTest is Test {
contract RewardsStreamerMPTest is StakeMath, Test {
MockToken stakingToken;
RewardsStreamerMP public streamer;
@@ -133,47 +134,12 @@ contract RewardsStreamerMPTest is Test {
vault.leave(account);
}
function _calculeInitialMP(uint256 amount) internal pure returns (uint256) {
return amount;
}
function _calculateMaxAccruedMP(uint256 amount) internal view returns (uint256) {
return amount * streamer.MAX_MULTIPLIER();
}
function _calculateAbsoluteMaxTotalMP(uint256 amount) internal view returns (uint256) {
return _calculeInitialMP(amount) + _calculateBonusMP(amount, streamer.MAX_LOCKUP_PERIOD())
+ _calculateMaxAccruedMP(amount);
}
function _calculateMaxTotalMP(uint256 amount, uint256 lockPeriod) internal view returns (uint256 maxTotalMaxMP) {
uint256 bonusMP = 0;
if (lockPeriod != 0) {
bonusMP = _calculateBonusMP(amount, lockPeriod);
}
return _calculeInitialMP(amount) + bonusMP + _calculateMaxAccruedMP(amount);
}
function _calculateBonusMP(uint256 amount, uint256 lockupTime) internal view returns (uint256) {
// solhint-disable-next-line
return Math.mulDiv(amount, lockupTime, 365 days);
}
function _calculateAccuredMP(uint256 totalStaked, uint256 timeDiff) internal view returns (uint256) {
return Math.mulDiv(timeDiff * totalStaked, streamer.MP_RATE_PER_YEAR(), 365 days);
}
function _calculateTimeToAccureMPLimit(uint256 amount) internal view returns (uint256) {
function _timeToAccrueMPLimit(uint256 amount) internal view returns (uint256) {
uint256 maxMP = amount * streamer.MAX_MULTIPLIER();
uint256 timeInSeconds = _calculateTimeToAccureMP(amount, maxMP);
uint256 timeInSeconds = _timeToAccrueMP(amount, maxMP);
return timeInSeconds;
}
function _calculateTimeToAccureMP(uint256 amount, uint256 target) internal view returns (uint256) {
uint256 mpPerYear = amount * streamer.MP_RATE_PER_YEAR();
return target * 365 days / mpPerYear;
}
function _upgradeStakeManager() internal {
UpgradeRewardsStreamerMPScript upgrade = new UpgradeRewardsStreamerMPScript();
upgrade.run(admin, IStakeManagerProxy(address(streamer)));
@@ -181,53 +147,45 @@ contract RewardsStreamerMPTest is Test {
}
contract MathTest is RewardsStreamerMPTest {
function test_CalcInitialMP() public {
assertEq(_calculeInitialMP(1), 1, "wrong initial MP");
assertEq(_calculeInitialMP(10e18), 10e18, "wrong initial MP");
assertEq(_calculeInitialMP(20e18), 20e18, "wrong initial MP");
assertEq(_calculeInitialMP(30e18), 30e18, "wrong initial MP");
function test_CalcInitialMP() public pure {
assertEq(_initialMP(1), 1, "wrong initial MP");
assertEq(_initialMP(10e18), 10e18, "wrong initial MP");
assertEq(_initialMP(20e18), 20e18, "wrong initial MP");
assertEq(_initialMP(30e18), 30e18, "wrong initial MP");
}
function test_CalcAccrueMP() public {
assertEq(_calculateAccuredMP(10e18, 0), 0, "wrong accrued MP");
assertEq(_calculateAccuredMP(10e18, 365 days / 2), 5e18, "wrong accrued MP");
assertEq(_calculateAccuredMP(10e18, 365 days), 10e18, "wrong accrued MP");
assertEq(_calculateAccuredMP(10e18, 365 days * 2), 20e18, "wrong accrued MP");
assertEq(_calculateAccuredMP(10e18, 365 days * 3), 30e18, "wrong accrued MP");
function test_CalcAccrueMP() public pure {
assertEq(_accrueMP(10e18, 0), 0, "wrong accrued MP");
assertEq(_accrueMP(10e18, 365 days / 2), 5e18, "wrong accrued MP");
assertEq(_accrueMP(10e18, 365 days), 10e18, "wrong accrued MP");
assertEq(_accrueMP(10e18, 365 days * 2), 20e18, "wrong accrued MP");
assertEq(_accrueMP(10e18, 365 days * 3), 30e18, "wrong accrued MP");
}
function test_CalcBonusMP() public {
assertEq(_calculateBonusMP(10e18, 0), 0, "wrong bonus MP");
assertEq(_calculateBonusMP(10e18, streamer.MIN_LOCKUP_PERIOD()), 2_465_753_424_657_534_246, "wrong bonus MP");
function test_CalcBonusMP() public view {
assertEq(_bonusMP(10e18, 0), 0, "wrong bonus MP");
assertEq(_bonusMP(10e18, streamer.MIN_LOCKUP_PERIOD()), 2_465_753_424_657_534_246, "wrong bonus MP");
assertEq(_bonusMP(10e18, streamer.MIN_LOCKUP_PERIOD() + 13 days), 2_821_917_808_219_178_082, "wrong bonus MP");
assertEq(_bonusMP(100e18, 0), 0, "wrong bonus MP");
}
function test_CalcMaxTotalMP() public view {
assertEq(_maxTotalMP(10e18, 0), 50e18, "wrong max total MP");
assertEq(_maxTotalMP(10e18, streamer.MIN_LOCKUP_PERIOD()), 52_465_753_424_657_534_246, "wrong max total MP");
assertEq(
_calculateBonusMP(10e18, streamer.MIN_LOCKUP_PERIOD() + 13 days),
2_821_917_808_219_178_082,
"wrong bonus MP"
_maxTotalMP(10e18, streamer.MIN_LOCKUP_PERIOD() + 13 days), 52_821_917_808_219_178_082, "wrong max total MP"
);
assertEq(_calculateBonusMP(100e18, 0), 0, "wrong bonus MP");
assertEq(_maxTotalMP(100e18, 0), 500e18, "wrong max total MP");
}
function test_CalcMaxTotalMP() public {
assertEq(_calculateMaxTotalMP(10e18, 0), 50e18, "wrong max total MP");
assertEq(
_calculateMaxTotalMP(10e18, streamer.MIN_LOCKUP_PERIOD()), 52_465_753_424_657_534_246, "wrong max total MP"
);
assertEq(
_calculateMaxTotalMP(10e18, streamer.MIN_LOCKUP_PERIOD() + 13 days),
52_821_917_808_219_178_082,
"wrong max total MP"
);
assertEq(_calculateMaxTotalMP(100e18, 0), 500e18, "wrong max total MP");
function test_CalcAbsoluteMaxTotalMP() public pure {
assertEq(_maxAbsoluteTotalMP(10e18), 90e18, "wrong absolute max total MP");
assertEq(_maxAbsoluteTotalMP(100e18), 900e18, "wrong absolute max total MP");
}
function test_CalcAbsoluteMaxTotalMP() public {
assertEq(_calculateAbsoluteMaxTotalMP(10e18), 90e18, "wrong absolute max total MP");
assertEq(_calculateAbsoluteMaxTotalMP(100e18), 900e18, "wrong absolute max total MP");
}
function test_CalcMaxAccruedMP() public {
assertEq(_calculateMaxAccruedMP(10e18), 40e18, "wrong max accrued MP");
assertEq(_calculateMaxAccruedMP(100e18), 400e18, "wrong max accrued MP");
function test_CalcMaxAccruedMP() public pure {
assertEq(_maxAccrueMP(10e18), 40e18, "wrong max accrued MP");
assertEq(_maxAccrueMP(100e18), 400e18, "wrong max accrued MP");
}
}
@@ -372,7 +330,7 @@ contract IntegrationTest is RewardsStreamerMPTest {
// T4
uint256 currentTime = vm.getBlockTimestamp();
vm.warp(currentTime + (365 days / 2));
vm.warp(currentTime + (YEAR / 2));
streamer.updateGlobalState();
checkStreamer(
@@ -660,10 +618,10 @@ contract StakeTest is RewardsStreamerMPTest {
function test_StakeOneAccountWithMinLockUp() public {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MIN_LOCKUP_PERIOD();
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);
uint256 expectedBonusMP = _bonusMP(stakeAmount, lockUpPeriod);
_stake(alice, stakeAmount, lockUpPeriod);
uint256 expectedMaxTotalMP = _calculateMaxTotalMP(stakeAmount, lockUpPeriod);
uint256 expectedMaxTotalMP = _maxTotalMP(stakeAmount, lockUpPeriod);
checkStreamer(
CheckStreamerParams({
@@ -681,7 +639,7 @@ contract StakeTest is RewardsStreamerMPTest {
function test_StakeOneAccountWithMaxLockUp() public {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MAX_LOCKUP_PERIOD();
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);
uint256 expectedBonusMP = _bonusMP(stakeAmount, lockUpPeriod);
_stake(alice, stakeAmount, lockUpPeriod);
@@ -701,10 +659,10 @@ contract StakeTest is RewardsStreamerMPTest {
function test_StakeOneAccountWithRandomLockUp() public {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MIN_LOCKUP_PERIOD() + 13 days;
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);
uint256 expectedBonusMP = _bonusMP(stakeAmount, lockUpPeriod);
_stake(alice, stakeAmount, lockUpPeriod);
uint256 expectedMaxTotalMP = _calculateMaxTotalMP(stakeAmount, lockUpPeriod);
uint256 expectedMaxTotalMP = _maxTotalMP(stakeAmount, lockUpPeriod);
checkStreamer(
CheckStreamerParams({
@@ -738,7 +696,7 @@ contract StakeTest is RewardsStreamerMPTest {
);
uint256 currentTime = vm.getBlockTimestamp();
vm.warp(currentTime + (365 days));
vm.warp(currentTime + (YEAR));
streamer.updateGlobalState();
streamer.updateVaultMP(vaults[alice]);
@@ -770,7 +728,7 @@ contract StakeTest is RewardsStreamerMPTest {
);
currentTime = vm.getBlockTimestamp();
vm.warp(currentTime + (365 days / 2));
vm.warp(currentTime + (YEAR / 2));
streamer.updateGlobalState();
streamer.updateVaultMP(vaults[alice]);
@@ -833,7 +791,7 @@ contract StakeTest is RewardsStreamerMPTest {
);
uint256 currentTime = vm.getBlockTimestamp();
uint256 timeToMaxMP = _calculateTimeToAccureMP(stakeAmount, totalMaxMP - totalMPAccrued);
uint256 timeToMaxMP = _timeToAccrueMP(stakeAmount, totalMaxMP - totalMPAccrued);
vm.warp(currentTime + timeToMaxMP);
streamer.updateGlobalState();
@@ -982,11 +940,11 @@ contract StakeTest is RewardsStreamerMPTest {
function test_StakeMultipleAccountsWithMinLockUp() public {
uint256 aliceStakeAmount = 10e18;
uint256 aliceLockUpPeriod = streamer.MIN_LOCKUP_PERIOD();
uint256 aliceExpectedBonusMP = _calculateBonusMP(aliceStakeAmount, aliceLockUpPeriod);
uint256 aliceExpectedBonusMP = _bonusMP(aliceStakeAmount, aliceLockUpPeriod);
uint256 bobStakeAmount = 30e18;
uint256 bobLockUpPeriod = 0;
uint256 bobExpectedBonusMP = _calculateBonusMP(bobStakeAmount, bobLockUpPeriod);
uint256 bobExpectedBonusMP = _bonusMP(bobStakeAmount, bobLockUpPeriod);
// alice stakes with lockup period
_stake(alice, aliceStakeAmount, aliceLockUpPeriod);
@@ -996,8 +954,8 @@ contract StakeTest is RewardsStreamerMPTest {
uint256 sumOfStakeAmount = aliceStakeAmount + bobStakeAmount;
uint256 sumOfExpectedBonusMP = aliceExpectedBonusMP + bobExpectedBonusMP;
uint256 expectedMaxTotalMP = _calculateMaxTotalMP(aliceStakeAmount, aliceLockUpPeriod)
+ _calculateMaxTotalMP(bobStakeAmount, bobLockUpPeriod);
uint256 expectedMaxTotalMP =
_maxTotalMP(aliceStakeAmount, aliceLockUpPeriod) + _maxTotalMP(bobStakeAmount, bobLockUpPeriod);
checkStreamer(
CheckStreamerParams({
totalStaked: sumOfStakeAmount,
@@ -1013,11 +971,11 @@ contract StakeTest is RewardsStreamerMPTest {
function test_StakeMultipleAccountsWithRandomLockUp() public {
uint256 aliceStakeAmount = 10e18;
uint256 aliceLockUpPeriod = streamer.MAX_LOCKUP_PERIOD() - 21 days;
uint256 aliceExpectedBonusMP = _calculateBonusMP(aliceStakeAmount, aliceLockUpPeriod);
uint256 aliceExpectedBonusMP = _bonusMP(aliceStakeAmount, aliceLockUpPeriod);
uint256 bobStakeAmount = 30e18;
uint256 bobLockUpPeriod = streamer.MIN_LOCKUP_PERIOD() + 43 days;
uint256 bobExpectedBonusMP = _calculateBonusMP(bobStakeAmount, bobLockUpPeriod);
uint256 bobExpectedBonusMP = _bonusMP(bobStakeAmount, bobLockUpPeriod);
// alice stakes with lockup period
_stake(alice, aliceStakeAmount, aliceLockUpPeriod);
@@ -1027,8 +985,8 @@ contract StakeTest is RewardsStreamerMPTest {
uint256 sumOfStakeAmount = aliceStakeAmount + bobStakeAmount;
uint256 sumOfExpectedBonusMP = aliceExpectedBonusMP + bobExpectedBonusMP;
uint256 expectedMaxTotalMP = _calculateMaxTotalMP(aliceStakeAmount, aliceLockUpPeriod)
+ _calculateMaxTotalMP(bobStakeAmount, bobLockUpPeriod);
uint256 expectedMaxTotalMP =
_maxTotalMP(aliceStakeAmount, aliceLockUpPeriod) + _maxTotalMP(bobStakeAmount, bobLockUpPeriod);
checkStreamer(
CheckStreamerParams({
@@ -1093,7 +1051,7 @@ contract StakeTest is RewardsStreamerMPTest {
);
uint256 currentTime = vm.getBlockTimestamp();
vm.warp(currentTime + (365 days));
vm.warp(currentTime + (YEAR));
streamer.updateGlobalState();
streamer.updateVaultMP(vaults[alice]);
@@ -1142,7 +1100,7 @@ contract StakeTest is RewardsStreamerMPTest {
);
currentTime = vm.getBlockTimestamp();
vm.warp(currentTime + (365 days / 2));
vm.warp(currentTime + (YEAR / 2));
streamer.updateGlobalState();
streamer.updateVaultMP(vaults[alice]);
@@ -1244,7 +1202,7 @@ contract UnstakeTest is StakeTest {
// wait for 1 year
uint256 currentTime = vm.getBlockTimestamp();
vm.warp(currentTime + (365 days));
vm.warp(currentTime + (YEAR));
streamer.updateGlobalState();
streamer.updateVaultMP(vaults[alice]);
@@ -1281,7 +1239,7 @@ contract UnstakeTest is StakeTest {
uint256 stakeAmount = 10e18;
uint256 lockUpPeriod = streamer.MIN_LOCKUP_PERIOD();
// 10e18 is what's used in `test_StakeOneAccountWithMinLockUp`
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockUpPeriod);
uint256 expectedBonusMP = _bonusMP(stakeAmount, lockUpPeriod);
uint256 unstakeAmount = 5e18;
uint256 warpLength = (365 days);
// wait for 1 year
@@ -1297,7 +1255,7 @@ contract UnstakeTest is StakeTest {
totalStaked: stakeAmount,
totalMPAccrued: (stakeAmount + expectedBonusMP) + stakeAmount, // we do `+ stakeAmount` we've accrued
// `stakeAmount` after 1 year
totalMaxMP: _calculateMaxTotalMP(stakeAmount, lockUpPeriod),
totalMaxMP: _maxTotalMP(stakeAmount, lockUpPeriod),
stakingBalance: 10e18,
rewardBalance: 0,
rewardIndex: 0
@@ -1307,13 +1265,13 @@ contract UnstakeTest is StakeTest {
// unstake half of the tokens
_unstake(alice, unstakeAmount);
uint256 expectedTotalMP = _calculeInitialMP(newBalance) + _calculateBonusMP(newBalance, lockUpPeriod)
+ _calculateAccuredMP(newBalance, warpLength);
uint256 expectedTotalMP =
_initialMP(newBalance) + _bonusMP(newBalance, lockUpPeriod) + _accrueMP(newBalance, warpLength);
checkStreamer(
CheckStreamerParams({
totalStaked: newBalance,
totalMPAccrued: expectedTotalMP,
totalMaxMP: _calculateMaxTotalMP(newBalance, lockUpPeriod),
totalMaxMP: _maxTotalMP(newBalance, lockUpPeriod),
stakingBalance: newBalance,
rewardBalance: 0,
rewardIndex: 0
@@ -1355,7 +1313,7 @@ contract UnstakeTest is StakeTest {
uint256 amountStaked = 10e18;
uint256 secondsLocked = streamer.MIN_LOCKUP_PERIOD();
uint256 reducedStake = 5e18;
uint256 increasedTime = 365 days;
uint256 increasedTime = YEAR;
//initialize memory placehodlders
uint256[4] memory timestamp;
@@ -1371,8 +1329,8 @@ contract UnstakeTest is StakeTest {
{
timestamp[stage] = block.timestamp;
totalStaked[stage] = amountStaked;
predictedBonusMP[stage] = totalStaked[stage] + _calculateBonusMP(totalStaked[stage], secondsLocked);
predictedTotalMaxMP[stage] = _calculateMaxTotalMP(totalStaked[stage], secondsLocked);
predictedBonusMP[stage] = totalStaked[stage] + _bonusMP(totalStaked[stage], secondsLocked);
predictedTotalMaxMP[stage] = _maxTotalMP(totalStaked[stage], secondsLocked);
increasedAccuredMP[stage] = 0; //no increased accured MP in first stage
predictedAccuredMP[stage] = 0; //no accured MP in first stage
predictedTotalMP[stage] = predictedBonusMP[stage] + predictedAccuredMP[stage];
@@ -1384,7 +1342,7 @@ contract UnstakeTest is StakeTest {
predictedBonusMP[stage] = predictedBonusMP[stage - 1]; //no change in bonusMP in second stage
predictedTotalMaxMP[stage] = predictedTotalMaxMP[stage - 1];
// solhint-disable-next-line max-line-length
increasedAccuredMP[stage] = _calculateAccuredMP(totalStaked[stage], timestamp[stage] - timestamp[stage - 1]);
increasedAccuredMP[stage] = _accrueMP(totalStaked[stage], timestamp[stage] - timestamp[stage - 1]);
predictedAccuredMP[stage] = predictedAccuredMP[stage - 1] + increasedAccuredMP[stage];
predictedTotalMP[stage] = predictedBonusMP[stage] + predictedAccuredMP[stage];
}
@@ -1604,8 +1562,8 @@ contract LockTest is RewardsStreamerMPTest {
);
// Lock for 1 year
uint256 lockPeriod = 365 days;
uint256 expectedBonusMP = _calculateBonusMP(stakeAmount, lockPeriod);
uint256 lockPeriod = YEAR;
uint256 expectedBonusMP = _bonusMP(stakeAmount, lockPeriod);
_lock(alice, lockPeriod);
@@ -1624,17 +1582,28 @@ contract LockTest is RewardsStreamerMPTest {
}
function test_LockFailsWithNoStake() public {
vm.expectRevert(RewardsStreamerMP.StakingManager__InsufficientBalance.selector);
_lock(alice, 365 days);
vm.expectRevert(StakeMath.StakeMath__InsufficientBalance.selector);
_lock(alice, YEAR);
}
function test_LockFailsWithInvalidPeriod() public {
function test_LockFailsWithZero() public {
_stake(alice, 10e18, 0);
// Test with period = 0
vm.expectRevert(RewardsStreamerMP.StakingManager__InvalidLockingPeriod.selector);
vm.expectRevert(RewardsStreamerMP.StakingManager__LockingPeriodCannotBeZero.selector);
_lock(alice, 0);
}
function test_LockFailsWithInvalidPeriod(uint256 _lockPeriod) public {
vm.assume(_lockPeriod > 0);
vm.assume(_lockPeriod < MIN_LOCKUP_PERIOD || _lockPeriod > MAX_LOCKUP_PERIOD);
vm.assume(_lockPeriod < (type(uint256).max - block.timestamp)); //prevents arithmetic overflow
_stake(alice, 10e18, 0);
vm.expectRevert(StakeMath.StakeMath__InvalidLockingPeriod.selector);
_lock(alice, _lockPeriod);
}
}
contract EmergencyExitTest is RewardsStreamerMPTest {

View File

@@ -6,6 +6,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TrustedCodehashAccess } from "./../../src/TrustedCodehashAccess.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import { IStakeConstants } from "./../../src/interfaces/IStakeConstants.sol";
contract StackOverflowStakeManager is
UUPSUpgradeable,
@@ -16,7 +17,7 @@ contract StackOverflowStakeManager is
IERC20 public STAKING_TOKEN;
uint256 public constant SCALE_FACTOR = 1e18;
uint256 public constant MP_RATE_PER_YEAR = 1e18;
uint256 public constant MP_APY = 100;
uint256 public constant MIN_LOCKUP_PERIOD = 90 days;
uint256 public constant MAX_LOCKUP_PERIOD = 4 * 365 days;