|
| 1 | +// Copyright (c) Immutable Pty Ltd 2018 - 2023 |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | +pragma solidity ^0.8.20; |
| 4 | + |
| 5 | +import "forge-std/Test.sol"; |
| 6 | +import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Proxy.sol"; |
| 7 | +import {TimelockController} from "openzeppelin-contracts-4.9.3/governance/TimelockController.sol"; |
| 8 | +import {IERC20} from "openzeppelin-contracts-4.9.3/token/ERC20/IERC20.sol"; |
| 9 | +import {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/proxy/utils/UUPSUpgradeable.sol"; |
| 10 | +import {IAccessControlUpgradeable} from "openzeppelin-contracts-upgradeable-4.9.3/access/IAccessControlUpgradeable.sol"; |
| 11 | + |
| 12 | +import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol"; |
| 13 | +import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol"; |
| 14 | +import {StakeHolderWIMXV2} from "../../contracts/staking/StakeHolderWIMXV2.sol"; |
| 15 | +import {WIMX} from "../../contracts/staking/WIMX.sol"; |
| 16 | +import {OwnableCreate3Deployer} from "../../contracts/deployer/create3/OwnableCreate3Deployer.sol"; |
| 17 | + |
| 18 | + |
| 19 | +/** |
| 20 | + * @notice Script for proposing and executing changes to which account has distributor role. |
| 21 | + * @dev testDeploy is the test. |
| 22 | + * @dev proposeChangeDistributor and executeChangeDistributor() are the functions the script should call. |
| 23 | + * For more details on deployment see ../../contracts/staking/README.md |
| 24 | + */ |
| 25 | +contract ChangeDistributor is Test { |
| 26 | + // Values that are the same on Testnet and Mainnet |
| 27 | + // Timelock controller proposer. |
| 28 | + bytes32 constant PROPOSER_ROLE = 0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1; |
| 29 | + // Timelock controller executor. |
| 30 | + bytes32 constant EXECUTOR_ROLE = 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63; |
| 31 | + // StakeHolder distributor. |
| 32 | + bytes32 private constant DISTRIBUTOR_ROLE = 0x444953545249425554455f524f4c450000000000000000000000000000000000; |
| 33 | + // Timelock controller contract. |
| 34 | + address constant TIMELOCK_CONTROLLER = 0x994a66607f947A47F33C2fA80e0470C03C30e289; |
| 35 | + // EIP1697 proxy. |
| 36 | + address constant STAKE_HOLDER_PROXY = 0xb6c2aA8690C8Ab6AC380a0bb798Ab0debe5C4C38; |
| 37 | + // Deployer of contracts and initial configuration. |
| 38 | + address constant DEPLOYER_ADDRESS = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333; |
| 39 | + // Ownable create3 factory used to deploy contracts. |
| 40 | + address constant OWNABLE_CREATE3_FACTORY = 0x37a59A845Bb6eD2034098af8738fbFFB9D589610; |
| 41 | + // One week time delay. |
| 42 | + uint256 constant TIMELOCK_DELAY = 604800; |
| 43 | + // Address configured for distributor when the stakeholder contracts were deployed. |
| 44 | + address private constant OLD_DISTRIBUTOR = DEPLOYER_ADDRESS; |
| 45 | + |
| 46 | + // Values that are different on Testnet and Mainnet |
| 47 | + // On mainnet Proposer and Execute are a GnosisSafeProxy. |
| 48 | + address constant MAINNET_PROPOSER = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; |
| 49 | + address constant MAINNET_EXECUTOR = 0xaA53161A1fD22b258c89bA76B4bA11019034612D; |
| 50 | + // On testnet Proposer and Execute are the deployer address. |
| 51 | + address constant TESTNET_PROPOSER = DEPLOYER_ADDRESS; |
| 52 | + address constant TESTNET_EXECUTOR = DEPLOYER_ADDRESS; |
| 53 | + // Distrubtor accounts |
| 54 | + address constant TESTNET_NEW_DISTRIBUTOR = 0x8BA97cE2C64E2d1b9826bb6aB5e288524873f63D; |
| 55 | + address constant MAINNET_NEW_DISTRIBUTOR = 0xAd34133D4EA0c6F0a98FdE5FA2c668E12062C33D; |
| 56 | + |
| 57 | + // Used for fork testing |
| 58 | + string constant MAINNET_RPC_URL = "https://rpc.immutable.com/"; |
| 59 | + |
| 60 | + TimelockController stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); |
| 61 | + |
| 62 | + |
| 63 | + function proposeChangeDistributor() external { |
| 64 | + uint256 isMainnet = vm.envUint("IMMUTABLE_NETWORK"); |
| 65 | + address newDistributor = (isMainnet == 1) ? MAINNET_NEW_DISTRIBUTOR : TESTNET_NEW_DISTRIBUTOR; |
| 66 | + address proposer = (isMainnet == 1) ? MAINNET_PROPOSER : TESTNET_PROPOSER; |
| 67 | + _proposeChangeDistributor(proposer, newDistributor); |
| 68 | + } |
| 69 | + |
| 70 | + function executeChangeDistributor() external { |
| 71 | + uint256 isMainnet = vm.envUint("IMMUTABLE_NETWORK"); |
| 72 | + address newDistributor = (isMainnet == 1) ? MAINNET_NEW_DISTRIBUTOR : TESTNET_NEW_DISTRIBUTOR; |
| 73 | + address executor = (isMainnet == 1) ? MAINNET_EXECUTOR : TESTNET_EXECUTOR; |
| 74 | + _executeChangeDistributor(executor, newDistributor); |
| 75 | + } |
| 76 | + |
| 77 | + function _proposeChangeDistributor(address _proposer, address _newDistributor) internal { |
| 78 | + assertTrue(stakeHolderTimeDelay.hasRole(PROPOSER_ROLE, _proposer), "Proposer does not have proposer role"); |
| 79 | + |
| 80 | + (address[] memory targets, uint256[] memory values, bytes[] memory data, |
| 81 | + bytes32 predecessor, bytes32 salt) = |
| 82 | + _getChangeDistributorProposalParams(OLD_DISTRIBUTOR, _newDistributor); |
| 83 | + |
| 84 | + vm.startBroadcast(_proposer); |
| 85 | + stakeHolderTimeDelay.scheduleBatch(targets, values, data, predecessor, salt, TIMELOCK_DELAY); |
| 86 | + vm.stopBroadcast(); |
| 87 | + } |
| 88 | + |
| 89 | + function _executeChangeDistributor(address _executor, address _newDistributor) internal { |
| 90 | + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); |
| 91 | + assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, _executor), "Executor does not have executor role"); |
| 92 | + |
| 93 | + (address[] memory targets, uint256[] memory values, bytes[] memory data, |
| 94 | + bytes32 predecessor, bytes32 salt) = |
| 95 | + _getChangeDistributorProposalParams(OLD_DISTRIBUTOR, _newDistributor); |
| 96 | + |
| 97 | + bytes32 id = stakeHolderTimeDelay.hashOperationBatch(targets, values, data, predecessor, salt); |
| 98 | + assertTrue(stakeHolderTimeDelay.isOperationReady(id), "Operation is not yet ready"); |
| 99 | + |
| 100 | + vm.startBroadcast(_executor); |
| 101 | + stakeHolderTimeDelay.executeBatch(targets, values, data, predecessor, salt); |
| 102 | + vm.stopBroadcast(); |
| 103 | + } |
| 104 | + |
| 105 | + function _getChangeDistributorProposalParams(address _oldAccount, address _newAccount) private returns ( |
| 106 | + address[] memory targets, uint256[] memory values, bytes[] memory data, bytes32 predecessor, bytes32 salt) { |
| 107 | + |
| 108 | + stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER)); |
| 109 | + |
| 110 | + bytes memory callData0 = abi.encodeWithSelector( |
| 111 | + IAccessControlUpgradeable.revokeRole.selector, |
| 112 | + DISTRIBUTOR_ROLE, |
| 113 | + _oldAccount); |
| 114 | + bytes memory callData1 = abi.encodeWithSelector( |
| 115 | + IAccessControlUpgradeable.grantRole.selector, |
| 116 | + DISTRIBUTOR_ROLE, |
| 117 | + _newAccount); |
| 118 | + |
| 119 | + targets = new address[](2); |
| 120 | + values = new uint256[](2); |
| 121 | + data = new bytes[](2); |
| 122 | + targets[0] = STAKE_HOLDER_PROXY; |
| 123 | + values[0] = 0; |
| 124 | + data[0] = callData0; |
| 125 | + targets[1] = STAKE_HOLDER_PROXY; |
| 126 | + values[1] = 0; |
| 127 | + data[1] = callData1; |
| 128 | + |
| 129 | + predecessor = bytes32(0); |
| 130 | + salt = bytes32(uint256(1)); |
| 131 | + } |
| 132 | + |
| 133 | + |
| 134 | + // Test the remainder of the upgrade process. |
| 135 | + function testRemainderChangeDistributor() public { |
| 136 | + uint256 mainnetFork = vm.createFork(MAINNET_RPC_URL); |
| 137 | + vm.selectFork(mainnetFork); |
| 138 | + |
| 139 | + IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY); |
| 140 | + if (!stakeHolder.hasRole(DISTRIBUTOR_ROLE, OLD_DISTRIBUTOR)) { |
| 141 | + // Change distributor has occurred. |
| 142 | + return; |
| 143 | + } |
| 144 | + |
| 145 | + (address[] memory targets, uint256[] memory values, bytes[] memory data, |
| 146 | + bytes32 predecessor, bytes32 salt) = |
| 147 | + _getChangeDistributorProposalParams(OLD_DISTRIBUTOR, MAINNET_NEW_DISTRIBUTOR); |
| 148 | + bytes32 id = stakeHolderTimeDelay.hashOperationBatch(targets, values, data, predecessor, salt); |
| 149 | + |
| 150 | + if (!stakeHolderTimeDelay.isOperation(id)) { |
| 151 | + _proposeChangeDistributor(MAINNET_PROPOSER, MAINNET_NEW_DISTRIBUTOR); |
| 152 | + } |
| 153 | + |
| 154 | + uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id); |
| 155 | + uint256 time = earliestExecuteTime; |
| 156 | + if (time < block.timestamp) { |
| 157 | + time = block.timestamp; |
| 158 | + } |
| 159 | + vm.warp(time); |
| 160 | + |
| 161 | + _executeChangeDistributor(MAINNET_EXECUTOR, MAINNET_NEW_DISTRIBUTOR); |
| 162 | + |
| 163 | + require(!stakeHolder.hasRole(DISTRIBUTOR_ROLE, OLD_DISTRIBUTOR), "Old distributor still has role"); |
| 164 | + require(stakeHolder.hasRole(DISTRIBUTOR_ROLE, MAINNET_NEW_DISTRIBUTOR), "New distributor does not have role"); |
| 165 | + } |
| 166 | +} |
0 commit comments