Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions script/staking/ChangeDistributor.t.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
139 changes: 1 addition & 138 deletions script/staking/StakeHolderScriptWIMX.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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");
}
}
Loading
Loading