github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/scripts/ForgeArtifacts.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity ^0.8.0; 3 4 import { Vm } from "forge-std/Vm.sol"; 5 import { Executables } from "scripts/Executables.sol"; 6 import { stdJson } from "forge-std/StdJson.sol"; 7 8 /// @notice Contains information about a storage slot. Mirrors the layout of the storage 9 /// slot object in Forge artifacts so that we can deserialize JSON into this struct. 10 struct StorageSlot { 11 uint256 astId; 12 string _contract; 13 string label; 14 uint256 offset; 15 string slot; 16 string _type; 17 } 18 19 /// @title ForgeArtifacts 20 /// @notice Library for interacting with the forge artifacts. 21 library ForgeArtifacts { 22 /// @notice Foundry cheatcode VM. 23 Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); 24 25 /// @notice Removes the semantic versioning from a contract name. The semver will exist if the contract is compiled 26 /// more than once with different versions of the compiler. 27 function _stripSemver(string memory _name) internal returns (string memory out_) { 28 string[] memory cmd = new string[](3); 29 cmd[0] = Executables.bash; 30 cmd[1] = "-c"; 31 cmd[2] = string.concat( 32 Executables.echo, " ", _name, " | ", Executables.sed, " -E 's/[.][0-9]+\\.[0-9]+\\.[0-9]+//g'" 33 ); 34 bytes memory res = vm.ffi(cmd); 35 out_ = string(res); 36 } 37 38 /// @notice Builds the fully qualified name of a contract. Assumes that the 39 /// file name is the same as the contract name but strips semver for the file name. 40 function _getFullyQualifiedName(string memory _name) internal returns (string memory out_) { 41 string memory sanitized = _stripSemver(_name); 42 out_ = string.concat(sanitized, ".sol:", _name); 43 } 44 45 /// @notice Returns the storage layout for a deployed contract. 46 function getStorageLayout(string memory _name) public returns (string memory layout_) { 47 string[] memory cmd = new string[](3); 48 cmd[0] = Executables.bash; 49 cmd[1] = "-c"; 50 cmd[2] = string.concat(Executables.jq, " -r '.storageLayout' < ", _getForgeArtifactPath(_name)); 51 bytes memory res = vm.ffi(cmd); 52 layout_ = string(res); 53 } 54 55 /// @notice Returns the abi from a the forge artifact 56 function getAbi(string memory _name) public returns (string memory abi_) { 57 string[] memory cmd = new string[](3); 58 cmd[0] = Executables.bash; 59 cmd[1] = "-c"; 60 cmd[2] = string.concat(Executables.jq, " -r '.abi' < ", _getForgeArtifactPath(_name)); 61 bytes memory res = vm.ffi(cmd); 62 abi_ = string(res); 63 } 64 65 /// @notice Returns the methodIdentifiers from the forge artifact 66 function getMethodIdentifiers(string memory _name) internal returns (string[] memory ids_) { 67 string[] memory cmd = new string[](3); 68 cmd[0] = Executables.bash; 69 cmd[1] = "-c"; 70 cmd[2] = string.concat(Executables.jq, " '.methodIdentifiers | keys' < ", _getForgeArtifactPath(_name)); 71 bytes memory res = vm.ffi(cmd); 72 ids_ = stdJson.readStringArray(string(res), ""); 73 } 74 75 function _getForgeArtifactDirectory(string memory _name) internal returns (string memory dir_) { 76 string[] memory cmd = new string[](3); 77 cmd[0] = Executables.bash; 78 cmd[1] = "-c"; 79 cmd[2] = string.concat(Executables.forge, " config --json | ", Executables.jq, " -r .out"); 80 bytes memory res = vm.ffi(cmd); 81 string memory contractName = _stripSemver(_name); 82 dir_ = string.concat(vm.projectRoot(), "/", string(res), "/", contractName, ".sol"); 83 } 84 85 /// @notice Returns the filesystem path to the artifact path. If the contract was compiled 86 /// with multiple solidity versions then return the first one based on the result of `ls`. 87 function _getForgeArtifactPath(string memory _name) internal returns (string memory out_) { 88 string memory directory = _getForgeArtifactDirectory(_name); 89 string memory path = string.concat(directory, "/", _name, ".json"); 90 if (vm.exists(path)) { 91 return path; 92 } 93 94 string[] memory cmd = new string[](3); 95 cmd[0] = Executables.bash; 96 cmd[1] = "-c"; 97 cmd[2] = string.concat( 98 Executables.ls, 99 " -1 --color=never ", 100 directory, 101 " | ", 102 Executables.jq, 103 " -R -s -c 'split(\"\n\") | map(select(length > 0))'" 104 ); 105 bytes memory res = vm.ffi(cmd); 106 string[] memory files = stdJson.readStringArray(string(res), ""); 107 out_ = string.concat(directory, "/", files[0]); 108 } 109 110 /// @notice Returns the forge artifact given a contract name. 111 function _getForgeArtifact(string memory _name) internal returns (string memory out_) { 112 string memory forgeArtifactPath = _getForgeArtifactPath(_name); 113 out_ = vm.readFile(forgeArtifactPath); 114 } 115 116 /// @notice Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract. 117 function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) { 118 string memory storageLayout = getStorageLayout(_contractName); 119 120 string[] memory command = new string[](3); 121 command[0] = Executables.bash; 122 command[1] = "-c"; 123 command[2] = string.concat( 124 Executables.echo, 125 " '", 126 storageLayout, 127 "'", 128 " | ", 129 Executables.jq, 130 " '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'" 131 ); 132 bytes memory rawSlot = vm.parseJson(string(vm.ffi(command))); 133 slot_ = abi.decode(rawSlot, (StorageSlot)); 134 } 135 136 /// @notice Accepts a filepath and then ensures that the directory 137 /// exists for the file to live in. 138 function ensurePath(string memory _path) internal { 139 (, bytes memory returndata) = 140 address(vm).call(abi.encodeWithSignature("split(string,string)", _path, string("/"))); 141 string[] memory outputs = abi.decode(returndata, (string[])); 142 143 string memory path = ""; 144 for (uint256 i = 0; i < outputs.length - 1; i++) { 145 path = string.concat(path, outputs[i], "/"); 146 } 147 vm.createDir(path, true); 148 } 149 }