diff --git a/script/staking/ChangeDistributor.t.sol b/script/staking/ChangeDistributor.t.sol new file mode 100644 index 00000000..300ad830 --- /dev/null +++ b/script/staking/ChangeDistributor.t.sol @@ -0,0 +1,166 @@ +// Copyright (c) Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; +import {TimelockController} from "openzeppelin-contracts-4.9.3/governance/TimelockController.sol"; +import {IERC20} from "openzeppelin-contracts-4.9.3/token/ERC20/IERC20.sol"; +import {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/proxy/utils/UUPSUpgradeable.sol"; +import {IAccessControlUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/access/IAccessControlUpgradeable.sol"; + +import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; +import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; +import {StakeHolderWIMXV2} from "../../contracts/staking/StakeHolderWIMXV2.sol"; +import {WIMX} from "../../contracts/staking/WIMX.sol"; +import {OwnableCreate3Deployer} from "../../contracts/deployer/create3/OwnableCreate3Deployer.sol"; + + +/** + * @notice Script for proposing and executing changes to which account has distributor role. + * @dev testDeploy is the test. + * @dev proposeChangeDistributor and executeChangeDistributor() are the functions the script should call. + * For more details on deployment see ../../contracts/staking/README.md + */ +contract ChangeDistributor is Test { + // Values that are the same on Testnet and Mainnet + // Timelock controller proposer. + bytes32 constant PROPOSER_ROLE = 0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1; + // Timelock controller executor. + bytes32 constant EXECUTOR_ROLE = 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63; + // StakeHolder distributor. + bytes32 private constant DISTRIBUTOR_ROLE = 0x444953545249425554455f524f4c450000000000000000000000000000000000; + // Timelock controller contract. + address constant TIMELOCK_CONTROLLER = 0x994a66607f947A47F33C2fA80e0470C03C30e289; + // EIP1697 proxy. + address constant STAKE_HOLDER_PROXY = 0xb6c2aA8690C8Ab6AC380a0bb798Ab0debe5C4C38; + // Deployer of contracts and initial configuration. + address constant DEPLOYER_ADDRESS = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; + // Ownable create3 factory used to deploy contracts. + address constant OWNABLE_CREATE3_FACTORY = 0x37a59A845Bb6eD2034098af8738fbFFB9D589610; + // One week time delay. + uint256 constant TIMELOCK_DELAY = 604800; + // Address configured for distributor when the stakeholder contracts were deployed. + address private constant OLD_DISTRIBUTOR = DEPLOYER_ADDRESS; + + // Values that are different on Testnet and Mainnet + // On mainnet Proposer and Execute are a GnosisSafeProxy. + address constant MAINNET_PROPOSER = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; + address constant MAINNET_EXECUTOR = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; + // On testnet Proposer and Execute are the deployer address. + address constant TESTNET_PROPOSER = DEPLOYER_ADDRESS; + address constant TESTNET_EXECUTOR = DEPLOYER_ADDRESS; + // Distrubtor accounts + address constant TESTNET_NEW_DISTRIBUTOR = 0x8BA97cE2C64E2d1b9826bb6aB5e288524873f63D; + address constant MAINNET_NEW_DISTRIBUTOR = 0xAd34133D4EA0c6F0a98FdE5FA2c668E12062C33D; + + // Used for fork testing + string constant MAINNET_RPC_URL = "https://rpc.immutable.com/"; + + TimelockController stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + + + function proposeChangeDistributor() external { + uint256 isMainnet = vm.envUint("IMMUTABLE_NETWORK"); + address newDistributor = (isMainnet == 1) ? MAINNET_NEW_DISTRIBUTOR : TESTNET_NEW_DISTRIBUTOR; + address proposer = (isMainnet == 1) ? MAINNET_PROPOSER : TESTNET_PROPOSER; + _proposeChangeDistributor(proposer, newDistributor); + } + + function executeChangeDistributor() external { + uint256 isMainnet = vm.envUint("IMMUTABLE_NETWORK"); + address newDistributor = (isMainnet == 1) ? MAINNET_NEW_DISTRIBUTOR : TESTNET_NEW_DISTRIBUTOR; + address executor = (isMainnet == 1) ? MAINNET_EXECUTOR : TESTNET_EXECUTOR; + _executeChangeDistributor(executor, newDistributor); + } + + function _proposeChangeDistributor(address _proposer, address _newDistributor) internal { + assertTrue(stakeHolderTimeDelay.hasRole(PROPOSER_ROLE, _proposer), "Proposer does not have proposer role"); + + (address[] memory targets, uint256[] memory values, bytes[] memory data, + bytes32 predecessor, bytes32 salt) = + _getChangeDistributorProposalParams(OLD_DISTRIBUTOR, _newDistributor); + + vm.startBroadcast(_proposer); + stakeHolderTimeDelay.scheduleBatch(targets, values, data, predecessor, salt, TIMELOCK_DELAY); + vm.stopBroadcast(); + } + + function _executeChangeDistributor(address _executor, address _newDistributor) internal { + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, _executor), "Executor does not have executor role"); + + (address[] memory targets, uint256[] memory values, bytes[] memory data, + bytes32 predecessor, bytes32 salt) = + _getChangeDistributorProposalParams(OLD_DISTRIBUTOR, _newDistributor); + + bytes32 id = stakeHolderTimeDelay.hashOperationBatch(targets, values, data, predecessor, salt); + assertTrue(stakeHolderTimeDelay.isOperationReady(id), "Operation is not yet ready"); + + vm.startBroadcast(_executor); + stakeHolderTimeDelay.executeBatch(targets, values, data, predecessor, salt); + vm.stopBroadcast(); + } + + function _getChangeDistributorProposalParams(address _oldAccount, address _newAccount) private returns ( + address[] memory targets, uint256[] memory values, bytes[] memory data, bytes32 predecessor, bytes32 salt) { + + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + + bytes memory callData0 = abi.encodeWithSelector( + IAccessControlUpgradeable.revokeRole.selector, + DISTRIBUTOR_ROLE, + _oldAccount); + bytes memory callData1 = abi.encodeWithSelector( + IAccessControlUpgradeable.grantRole.selector, + DISTRIBUTOR_ROLE, + _newAccount); + + targets = new address[](2); + values = new uint256[](2); + data = new bytes[](2); + targets[0] = STAKE_HOLDER_PROXY; + values[0] = 0; + data[0] = callData0; + targets[1] = STAKE_HOLDER_PROXY; + values[1] = 0; + data[1] = callData1; + + predecessor = bytes32(0); + salt = bytes32(uint256(1)); + } + + + // Test the remainder of the upgrade process. + function testRemainderChangeDistributor() public { + uint256 mainnetFork = vm.createFork(MAINNET_RPC_URL); + vm.selectFork(mainnetFork); + + IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); + if (!stakeHolder.hasRole(DISTRIBUTOR_ROLE, OLD_DISTRIBUTOR)) { + // Change distributor has occurred. + return; + } + + (address[] memory targets, uint256[] memory values, bytes[] memory data, + bytes32 predecessor, bytes32 salt) = + _getChangeDistributorProposalParams(OLD_DISTRIBUTOR, MAINNET_NEW_DISTRIBUTOR); + bytes32 id = stakeHolderTimeDelay.hashOperationBatch(targets, values, data, predecessor, salt); + + if (!stakeHolderTimeDelay.isOperation(id)) { + _proposeChangeDistributor(MAINNET_PROPOSER, MAINNET_NEW_DISTRIBUTOR); + } + + uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id); + uint256 time = earliestExecuteTime; + if (time < block.timestamp) { + time = block.timestamp; + } + vm.warp(time); + + _executeChangeDistributor(MAINNET_EXECUTOR, MAINNET_NEW_DISTRIBUTOR); + + require(!stakeHolder.hasRole(DISTRIBUTOR_ROLE, OLD_DISTRIBUTOR), "Old distributor still has role"); + require(stakeHolder.hasRole(DISTRIBUTOR_ROLE, MAINNET_NEW_DISTRIBUTOR), "New distributor does not have role"); + } +} diff --git a/script/staking/StakeHolderScriptWIMX.t.sol b/script/staking/StakeHolderScriptWIMX.t.sol index bfbe7157..041cf14c 100644 --- a/script/staking/StakeHolderScriptWIMX.t.sol +++ b/script/staking/StakeHolderScriptWIMX.t.sol @@ -7,6 +7,7 @@ import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Pr import {TimelockController} from "openzeppelin-contracts-4.9.3/governance/TimelockController.sol"; import {IERC20} from "openzeppelin-contracts-4.9.3/token/ERC20/IERC20.sol"; import {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/proxy/utils/UUPSUpgradeable.sol"; +import {IAccessControlUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/access/IAccessControlUpgradeable.sol"; import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; @@ -383,142 +384,4 @@ contract StakeHolderScriptWIMX is Test { assertEq(user1.balance, 97 ether, "User1 balance after unstake"); assertEq(erc20.balanceOf(address(_stakeHolder)), 3 ether, "StakeHolder balance after unstake"); } - - - // *********************** UPGRADE TO V2 *************************** - - string constant MAINNET_RPC_URL = "https://rpc.immutable.com/"; - address constant STAKE_HOLDER_PROXY = 0xb6c2aA8690C8Ab6AC380a0bb798Ab0debe5C4C38; - address constant TIMELOCK_CONTROLLER = 0x994a66607f947A47F33C2fA80e0470C03C30e289; - bytes32 constant PROPOSER_ROLE = 0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1; - bytes32 constant EXECUTOR_ROLE = 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63; - - // On mainnet Proposer and Execute are a GnosisSafeProxy. - address constant PROPOSER = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; - address constant EXECUTOR = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; - uint256 constant TIMELOCK_DELAY = 604800; - - address constant DEPLOYER_ADDRESS = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; - address constant OWNABLE_CREATE3_FACTORY = 0x37a59A845Bb6eD2034098af8738fbFFB9D589610; - - address constant STAKE_HOLDER_V2 = address(0x0); - - TimelockController stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); - - - function deployV2() external { - address stakeHolderV2 = _deployV2(); - console.log("Deployed StakeHolderWIMXV2 to: %s", stakeHolderV2); - } - - function proposeUpgradeToV2() external { - _proposeUpgradeToV2(STAKE_HOLDER_V2); - } - - function executeUpgradeToV2() external { - _executeUpgradeToV2(STAKE_HOLDER_V2); - } - - function _deployV2() internal returns (address) { - bytes32 salt = bytes32(uint256(17)); - - IDeployer ownableCreate3 = IDeployer(OWNABLE_CREATE3_FACTORY); - - // Deploy StakeHolderWIMXV2 via the Ownable Create3 factory. - // Create deployment bytecode and encode constructor args - bytes memory deploymentBytecode = abi.encodePacked( - type(StakeHolderWIMXV2).creationCode - ); - /// @dev Deploy the contract via the Ownable CREATE3 factory - vm.startBroadcast(DEPLOYER_ADDRESS); - address stakeHolderImplAddress = ownableCreate3.deploy(deploymentBytecode, salt); - vm.stopBroadcast(); - return stakeHolderImplAddress; - } - - function _proposeUpgradeToV2(address _v2Impl) internal { - assertTrue(stakeHolderTimeDelay.hasRole(PROPOSER_ROLE, PROPOSER), "Proposer does not have proposer role"); - assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, EXECUTOR), "Executor does not have executor role"); - - (address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) = - _getProposalParams(_v2Impl); - - vm.startBroadcast(PROPOSER); - stakeHolderTimeDelay.schedule(target, value, data, predecessor, salt, TIMELOCK_DELAY); - vm.stopBroadcast(); - } - - function _executeUpgradeToV2(address _v2Impl) internal { - stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); - assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, EXECUTOR), "Executor does not have executor role"); - - (address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) = - _getProposalParams(_v2Impl); - - bytes32 id = stakeHolderTimeDelay.hashOperation(target, value, data, predecessor, salt); - assertTrue(stakeHolderTimeDelay.isOperationReady(id), "Operation is not yet ready"); - - vm.startBroadcast(EXECUTOR); - stakeHolderTimeDelay.execute(target, value, data, predecessor, salt); - vm.stopBroadcast(); - - IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); - assertEq(stakeHolder.version(), 2, "Upgrade did not upgrade to version 2"); - } - - function _getProposalParams(address _v2Impl) private returns ( - address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) { - - stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); - assertNotEq(_v2Impl, address(0), "StakeHolderV2 can not be address(0)"); - - bytes memory callData = abi.encodeWithSelector(StakeHolderBase.upgradeStorage.selector, bytes("")); - bytes memory upgradeCall = abi.encodeWithSelector( - UUPSUpgradeable.upgradeToAndCall.selector, _v2Impl, callData); - - target = STAKE_HOLDER_PROXY; - value = 0; - data = upgradeCall; - predecessor = bytes32(0); - salt = bytes32(uint256(1)); - } - - - // Test the remainder of the upgrade process. - function testRemainderOfUpgradeProcessToV2() public { - uint256 mainnetFork = vm.createFork(MAINNET_RPC_URL); - vm.selectFork(mainnetFork); - - IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); - if (stakeHolder.version() != 0) { - // Upgrade has occurred. Nothing to test. - return; - } - - address stakeHolderV2 = STAKE_HOLDER_V2; - if (stakeHolderV2 == address(0)) { - // StakeHolderWIMXV2 has not been deployed yet. - stakeHolderV2 = _deployV2(); - } - - (address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) = - _getProposalParams(stakeHolderV2); - bytes32 id = stakeHolderTimeDelay.hashOperation(target, value, data, predecessor, salt); - if (!stakeHolderTimeDelay.isOperation(id)) { - // The upgrade hasn't been proposed yet. - _proposeUpgradeToV2(stakeHolderV2); - } - - uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id); - uint256 time = earliestExecuteTime; - if (time < block.timestamp) { - time = block.timestamp; - } - vm.warp(time); - - uint256 numStakersBefore = stakeHolder.getNumStakers(); - _executeUpgradeToV2(stakeHolderV2); - uint256 numStakersAfter = stakeHolder.getNumStakers(); - assertEq(numStakersBefore, numStakersAfter, "Number of stakers before and after upgrade do not match"); - } } diff --git a/script/staking/UpgradeToWIMXV2.t.sol b/script/staking/UpgradeToWIMXV2.t.sol new file mode 100644 index 00000000..c4113a3e --- /dev/null +++ b/script/staking/UpgradeToWIMXV2.t.sol @@ -0,0 +1,201 @@ +// Copyright (c) Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; +import {TimelockController} from "openzeppelin-contracts-4.9.3/governance/TimelockController.sol"; +import {IERC20} from "openzeppelin-contracts-4.9.3/token/ERC20/IERC20.sol"; +import {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/proxy/utils/UUPSUpgradeable.sol"; +import {IAccessControlUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/access/IAccessControlUpgradeable.sol"; + +import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; +import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; +import {StakeHolderWIMXV2} from "../../contracts/staking/StakeHolderWIMXV2.sol"; +import {WIMX} from "../../contracts/staking/WIMX.sol"; +import {OwnableCreate3Deployer} from "../../contracts/deployer/create3/OwnableCreate3Deployer.sol"; + +/** + * @title IDeployer Interface + * @notice This interface defines the contract responsible for deploying and optionally initializing new contracts + * via a specified deployment method. + * @dev Credit to axelarnetwork https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/main/contracts/interfaces/IDeployer.sol + */ +interface IDeployer { + function deploy(bytes memory bytecode, bytes32 salt) external payable returns (address deployedAddress_); + function deployAndInit(bytes memory bytecode, bytes32 salt, bytes calldata init) + external + payable + returns (address deployedAddress_); + function deployedAddress(bytes calldata bytecode, address sender, bytes32 salt) + external + view + returns (address deployedAddress_); +} + +/** + * @notice Deployment script and test code for the deployment script. + * @dev testRemainderOfUpgradeProcessToV2 Tests the upgrade + * @dev deployV2() to deploy the V2 contract. + * @dev proposeUpgradeToV2() to propose the upgrade. + * @dev executeUpgradeToV2deploy() to execute the upgrade. + * For more details on deployment see ../../contracts/staking/README.md + */ +contract UpgradeToWIMXV2 is Test { + // Values that are the same on Testnet and Mainnet + // Timelock controller proposer. + bytes32 constant PROPOSER_ROLE = 0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1; + // Timelock controller executor. + bytes32 constant EXECUTOR_ROLE = 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63; + // StakeHolder distributor. + bytes32 private constant DISTRIBUTOR_ROLE = 0x444953545249425554455f524f4c450000000000000000000000000000000000; + // Timelock controller contract. + address constant TIMELOCK_CONTROLLER = 0x994a66607f947A47F33C2fA80e0470C03C30e289; + // EIP1697 proxy. + address constant STAKE_HOLDER_PROXY = 0xb6c2aA8690C8Ab6AC380a0bb798Ab0debe5C4C38; + // Deployer of contracts and initial configuration. + address constant DEPLOYER_ADDRESS = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; + // Ownable create3 factory used to deploy contracts. + address constant OWNABLE_CREATE3_FACTORY = 0x37a59A845Bb6eD2034098af8738fbFFB9D589610; + // One week time delay. + uint256 constant TIMELOCK_DELAY = 604800; + // Address that StakeHolderWIMX v2 is deployed to. + address constant STAKE_HOLDER_V2 = 0x2dE15aB8337a86787bEc585cd9159dfb75aFf97F; + + // Values that are different on Testnet and Mainnet + // On mainnet Proposer and Execute are a GnosisSafeProxy. + address constant MAINNET_PROPOSER = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; + address constant MAINNET_EXECUTOR = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; + // On testnet Proposer and Execute are the deployer address. + address constant TESTNET_PROPOSER = DEPLOYER_ADDRESS; + address constant TESTNET_EXECUTOR = DEPLOYER_ADDRESS; + // Distrubtor accounts + address constant TESTNET_NEW_DISTRIBUTOR = 0x8BA97cE2C64E2d1b9826bb6aB5e288524873f63D; + address constant MAINNET_NEW_DISTRIBUTOR = 0xAd34133D4EA0c6F0a98FdE5FA2c668E12062C33D; + + // Used for fork testing + string constant MAINNET_RPC_URL = "https://rpc.immutable.com/"; + + TimelockController stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + + + function deployV2() external { + address stakeHolderV2 = _deployV2(); + console.log("Deployed StakeHolderWIMXV2 to: %s", stakeHolderV2); + } + + function proposeUpgradeToV2() external { + uint256 isMainnet = vm.envUint("IMMUTABLE_NETWORK"); + address proposer = (isMainnet == 1) ? MAINNET_PROPOSER : TESTNET_PROPOSER; + _proposeUpgradeToV2(proposer, STAKE_HOLDER_V2); + } + + function executeUpgradeToV2() external { + uint256 isMainnet = vm.envUint("IMMUTABLE_NETWORK"); + address executor = (isMainnet == 1) ? MAINNET_EXECUTOR : TESTNET_EXECUTOR; + _executeUpgradeToV2(executor, STAKE_HOLDER_V2); + } + + function _deployV2() internal returns (address) { + bytes32 salt = bytes32(uint256(17)); + + IDeployer ownableCreate3 = IDeployer(OWNABLE_CREATE3_FACTORY); + + // Deploy StakeHolderWIMXV2 via the Ownable Create3 factory. + // Create deployment bytecode and encode constructor args + bytes memory deploymentBytecode = abi.encodePacked( + type(StakeHolderWIMXV2).creationCode + ); + /// @dev Deploy the contract via the Ownable CREATE3 factory + vm.startBroadcast(DEPLOYER_ADDRESS); + address stakeHolderImplAddress = ownableCreate3.deploy(deploymentBytecode, salt); + vm.stopBroadcast(); + return stakeHolderImplAddress; + } + + function _proposeUpgradeToV2(address _proposer, address _v2Impl) internal { + assertTrue(stakeHolderTimeDelay.hasRole(PROPOSER_ROLE, _proposer), "Proposer does not have proposer role"); + + (address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) = + _getProposalParams(_v2Impl); + + vm.startBroadcast(_proposer); + stakeHolderTimeDelay.schedule(target, value, data, predecessor, salt, TIMELOCK_DELAY); + vm.stopBroadcast(); + } + + function _executeUpgradeToV2(address _executor, address _v2Impl) internal { + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, _executor), "Executor does not have executor role"); + + (address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) = + _getProposalParams(_v2Impl); + + bytes32 id = stakeHolderTimeDelay.hashOperation(target, value, data, predecessor, salt); + assertTrue(stakeHolderTimeDelay.isOperationReady(id), "Operation is not yet ready"); + + vm.startBroadcast(_executor); + stakeHolderTimeDelay.execute(target, value, data, predecessor, salt); + vm.stopBroadcast(); + + IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); + assertEq(stakeHolder.version(), 2, "Upgrade did not upgrade to version 2"); + } + + function _getProposalParams(address _v2Impl) private returns ( + address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) { + + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + assertNotEq(_v2Impl, address(0), "StakeHolderV2 can not be address(0)"); + + bytes memory callData = abi.encodeWithSelector(StakeHolderBase.upgradeStorage.selector, bytes("")); + bytes memory upgradeCall = abi.encodeWithSelector( + UUPSUpgradeable.upgradeToAndCall.selector, _v2Impl, callData); + + target = STAKE_HOLDER_PROXY; + value = 0; + data = upgradeCall; + predecessor = bytes32(0); + salt = bytes32(uint256(1)); + } + + + // Test the remainder of the upgrade process. + function testRemainderOfUpgradeProcessToV2() public { + uint256 mainnetFork = vm.createFork(MAINNET_RPC_URL); + vm.selectFork(mainnetFork); + + IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); + if (stakeHolder.version() != 0) { + // Upgrade has occurred. Nothing to test. + return; + } + + address stakeHolderV2 = STAKE_HOLDER_V2; + if (stakeHolderV2.code.length == 0) { + // StakeHolderWIMXV2 has not been deployed yet. + stakeHolderV2 = _deployV2(); + require(stakeHolderV2 == STAKE_HOLDER_V2, "Incorrect deployment address"); + } + + (address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) = + _getProposalParams(stakeHolderV2); + bytes32 id = stakeHolderTimeDelay.hashOperation(target, value, data, predecessor, salt); + if (!stakeHolderTimeDelay.isOperation(id)) { + // The upgrade hasn't been proposed yet. + _proposeUpgradeToV2(MAINNET_PROPOSER, stakeHolderV2); + } + + uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id); + uint256 time = earliestExecuteTime; + if (time < block.timestamp) { + time = block.timestamp; + } + vm.warp(time); + + uint256 numStakersBefore = stakeHolder.getNumStakers(); + _executeUpgradeToV2(MAINNET_EXECUTOR, stakeHolderV2); + uint256 numStakersAfter = stakeHolder.getNumStakers(); + assertEq(numStakersBefore, numStakersAfter, "Number of stakers before and after upgrade do not match"); + } +} diff --git a/script/staking/changeDistributorAccount_Execxute.sh b/script/staking/changeDistributorAccount_Execxute.sh new file mode 100644 index 00000000..1e0f9cae --- /dev/null +++ b/script/staking/changeDistributorAccount_Execxute.sh @@ -0,0 +1,6 @@ +#!/bin/bash +FUNCTION_TO_EXECUTE='executeChangeDistributor()' +STAKEHOLDER_TYPE=ANY +script=script/staking/ChangeDistributor.t.sol:ChangeDistributor +# Set-up variables and execute forge +source $(dirname "$0")/common.sh diff --git a/script/staking/changeDistributorAccount_Propose.sh b/script/staking/changeDistributorAccount_Propose.sh new file mode 100644 index 00000000..552fd43d --- /dev/null +++ b/script/staking/changeDistributorAccount_Propose.sh @@ -0,0 +1,6 @@ +#!/bin/bash +FUNCTION_TO_EXECUTE='proposeChangeDistributor()' +STAKEHOLDER_TYPE=ANY +script=script/staking/ChangeDistributor.t.sol:ChangeDistributor +# Set-up variables and execute forge +source $(dirname "$0")/common.sh diff --git a/script/staking/common.sh b/script/staking/common.sh index 942f87ac..deab344b 100644 --- a/script/staking/common.sh +++ b/script/staking/common.sh @@ -48,14 +48,19 @@ if [ -z "${STAKEHOLDER_TYPE}" ]; then echo "Error: STAKEHOLDER_TYPE variable is not set. Should be ERC20 or WIMX" exit 1 fi -if [ "$STAKEHOLDER_TYPE" = "ERC20" ]; then - script=script/staking/StakeHolderScriptERC20.t.sol:StakeHolderScriptERC20 +if [ "$STAKEHOLDER_TYPE" = "ANY" ]; then + # "script" must be specified by the outer script. + echo . else - if [ "$STAKEHOLDER_TYPE" = "WIMX" ]; then - script=script/staking/StakeHolderScriptWIMX.t.sol:StakeHolderScriptWIMX - else - echo "Error: Unknown STAKEHOLDER_TYPE: " $STAKEHOLDER_TYPE - exit 1 + if [ "$STAKEHOLDER_TYPE" = "ERC20" ]; then + script=script/staking/StakeHolderScriptERC20.t.sol:StakeHolderScriptERC20 + else + if [ "$STAKEHOLDER_TYPE" = "WIMX" ]; then + script=script/staking/StakeHolderScriptWIMX.t.sol:StakeHolderScriptWIMX + else + echo "Error: Unknown STAKEHOLDER_TYPE: " $STAKEHOLDER_TYPE + exit 1 + fi fi fi diff --git a/script/staking/upgradeToWIMXV2_Deploy.sh b/script/staking/upgradeToWIMXV2_Deploy.sh index b1323375..e4c7d6cf 100644 --- a/script/staking/upgradeToWIMXV2_Deploy.sh +++ b/script/staking/upgradeToWIMXV2_Deploy.sh @@ -1,6 +1,7 @@ #!/bin/bash FUNCTION_TO_EXECUTE='deployV2()' -STAKEHOLDER_TYPE=WIMX +STAKEHOLDER_TYPE=ANY +script=script/staking/UpgradeToWIMXV2.t.sol:UpgradeToWIMXV2 # Set-up variables and execute forge source $(dirname "$0")/common.sh diff --git a/script/staking/upgradeToWIMXV2_Execute.sh b/script/staking/upgradeToWIMXV2_Execute.sh index 65bb2162..c668c8f7 100644 --- a/script/staking/upgradeToWIMXV2_Execute.sh +++ b/script/staking/upgradeToWIMXV2_Execute.sh @@ -1,6 +1,7 @@ #!/bin/bash FUNCTION_TO_EXECUTE='executeUpgradeToV2()' -STAKEHOLDER_TYPE=WIMX +STAKEHOLDER_TYPE=ANY +script=script/staking/UpgradeToWIMXV2.t.sol:UpgradeToWIMXV2 # Set-up variables and execute forge source $(dirname "$0")/common.sh diff --git a/script/staking/upgradeToWIMXV2_Propose.sh b/script/staking/upgradeToWIMXV2_Propose.sh index b11d35e0..e4c72e78 100644 --- a/script/staking/upgradeToWIMXV2_Propose.sh +++ b/script/staking/upgradeToWIMXV2_Propose.sh @@ -1,6 +1,7 @@ #!/bin/bash FUNCTION_TO_EXECUTE='proposeUpgradeToV2()' -STAKEHOLDER_TYPE=WIMX +STAKEHOLDER_TYPE=ANY +script=script/staking/UpgradeToWIMXV2.t.sol:UpgradeToWIMXV2 # Set-up variables and execute forge source $(dirname "$0")/common.sh diff --git a/test/staking/StakeHolderAttackWallet2.sol b/test/staking/StakeHolderAttackWallet2.sol index 2605e66c..d3009909 100644 --- a/test/staking/StakeHolderAttackWallet2.sol +++ b/test/staking/StakeHolderAttackWallet2.sol @@ -12,10 +12,11 @@ contract StakeHolderAttackWallet2 { stakeHolder = StakeHolderNative(_stakeHolder); } receive() external payable { - // Cause a revert that has zero call data length. The easiest way to do this is to - // call a function on an address that doesn't have a contract associated with it. - StakeHolderAttackWallet2 notARealContract = StakeHolderAttackWallet2(payable(address(0x12345))); - notARealContract.stake(0); + // Cause a revert that has zero call data length. + assembly { + revert(0, 0) + } + } function stake(uint256 _amount) external { stakeHolder.stake{value: _amount}(_amount);