diff --git a/rvsol/foundry.toml b/rvsol/foundry.toml index e748f6c..732e5ec 100644 --- a/rvsol/foundry.toml +++ b/rvsol/foundry.toml @@ -18,6 +18,7 @@ remappings = [ 'src/dispute=lib/optimism/packages/contracts-bedrock/src/dispute', 'src/libraries=lib/optimism/packages/contracts-bedrock/src/libraries', 'scripts/libraries=lib/optimism/packages/contracts-bedrock/scripts/libraries', + 'test/mocks=lib/optimism/packages/contracts-bedrock/test/mocks', # required for etherscan contract verification 'src/L1=lib/optimism/packages/contracts-bedrock/src/L1', diff --git a/rvsol/lib/forge-std b/rvsol/lib/forge-std index ae570fe..2d8b7b8 160000 --- a/rvsol/lib/forge-std +++ b/rvsol/lib/forge-std @@ -1 +1 @@ -Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce +Subproject commit 2d8b7b876a5b328d6a73e13c4740ed7a0d72d5f4 diff --git a/rvsol/scripts/Deploy.s.sol b/rvsol/scripts/Deploy.s.sol index 25a6462..f41e64c 100644 --- a/rvsol/scripts/Deploy.s.sol +++ b/rvsol/scripts/Deploy.s.sol @@ -13,6 +13,7 @@ import { IBigStepper } from "@optimism/src/dispute/interfaces/IBigStepper.sol"; import { IPreimageOracle } from "@optimism/src/cannon/interfaces/IPreimageOracle.sol"; import { IDisputeGameFactory } from "@optimism/src/dispute/interfaces/IDisputeGameFactory.sol"; import { IDisputeGame } from "@optimism/src/dispute/interfaces/IDisputeGame.sol"; +import { FaultDisputeGame } from "@optimism/src/dispute/FaultDisputeGame.sol"; import { IDelayedWETH } from "@optimism/src/dispute/interfaces/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "@optimism/src/dispute/interfaces/IAnchorStateRegistry.sol"; import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; @@ -70,7 +71,7 @@ contract Deploy is Deployer { /// @notice Deploy RISCV function deployRiscv() public broadcast returns (address addr_) { console.log("Deploying RISCV implementation"); - RISCV riscv = new RISCV{ salt: _implSalt() }(IPreimageOracle(mustGetChainAddress("PreimageOracle"))); + RISCV riscv = new RISCV{ salt: _implSalt() }(IPreimageOracle(mustGetAddress("PreimageOracle"))); save("RISCV", address(riscv)); console.log("RISCV deployed at %s", address(riscv)); addr_ = address(riscv); @@ -101,27 +102,6 @@ contract Deploy is Deployer { } } - /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data - function _callViaSafe(address _target, bytes memory _data) internal { - Safe safe = Safe(mustGetChainAddress("SystemOwnerSafe")); - - // This is the signature format used the caller is also the signer. - bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); - - safe.execTransaction({ - to: _target, - value: 0, - data: _data, - operation: SafeOps.Operation.Call, - safeTxGas: 0, - baseGas: 0, - gasPrice: 0, - gasToken: address(0), - refundReceiver: payable(address(0)), - signatures: signature - }); - } - /// @notice Sets the implementation for the given fault game type in the `DisputeGameFactory`. function setAsteriscFaultGameImplementation(bool _allowUpgrade) public broadcast { console.log("Setting Asterisc FaultDisputeGame implementation"); @@ -144,6 +124,27 @@ contract Deploy is Deployer { }); } + /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data + function _callViaSafe(address _target, bytes memory _data) internal { + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + + // This is the signature format used the caller is also the signer. + bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); + + safe.execTransaction({ + to: _target, + value: 0, + data: _data, + operation: SafeOps.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: signature + }); + } + /// @notice Sets the implementation for the given fault game type in the `DisputeGameFactory`. function _setFaultGameImplementation( IDisputeGameFactory _factory, @@ -161,34 +162,30 @@ contract Deploy is Deployer { } uint32 rawGameType = GameType.unwrap(_params.gameType); - _factory.setImplementation( - _params.gameType, - IDisputeGame( - _deploy( - "RISCV", - abi.encode( - _params.gameType, - _params.absolutePrestate, - _params.maxGameDepth, - cfg.faultGameSplitDepth(), - cfg.faultGameClockExtension(), - _params.maxClockDuration, - _params.faultVm, - _params.weth, - _params.anchorStateRegistry, - cfg.l2ChainID(), - cfg.l2OutputOracleProposer(), - cfg.l2OutputOracleChallenger() - ) + IDisputeGame dg = IDisputeGame( + _deploy( + "FaultDisputeGame", + string.concat("FaultDisputeGame_", vm.toString(rawGameType)), + abi.encode( + _params.gameType, + _params.absolutePrestate, + _params.maxGameDepth, + cfg.faultGameSplitDepth(), + cfg.faultGameClockExtension(), + _params.maxClockDuration, + _params.faultVm, + _params.weth, + _params.anchorStateRegistry, + cfg.l2ChainID() ) ) ); - string memory gameTypeString = "Asterisc"; + bytes memory data = abi.encodeCall(IDisputeGameFactory.setImplementation, (_params.gameType, dg)); + _callViaSafe(address(_factory), data); console.log( - "DisputeGameFactoryProxy: set `FaultDisputeGame` implementation (Backend: %s | GameType: %s)", - gameTypeString, + "DisputeGameFactoryProxy: set `FaultDisputeGame` implementation (Backend: Asterisc | GameType: %s)", vm.toString(rawGameType) ); } diff --git a/rvsol/scripts/Deploy_Stage_1_4.sol b/rvsol/scripts/Deploy_Stage_1_4.sol index 5196936..c6ab92d 100644 --- a/rvsol/scripts/Deploy_Stage_1_4.sol +++ b/rvsol/scripts/Deploy_Stage_1_4.sol @@ -9,6 +9,7 @@ import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; +import { FaultDisputeGame } from "@optimism/src/dispute/FaultDisputeGame.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "@optimism/src/dispute/interfaces/IAnchorStateRegistry.sol"; @@ -23,6 +24,8 @@ import { Chains } from "@optimism/scripts/libraries/Chains.sol"; import { IBigStepper } from "@optimism/src/dispute/interfaces/IBigStepper.sol"; import "@optimism/src/dispute/lib/Types.sol"; import { console2 as console } from "forge-std/console2.sol"; +import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; +import { Enum as SafeOps } from "safe-contracts/common/Enum.sol"; import { StdAssertions } from "forge-std/StdAssertions.sol"; contract Deploy is Deployer, StdAssertions { @@ -98,7 +101,7 @@ contract Deploy is Deployer, StdAssertions { /// @notice Deploy RISCV function deployRiscv() public broadcast returns (address addr_) { console.log("Deploying RISCV implementation"); - RISCV riscv = new RISCV{ salt: _implSalt() }(IPreimageOracle(mustGetChainAddress("PreimageOracle"))); + RISCV riscv = new RISCV{ salt: _implSalt() }(IPreimageOracle(mustGetAddress("PreimageOracle"))); save("RISCV", address(riscv)); console.log("RISCV deployed at %s", address(riscv)); addr_ = address(riscv); @@ -278,6 +281,27 @@ contract Deploy is Deployer, StdAssertions { } } + /// @notice Make a call from the Safe contract to an arbitrary address with arbitrary data + function _callViaSafe(address _target, bytes memory _data) internal { + Safe safe = Safe(mustGetAddress("SystemOwnerSafe")); + + // This is the signature format used the caller is also the signer. + bytes memory signature = abi.encodePacked(uint256(uint160(msg.sender)), bytes32(0), uint8(1)); + + safe.execTransaction({ + to: _target, + value: 0, + data: _data, + operation: SafeOps.Operation.Call, + safeTxGas: 0, + baseGas: 0, + gasPrice: 0, + gasToken: address(0), + refundReceiver: payable(address(0)), + signatures: signature + }); + } + /// @notice Sets the implementation for the given fault game type in the `DisputeGameFactory`. function setAsteriscFaultGameImplementation(bool _allowUpgrade) public broadcast { console.log("Setting Asterisc FaultDisputeGame implementation"); @@ -299,14 +323,13 @@ contract Deploy is Deployer, StdAssertions { }) }); } - /// @notice Sets the implementation for the given fault game type in the `DisputeGameFactory`. function _setFaultGameImplementation( IDisputeGameFactory _factory, bool _allowUpgrade, FaultDisputeGameParams memory _params ) - internal + internal { if (address(_factory.gameImpls(_params.gameType)) != address(0) && !_allowUpgrade) { console.log( @@ -317,34 +340,30 @@ contract Deploy is Deployer, StdAssertions { } uint32 rawGameType = GameType.unwrap(_params.gameType); - _factory.setImplementation( - _params.gameType, - IDisputeGame( - _deploy( - "RISCV", - abi.encode( - _params.gameType, - _params.absolutePrestate, - _params.maxGameDepth, - cfg.faultGameSplitDepth(), - cfg.faultGameClockExtension(), - _params.maxClockDuration, - _params.faultVm, - _params.weth, - _params.anchorStateRegistry, - cfg.l2ChainID(), - cfg.l2OutputOracleProposer(), - cfg.l2OutputOracleChallenger() - ) + IDisputeGame dg = IDisputeGame( + _deploy( + "FaultDisputeGame", + string.concat("FaultDisputeGame_", vm.toString(rawGameType)), + abi.encode( + _params.gameType, + _params.absolutePrestate, + _params.maxGameDepth, + cfg.faultGameSplitDepth(), + cfg.faultGameClockExtension(), + _params.maxClockDuration, + _params.faultVm, + _params.weth, + _params.anchorStateRegistry, + cfg.l2ChainID() ) ) ); - string memory gameTypeString = "Asterisc"; + bytes memory data = abi.encodeCall(IDisputeGameFactory.setImplementation, (_params.gameType, dg)); + _callViaSafe(address(_factory), data); console.log( - "DisputeGameFactoryProxy: set `FaultDisputeGame` implementation (Backend: %s | GameType: %s)", - gameTypeString, + "DisputeGameFactoryProxy: set `FaultDisputeGame` implementation (Backend: Asterisc | GameType: %s)", vm.toString(rawGameType) ); } @@ -418,7 +437,7 @@ contract Deploy is Deployer, StdAssertions { } else { assertEq(gameImpl.absolutePrestate().raw(), bytes32(cfg.faultGameAbsolutePrestate())); } - address wethProxyAddr = mustGetChainAddress("DelayedWETHProxy"); + address wethProxyAddr = mustGetAddress("DelayedWETHProxy"); assertEq(address(gameImpl.weth()), wethProxyAddr); assertEq(address(gameImpl.anchorStateRegistry()), address(asr)); assertEq(address(gameImpl.vm()), address(riscv)); diff --git a/rvsol/scripts/lib/Artifacts.s.sol b/rvsol/scripts/lib/Artifacts.s.sol index 7943194..cbe9a6d 100644 --- a/rvsol/scripts/lib/Artifacts.s.sol +++ b/rvsol/scripts/lib/Artifacts.s.sol @@ -1,19 +1,30 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; +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 { Executables } from "scripts/libraries/Executables.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; import { Config } from "scripts/lib/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 "src/legacy/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; -} + 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`. abstract contract Artifacts { /// @notice Foundry cheatcode VM. Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -22,38 +33,33 @@ abstract contract Artifacts { error DeploymentDoesNotExist(string); /// @notice Error for when trying to save an invalid deployment error InvalidDeployment(string); + /// @notice Error for when attempting to load the initialized slot of an unsupported contract. + error UnsupportedInitializableContract(string); - /// @notice The set of chain deployments that have been already deployed. - mapping(string => Deployment) internal _chainDeployments; /// @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 Path to the directory containing the hh deploy style artifacts + string internal deploymentsDir; /// @notice The path to the deployment artifact that is being written to. string internal deploymentOutfile; + /// @notice The namespace for the deployment. Can be set with the env var DEPLOYMENT_CONTEXT. + string internal deploymentContext; /// @notice The path to the l1 Alloc that is being loaded for asterisc deployment. // Required because we assume all other components are already at chaindata. string internal l1Allocfile; /// @notice The path to the asterisc absolute prestate. string internal asteriscPrestatefile; - /// @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); - } - /// @notice Setup function. The arguments here function setUp() public virtual { deploymentOutfile = Config.deploymentOutfile(); console.log("Writing artifact to %s", deploymentOutfile); - ensurePath(deploymentOutfile); + ForgeArtifacts.ensurePath(deploymentOutfile); + + uint256 chainId = Config.chainID(); + console.log("Connected to network with chainid %s", chainId); // Load L1 allocs from a genesis file l1Allocfile = Config.chainL1AllocPath(); @@ -61,68 +67,52 @@ abstract contract Artifacts { // Prepare absolute asterisc prestate asteriscPrestatefile = Config.asteriscPrestatePath(); - // Load addresses from a JSON file if the TARGET_L2_DEPLOYMENT_FILE environment variable + // 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.chainDeploymentFile(); if (bytes(addresses).length > 0) { - console.log("Loading chain addresses from %s", addresses); - _loadChainAddresses(addresses); + 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 JSON key is the name of the contract and the value is an address. - function _loadChainAddresses(string memory _path) internal { + /// 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(vm.ffi(commands)); + 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)); - Deployment memory deployment = Deployment({ name: key, addr: payable(addr) }); - _chainDeployments[key] = deployment; - console.log("Loading %s: %s", key, addr); - } - } - - /// @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"); + save(key, addr); } - 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; - _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 Returns all of the deployments done in the current context. + function newDeployments() external view returns (Deployment[] memory) { + return _newDeployments; } - /// @notice Returns a deployment that is suitable to be used to interact with contracts. + /// @notice Returns whether or not a particular deployment exists. /// @param _name The name of the deployment. - /// @return The deployment. - function getChainDeployment(string memory _name) public view returns (Deployment memory) { - return _chainDeployments[_name]; + /// @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 getChainAddress(string memory _name) public view returns (address payable) { - Deployment memory existing = _chainDeployments[_name]; + 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)); @@ -130,44 +120,137 @@ abstract contract Artifacts { 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); + } else if (digest == keccak256(bytes("OptimismSuperchainERC20Factory"))) { + return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY); + } else if (digest == keccak256(bytes("OptimismSuperchainERC20Beacon"))) { + return payable(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_BEACON); + } 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 mustGetChainAddress(string memory _name) public view returns (address payable) { - address addr = getChainAddress(_name); + 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 the address of a deployment. Also handles the predeploys. + /// @notice Returns a deployment that is suitable to be used to interact with contracts. /// @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; + /// @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"); } - return payable(address(0)); + 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 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); + /// @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"); } - return payable(addr); + + 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_) { + // FaultDisputeGame and PermissionedDisputeGame are initializable but cannot be loaded with + // this function yet because they are not properly labeled in the deploy script. + // TODO: Remove this restriction once the deploy script is fixed. + if (LibString.eq(_contractName, "FaultDisputeGame") || LibString.eq(_contractName, "PermissionedDisputeGame")) { + revert UnsupportedInitializableContract(_contractName); + } + + 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); } }