From a565dbbac4bef5ba2a75103913039f7037b50110 Mon Sep 17 00:00:00 2001 From: r4bbit <445106+0x-r4bbit@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:06:45 +0100 Subject: [PATCH] feat: introduce deployment script for `RewardsStreamerMP` This commit introduces a deployment script for the stake manager which can later be extended to work with other networks. The deployment script is also used inside our testsuite, ensuring it's working as intended. Closes #88 --- .gas-report | 61 +++++++++++++++++++++++----- .gas-snapshot | 14 +++---- script/Base.s.sol | 41 +++++++++++++++++++ script/DeployRewardsStreamerMP.s.sol | 33 +++++++++++++++ script/DeploymentConfig.s.sol | 42 +++++++++++++++++++ script/RewardsStreamer.s.sol | 17 -------- test/RewardsStreamerMP.t.sol | 28 ++++--------- 7 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 script/Base.s.sol create mode 100644 script/DeployRewardsStreamerMP.s.sol create mode 100644 script/DeploymentConfig.s.sol delete mode 100644 script/RewardsStreamer.s.sol diff --git a/.gas-report b/.gas-report index 5554d7f..0b1f545 100644 --- a/.gas-report +++ b/.gas-report @@ -1,3 +1,19 @@ +| script/DeployRewardsStreamerMP.s.sol:DeployRewardsStreamerMPScript contract | | | | | | +|-----------------------------------------------------------------------------|-----------------|---------|---------|---------|---------| +| Deployment Cost | Deployment Size | | | | | +| 6200681 | 29704 | | | | | +| Function Name | min | avg | median | max | # calls | +| run | 5302574 | 5302574 | 5302574 | 5302574 | 57 | + + +| script/DeploymentConfig.s.sol:DeploymentConfig contract | | | | | | +|---------------------------------------------------------|-----------------|-----|--------|-----|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| activeNetworkConfig | 454 | 454 | 454 | 454 | 114 | + + | src/RewardsStreamer.sol:RewardsStreamer contract | | | | | | |--------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -21,7 +37,7 @@ | MAX_MULTIPLIER | 251 | 251 | 251 | 251 | 11 | | MIN_LOCKUP_PERIOD | 297 | 297 | 297 | 297 | 11 | | MP_RATE_PER_YEAR | 253 | 253 | 253 | 253 | 3 | -| STAKING_TOKEN | 2428 | 2428 | 2428 | 2428 | 292 | +| STAKING_TOKEN | 428 | 2037 | 2428 | 2428 | 292 | | emergencyModeEnabled | 2420 | 2420 | 2420 | 2420 | 7 | | enableEmergencyMode | 2485 | 19392 | 24677 | 24677 | 8 | | getAccount | 1661 | 1661 | 1661 | 1661 | 72 | @@ -31,7 +47,6 @@ | 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 | 395 | 1395 | 1395 | 2395 | 2 | | leave | 56244 | 56244 | 56244 | 56244 | 1 | | lock | 12063 | 34172 | 16480 | 73975 | 3 | @@ -41,7 +56,7 @@ | rewardStartTime | 352 | 1352 | 1352 | 2352 | 2 | | rewardsBalanceOf | 1317 | 1317 | 1317 | 1317 | 4 | | setReward | 2583 | 50892 | 60278 | 102595 | 7 | -| setTrustedCodehash | 26243 | 26243 | 26243 | 26243 | 59 | +| setTrustedCodehash | 24243 | 24310 | 24243 | 26243 | 59 | | stake | 131082 | 170202 | 177899 | 198232 | 66 | | totalMP | 373 | 373 | 373 | 373 | 81 | | totalMaxMP | 350 | 350 | 350 | 350 | 81 | @@ -54,13 +69,39 @@ | upgradeToAndCall | 3225 | 9387 | 10926 | 10936 | 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/StakeManagerProxy.sol:StakeManagerProxy contract | | | | | | +|------------------------------------------------------|-----------------|-------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 256467 | 1263 | | | | | +| Function Name | min | avg | median | max | # calls | +| MAX_LOCKUP_PERIOD | 5276 | 5276 | 5276 | 5276 | 4 | +| MAX_MULTIPLIER | 678 | 3132 | 5178 | 5178 | 11 | +| MIN_LOCKUP_PERIOD | 724 | 3996 | 5224 | 5224 | 11 | +| MP_RATE_PER_YEAR | 680 | 680 | 680 | 680 | 3 | +| STAKING_TOKEN | 855 | 6086 | 7355 | 7355 | 292 | +| emergencyModeEnabled | 7347 | 7347 | 7347 | 7347 | 7 | +| enableEmergencyMode | 28480 | 45381 | 50665 | 50665 | 8 | +| getAccount | 2115 | 2115 | 2115 | 2115 | 72 | +| getStakedBalance | 7559 | 7559 | 7559 | 7559 | 1 | +| getUserTotalMP | 9660 | 9660 | 9660 | 9660 | 1 | +| getUserTotalMaxMP | 3553 | 3553 | 3553 | 3553 | 1 | +| getUserTotalStakedBalance | 15548 | 15548 | 15548 | 15548 | 1 | +| getUserVaults | 5637 | 6762 | 5637 | 10137 | 4 | +| implementation | 343 | 808 | 343 | 2343 | 382 | +| lastRewardTime | 822 | 1822 | 1822 | 2822 | 2 | +| rewardEndTime | 800 | 1800 | 1800 | 2800 | 2 | +| rewardStartTime | 779 | 4029 | 4029 | 7279 | 2 | +| rewardsBalanceOf | 1747 | 1747 | 1747 | 1747 | 4 | +| setReward | 28863 | 77206 | 86636 | 128881 | 7 | +| setTrustedCodehash | 52889 | 52889 | 52889 | 52889 | 2 | +| totalMP | 800 | 800 | 800 | 800 | 81 | +| totalMaxMP | 777 | 777 | 777 | 777 | 81 | +| totalRewardsAccrued | 800 | 800 | 800 | 800 | 3 | +| totalRewardsSupply | 1387 | 2498 | 2151 | 11627 | 30 | +| totalStaked | 823 | 823 | 823 | 823 | 82 | +| updateAccountMP | 41755 | 44833 | 44257 | 61357 | 21 | +| updateGlobalState | 37076 | 54033 | 51237 | 136272 | 21 | +| upgradeToAndCall | 29868 | 36025 | 37562 | 37572 | 5 | | src/StakeVault.sol:StakeVault contract | | | | | | diff --git a/.gas-snapshot b/.gas-snapshot index db4e45c..70f4f78 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -9,7 +9,7 @@ EmergencyExitTest:test_OnlyOwnerCanEnableEmergencyMode() (gas: 39430) IntegrationTest:testStakeFoo() (gas: 1178499) LeaveTest:test_LeaveShouldProperlyUpdateAccounting() (gas: 2960876) LeaveTest:test_RevertWhenStakeManagerIsTrusted() (gas: 294826) -LeaveTest:test_TrustNewStakeManager() (gas: 3036018) +LeaveTest:test_TrustNewStakeManager() (gas: 3038518) LockTest:test_LockFailsWithInvalidPeriod() (gas: 309911) LockTest:test_LockFailsWithNoStake() (gas: 63708) LockTest:test_LockWithoutPriorLock() (gas: 385937) @@ -21,12 +21,12 @@ NFTMetadataGeneratorSVGTest:testSetImageStringsRevert() (gas: 35804) NFTMetadataGeneratorURLTest:testGenerateMetadata() (gas: 102512) NFTMetadataGeneratorURLTest:testSetBaseURL() (gas: 49555) NFTMetadataGeneratorURLTest:testSetBaseURLRevert() (gas: 35979) -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) +RewardsStreamerMP_RewardsTest:testRewardsBalanceOf() (gas: 670670) +RewardsStreamerMP_RewardsTest:testSetRewards() (gas: 160274) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadAmount() (gas: 39339) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsBadDuration() (gas: 39362) +RewardsStreamerMP_RewardsTest:testSetRewards_RevertsNotAuthorized() (gas: 39375) +RewardsStreamerMP_RewardsTest:testTotalRewardsSupply() (gas: 610684) RewardsStreamerTest:testStake() (gas: 869181) StakeTest:test_StakeMultipleAccounts() (gas: 494442) StakeTest:test_StakeMultipleAccountsAndRewards() (gas: 500380) diff --git a/script/Base.s.sol b/script/Base.s.sol new file mode 100644 index 0000000..ad9234c --- /dev/null +++ b/script/Base.s.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.26 <=0.9.0; + +import { Script } from "forge-std/Script.sol"; + +abstract contract BaseScript is Script { + /// @dev Included to enable compilation of the script without a $MNEMONIC environment variable. + string internal constant TEST_MNEMONIC = "test test test test test test test test test test test junk"; + + /// @dev Needed for the deterministic deployments. + bytes32 internal constant ZERO_SALT = bytes32(0); + + /// @dev The address of the transaction broadcaster. + address internal broadcaster; + + /// @dev Used to derive the broadcaster's address if $ETH_FROM is not defined. + string internal mnemonic; + + /// @dev Initializes the transaction broadcaster like this: + /// + /// - If $ETH_FROM is defined, use it. + /// - Otherwise, derive the broadcaster address from $MNEMONIC. + /// - If $MNEMONIC is not defined, default to a test mnemonic. + /// + /// The use case for $ETH_FROM is to specify the broadcaster key and its address via the command line. + constructor() { + address from = vm.envOr({ name: "ETH_FROM", defaultValue: address(0) }); + if (from != address(0)) { + broadcaster = from; + } else { + mnemonic = vm.envOr({ name: "MNEMONIC", defaultValue: TEST_MNEMONIC }); + (broadcaster,) = deriveRememberKey({ mnemonic: mnemonic, index: 0 }); + } + } + + modifier broadcast() { + vm.startBroadcast(broadcaster); + _; + vm.stopBroadcast(); + } +} diff --git a/script/DeployRewardsStreamerMP.s.sol b/script/DeployRewardsStreamerMP.s.sol new file mode 100644 index 0000000..387a625 --- /dev/null +++ b/script/DeployRewardsStreamerMP.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { BaseScript } from "./Base.s.sol"; +import { DeploymentConfig } from "./DeploymentConfig.s.sol"; +import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; +import { StakeManagerProxy } from "../src/StakeManagerProxy.sol"; +import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; +import { StakeVault } from "../src/StakeVault.sol"; + +contract DeployRewardsStreamerMPScript is BaseScript { + function run() public returns (RewardsStreamerMP, DeploymentConfig) { + DeploymentConfig deploymentConfig = new DeploymentConfig(broadcaster); + (address deployer, address stakingToken) = deploymentConfig.activeNetworkConfig(); + + bytes memory initializeData = abi.encodeCall(RewardsStreamerMP.initialize, (deployer, stakingToken)); + + vm.startBroadcast(deployer); + address impl = address(new RewardsStreamerMP()); + address proxy = address(new StakeManagerProxy(impl, initializeData)); + 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); + } +} diff --git a/script/DeploymentConfig.s.sol b/script/DeploymentConfig.s.sol new file mode 100644 index 0000000..3195f4b --- /dev/null +++ b/script/DeploymentConfig.s.sol @@ -0,0 +1,42 @@ +//// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.8.26 <=0.9.0; + +import { Script } from "forge-std/Script.sol"; +import { MockToken } from "../test/mocks/MockToken.sol"; + +contract DeploymentConfig is Script { + error DeploymentConfig_InvalidDeployerAddress(); + error DeploymentConfig_NoConfigForChain(uint256); + + struct NetworkConfig { + address deployer; + address stakingToken; + } + + NetworkConfig public activeNetworkConfig; + + address private deployer; + + constructor(address _broadcaster) { + if (_broadcaster == address(0)) revert DeploymentConfig_InvalidDeployerAddress(); + deployer = _broadcaster; + if (block.chainid == 31_337) { + activeNetworkConfig = getOrCreateAnvilEthConfig(); + } else { + revert DeploymentConfig_NoConfigForChain(block.chainid); + } + } + + function getOrCreateAnvilEthConfig() public returns (NetworkConfig memory) { + MockToken stakingToken = new MockToken("Staking Token", "ST"); + return NetworkConfig({ deployer: deployer, stakingToken: address(stakingToken) }); + } + + // This function is a hack to have it excluded by `forge coverage` until + // https://github.com/foundry-rs/foundry/issues/2988 is fixed. + // See: https://github.com/foundry-rs/foundry/issues/2988#issuecomment-1437784542 + // for more info. + // solhint-disable-next-line + function test() public { } +} diff --git a/script/RewardsStreamer.s.sol b/script/RewardsStreamer.s.sol deleted file mode 100644 index 4656952..0000000 --- a/script/RewardsStreamer.s.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.26; - -import { Script } from "forge-std/Script.sol"; -import { RewardsStreamer } from "../src/RewardsStreamer.sol"; - -contract RewardsStreamerScript is Script { - RewardsStreamer public rewardsStreamer; - - function run() public { - vm.startBroadcast(); - - // stakeManager = new StakeManager(); - - vm.stopBroadcast(); - } -} diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index 9270aa6..8595da2 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { DeployRewardsStreamerMPScript } from "../script/DeployRewardsStreamerMP.s.sol"; +import { DeploymentConfig } from "../script/DeploymentConfig.s.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { StakeVault } from "../src/StakeVault.sol"; import { IStakeManagerProxy } from "../src/interfaces/IStakeManagerProxy.sol"; @@ -17,7 +18,7 @@ contract RewardsStreamerMPTest is Test { MockToken stakingToken; RewardsStreamerMP public streamer; - address admin = makeAddr("admin"); + address admin; address alice = makeAddr("alice"); address bob = makeAddr("bob"); address charlie = makeAddr("charlie"); @@ -26,20 +27,14 @@ contract RewardsStreamerMPTest is Test { mapping(address owner => address vault) public vaults; function setUp() public virtual { - stakingToken = new MockToken("Staking Token", "ST"); + DeployRewardsStreamerMPScript deployment = new DeployRewardsStreamerMPScript(); + (RewardsStreamerMP stakeManager, DeploymentConfig deploymentConfig) = deployment.run(); - bytes memory initializeData = abi.encodeCall(RewardsStreamerMP.initialize, (admin, address(stakingToken))); - address impl = address(new RewardsStreamerMP()); - address proxy = address(new StakeManagerProxy(impl, initializeData)); - streamer = RewardsStreamerMP(proxy); + (address _deployer, 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; - - // Register the codehash before creating any user vaults - vm.prank(admin); - streamer.setTrustedCodehash(vaultCodeHash, true); + streamer = stakeManager; + stakingToken = MockToken(_stakingToken); + admin = _deployer; address[4] memory accounts = [alice, bob, charlie, dave]; for (uint256 i = 0; i < accounts.length; i++) { @@ -111,11 +106,6 @@ contract RewardsStreamerMPTest is Test { vm.prank(owner); vault = new StakeVault(owner, IStakeManagerProxy(address(streamer))); vault.register(); - - if (!streamer.isTrustedCodehash(address(vault).codehash)) { - vm.prank(admin); - streamer.setTrustedCodehash(address(vault).codehash, true); - } } function _stake(address account, uint256 amount, uint256 lockupTime) public {