-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathChangeOwnerToTimelock.s.sol
More file actions
153 lines (136 loc) · 5.35 KB
/
ChangeOwnerToTimelock.s.sol
File metadata and controls
153 lines (136 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import {Script, console} from "lib/forge-std/src/Script.sol";
import {HelperConfig} from "../HelperConfig.s.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
/// @title ChangeOwnerToTimelock (Split Architecture)
/// @notice Deploys a TimelockController and transfers ownership of the shared
/// ProxyAdmin to it, gating contract upgrades behind a time delay.
/// @dev Only the ProxyAdmin is transferred to the timelock. The DEFAULT_ADMIN_ROLE
/// on individual contracts (CollateralManagement, PegIn, PegOut, FlyoverDiscovery)
/// is intentionally left unchanged so that time-sensitive operations like
/// emergency pause can be executed immediately.
/// Proposers and executors are read from timelock-roles.json, falling back
/// to the single-address config values if the file is not available.
contract ChangeOwnerToTimelock is Script {
error NoProposersConfigured();
error NoExecutorsConfigured();
error ProxyAdminAddressNotProvided();
error ProxyAdminOwnerTransferFailed();
function run() external {
HelperConfig helper = new HelperConfig();
HelperConfig.FlyoverConfig memory cfg = helper.getFlyoverConfig();
uint256 deployerKey = helper.getDeployerPrivateKey();
vm.rememberKey(deployerKey);
address proxyAdminAddress = vm.envAddress("PROXY_ADMIN");
if (proxyAdminAddress == address(0)) {
revert ProxyAdminAddressNotProvided();
}
(address[] memory proposers, address[] memory executors) = _readRoles(
cfg
);
vm.startBroadcast(deployerKey);
TimelockController timelock = execute(
proxyAdminAddress,
cfg.timelockMinDelay,
proposers,
executors,
cfg.timelockAdmin
);
vm.stopBroadcast();
_logFinalState(proxyAdminAddress, timelock, proposers, executors);
}
/// @notice Core logic: deploys a TimelockController and transfers ProxyAdmin
/// ownership to it. No console.log calls -- safe for broadcast.
function execute(
address proxyAdminAddress,
uint256 minDelay,
address[] memory proposers,
address[] memory executors,
address admin
) public returns (TimelockController) {
if (proposers.length == 0) {
revert NoProposersConfigured();
}
if (executors.length == 0) {
revert NoExecutorsConfigured();
}
TimelockController timelock = new TimelockController(
minDelay,
proposers,
executors,
admin
);
ProxyAdmin proxyAdmin = ProxyAdmin(proxyAdminAddress);
if (proxyAdmin.owner() != address(timelock)) {
proxyAdmin.transferOwnership(address(timelock));
if (proxyAdmin.owner() != address(timelock)) {
revert ProxyAdminOwnerTransferFailed();
}
}
return timelock;
}
function _readRoles(
HelperConfig.FlyoverConfig memory cfg
)
internal
view
returns (address[] memory proposers, address[] memory executors)
{
string memory json = vm.readFile(
"script/deployment/timelock-roles.json"
);
string memory networkKey = _networkKey();
proposers = vm.parseJsonAddressArray(
json,
string.concat(".", networkKey, ".proposers")
);
executors = vm.parseJsonAddressArray(
json,
string.concat(".", networkKey, ".executors")
);
if (proposers.length == 0 && cfg.timelockProposer != address(0)) {
proposers = new address[](1);
proposers[0] = cfg.timelockProposer;
}
if (executors.length == 0 && cfg.timelockExecutor != address(0)) {
executors = new address[](1);
executors[0] = cfg.timelockExecutor;
}
}
function _networkKey() internal view returns (string memory) {
uint256 chainId = block.chainid;
if (chainId == 30) return "rskMainnet";
if (chainId == 31) return "rskTestnet";
return "rskRegtest";
}
function _logFinalState(
address proxyAdminAddress,
TimelockController timelock,
address[] memory proposers,
address[] memory executors
) internal view {
console.log("=== Timelock ownership setup complete ===");
console.log("Timelock:", address(timelock));
console.log("Timelock minDelay:", timelock.getMinDelay());
console.log("ProxyAdmin:", proxyAdminAddress);
console.log("ProxyAdmin owner:", ProxyAdmin(proxyAdminAddress).owner());
bytes32 proposerRole = timelock.PROPOSER_ROLE();
bytes32 executorRole = timelock.EXECUTOR_ROLE();
for (uint256 i = 0; i < proposers.length; i++) {
console.log(
"Proposer:",
proposers[i],
timelock.hasRole(proposerRole, proposers[i])
);
}
for (uint256 i = 0; i < executors.length; i++) {
console.log(
"Executor:",
executors[i],
timelock.hasRole(executorRole, executors[i])
);
}
}
}