diff --git a/.gas-report b/.gas-report index bb2d58d..5554d7f 100644 --- a/.gas-report +++ b/.gas-report @@ -15,81 +15,52 @@ | src/RewardsStreamerMP.sol:RewardsStreamerMP contract | | | | | | |------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 2494954 | 11497 | | | | | +| 2525879 | 11640 | | | | | | Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 294 | 294 | 294 | 294 | 23 | -| MAX_MULTIPLIER | 251 | 251 | 251 | 251 | 30 | +| MAX_LOCKUP_PERIOD | 349 | 349 | 349 | 349 | 4 | +| MAX_MULTIPLIER | 251 | 251 | 251 | 251 | 11 | | MIN_LOCKUP_PERIOD | 297 | 297 | 297 | 297 | 11 | | MP_RATE_PER_YEAR | 253 | 253 | 253 | 253 | 3 | -| SCALE_FACTOR | 273 | 273 | 273 | 273 | 41 | | STAKING_TOKEN | 2428 | 2428 | 2428 | 2428 | 292 | -| emergencyModeEnabled | 2398 | 2398 | 2398 | 2398 | 7 | -| enableEmergencyMode | 2463 | 19370 | 24655 | 24655 | 8 | -| getAccount | 1639 | 1639 | 1639 | 1639 | 72 | +| emergencyModeEnabled | 2420 | 2420 | 2420 | 2420 | 7 | +| enableEmergencyMode | 2485 | 19392 | 24677 | 24677 | 8 | +| getAccount | 1661 | 1661 | 1661 | 1661 | 72 | | getStakedBalance | 2629 | 2629 | 2629 | 2629 | 1 | | getUserTotalMP | 9230 | 9230 | 9230 | 9230 | 1 | | getUserTotalMaxMP | 3123 | 3123 | 3123 | 3123 | 1 | -| getUserTotalStakedBalance | 15162 | 15162 | 15162 | 15162 | 1 | -| getUserVaults | 5245 | 5245 | 5245 | 5245 | 4 | +| getUserTotalStakedBalance | 15118 | 15118 | 15118 | 15118 | 1 | +| getUserVaults | 5201 | 5201 | 5201 | 5201 | 4 | | initialize | 115611 | 115611 | 115611 | 115611 | 59 | | isTrustedCodehash | 519 | 519 | 519 | 519 | 231 | -| lastRewardTime | 373 | 1373 | 1373 | 2373 | 2 | -| leave | 56613 | 56613 | 56613 | 56613 | 1 | -| lock | 12041 | 34170 | 16370 | 74099 | 3 | +| lastRewardTime | 395 | 1395 | 1395 | 2395 | 2 | +| leave | 56244 | 56244 | 56244 | 56244 | 1 | +| lock | 12063 | 34172 | 16480 | 73975 | 3 | | proxiableUUID | 331 | 331 | 331 | 331 | 3 | | registerVault | 55866 | 72745 | 72966 | 72966 | 233 | | rewardEndTime | 373 | 1373 | 1373 | 2373 | 2 | | rewardStartTime | 352 | 1352 | 1352 | 2352 | 2 | -| rewardsBalanceOf | 1294 | 1294 | 1294 | 1294 | 4 | -| setReward | 2561 | 50875 | 60256 | 102573 | 7 | +| rewardsBalanceOf | 1317 | 1317 | 1317 | 1317 | 4 | +| setReward | 2583 | 50892 | 60278 | 102595 | 7 | | setTrustedCodehash | 26243 | 26243 | 26243 | 26243 | 59 | -| stake | 131082 | 170235 | 177899 | 198378 | 66 | +| stake | 131082 | 170202 | 177899 | 198232 | 66 | | totalMP | 373 | 373 | 373 | 373 | 81 | | totalMaxMP | 350 | 350 | 350 | 350 | 81 | -| totalRewardsAccrued | 351 | 351 | 351 | 351 | 3 | -| totalRewardsSupply | 1003 | 1964 | 1767 | 6743 | 30 | +| totalRewardsAccrued | 373 | 373 | 373 | 373 | 3 | +| totalRewardsSupply | 960 | 1921 | 1724 | 6700 | 30 | | totalStaked | 396 | 396 | 396 | 396 | 82 | -| unstake | 60706 | 61254 | 60706 | 64269 | 13 | -| updateAccountMP | 15464 | 18542 | 17966 | 35066 | 21 | -| updateGlobalState | 11066 | 28094 | 25315 | 110295 | 21 | +| unstake | 60382 | 60919 | 60382 | 63877 | 13 | +| updateAccountMP | 15396 | 18474 | 17898 | 34998 | 21 | +| updateGlobalState | 11088 | 28045 | 25249 | 110284 | 21 | | upgradeToAndCall | 3225 | 9387 | 10926 | 10936 | 5 | -| src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | | -|------------------------------------------------------|-----------------|-------|--------|--------|---------| -| Deployment Cost | Deployment Size | | | | | -| 256079 | 1231 | | | | | -| Function Name | min | avg | median | max | # calls | -| MAX_LOCKUP_PERIOD | 721 | 1503 | 721 | 5221 | 23 | -| MAX_MULTIPLIER | 678 | 1578 | 678 | 5178 | 30 | -| MIN_LOCKUP_PERIOD | 724 | 3996 | 5224 | 5224 | 11 | -| MP_RATE_PER_YEAR | 680 | 680 | 680 | 680 | 3 | -| SCALE_FACTOR | 700 | 700 | 700 | 700 | 41 | -| STAKING_TOKEN | 7355 | 7355 | 7355 | 7355 | 292 | -| emergencyModeEnabled | 7325 | 7325 | 7325 | 7325 | 7 | -| enableEmergencyMode | 28458 | 45359 | 50643 | 50643 | 8 | -| getAccount | 2093 | 2093 | 2093 | 2093 | 72 | -| getStakedBalance | 7559 | 7559 | 7559 | 7559 | 1 | -| getUserTotalMP | 9660 | 9660 | 9660 | 9660 | 1 | -| getUserTotalMaxMP | 3553 | 3553 | 3553 | 3553 | 1 | -| getUserTotalStakedBalance | 15592 | 15592 | 15592 | 15592 | 1 | -| getUserVaults | 5681 | 6806 | 5681 | 10181 | 4 | -| implementation | 343 | 808 | 343 | 2343 | 382 | -| isTrustedCodehash | 949 | 949 | 949 | 949 | 231 | -| lastRewardTime | 800 | 1800 | 1800 | 2800 | 2 | -| rewardEndTime | 800 | 1800 | 1800 | 2800 | 2 | -| rewardStartTime | 779 | 4029 | 4029 | 7279 | 2 | -| rewardsBalanceOf | 1724 | 1724 | 1724 | 1724 | 4 | -| setReward | 28841 | 77189 | 86614 | 128859 | 7 | -| setTrustedCodehash | 52889 | 52889 | 52889 | 52889 | 59 | -| totalMP | 800 | 800 | 800 | 800 | 81 | -| totalMaxMP | 777 | 777 | 777 | 777 | 81 | -| totalRewardsAccrued | 778 | 778 | 778 | 778 | 3 | -| totalRewardsSupply | 1430 | 2541 | 2194 | 11670 | 30 | -| totalStaked | 823 | 823 | 823 | 823 | 82 | -| updateAccountMP | 41823 | 44901 | 44325 | 61425 | 21 | -| updateGlobalState | 37054 | 54082 | 51303 | 136283 | 21 | -| upgradeToAndCall | 29868 | 36025 | 37562 | 37572 | 5 | +| src/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | | +|------------------------------------------------------|-----------------|------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 256079 | 1231 | | | | | +| Function Name | min | avg | median | max | # calls | +| fallback | 678 | 8892 | 2115 | 136272 | 1047 | +| implementation | 343 | 808 | 343 | 2343 | 382 | | src/StakeVault.sol:StakeVault contract | | | | | | @@ -98,15 +69,15 @@ | 1420425 | 6695 | | | | | | Function Name | min | avg | median | max | # calls | | STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 | -| emergencyExit | 36353 | 48857 | 48091 | 65191 | 7 | -| leave | 33507 | 131513 | 60783 | 370978 | 4 | -| lock | 33245 | 60675 | 50779 | 107896 | 4 | +| emergencyExit | 36375 | 48879 | 48113 | 65213 | 7 | +| leave | 33507 | 131439 | 60635 | 370978 | 4 | +| lock | 33245 | 60677 | 50845 | 107772 | 4 | | owner | 2339 | 2339 | 2339 | 2339 | 233 | | register | 87015 | 103894 | 104115 | 104115 | 233 | -| stake | 33411 | 241524 | 252403 | 272930 | 67 | +| stake | 33411 | 241491 | 252403 | 272784 | 67 | | stakeManager | 368 | 368 | 368 | 368 | 233 | | trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 | -| unstake | 33282 | 96931 | 102420 | 110233 | 14 | +| unstake | 33282 | 96630 | 102062 | 109909 | 14 | | withdraw | 42289 | 42289 | 42289 | 42289 | 1 | diff --git a/.gas-snapshot b/.gas-snapshot index eb9651e..db4e45c 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,68 +1,68 @@ -EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92646) -EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 297695) -EmergencyExitTest:test_EmergencyExitBasic() (gas: 384389) -EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 659100) -EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 392308) -EmergencyExitTest:test_EmergencyExitWithLock() (gas: 391886) -EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 377272) -EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39408) -IntegrationTest:testStakeFoo() (gas: 1178931) -LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 2930114) +EmergencyExitTest:test_CannotEnableEmergencyModeTwice() (gas: 92690) +EmergencyExitTest:test_CannotLeaveBeforeEmergencyMode() (gas: 297717) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 384455) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 659210) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 392374) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 391784) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 377316) +EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39430) +IntegrationTest:testStakeFoo() (gas: 1178499) +LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 2960876) LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 294826) -LeaveTest:test_TrustNewStakeManager() (gas: 3007555) -LockTest:test_LockFailsWithInvalidPeriod() (gas: 309889) -LockTest:test_LockFailsWithNoStake() (gas: 63598) -LockTest:test_LockWithoutPriorLock() (gas: 390882) +LeaveTest:test_TrustNewStakeManager() (gas: 3036018) +LockTest:test_LockFailsWithInvalidPeriod() (gas: 309911) +LockTest:test_LockFailsWithNoStake() (gas: 63708) +LockTest:test_LockWithoutPriorLock() (gas: 385937) MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1745333) -MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 716820) +MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 716776) 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: 670855) -RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160214) -RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39323) -RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39346) -RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39359) -RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 611786) +RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 670682) +RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160280) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39345) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39368) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39381) +RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 610702) RewardsStreamerTest:testStake() (gas: 869181) -StakeTest:test_StakeMultipleAccounts() (gas: 494398) -StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500336) -StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 830907) -StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517393) -StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539321) -StakeTest:test_StakeOneAccount() (gas: 276911) -StakeTest:test_StakeOneAccountAndRewards() (gas: 282880) -StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499810) -StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 496147) -StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 301766) -StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 301755) -StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 301822) +StakeTest:test_StakeMultipleAccounts() (gas: 494442) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500380) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 830635) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 507518) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 529355) +StakeTest:test_StakeOneAccount() (gas: 276933) +StakeTest:test_StakeOneAccountAndRewards() (gas: 282902) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499586) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 494699) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 296811) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 296745) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 296812) StakingTokenTest:testStakeToken() (gas: 10422) -UnstakeTest:test_StakeMultipleAccounts() (gas: 494420) -UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500336) -UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 830884) -UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 517415) -UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 539343) -UnstakeTest:test_StakeOneAccount() (gas: 276934) -UnstakeTest:test_StakeOneAccountAndRewards() (gas: 282902) -UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499832) -UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 496127) -UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 301766) -UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 301755) -UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 301866) -UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 542840) -UnstakeTest:test_UnstakeMultipleAccounts() (gas: 693159) -UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 786966) -UnstakeTest:test_UnstakeOneAccount() (gas: 473331) -UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 495001) -UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 404402) -UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 531506) -UpgradeTest:test_RevertWhenNotOwner() (gas: 2571219) -UpgradeTest:test_UpgradeStakeManager() (gas: 2844658) -VaultRegistrationTest:test_VaultRegistration() (gas: 62211) +UnstakeTest:test_StakeMultipleAccounts() (gas: 494464) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500380) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 830612) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 507540) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 529377) +UnstakeTest:test_StakeOneAccount() (gas: 276956) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 282924) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 499608) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 494679) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 296811) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 296745) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 296856) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 536198) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 692599) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 786234) +UnstakeTest:test_UnstakeOneAccount() (gas: 472857) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 494565) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 404122) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 516307) +UpgradeTest:test_RevertWhenNotOwner() (gas: 2602182) +UpgradeTest:test_UpgradeStakeManager() (gas: 2875621) +VaultRegistrationTest:test_VaultRegistration() (gas: 62035) WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 310550) XPNFTTokenTest:testApproveNotAllowed() (gas: 10500) XPNFTTokenTest:testGetApproved() (gas: 10523) diff --git a/certora/specs/EmergencyMode.spec b/certora/specs/EmergencyMode.spec index db1ece5..a793366 100644 --- a/certora/specs/EmergencyMode.spec +++ b/certora/specs/EmergencyMode.spec @@ -6,6 +6,7 @@ methods { } 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 || diff --git a/certora/specs/RewardsStreamerMP.spec b/certora/specs/RewardsStreamerMP.spec index 897e5cb..df6fc3f 100644 --- a/certora/specs/RewardsStreamerMP.spec +++ b/certora/specs/RewardsStreamerMP.spec @@ -13,6 +13,12 @@ methods { function updateAccountMP(address accountAddress) external; function emergencyModeEnabled() external returns (bool) envfree; function leave() external; + function Math.mulDiv(uint256 a, uint256 b, uint256 c) internal returns uint256 => mulDivSummary(a,b,c); +} + +function mulDivSummary(uint256 a, uint256 b, uint256 c) returns uint256 { + require c != 0; + return require_uint256(a*b/c); } ghost mathint sumOfBalances { diff --git a/src/RewardsStreamerMP.sol b/src/RewardsStreamerMP.sol index c059c1e..de2294f 100644 --- a/src/RewardsStreamerMP.sol +++ b/src/RewardsStreamerMP.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -33,10 +34,11 @@ contract RewardsStreamerMP is IERC20 public STAKING_TOKEN; uint256 public constant SCALE_FACTOR = 1e18; - uint256 public constant MP_RATE_PER_YEAR = 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 * 365 days; + uint256 public constant MAX_LOCKUP_PERIOD = 4 * YEAR; uint256 public constant MAX_MULTIPLIER = 4; uint256 public totalStaked; @@ -289,8 +291,9 @@ contract RewardsStreamerMP is uint256 previousStakedBalance = account.stakedBalance; - uint256 mpToReduce = (account.accountMP * amount * SCALE_FACTOR) / (previousStakedBalance * SCALE_FACTOR); - uint256 maxMPToReduce = (account.maxMP * amount * SCALE_FACTOR) / (previousStakedBalance * SCALE_FACTOR); + // solhint-disable-next-line + uint256 mpToReduce = Math.mulDiv(account.accountMP, amount, previousStakedBalance); + uint256 maxMPToReduce = Math.mulDiv(account.maxMP, amount, previousStakedBalance); account.stakedBalance -= amount; account.accountMP -= mpToReduce; @@ -340,7 +343,7 @@ contract RewardsStreamerMP is return; } - uint256 accruedMP = (timeDiff * totalStaked * MP_RATE_PER_YEAR) / (365 days * SCALE_FACTOR); + uint256 accruedMP = (timeDiff * totalStaked * MP_RATE_PER_YEAR) / YEAR; if (totalMP + accruedMP > totalMaxMP) { accruedMP = totalMaxMP - totalMP; } @@ -423,13 +426,15 @@ contract RewardsStreamerMP is } totalRewardsAccrued += newRewards; - rewardIndex += (newRewards * SCALE_FACTOR) / totalWeight; - lastRewardTime = block.timestamp < rewardEndTime ? block.timestamp : rewardEndTime; + uint256 indexIncrease = Math.mulDiv(newRewards, SCALE_FACTOR, totalWeight); + if (indexIncrease > 0) { + rewardIndex += indexIncrease; + lastRewardTime = block.timestamp < rewardEndTime ? block.timestamp : rewardEndTime; + } } function _calculateBonusMP(uint256 amount, uint256 lockPeriod) internal pure returns (uint256) { - uint256 lockMultiplier = (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR) / MAX_LOCKUP_PERIOD; - return amount * lockMultiplier / SCALE_FACTOR; + return Math.mulDiv(amount, lockPeriod, YEAR); } function _getAccountAccruedMP(Account storage account) internal view returns (uint256) { @@ -442,7 +447,7 @@ contract RewardsStreamerMP is return 0; } - uint256 accruedMP = (timeDiff * account.stakedBalance * MP_RATE_PER_YEAR) / (365 days * SCALE_FACTOR); + uint256 accruedMP = Math.mulDiv(timeDiff * account.stakedBalance, MP_RATE_PER_YEAR, YEAR); if (account.accountMP + accruedMP > account.maxMP) { accruedMP = account.maxMP - account.accountMP; @@ -468,7 +473,7 @@ contract RewardsStreamerMP is uint256 accountWeight = account.stakedBalance + account.accountMP; uint256 deltaRewardIndex = rewardIndex - account.accountRewardIndex; - return (accountWeight * deltaRewardIndex) / SCALE_FACTOR; + return Math.mulDiv(accountWeight, deltaRewardIndex, SCALE_FACTOR); } function enableEmergencyMode() external onlyOwner { diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index e6869b6..9270aa6 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; @@ -142,20 +143,18 @@ contract RewardsStreamerMPTest is Test { } function _calculateBonusMP(uint256 amount, uint256 lockupTime) public view returns (uint256) { - return amount - * (lockupTime * streamer.MAX_MULTIPLIER() * streamer.SCALE_FACTOR() / streamer.MAX_LOCKUP_PERIOD()) - / streamer.SCALE_FACTOR(); + // solhint-disable-next-line + return Math.mulDiv(amount, lockupTime, 365 days); } function _calculeAccuredMP(uint256 totalStaked, uint256 timeDiff) public view returns (uint256) { - return (timeDiff * totalStaked * streamer.MP_RATE_PER_YEAR()) / (365 days * streamer.SCALE_FACTOR()); + return Math.mulDiv(timeDiff * totalStaked, streamer.MP_RATE_PER_YEAR(), 365 days); } function _calculateTimeToMPLimit(uint256 amount) public view returns (uint256) { uint256 maxMP = amount * streamer.MAX_MULTIPLIER(); - uint256 mpPerYear = (amount * streamer.MP_RATE_PER_YEAR()) / streamer.SCALE_FACTOR(); - uint256 timeInSeconds = (maxMP * 365 days) / mpPerYear; - return timeInSeconds; + uint256 mpPerYear = amount * streamer.MP_RATE_PER_YEAR(); + return maxMP * 365 days / mpPerYear; } } @@ -597,7 +596,7 @@ contract StakeTest is RewardsStreamerMPTest { totalStaked: stakeAmount, // 10e18 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKUP_PERIOD) / SCALE_FACTOR) totalMP: stakeAmount + expectedBonusMP, - totalMaxMP: 52_465_753_424_657_534_240, + totalMaxMP: 52_465_753_424_657_534_246, stakingBalance: stakeAmount, rewardBalance: 0, rewardIndex: 0 @@ -637,7 +636,7 @@ contract StakeTest is RewardsStreamerMPTest { totalStaked: stakeAmount, // 10 + (amount * (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR / MAX_LOCKUP_PERIOD) / SCALE_FACTOR) totalMP: stakeAmount + expectedBonusMP, - totalMaxMP: 52_821_917_808_219_178_080, + totalMaxMP: 52_821_917_808_219_178_082, stakingBalance: stakeAmount, rewardBalance: 0, rewardIndex: 0 @@ -927,7 +926,7 @@ contract StakeTest is RewardsStreamerMPTest { CheckStreamerParams({ totalStaked: sumOfStakeAmount, totalMP: sumOfStakeAmount + sumOfExpectedBonusMP, - totalMaxMP: 202_465_753_424_657_534_240, + totalMaxMP: 202_465_753_424_657_534_246, stakingBalance: sumOfStakeAmount, rewardBalance: 0, rewardIndex: 0 @@ -957,7 +956,7 @@ contract StakeTest is RewardsStreamerMPTest { CheckStreamerParams({ totalStaked: sumOfStakeAmount, totalMP: sumOfStakeAmount + sumOfExpectedBonusMP, - totalMaxMP: 250_356_164_383_561_643_820, + totalMaxMP: 250_356_164_383_561_643_835, stakingBalance: sumOfStakeAmount, rewardBalance: 0, rewardIndex: 0 @@ -1218,7 +1217,7 @@ contract UnstakeTest is StakeTest { totalStaked: stakeAmount, totalMP: (stakeAmount + expectedBonusMP) + stakeAmount, // we do `+ stakeAmount` we've accrued // `stakeAmount` after 1 year - totalMaxMP: 52_465_753_424_657_534_240, + totalMaxMP: 52_465_753_424_657_534_246, stakingBalance: 10e18, rewardBalance: 0, rewardIndex: 0 @@ -1233,7 +1232,7 @@ contract UnstakeTest is StakeTest { CheckStreamerParams({ totalStaked: 5e18, totalMP: (5e18 + expectedBonusMP) + 5e18, - totalMaxMP: 26_232_876_712_328_767_120, + totalMaxMP: 26_232_876_712_328_767_123, stakingBalance: 5e18, rewardBalance: 0, rewardIndex: 0 @@ -1292,7 +1291,7 @@ contract UnstakeTest is StakeTest { timestamp[stage] = block.timestamp; totalStaked[stage] = amountStaked; predictedBonusMP[stage] = totalStaked[stage] + _calculateBonusMP(totalStaked[stage], secondsLocked); - predictedTotalMaxMP[stage] = 52_465_753_424_657_534_240; + predictedTotalMaxMP[stage] = 52_465_753_424_657_534_246; 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];