-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
Artifacts.s.sol
328 lines (292 loc) · 15.3 KB
/
Artifacts.s.sol
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { console2 as console } from "forge-std/console2.sol";
import { stdJson } from "forge-std/StdJson.sol";
import { Vm } from "forge-std/Vm.sol";
import { VmSafe } from "forge-std/Vm.sol";
import { Executables } from "scripts/libraries/Executables.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Config } from "scripts/libraries/Config.sol";
import { StorageSlot } from "scripts/libraries/ForgeArtifacts.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { LibString } from "@solady/utils/LibString.sol";
import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol";
import { IAddressManager } from "scripts/interfaces/IAddressManager.sol";
import { Process } from "scripts/libraries/Process.sol";
/// @notice Represents a deployment. Is serialized to JSON as a key/value
/// pair. Can be accessed from within scripts.
struct Deployment {
string name;
address payable addr;
}
/// @title Artifacts
/// @notice Useful for accessing deployment artifacts from within scripts.
/// When a contract is deployed, call the `save` function to write its name and
/// contract address to disk. Inspired by `forge-deploy`.
/// @dev This uses the DeploymentRegistry contract, to load/store deployments,
/// without broadcasting the interactions with the DeploymentRegistry.
/// The DeploymentRegistry can be overridden, to load/store deployments in a sub-context, without file IO.
abstract contract Artifacts {
/// @notice Foundry cheatcode VM.
Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
Artifacts public constant deploymentRegistry =
Artifacts(address(uint160(uint256(keccak256("optimism.deploymentregistry")))));
function setUp() public virtual {
if (!vm.envBool("DISABLE_DEPLOYMENT_REGISTRY")) {
vm.etch(address(deploymentRegistry), vm.getDeployedCode("Artifacts.s.sol:DeploymentRegistry"));
vm.label(address(deploymentRegistry), "Artifacts");
vm.allowCheatcodes(address(deploymentRegistry));
deploymentRegistry.setUp();
}
}
/// @notice Modifier that wraps a function to temporarily not broadcast.
/// Note that view() functions do not need this modifier, as static-call does not get broadcast.
modifier noBroadcast() {
VmSafe.CallerMode callerMode;
address msgSender;
(callerMode, msgSender,) = vm.readCallers();
// If we were broadcasting, stop it.
if (callerMode == VmSafe.CallerMode.RecurrentBroadcast || callerMode == VmSafe.CallerMode.Broadcast) {
vm.stopBroadcast();
}
// execute the function
_;
// Now recover the broadcasting state, if any
if (callerMode == VmSafe.CallerMode.RecurrentBroadcast) {
vm.startBroadcast(msgSender);
}
if (callerMode == VmSafe.CallerMode.Broadcast) {
vm.broadcast(msgSender);
}
}
/// @notice Returns all of the deployments done in the current context.
function newDeployments() external view returns (Deployment[] memory) {
return deploymentRegistry.newDeployments();
}
/// @notice Returns whether or not a particular deployment exists.
/// @param _name The name of the deployment.
/// @return out_ Whether the deployment exists or not.
function has(string memory _name) public view returns (bool) {
return deploymentRegistry.has(_name);
}
/// @notice Returns the address of a deployment. Also handles the predeploys.
/// @param _name The name of the deployment.
/// @return out_ The address of the deployment. May be `address(0)` if the deployment does not
/// exist.
function getAddress(string memory _name) public view returns (address payable) {
return deploymentRegistry.getAddress(_name);
}
/// @notice Returns the address of a deployment and reverts if the deployment
/// does not exist.
/// @return out_ The address of the deployment.
function mustGetAddress(string memory _name) public view returns (address payable) {
return deploymentRegistry.mustGetAddress(_name);
}
/// @notice Returns a deployment that is suitable to be used to interact with contracts.
/// @param _name The name of the deployment.
/// @return out_ The deployment.
function get(string memory _name) public view returns (Deployment memory) {
return deploymentRegistry.get(_name);
}
/// @notice Appends a deployment to disk as a JSON deploy artifact.
/// @param _name The name of the deployment.
/// @param _deployed The address of the deployment.
function save(string memory _name, address _deployed) public noBroadcast {
deploymentRegistry.save(_name, _deployed);
}
/// @notice Stubs a deployment retrieved through `get`.
/// @param _name The name of the deployment.
/// @param _addr The mock address of the deployment.
function prankDeployment(string memory _name, address _addr) public noBroadcast {
deploymentRegistry.prankDeployment(_name, _addr);
}
/// @notice calls DeploymentRegistry.loadInitializedSlot
function loadInitializedSlot(string memory _contractName) public noBroadcast returns (uint8 initialized_) {
initialized_ = deploymentRegistry.loadInitializedSlot(_contractName);
}
}
/// @title DeploymentRegistry
/// @notice The legacy Artifacts functionality, encapsulated as its own concrete contract account.
contract DeploymentRegistry {
/// @notice Foundry cheatcode VM.
Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
/// @notice Error for when attempting to fetch a deployment and it does not exist
error DeploymentDoesNotExist(string);
/// @notice Error for when trying to save an invalid deployment
error InvalidDeployment(string);
/// @notice The set of deployments that have been done during execution.
mapping(string => Deployment) internal _namedDeployments;
/// @notice The same as `_namedDeployments` but as an array.
Deployment[] internal _newDeployments;
/// @notice The path to the deployment artifact that is being written to.
string internal deploymentOutfile;
/// @notice Setup function.
function setUp() public {
deploymentOutfile = Config.deploymentOutfile();
console.log("Writing artifact to %s", deploymentOutfile);
ForgeArtifacts.ensurePath(deploymentOutfile);
uint256 chainId = Config.chainID();
console.log("Connected to network with chainid %s", chainId);
// Load addresses from a JSON file if the CONTRACT_ADDRESSES_PATH environment variable
// is set. Great for loading addresses from `superchain-registry`.
string memory addresses = Config.contractAddressesPath();
if (bytes(addresses).length > 0) {
console.log("Loading addresses from %s", addresses);
_loadAddresses(addresses);
}
}
/// @notice Populates the addresses to be used in a script based on a JSON file.
/// The format of the JSON file is the same that it output by this script
/// as well as the JSON files that contain addresses in the `superchain-registry`
/// repo. The JSON key is the name of the contract and the value is an address.
function _loadAddresses(string memory _path) internal {
string[] memory commands = new string[](3);
commands[0] = "bash";
commands[1] = "-c";
commands[2] = string.concat("jq -cr < ", _path);
string memory json = string(Process.run(commands));
string[] memory keys = vm.parseJsonKeys(json, "");
for (uint256 i; i < keys.length; i++) {
string memory key = keys[i];
address addr = stdJson.readAddress(json, string.concat("$.", key));
save(key, addr);
}
}
/// @notice Returns all of the deployments done in the current context.
function newDeployments() external view returns (Deployment[] memory) {
return _newDeployments;
}
/// @notice Returns whether or not a particular deployment exists.
/// @param _name The name of the deployment.
/// @return Whether the deployment exists or not.
function has(string memory _name) public view returns (bool) {
Deployment memory existing = _namedDeployments[_name];
return bytes(existing.name).length > 0;
}
/// @notice Returns the address of a deployment. Also handles the predeploys.
/// @param _name The name of the deployment.
/// @return The address of the deployment. May be `address(0)` if the deployment does not
/// exist.
function getAddress(string memory _name) public view returns (address payable) {
Deployment memory existing = _namedDeployments[_name];
if (existing.addr != address(0)) {
if (bytes(existing.name).length == 0) {
return payable(address(0));
}
return existing.addr;
}
bytes32 digest = keccak256(bytes(_name));
if (digest == keccak256(bytes("L2CrossDomainMessenger"))) {
return payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER);
} else if (digest == keccak256(bytes("L2ToL1MessagePasser"))) {
return payable(Predeploys.L2_TO_L1_MESSAGE_PASSER);
} else if (digest == keccak256(bytes("L2StandardBridge"))) {
return payable(Predeploys.L2_STANDARD_BRIDGE);
} else if (digest == keccak256(bytes("L2StandardBridgeInterop"))) {
return payable(Predeploys.L2_STANDARD_BRIDGE);
} else if (digest == keccak256(bytes("L2ERC721Bridge"))) {
return payable(Predeploys.L2_ERC721_BRIDGE);
} else if (digest == keccak256(bytes("SequencerFeeWallet"))) {
return payable(Predeploys.SEQUENCER_FEE_WALLET);
} else if (digest == keccak256(bytes("OptimismMintableERC20Factory"))) {
return payable(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY);
} else if (digest == keccak256(bytes("OptimismMintableERC721Factory"))) {
return payable(Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY);
} else if (digest == keccak256(bytes("L1Block"))) {
return payable(Predeploys.L1_BLOCK_ATTRIBUTES);
} else if (digest == keccak256(bytes("GasPriceOracle"))) {
return payable(Predeploys.GAS_PRICE_ORACLE);
} else if (digest == keccak256(bytes("L1MessageSender"))) {
return payable(Predeploys.L1_MESSAGE_SENDER);
} else if (digest == keccak256(bytes("DeployerWhitelist"))) {
return payable(Predeploys.DEPLOYER_WHITELIST);
} else if (digest == keccak256(bytes("WETH"))) {
return payable(Predeploys.WETH);
} else if (digest == keccak256(bytes("LegacyERC20ETH"))) {
return payable(Predeploys.LEGACY_ERC20_ETH);
} else if (digest == keccak256(bytes("L1BlockNumber"))) {
return payable(Predeploys.L1_BLOCK_NUMBER);
} else if (digest == keccak256(bytes("LegacyMessagePasser"))) {
return payable(Predeploys.LEGACY_MESSAGE_PASSER);
} else if (digest == keccak256(bytes("ProxyAdmin"))) {
return payable(Predeploys.PROXY_ADMIN);
} else if (digest == keccak256(bytes("BaseFeeVault"))) {
return payable(Predeploys.BASE_FEE_VAULT);
} else if (digest == keccak256(bytes("L1FeeVault"))) {
return payable(Predeploys.L1_FEE_VAULT);
} else if (digest == keccak256(bytes("GovernanceToken"))) {
return payable(Predeploys.GOVERNANCE_TOKEN);
} else if (digest == keccak256(bytes("SchemaRegistry"))) {
return payable(Predeploys.SCHEMA_REGISTRY);
} else if (digest == keccak256(bytes("EAS"))) {
return payable(Predeploys.EAS);
}
return payable(address(0));
}
/// @notice Returns the address of a deployment and reverts if the deployment
/// does not exist.
/// @return The address of the deployment.
function mustGetAddress(string memory _name) public view returns (address payable) {
address addr = getAddress(_name);
if (addr == address(0)) {
revert DeploymentDoesNotExist(_name);
}
return payable(addr);
}
/// @notice Returns a deployment that is suitable to be used to interact with contracts.
/// @param _name The name of the deployment.
/// @return The deployment.
function get(string memory _name) public view returns (Deployment memory) {
return _namedDeployments[_name];
}
/// @notice Appends a deployment to disk as a JSON deploy artifact.
/// @param _name The name of the deployment.
/// @param _deployed The address of the deployment.
function save(string memory _name, address _deployed) public {
if (bytes(_name).length == 0) {
revert InvalidDeployment("EmptyName");
}
if (bytes(_namedDeployments[_name].name).length > 0) {
revert InvalidDeployment("AlreadyExists");
}
console.log("Saving %s: %s", _name, _deployed);
Deployment memory deployment = Deployment({ name: _name, addr: payable(_deployed) });
_namedDeployments[_name] = deployment;
_newDeployments.push(deployment);
_appendDeployment(_name, _deployed);
}
/// @notice Adds a deployment to the temp deployments file
function _appendDeployment(string memory _name, address _deployed) internal {
vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: deploymentOutfile });
}
/// @notice Stubs a deployment retrieved through `get`.
/// @param _name The name of the deployment.
/// @param _addr The mock address of the deployment.
function prankDeployment(string memory _name, address _addr) public {
if (bytes(_name).length == 0) {
revert InvalidDeployment("EmptyName");
}
Deployment memory deployment = Deployment({ name: _name, addr: payable(_addr) });
_namedDeployments[_name] = deployment;
}
/// @notice Returns the value of the internal `_initialized` storage slot for a given contract.
function loadInitializedSlot(string memory _contractName) public returns (uint8 initialized_) {
address contractAddress;
// Check if the contract name ends with `Proxy` and, if so, get the implementation address
if (LibString.endsWith(_contractName, "Proxy")) {
contractAddress = EIP1967Helper.getImplementation(getAddress(_contractName));
_contractName = LibString.slice(_contractName, 0, bytes(_contractName).length - 5);
// If the EIP1967 implementation address is 0, we try to get the implementation address from legacy
// AddressManager, which would work if the proxy is ResolvedDelegateProxy like L1CrossDomainMessengerProxy.
if (contractAddress == address(0)) {
contractAddress =
IAddressManager(mustGetAddress("AddressManager")).getAddress(string.concat("OVM_", _contractName));
}
} else {
contractAddress = mustGetAddress(_contractName);
}
StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(_contractName);
bytes32 slotVal = vm.load(contractAddress, bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF);
}
}