Skip to content

Commit 65fb800

Browse files
authored
Staking system: Change account with distributor role (#283)
1 parent 4199887 commit 65fb800

10 files changed

+403
-152
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
}

script/staking/StakeHolderScriptWIMX.t.sol

Lines changed: 1 addition & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {ERC1967Proxy} from "openzeppelin-contracts-4.9.3/proxy/ERC1967/ERC1967Pr
77
import {TimelockController} from "openzeppelin-contracts-4.9.3/governance/TimelockController.sol";
88
import {IERC20} from "openzeppelin-contracts-4.9.3/token/ERC20/IERC20.sol";
99
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";
1011

1112
import {IStakeHolder} from "../../contracts/staking/IStakeHolder.sol";
1213
import {StakeHolderBase} from "../../contracts/staking/StakeHolderBase.sol";
@@ -383,142 +384,4 @@ contract StakeHolderScriptWIMX is Test {
383384
assertEq(user1.balance, 97 ether, "User1 balance after unstake");
384385
assertEq(erc20.balanceOf(address(_stakeHolder)), 3 ether, "StakeHolder balance after unstake");
385386
}
386-
387-
388-
// *********************** UPGRADE TO V2 ***************************
389-
390-
string constant MAINNET_RPC_URL = "https://rpc.immutable.com/";
391-
address constant STAKE_HOLDER_PROXY = 0xb6c2aA8690C8Ab6AC380a0bb798Ab0debe5C4C38;
392-
address constant TIMELOCK_CONTROLLER = 0x994a66607f947A47F33C2fA80e0470C03C30e289;
393-
bytes32 constant PROPOSER_ROLE = 0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1;
394-
bytes32 constant EXECUTOR_ROLE = 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63;
395-
396-
// On mainnet Proposer and Execute are a GnosisSafeProxy.
397-
address constant PROPOSER = 0xaA53161A1fD22b258c89bA76B4bA11019034612D;
398-
address constant EXECUTOR = 0xaA53161A1fD22b258c89bA76B4bA11019034612D;
399-
uint256 constant TIMELOCK_DELAY = 604800;
400-
401-
address constant DEPLOYER_ADDRESS = 0xdDA0d9448Ebe3eA43aFecE5Fa6401F5795c19333;
402-
address constant OWNABLE_CREATE3_FACTORY = 0x37a59A845Bb6eD2034098af8738fbFFB9D589610;
403-
404-
address constant STAKE_HOLDER_V2 = address(0x0);
405-
406-
TimelockController stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER));
407-
408-
409-
function deployV2() external {
410-
address stakeHolderV2 = _deployV2();
411-
console.log("Deployed StakeHolderWIMXV2 to: %s", stakeHolderV2);
412-
}
413-
414-
function proposeUpgradeToV2() external {
415-
_proposeUpgradeToV2(STAKE_HOLDER_V2);
416-
}
417-
418-
function executeUpgradeToV2() external {
419-
_executeUpgradeToV2(STAKE_HOLDER_V2);
420-
}
421-
422-
function _deployV2() internal returns (address) {
423-
bytes32 salt = bytes32(uint256(17));
424-
425-
IDeployer ownableCreate3 = IDeployer(OWNABLE_CREATE3_FACTORY);
426-
427-
// Deploy StakeHolderWIMXV2 via the Ownable Create3 factory.
428-
// Create deployment bytecode and encode constructor args
429-
bytes memory deploymentBytecode = abi.encodePacked(
430-
type(StakeHolderWIMXV2).creationCode
431-
);
432-
/// @dev Deploy the contract via the Ownable CREATE3 factory
433-
vm.startBroadcast(DEPLOYER_ADDRESS);
434-
address stakeHolderImplAddress = ownableCreate3.deploy(deploymentBytecode, salt);
435-
vm.stopBroadcast();
436-
return stakeHolderImplAddress;
437-
}
438-
439-
function _proposeUpgradeToV2(address _v2Impl) internal {
440-
assertTrue(stakeHolderTimeDelay.hasRole(PROPOSER_ROLE, PROPOSER), "Proposer does not have proposer role");
441-
assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, EXECUTOR), "Executor does not have executor role");
442-
443-
(address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) =
444-
_getProposalParams(_v2Impl);
445-
446-
vm.startBroadcast(PROPOSER);
447-
stakeHolderTimeDelay.schedule(target, value, data, predecessor, salt, TIMELOCK_DELAY);
448-
vm.stopBroadcast();
449-
}
450-
451-
function _executeUpgradeToV2(address _v2Impl) internal {
452-
stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER));
453-
assertTrue(stakeHolderTimeDelay.hasRole(EXECUTOR_ROLE, EXECUTOR), "Executor does not have executor role");
454-
455-
(address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) =
456-
_getProposalParams(_v2Impl);
457-
458-
bytes32 id = stakeHolderTimeDelay.hashOperation(target, value, data, predecessor, salt);
459-
assertTrue(stakeHolderTimeDelay.isOperationReady(id), "Operation is not yet ready");
460-
461-
vm.startBroadcast(EXECUTOR);
462-
stakeHolderTimeDelay.execute(target, value, data, predecessor, salt);
463-
vm.stopBroadcast();
464-
465-
IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY);
466-
assertEq(stakeHolder.version(), 2, "Upgrade did not upgrade to version 2");
467-
}
468-
469-
function _getProposalParams(address _v2Impl) private returns (
470-
address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) {
471-
472-
stakeHolderTimeDelay = TimelockController(payable(TIMELOCK_CONTROLLER));
473-
assertNotEq(_v2Impl, address(0), "StakeHolderV2 can not be address(0)");
474-
475-
bytes memory callData = abi.encodeWithSelector(StakeHolderBase.upgradeStorage.selector, bytes(""));
476-
bytes memory upgradeCall = abi.encodeWithSelector(
477-
UUPSUpgradeable.upgradeToAndCall.selector, _v2Impl, callData);
478-
479-
target = STAKE_HOLDER_PROXY;
480-
value = 0;
481-
data = upgradeCall;
482-
predecessor = bytes32(0);
483-
salt = bytes32(uint256(1));
484-
}
485-
486-
487-
// Test the remainder of the upgrade process.
488-
function testRemainderOfUpgradeProcessToV2() public {
489-
uint256 mainnetFork = vm.createFork(MAINNET_RPC_URL);
490-
vm.selectFork(mainnetFork);
491-
492-
IStakeHolder stakeHolder = IStakeHolder(STAKE_HOLDER_PROXY);
493-
if (stakeHolder.version() != 0) {
494-
// Upgrade has occurred. Nothing to test.
495-
return;
496-
}
497-
498-
address stakeHolderV2 = STAKE_HOLDER_V2;
499-
if (stakeHolderV2 == address(0)) {
500-
// StakeHolderWIMXV2 has not been deployed yet.
501-
stakeHolderV2 = _deployV2();
502-
}
503-
504-
(address target, uint256 value, bytes memory data, bytes32 predecessor, bytes32 salt) =
505-
_getProposalParams(stakeHolderV2);
506-
bytes32 id = stakeHolderTimeDelay.hashOperation(target, value, data, predecessor, salt);
507-
if (!stakeHolderTimeDelay.isOperation(id)) {
508-
// The upgrade hasn't been proposed yet.
509-
_proposeUpgradeToV2(stakeHolderV2);
510-
}
511-
512-
uint256 earliestExecuteTime = stakeHolderTimeDelay.getTimestamp(id);
513-
uint256 time = earliestExecuteTime;
514-
if (time < block.timestamp) {
515-
time = block.timestamp;
516-
}
517-
vm.warp(time);
518-
519-
uint256 numStakersBefore = stakeHolder.getNumStakers();
520-
_executeUpgradeToV2(stakeHolderV2);
521-
uint256 numStakersAfter = stakeHolder.getNumStakers();
522-
assertEq(numStakersBefore, numStakersAfter, "Number of stakers before and after upgrade do not match");
523-
}
524387
}

0 commit comments

Comments
 (0)