Commit cf978534 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

contracts-bedrock: cleanup scripts (#9755)

* contracts-bedrock: cleanup scripts

Modularize and cleanup the deploy scripts. Breaks out functionality
into reusable libraries to enable more reuse of code. This sort
of refactor has been necessary for some time, now it is much easier
that there is no need for hardhat deploy artifacts. Deletes a bunch
of dead code meant to handle the hh deploy artifacts and generally
simplifies the deploy scripts. Now it is much easier to read them
without having to go in circles or deal with understanding
spaghetti code.

* contracts-bedrock: lint

* contracts-bedrock: lint

* contracts-bedrock: lint
parent eac79900
...@@ -6,8 +6,12 @@ import { stdJson } from "forge-std/StdJson.sol"; ...@@ -6,8 +6,12 @@ import { stdJson } from "forge-std/StdJson.sol";
import { Vm } from "forge-std/Vm.sol"; import { Vm } from "forge-std/Vm.sol";
import { Executables } from "scripts/Executables.sol"; import { Executables } from "scripts/Executables.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
import { Chains } from "scripts/Chains.sol";
import { Config } from "scripts/Config.sol"; import { Config } from "scripts/Config.sol";
import { StorageSlot } from "scripts/ForgeArtifacts.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { LibString } from "solady/utils/LibString.sol";
import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol";
import { IAddressManager } from "scripts/interfaces/IAddressManager.sol";
/// @notice Represents a deployment. Is serialized to JSON as a key/value /// @notice Represents a deployment. Is serialized to JSON as a key/value
/// pair. Can be accessed from within scripts. /// pair. Can be accessed from within scripts.
...@@ -42,24 +46,9 @@ abstract contract Artifacts { ...@@ -42,24 +46,9 @@ abstract contract Artifacts {
/// @notice Setup function. The arguments here /// @notice Setup function. The arguments here
function setUp() public virtual { function setUp() public virtual {
string memory root = vm.projectRoot(); deploymentOutfile = Config.deploymentOutfile();
console.log("Writing artifact to %s", deploymentOutfile);
// The `deploymentContext` should match the name of the deploy-config file. ForgeArtifacts.ensurePath(deploymentOutfile);
deploymentContext = _getDeploymentContext();
deploymentsDir = string.concat(root, "/deployments/", deploymentContext);
if (!vm.isDir(deploymentsDir)) {
vm.createDir(deploymentsDir, true);
}
deploymentOutfile = Config.deploymentOutfile(deploymentsDir);
try vm.readFile(deploymentOutfile) returns (string memory) { }
catch {
vm.writeJson("{}", deploymentOutfile);
}
console.log("Using deploy artifact %s", deploymentOutfile);
try vm.createDir(deploymentsDir, true) { } catch (bytes memory) { }
uint256 chainId = Config.chainID(); uint256 chainId = Config.chainID();
console.log("Connected to network with chainid %s", chainId); console.log("Connected to network with chainid %s", chainId);
...@@ -238,34 +227,24 @@ abstract contract Artifacts { ...@@ -238,34 +227,24 @@ abstract contract Artifacts {
_namedDeployments[_name] = deployment; _namedDeployments[_name] = deployment;
} }
/// @notice The context of the deployment is used to namespace the artifacts. /// @notice Returns the value of the internal `_initialized` storage slot for a given contract.
/// An unknown context will use the chainid as the context name. function loadInitializedSlot(string memory _contractName) public returns (uint8 initialized_) {
/// This is legacy code and should be removed in the future. address contractAddress;
function _getDeploymentContext() private view returns (string memory) { // Check if the contract name ends with `Proxy` and, if so, get the implementation address
string memory context = Config.deploymentContext(); if (LibString.endsWith(_contractName, "Proxy")) {
if (bytes(context).length > 0) { contractAddress = EIP1967Helper.getImplementation(getAddress(_contractName));
return context; _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.
uint256 chainid = Config.chainID(); if (contractAddress == address(0)) {
if (chainid == Chains.Mainnet) { contractAddress =
return "mainnet"; IAddressManager(mustGetAddress("AddressManager")).getAddress(string.concat("OVM_", _contractName));
} else if (chainid == Chains.Goerli) { }
return "goerli";
} else if (chainid == Chains.OPGoerli) {
return "optimism-goerli";
} else if (chainid == Chains.OPMainnet) {
return "optimism-mainnet";
} else if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet) {
return "devnetL1";
} else if (chainid == Chains.Hardhat) {
return "hardhat";
} else if (chainid == Chains.Sepolia) {
return "sepolia";
} else if (chainid == Chains.OPSepolia) {
return "optimism-sepolia";
} else { } else {
return vm.toString(chainid); 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);
} }
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { Vm } from "forge-std/Vm.sol"; import { Vm } from "forge-std/Vm.sol";
import { Chains } from "scripts/Chains.sol";
/// @title Config /// @title Config
/// @notice Contains all env var based config. Add any new env var parsing to this file /// @notice Contains all env var based config. Add any new env var parsing to this file
...@@ -12,8 +13,17 @@ library Config { ...@@ -12,8 +13,17 @@ library Config {
/// @notice Returns the path on the local filesystem where the deployment artifact is /// @notice Returns the path on the local filesystem where the deployment artifact is
/// written to disk after doing a deployment. /// written to disk after doing a deployment.
function deploymentOutfile(string memory _deploymentsDir) internal view returns (string memory _env) { function deploymentOutfile() internal view returns (string memory _env) {
_env = vm.envOr("DEPLOYMENT_OUTFILE", string.concat(_deploymentsDir, "/.deploy")); _env = vm.envOr(
"DEPLOYMENT_OUTFILE", string.concat(vm.projectRoot(), "/deployments/", _getDeploymentContext(), "/.deploy")
);
}
/// @notice Returns the path on the local filesystem where the deploy config is
function deployConfigPath() internal view returns (string memory _env) {
_env = vm.envOr(
"DEPLOY_CONFIG_PATH", string.concat(vm.projectRoot(), "/deploy-config/", _getDeploymentContext(), ".json")
);
} }
/// @notice Returns the chainid from the EVM context or the value of the CHAIN_ID env var as /// @notice Returns the chainid from the EVM context or the value of the CHAIN_ID env var as
...@@ -65,4 +75,35 @@ library Config { ...@@ -65,4 +75,35 @@ library Config {
function drippieOwnerPrivateKey() internal view returns (uint256 _env) { function drippieOwnerPrivateKey() internal view returns (uint256 _env) {
_env = vm.envUint("DRIPPIE_OWNER_PRIVATE_KEY"); _env = vm.envUint("DRIPPIE_OWNER_PRIVATE_KEY");
} }
/// @notice The context of the deployment is used to namespace the artifacts.
/// An unknown context will use the chainid as the context name.
/// This is legacy code and should be removed in the future.
function _getDeploymentContext() private view returns (string memory) {
string memory context = deploymentContext();
if (bytes(context).length > 0) {
return context;
}
uint256 chainid = Config.chainID();
if (chainid == Chains.Mainnet) {
return "mainnet";
} else if (chainid == Chains.Goerli) {
return "goerli";
} else if (chainid == Chains.OPGoerli) {
return "optimism-goerli";
} else if (chainid == Chains.OPMainnet) {
return "optimism-mainnet";
} else if (chainid == Chains.LocalDevnet || chainid == Chains.GethDevnet) {
return "devnetL1";
} else if (chainid == Chains.Hardhat) {
return "hardhat";
} else if (chainid == Chains.Sepolia) {
return "sepolia";
} else if (chainid == Chains.OPSepolia) {
return "optimism-sepolia";
} else {
return vm.toString(chainid);
}
}
} }
...@@ -52,6 +52,7 @@ import { ChainAssertions } from "scripts/ChainAssertions.sol"; ...@@ -52,6 +52,7 @@ import { ChainAssertions } from "scripts/ChainAssertions.sol";
import { Types } from "scripts/Types.sol"; import { Types } from "scripts/Types.sol";
import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol"; import { LibStateDiff } from "scripts/libraries/LibStateDiff.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol";
/// @title Deploy /// @title Deploy
/// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function. /// @notice Script used to deploy a bedrock system. The entire system is deployed within the `run` function.
...@@ -62,9 +63,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; ...@@ -62,9 +63,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
/// contract name to address pairs. That enables this script to be much more flexible in the way it is used. /// contract name to address pairs. That enables this script to be much more flexible in the way it is used.
/// This contract must not have constructor logic because it is set into state using `etch`. /// This contract must not have constructor logic because it is set into state using `etch`.
contract Deploy is Deployer { contract Deploy is Deployer {
DeployConfig public constant cfg =
DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig"))))));
using stdJson for string; using stdJson for string;
/// @notice FaultDisputeGameParams is a struct that contains the parameters necessary to call /// @notice FaultDisputeGameParams is a struct that contains the parameters necessary to call
...@@ -253,26 +251,6 @@ contract Deploy is Deployer { ...@@ -253,26 +251,6 @@ contract Deploy is Deployer {
// SetUp and Run // // SetUp and Run //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
function setUp() public virtual override {
super.setUp();
// Load the `useFaultProofs` slot value prior to etching the DeployConfig's bytecode and reading the deploy
// config file. If this slot has already been set, it will override the preference in the deploy config.
bytes32 useFaultProofsOverride = vm.load(address(cfg), USE_FAULT_PROOFS_SLOT);
string memory path = string.concat(vm.projectRoot(), "/deploy-config/", deploymentContext, ".json");
vm.etch(address(cfg), vm.getDeployedCode("DeployConfig.s.sol:DeployConfig"));
vm.label(address(cfg), "DeployConfig");
vm.allowCheatcodes(address(cfg));
cfg.read(path);
if (useFaultProofsOverride != 0) {
vm.store(address(cfg), USE_FAULT_PROOFS_SLOT, useFaultProofsOverride);
}
console.log("Deployment context: %s", deploymentContext);
}
/// @notice Deploy all of the L1 contracts necessary for a full Superchain with a single Op Chain. /// @notice Deploy all of the L1 contracts necessary for a full Superchain with a single Op Chain.
function run() public { function run() public {
console.log("Deploying a fresh OP Stack including SuperchainConfig"); console.log("Deploying a fresh OP Stack including SuperchainConfig");
......
...@@ -82,8 +82,7 @@ contract DeployConfig is Script { ...@@ -82,8 +82,7 @@ contract DeployConfig is Script {
try vm.readFile(_path) returns (string memory data) { try vm.readFile(_path) returns (string memory data) {
_json = data; _json = data;
} catch { } catch {
console.log("Warning: unable to read config. Do not deploy unless you are not using config."); require(false, string.concat("Cannot find deploy config file at ", _path));
return;
} }
finalSystemOwner = stdJson.readAddress(_json, "$.finalSystemOwner"); finalSystemOwner = stdJson.readAddress(_json, "$.finalSystemOwner");
......
...@@ -3,8 +3,9 @@ pragma solidity ^0.8.0; ...@@ -3,8 +3,9 @@ pragma solidity ^0.8.0;
import { console2 as console } from "forge-std/console2.sol"; import { console2 as console } from "forge-std/console2.sol";
import { Deployer } from "./Deployer.sol"; import { Script } from "forge-std/Script.sol";
import { PeripheryDeployConfig } from "./PeripheryDeployConfig.s.sol"; import { Artifacts } from "scripts/Artifacts.s.sol";
import { PeripheryDeployConfig } from "scripts/PeripheryDeployConfig.s.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol"; import { Proxy } from "src/universal/Proxy.sol";
...@@ -20,17 +21,17 @@ import { Config } from "scripts/Config.sol"; ...@@ -20,17 +21,17 @@ import { Config } from "scripts/Config.sol";
/// @title DeployPeriphery /// @title DeployPeriphery
/// @notice Script used to deploy periphery contracts. /// @notice Script used to deploy periphery contracts.
contract DeployPeriphery is Deployer { contract DeployPeriphery is Script, Artifacts {
PeripheryDeployConfig cfg; PeripheryDeployConfig cfg;
/// @notice The name of the script, used to ensure the right deploy artifacts /// @notice The name of the script, used to ensure the right deploy artifacts
/// are used. /// are used.
function name() public pure override returns (string memory) { function name() public pure returns (string memory name_) {
return "DeployPeriphery"; name_ = "DeployPeriphery";
} }
function setUp() public override { function setUp() public override {
super.setUp(); Artifacts.setUp();
string memory path = string.concat(vm.projectRoot(), "/periphery-deploy-config/", deploymentContext, ".json"); string memory path = string.concat(vm.projectRoot(), "/periphery-deploy-config/", deploymentContext, ".json");
cfg = new PeripheryDeployConfig(path); cfg = new PeripheryDeployConfig(path);
......
...@@ -4,31 +4,36 @@ pragma solidity ^0.8.0; ...@@ -4,31 +4,36 @@ pragma solidity ^0.8.0;
import { Script } from "forge-std/Script.sol"; import { Script } from "forge-std/Script.sol";
import { stdJson } from "forge-std/StdJson.sol"; import { stdJson } from "forge-std/StdJson.sol";
import { console2 as console } from "forge-std/console2.sol"; import { console2 as console } from "forge-std/console2.sol";
import { Executables } from "scripts/Executables.sol";
import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";
import { IAddressManager } from "scripts/interfaces/IAddressManager.sol"; import { IAddressManager } from "scripts/interfaces/IAddressManager.sol";
import { LibString } from "solady/utils/LibString.sol"; import { LibString } from "solady/utils/LibString.sol";
import { Artifacts, Deployment } from "scripts/Artifacts.s.sol"; import { Artifacts, Deployment } from "scripts/Artifacts.s.sol";
import { Config } from "scripts/Config.sol"; import { Config } from "scripts/Config.sol";
import "scripts/DeployConfig.s.sol";
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
struct StorageSlot {
uint256 astId;
string _contract;
string label;
uint256 offset;
string slot;
string _type;
}
/// @title Deployer /// @title Deployer
/// @author tynes /// @author tynes
/// @notice A contract that can make deploying and interacting with deployments easy. /// @notice A contract that can make deploying and interacting with deployments easy.
abstract contract Deployer is Script, Artifacts { abstract contract Deployer is Script, Artifacts {
DeployConfig public constant cfg =
DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig"))))));
/// @notice Sets up the artifacts contract. /// @notice Sets up the artifacts contract.
function setUp() public virtual override { function setUp() public virtual override {
Artifacts.setUp(); Artifacts.setUp();
// Load the `useFaultProofs` slot value prior to etching the DeployConfig's bytecode and reading the deploy
// config file. If this slot has already been set, it will override the preference in the deploy config.
bytes32 useFaultProofsOverride = vm.load(address(cfg), USE_FAULT_PROOFS_SLOT);
vm.etch(address(cfg), vm.getDeployedCode("DeployConfig.s.sol:DeployConfig"));
vm.label(address(cfg), "DeployConfig");
vm.allowCheatcodes(address(cfg));
cfg.read(Config.deployConfigPath());
if (useFaultProofsOverride != 0) {
vm.store(address(cfg), USE_FAULT_PROOFS_SLOT, useFaultProofsOverride);
}
} }
/// @notice Returns the name of the deployment script. Children contracts /// @notice Returns the name of the deployment script. Children contracts
...@@ -36,134 +41,4 @@ abstract contract Deployer is Script, Artifacts { ...@@ -36,134 +41,4 @@ abstract contract Deployer is Script, Artifacts {
/// This should be the same as the name of the script and is used as the file /// This should be the same as the name of the script and is used as the file
/// name inside of the `broadcast` directory when looking up deployment artifacts. /// name inside of the `broadcast` directory when looking up deployment artifacts.
function name() public pure virtual returns (string memory); function name() public pure virtual returns (string memory);
/// @notice Removes the semantic versioning from a contract name. The semver will exist if the contract is compiled
/// more than once with different versions of the compiler.
function _stripSemver(string memory _name) internal returns (string memory) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(
Executables.echo, " ", _name, " | ", Executables.sed, " -E 's/[.][0-9]+\\.[0-9]+\\.[0-9]+//g'"
);
bytes memory res = vm.ffi(cmd);
return string(res);
}
/// @notice Builds the fully qualified name of a contract. Assumes that the
/// file name is the same as the contract name but strips semver for the file name.
function _getFullyQualifiedName(string memory _name) internal returns (string memory) {
string memory sanitized = _stripSemver(_name);
return string.concat(sanitized, ".sol:", _name);
}
/// @notice Returns the storage layout for a deployed contract.
function getStorageLayout(string memory _name) public returns (string memory layout_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.jq, " -r '.storageLayout' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd);
layout_ = string(res);
}
/// @notice Returns the abi from a the forge artifact
function getAbi(string memory _name) public returns (string memory abi_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.jq, " -r '.abi' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd);
abi_ = string(res);
}
/// @notice Returns the methodIdentifiers from the forge artifact
function getMethodIdentifiers(string memory _name) public returns (string[] memory ids_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.jq, " '.methodIdentifiers | keys' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd);
ids_ = stdJson.readStringArray(string(res), "");
}
function _getForgeArtifactDirectory(string memory _name) internal returns (string memory dir_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.forge, " config --json | ", Executables.jq, " -r .out");
bytes memory res = vm.ffi(cmd);
string memory contractName = _stripSemver(_name);
dir_ = string.concat(vm.projectRoot(), "/", string(res), "/", contractName, ".sol");
}
/// @notice Returns the filesystem path to the artifact path. If the contract was compiled
/// with multiple solidity versions then return the first one based on the result of `ls`.
function _getForgeArtifactPath(string memory _name) internal returns (string memory) {
string memory directory = _getForgeArtifactDirectory(_name);
string memory path = string.concat(directory, "/", _name, ".json");
if (vm.exists(path)) return path;
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(
Executables.ls,
" -1 --color=never ",
directory,
" | ",
Executables.jq,
" -R -s -c 'split(\"\n\") | map(select(length > 0))'"
);
bytes memory res = vm.ffi(cmd);
string[] memory files = stdJson.readStringArray(string(res), "");
return string.concat(directory, "/", files[0]);
}
/// @notice Returns the forge artifact given a contract name.
function _getForgeArtifact(string memory _name) internal returns (string memory) {
string memory forgeArtifactPath = _getForgeArtifactPath(_name);
return vm.readFile(forgeArtifactPath);
}
/// @dev Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @dev 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 = getInitializedSlot(_contractName);
bytes32 slotVal = vm.load(contractAddress, bytes32(vm.parseUint(slot.slot)));
initialized_ = uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF);
}
} }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Vm } from "forge-std/Vm.sol";
import { Executables } from "scripts/Executables.sol";
import { stdJson } from "forge-std/StdJson.sol";
/// @notice Contains information about a storage slot. Mirrors the layout of the storage
/// slot object in Forge artifacts so that we can deserialize JSON into this struct.
struct StorageSlot {
uint256 astId;
string _contract;
string label;
uint256 offset;
string slot;
string _type;
}
/// @title ForgeArtifacts
/// @notice Library for interacting with the forge artifacts.
library ForgeArtifacts {
/// @notice Foundry cheatcode VM.
Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
/// @notice Removes the semantic versioning from a contract name. The semver will exist if the contract is compiled
/// more than once with different versions of the compiler.
function _stripSemver(string memory _name) internal returns (string memory out_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(
Executables.echo, " ", _name, " | ", Executables.sed, " -E 's/[.][0-9]+\\.[0-9]+\\.[0-9]+//g'"
);
bytes memory res = vm.ffi(cmd);
out_ = string(res);
}
/// @notice Builds the fully qualified name of a contract. Assumes that the
/// file name is the same as the contract name but strips semver for the file name.
function _getFullyQualifiedName(string memory _name) internal returns (string memory out_) {
string memory sanitized = _stripSemver(_name);
out_ = string.concat(sanitized, ".sol:", _name);
}
/// @notice Returns the storage layout for a deployed contract.
function getStorageLayout(string memory _name) public returns (string memory layout_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.jq, " -r '.storageLayout' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd);
layout_ = string(res);
}
/// @notice Returns the abi from a the forge artifact
function getAbi(string memory _name) public returns (string memory abi_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.jq, " -r '.abi' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd);
abi_ = string(res);
}
/// @notice Returns the methodIdentifiers from the forge artifact
function getMethodIdentifiers(string memory _name) internal returns (string[] memory ids_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.jq, " '.methodIdentifiers | keys' < ", _getForgeArtifactPath(_name));
bytes memory res = vm.ffi(cmd);
ids_ = stdJson.readStringArray(string(res), "");
}
function _getForgeArtifactDirectory(string memory _name) internal returns (string memory dir_) {
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(Executables.forge, " config --json | ", Executables.jq, " -r .out");
bytes memory res = vm.ffi(cmd);
string memory contractName = _stripSemver(_name);
dir_ = string.concat(vm.projectRoot(), "/", string(res), "/", contractName, ".sol");
}
/// @notice Returns the filesystem path to the artifact path. If the contract was compiled
/// with multiple solidity versions then return the first one based on the result of `ls`.
function _getForgeArtifactPath(string memory _name) internal returns (string memory out_) {
string memory directory = _getForgeArtifactDirectory(_name);
string memory path = string.concat(directory, "/", _name, ".json");
if (vm.exists(path)) {
return path;
}
string[] memory cmd = new string[](3);
cmd[0] = Executables.bash;
cmd[1] = "-c";
cmd[2] = string.concat(
Executables.ls,
" -1 --color=never ",
directory,
" | ",
Executables.jq,
" -R -s -c 'split(\"\n\") | map(select(length > 0))'"
);
bytes memory res = vm.ffi(cmd);
string[] memory files = stdJson.readStringArray(string(res), "");
out_ = string.concat(directory, "/", files[0]);
}
/// @notice Returns the forge artifact given a contract name.
function _getForgeArtifact(string memory _name) internal returns (string memory out_) {
string memory forgeArtifactPath = _getForgeArtifactPath(_name);
out_ = vm.readFile(forgeArtifactPath);
}
/// @notice Pulls the `_initialized` storage slot information from the Forge artifacts for a given contract.
function getInitializedSlot(string memory _contractName) internal returns (StorageSlot memory slot_) {
string memory storageLayout = getStorageLayout(_contractName);
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.echo,
" '",
storageLayout,
"'",
" | ",
Executables.jq,
" '.storage[] | select(.label == \"_initialized\" and .type == \"t_uint8\")'"
);
bytes memory rawSlot = vm.parseJson(string(vm.ffi(command)));
slot_ = abi.decode(rawSlot, (StorageSlot));
}
/// @notice Accepts a filepath and then ensures that the directory
/// exists for the file to live in.
function ensurePath(string memory _path) internal {
(, bytes memory returndata) =
address(vm).call(abi.encodeWithSignature("split(string,string)", _path, string("/")));
string[] memory outputs = abi.decode(returndata, (string[]));
string memory path = "";
for (uint256 i = 0; i < outputs.length - 1; i++) {
path = string.concat(path, outputs[i], "/");
}
vm.createDir(path, true);
}
}
...@@ -9,6 +9,7 @@ import { OptimismPortal } from "src/L1/OptimismPortal.sol"; ...@@ -9,6 +9,7 @@ import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol";
import { DataAvailabilityChallenge } from "src/L1/DataAvailabilityChallenge.sol"; import { DataAvailabilityChallenge } from "src/L1/DataAvailabilityChallenge.sol";
import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol";
/// @title Specification_Test /// @title Specification_Test
/// @dev Specifies common security properties of entrypoints to L1 contracts, including authorization and /// @dev Specifies common security properties of entrypoints to L1 contracts, including authorization and
...@@ -523,7 +524,7 @@ contract Specification_Test is CommonTest { ...@@ -523,7 +524,7 @@ contract Specification_Test is CommonTest {
for (uint256 i; i < contractNames.length; i++) { for (uint256 i; i < contractNames.length; i++) {
string memory contractName = contractNames[i]; string memory contractName = contractNames[i];
string[] memory methodIdentifiers = deploy.getMethodIdentifiers(contractName); string[] memory methodIdentifiers = ForgeArtifacts.getMethodIdentifiers(contractName);
abis_[i].contractName = contractName; abis_[i].contractName = contractName;
abis_[i].entries = new AbiEntry[](methodIdentifiers.length); abis_[i].entries = new AbiEntry[](methodIdentifiers.length);
for (uint256 j; j < methodIdentifiers.length; j++) { for (uint256 j; j < methodIdentifiers.length; j++) {
......
...@@ -9,6 +9,7 @@ import { SystemConfig } from "src/L1/SystemConfig.sol"; ...@@ -9,6 +9,7 @@ import { SystemConfig } from "src/L1/SystemConfig.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol";
import "src/L1/ProtocolVersions.sol"; import "src/L1/ProtocolVersions.sol";
import "scripts/Deployer.sol"; import "scripts/Deployer.sol";
...@@ -374,7 +375,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -374,7 +375,7 @@ contract Initializer_Test is Bridge_Initializer {
for (uint256 i; i < l1ContractNames.length; i++) { for (uint256 i; i < l1ContractNames.length; i++) {
string memory contractName = l1ContractNames[i]; string memory contractName = l1ContractNames[i];
string memory contractAbi = deploy.getAbi(contractName); string memory contractAbi = ForgeArtifacts.getAbi(contractName);
// Query the contract's ABI for an `initialize()` function. // Query the contract's ABI for an `initialize()` function.
command[2] = string.concat( command[2] = string.concat(
...@@ -411,7 +412,7 @@ contract Initializer_Test is Bridge_Initializer { ...@@ -411,7 +412,7 @@ contract Initializer_Test is Bridge_Initializer {
for (uint256 i; i < l2ContractNames.length; i++) { for (uint256 i; i < l2ContractNames.length; i++) {
string memory contractName = l2ContractNames[i]; string memory contractName = l2ContractNames[i];
string memory contractAbi = deploy.getAbi(contractName); string memory contractAbi = ForgeArtifacts.getAbi(contractName);
// Query the contract's ABI for an `initialize()` function. // Query the contract's ABI for an `initialize()` function.
command[2] = string.concat( command[2] = string.concat(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment