diff --git a/.gas-report b/.gas-report index 0c435e1..313ba48 100644 --- a/.gas-report +++ b/.gas-report @@ -4,13 +4,13 @@ +=======================================================================================================================================+ | Deployment Cost | Deployment Size | | | | | |-----------------------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| 6539451 | 31274 | | | | | +| 7103409 | 33906 | | | | | |-----------------------------------------------------------------------------+-----------------+---------+---------+---------+---------| | | | | | | | |-----------------------------------------------------------------------------+-----------------+---------+---------+---------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------------------------------------------+-----------------+---------+---------+---------+---------| -| run | 5618268 | 5618268 | 5618268 | 5618268 | 64 | +| run | 6202546 | 6202546 | 6202546 | 6202546 | 67 | ╰-----------------------------------------------------------------------------+-----------------+---------+---------+---------+---------╯ ╭---------------------------------------------------------+-----------------+-----+--------+-----+---------╮ @@ -24,7 +24,7 @@ |---------------------------------------------------------+-----------------+-----+--------+-----+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------------------+-----------------+-----+--------+-----+---------| -| activeNetworkConfig | 454 | 454 | 454 | 454 | 128 | +| activeNetworkConfig | 454 | 454 | 454 | 454 | 134 | ╰---------------------------------------------------------+-----------------+-----+--------+-----+---------╯ ╭-------------------------------------------------------------------------------+-----------------+---------+---------+---------+---------╮ @@ -58,8 +58,6 @@ |------------------------------------------------------+-----------------+--------+--------+--------+---------| | 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 | @@ -74,19 +72,19 @@ |------------------------------------------------------+-----------------+--------+--------+--------+---------| | getVault | 1621 | 1621 | 1621 | 1621 | 72 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| -| initialize | 115654 | 115654 | 115654 | 115654 | 66 | +| initialize | 115654 | 115654 | 115654 | 115654 | 67 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| | lastRewardTime | 428 | 1428 | 1428 | 2428 | 2 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| | leave | 79955 | 79955 | 79955 | 79955 | 1 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| -| lock | 14282 | 42729 | 42692 | 78446 | 259 | +| lock | 14282 | 42727 | 42692 | 78446 | 259 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| | mpBalanceOfAccount | 10308 | 10308 | 10308 | 10308 | 1 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| | proxiableUUID | 387 | 387 | 387 | 387 | 3 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| -| registerVault | 55866 | 72769 | 72966 | 72966 | 261 | +| registerVault | 2562 | 70883 | 71335 | 71335 | 266 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| | rewardEndTime | 362 | 1362 | 1362 | 2362 | 2 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| @@ -96,9 +94,9 @@ |------------------------------------------------------+-----------------+--------+--------+--------+---------| | setReward | 2606 | 58415 | 86507 | 105754 | 7 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| -| setTrustedCodehash | 24199 | 24259 | 24199 | 26199 | 66 | +| setTrustedCodehash | 24199 | 24199 | 24199 | 24199 | 67 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| -| stake | 136333 | 179336 | 180726 | 201200 | 322 | +| stake | 2637 | 178788 | 180726 | 201200 | 323 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| | totalMPAccrued | 384 | 384 | 384 | 384 | 81 | |------------------------------------------------------+-----------------+--------+--------+--------+---------| @@ -124,33 +122,35 @@ +===============================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| -| 1420392 | 6695 | | | | | +| 1557273 | 7323 | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| | | | | | | | |----------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------+-----------------+--------+--------+--------+---------| -| STAKING_TOKEN | 216 | 216 | 216 | 216 | 1 | +| STAKING_TOKEN | 239 | 239 | 239 | 239 | 1 | +|----------------------------------------+-----------------+--------+--------+--------+---------| +| emergencyExit | 14974 | 31592 | 31512 | 48612 | 7 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| emergencyExit | 36353 | 48857 | 48091 | 65191 | 7 | +| initialize | 98005 | 98005 | 98005 | 98005 | 266 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| leave | 33507 | 136181 | 70120 | 370978 | 4 | +| leave | 12145 | 122559 | 62090 | 353914 | 4 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| lock | 33245 | 79218 | 79302 | 112243 | 260 | +| lock | 12075 | 57976 | 58119 | 93872 | 260 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| owner | 2339 | 2339 | 2339 | 2339 | 261 | +| owner | 377 | 377 | 377 | 377 | 265 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| register | 87015 | 103918 | 104115 | 104115 | 261 | +| register | 12654 | 74497 | 74924 | 74924 | 266 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| stake | 33411 | 253159 | 255230 | 275752 | 323 | +| stake | 12077 | 233957 | 236707 | 257181 | 324 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| stakeManager | 368 | 368 | 368 | 368 | 261 | +| stakeManager | 367 | 367 | 367 | 367 | 265 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 | +| trustStakeManager | 7577 | 7577 | 7577 | 7577 | 1 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| unstake | 33282 | 99858 | 105888 | 113384 | 14 | +| unstake | 12054 | 89644 | 94967 | 99164 | 14 | |----------------------------------------+-----------------+--------+--------+--------+---------| -| withdraw | 42278 | 42278 | 42278 | 42278 | 1 | +| withdraw | 20705 | 20705 | 20705 | 20705 | 1 | ╰----------------------------------------+-----------------+--------+--------+--------+---------╯ ╭----------------------------------------------------+-----------------+------+--------+--------+---------╮ @@ -158,17 +158,31 @@ +=========================================================================================================+ | Deployment Cost | Deployment Size | | | | | |----------------------------------------------------+-----------------+------+--------+--------+---------| -| 256510 | 1231 | | | | | +| 0 | 1231 | | | | | |----------------------------------------------------+-----------------+------+--------+--------+---------| | | | | | | | |----------------------------------------------------+-----------------+------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |----------------------------------------------------+-----------------+------+--------+--------+---------| -| fallback | 689 | 7187 | 2075 | 132112 | 790 | +| fallback | 689 | 7797 | 854 | 132112 | 461 | |----------------------------------------------------+-----------------+------+--------+--------+---------| -| implementation | 343 | 1636 | 2343 | 2343 | 929 | +| implementation | 343 | 2340 | 2343 | 2343 | 869 | ╰----------------------------------------------------+-----------------+------+--------+--------+---------╯ +╭--------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/VaultFactory.sol:VaultFactory Contract | | | | | | ++===================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|--------------------------------------------+-----------------+--------+--------+--------+---------| +| 0 | 1991 | | | | | +|--------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|--------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|--------------------------------------------+-----------------+--------+--------+--------+---------| +| createVault | 227659 | 244565 | 244759 | 244759 | 265 | +╰--------------------------------------------+-----------------+--------+--------+--------+---------╯ + ╭-------------------------------------------------+-----------------+-------+--------+-------+---------╮ | src/XPNFTToken.sol:XPNFTToken Contract | | | | | | +======================================================================================================+ @@ -300,17 +314,17 @@ +==================================================================================================+ | Deployment Cost | Deployment Size | | | | | |---------------------------------------------+-----------------+-------+--------+-------+---------| -| 625454 | 3260 | | | | | +| 625370 | 3260 | | | | | |---------------------------------------------+-----------------+-------+--------+-------+---------| | | | | | | | |---------------------------------------------+-----------------+-------+--------+-------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |---------------------------------------------+-----------------+-------+--------+-------+---------| -| approve | 46330 | 46339 | 46342 | 46342 | 261 | +| approve | 46342 | 46342 | 46342 | 46342 | 265 | |---------------------------------------------+-----------------+-------+--------+-------+---------| | balanceOf | 558 | 926 | 558 | 2558 | 103 | |---------------------------------------------+-----------------+-------+--------+-------+---------| -| mint | 51279 | 56395 | 51279 | 68379 | 274 | +| mint | 51279 | 56383 | 51279 | 68379 | 278 | ╰---------------------------------------------+-----------------+-------+--------+-------+---------╯ ╭-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╮ @@ -324,7 +338,7 @@ |-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------| | Function Name | Min | Avg | Median | Max | # Calls | |-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------| -| leave | 391 | 161316 | 161316 | 322322 | 334 | +| leave | 845 | 161317 | 161317 | 321839 | 333 | |-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------| | proxiableUUID | 330 | 330 | 330 | 330 | 1 | ╰-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╯ diff --git a/.gas-snapshot b/.gas-snapshot index bf9a0b2..54c7cd4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,75 +1,76 @@ 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_CannotLeaveBeforeEmergencyMode() (gas: 306012) +EmergencyExitTest:test_EmergencyExitBasic() (gas: 392806) +EmergencyExitTest:test_EmergencyExitMultipleUsers() (gas: 678359) +EmergencyExitTest:test_EmergencyExitToAlternateAddress() (gas: 400605) +EmergencyExitTest:test_EmergencyExitWithLock() (gas: 400174) +EmergencyExitTest:test_EmergencyExitWithRewards() (gas: 385707) 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: 1000, μ: 344783, ~: 344801) -LockTest:test_LockFailsWithNoStake() (gas: 102637) -LockTest:test_LockFailsWithZero() (gas: 315022) -LockTest:test_LockWithoutPriorLock() (gas: 393335) -MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1752531) +IntegrationTest:testStakeFoo() (gas: 1232258) +LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 6218572) +LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 303138) +LeaveTest:test_TrustNewStakeManager() (gas: 6286362) +LockTest:test_LockFailsWithInvalidPeriod(uint256) (runs: 1000, μ: 350272, ~: 350297) +LockTest:test_LockFailsWithNoStake() (gas: 105377) +LockTest:test_LockFailsWithZero() (gas: 320506) +LockTest:test_LockWithoutPriorLock() (gas: 398817) +MaliciousUpgradeTest:test_UpgradeStackOverflowStakeManager() (gas: 1757478) MathTest:test_CalcAbsoluteMaxTotalMP() (gas: 4996) -MathTest:test_CalcAccrueMP() (gas: 7990) -MathTest:test_CalcBonusMP() (gas: 18676) -MathTest:test_CalcInitialMP() (gas: 5352) +MathTest:test_CalcAccrueMP() (gas: 8013) +MathTest:test_CalcBonusMP() (gas: 18644) +MathTest:test_CalcInitialMP() (gas: 5375) MathTest:test_CalcMaxAccruedMP() (gas: 4642) -MathTest:test_CalcMaxTotalMP() (gas: 19449) -MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 731369) +MathTest:test_CalcMaxTotalMP() (gas: 19411) +MultipleVaultsStakeTest:test_StakeMultipleVaults() (gas: 739601) 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: 490632) +RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 493376) 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: 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) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39407) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39385) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39420) +RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 623466) +StakeTest:test_StakeMultipleAccounts() (gas: 508049) +StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 514084) +StakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 852890) +StakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 523193) +StakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 545137) +StakeTest:test_StakeOneAccount() (gas: 282585) +StakeTest:test_StakeOneAccountAndRewards() (gas: 288640) +StakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 513211) +StakeTest:test_StakeOneAccountReachingMPLimit() (gas: 502753) +StakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 302900) +StakeTest:test_StakeOneAccountWithMinLockUp() (gas: 303440) +StakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 303507) +StakingTokenTest:testStakeToken() (gas: 13140) +TrustedCodehashAccessTest:test_RevertWhenProxyCloneCodehashNotTrusted() (gas: 1896237) +UnstakeTest:test_StakeMultipleAccounts() (gas: 508026) +UnstakeTest:test_StakeMultipleAccountsAndRewards() (gas: 514106) +UnstakeTest:test_StakeMultipleAccountsMPIncreasesMaxMPDoesNotChange() (gas: 852867) +UnstakeTest:test_StakeMultipleAccountsWithMinLockUp() (gas: 523170) +UnstakeTest:test_StakeMultipleAccountsWithRandomLockUp() (gas: 545159) +UnstakeTest:test_StakeOneAccount() (gas: 282585) +UnstakeTest:test_StakeOneAccountAndRewards() (gas: 288662) +UnstakeTest:test_StakeOneAccountMPIncreasesMaxMPDoesNotChange() (gas: 513233) +UnstakeTest:test_StakeOneAccountReachingMPLimit() (gas: 502797) +UnstakeTest:test_StakeOneAccountWithMaxLockUp() (gas: 302900) +UnstakeTest:test_StakeOneAccountWithMinLockUp() (gas: 303462) +UnstakeTest:test_StakeOneAccountWithRandomLockUp() (gas: 303551) +UnstakeTest:test_UnstakeBonusMPAndAccuredMP() (gas: 551979) +UnstakeTest:test_UnstakeMultipleAccounts() (gas: 718583) +UnstakeTest:test_UnstakeMultipleAccountsAndRewards() (gas: 816209) +UnstakeTest:test_UnstakeOneAccount() (gas: 488546) +UnstakeTest:test_UnstakeOneAccountAndAccruedMP() (gas: 510466) +UnstakeTest:test_UnstakeOneAccountAndRewards() (gas: 416131) +UnstakeTest:test_UnstakeOneAccountWithLockUpAndAccruedMP() (gas: 535543) +UpgradeTest:test_RevertWhenNotOwner() (gas: 2897728) +UpgradeTest:test_UpgradeStakeManager() (gas: 6117494) VaultRegistrationTest:test_VaultRegistration() (gas: 62154) -WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 313397) +WithdrawTest:test_CannotWithdrawStakedFunds() (gas: 318812) XPNFTTokenTest:testApproveNotAllowed() (gas: 10500) XPNFTTokenTest:testGetApproved() (gas: 10523) XPNFTTokenTest:testIsApprovedForAll() (gas: 10698) diff --git a/script/DeployRewardsStreamerMP.s.sol b/script/DeployRewardsStreamerMP.s.sol index 62caa91..bd3d979 100644 --- a/script/DeployRewardsStreamerMP.s.sol +++ b/script/DeployRewardsStreamerMP.s.sol @@ -1,15 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; + import { BaseScript } from "./Base.s.sol"; import { DeploymentConfig } from "./DeploymentConfig.s.sol"; + import { TransparentProxy } from "../src/TransparentProxy.sol"; -import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; +import { VaultFactory } from "../src/VaultFactory.sol"; contract DeployRewardsStreamerMPScript is BaseScript { - function run() public returns (RewardsStreamerMP, DeploymentConfig) { + function run() public returns (RewardsStreamerMP, VaultFactory, DeploymentConfig) { DeploymentConfig deploymentConfig = new DeploymentConfig(broadcaster); (address deployer, address stakingToken) = deploymentConfig.activeNetworkConfig(); @@ -21,16 +25,19 @@ contract DeployRewardsStreamerMPScript is BaseScript { address impl = address(new RewardsStreamerMP()); // Create upgradeable proxy address proxy = address(new TransparentProxy(impl, initializeData)); - vm.stopBroadcast(); - RewardsStreamerMP stakeManager = RewardsStreamerMP(proxy); - StakeVault tempVault = new StakeVault(address(this), IStakeManagerProxy(proxy)); - bytes32 vaultCodeHash = address(tempVault).codehash; + // Create vault implementation for proxy clones + address vaultImplementation = address(new StakeVault(IERC20(stakingToken))); + address proxyClone = Clones.clone(vaultImplementation); + + // Whitelist vault implementation codehash + RewardsStreamerMP(proxy).setTrustedCodehash(proxyClone.codehash, true); + + // Create vault factory + VaultFactory vaultFactory = new VaultFactory(deployer, proxy, vaultImplementation); - vm.startBroadcast(deployer); - stakeManager.setTrustedCodehash(vaultCodeHash, true); vm.stopBroadcast(); - return (stakeManager, deploymentConfig); + return (RewardsStreamerMP(proxy), vaultFactory, deploymentConfig); } } diff --git a/src/StakeVault.sol b/src/StakeVault.sol index 3d09caa..e4a3216 100644 --- a/src/StakeVault.sol +++ b/src/StakeVault.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.26; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IStakeManagerProxy } from "./interfaces/IStakeManagerProxy.sol"; import { IStakeVault } from "./interfaces/IStakeVault.sol"; @@ -12,8 +13,10 @@ import { IStakeVault } from "./interfaces/IStakeVault.sol"; * @author Ricardo Guilherme Schmidt * @notice A contract to secure user stakes and manage staking with IStakeManager. * @dev This contract is owned by the user and allows staking, unstaking, and withdrawing tokens. + * @dev The only reason this is `OwnableUpgradeable` is because we use proxy clones + * to create stake vault instances. Hence, we need to use `Initializeable` to set the owner. */ -contract StakeVault is IStakeVault, Ownable { +contract StakeVault is IStakeVault, Initializable, OwnableUpgradeable { error StakeVault__NotEnoughAvailableBalance(); error StakeVault__InvalidDestinationAddress(); error StakeVault__UpdateNotAvailable(); @@ -55,13 +58,20 @@ contract StakeVault is IStakeVault, Ownable { /** * @notice Initializes the contract with the owner, staked token, and stake manager. + */ + constructor(IERC20 token) { + STAKING_TOKEN = token; + _disableInitializers(); + } + + /** * @param _owner The address of the owner. * @param _stakeManager The address of the StakeManager contract. */ - constructor(address _owner, IStakeManagerProxy _stakeManager) Ownable(_owner) { - STAKING_TOKEN = _stakeManager.STAKING_TOKEN(); - stakeManager = _stakeManager; - stakeManagerImplementationAddress = _stakeManager.implementation(); + function initialize(address _owner, address _stakeManager) public initializer { + __Ownable_init(_owner); + stakeManager = IStakeManagerProxy(_stakeManager); + stakeManagerImplementationAddress = stakeManager.implementation(); } /** @@ -82,7 +92,7 @@ contract StakeVault is IStakeVault, Ownable { /** * @notice Returns the address of the current owner. */ - function owner() public view override(Ownable, IStakeVault) returns (address) { + function owner() public view override(OwnableUpgradeable, IStakeVault) returns (address) { return super.owner(); } diff --git a/src/VaultFactory.sol b/src/VaultFactory.sol new file mode 100644 index 0000000..318aa5c --- /dev/null +++ b/src/VaultFactory.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.26; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +// import { TransparentClones } from "./TransparentClones.sol"; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; +import { StakeVault } from "./StakeVault.sol"; + +/** + * @title VaultFactory + * @author 0x-r4bbit + * + * This contract is reponsible for creating staking vaults for users. + * A user of the staking protocol is able to create multiple vaults to facilitate + * different strategies. For example, a user may want to create a vault for + * a long-term lock period, while also creating a vault that has no lock period + * at all. + * + * @notice This contract is used by users to create staking vaults. + * @dev This contract will be deployed by Status, making Status the owner of the contract. + * @dev A contract address for a `StakeManager` has to be provided to create this contract. + * @dev Reverts with {VaultFactory__InvalidStakeManagerAddress} if the provided + * `StakeManager` address is zero. + * @dev The `StakeManager` contract address can be changed by the owner. + */ +contract VaultFactory is Ownable { + error VaultFactory__InvalidStakeManagerAddress(); + + event VaultCreated(address indexed vault, address indexed owner); + event StakeManagerAddressChanged(address indexed newStakeManagerAddress); + event VaultImplementationChanged(address indexed newVaultImplementation); + + /// @dev Address of the `StakeManager` contract instance. + address public stakeManager; + /// @dev Address of the `StakeVault` implementation contract. + address public vaultImplementation; + + /// @param _stakeManager Address of the `StakeManager` contract instance. + constructor(address _owner, address _stakeManager, address _vaultImplementation) Ownable(_owner) { + if (_stakeManager == address(0)) { + revert VaultFactory__InvalidStakeManagerAddress(); + } + stakeManager = _stakeManager; + vaultImplementation = _vaultImplementation; + } + + /// @notice Sets the `StakeManager` contract address. + /// @dev Only the owner can call this function. + /// @dev Emits a {StakeManagerAddressChanged} event. + /// @param _stakeManager Address of the `StakeManager` contract instance. + function setStakeManager(address _stakeManager) external onlyOwner { + stakeManager = _stakeManager; + emit StakeManagerAddressChanged(_stakeManager); + } + + /// @notice Sets the `StakeVault` implementation contract address. + /// @dev Only the owner can call this function. + /// @dev Emits a {VaultImplementationChanged} event. + /// @param _vaultImplementation Address of the `StakeVault` implementation contract. + /// @dev This function is used to change the implementation of the `StakeVault` contract. + function setVaultImplementation(address _vaultImplementation) external onlyOwner { + vaultImplementation = _vaultImplementation; + emit VaultImplementationChanged(_vaultImplementation); + } + + /// @notice Creates an instance of a `StakeVault` contract. + /// @dev Anyone can call this function. + /// @dev Emits a {VaultCreated} event. + function createVault() external returns (StakeVault clone) { + clone = StakeVault(Clones.clone(vaultImplementation)); + clone.initialize(msg.sender, stakeManager); + clone.register(); + emit VaultCreated(address(clone), msg.sender); + } +} diff --git a/src/interfaces/IStakeConstants.sol b/src/interfaces/IStakeConstants.sol index 7c46c1f..3ac8507 100644 --- a/src/interfaces/IStakeConstants.sol +++ b/src/interfaces/IStakeConstants.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; -import { ITrustedCodehashAccess } from "./ITrustedCodehashAccess.sol"; - /** * @title IStakeConstants * @author Ricardo Guilherme Schmidt diff --git a/src/interfaces/IStakeManagerProxy.sol b/src/interfaces/IStakeManagerProxy.sol index 03af2c4..6fcc168 100644 --- a/src/interfaces/IStakeManagerProxy.sol +++ b/src/interfaces/IStakeManagerProxy.sol @@ -4,4 +4,5 @@ pragma solidity ^0.8.26; import { IStakeManager } from "./IStakeManager.sol"; import { ITransparentProxy } from "./ITransparentProxy.sol"; +// solhint-disable-next-line interface IStakeManagerProxy is IStakeManager, ITransparentProxy { } diff --git a/src/math/StakeMath.sol b/src/math/StakeMath.sol index 1babdf8..1fd151f 100644 --- a/src/math/StakeMath.sol +++ b/src/math/StakeMath.sol @@ -51,6 +51,7 @@ abstract contract StakeMath is MultiplierPointMath { { uint256 newBalance = _balance + _increasedAmount; _newLockEnd = Math.max(_currentLockEndTime, _processTime) + _increasedLockSeconds; + // solhint-disable-next-line uint256 dt_lock = _newLockEnd - _processTime; if (dt_lock != 0 && (dt_lock < MIN_LOCKUP_PERIOD || dt_lock > MAX_LOCKUP_PERIOD)) { revert StakeMath__InvalidLockingPeriod(); @@ -99,6 +100,7 @@ abstract contract StakeMath is MultiplierPointMath { } _newLockEnd = Math.max(_currentLockEndTime, _processTime) + _increasedLockSeconds; + // solhint-disable-next-line uint256 dt_lock = _newLockEnd - _processTime; if (dt_lock != 0 && (dt_lock < MIN_LOCKUP_PERIOD || dt_lock > MAX_LOCKUP_PERIOD)) { revert StakeMath__InvalidLockingPeriod(); diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index b0d51b4..3642bda 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -7,34 +7,41 @@ import { DeployRewardsStreamerMPScript } from "../script/DeployRewardsStreamerMP import { UpgradeRewardsStreamerMPScript } from "../script/UpgradeRewardsStreamerMP.s.sol"; import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; +import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; +import { ITrustedCodehashAccess } from "../src/interfaces/ITrustedCodehashAccess.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 { VaultFactory } from "../src/VaultFactory.sol"; import { MockToken } from "./mocks/MockToken.sol"; import { StackOverflowStakeManager } from "./mocks/StackOverflowStakeManager.sol"; contract RewardsStreamerMPTest is StakeMath, Test { - MockToken stakingToken; + MockToken internal stakingToken; RewardsStreamerMP public streamer; + VaultFactory public vaultFactory; - address admin; - address alice = makeAddr("alice"); - address bob = makeAddr("bob"); - address charlie = makeAddr("charlie"); - address dave = makeAddr("dave"); + address internal admin; + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + address internal charlie = makeAddr("charlie"); + address internal dave = makeAddr("dave"); mapping(address owner => address vault) public vaults; function setUp() public virtual { DeployRewardsStreamerMPScript deployment = new DeployRewardsStreamerMPScript(); - (RewardsStreamerMP stakeManager, DeploymentConfig deploymentConfig) = deployment.run(); + (RewardsStreamerMP stakeManager, VaultFactory _vaultFactory, DeploymentConfig deploymentConfig) = + deployment.run(); (address _deployer, address _stakingToken) = deploymentConfig.activeNetworkConfig(); streamer = stakeManager; stakingToken = MockToken(_stakingToken); + vaultFactory = _vaultFactory; admin = _deployer; address[4] memory accounts = [alice, bob, charlie, dave]; @@ -105,8 +112,7 @@ contract RewardsStreamerMPTest is StakeMath, Test { function _createTestVault(address owner) internal returns (StakeVault vault) { vm.prank(owner); - vault = new StakeVault(owner, IStakeManagerProxy(address(streamer))); - vault.register(); + vault = vaultFactory.createVault(); } function _stake(address account, uint256 amount, uint256 lockupTime) public { @@ -203,6 +209,27 @@ contract VaultRegistrationTest is RewardsStreamerMPTest { } } +contract TrustedCodehashAccessTest is RewardsStreamerMPTest { + function setUp() public virtual override { + super.setUp(); + } + + function test_RevertWhenProxyCloneCodehashNotTrusted() public { + // create independent (possibly malicious) StakeVault + address vaultTpl = address(new StakeVault(stakingToken)); + StakeVault proxyClone = StakeVault(Clones.clone(vaultTpl)); + proxyClone.initialize(address(this), address(streamer)); + + // registering already fails as codehash is not trusted + vm.expectRevert(ITrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); + proxyClone.register(); + + // staking fails as codehash is not trusted + vm.expectRevert(ITrustedCodehashAccess.TrustedCodehashAccess__UnauthorizedCodehash.selector); + proxyClone.stake(10e10, 0); + } +} + contract IntegrationTest is RewardsStreamerMPTest { function setUp() public virtual override { super.setUp(); diff --git a/test/StakeVault.test.sol b/test/StakeVault.test.sol index f2c270c..360eeae 100644 --- a/test/StakeVault.test.sol +++ b/test/StakeVault.test.sol @@ -4,12 +4,16 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; -import { TransparentProxy } from "../src/TransparentProxy.sol"; +import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; +import { DeployRewardsStreamerMPScript } from "../script/DeployRewardsStreamerMP.s.sol"; +import { VaultFactory } from "../src/VaultFactory.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; import { MockToken } from "./mocks/MockToken.sol"; contract StakeVaultTest is Test { + VaultFactory internal vaultFactory; + RewardsStreamerMP internal streamer; StakeVault internal stakeVault; @@ -22,28 +26,23 @@ contract StakeVaultTest is Test { function _createTestVault(address owner) internal returns (StakeVault vault) { vm.prank(owner); - vault = new StakeVault(owner, IStakeManagerProxy(address(streamer))); - vault.register(); + vault = vaultFactory.createVault(); } function setUp() public virtual { rewardToken = new MockToken("Reward Token", "RT"); stakingToken = new MockToken("Staking Token", "ST"); - address impl = address(new RewardsStreamerMP()); - bytes memory initializeData = abi.encodeWithSelector( - RewardsStreamerMP.initialize.selector, address(this), address(stakingToken), address(rewardToken) - ); - address proxy = address(new TransparentProxy(impl, initializeData)); - streamer = RewardsStreamerMP(proxy); - stakingToken.mint(alice, 10_000e18); + DeployRewardsStreamerMPScript deployment = new DeployRewardsStreamerMPScript(); + (RewardsStreamerMP stakeManager, VaultFactory _vaultFactory, DeploymentConfig deploymentConfig) = + deployment.run(); + (, address _stakingToken) = deploymentConfig.activeNetworkConfig(); - // Create a temporary vault just to get the codehash - StakeVault tempVault = new StakeVault(address(this), IStakeManagerProxy(address(streamer))); - bytes32 vaultCodeHash = address(tempVault).codehash; + streamer = stakeManager; + stakingToken = MockToken(_stakingToken); + vaultFactory = _vaultFactory; - // Register the codehash before creating any user vaults - streamer.setTrustedCodehash(vaultCodeHash, true); + stakingToken.mint(alice, 10_000e18); stakeVault = _createTestVault(alice); diff --git a/test/mocks/StackOverflowStakeManager.sol b/test/mocks/StackOverflowStakeManager.sol index 70b1464..697bc72 100644 --- a/test/mocks/StackOverflowStakeManager.sol +++ b/test/mocks/StackOverflowStakeManager.sol @@ -6,7 +6,6 @@ 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, @@ -14,6 +13,7 @@ contract StackOverflowStakeManager is TrustedCodehashAccess, ReentrancyGuardUpgradeable { + // solhint-disable-next-line IERC20 public STAKING_TOKEN; uint256 public constant SCALE_FACTOR = 1e18; @@ -42,15 +42,19 @@ contract StackOverflowStakeManager is mapping(address account => Account data) public accounts; + // solhint-disable-next-line function getStakedBalance(address _vault) external view override returns (uint256 _balance) { // implementation } + // solhint-disable-next-line function lock(uint256 _seconds) external override { // implementation } + // solhint-disable-next-line function stake(uint256 _amount, uint256 _seconds) external override { // implementation } + // solhint-disable-next-line function unstake(uint256 _amount) external override { // implementation } @@ -67,6 +71,7 @@ contract StackOverflowStakeManager is return accounts[_account]; } + // solhint-disable-next-line function registerVault() external override { // implementation } diff --git a/test/mocks/XPProviderMock.sol b/test/mocks/XPProviderMock.sol index 4d3b7fc..6e4e72b 100644 --- a/test/mocks/XPProviderMock.sol +++ b/test/mocks/XPProviderMock.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.26; import { IRewardProvider } from "../../src/interfaces/IRewardProvider.sol"; contract XPProviderMock is IRewardProvider { + // solhint-disable-next-line mapping(address => uint256) public userXPShare; uint256 public totalXPShares;