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 {