From 3ec5bbd1259f64b76650f8462c2fca922d3fa206 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Fri, 22 Aug 2025 11:17:24 +1000 Subject: [PATCH 1/7] Added ability to change distributor role --- script/staking/StakeHolderScriptWIMX.t.sol | 117 ++++++++++++++++++ .../changeDistributorAccount_Execxute.sh | 7 ++ .../changeDistributorAccount_Propose.sh | 7 ++ 3 files changed, 131 insertions(+) create mode 100644 script/staking/changeDistributorAccount_Execxute.sh create mode 100644 script/staking/changeDistributorAccount_Propose.sh diff --git a/script/staking/StakeHolderScriptWIMX.t.sol b/script/staking/StakeHolderScriptWIMX.t.sol index bfbe7157..2aa0be3d 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"; @@ -521,4 +522,120 @@ contract StakeHolderScriptWIMX is Test { uint256 numStakersAfter = stakeHolder.getNumStakers(); assertEq(numStakersBefore, numStakersAfter, "Number of stakers before and after upgrade do not match"); } + + + // *********************** Change Distributor Account *************************** + bytes32 private constant DISTRIBUTOR_ROLE = 0x444953545249425554455f524f4c450000000000000000000000000000000000; + address private constant OLD_DISTRIBUTOR = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; + + function proposeChangeDistributor() external { + address distributeAdmin = vm.envAddress("DISTRIBUTE_ADMIN"); + _proposeChangeDistributor(distributeAdmin); + } + + function executeChangeDistribturo() external { + address distributeAdmin = vm.envAddress("DISTRIBUTE_ADMIN"); + _executeChangeDistributor(distributeAdmin); + } + + function _proposeChangeDistributor(address newDistributor) 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 oldDistributor = OLD_DISTRIBUTOR; + + (address[] memory targets, uint256[] memory values, bytes[] memory data, + bytes32 predecessor, bytes32 salt) = + _getChangeDistributorProposalParams(oldDistributor, newDistributor); + + vm.startBroadcast(PROPOSER); + stakeHolderTimeDelay.scheduleBatch(targets, values, data, predecessor, salt, TIMELOCK_DELAY); + vm.stopBroadcast(); + } + + function _executeChangeDistributor(address newDistributor) internal { + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); + assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, EXECUTOR), "Executor does not have executor role"); + + address oldDistributor = OLD_DISTRIBUTOR; + + (address[] memory targets, uint256[] memory values, bytes[] memory data, + bytes32 predecessor, bytes32 salt) = + _getChangeDistributorProposalParams(oldDistributor, 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); + + address oldDistributor = OLD_DISTRIBUTOR; + IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); + if (!stakeHolder.hasRole(DISTRIBUTOR_ROLE, oldDistributor)) { + // Change distributor has occurred. + return; + } + + address newDistributor = 0x8BA97cE2C64E2d1b9826bb6aB5e288524873f63D; + + (address[] memory targets, uint256[] memory values, bytes[] memory data, + bytes32 predecessor, bytes32 salt) = + _getChangeDistributorProposalParams(oldDistributor, newDistributor); + bytes32 id = stakeHolderTimeDelay.hashOperationBatch(targets, values, data, predecessor, salt); + + if (!stakeHolderTimeDelay.isOperation(id)) { + _proposeChangeDistributor(newDistributor); + } + + uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id); + uint256 time = earliestExecuteTime; + if (time < block.timestamp) { + time = block.timestamp; + } + vm.warp(time); + + _executeChangeDistributor(newDistributor); + + require(!stakeHolder.hasRole(DISTRIBUTOR_ROLE, oldDistributor), "Old distributor still has role"); + require(stakeHolder.hasRole(DISTRIBUTOR_ROLE, newDistributor), "New distributor does not have role"); + } + } diff --git a/script/staking/changeDistributorAccount_Execxute.sh b/script/staking/changeDistributorAccount_Execxute.sh new file mode 100644 index 00000000..a6d20fd6 --- /dev/null +++ b/script/staking/changeDistributorAccount_Execxute.sh @@ -0,0 +1,7 @@ +#!/bin/bash +FUNCTION_TO_EXECUTE='executeChangeDistributor()' +STAKEHOLDER_TYPE=WIMX +# 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..693b3a47 --- /dev/null +++ b/script/staking/changeDistributorAccount_Propose.sh @@ -0,0 +1,7 @@ +#!/bin/bash +FUNCTION_TO_EXECUTE='proposeChangeDistributor()' +STAKEHOLDER_TYPE=WIMX +# Set-up variables and execute forge +source $(dirname "$0")/common.sh + + From f3c9c96d79fcd10c03433a1651984eb0a1614efc Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Fri, 22 Aug 2025 15:16:58 +1000 Subject: [PATCH 2/7] Add deployment address of WIMX v2 --- script/staking/StakeHolderScriptWIMX.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/staking/StakeHolderScriptWIMX.t.sol b/script/staking/StakeHolderScriptWIMX.t.sol index 2aa0be3d..1cde05b0 100644 --- a/script/staking/StakeHolderScriptWIMX.t.sol +++ b/script/staking/StakeHolderScriptWIMX.t.sol @@ -402,7 +402,7 @@ contract StakeHolderScriptWIMX is Test { address constant DEPLOYER_ADDRESS = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; address constant OWNABLE_CREATE3_FACTORY = 0x37a59A845Bb6eD2034098af8738fbFFB9D589610; - address constant STAKE_HOLDER_V2 = address(0x0); + address constant STAKE_HOLDER_V2 = address(0x2de15ab8337a86787bec585cd9159dfb75aff97f); TimelockController stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); From 136ec0308bdd930e15ec4e02e5df2bddd2c4bf97 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Mon, 25 Aug 2025 13:12:49 +1000 Subject: [PATCH 3/7] Split out scripts for upgrade and changing distributor --- script/staking/ChangeDistributor.t.sol | 166 ++++++++++++ script/staking/StakeHolderScriptWIMX.t.sol | 254 ------------------ script/staking/UpgradeToWIMXV2.t.sol | 229 ++++++++++++++++ .../changeDistributorAccount_Execxute.sh | 5 +- .../changeDistributorAccount_Propose.sh | 5 +- script/staking/common.sh | 19 +- script/staking/upgradeToWIMXV2_Deploy.sh | 3 +- script/staking/upgradeToWIMXV2_Execute.sh | 3 +- script/staking/upgradeToWIMXV2_Propose.sh | 3 +- 9 files changed, 417 insertions(+), 270 deletions(-) create mode 100644 script/staking/ChangeDistributor.t.sol create mode 100644 script/staking/UpgradeToWIMXV2.t.sol 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 1cde05b0..041cf14c 100644 --- a/script/staking/StakeHolderScriptWIMX.t.sol +++ b/script/staking/StakeHolderScriptWIMX.t.sol @@ -384,258 +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(0x2de15ab8337a86787bec585cd9159dfb75aff97f); - - 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"); - } - - - // *********************** Change Distributor Account *************************** - bytes32 private constant DISTRIBUTOR_ROLE = 0x444953545249425554455f524f4c450000000000000000000000000000000000; - address private constant OLD_DISTRIBUTOR = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; - - function proposeChangeDistributor() external { - address distributeAdmin = vm.envAddress("DISTRIBUTE_ADMIN"); - _proposeChangeDistributor(distributeAdmin); - } - - function executeChangeDistribturo() external { - address distributeAdmin = vm.envAddress("DISTRIBUTE_ADMIN"); - _executeChangeDistributor(distributeAdmin); - } - - function _proposeChangeDistributor(address newDistributor) 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 oldDistributor = OLD_DISTRIBUTOR; - - (address[] memory targets, uint256[] memory values, bytes[] memory data, - bytes32 predecessor, bytes32 salt) = - _getChangeDistributorProposalParams(oldDistributor, newDistributor); - - vm.startBroadcast(PROPOSER); - stakeHolderTimeDelay.scheduleBatch(targets, values, data, predecessor, salt, TIMELOCK_DELAY); - vm.stopBroadcast(); - } - - function _executeChangeDistributor(address newDistributor) internal { - stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); - assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, EXECUTOR), "Executor does not have executor role"); - - address oldDistributor = OLD_DISTRIBUTOR; - - (address[] memory targets, uint256[] memory values, bytes[] memory data, - bytes32 predecessor, bytes32 salt) = - _getChangeDistributorProposalParams(oldDistributor, 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); - - address oldDistributor = OLD_DISTRIBUTOR; - IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); - if (!stakeHolder.hasRole(DISTRIBUTOR_ROLE, oldDistributor)) { - // Change distributor has occurred. - return; - } - - address newDistributor = 0x8BA97cE2C64E2d1b9826bb6aB5e288524873f63D; - - (address[] memory targets, uint256[] memory values, bytes[] memory data, - bytes32 predecessor, bytes32 salt) = - _getChangeDistributorProposalParams(oldDistributor, newDistributor); - bytes32 id = stakeHolderTimeDelay.hashOperationBatch(targets, values, data, predecessor, salt); - - if (!stakeHolderTimeDelay.isOperation(id)) { - _proposeChangeDistributor(newDistributor); - } - - uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id); - uint256 time = earliestExecuteTime; - if (time < block.timestamp) { - time = block.timestamp; - } - vm.warp(time); - - _executeChangeDistributor(newDistributor); - - require(!stakeHolder.hasRole(DISTRIBUTOR_ROLE, oldDistributor), "Old distributor still has role"); - require(stakeHolder.hasRole(DISTRIBUTOR_ROLE, newDistributor), "New distributor does not have role"); - } - } diff --git a/script/staking/UpgradeToWIMXV2.t.sol b/script/staking/UpgradeToWIMXV2.t.sol new file mode 100644 index 00000000..586a1c59 --- /dev/null +++ b/script/staking/UpgradeToWIMXV2.t.sol @@ -0,0 +1,229 @@ +// 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_); +} + +// Args needed for compex deployment using CREATE3 and a TimelockController +struct ComplexDeploymentArgs { + address signer; + address factory; + string salt; +} +struct ComplexStakeHolderContractArgs { + address distributeAdmin; + address token; +} +struct ComplexTimelockContractArgs { + uint256 timeDelayInSeconds; + address proposerAdmin; + address executorAdmin; +} + + +// Args needed for simple deployment +struct SimpleDeploymentArgs { + address deployer; +} +struct SimpleStakeHolderContractArgs { + address roleAdmin; + address upgradeAdmin; + address distributeAdmin; + address token; +} + + + +/** + * @notice Deployment script and test code for the deployment script. + * @dev testDeploy is the test. + * @dev deploy() is the function the script should call. + * 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 index a6d20fd6..1e0f9cae 100644 --- a/script/staking/changeDistributorAccount_Execxute.sh +++ b/script/staking/changeDistributorAccount_Execxute.sh @@ -1,7 +1,6 @@ #!/bin/bash FUNCTION_TO_EXECUTE='executeChangeDistributor()' -STAKEHOLDER_TYPE=WIMX +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 index 693b3a47..552fd43d 100644 --- a/script/staking/changeDistributorAccount_Propose.sh +++ b/script/staking/changeDistributorAccount_Propose.sh @@ -1,7 +1,6 @@ #!/bin/bash FUNCTION_TO_EXECUTE='proposeChangeDistributor()' -STAKEHOLDER_TYPE=WIMX +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..2312ec0b 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. + 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 From 3dad78507c093fcf911e9632f5d5c49e9190377b Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Mon, 25 Aug 2025 13:18:54 +1000 Subject: [PATCH 4/7] Fix documentation --- script/staking/UpgradeToWIMXV2.t.sol | 36 ++++------------------------ 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/script/staking/UpgradeToWIMXV2.t.sol b/script/staking/UpgradeToWIMXV2.t.sol index 586a1c59..c4113a3e 100644 --- a/script/staking/UpgradeToWIMXV2.t.sol +++ b/script/staking/UpgradeToWIMXV2.t.sol @@ -33,40 +33,12 @@ interface IDeployer { returns (address deployedAddress_); } -// Args needed for compex deployment using CREATE3 and a TimelockController -struct ComplexDeploymentArgs { - address signer; - address factory; - string salt; -} -struct ComplexStakeHolderContractArgs { - address distributeAdmin; - address token; -} -struct ComplexTimelockContractArgs { - uint256 timeDelayInSeconds; - address proposerAdmin; - address executorAdmin; -} - - -// Args needed for simple deployment -struct SimpleDeploymentArgs { - address deployer; -} -struct SimpleStakeHolderContractArgs { - address roleAdmin; - address upgradeAdmin; - address distributeAdmin; - address token; -} - - - /** * @notice Deployment script and test code for the deployment script. - * @dev testDeploy is the test. - * @dev deploy() is the function the script should call. + * @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 { From c95c8bee13cba08681fa37d0613957ef8f70872b Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Mon, 25 Aug 2025 15:18:58 +1000 Subject: [PATCH 5/7] Change the way to generate a zero return length revert --- test/staking/StakeHolderAttackWallet2.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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); From a89fb2c06ab65cb1d24f6fb8ba334ba5fba82998 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Wed, 27 Aug 2025 14:09:05 +1000 Subject: [PATCH 6/7] Remove white space --- script/staking/common.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/script/staking/common.sh b/script/staking/common.sh index 2312ec0b..24fdadab 100644 --- a/script/staking/common.sh +++ b/script/staking/common.sh @@ -50,7 +50,6 @@ if [ -z "${STAKEHOLDER_TYPE}" ]; then fi if [ "$STAKEHOLDER_TYPE" = "ANY" ]; then # "script" must be specified by the outer script. - else if [ "$STAKEHOLDER_TYPE" = "ERC20" ]; then script=script/staking/StakeHolderScriptERC20.t.sol:StakeHolderScriptERC20 From 186e6ceaaefe360c2f7d2b3b6245b21fb602d898 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Wed, 27 Aug 2025 14:10:51 +1000 Subject: [PATCH 7/7] Add echo --- script/staking/common.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/script/staking/common.sh b/script/staking/common.sh index 24fdadab..deab344b 100644 --- a/script/staking/common.sh +++ b/script/staking/common.sh @@ -50,6 +50,7 @@ if [ -z "${STAKEHOLDER_TYPE}" ]; then fi if [ "$STAKEHOLDER_TYPE" = "ANY" ]; then # "script" must be specified by the outer script. + echo . else if [ "$STAKEHOLDER_TYPE" = "ERC20" ]; then script=script/staking/StakeHolderScriptERC20.t.sol:StakeHolderScriptERC20