feat: introduce proxy clones

This commit introduces proxy clones to make create `StakeVault`s as
cheap as possible.

Major changes here are:

- Introduce `VaultFactory` which takes care of creating clones and
  registering them with the stake manager
- Make `StakeVault` and `Initializable` so it can be used as a
  "template" contract to later have proxies point to it
- Adjust the deployment script to also deploy `VaultFactory` and ensure
  The proxy is whitelisted in the stake manager
- Make use of the new proxy clones in tests
- Add a test for `TrustedCodehashAccess` that ensures the proxy
  whitelisting works and setting up a (malicious) proxy is not going to
  work

Closes #101
This commit is contained in:
r4bbit
2025-02-03 14:02:51 +01:00
parent 177aba24d6
commit 70a7f30d2a
7 changed files with 267 additions and 133 deletions

View File

@@ -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 | 36353 | 48857 | 48091 | 65191 | 7 |
| emergencyExit | 14974 | 31592 | 31512 | 48612 | 7 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| leave | 33507 | 136181 | 70120 | 370978 | 4 |
| initialize | 98005 | 98005 | 98005 | 98005 | 266 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| lock | 33245 | 79218 | 79302 | 112243 | 260 |
| leave | 12145 | 122559 | 62090 | 353914 | 4 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| owner | 2339 | 2339 | 2339 | 2339 | 261 |
| lock | 12075 | 57976 | 58119 | 93872 | 260 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| register | 87015 | 103918 | 104115 | 104115 | 261 |
| owner | 377 | 377 | 377 | 377 | 265 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| stake | 33411 | 253159 | 255230 | 275752 | 323 |
| register | 12654 | 74497 | 74924 | 74924 | 266 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| stakeManager | 368 | 368 | 368 | 368 | 261 |
| stake | 12077 | 233957 | 236707 | 257181 | 324 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| trustStakeManager | 28953 | 28953 | 28953 | 28953 | 1 |
| stakeManager | 367 | 367 | 367 | 367 | 265 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| unstake | 33282 | 99858 | 105888 | 113384 | 14 |
| trustStakeManager | 7577 | 7577 | 7577 | 7577 | 1 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| withdraw | 42278 | 42278 | 42278 | 42278 | 1 |
| unstake | 12054 | 89644 | 94967 | 99164 | 14 |
|----------------------------------------+-----------------+--------+--------+--------+---------|
| 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 |
╰-----------------------------------------------------------------------------+-----------------+--------+--------+--------+---------╯

View File

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

View File

@@ -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));
// 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.stopBroadcast();
RewardsStreamerMP stakeManager = RewardsStreamerMP(proxy);
StakeVault tempVault = new StakeVault(address(this), IStakeManagerProxy(proxy));
bytes32 vaultCodeHash = address(tempVault).codehash;
vm.startBroadcast(deployer);
stakeManager.setTrustedCodehash(vaultCodeHash, true);
vm.stopBroadcast();
return (stakeManager, deploymentConfig);
return (RewardsStreamerMP(proxy), vaultFactory, deploymentConfig);
}
}

View File

@@ -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 <ricardo3@status.im>
* @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();
}

76
src/VaultFactory.sol Normal file
View File

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

View File

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

View File

@@ -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,29 +26,24 @@ 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);
DeployRewardsStreamerMPScript deployment = new DeployRewardsStreamerMPScript();
(RewardsStreamerMP stakeManager, VaultFactory _vaultFactory, DeploymentConfig deploymentConfig) =
deployment.run();
(, address _stakingToken) = deploymentConfig.activeNetworkConfig();
streamer = stakeManager;
stakingToken = MockToken(_stakingToken);
vaultFactory = _vaultFactory;
stakingToken.mint(alice, 10_000e18);
// Create a temporary vault just to get the codehash
StakeVault tempVault = new StakeVault(address(this), IStakeManagerProxy(address(streamer)));
bytes32 vaultCodeHash = address(tempVault).codehash;
// Register the codehash before creating any user vaults
streamer.setTrustedCodehash(vaultCodeHash, true);
stakeVault = _createTestVault(alice);
vm.prank(alice);