github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/scripts/Artifacts.s.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity ^0.8.0; 3 4 import { console2 as console } from "forge-std/console2.sol"; 5 import { stdJson } from "forge-std/StdJson.sol"; 6 import { Vm } from "forge-std/Vm.sol"; 7 import { Executables } from "scripts/Executables.sol"; 8 import { Predeploys } from "src/libraries/Predeploys.sol"; 9 import { Config } from "scripts/Config.sol"; 10 import { StorageSlot } from "scripts/ForgeArtifacts.sol"; 11 import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; 12 import { LibString } from "solady/utils/LibString.sol"; 13 import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol"; 14 import { IAddressManager } from "scripts/interfaces/IAddressManager.sol"; 15 16 /// @notice Represents a deployment. Is serialized to JSON as a key/value 17 /// pair. Can be accessed from within scripts. 18 struct Deployment { 19 string name; 20 address payable addr; 21 } 22 23 /// @title Artifacts 24 /// @notice Useful for accessing deployment artifacts from within scripts. 25 /// When a contract is deployed, call the `save` function to write its name and 26 /// contract address to disk. Inspired by `forge-deploy`. 27 abstract contract Artifacts { 28 /// @notice Foundry cheatcode VM. 29 Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); 30 /// @notice Error for when attempting to fetch a deployment and it does not exist 31 32 error DeploymentDoesNotExist(string); 33 /// @notice Error for when trying to save an invalid deployment 34 error InvalidDeployment(string); 35 /// @notice The set of deployments that have been done during execution. 36 37 mapping(string => Deployment) internal _namedDeployments; 38 /// @notice The same as `_namedDeployments` but as an array. 39 Deployment[] internal _newDeployments; 40 /// @notice Path to the directory containing the hh deploy style artifacts 41 string internal deploymentsDir; 42 /// @notice The path to the deployment artifact that is being written to. 43 string internal deploymentOutfile; 44 /// @notice The namespace for the deployment. Can be set with the env var DEPLOYMENT_CONTEXT. 45 string internal deploymentContext; 46 47 /// @notice Setup function. The arguments here 48 function setUp() public virtual { 49 deploymentOutfile = Config.deploymentOutfile(); 50 console.log("Writing artifact to %s", deploymentOutfile); 51 ForgeArtifacts.ensurePath(deploymentOutfile); 52 53 uint256 chainId = Config.chainID(); 54 console.log("Connected to network with chainid %s", chainId); 55 56 // Load addresses from a JSON file if the CONTRACT_ADDRESSES_PATH environment variable 57 // is set. Great for loading addresses from `superchain-registry`. 58 string memory addresses = Config.contractAddressesPath(); 59 if (bytes(addresses).length > 0) { 60 console.log("Loading addresses from %s", addresses); 61 _loadAddresses(addresses); 62 } 63 } 64 65 /// @notice Populates the addresses to be used in a script based on a JSON file. 66 /// The format of the JSON file is the same that it output by this script 67 /// as well as the JSON files that contain addresses in the `superchain-registry` 68 /// repo. The JSON key is the name of the contract and the value is an address. 69 function _loadAddresses(string memory _path) internal { 70 string[] memory commands = new string[](3); 71 commands[0] = "bash"; 72 commands[1] = "-c"; 73 commands[2] = string.concat("jq -cr < ", _path); 74 string memory json = string(vm.ffi(commands)); 75 string[] memory keys = vm.parseJsonKeys(json, ""); 76 for (uint256 i; i < keys.length; i++) { 77 string memory key = keys[i]; 78 address addr = stdJson.readAddress(json, string.concat("$.", key)); 79 save(key, addr); 80 } 81 } 82 83 /// @notice Returns all of the deployments done in the current context. 84 function newDeployments() external view returns (Deployment[] memory) { 85 return _newDeployments; 86 } 87 88 /// @notice Returns whether or not a particular deployment exists. 89 /// @param _name The name of the deployment. 90 /// @return Whether the deployment exists or not. 91 function has(string memory _name) public view returns (bool) { 92 Deployment memory existing = _namedDeployments[_name]; 93 return bytes(existing.name).length > 0; 94 } 95 96 /// @notice Returns the address of a deployment. Also handles the predeploys. 97 /// @param _name The name of the deployment. 98 /// @return The address of the deployment. May be `address(0)` if the deployment does not 99 /// exist. 100 function getAddress(string memory _name) public view returns (address payable) { 101 Deployment memory existing = _namedDeployments[_name]; 102 if (existing.addr != address(0)) { 103 if (bytes(existing.name).length == 0) { 104 return payable(address(0)); 105 } 106 return existing.addr; 107 } 108 109 bytes32 digest = keccak256(bytes(_name)); 110 if (digest == keccak256(bytes("L2CrossDomainMessenger"))) { 111 return payable(Predeploys.L2_CROSS_DOMAIN_MESSENGER); 112 } else if (digest == keccak256(bytes("L2ToL1MessagePasser"))) { 113 return payable(Predeploys.L2_TO_L1_MESSAGE_PASSER); 114 } else if (digest == keccak256(bytes("L2StandardBridge"))) { 115 return payable(Predeploys.L2_STANDARD_BRIDGE); 116 } else if (digest == keccak256(bytes("L2ERC721Bridge"))) { 117 return payable(Predeploys.L2_ERC721_BRIDGE); 118 } else if (digest == keccak256(bytes("SequencerFeeWallet"))) { 119 return payable(Predeploys.SEQUENCER_FEE_WALLET); 120 } else if (digest == keccak256(bytes("OptimismMintableERC20Factory"))) { 121 return payable(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY); 122 } else if (digest == keccak256(bytes("OptimismMintableERC721Factory"))) { 123 return payable(Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY); 124 } else if (digest == keccak256(bytes("L1Block"))) { 125 return payable(Predeploys.L1_BLOCK_ATTRIBUTES); 126 } else if (digest == keccak256(bytes("GasPriceOracle"))) { 127 return payable(Predeploys.GAS_PRICE_ORACLE); 128 } else if (digest == keccak256(bytes("L1MessageSender"))) { 129 return payable(Predeploys.L1_MESSAGE_SENDER); 130 } else if (digest == keccak256(bytes("DeployerWhitelist"))) { 131 return payable(Predeploys.DEPLOYER_WHITELIST); 132 } else if (digest == keccak256(bytes("WETH9"))) { 133 return payable(Predeploys.WETH9); 134 } else if (digest == keccak256(bytes("LegacyERC20ETH"))) { 135 return payable(Predeploys.LEGACY_ERC20_ETH); 136 } else if (digest == keccak256(bytes("L1BlockNumber"))) { 137 return payable(Predeploys.L1_BLOCK_NUMBER); 138 } else if (digest == keccak256(bytes("LegacyMessagePasser"))) { 139 return payable(Predeploys.LEGACY_MESSAGE_PASSER); 140 } else if (digest == keccak256(bytes("ProxyAdmin"))) { 141 return payable(Predeploys.PROXY_ADMIN); 142 } else if (digest == keccak256(bytes("BaseFeeVault"))) { 143 return payable(Predeploys.BASE_FEE_VAULT); 144 } else if (digest == keccak256(bytes("L1FeeVault"))) { 145 return payable(Predeploys.L1_FEE_VAULT); 146 } else if (digest == keccak256(bytes("GovernanceToken"))) { 147 return payable(Predeploys.GOVERNANCE_TOKEN); 148 } else if (digest == keccak256(bytes("SchemaRegistry"))) { 149 return payable(Predeploys.SCHEMA_REGISTRY); 150 } else if (digest == keccak256(bytes("EAS"))) { 151 return payable(Predeploys.EAS); 152 } 153 return payable(address(0)); 154 } 155 156 /// @notice Returns the address of a deployment and reverts if the deployment 157 /// does not exist. 158 /// @return The address of the deployment. 159 function mustGetAddress(string memory _name) public view returns (address payable) { 160 address addr = getAddress(_name); 161 if (addr == address(0)) { 162 revert DeploymentDoesNotExist(_name); 163 } 164 return payable(addr); 165 } 166 167 /// @notice Returns a deployment that is suitable to be used to interact with contracts. 168 /// @param _name The name of the deployment. 169 /// @return The deployment. 170 function get(string memory _name) public view returns (Deployment memory) { 171 return _namedDeployments[_name]; 172 } 173 174 /// @notice Appends a deployment to disk as a JSON deploy artifact. 175 /// @param _name The name of the deployment. 176 /// @param _deployed The address of the deployment. 177 function save(string memory _name, address _deployed) public { 178 if (bytes(_name).length == 0) { 179 revert InvalidDeployment("EmptyName"); 180 } 181 if (bytes(_namedDeployments[_name].name).length > 0) { 182 revert InvalidDeployment("AlreadyExists"); 183 } 184 185 console.log("Saving %s: %s", _name, _deployed); 186 Deployment memory deployment = Deployment({ name: _name, addr: payable(_deployed) }); 187 _namedDeployments[_name] = deployment; 188 _newDeployments.push(deployment); 189 _appendDeployment(_name, _deployed); 190 } 191 192 /// @notice Reads the deployment artifact from disk that were generated 193 /// by the deploy script. 194 /// @return An array of deployments. 195 function _getDeployments() internal returns (Deployment[] memory) { 196 string memory json = vm.readFile(deploymentOutfile); 197 string[] memory cmd = new string[](3); 198 cmd[0] = Executables.bash; 199 cmd[1] = "-c"; 200 cmd[2] = string.concat(Executables.jq, " 'keys' <<< '", json, "'"); 201 bytes memory res = vm.ffi(cmd); 202 string[] memory names = stdJson.readStringArray(string(res), ""); 203 204 Deployment[] memory deployments = new Deployment[](names.length); 205 for (uint256 i; i < names.length; i++) { 206 string memory contractName = names[i]; 207 address addr = stdJson.readAddress(json, string.concat("$.", contractName)); 208 deployments[i] = Deployment({ name: contractName, addr: payable(addr) }); 209 } 210 return deployments; 211 } 212 213 /// @notice Adds a deployment to the temp deployments file 214 function _appendDeployment(string memory _name, address _deployed) internal { 215 vm.writeJson({ json: stdJson.serialize("", _name, _deployed), path: deploymentOutfile }); 216 } 217 218 /// @notice Stubs a deployment retrieved through `get`. 219 /// @param _name The name of the deployment. 220 /// @param _addr The mock address of the deployment. 221 function prankDeployment(string memory _name, address _addr) public { 222 if (bytes(_name).length == 0) { 223 revert InvalidDeployment("EmptyName"); 224 } 225 226 Deployment memory deployment = Deployment({ name: _name, addr: payable(_addr) }); 227 _namedDeployments[_name] = deployment; 228 } 229 230 /// @notice Returns the value of the internal `_initialized` storage slot for a given contract. 231 function loadInitializedSlot(string memory _contractName) public returns (uint8 initialized_) { 232 address contractAddress; 233 // Check if the contract name ends with `Proxy` and, if so, get the implementation address 234 if (LibString.endsWith(_contractName, "Proxy")) { 235 contractAddress = EIP1967Helper.getImplementation(getAddress(_contractName)); 236 _contractName = LibString.slice(_contractName, 0, bytes(_contractName).length - 5); 237 // If the EIP1967 implementation address is 0, we try to get the implementation address from legacy 238 // AddressManager, which would work if the proxy is ResolvedDelegateProxy like L1CrossDomainMessengerProxy. 239 if (contractAddress == address(0)) { 240 contractAddress = 241 IAddressManager(mustGetAddress("AddressManager")).getAddress(string.concat("OVM_", _contractName)); 242 } 243 } else { 244 contractAddress = mustGetAddress(_contractName); 245 } 246 StorageSlot memory slot = ForgeArtifacts.getInitializedSlot(_contractName); 247 bytes32 slotVal = vm.load(contractAddress, bytes32(vm.parseUint(slot.slot))); 248 initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF); 249 } 250 }