Quick Guide !!

We have prepared a set of scripts to deploy all contracts in one click. Otherwise, you can look into and customize each contract as you like.

we reference to OPStack 's version of v1.9.4

4.2 : Prerequisites

Make sure you have run the deploy script for l2 OP Chain - Proxies :

You should have following file with below fields saved at the deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

{
"SafeProxyFactory": "<ADDRESS_1>",
"SafeSingleton": "<ADDRESS_2>",
"SystemOwnerSafe": "<ADDRESS_3>",
"OptimismPortalProxy": "<ADDRESS_4>",
"ProxyAdmin": "<ADDRESS_5>",
"SuperchainConfigProxy": "<ADDRESS_6>",
"SuperchainConfig": "<ADDRESS_7>",
"ProtocolVersionsProxy": "<ADDRESS_8>",
"ProtocolVersions": "<ADDRESS_9>",
"DataAvailabilityChallengeProxy": "<ADDRESS_10>", // optional
"DataAvailabilityChallenge": "<ADDRESS_11>" // optional
"OptimismPortalProxy": "<ADDRESS_12>",
"SystemConfigProxy": "<ADDRESS_13>",
"L1StandardBridgeProxy": "<ADDRESS_14>",
"L1CrossDomainMessengerProxy": "<ADDRESS_15>",
"OptimismMintableERC20FactoryProxy": "<ADDRESS_16>",
"L1ERC721BridgeProxy": "<ADDRESS_17>",
"DisputeGameFactoryProxy": "<ADDRESS_18>",
"L2OutputOracleProxy": "<ADDRESS_19>",
"DelayedWETHProxy": "<ADDRESS_20>",
"PermissionedDelayedWETHProxy": "<ADDRESS_21>",
"AnchorStateRegistryProxy": "<ADDRESS_22>"
}

Without this artifact file, the next deployment scripts can not be run.

One-Click L2 Opchain (Implementations & Fault Proof) Deployment

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/000_DeployAllScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Step 1

Owner

Step 2

Step 3

Step 4.1

Step 4.2

Deploy Script:
000_DeployAllScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeploySafeProxyScript} from "@script/101_DeploySafeProxyScript.s.sol";
import {Script} from "@redprint-forge-std/Script.sol";
import {SetupOpAltDAScript} from "@script/300_SetupOpAltDAScript.s.sol";
import {SetupOpchainScript} from "@script/400_SetupOpchain.s.sol";
import {SetupSuperchainScript} from "@script/200_SetupSuperchain.s.sol";

contract DeployAllScript is Script {
    function run() public {
        DeploySafeProxyScript safeDeployments = new DeploySafeProxyScript();
        //1) set up Safe Multisig
        safeDeployments.deploy();
        SetupSuperchainScript superchainSetups = new SetupSuperchainScript();
        superchainSetups.run();
        SetupOpchainScript opchainSetups = new SetupOpchainScript();
        opchainSetups.run();
        SetupOpAltDAScript opAltDASetups = new SetupOpAltDAScript();
        opAltDASetups.run();
    }
}

        
      

Contract Settings

Step 4

Implementation

L1CrossDomainMessenger OptimismMintableERC20Factory
SystemConfig
L1StandardBridge L1ERC721Bridge OptimismPortal L2OutputOracle

Fault Proofs

OptimismPortal2
DisputeGameFactory DelayedWETH PreimageOracle MIPS AnchorStateRegistry InitializeImplementations SetFaultGameImplementation
Deploy Script:
400_SetupOpchainScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AddressManager} from "@redprint-core/legacy/AddressManager.sol";
import {DeployAnchorStateRegistryProxyScript} from "@scripts/401K_DeployAnchorStateRegistryProxyScript.s.sol";
import {DeployAnchorStateRegistryScript} from "@scripts/402M_DeployAnchorStateRegistryScript.s.sol";
import {DeployDelayedWETHProxyScript} from "@scripts/401I_DeployDelayedWETHProxyScript.s.sol";
import {DeployDelayedWETHScript} from "@scripts/402J_DeployDelayedWETHScript.s.sol";
import {DeployDisputeGameFactoryProxyScript} from "@scripts/401G_DeployDisputeGameFactoryProxyScript.s.sol";
import {DeployDisputeGameFactoryScript} from "@scripts/402I_DeployDisputeGameFactoryScript.s.sol";
import {DeployL1CrossDomainMessengerProxyScript} from "@scripts/401D_DeployL1CrossDomainMessengerProxyScript.s.sol";
import {DeployL1CrossDomainMessengerScript} from "@scripts/402A_DeployL1CrossDomainMessengerScript.s.sol";
import {DeployL1ERC721BridgeProxyScript} from "@scripts/401F_DeployL1ERC721BridgeProxyScript.s.sol";
import {DeployL1ERC721BridgeScript} from "@scripts/402E_DeployL1ERC721BridgeScript.s.sol";
import {DeployL1StandardBridgeProxyScript} from "@scripts/401C_DeployL1StandardBridgeProxyScript.s.sol";
import {DeployL1StandardBridgeScript} from "@scripts/402D_DeployL1StandardBridgeScript.s.sol";
import {DeployL2OutputOracleProxyScript} from "@scripts/401H_DeployL2OutputOracleProxyScript.s.sol";
import {DeployL2OutputOracleScript} from "@scripts/402G_DeployL2OutputOracleScript.s.sol";
import {DeployMIPSScript} from "@scripts/402L_DeployMIPSScript.s.sol";
import {DeployOptimismMintableERC20FactoryProxyScript} from "@scripts/401E_DeployOptimismMintableERC20FactoryProxyScript.s.sol";
import {DeployOptimismMintableERC20FactoryScript} from "@scripts/402B_DeployOptimismMintableERC20Factory.s.sol";
import {DeployOptimismPortal2Script} from "@scripts/402H_DeployOptimismPortal2Script.s.sol";
import {DeployOptimismPortalProxyScript} from "@scripts/401A_DeployOptimismPortalProxyScript.s.sol";
import {DeployOptimismPortalScript} from "@scripts/402F_DeployOptimismPortalScript.s.sol";
import {DeployPermissionedDelayedWETHProxyScript} from "@scripts/401J_DeployPermissionedDelayedWETHProxyScript.s.sol";
import {DeployPreimageOracleScript} from "@scripts/402K_DeployPreimageOracleScript.s.sol";
import {DeploySystemConfigProxyScript} from "@scripts/401B_DeploySystemConfigProxyScript.s.sol";
import {DeploySystemConfigScript} from "@scripts/402C_DeploySystemConfigScript.s.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {InitializeImplementationsScript} from "@scripts/402N_InitializeImplementationsScript.s.sol";
import {Script} from "@redprint-forge-std/Script.sol";
import {SetFaultGameImplementationScript} from "@scripts/402O_SetFaultGameImplementationScript.s.sol";
import {TransferAddressManagerOwnershipScript} from "@scripts/401L_TransferAddressManagerOwnershipScript.s.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {console} from "@redprint-forge-std/console.sol";

contract SetupOpchainScript is Script {
    IDeployer deployerProcedue;

    function run() public {
        deployerProcedue = getDeployer();
        deployerProcedue.setAutoSave(true);
        
        DeployOptimismPortalProxyScript optimismPortalProxyDeployments = new DeployOptimismPortalProxyScript();
        DeploySystemConfigProxyScript systemConfigProxyDeployments = new DeploySystemConfigProxyScript();
        DeployL1StandardBridgeProxyScript l1StandardBridgeProxyDeployments = new DeployL1StandardBridgeProxyScript();
        DeployL1CrossDomainMessengerProxyScript l1CrossDomainMessengerProxyDeployments = new DeployL1CrossDomainMessengerProxyScript();
        DeployOptimismMintableERC20FactoryProxyScript optimismMintableERC20FactoryProxyDeployments = new DeployOptimismMintableERC20FactoryProxyScript();
        DeployL1ERC721BridgeProxyScript l1ERC721BridgeProxyDeployments = new DeployL1ERC721BridgeProxyScript();
        DeployDisputeGameFactoryProxyScript disputeGameFactoryProxyDeployments = new DeployDisputeGameFactoryProxyScript();
        DeployL2OutputOracleProxyScript l2OutputOracleProxyDeployments = new DeployL2OutputOracleProxyScript();
        DeployDelayedWETHProxyScript delayedWETHProxyDeployments = new DeployDelayedWETHProxyScript();
        DeployPermissionedDelayedWETHProxyScript permissionedDelayedWETHProxyDeployments = new DeployPermissionedDelayedWETHProxyScript();
        DeployAnchorStateRegistryProxyScript anchorStateRegistryProxyDeployments = new DeployAnchorStateRegistryProxyScript();
        TransferAddressManagerOwnershipScript transferAddressManagerOwnership = new TransferAddressManagerOwnershipScript();

        optimismPortalProxyDeployments.deploy();
        systemConfigProxyDeployments.deploy();
        l1StandardBridgeProxyDeployments.deploy();
        l1CrossDomainMessengerProxyDeployments.deploy();
        optimismMintableERC20FactoryProxyDeployments.deploy();
        l1ERC721BridgeProxyDeployments.deploy();
        disputeGameFactoryProxyDeployments.deploy();
        l2OutputOracleProxyDeployments.deploy();
        delayedWETHProxyDeployments.deploy();
        permissionedDelayedWETHProxyDeployments.deploy();
        anchorStateRegistryProxyDeployments.deploy();
        transferAddressManagerOwnership.run();
        
        console.log("Setup Opchain ... ");
        
        console.log("OptimismPortalProxy at: ", deployerProcedue.getAddress("OptimismPortalProxy"));
        console.log("SystemConfigProxy at: ", deployerProcedue.getAddress("SystemConfigProxy"));
        console.log("L1CrossDomainMessengerProxy at: ", deployerProcedue.getAddress("L1CrossDomainMessengerProxy"));
        console.log("L1ERC721BridgeProxy at: ", deployerProcedue.getAddress("L1ERC721BridgeProxy"));

        console.log("DisputeGameFactoryProxy at: ", deployerProcedue.getAddress("DisputeGameFactoryProxy"));
        console.log("L2OutputOracleProxy at: ", deployerProcedue.getAddress("L2OutputOracleProxy"));
        console.log("DelayedWETHProxy at: ", deployerProcedue.getAddress("DelayedWETHProxy"));
        console.log("PermissiTwodDelayedWETHProxy at: ", deployerProcedue.getAddress("PermissiTwodDelayedWETHProxy"));
        console.log("AnchorStateRegistryProxy at: ", deployerProcedue.getAddress("AnchorStateRegistryProxy"));
        
        DeployL1CrossDomainMessengerScript l1CrossDomainMessengerDeployments = new DeployL1CrossDomainMessengerScript();
        DeployOptimismMintableERC20FactoryScript optimismMintableERC20FactoryDeployments = new DeployOptimismMintableERC20FactoryScript();
        DeploySystemConfigScript systemConfigDeployments = new DeploySystemConfigScript();
        DeployL1StandardBridgeScript l1StandardBridgeDeployments = new DeployL1StandardBridgeScript();
        DeployL1ERC721BridgeScript l1ERC721BridgeDeployments = new DeployL1ERC721BridgeScript();
        DeployOptimismPortalScript optimismPortalDeployments = new DeployOptimismPortalScript();
        DeployL2OutputOracleScript l2OutputOracleDeployments = new DeployL2OutputOracleScript();
        DeployOptimismPortal2Script optimismPortal2Deployments = new DeployOptimismPortal2Script();
        DeployDisputeGameFactoryScript disputeGameFactoryDeployments = new DeployDisputeGameFactoryScript();
        DeployDelayedWETHScript delayedWETHDeployments = new DeployDelayedWETHScript();
        DeployPreimageOracleScript preimageOracleDeployments = new DeployPreimageOracleScript();
        DeployMIPSScript mipsDeployments = new DeployMIPSScript();
        DeployAnchorStateRegistryScript anchorStateRegistryDeployments = new DeployAnchorStateRegistryScript();
        InitializeImplementationsScript initializeImplementations = new InitializeImplementationsScript();
        SetFaultGameImplementationScript setFaultGameImplementation = new SetFaultGameImplementationScript();
        
        l1CrossDomainMessengerDeployments.deploy();
        optimismMintableERC20FactoryDeployments.deploy();
        systemConfigDeployments.deploy();
        l1StandardBridgeDeployments.deploy();
        l1ERC721BridgeDeployments.deploy();
        optimismPortalDeployments.deploy();
        l2OutputOracleDeployments.deploy();
        optimismPortal2Deployments.deploy();
        disputeGameFactoryDeployments.deploy();
        delayedWETHDeployments.deploy();
        preimageOracleDeployments.deploy();
        mipsDeployments.deploy();
        anchorStateRegistryDeployments.deploy();
        initializeImplementations.run();
        setFaultGameImplementation.run();

        console.log("L1CrossDomainMessenger at: ", deployerProcedue.getAddress("L1CrossDomainMessenger"));
        console.log("OptimismMintableERC20Factory at: ", deployerProcedue.getAddress("OptimismMintableERC20Factory"));
        console.log("SystemConfig at: ", deployerProcedue.getAddress("SystemConfig"));
        console.log("L1StandardBridge at: ", deployerProcedue.getAddress("L1StandardBridge"));
        console.log("L1ERC721Bridge at: ", deployerProcedue.getAddress("L1ERC721Bridge"));
        console.log("OptimismPortal at: ", deployerProcedue.getAddress("OptimismPortal"));
        console.log("L2OutputOracle at: ", deployerProcedue.getAddress("L2OutputOracle"));
        console.log("OptimismPortal2 at: ", deployerProcedue.getAddress("OptimismPortal2"));
        console.log("DisputeGameFactory at: ", deployerProcedue.getAddress("DisputeGameFactory"));
        console.log("DelayedWETH at: ", deployerProcedue.getAddress("DelayedWETH"));
        console.log("PreimageOracle at: ", deployerProcedue.getAddress("PreimageOracle"));
        console.log("MIPS at: ", deployerProcedue.getAddress("Mips"));
        console.log("AnchorStateRegistry at: ", deployerProcedue.getAddress("AnchorStateRegistry"));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2 - Part 1 : Deploy Implementations Contracts

4.2A : Deploy L1CrossDomainMessenger Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/401A_DeployL1CrossDomainMessengerScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
L1CrossDomainMessenger.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {CrossDomainMessenger} from "@redprint-core/universal/CrossDomainMessenger.sol";
import {IOptimismPortal} from "@redprint-core/L1/interfaces/IOptimismPortal.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {ISystemConfig} from "@redprint-core/L1/interfaces/ISystemConfig.sol";
import {Predeploys} from "@redprint-core/libraries/Predeploys.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol
contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver {
    /// @notice Contract of the SuperchainConfig.
    ISuperchainConfig public superchainConfig;
    /// @notice Contract of the OptimismPortal.
    /// @custom:network-specific
    IOptimismPortal public portal;
    /// @notice Address of the SystemConfig contract.
    ISystemConfig public systemConfig;
    /// @notice Semantic version.
    /// @custom:semver 2.4.1-beta.2
    string public constant version = "2.4.1-beta.2";

    constructor() CrossDomainMessenger() {
        initialize({
            _superchainConfig: ISuperchainConfig(address(0)),
            _portal: IOptimismPortal(payable(address(0))),
            _systemConfig: ISystemConfig(address(0))
        });
    }

    function initialize(ISuperchainConfig _superchainConfig, IOptimismPortal _portal, ISystemConfig _systemConfig)
        public
        initializer
    {
        superchainConfig = _superchainConfig;
        portal = _portal;
        systemConfig = _systemConfig;
        __CrossDomainMessenger_init({ _otherMessenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) });
    }

    function gasPayingToken()
        internal
        view
        override
        returns (address addr_, uint8 decimals_)
    {
        (addr_, decimals_) = systemConfig.gasPayingToken();
    }

    function PORTAL() external view returns (IOptimismPortal) {
        return portal;
    }

    function _sendMessage(address _to, uint64 _gasLimit, uint256 _value, bytes memory _data)
        internal
        override
    {
        portal.depositTransaction{ value: _value }({
        _to: _to,
        _value: _value,
        _gasLimit: _gasLimit,
        _isCreation: false,
        _data: _data
    });
    }

    function _isOtherMessenger() internal view override returns (bool) {
        return msg.sender == address(portal) && portal.l2Sender() == address(otherMessenger);
    }

    function _isUnsafeTarget(address _target)
        internal
        view
        override
        returns (bool)
    {
        return _target == address(this) || _target == address(portal);
    }

    function paused() public view override returns (bool) {
        return superchainConfig.paused();
    }
}

        
      
Deploy Script:
401A_DeployL1CrossDomainMessengerScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {L1CrossDomainMessenger} from "@redprint-core/L1/L1CrossDomainMessenger.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployL1CrossDomainMessengerScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    L1CrossDomainMessenger l1CrossDomainMessenger;

    function deploy() external returns (L1CrossDomainMessenger) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});
        l1CrossDomainMessenger = deployer.deploy_L1CrossDomainMessenger("L1CrossDomainMessenger", options);
        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.L1CrossDomainMessenger = address(l1CrossDomainMessenger);
        ChainAssertions.checkL1CrossDomainMessenger({ _contracts: contracts, _vm: vm, _isProxy: false });

        return l1CrossDomainMessenger;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2B : Deploy OptimismMintableERC20Factory Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402B_DeployOptimismMintableERC20FactoryScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
OptimismMintableERC20Factory.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IOptimismERC20Factory} from "@redprint-core/L2/interfaces/IOptimismERC20Factory.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {OptimismMintableERC20} from "@redprint-core/universal/OptimismMintableERC20.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol
contract OptimismMintableERC20Factory is Initializable, ISemver, IOptimismERC20Factory {
    /// @custom:spacer OptimismMintableERC20Factory's initializer slot spacing
    /// @notice Spacer to avoid packing into the initializer slot
    bytes30 private spacer_0_2_30;
    /// @notice Address of the StandardBridge on this chain.
    /// @custom:network-specific
    address public bridge;
    /// @notice Mapping of local token address to remote token address.
    ///         This is used to keep track of the token deployments.
    mapping(address => address) public deployments;
    /// @notice Reserve extra slots in the storage layout for future upgrades.
    ///         A gap size of 48 was chosen here, so that the first slot used in a child contract
    ///         would be a multiple of 50.
    uint256[48] private __gap;
    /// @custom:legacy
    /// @notice Emitted whenever a new OptimismMintableERC20 is created. Legacy version of the newer
    ///         OptimismMintableERC20Created event. We recommend relying on that event instead.
    /// @param remoteToken Address of the token on the remote chain.
    /// @param localToken  Address of the created token on the local chain.
    event StandardL2TokenCreated(address indexed remoteToken, address indexed localToken);
    /// @notice Emitted whenever a new OptimismMintableERC20 is created.
    /// @param localToken  Address of the created token on the local chain.
    /// @param remoteToken Address of the corresponding token on the remote chain.
    /// @param deployer    Address of the account that deployed the token.
    event OptimismMintableERC20Created(address indexed localToken, address indexed remoteToken, address deployer);
    /// @notice The semver MUST be bumped any time that there is a change in
    ///         the OptimismMintableERC20 token contract since this contract
    ///         is responsible for deploying OptimismMintableERC20 contracts.
    /// @notice Semantic version.
    /// @custom:semver 1.10.1-beta.4
    string public constant version = "1.10.1-beta.4";

    constructor() {
        initialize({ _bridge: address(0) });
    }

    function initialize(address _bridge) public initializer {
        bridge = _bridge;
    }

    function BRIDGE() external view returns (address) {
        return bridge;
    }

    function createStandardL2Token(address _remoteToken, string memory _name, string memory _symbol)
        external
        returns (address)
    {
        return createOptimismMintableERC20(_remoteToken, _name, _symbol);
    }

    function createOptimismMintableERC20(address _remoteToken, string memory _name, string memory _symbol)
        public
        returns (address)
    {
        return createOptimismMintableERC20WithDecimals(_remoteToken, _name, _symbol, 18);
    }

    function createOptimismMintableERC20WithDecimals(address _remoteToken, string memory _name, string memory _symbol, uint8 _decimals)
        public
        returns (address)
    {
        require(_remoteToken != address(0), "OptimismMintableERC20Factory: must provide remote token address");

        bytes32 salt = keccak256(abi.encode(_remoteToken, _name, _symbol, _decimals));

        address localToken =
            address(new OptimismMintableERC20{ salt: salt }(bridge, _remoteToken, _name, _symbol, _decimals));

        deployments[localToken] = _remoteToken;

        // Emit the old event too for legacy support.
        emit StandardL2TokenCreated(_remoteToken, localToken);

        // Emit the updated event. The arguments here differ from the legacy event, but
        // are consistent with the ordering used in StandardBridge events.
        emit OptimismMintableERC20Created(localToken, _remoteToken, msg.sender);

        return localToken;
    }
}

        
      
Deploy Script:
402B_DeployOptimismMintableERC20FactoryScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {OptimismMintableERC20Factory} from "@redprint-core/universal/OptimismMintableERC20Factory.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployOptimismMintableERC20FactoryScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    OptimismMintableERC20Factory factory;

    function deploy() external returns (OptimismMintableERC20Factory) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});
        factory = deployer.deploy_OptimismMintableERC20Factory("OptimismMintableERC20Factory", options);
        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.OptimismMintableERC20Factory = address(factory);
        ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: contracts, _isProxy: false });

        return factory;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2C : Deploy SystemConfig Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402C_DeploySystemConfigScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

When configuring useInterop=false, the contract is SystemConfig(Default). Otherwise, it is SystemConfigInterop.

Contract Settings

Smart Contract:
SystemConfig.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Constants} from "@redprint-core/libraries/Constants.sol";
import {ERC20} from "@redprint-openzeppelin/token/ERC20/ERC20.sol";
import {IGasToken, GasPayingToken} from "@redprint-core/libraries/GasPayingToken.sol";
import {IOptimismPortal} from "@redprint-core/L1/interfaces/IOptimismPortal.sol";
import {IResourceMetering} from "@redprint-core/L1/interfaces/IResourceMetering.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {OwnableUpgradeable} from "@redprint-openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {Storage} from "@redprint-core/libraries/Storage.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/SystemConfig.sol
contract SystemConfig is OwnableUpgradeable, IGasToken, ISemver {
    /// @notice Enum representing different types of updates.
    /// @custom:value BATCHER              Represents an update to the batcher hash.
    /// @custom:value FEE_SCALARS          Represents an update to l1 data fee scalars.
    /// @custom:value GAS_LIMIT            Represents an update to gas limit on L2.
    /// @custom:value UNSAFE_BLOCK_SIGNER  Represents an update to the signer key for unsafe
    ///                                    block distrubution.
    enum UpdateType {
        BATCHER,
        FEE_SCALARS,
        GAS_LIMIT,
        UNSAFE_BLOCK_SIGNER,
        EIP_1559_PARAMS
    }
    /// @notice Struct representing the addresses of L1 system contracts. These should be the
    ///         contracts that users interact with (not implementations for proxied contracts)
    ///         and are network specific.
    struct Addresses {
        address l1CrossDomainMessenger;
        address l1ERC721Bridge;
        address l1StandardBridge;
        address disputeGameFactory;
        address optimismPortal;
        address optimismMintableERC20Factory;
        address gasPayingToken;
    }
    /// @notice Version identifier, used for upgrades.
    uint256 public constant VERSION = 0;
    /// @notice Storage slot that the unsafe block signer is stored at.
    ///         Storing it at this deterministic storage slot allows for decoupling the storage
    ///         layout from the way that "solc" lays out storage. The "op-node" uses a storage
    ///         proof to fetch this value.
    /// @dev    NOTE: this value will be migrated to another storage slot in a future version.
    ///         User input should not be placed in storage in this contract until this migration
    ///         happens. It is unlikely that keccak second preimage resistance will be broken,
    ///         but it is better to be safe than sorry.
    bytes32 public constant UNSAFE_BLOCK_SIGNER_SLOT = keccak256("systemconfig.unsafeblocksigner");
    /// @notice Storage slot that the L1CrossDomainMessenger address is stored at.
    bytes32 public constant L1_CROSS_DOMAIN_MESSENGER_SLOT =
        bytes32(uint256(keccak256("systemconfig.l1crossdomainmessenger")) - 1);
    /// @notice Storage slot that the L1ERC721Bridge address is stored at.
    bytes32 public constant L1_ERC_721_BRIDGE_SLOT = bytes32(uint256(keccak256("systemconfig.l1erc721bridge")) - 1);
    /// @notice Storage slot that the L1StandardBridge address is stored at.
    bytes32 public constant L1_STANDARD_BRIDGE_SLOT = bytes32(uint256(keccak256("systemconfig.l1standardbridge")) - 1);
    /// @notice Storage slot that the OptimismPortal address is stored at.
    bytes32 public constant OPTIMISM_PORTAL_SLOT = bytes32(uint256(keccak256("systemconfig.optimismportal")) - 1);
    /// @notice Storage slot that the OptimismMintableERC20Factory address is stored at.
    bytes32 public constant OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT =
        bytes32(uint256(keccak256("systemconfig.optimismmintableerc20factory")) - 1);
    /// @notice Storage slot that the batch inbox address is stored at.
    bytes32 public constant BATCH_INBOX_SLOT = bytes32(uint256(keccak256("systemconfig.batchinbox")) - 1);
    /// @notice Storage slot for block at which the op-node can start searching for logs from.
    bytes32 public constant START_BLOCK_SLOT = bytes32(uint256(keccak256("systemconfig.startBlock")) - 1);
    /// @notice Storage slot for the DisputeGameFactory address.
    bytes32 public constant DISPUTE_GAME_FACTORY_SLOT =
        bytes32(uint256(keccak256("systemconfig.disputegamefactory")) - 1);
    /// @notice The number of decimals that the gas paying token has.
    uint8 internal constant GAS_PAYING_TOKEN_DECIMALS = 18;
    /// @notice The maximum gas limit that can be set for L2 blocks. This limit is used to enforce that the blocks
    ///         on L2 are not too large to process and prove. Over time, this value can be increased as various
    ///         optimizations and improvements are made to the system at large.
    uint64 internal constant MAX_GAS_LIMIT = 200_000_000;
    /// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation.
    ///         Deprecated since the Ecotone network upgrade
    uint256 public overhead;
    /// @notice Dynamic L2 gas overhead. Used as part of the L2 fee calculation.
    ///         The most significant byte is used to determine the version since the
    ///         Ecotone network upgrade.
    uint256 public scalar;
    /// @notice Identifier for the batcher.
    ///         For version 1 of this configuration, this is represented as an address left-padded
    ///         with zeros to 32 bytes.
    bytes32 public batcherHash;
    /// @notice L2 block gas limit.
    uint64 public gasLimit;
    /// @notice Basefee scalar value. Part of the L2 fee calculation since the Ecotone network upgrade.
    uint32 public basefeeScalar;
    /// @notice Blobbasefee scalar value. Part of the L2 fee calculation since the Ecotone network upgrade.
    uint32 public blobbasefeeScalar;
    /// @notice The configuration for the deposit fee market.
    ///         Used by the OptimismPortal to meter the cost of buying L2 gas on L1.
    ///         Set as internal with a getter so that the struct is returned instead of a tuple.
    IResourceMetering.ResourceConfig internal _resourceConfig;
    /// @notice The EIP-1559 base fee max change denominator.
    uint32 public eip1559Denominator;
    /// @notice The EIP-1559 elasticity multiplier.
    uint32 public eip1559Elasticity;
    /// @notice Emitted when configuration is updated.
    /// @param version    SystemConfig version.
    /// @param updateType Type of update.
    /// @param data       Encoded update data.
    event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);

    constructor() {
        Storage.setUint(START_BLOCK_SLOT, type(uint256).max);
        initialize({
            _owner: address(0xdEaD),
            _basefeeScalar: 0,
            _blobbasefeeScalar: 0,
            _batcherHash: bytes32(0),
            _gasLimit: 1,
            _unsafeBlockSigner: address(0),
            _config: IResourceMetering.ResourceConfig({
                maxResourceLimit: 1,
                elasticityMultiplier: 1,
                baseFeeMaxChangeDenominator: 2,
                minimumBaseFee: 0,
                systemTxMaxGas: 0,
                maximumBaseFee: 0
            }),
            _batchInbox: address(0),
            _addresses: SystemConfig.Addresses({
                l1CrossDomainMessenger: address(0),
                l1ERC721Bridge: address(0),
                l1StandardBridge: address(0),
                disputeGameFactory: address(0),
                optimismPortal: address(0),
                optimismMintableERC20Factory: address(0),
                gasPayingToken: address(0)
            })
        });
    }

    function version() public pure virtual returns (string memory) {
        return "2.3.0-beta.5";
    }

    function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, IResourceMetering.ResourceConfig memory _config, address _batchInbox, SystemConfig.Addresses memory _addresses)
        public
        initializer
    {
        __Ownable_init();
        transferOwnership(_owner);

        // These are set in ascending order of their UpdateTypes.
        _setBatcherHash(_batcherHash);
        _setGasConfigEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar });
        _setGasLimit(_gasLimit);

        Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner);
        Storage.setAddress(BATCH_INBOX_SLOT, _batchInbox);
        Storage.setAddress(L1_CROSS_DOMAIN_MESSENGER_SLOT, _addresses.l1CrossDomainMessenger);
        Storage.setAddress(L1_ERC_721_BRIDGE_SLOT, _addresses.l1ERC721Bridge);
        Storage.setAddress(L1_STANDARD_BRIDGE_SLOT, _addresses.l1StandardBridge);
        Storage.setAddress(DISPUTE_GAME_FACTORY_SLOT, _addresses.disputeGameFactory);
        Storage.setAddress(OPTIMISM_PORTAL_SLOT, _addresses.optimismPortal);
        Storage.setAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT, _addresses.optimismMintableERC20Factory);

        _setStartBlock();
        _setGasPayingToken(_addresses.gasPayingToken);

        _setResourceConfig(_config);
        require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
    }

    function minimumGasLimit() public view returns (uint64) {
        return uint64(_resourceConfig.maxResourceLimit) + uint64(_resourceConfig.systemTxMaxGas);
    }

    function maximumGasLimit() public pure returns (uint64) {
         return MAX_GAS_LIMIT;
    }

    function unsafeBlockSigner() public view returns (address addr_) {
        addr_ = Storage.getAddress(UNSAFE_BLOCK_SIGNER_SLOT);
    }

    function l1CrossDomainMessenger() external view returns (address addr_) {
        addr_ = Storage.getAddress(L1_CROSS_DOMAIN_MESSENGER_SLOT);
    }

    function l1ERC721Bridge() external view returns (address addr_) {
        addr_ = Storage.getAddress(L1_ERC_721_BRIDGE_SLOT);
    }

    function l1StandardBridge() external view returns (address addr_) {
        addr_ = Storage.getAddress(L1_STANDARD_BRIDGE_SLOT);
    }

    function disputeGameFactory() external view returns (address addr_) {
        addr_ = Storage.getAddress(DISPUTE_GAME_FACTORY_SLOT);
    }

    function optimismPortal() public view returns (address addr_) {
        addr_ = Storage.getAddress(OPTIMISM_PORTAL_SLOT);
    }

    function optimismMintableERC20Factory() external view returns (address addr_) {
        addr_ = Storage.getAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT);
    }

    function batchInbox() external view returns (address addr_) {
        addr_ = Storage.getAddress(BATCH_INBOX_SLOT);
    }

    function startBlock() external view returns (uint256) {
        return Storage.getUint(START_BLOCK_SLOT);
    }

    function gasPayingToken()
        public
        view
        returns (address addr_, uint8 decimals_)
    {
        (addr_, decimals_) = GasPayingToken.getToken();
    }

    function isCustomGasToken() public view returns (bool) {
        (address token,) = gasPayingToken();
        return token != Constants.ETHER;
    }

    function gasPayingTokenName() external view returns (string memory name_) {
        name_ = GasPayingToken.getName();
    }

    function gasPayingTokenSymbol() external view returns (string memory symbol_) {
        symbol_ = GasPayingToken.getSymbol();
    }

    function _setGasPayingToken(address _token) internal virtual {
        if (_token != address(0) && _token != Constants.ETHER && !isCustomGasToken()) {
            require(
                ERC20(_token).decimals() == GAS_PAYING_TOKEN_DECIMALS, "SystemConfig: bad decimals of gas paying token"
            );
            bytes32 name = GasPayingToken.sanitize(ERC20(_token).name());
            bytes32 symbol = GasPayingToken.sanitize(ERC20(_token).symbol());

            // Set the gas paying token in storage and in the OptimismPortal.
            GasPayingToken.set({ _token: _token, _decimals: GAS_PAYING_TOKEN_DECIMALS, _name: name, _symbol: symbol });
            IOptimismPortal(payable(optimismPortal())).setGasPayingToken({
                _token: _token,
                _decimals: GAS_PAYING_TOKEN_DECIMALS,
                _name: name,
                _symbol: symbol
            });
        }
    }

    function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner {
        _setUnsafeBlockSigner(_unsafeBlockSigner);
    }

    function _setUnsafeBlockSigner(address _unsafeBlockSigner) internal {
        Storage.setAddress(UNSAFE_BLOCK_SIGNER_SLOT, _unsafeBlockSigner);

        bytes memory data = abi.encode(_unsafeBlockSigner);
        emit ConfigUpdate(VERSION, UpdateType.UNSAFE_BLOCK_SIGNER, data);
    }

    function setBatcherHash(bytes32 _batcherHash) external onlyOwner {
        _setBatcherHash(_batcherHash);
    }

    function _setBatcherHash(bytes32 _batcherHash) internal {
        batcherHash = _batcherHash;

        bytes memory data = abi.encode(_batcherHash);
        emit ConfigUpdate(VERSION, UpdateType.BATCHER, data);
    }

    function setGasConfig(uint256 _overhead, uint256 _scalar) external onlyOwner {
        _setGasConfig(_overhead, _scalar);
    }

    function _setGasConfig(uint256 _overhead, uint256 _scalar) internal {
        require((uint256(0xff) << 248) & _scalar == 0, "SystemConfig: scalar exceeds max.");

        overhead = _overhead;
        scalar = _scalar;

        bytes memory data = abi.encode(_overhead, _scalar);
        emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data);
    }

    function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar)
        external
        onlyOwner
    {
        _setGasConfigEcotone(_basefeeScalar, _blobbasefeeScalar);
    }

    function _setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar)
        internal
    {
        basefeeScalar = _basefeeScalar;
        blobbasefeeScalar = _blobbasefeeScalar;

        scalar = (uint256(0x01) << 248) | (uint256(_blobbasefeeScalar) << 32) | _basefeeScalar;

        bytes memory data = abi.encode(overhead, scalar);
        emit ConfigUpdate(VERSION, UpdateType.FEE_SCALARS, data);
    }

    function setGasLimit(uint64 _gasLimit) external onlyOwner {
        _setGasLimit(_gasLimit);
    }

    function _setGasLimit(uint64 _gasLimit) internal {
        require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
        require(_gasLimit <= maximumGasLimit(), "SystemConfig: gas limit too high");
        gasLimit = _gasLimit;

        bytes memory data = abi.encode(_gasLimit);
        emit ConfigUpdate(VERSION, UpdateType.GAS_LIMIT, data);
    }

    function setEIP1559Params(uint32 _denominator, uint32 _elasticity)
        external
        onlyOwner
    {
        _setEIP1559Params(_denominator, _elasticity);
    }

    function _setEIP1559Params(uint32 _denominator, uint32 _elasticity) internal {
        // require the parameters have sane values:
        require(_denominator >= 1, "SystemConfig: denominator must be >= 1");
        require(_elasticity >= 1, "SystemConfig: elasticity must be >= 1");
        eip1559Denominator = _denominator;
        eip1559Elasticity = _elasticity;

        bytes memory data = abi.encode(uint256(_denominator) << 32 | uint64(_elasticity));
        emit ConfigUpdate(VERSION, UpdateType.EIP_1559_PARAMS, data);
    }

    function _setStartBlock() internal {
        if (Storage.getUint(START_BLOCK_SLOT) == 0) {
            Storage.setUint(START_BLOCK_SLOT, block.number);
        }
    }

    function resourceConfig()
        external
        view
        returns (IResourceMetering.ResourceConfig memory)
    {
        return _resourceConfig;
    }

    function _setResourceConfig(IResourceMetering.ResourceConfig memory _config)
        internal
    {
        // Min base fee must be less than or equal to max base fee.
        require(
            _config.minimumBaseFee <= _config.maximumBaseFee, "SystemConfig: min base fee must be less than max base"
        );
        // Base fee change denominator must be greater than 1.
        require(_config.baseFeeMaxChangeDenominator > 1, "SystemConfig: denominator must be larger than 1");
        // Max resource limit plus system tx gas must be less than or equal to the L2 gas limit.
        // The gas limit must be increased before these values can be increased.
        require(_config.maxResourceLimit + _config.systemTxMaxGas <= gasLimit, "SystemConfig: gas limit too low");
        // Elasticity multiplier must be greater than 0.
        require(_config.elasticityMultiplier > 0, "SystemConfig: elasticity multiplier cannot be 0");
        // No precision loss when computing target resource limit.
        require(
            ((_config.maxResourceLimit / _config.elasticityMultiplier) * _config.elasticityMultiplier)
                == _config.maxResourceLimit,
            "SystemConfig: precision loss with target resource limit"
        );

        _resourceConfig = _config;
    }
}

        
      
Deploy Script:
402C_DeploySystemConfigScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {SystemConfig} from "@redprint-core/L1/SystemConfig.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeploySystemConfigScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    SystemConfig systemConfig;

    function deploy() external returns (SystemConfig) {
        DeployConfig cfg = deployer.getConfig();

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        systemConfig = deployer.deploy_SystemConfig("SystemConfig", options);
        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
        contracts.SystemConfig = address(systemConfig);
        ChainAssertions.checkSystemConfig({ _contracts: contracts, _cfg: cfg, _isProxy: false });

        return systemConfig;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2D : Deploy L1StandardBridge Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402D_DeployL1StandardBridgeScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
L1StandardBridge.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ICrossDomainMessenger} from "@redprint-core/universal/interfaces/ICrossDomainMessenger.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {ISystemConfig} from "@redprint-core/L1/interfaces/ISystemConfig.sol";
import {Predeploys} from "@redprint-core/libraries/Predeploys.sol";
import {StandardBridge} from "@redprint-core/universal/StandardBridge.sol";

/// @custom:security-contact Consult full code athttps://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/L1StandardBridge.sol
contract L1StandardBridge is StandardBridge, ISemver {
    /// @custom:legacy
    /// @notice Emitted whenever a deposit of ETH from L1 into L2 is initiated.
    /// @param from      Address of the depositor.
    /// @param to        Address of the recipient on L2.
    /// @param amount    Amount of ETH deposited.
    /// @param extraData Extra data attached to the deposit.
    event ETHDepositInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData);

    /// @custom:legacy
    /// @notice Emitted whenever a withdrawal of ETH from L2 to L1 is finalized.
    /// @param from      Address of the withdrawer.
    /// @param to        Address of the recipient on L1.
    /// @param amount    Amount of ETH withdrawn.
    /// @param extraData Extra data attached to the withdrawal.
    event ETHWithdrawalFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData);
    /// @custom:legacy
    /// @notice Emitted whenever an ERC20 deposit is initiated.
    /// @param l1Token   Address of the token on L1.
    /// @param l2Token   Address of the corresponding token on L2.
    /// @param from      Address of the depositor.
    /// @param to        Address of the recipient on L2.
    /// @param amount    Amount of the ERC20 deposited.
    /// @param extraData Extra data attached to the deposit.
    event ERC20DepositInitiated(
        address indexed l1Token,
        address indexed l2Token,
        address indexed from,
        address to,
        uint256 amount,
        bytes extraData
    );
    /// @custom:legacy
    /// @notice Emitted whenever an ERC20 withdrawal is finalized.
    /// @param l1Token   Address of the token on L1.
    /// @param l2Token   Address of the corresponding token on L2.
    /// @param from      Address of the withdrawer.
    /// @param to        Address of the recipient on L1.
    /// @param amount    Amount of the ERC20 withdrawn.
    /// @param extraData Extra data attached to the withdrawal.
    event ERC20WithdrawalFinalized(
        address indexed l1Token,
        address indexed l2Token,
        address indexed from,
        address to,
        uint256 amount,
        bytes extraData
    );
    /// @notice Semantic version.
    /// @custom:semver 2.2.1-beta.1
    string public constant version = "2.2.1-beta.1";
    /// @notice Address of the SuperchainConfig contract.
    ISuperchainConfig public superchainConfig;
    /// @notice Address of the SystemConfig contract.
    ISystemConfig public systemConfig;

    constructor() StandardBridge() {
        initialize({
            _messenger: ICrossDomainMessenger(address(0)),
            _superchainConfig: ISuperchainConfig(address(0)),
            _systemConfig: ISystemConfig(address(0))
        });
    }

    receive() external payable override onlyEOA {
        _initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes(""));
    }

    function _emitETHBridgeInitiated(address _from, address _to, uint256 _amount, bytes memory _extraData)
        internal
        override
    {
        emit ETHDepositInitiated(_from, _to, _amount, _extraData);
        super._emitETHBridgeInitiated(_from, _to, _amount, _extraData);
    }

    function _emitETHBridgeFinalized(address _from, address _to, uint256 _amount, bytes memory _extraData)
        internal
        override
    {
        emit ETHWithdrawalFinalized(_from, _to, _amount, _extraData);
        super._emitETHBridgeFinalized(_from, _to, _amount, _extraData);
    }

    function _emitERC20BridgeInitiated(address _localToken, address _remoteToken, address _from, address _to, uint256 _amount, bytes memory _extraData)
        internal
        override
    {
         emit ERC20DepositInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
        super._emitERC20BridgeInitiated(_localToken, _remoteToken, _from, _to, _amount, _extraData);
    }

    function _emitERC20BridgeFinalized(address _localToken, address _remoteToken, address _from, address _to, uint256 _amount, bytes memory _extraData)
        internal
        override
    {
        emit ERC20WithdrawalFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
        super._emitERC20BridgeFinalized(_localToken, _remoteToken, _from, _to, _amount, _extraData);
    }

    function initialize( ICrossDomainMessenger _messenger, ISuperchainConfig _superchainConfig, ISystemConfig _systemConfig)
        public
        initializer
    {
        superchainConfig = _superchainConfig;
        systemConfig = _systemConfig;
        __StandardBridge_init({
            _messenger: _messenger,
            _otherBridge: StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE))
        });
    }

    function paused() public view override returns (bool) {
        return superchainConfig.paused();
    }

    function gasPayingToken()
        internal
        view
        override
        returns (address addr_, uint8 decimals_)
    {
        (addr_, decimals_) = systemConfig.gasPayingToken();
    }

    function depositETH(uint32 _minGasLimit, bytes calldata _extraData)
        external payable
        onlyEOA
    {
        _initiateETHDeposit(msg.sender, msg.sender, _minGasLimit, _extraData);
    }

    function depositETHTo(address _to, uint32 _minGasLimit, bytes calldata _extraData)
        external payable
        onlyEOA
    {
        _initiateETHDeposit(msg.sender, _to, _minGasLimit, _extraData);
    }

    function depositERC20(address _l1Token, address _l2Token, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData)
        external
        virtual onlyEOA
    {
        _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData);
    }

    function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData)
        external
        virtual
    {
        _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData);
    }

    function finalizeETHWithdrawal(address _from, address _to, uint256 _amount, bytes calldata _extraData)
        external payable
    {
        finalizeBridgeETH(_from, _to, _amount, _extraData);
    }

    function finalizeERC20Withdrawal(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes calldata _extraData)
        external
    {
        finalizeBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _extraData);
    }

    function l2TokenBridge() external view returns (address) {
        return address(otherBridge);
    }

    function _initiateETHDeposit(address _from, address _to, uint32 _minGasLimit, bytes memory _extraData)
        internal
    {
        _initiateBridgeETH(_from, _to, msg.value, _minGasLimit, _extraData);
    }

    function _initiateERC20Deposit(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, uint32 _minGasLimit, bytes memory _extraData)
        internal
    {
        _initiateBridgeERC20(_l1Token, _l2Token, _from, _to, _amount, _minGasLimit, _extraData);
    }
}

        
      
Deploy Script:
402D_DeployL1StandardBridgeScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {L1StandardBridge} from "@redprint-core/L1/L1StandardBridge.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployL1StandardBridgeScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    L1StandardBridge l1StandardBridge;

    function deploy() external returns (L1StandardBridge) {
        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        l1StandardBridge = deployer.deploy_L1StandardBridge("L1StandardBridge", options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.L1StandardBridge = address(l1StandardBridge);
        ChainAssertions.checkL1StandardBridge({ _contracts: contracts, _isProxy: false });

        return l1StandardBridge;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2E : Deploy L1ERC721Bridge Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402E_DeployL1ERC721BridgeScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
L1ERC721Bridge.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC721Bridge} from "@redprint-core/universal/ERC721Bridge.sol";
import {ICrossDomainMessenger} from "@redprint-core/universal/interfaces/ICrossDomainMessenger.sol";
import {IERC721} from "@redprint-openzeppelin/token/ERC721/IERC721.sol";
import {IL2ERC721Bridge} from "@redprint-core/L2/interfaces/IL2ERC721Bridge.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {Predeploys} from "@redprint-core/libraries/Predeploys.sol";

/// @custom:security-contact Consult full code athttps://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol
contract L1ERC721Bridge is ERC721Bridge, ISemver {
    /// @notice Mapping of L1 token to L2 token to ID to boolean, indicating if the given L1 token
    ///         by ID was deposited for a given L2 token.
    mapping(address => mapping(address => mapping(uint256 => bool))) public deposits;
    /// @notice Address of the SuperchainConfig contract.
    ISuperchainConfig public superchainConfig;
    /// @notice Semantic version.
    /// @custom:semver 2.1.1-beta.4
    string public constant version = "2.1.1-beta.4";

    constructor() ERC721Bridge() {
        initialize({ _messenger: ICrossDomainMessenger(address(0)), _superchainConfig: ISuperchainConfig(address(0)) });
    }

    function initialize( ICrossDomainMessenger _messenger, ISuperchainConfig _superchainConfig)
        public
        initializer
    {
        superchainConfig = _superchainConfig;
        __ERC721Bridge_init({ _messenger: _messenger, _otherBridge: ERC721Bridge(payable(Predeploys.L2_ERC721_BRIDGE)) });
    }

    function finalizeBridgeERC721(address _localToken, address _remoteToken, address _from, address _to, uint256 _tokenId, bytes calldata _extraData)
        external
        onlyOtherBridge
    {
        require(paused() == false, "L1ERC721Bridge: paused");
        require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self");

        // Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge.
        require(
            deposits[_localToken][_remoteToken][_tokenId] == true,
            "L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge"
        );

        // Mark that the token ID for this L1/L2 token pair is no longer escrowed in the L1
        // Bridge.
        deposits[_localToken][_remoteToken][_tokenId] = false;

        // When a withdrawal is finalized on L1, the L1 Bridge transfers the NFT to the
        // withdrawer.
        IERC721(_localToken).safeTransferFrom({ from: address(this), to: _to, tokenId: _tokenId });

        // slither-disable-next-line reentrancy-events
        emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
    }

    function _initiateBridgeERC721(address _localToken, address _remoteToken, address _from, address _to, uint256 _tokenId, uint32 _minGasLimit, bytes calldata _extraData)
        internal
        override
    {
        require(_remoteToken != address(0), "L1ERC721Bridge: remote token cannot be address(0)");

        // Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId)
        bytes memory message = abi.encodeWithSelector(
            IL2ERC721Bridge.finalizeBridgeERC721.selector, _remoteToken, _localToken, _from, _to, _tokenId, _extraData
        );

        // Lock token into bridge
        deposits[_localToken][_remoteToken][_tokenId] = true;
        IERC721(_localToken).transferFrom({ from: _from, to: address(this), tokenId: _tokenId });

        // Send calldata into L2
        messenger.sendMessage({ _target: address(otherBridge), _message: message, _minGasLimit: _minGasLimit });
        emit ERC721BridgeInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
    }
}

        
      
Deploy Script:
402E_DeployL1ERC721BridgeScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {L1ERC721Bridge} from "@redprint-core/L1/L1ERC721Bridge.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployL1ERC721BridgeScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    L1ERC721Bridge l1ERC721Bridge;

    function deploy() external returns (L1ERC721Bridge) {
        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        l1ERC721Bridge = deployer.deploy_L1ERC721Bridge("L1ERC721Bridge", options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.L1ERC721Bridge = address(l1ERC721Bridge);
        ChainAssertions.checkL1ERC721Bridge({ _contracts: contracts, _isProxy: false });

        return l1ERC721Bridge;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2F : Deploy OptimismPortal Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402F_DeployOptimismPortalScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
OptimismPortal.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@redprint-core/libraries/PortalErrors.sol";
import {AddressAliasHelper} from "@redprint-core/vendor/AddressAliasHelper.sol";
import {Constants} from "@redprint-core/libraries/Constants.sol";
import {Hashing} from "@redprint-core/libraries/Hashing.sol";
import {IERC20} from "@redprint-openzeppelin/token/ERC20/IERC20.sol";
import {IL1Block} from "@redprint-core/L2/interfaces/IL1Block.sol";
import {IL2OutputOracle} from "@redprint-core/L1/interfaces/IL2OutputOracle.sol";
import {IResourceMetering} from "@redprint-core/L1/interfaces/IResourceMetering.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {ISystemConfig} from "@redprint-core/L1/interfaces/ISystemConfig.sol";
import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {Predeploys} from "@redprint-core/libraries/Predeploys.sol";
import {ResourceMetering} from "@redprint-core/L1/ResourceMetering.sol";
import {SafeCall} from "@redprint-core/libraries/SafeCall.sol";
import {SafeERC20} from "@redprint-openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {SecureMerkleTrie} from "@redprint-core/libraries/trie/SecureMerkleTrie.sol";
import {Types} from "@redprint-core/libraries/Types.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/OptimismPortal.sol
contract OptimismPortal is Initializable, ResourceMetering, ISemver {
    using SafeERC20 for IERC20 ;
    /// @notice Represents a proven withdrawal.
    /// @custom:field outputRoot    Root of the L2 output this was proven against.
    /// @custom:field timestamp     Timestamp at whcih the withdrawal was proven.
    /// @custom:field l2OutputIndex Index of the output this was proven against.
    struct ProvenWithdrawal {
        bytes32 outputRoot;
        uint128 timestamp;
        uint128 l2OutputIndex;
    }
    /// @notice Version of the deposit event.
    uint256 internal constant DEPOSIT_VERSION = 0;
    /// @notice The L2 gas limit set when eth is deposited using the receive() function.
    uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
    /// @notice The L2 gas limit for system deposit transactions that are initiated from L1.
    uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000;
    /// @notice Address of the L2 account which initiated a withdrawal in this transaction.
    ///         If the of this variable is the default L2 sender address, then we are NOT inside of
    ///         a call to finalizeWithdrawalTransaction.
    address public l2Sender;
    /// @notice A list of withdrawal hashes which have been successfully finalized.
    mapping(bytes32 => bool) public finalizedWithdrawals;
    /// @notice A mapping of withdrawal hashes to ProvenWithdrawal data.
    mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
    /// @custom:legacy
    /// @custom:spacer paused
    /// @notice Spacer for backwards compatibility.
    bool private spacer_53_0_1;
    /// @notice Contract of the Superchain Config.
    ISuperchainConfig public superchainConfig;
    /// @notice Contract of the L2OutputOracle.
    /// @custom:network-specific
    IL2OutputOracle public l2Oracle;
    //// @notice Contract of the SystemConfig.
    /// @custom:network-specific
    ISystemConfig public systemConfig;
    /// @custom:spacer disputeGameFactory
    /// @notice Spacer for backwards compatibility.
    address private spacer_56_0_20;

    /// @custom:spacer provenWithdrawals
    /// @notice Spacer for backwards compatibility.
    bytes32 private spacer_57_0_32;

    /// @custom:spacer disputeGameBlacklist
    /// @notice Spacer for backwards compatibility.
    bytes32 private spacer_58_0_32;

    /// @custom:spacer respectedGameType + respectedGameTypeUpdatedAt
    /// @notice Spacer for backwards compatibility.
    bytes32 private spacer_59_0_32;

    /// @custom:spacer proofSubmitters
    /// @notice Spacer for backwards compatibility.
    bytes32 private spacer_60_0_32;
    /// @notice Represents the amount of native asset minted in L2. This may not
    ///         be 100% accurate due to the ability to send ether to the contract
    ///         without triggering a deposit transaction. It also is used to prevent
    ///         overflows for L2 account balances when custom gas tokens are used.
    ///         It is not safe to trust ERC20.balanceOf as it may lie.
    uint256 internal _balance;
    /// @notice Emitted when a transaction is deposited from L1 to L2.
    ///         The parameters of this event are read by the rollup node and used to derive deposit
    ///         transactions on L2.
    /// @param from       Address that triggered the deposit transaction.
    /// @param to         Address that the deposit transaction is directed to.
    /// @param version    Version of this deposit transaction event.
    /// @param opaqueData ABI encoded deposit data to be parsed off-chain.
    event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
    /// @notice Emitted when a withdrawal transaction is proven.
    /// @param withdrawalHash Hash of the withdrawal transaction.
    /// @param from           Address that triggered the withdrawal transaction.
    /// @param to             Address that the withdrawal transaction is directed to.
    event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
    /// @notice Emitted when a withdrawal transaction is finalized.
    /// @param withdrawalHash Hash of the withdrawal transaction.
    /// @param success        Whether the withdrawal transaction was successful.
    event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);

    modifier whenNotPaused() {
        if (paused()) revert CallPaused();
        _;
    }

    constructor() {
        initialize({
            _l2Oracle: IL2OutputOracle(address(0)),
            _systemConfig: ISystemConfig(address(0)),
            _superchainConfig: ISuperchainConfig(address(0))
        });
    }

    receive() external payable {
        depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes(""));
    }

    function version() public pure virtual returns (string memory) {
        return "2.8.1-beta.3";
    }

    function initialize(IL2OutputOracle _l2Oracle, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig)
        public
        initializer
    {
        l2Oracle = _l2Oracle;
        systemConfig = _systemConfig;
        superchainConfig = _superchainConfig;
        if (l2Sender == address(0)) {
            l2Sender = Constants.DEFAULT_L2_SENDER;
        }
        __ResourceMetering_init();
    }

    function balance() public view returns (uint256) {
        (address token,) = gasPayingToken();
        if (token == Constants.ETHER) {
            return address(this).balance;
        } else {
            return _balance;
        }
    }

    function guardian() public view returns (address) {
        return superchainConfig.guardian();
    }

    function paused() public view returns (bool paused_) {
        paused_ = superchainConfig.paused();
    }

    function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
        return _byteCount * 16 + 21000;
    }

    function donateETH() external payable {
        // Intentionally empty.
    }

    function gasPayingToken()
        internal
        view
        returns (address addr_, uint8 decimals_)
    {
        (addr_, decimals_) = systemConfig.gasPayingToken();
    }

    function _resourceConfig()
        internal
        view
        override
        returns (ResourceConfig memory)
    {
        IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig();
        return ResourceConfig({
            maxResourceLimit: config.maxResourceLimit,
            elasticityMultiplier: config.elasticityMultiplier,
            baseFeeMaxChangeDenominator: config.baseFeeMaxChangeDenominator,
            minimumBaseFee: config.minimumBaseFee,
            systemTxMaxGas: config.systemTxMaxGas,
            maximumBaseFee: config.maximumBaseFee
        });
    }

    function proveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, uint256 _l2OutputIndex, Types.OutputRootProof calldata _outputRootProof, bytes[] calldata _withdrawalProof)
        external
        whenNotPaused
    {
        // Prevent users from creating a deposit transaction where this address is the message
        // sender on L2. Because this is checked here, we do not need to check again in
        // finalizeWithdrawalTransaction.
        if (_tx.target == address(this)) revert BadTarget();

        // Get the output root and load onto the stack to prevent multiple mloads. This will
        // revert if there is no output root for the given block number.
        bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;

        // Verify that the output root can be generated with the elements in the proof.
        require(
            outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof"
        );

        // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
        bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
        ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];

        // We generally want to prevent users from proving the same withdrawal multiple times
        // because each successive proof will update the timestamp. A malicious user can take
        // advantage of this to prevent other users from finalizing their withdrawal. However,
        // since withdrawals are proven before an output root is finalized, we need to allow users
        // to re-prove their withdrawal only in the case that the output root for their specified
        // output index has been updated.
        require(
            provenWithdrawal.timestamp == 0
                || l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot,
            "OptimismPortal: withdrawal hash has already been proven"
        );

        // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
        // Refer to the Solidity documentation for more information on how storage layouts are
        // computed for mappings.
        bytes32 storageKey = keccak256(
            abi.encode(
                withdrawalHash,
                uint256(0) // The withdrawals mapping is at the first slot in the layout.
            )
        );

        // Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
        // on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
        // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
        // be relayed on L1.
        require(
            SecureMerkleTrie.verifyInclusionProof({
                _key: abi.encode(storageKey),
                _value: hex"01",
                _proof: _withdrawalProof,
                _root: _outputRootProof.messagePasserStorageRoot
            }),
            "OptimismPortal: invalid withdrawal inclusion proof"
        );

        // Designate the withdrawalHash as proven by storing the outputRoot, timestamp, and
        // l2BlockNumber in the provenWithdrawals mapping. A withdrawalHash can only be
        // proven once unless it is submitted again with a different outputRoot.
        provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
            outputRoot: outputRoot,
            timestamp: uint128(block.timestamp),
            l2OutputIndex: uint128(_l2OutputIndex)
        });

        // Emit a WithdrawalProven event.
        emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
    }

    function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx)
        external
        whenNotPaused
    {
        // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
        // than the default value when a withdrawal transaction is being finalized. This check is
        // a defacto reentrancy guard.
        if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant();

        // Grab the proven withdrawal from the provenWithdrawals map.
        bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
        ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];

        // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
        // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
        // a timestamp of zero.
        require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");

        // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
        // starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
        // safety against weird bugs in the proving step.
        require(
            provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(),
            "OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
        );

        // A proven withdrawal must wait at least the finalization period before it can be
        // finalized. This waiting period can elapse in parallel with the waiting period for the
        // output the withdrawal was proven against. In effect, this means that the minimum
        // withdrawal time is proposal submission time + finalization period.
        require(
            _isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
            "OptimismPortal: proven withdrawal finalization period has not elapsed"
        );

        // Grab the OutputProposal from the L2OutputOracle, will revert if the output that
        // corresponds to the given index has not been proposed yet.
        Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex);

        // Check that the output root that was used to prove the withdrawal is the same as the
        // current output root for the given output index. An output root may change if it is
        // deleted by the challenger address and then re-proposed.
        require(
            proposal.outputRoot == provenWithdrawal.outputRoot,
            "OptimismPortal: output root proven is not the same as current output root"
        );

        // Check that the output proposal has also been finalized.
        require(
            _isFinalizationPeriodElapsed(proposal.timestamp),
            "OptimismPortal: output proposal finalization period has not elapsed"
        );

        // Check that this withdrawal has not already been finalized, this is replay protection.
        require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized");

        // Mark the withdrawal as finalized so it can't be replayed.
        finalizedWithdrawals[withdrawalHash] = true;

        // Set the l2Sender so contracts know who triggered this withdrawal on L2.
        // This acts as a reentrancy guard.
        l2Sender = _tx.sender;

        bool success;
        (address token,) = gasPayingToken();
        if (token == Constants.ETHER) {
            // Trigger the call to the target contract. We use a custom low level method
            // SafeCall.callWithMinGas to ensure two key properties
            //   1. Target contracts cannot force this call to run out of gas by returning a very large
            //      amount of data (and this is OK because we don't care about the returndata here).
            //   2. The amount of gas provided to the execution context of the target is at least the
            //      gas limit specified by the user. If there is not enough gas in the current context
            //      to accomplish this, callWithMinGas will revert.
            success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
        } else {
            // Cannot call the token contract directly from the portal. This would allow an attacker
            // to call approve from a withdrawal and drain the balance of the portal.
            if (_tx.target == token) revert BadTarget();

            // Only transfer value when a non zero value is specified. This saves gas in the case of
            // using the standard bridge or arbitrary message passing.
            if (_tx.value != 0) {
                // Update the contracts internal accounting of the amount of native asset in L2.
                _balance -= _tx.value;

                // Read the balance of the target contract before the transfer so the consistency
                // of the transfer can be checked afterwards.
                uint256 startBalance = IERC20(token).balanceOf(address(this));

                // Transfer the ERC20 balance to the target, accounting for non standard ERC20
                // implementations that may not return a boolean. This reverts if the low level
                // call is not successful.
                IERC20(token).safeTransfer({ to: _tx.target, value: _tx.value });

                // The balance must be transferred exactly.
                if (IERC20(token).balanceOf(address(this)) != startBalance - _tx.value) {
                    revert TransferFailed();
                }
            }

            // Make a call to the target contract only if there is calldata.
            if (_tx.data.length != 0) {
                success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
            } else {
                success = true;
            }
        }

        // Reset the l2Sender back to the default value.
        l2Sender = Constants.DEFAULT_L2_SENDER;

        // All withdrawals are immediately finalized. Replayability can
        // be achieved through contracts built on top of this contract
        emit WithdrawalFinalized(withdrawalHash, success);

        // Reverting here is useful for determining the exact gas cost to successfully execute the
        // sub call to the target contract if the minimum gas limit specified by the user would not
        // be sufficient to execute the sub call.
        if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
            revert GasEstimation();
        }
    }

    function depositERC20Transaction(address _to, uint256 _mint, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes memory _data)
        public
        metered(_gasLimit)
    {
        // Can only be called if an ERC20 token is used for gas paying on L2
        (address token,) = gasPayingToken();
        if (token == Constants.ETHER) revert OnlyCustomGasToken();

        // Gives overflow protection for L2 account balances.
        _balance += _mint;

        // Get the balance of the portal before the transfer.
        uint256 startBalance = IERC20(token).balanceOf(address(this));

        // Take ownership of the token. It is assumed that the user has given the portal an approval.
        IERC20(token).safeTransferFrom({ from: msg.sender, to: address(this), value: _mint });

        // Double check that the portal now has the exact amount of token.
        if (IERC20(token).balanceOf(address(this)) != startBalance + _mint) {
            revert TransferFailed();
        }

        _depositTransaction({
            _to: _to,
            _mint: _mint,
            _value: _value,
            _gasLimit: _gasLimit,
            _isCreation: _isCreation,
            _data: _data
        });
    }

    function depositTransaction(address _to, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes memory _data)
        public
        payable metered(_gasLimit)
    {
        (address token,) = gasPayingToken();
        if (token != Constants.ETHER && msg.value != 0) revert NoValue();

        _depositTransaction({
            _to: _to,
            _mint: msg.value,
            _value: _value,
            _gasLimit: _gasLimit,
            _isCreation: _isCreation,
            _data: _data
        });
    }

    function _depositTransaction(address _to, uint256 _mint, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes memory _data)
        internal
    {
         // Just to be safe, make sure that people specify address(0) as the target when doing
        // contract creations.
        if (_isCreation && _to != address(0)) revert BadTarget();

        // Prevent depositing transactions that have too small of a gas limit. Users should pay
        // more for more resource usage.
        if (_gasLimit < minimumGasLimit(uint64(_data.length))) revert SmallGasLimit();

        // Prevent the creation of deposit transactions that have too much calldata. This gives an
        // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
        // that the transaction can fit into the p2p network policy of 128kb even though deposit
        // transactions are not gossipped over the p2p network.
        if (_data.length > 120_000) revert LargeCalldata();

        // Transform the from-address to its alias if the caller is a contract.
        address from = msg.sender;
        if (msg.sender != tx.origin) {
            from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
        }

        // Compute the opaque data that will be emitted as part of the TransactionDeposited event.
        // We use opaque data so that we can update the TransactionDeposited event in the future
        // without breaking the current interface.
        bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);

        // Emit a TransactionDeposited event so that the rollup node can derive a deposit
        // transaction for this deposit.
        emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
    }

    function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol)
        external
    {
        if (msg.sender != address(systemConfig)) revert Unauthorized();

        // Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas.
        // This value must be large enough to cover the cost of calling L1Block.setGasPayingToken.
        useGas(SYSTEM_DEPOSIT_GAS_LIMIT);

        // Emit the special deposit transaction directly that sets the gas paying
        // token in the L1Block predeploy contract.
        emit TransactionDeposited(
            Constants.DEPOSITOR_ACCOUNT,
            Predeploys.L1_BLOCK_ATTRIBUTES,
            DEPOSIT_VERSION,
            abi.encodePacked(
                uint256(0), // mint
                uint256(0), // value
                uint64(SYSTEM_DEPOSIT_GAS_LIMIT), // gasLimit
                false, // isCreation,
                abi.encodeCall(IL1Block.setGasPayingToken, (_token, _decimals, _name, _symbol))
            )
        );
    }

    function isOutputFinalized(uint256 _l2OutputIndex)
        external
        view
        returns (bool)
    {
        return _isFinalizationPeriodElapsed(l2Oracle.getL2Output(_l2OutputIndex).timestamp);
    }

    function _isFinalizationPeriodElapsed(uint256 _timestamp)
        internal
        view
        returns (bool)
    {
        return block.timestamp > _timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS();
    }
}

        
      
Deploy Script:
402F_DeployOptimismPortalScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {OptimismPortal} from "@redprint-core/L1/OptimismPortal.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployOptimismPortalScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    OptimismPortal optimismPortal;

    function deploy() external returns (OptimismPortal) {
        DeployConfig cfg = deployer.getConfig();

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        optimismPortal = deployer.deploy_OptimismPortal("OptimismPortal", options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.OptimismPortal = address(optimismPortal);
        ChainAssertions.checkOptimismPortal({ _contracts: contracts, _cfg: cfg, _isProxy: false });

        return optimismPortal;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2G : Deploy L2OutputOracle Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402G_DeployL2OutputOracleScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
L2OutputOracle.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {Types} from "@redprint-core/libraries/Types.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/L2OutputOracle.sol
contract L2OutputOracle is Initializable, ISemver {
    uint256 public startingBlockNumber;
    /// @notice The timestamp of the first L2 block recorded in this contract.
    uint256 public startingTimestamp;
    /// @notice An array of L2 output proposals.
    Types.OutputProposal[] internal l2Outputs;
    /// @notice The interval in L2 blocks at which checkpoints must be submitted.
    /// @custom:network-specific
    uint256 public submissionInterval;
    /// @notice The time between L2 blocks in seconds. Once set, this value MUST NOT be modified.
    /// @custom:network-specific
    uint256 public l2BlockTime;
    /// @notice The address of the challenger. Can be updated via upgrade.
    /// @custom:network-specific
    address public challenger;
    /// @notice The address of the proposer. Can be updated via upgrade.
    /// @custom:network-specific
    address public proposer;
    /// @notice The minimum time (in seconds) that must elapse before a withdrawal can be finalized.
    /// @custom:network-specific
    uint256 public finalizationPeriodSeconds;
    /// @notice Emitted when an output is proposed.
    /// @param outputRoot    The output root.
    /// @param l2OutputIndex The index of the output in the l2Outputs array.
    /// @param l2BlockNumber The L2 block number of the output root.
    /// @param l1Timestamp   The L1 timestamp when proposed.
    event OutputProposed(
        bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp
    );
    /// @notice Emitted when outputs are deleted.
    /// @param prevNextOutputIndex Next L2 output index before the deletion.
    /// @param newNextOutputIndex  Next L2 output index after the deletion.
    event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex);
    /// @notice Semantic version.
    /// @custom:semver 1.8.1-beta.2
    string public constant version = "1.8.1-beta.2";

    constructor() {
        initialize({
            _submissionInterval: 1,
            _l2BlockTime: 1,
            _startingBlockNumber: 0,
            _startingTimestamp: 0,
            _proposer: address(0),
            _challenger: address(0),
            _finalizationPeriodSeconds: 0
        });
    }

    function initialize(uint256 _submissionInterval, uint256 _l2BlockTime, uint256 _startingBlockNumber, uint256 _startingTimestamp, address _proposer, address _challenger, uint256 _finalizationPeriodSeconds)
        public
        initializer
    {
        require(_submissionInterval > 0, "L2OutputOracle: submission interval must be greater than 0");
        require(_l2BlockTime > 0, "L2OutputOracle: L2 block time must be greater than 0");
        require(
            _startingTimestamp <= block.timestamp,
            "L2OutputOracle: starting L2 timestamp must be less than current time"
        );

        submissionInterval = _submissionInterval;
        l2BlockTime = _l2BlockTime;
        startingBlockNumber = _startingBlockNumber;
        startingTimestamp = _startingTimestamp;
        proposer = _proposer;
        challenger = _challenger;
        finalizationPeriodSeconds = _finalizationPeriodSeconds;
    }

    function SUBMISSION_INTERVAL() external view returns (uint256) {
        return submissionInterval;
    }

    function CHALLENGER() external view returns (address) {
        return challenger;
    }

    function L2_BLOCK_TIME() external view returns (uint256) {
        return l2BlockTime;
    }

    function PROPOSER() external view returns (address) {
        return proposer;
    }

    function FINALIZATION_PERIOD_SECONDS() external view returns (uint256) {
        return finalizationPeriodSeconds;
    }

    function deleteL2Outputs(uint256 _l2OutputIndex) external {
        require(msg.sender == challenger, "L2OutputOracle: only the challenger address can delete outputs");

        // Make sure we're not *increasing* the length of the array.
        require(
            _l2OutputIndex < l2Outputs.length, "L2OutputOracle: cannot delete outputs after the latest output index"
        );

        // Do not allow deleting any outputs that have already been finalized.
        require(
            block.timestamp - l2Outputs[_l2OutputIndex].timestamp < finalizationPeriodSeconds,
            "L2OutputOracle: cannot delete outputs that have already been finalized"
        );

        uint256 prevNextL2OutputIndex = nextOutputIndex();

        // Use assembly to delete the array elements because Solidity doesn't allow it.
        assembly {
            sstore(l2Outputs.slot, _l2OutputIndex)
        }

        emit OutputsDeleted(prevNextL2OutputIndex, _l2OutputIndex);
    }

    function proposeL2Output(bytes32 _outputRoot, uint256 _l2BlockNumber, bytes32 _l1BlockHash, uint256 _l1BlockNumber)
        external payable
    {
        require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs");

        require(
            _l2BlockNumber == nextBlockNumber(),
            "L2OutputOracle: block number must be equal to next expected block number"
        );

        require(
            computeL2Timestamp(_l2BlockNumber) < block.timestamp,
            "L2OutputOracle: cannot propose L2 output in the future"
        );

        require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash");

        if (_l1BlockHash != bytes32(0)) {
            // This check allows the proposer to propose an output based on a given L1 block,
            // without fear that it will be reorged out.
            // It will also revert if the blockheight provided is more than 256 blocks behind the
            // chain tip (as the hash will return as zero). This does open the door to a griefing
            // attack in which the proposer's submission is censored until the block is no longer
            // retrievable, if the proposer is experiencing this attack it can simply leave out the
            // blockhash value, and delay submission until it is confident that the L1 block is
            // finalized.
            require(
                blockhash(_l1BlockNumber) == _l1BlockHash,
                "L2OutputOracle: block hash does not match the hash at the expected height"
            );
        }

        emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp);

        l2Outputs.push(
            Types.OutputProposal({
                outputRoot: _outputRoot,
                timestamp: uint128(block.timestamp),
                l2BlockNumber: uint128(_l2BlockNumber)
            })
        );
    }

    function getL2Output(uint256 _l2OutputIndex)
        external
        view
        returns (Types.OutputProposal memory)
    {
        return l2Outputs[_l2OutputIndex];
    }

    function getL2OutputIndexAfter(uint256 _l2BlockNumber)
        public
        view
        returns (uint256)
    {
        // Make sure an output for this block number has actually been proposed.
        require(
            _l2BlockNumber <= latestBlockNumber(),
            "L2OutputOracle: cannot get output for a block that has not been proposed"
        );

        // Make sure there's at least one output proposed.
        require(l2Outputs.length > 0, "L2OutputOracle: cannot get output as no outputs have been proposed yet");

        // Find the output via binary search, guaranteed to exist.
        uint256 lo = 0;
        uint256 hi = l2Outputs.length;
        while (lo < hi) {
            uint256 mid = (lo + hi) / 2;
            if (l2Outputs[mid].l2BlockNumber < _l2BlockNumber) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }

        return lo;
    }

    function getL2OutputAfter(uint256 _l2BlockNumber)
        external
        view
        returns (Types.OutputProposal memory)
    {
        return l2Outputs[getL2OutputIndexAfter(_l2BlockNumber)];
    }

    function latestOutputIndex() external view returns (uint256) {
        return l2Outputs.length - 1;
    }

    function nextOutputIndex() public view returns (uint256) {
        return l2Outputs.length;
    }

    function latestBlockNumber() public view returns (uint256) {
        return l2Outputs.length == 0 ? startingBlockNumber : l2Outputs[l2Outputs.length - 1].l2BlockNumber;
    }

    function nextBlockNumber() public view returns (uint256) {
        return latestBlockNumber() + submissionInterval;
    }

    function computeL2Timestamp(uint256 _l2BlockNumber)
        public
        view
        returns (uint256)
    {
         return startingTimestamp + ((_l2BlockNumber - startingBlockNumber) * l2BlockTime);
    }
}

        
      
Deploy Script:
402G_DeployL2OutputOracleScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {L2OutputOracle} from "@redprint-core/L1/L2OutputOracle.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployL2OutputOracleScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    L2OutputOracle l2OutputOracle;

    function deploy() external returns (L2OutputOracle) {
        DeployConfig cfg = deployer.getConfig();

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        l2OutputOracle = deployer.deploy_L2OutputOracle("L2OutputOracle", options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.L2OutputOracle = address(l2OutputOracle);
        ChainAssertions.checkL2OutputOracle({ _contracts: contracts, _cfg: cfg, _l2OutputOracleStartingTimestamp: 0, _isProxy: false });

        return l2OutputOracle;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2 - Part 2 : Deploy and Set up Fault proofs System

4.2H : Deploy OptimismPortal2 Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402H_DeployOptimismPortal2Script.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

When configuring useInterop=false, the contract is OptimismPortal2(Default). Otherwise, it is OptimismPortalInterop.

The mainnet default value of proofMaturityDelaySeconds is 604800.

The mainnet default value of disputeGameFinalityDelaySeconds is 302400.

Contract Settings

Smart Contract:
OptimismPortal2.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@redprint-core/libraries/PortalErrors.sol";
import "@redprint-core/dispute/lib/Types.sol";
import {AddressAliasHelper} from "@redprint-core/vendor/AddressAliasHelper.sol";
import {Constants} from "@redprint-core/libraries/Constants.sol";
import {Hashing} from "@redprint-core/libraries/Hashing.sol";
import {IDisputeGame} from "@redprint-core/dispute/interfaces/IDisputeGame.sol";
import {IDisputeGameFactory} from "@redprint-core/dispute/interfaces/IDisputeGameFactory.sol";
import {IERC20} from "@redprint-openzeppelin/token/ERC20/IERC20.sol";
import {IL1Block} from "@redprint-core/L2/interfaces/IL1Block.sol";
import {IL2OutputOracle} from "@redprint-core/L1/interfaces/IL2OutputOracle.sol";
import {IResourceMetering} from "@redprint-core/L1/interfaces/IResourceMetering.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {ISystemConfig} from "@redprint-core/L1/interfaces/ISystemConfig.sol";
import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {Predeploys} from "@redprint-core/libraries/Predeploys.sol";
import {ResourceMetering} from "@redprint-core/L1/ResourceMetering.sol";
import {SafeCall} from "@redprint-core/libraries/SafeCall.sol";
import {SafeERC20} from "@redprint-openzeppelin/token/ERC20/utils/SafeERC20.sol";
import {SecureMerkleTrie} from "@redprint-core/libraries/trie/SecureMerkleTrie.sol";
import {Types} from "@redprint-core/libraries/Types.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/OptimismPortal2.sol
contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
    using SafeERC20 for IERC20 ;
    //// @notice Represents a proven withdrawal.
    /// @custom:field disputeGameProxy The address of the dispute game proxy that the withdrawal was proven against.
    /// @custom:field timestamp        Timestamp at whcih the withdrawal was proven.
    struct ProvenWithdrawal {
        IDisputeGame disputeGameProxy;
        uint64 timestamp;
    }
    /// @notice The delay between when a withdrawal transaction is proven and when it may be finalized.
    uint256 internal immutable PROOF_MATURITY_DELAY_SECONDS;
    /// @notice The delay between when a dispute game is resolved and when a withdrawal proven against it may be
    ///         finalized.
    uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS;
    /// @notice Version of the deposit event.
    uint256 internal constant DEPOSIT_VERSION = 0;
    /// @notice The L2 gas limit set when eth is deposited using the receive() function.
    uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
    /// @notice The L2 gas limit for system deposit transactions that are initiated from L1.
    uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000;
    /// @notice Address of the L2 account which initiated a withdrawal in this transaction.
    ///         If the of this variable is the default L2 sender address, then we are NOT inside of
    ///         a call to finalizeWithdrawalTransaction.
    address public l2Sender;
    /// @notice A list of withdrawal hashes which have been successfully finalized.
    mapping(bytes32 => bool) public finalizedWithdrawals;
    /// @custom:legacy
    /// @custom:spacer provenWithdrawals
    /// @notice Spacer taking up the legacy provenWithdrawals mapping slot.
    bytes32 private spacer_52_0_32;
    /// @custom:legacy
    /// @custom:spacer paused
    /// @notice Spacer for backwards compatibility.
    bool private spacer_53_0_1;
    /// @notice Contract of the Superchain Config.
    ISuperchainConfig public superchainConfig;
    /// @custom:legacy
    /// @custom:spacer l2Oracle
    /// @notice Spacer taking up the legacy l2Oracle address slot.
    address private spacer_54_0_20;
    /// @notice Contract of the SystemConfig.
        /// @custom:network-specific
        ISystemConfig public systemConfig;
    /// @notice Address of the DisputeGameFactory.
    /// @custom:network-specific
    IDisputeGameFactory public disputeGameFactory;
    /// @notice A mapping of withdrawal hashes to proof submitters to ProvenWithdrawal data.
    mapping(bytes32 => mapping(address => ProvenWithdrawal)) public provenWithdrawals;
    /// @notice A mapping of dispute game addresses to whether or not they are blacklisted.
    mapping(IDisputeGame => bool) public disputeGameBlacklist;
    /// @notice The game type that the OptimismPortal consults for output proposals.
    GameType public respectedGameType;
    /// @notice The timestamp at which the respected game type was last updated.
    uint64 public respectedGameTypeUpdatedAt;
    /// @notice Mapping of withdrawal hashes to addresses that have submitted a proof for the
    ///         withdrawal. Original OptimismPortal contract only allowed one proof to be submitted
    ///         for any given withdrawal hash. Fault Proofs version of this contract must allow
    ///         multiple proofs for the same withdrawal hash to prevent a malicious user from
    ///         blocking other withdrawals by proving them against invalid proposals. Submitters
    ///         are tracked in an array to simplify the off-chain process of determining which
    ///         proof submission should be used when finalizing a withdrawal.
    mapping(bytes32 => address[]) public proofSubmitters;
    /// @notice Represents the amount of native asset minted in L2. This may not
    ///         be 100% accurate due to the ability to send ether to the contract
    ///         without triggering a deposit transaction. It also is used to prevent
    ///         overflows for L2 account balances when custom gas tokens are used.
    ///         It is not safe to trust ERC20.balanceOf as it may lie.
    uint256 internal _balance;
    /// @notice Emitted when a transaction is deposited from L1 to L2.
    ///         The parameters of this event are read by the rollup node and used to derive deposit
    ///         transactions on L2.
    /// @param from       Address that triggered the deposit transaction.
    /// @param to         Address that the deposit transaction is directed to.
    /// @param version    Version of this deposit transaction event.
    /// @param opaqueData ABI encoded deposit data to be parsed off-chain.
    event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
    /// @notice Emitted when a withdrawal transaction is proven.
    /// @param withdrawalHash Hash of the withdrawal transaction.
    /// @param from           Address that triggered the withdrawal transaction.
    /// @param to             Address that the withdrawal transaction is directed to.
    event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
    /// @notice Emitted when a withdrawal transaction is proven. Exists as a separate event to allow for backwards
    ///         compatibility for tooling that observes the WithdrawalProven event.
    /// @param withdrawalHash Hash of the withdrawal transaction.
    /// @param proofSubmitter Address of the proof submitter.
    event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter);
    /// @notice Emitted when a withdrawal transaction is finalized.
    /// @param withdrawalHash Hash of the withdrawal transaction.
    /// @param success        Whether the withdrawal transaction was successful.
    event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
    /// @notice Emitted when a dispute game is blacklisted by the Guardian.
    /// @param disputeGame Address of the dispute game that was blacklisted.
    event DisputeGameBlacklisted(IDisputeGame indexed disputeGame);
    /// @notice Emitted when the Guardian changes the respected game type in the portal.
    /// @param newGameType The new respected game type.
    /// @param updatedAt   The timestamp at which the respected game type was updated.
    event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt);

    modifier whenNotPaused() {
        if (paused()) revert CallPaused();
        _;
    }

    constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds)
    {
        PROOF_MATURITY_DELAY_SECONDS = _proofMaturityDelaySeconds;
        DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds;

        initialize({
            _disputeGameFactory: IDisputeGameFactory(address(0)),
            _systemConfig: ISystemConfig(address(0)),
            _superchainConfig: ISuperchainConfig(address(0)),
            _initialRespectedGameType: GameType.wrap(0)
        });
    }

    receive() external payable {
        depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes(""));
    }

    function version() public pure virtual returns (string memory) {
        return "3.11.0-beta.5";
    }

    function initialize(IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, GameType _initialRespectedGameType)
        public
        initializer
    {
        disputeGameFactory = _disputeGameFactory;
        systemConfig = _systemConfig;
        superchainConfig = _superchainConfig;

        // Set the l2Sender slot, only if it is currently empty. This signals the first initialization of the
        // contract.
        if (l2Sender == address(0)) {
            l2Sender = Constants.DEFAULT_L2_SENDER;

            // Set the respectedGameTypeUpdatedAt timestamp, to ignore all games of the respected type prior
            // to this operation.
            respectedGameTypeUpdatedAt = uint64(block.timestamp);

            // Set the initial respected game type
            respectedGameType = _initialRespectedGameType;
        }

        __ResourceMetering_init();
    }

    function balance() public view returns (uint256) {
        (address token,) = gasPayingToken();
        if (token == Constants.ETHER) {
            return address(this).balance;
        } else {
            return _balance;
        }
    }

    function guardian() public view returns (address) {
        return superchainConfig.guardian();
    }

    function paused() public view returns (bool paused_) {
        paused_ = superchainConfig.paused();
    }

    function proofMaturityDelaySeconds() public view returns (uint256) {
        return PROOF_MATURITY_DELAY_SECONDS;
    }

    function disputeGameFinalityDelaySeconds() public view returns (uint256) {
        return DISPUTE_GAME_FINALITY_DELAY_SECONDS;
    }

    function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
        return _byteCount * 16 + 21000;
    }

    function donateETH() external payable {
        // Intentionally empty.
    }

    function gasPayingToken()
        internal
        view
        returns (address addr_, uint8 decimals_)
    {
        (addr_, decimals_) = systemConfig.gasPayingToken();
    }

    function _resourceConfig()
        internal
        view
        override
        returns (ResourceMetering.ResourceConfig memory config_)
    {
        IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig();
        assembly ("memory-safe") {
            config_ := config
        }
    }

    function proveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, uint256 _disputeGameIndex, Types.OutputRootProof calldata _outputRootProof, bytes[] calldata _withdrawalProof)
        external
        whenNotPaused
    {
        // Prevent users from creating a deposit transaction where this address is the message
        // sender on L2. Because this is checked here, we do not need to check again in
        // finalizeWithdrawalTransaction.
        if (_tx.target == address(this)) revert BadTarget();

        // Fetch the dispute game proxy from the DisputeGameFactory contract.
        (GameType gameType,, IDisputeGame gameProxy) = disputeGameFactory.gameAtIndex(_disputeGameIndex);
        Claim outputRoot = gameProxy.rootClaim();

        // The game type of the dispute game must be the respected game type.
        if (gameType.raw() != respectedGameType.raw()) revert InvalidGameType();

        // Verify that the output root can be generated with the elements in the proof.
        if (outputRoot.raw() != Hashing.hashOutputRootProof(_outputRootProof)) revert InvalidProof();

        // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
        bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);

        // We do not allow for proving withdrawals against dispute games that have resolved against the favor
        // of the root claim.
        if (gameProxy.status() == GameStatus.CHALLENGER_WINS) revert InvalidDisputeGame();

        // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
        // Refer to the Solidity documentation for more information on how storage layouts are
        // computed for mappings.
        bytes32 storageKey = keccak256(
            abi.encode(
                withdrawalHash,
                uint256(0) // The withdrawals mapping is at the first slot in the layout.
            )
        );

        // Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
        // on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
        // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
        // be relayed on L1.
        if (
            SecureMerkleTrie.verifyInclusionProof({
                _key: abi.encode(storageKey),
                _value: hex"01",
                _proof: _withdrawalProof,
                _root: _outputRootProof.messagePasserStorageRoot
            }) == false
        ) revert InvalidMerkleProof();

        // Designate the withdrawalHash as proven by storing the disputeGameProxy & timestamp in the
        // provenWithdrawals mapping. A withdrawalHash can only be proven once unless the dispute game it proved
        // against resolves against the favor of the root claim.
        provenWithdrawals[withdrawalHash][msg.sender] =
            ProvenWithdrawal({ disputeGameProxy: gameProxy, timestamp: uint64(block.timestamp) });

        // Emit a WithdrawalProven event.
        emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
        // Emit a WithdrawalProvenExtension1 event.
        emit WithdrawalProvenExtension1(withdrawalHash, msg.sender);

        // Add the proof submitter to the list of proof submitters for this withdrawal hash.
        proofSubmitters[withdrawalHash].push(msg.sender);
    }

    function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx)
        external
        whenNotPaused
    {
        finalizeWithdrawalTransactionExternalProof(_tx, msg.sender);
    }

    function finalizeWithdrawalTransactionExternalProof(Types.WithdrawalTransaction memory _tx, address _proofSubmitter)
        public
        whenNotPaused
    {
        // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
        // than the default value when a withdrawal transaction is being finalized. This check is
        // a defacto reentrancy guard.
        if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant();

        // Compute the withdrawal hash.
        bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);

        // Check that the withdrawal can be finalized.
        checkWithdrawal(withdrawalHash, _proofSubmitter);

        // Mark the withdrawal as finalized so it can't be replayed.
        finalizedWithdrawals[withdrawalHash] = true;

        // Set the l2Sender so contracts know who triggered this withdrawal on L2.
        l2Sender = _tx.sender;

        bool success;
        (address token,) = gasPayingToken();
        if (token == Constants.ETHER) {
            // Trigger the call to the target contract. We use a custom low level method
            // SafeCall.callWithMinGas to ensure two key properties
            //   1. Target contracts cannot force this call to run out of gas by returning a very large
            //      amount of data (and this is OK because we don't care about the returndata here).
            //   2. The amount of gas provided to the execution context of the target is at least the
            //      gas limit specified by the user. If there is not enough gas in the current context
            //      to accomplish this, callWithMinGas will revert.
            success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
        } else {
            // Cannot call the token contract directly from the portal. This would allow an attacker
            // to call approve from a withdrawal and drain the balance of the portal.
            if (_tx.target == token) revert BadTarget();

            // Only transfer value when a non zero value is specified. This saves gas in the case of
            // using the standard bridge or arbitrary message passing.
            if (_tx.value != 0) {
                // Update the contracts internal accounting of the amount of native asset in L2.
                _balance -= _tx.value;

                // Read the balance of the target contract before the transfer so the consistency
                // of the transfer can be checked afterwards.
                uint256 startBalance = IERC20(token).balanceOf(address(this));

                // Transfer the ERC20 balance to the target, accounting for non standard ERC20
                // implementations that may not return a boolean. This reverts if the low level
                // call is not successful.
                IERC20(token).safeTransfer({ to: _tx.target, value: _tx.value });

                // The balance must be transferred exactly.
                if (IERC20(token).balanceOf(address(this)) != startBalance - _tx.value) {
                    revert TransferFailed();
                }
            }

            // Make a call to the target contract only if there is calldata.
            if (_tx.data.length != 0) {
                success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
            } else {
                success = true;
            }
        }

        // Reset the l2Sender back to the default value.
        l2Sender = Constants.DEFAULT_L2_SENDER;

        // All withdrawals are immediately finalized. Replayability can
        // be achieved through contracts built on top of this contract
        emit WithdrawalFinalized(withdrawalHash, success);

        // Reverting here is useful for determining the exact gas cost to successfully execute the
        // sub call to the target contract if the minimum gas limit specified by the user would not
        // be sufficient to execute the sub call.
        if (!success && tx.origin == Constants.ESTIMATION_ADDRESS) {
            revert GasEstimation();
        }
    }

    function depositERC20Transaction(address _to, uint256 _mint, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes memory _data)
        public
        metered(_gasLimit)
    {
        // Can only be called if an ERC20 token is used for gas paying on L2
        (address token,) = gasPayingToken();
        if (token == Constants.ETHER) revert OnlyCustomGasToken();

        // Gives overflow protection for L2 account balances.
        _balance += _mint;

        // Get the balance of the portal before the transfer.
        uint256 startBalance = IERC20(token).balanceOf(address(this));

        // Take ownership of the token. It is assumed that the user has given the portal an approval.
        IERC20(token).safeTransferFrom({ from: msg.sender, to: address(this), value: _mint });

        // Double check that the portal now has the exact amount of token.
        if (IERC20(token).balanceOf(address(this)) != startBalance + _mint) {
            revert TransferFailed();
        }

        _depositTransaction({
            _to: _to,
            _mint: _mint,
            _value: _value,
            _gasLimit: _gasLimit,
            _isCreation: _isCreation,
            _data: _data
        });
    }

    function depositTransaction(address _to, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes memory _data)
        public
        payable metered(_gasLimit)
    {
        (address token,) = gasPayingToken();
        if (token != Constants.ETHER && msg.value != 0) revert NoValue();

        _depositTransaction({
            _to: _to,
            _mint: msg.value,
            _value: _value,
            _gasLimit: _gasLimit,
            _isCreation: _isCreation,
            _data: _data
        });
    }

    function _depositTransaction(address _to, uint256 _mint, uint256 _value, uint64 _gasLimit, bool _isCreation, bytes memory _data)
        internal
    {
        // Just to be safe, make sure that people specify address(0) as the target when doing
        // contract creations.
        if (_isCreation && _to != address(0)) revert BadTarget();

        // Prevent depositing transactions that have too small of a gas limit. Users should pay
        // more for more resource usage.
        if (_gasLimit < minimumGasLimit(uint64(_data.length))) revert SmallGasLimit();

        // Prevent the creation of deposit transactions that have too much calldata. This gives an
        // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
        // that the transaction can fit into the p2p network policy of 128kb even though deposit
        // transactions are not gossipped over the p2p network.
        if (_data.length > 120_000) revert LargeCalldata();

        // Transform the from-address to its alias if the caller is a contract.
        address from = msg.sender;
        if (msg.sender != tx.origin) {
            from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
        }

        // Compute the opaque data that will be emitted as part of the TransactionDeposited event.
        // We use opaque data so that we can update the TransactionDeposited event in the future
        // without breaking the current interface.
        bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);

        // Emit a TransactionDeposited event so that the rollup node can derive a deposit
        // transaction for this deposit.
        emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
    }

    function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol)
        external
    {
        if (msg.sender != address(systemConfig)) revert Unauthorized();

        // Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas.
        // This value must be large enough to cover the cost of calling L1Block.setGasPayingToken.
        useGas(SYSTEM_DEPOSIT_GAS_LIMIT);

        // Emit the special deposit transaction directly that sets the gas paying
        // token in the L1Block predeploy contract.
        emit TransactionDeposited(
            Constants.DEPOSITOR_ACCOUNT,
            Predeploys.L1_BLOCK_ATTRIBUTES,
            DEPOSIT_VERSION,
            abi.encodePacked(
                uint256(0), // mint
                uint256(0), // value
                uint64(SYSTEM_DEPOSIT_GAS_LIMIT), // gasLimit
                false, // isCreation,
                abi.encodeCall(IL1Block.setGasPayingToken, (_token, _decimals, _name, _symbol))
            )
        );
    }

    function blacklistDisputeGame(IDisputeGame _disputeGame) external {
        if (msg.sender != guardian()) revert Unauthorized();
        disputeGameBlacklist[_disputeGame] = true;
        emit DisputeGameBlacklisted(_disputeGame);
    }

    function setRespectedGameType(GameType _gameType) external {
        if (msg.sender != guardian()) revert Unauthorized();
        respectedGameType = _gameType;
        respectedGameTypeUpdatedAt = uint64(block.timestamp);
        emit RespectedGameTypeSet(_gameType, Timestamp.wrap(respectedGameTypeUpdatedAt));
    }

    function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter)
        public
        view
    {
        ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash][_proofSubmitter];
        IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy;

        // The dispute game must not be blacklisted.
        if (disputeGameBlacklist[disputeGameProxy]) revert Blacklisted();

        // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
        // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
        // a timestamp of zero.
        if (provenWithdrawal.timestamp == 0) revert Unproven();

        uint64 createdAt = disputeGameProxy.createdAt().raw();

        // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
        // starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of
        // safety against weird bugs in the proving step.
        require(
            provenWithdrawal.timestamp > createdAt,
            "OptimismPortal: withdrawal timestamp less than dispute game creation timestamp"
        );

        // A proven withdrawal must wait at least PROOF_MATURITY_DELAY_SECONDS before finalizing.
        require(
            block.timestamp - provenWithdrawal.timestamp > PROOF_MATURITY_DELAY_SECONDS,
            "OptimismPortal: proven withdrawal has not matured yet"
        );

        // A proven withdrawal must wait until the dispute game it was proven against has been
        // resolved in favor of the root claim (the output proposal). This is to prevent users
        // from finalizing withdrawals proven against non-finalized output roots.
        if (disputeGameProxy.status() != GameStatus.DEFENDER_WINS) revert ProposalNotValidated();

        // The game type of the dispute game must be the respected game type. This was also checked in
        // proveWithdrawalTransaction, but we check it again in case the respected game type has changed since
        // the withdrawal was proven.
        if (disputeGameProxy.gameType().raw() != respectedGameType.raw()) revert InvalidGameType();

        // The game must have been created after respectedGameTypeUpdatedAt. This is to prevent users from creating
        // invalid disputes against a deployed game type while the off-chain challenge agents are not watching.
        require(
            createdAt >= respectedGameTypeUpdatedAt,
            "OptimismPortal: dispute game created before respected game type was updated"
        );

        // Before a withdrawal can be finalized, the dispute game it was proven against must have been
        // resolved for at least DISPUTE_GAME_FINALITY_DELAY_SECONDS. This is to allow for manual
        // intervention in the event that a dispute game is resolved incorrectly.
        require(
            block.timestamp - disputeGameProxy.resolvedAt().raw() > DISPUTE_GAME_FINALITY_DELAY_SECONDS,
            "OptimismPortal: output proposal in air-gap"
        );

        // Check that this withdrawal has not already been finalized, this is replay protection.
        if (finalizedWithdrawals[_withdrawalHash]) revert AlreadyFinalized();
    }

    function numProofSubmitters(bytes32 _withdrawalHash)
        external
        view
        returns (uint256)
    {
        return proofSubmitters[_withdrawalHash].length;
    }
}

        
      
Deploy Script:
402H_DeployOptimismPortal2Script.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {OptimismPortal2} from "@redprint-core/L1/OptimismPortal2.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployOptimismPortal2Script is DeployScript {
    using DeployerFunctions for IDeployer ;
    OptimismPortal2 optimismPortal;

    function deploy() external returns (OptimismPortal2) {
        DeployConfig cfg = deployer.getConfig();
        // Could also verify this inside DeployConfig but doing it here is a bit more reliable.
        require(
            uint32(cfg.respectedGameType()) == cfg.respectedGameType(), "Deploy: respectedGameType must fit into uint32"
        );

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        optimismPortal = deployer.deploy_OptimismPortal2("OptimismPortal2", cfg.proofMaturityDelaySeconds(), cfg.disputeGameFinalityDelaySeconds(), options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.OptimismPortal2 = address(optimismPortal);
        ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false });

        return optimismPortal;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2I : Deploy DisputeGameFactory Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402I_DeployDisputeGameFactoryScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
DisputeGameFactory.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@redprint-core/dispute/lib/Types.sol";
import "@redprint-core/dispute/lib/Errors.sol";
import {IDisputeGame} from "@redprint-core/dispute/interfaces/IDisputeGame.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {LibClone} from "@redprint-solady/utils/LibClone.sol";
import {OwnableUpgradeable} from "@redprint-openzeppelin-upgradeable/access/OwnableUpgradeable.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol
contract DisputeGameFactory is OwnableUpgradeable, ISemver {
    using LibClone for address ;
    /// @notice Emitted when a new dispute game is created
    /// @param disputeProxy The address of the dispute game proxy
    /// @param gameType The type of the dispute game proxy's implementation
    /// @param rootClaim The root claim of the dispute game
    event DisputeGameCreated(address indexed disputeProxy, GameType indexed gameType, Claim indexed rootClaim);
    /// @notice Emitted when a new game implementation added to the factory
    /// @param impl The implementation contract for the given GameType.
    /// @param gameType The type of the DisputeGame.
    event ImplementationSet(address indexed impl, GameType indexed gameType);
    /// @notice Emitted when a game type's initialization bond is updated
    /// @param gameType The type of the DisputeGame.
    /// @param newBond The new bond (in wei) for initializing the game type.
    event InitBondUpdated(GameType indexed gameType, uint256 indexed newBond);
    /// @notice Information about a dispute game found in a findLatestGames search.
    struct GameSearchResult {
        uint256 index;
        GameId metadata;
        Timestamp timestamp;
        Claim rootClaim;
        bytes extraData;
    }
    /// @notice Semantic version.
    /// @custom:semver 1.0.1-beta.2
    string public constant version = "1.0.1-beta.2";
    /// @notice gameImpls is a mapping that maps 'GameType's to their respective
    ///         'IDisputeGame' implementations.
    mapping(GameType => IDisputeGame) public gameImpls;
    /// @notice Returns the required bonds for initializing a dispute game of the given type.
    mapping(GameType => uint256) public initBonds;
    //// @notice Mapping of a hash of 'gameType || rootClaim || extraData' to the deployed 'IDisputeGame' clone where
    //          || denotes concatenation).
    mapping(Hash => GameId) internal _disputeGames;
    /// @notice An append-only array of disputeGames that have been created. Used by offchain game solvers to
    ///         efficiently track dispute games.
    GameId[] internal _disputeGameList;

    constructor() OwnableUpgradeable() {
        initialize(address(0));
    }

    function initialize(address _owner) public initializer {
        __Ownable_init();
        _transferOwnership(_owner);
    }

    function gameCount() external view returns (uint256 gameCount_) {
        gameCount_ = _disputeGameList.length;
    }

    function games(GameType _gameType, Claim _rootClaim, bytes calldata _extraData)
        external
        view
        returns (IDisputeGame proxy_, Timestamp timestamp_)
    {
        Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
        (, Timestamp timestamp, address proxy) = _disputeGames[uuid].unpack();
        (proxy_, timestamp_) = (IDisputeGame(proxy), timestamp);
    }

    function gameAtIndex(uint256 _index)
        external
        view
        returns (GameType gameType_, Timestamp timestamp_, IDisputeGame proxy_)
    {
        (GameType gameType, Timestamp timestamp, address proxy) = _disputeGameList[_index].unpack();
        (gameType_, timestamp_, proxy_) = (gameType, timestamp, IDisputeGame(proxy));
    }

    function create(GameType _gameType, Claim _rootClaim, bytes calldata _extraData)
        external payable
        returns (IDisputeGame proxy_)
    {
        // Grab the implementation contract for the given 'GameType'.
        IDisputeGame impl = gameImpls[_gameType];

        // If there is no implementation to clone for the given 'GameType', revert.
        if (address(impl) == address(0)) revert NoImplementation(_gameType);

        // If the required initialization bond is not met, revert.
        if (msg.value != initBonds[_gameType]) revert IncorrectBondAmount();

        // Get the hash of the parent block.
        bytes32 parentHash = blockhash(block.number - 1);

        // Clone the implementation contract and initialize it with the given parameters.
        //
        // CWIA Calldata Layout:
        // ┌──────────────┬────────────────────────────────────┐
        // │    Bytes     │            Description             │
        // ├──────────────┼────────────────────────────────────┤
        // │ [0, 20)      │ Game creator address               │
        // │ [20, 52)     │ Root claim                         │
        // │ [52, 84)     │ Parent block hash at creation time │
        // │ [84, 84 + n) │ Extra data (opaque)                │
        // └──────────────┴────────────────────────────────────┘
        proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _extraData)));
        proxy_.initialize{ value: msg.value }();

        // Compute the unique identifier for the dispute game.
        Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);

        // If a dispute game with the same UUID already exists, revert.
        if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);

        // Pack the game ID.
        GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), address(proxy_));

        // Store the dispute game id in the mapping & emit the 'DisputeGameCreated' event.
        _disputeGames[uuid] = id;
        _disputeGameList.push(id);
        emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim);
    }

    function getGameUUID(GameType _gameType, Claim _rootClaim, bytes calldata _extraData)
        public
        pure
        returns (Hash uuid_)
    {
        uuid_ = Hash.wrap(keccak256(abi.encode(_gameType, _rootClaim, _extraData)));
    }

    function findLatestGames(GameType _gameType, uint256 _start, uint256 _n)
        external
        view
        returns (GameSearchResult[] memory games_)
    {
        // If the '_start' index is greater than or equal to the game array length or '_n == 0', return an empty array.
        if (_start >= _disputeGameList.length || _n == 0) return games_;

        // Allocate enough memory for the full array, but start the array's length at '0'. We may not use all of the
        // memory allocated, but we don't know ahead of time the final size of the array.
        assembly {
            games_ := mload(0x40)
            mstore(0x40, add(games_, add(0x20, shl(0x05, _n))))
        }

        // Perform a reverse linear search for the '_n' most recent games of type '_gameType'.
        for (uint256 i = _start; i >= 0 && i <= _start;) {
            GameId id = _disputeGameList[i];
            (GameType gameType, Timestamp timestamp, address proxy) = id.unpack();

            if (gameType.raw() == _gameType.raw()) {
                // Increase the size of the 'games_' array by 1.
                // SAFETY: We can safely lazily allocate memory here because we pre-allocated enough memory for the max
                //         possible size of the array.
                assembly {
                    mstore(games_, add(mload(games_), 0x01))
                }

                bytes memory extraData = IDisputeGame(proxy).extraData();
                Claim rootClaim = IDisputeGame(proxy).rootClaim();
                games_[games_.length - 1] = GameSearchResult({
                    index: i,
                    metadata: id,
                    timestamp: timestamp,
                    rootClaim: rootClaim,
                    extraData: extraData
                });
                if (games_.length >= _n) break;
            }

            unchecked {
                i--;
            }
        }
    }

    function setImplementation(GameType _gameType, IDisputeGame _impl)
        external
        onlyOwner
    {
        gameImpls[_gameType] = _impl;
        emit ImplementationSet(address(_impl), _gameType);
    }

    function setInitBond(GameType _gameType, uint256 _initBond) public onlyOwner {
        initBonds[_gameType] = _initBond;
        emit InitBondUpdated(_gameType, _initBond);
    }
}

        
      
Deploy Script:
402I_DeployDisputeGameFactoryScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {DisputeGameFactory} from "@redprint-core/dispute/DisputeGameFactory.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployDisputeGameFactoryScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    DisputeGameFactory disputeGameFactory;

    function deploy() external returns (DisputeGameFactory) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});
        disputeGameFactory = deployer.deploy_DisputeGameFactory("DisputeGameFactory", options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.DisputeGameFactory = address(disputeGameFactory);
        ChainAssertions.checkDisputeGameFactory({ _contracts: contracts, _expectedOwner: address(0), _isProxy: false });

        return disputeGameFactory;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2J : Deploy DelayedWETH Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402J_DeployDelayedWETHScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

The mainnet default value of faultGameWithdrawalDelay is 604800.

Contract Settings

Smart Contract:
DelayedWETH.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {OwnableUpgradeable} from "@redprint-openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {WETH98} from "@redprint-core/universal/WETH98.sol";

/// @custom:security-contact Consult full code athttps://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/dispute/DelayedWETH.sol
contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver {
    /// @notice Represents a withdrawal request.
    struct WithdrawalRequest {
        uint256 amount;
        uint256 timestamp;
    }
    /// @notice Emitted when an unwrap is started.
    /// @param src The address that started the unwrap.
    /// @param wad The amount of WETH that was unwrapped.
    event Unwrap(address indexed src, uint256 wad);
    /// @notice Semantic version.
    /// @custom:semver 1.2.0-beta.2
    string public constant version = "1.2.0-beta.2";
    /// @notice Returns a withdrawal request for the given address.
    mapping(address => mapping(address => WithdrawalRequest)) public withdrawals;
    /// @notice Withdrawal delay in seconds.
    uint256 internal immutable DELAY_SECONDS;
    /// @notice Address of the SuperchainConfig contract.
    ISuperchainConfig public config;

    constructor(uint256 _delay) {
        DELAY_SECONDS = _delay;
        initialize({ _owner: address(0), _config: ISuperchainConfig(address(0)) });
    }

    function initialize(address _owner, ISuperchainConfig _config)
        public
        initializer
    {
        __Ownable_init();
        _transferOwnership(_owner);
        config = _config;
    }

    function delay() external view returns (uint256) {
        return DELAY_SECONDS;
    }

    function unlock(address _guy, uint256 _wad) external {
        // Note that the unlock function can be called by any address, but the actual unlocking capability still only
        // gives the msg.sender the ability to withdraw from the account. As long as the unlock and withdraw functions
        // are called with the proper recipient addresses, this will be safe. Could be made safer by having external
        // accounts execute withdrawals themselves but that would have added extra complexity and made DelayedWETH a
        // leaky abstraction, so we chose this instead.
        WithdrawalRequest storage wd = withdrawals[msg.sender][_guy];
        wd.timestamp = block.timestamp;
        wd.amount += _wad;
    }

    function withdraw(uint256 _wad) public override {
        withdraw(msg.sender, _wad);
    }

    function withdraw(address _guy, uint256 _wad) public {
        require(!config.paused(), "DelayedWETH: contract is paused");
        WithdrawalRequest storage wd = withdrawals[msg.sender][_guy];
        require(wd.amount >= _wad, "DelayedWETH: insufficient unlocked withdrawal");
        require(wd.timestamp > 0, "DelayedWETH: withdrawal not unlocked");
        require(wd.timestamp + DELAY_SECONDS <= block.timestamp, "DelayedWETH: withdrawal delay not met");
        wd.amount -= _wad;
        super.withdraw(_wad);
    }

    function recover(uint256 _wad) external {
        require(msg.sender == owner(), "DelayedWETH: not owner");
        uint256 amount = _wad < address(this).balance ? _wad : address(this).balance;
        (bool success,) = payable(msg.sender).call{ value: amount }(hex"");
        require(success, "DelayedWETH: recover failed");
    }

    function hold(address _guy, uint256 _wad) external {
        require(msg.sender == owner(), "DelayedWETH: not owner");
        allowance[_guy][msg.sender] = _wad;
        emit Approval(_guy, msg.sender, _wad);
    }
}

        
      
Deploy Script:
402J_DeployDelayedWETHScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DelayedWETH} from "@redprint-core/dispute/DelayedWETH.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployDelayedWETHScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    DelayedWETH delayedWETH;

    function deploy() external returns (DelayedWETH) {
        DeployConfig cfg = deployer.getConfig();

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        delayedWETH = deployer.deploy_DelayedWETH("DelayedWETH", cfg.faultGameWithdrawalDelay(), options);

        Types.ContractSet memory contracts =  deployer.getProxiesUnstrict();
       
        contracts.DelayedWETH = address(delayedWETH);
        ChainAssertions.checkDelayedWETH({ _contracts: contracts, _cfg: cfg, _isProxy: false, _expectedOwner: address(0) });

        return delayedWETH;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2K : Deploy PreimageOracle Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402K_DeployPreimageOracleScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

The default value of preimageOracleMinProposalSize is 1800000.

The default value of preimageOracleChallengePeriod is 86400.

Contract Settings

Smart Contract:
PreimageOracle.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@redprint-core/cannon/libraries/CannonErrors.sol";
import "@redprint-core/cannon/libraries/CannonTypes.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {LibKeccak} from "@redprint-lib-keccak/LibKeccak.sol";
import {PreimageKeyLib} from "@redprint-core/cannon/PreimageKeyLib.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol
contract PreimageOracle is ISemver {
    ////////////////////////////////////////////////////////////////
    //                   Constants & Immutables                   //
    ////////////////////////////////////////////////////////////////

    /// @notice The duration of the large preimage proposal challenge period.
    uint256 internal immutable CHALLENGE_PERIOD;
    /// @notice The minimum size of a preimage that can be proposed in the large preimage path.
    uint256 internal immutable MIN_LPP_SIZE_BYTES;
    /// @notice The minimum bond size for large preimage proposals.
    uint256 public constant MIN_BOND_SIZE = 0.25 ether;
    /// @notice The depth of the keccak256 merkle tree. Supports up to 65,536 keccak blocks, or ~8.91MB preimages.
    uint256 public constant KECCAK_TREE_DEPTH = 16;
    /// @notice The maximum number of keccak blocks that can fit into the merkle tree.
    uint256 public constant MAX_LEAF_COUNT = 2 ** KECCAK_TREE_DEPTH - 1;
    /// @notice The reserved gas for precompile call setup.
    uint256 public constant PRECOMPILE_CALL_RESERVED_GAS = 100_000;
    /// @notice The semantic version of the Preimage Oracle contract.
    /// @custom:semver 1.1.3-beta.5
    string public constant version = "1.1.3-beta.5";
    ////////////////////////////////////////////////////////////////
    //                 Authorized Preimage Parts                  //
    ////////////////////////////////////////////////////////////////

    /// @notice Mapping of pre-image keys to pre-image lengths.
    mapping(bytes32 => uint256) public preimageLengths;
    /// @notice Mapping of pre-image keys to pre-image offsets to pre-image parts.
    mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts;
    /// @notice Mapping of pre-image keys to pre-image part offsets to preimage preparedness.
    mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk;
    ////////////////////////////////////////////////////////////////
    //                  Large Preimage Proposals                  //
    ////////////////////////////////////////////////////////////////

    /// @notice A raw leaf of the large preimage proposal merkle tree.
    struct Leaf {
        /// @notice The input absorbed for the block, exactly 136 bytes.
        bytes input;
        /// @notice The index of the block in the absorption process.
        uint256 index;
        /// @notice The hash of the internal state after absorbing the input.
        bytes32 stateCommitment;
    }

    /// @notice Unpacked keys for large preimage proposals.
    struct LargePreimageProposalKeys {
        /// @notice The claimant of the large preimage proposal.
        address claimant;
        /// @notice The UUID of the large preimage proposal.
        uint256 uuid;
    }

    /// @notice Static padding hashes. These values are persisted in storage, but are entirely immutable
    ///         after the constructor's execution.
    bytes32[KECCAK_TREE_DEPTH] public zeroHashes;
    /// @notice Append-only array of large preimage proposals for off-chain reference.
    LargePreimageProposalKeys[] public proposals;
    /// @notice Mapping of claimants to proposal UUIDs to the current branch path of the merkleization process.
    mapping(address => mapping(uint256 => bytes32[KECCAK_TREE_DEPTH])) public proposalBranches;
    /// @notice Mapping of claimants to proposal UUIDs to the timestamp of creation of the proposal as well as the
    /// challenged status.
    mapping(address => mapping(uint256 => LPPMetaData)) public proposalMetadata;
    /// @notice Mapping of claimants to proposal UUIDs to bond amounts.
    mapping(address => mapping(uint256 => uint256)) public proposalBonds;
    /// @notice Mapping of claimants to proposal UUIDs to the preimage part picked up during the absorbtion process.
    mapping(address => mapping(uint256 => bytes32)) public proposalParts;
    /// @notice Mapping of claimants to proposal UUIDs to blocks which leaves were added to the merkle tree.
    mapping(address => mapping(uint256 => uint64[])) public proposalBlocks;

    constructor(uint256 _minProposalSize, uint256 _challengePeriod) {
        MIN_LPP_SIZE_BYTES = _minProposalSize;
        CHALLENGE_PERIOD = _challengePeriod;

        // Make sure challenge period fits within uint64 so that it can safely be used within the
        // FaultDisputeGame contract to compute clock extensions. Adding this check is simpler than
        // changing the existing contract ABI.
        require(_challengePeriod <= type(uint64).max, "PreimageOracle: challenge period too large");

        // Compute hashes in empty sparse Merkle tree. The first hash is not set, and kept as zero as the identity.
        for (uint256 height = 0; height < KECCAK_TREE_DEPTH - 1; height++) {
            zeroHashes[height + 1] = keccak256(abi.encodePacked(zeroHashes[height], zeroHashes[height]));
        }
    }

    function readPreimage(bytes32 _key, uint256 _offset)
        external
        view
        returns (bytes32 dat_, uint256 datLen_)
    {
        require(preimagePartOk[_key][_offset], "pre-image must exist");

        // Calculate the length of the pre-image data
        // Add 8 for the length-prefix part
        datLen_ = 32;
        uint256 length = preimageLengths[_key];
        if (_offset + 32 >= length + 8) {
            datLen_ = length + 8 - _offset;
        }

        // Retrieve the pre-image data
        dat_ = preimageParts[_key][_offset];
    }

    function loadLocalData(uint256 _ident, bytes32 _localContext, bytes32 _word, uint256 _size, uint256 _partOffset)
        external
        returns (bytes32 key_)
    {
        // Compute the localized key from the given local identifier.
        key_ = PreimageKeyLib.localizeIdent(_ident, _localContext);

        // Revert if the given part offset is not within bounds.
        if (_partOffset >= _size + 8 || _size > 32) {
            revert PartOffsetOOB();
        }

        // Prepare the local data part at the given offset
        bytes32 part;
        assembly {
            // Clean the memory in [0x20, 0x40)
            mstore(0x20, 0x00)

            // Store the full local data in scratch space.
            mstore(0x00, shl(192, _size))
            mstore(0x08, _word)

            // Prepare the local data part at the requested offset.
            part := mload(_partOffset)
        }

        // Store the first part with '_partOffset'.
        preimagePartOk[key_][_partOffset] = true;
        preimageParts[key_][_partOffset] = part;
        // Assign the length of the preimage at the localized key.
        preimageLengths[key_] = _size;
    }

    function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage)
        external
    {
        uint256 size;
        bytes32 key;
        bytes32 part;
        assembly {
            // len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44
            size := calldataload(0x44)

            // revert if part offset >= size+8 (i.e. parts must be within bounds)
            if iszero(lt(_partOffset, add(size, 8))) {
                // Store "PartOffsetOOB()"
                mstore(0x00, 0xfe254987)
                // Revert with "PartOffsetOOB()"
                revert(0x1c, 0x04)
            }
            // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory.
            let ptr := 0x80
            // put size as big-endian uint64 at start of pre-image
            mstore(ptr, shl(192, size))
            ptr := add(ptr, 0x08)
            // copy preimage payload into memory so we can hash and read it.
            calldatacopy(ptr, _preimage.offset, size)
            // Note that it includes the 8-byte big-endian uint64 length prefix.
            // this will be zero-padded at the end, since memory at end is clean.
            part := mload(add(sub(ptr, 0x08), _partOffset))
            let h := keccak256(ptr, size) // compute preimage keccak256 hash
            // mask out prefix byte, replace with type 2 byte
            key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x02))
        }
        preimagePartOk[key][_partOffset] = true;
        preimageParts[key][_partOffset] = part;
        preimageLengths[key] = size;
    }

    function loadSha256PreimagePart(uint256 _partOffset, bytes calldata _preimage)
        external
    {
        uint256 size;
        bytes32 key;
        bytes32 part;
        assembly {
            // len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44
            size := calldataload(0x44)

            // revert if part offset >= size+8 (i.e. parts must be within bounds)
            if iszero(lt(_partOffset, add(size, 8))) {
                // Store "PartOffsetOOB()"
                mstore(0, 0xfe254987)
                // Revert with "PartOffsetOOB()"
                revert(0x1c, 4)
            }
            // we leave solidity slots 0x40 and 0x60 untouched,
            // and everything after as scratch-memory.
            let ptr := 0x80
            // put size as big-endian uint64 at start of pre-image
            mstore(ptr, shl(192, size))
            ptr := add(ptr, 8)
            // copy preimage payload into memory so we can hash and read it.
            calldatacopy(ptr, _preimage.offset, size)
            // Note that it includes the 8-byte big-endian uint64 length prefix.
            // this will be zero-padded at the end, since memory at end is clean.
            part := mload(add(sub(ptr, 8), _partOffset))

            // compute SHA2-256 hash with pre-compile
            let success :=
                staticcall(
                    gas(), // Forward all available gas
                    0x02, // Address of SHA-256 precompile
                    ptr, // Start of input data in memory
                    size, // Size of input data
                    0, // Store output in scratch memory
                    0x20 // Output is always 32 bytes
                )
            // Check if the staticcall succeeded
            if iszero(success) { revert(0, 0) }
            let h := mload(0) // get return data
            // mask out prefix byte, replace with type 4 byte
            key := or(and(h, not(shl(248, 0xFF))), shl(248, 4))
        }
        preimagePartOk[key][_partOffset] = true;
        preimageParts[key][_partOffset] = part;
        preimageLengths[key] = size;
    }

    function loadBlobPreimagePart(uint256 _z, uint256 _y, bytes calldata _commitment, bytes calldata _proof, uint256 _partOffset)
        external
    {
        bytes32 key;
        bytes32 part;
        assembly {
            // Compute the versioned hash. The SHA2 hash of the 48 byte commitment is masked with the version byte,
            // which is currently 1. https://eips.ethereum.org/EIPS/eip-4844#parameters
            // SAFETY: We're only reading 48 bytes from '_commitment' into scratch space, so we're not reading into the
            //         free memory ptr region. Since the exact number of btyes that is copied into scratch space is
            //         the same size as the hash input, there's no concern of dirty memory being read into the hash
            //         input.
            calldatacopy(0x00, _commitment.offset, 0x30)
            let success := staticcall(gas(), 0x02, 0x00, 0x30, 0x00, 0x20)
            if iszero(success) {
                // Store the "ShaFailed()" error selector.
                mstore(0x00, 0xf9112969)
                // revert with "ShaFailed()"
                revert(0x1C, 0x04)
            }
            // Set the 'VERSIONED_HASH_VERSION_KZG' byte = 1 in the high-order byte of the hash.
            let versionedHash := or(and(mload(0x00), not(shl(248, 0xFF))), shl(248, 0x01))

            // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory.
            let ptr := 0x80

            // Load the inputs for the point evaluation precompile into memory. The inputs to the point evaluation
            // precompile are packed, and not supposed to be ABI-encoded.
            mstore(ptr, versionedHash)
            mstore(add(ptr, 0x20), _z)
            mstore(add(ptr, 0x40), _y)
            calldatacopy(add(ptr, 0x60), _commitment.offset, 0x30)
            calldatacopy(add(ptr, 0x90), _proof.offset, 0x30)

            // Verify the KZG proof by calling the point evaluation precompile. If the proof is invalid, the precompile
            // will revert.
            success :=
                staticcall(
                    gas(), // forward all gas
                    0x0A, // point evaluation precompile address
                    ptr, // input ptr
                    0xC0, // input size = 192 bytes
                    0x00, // output ptr
                    0x00 // output size
                )
            if iszero(success) {
                // Store the "InvalidProof()" error selector.
                mstore(0x00, 0x09bde339)
                // revert with "InvalidProof()"
                revert(0x1C, 0x04)
            }

            // revert if part offset >= 32+8 (i.e. parts must be within bounds)
            if iszero(lt(_partOffset, 0x28)) {
                // Store "PartOffsetOOB()"
                mstore(0x00, 0xfe254987)
                // Revert with "PartOffsetOOB()"
                revert(0x1C, 0x04)
            }
            // Clean the word at 'ptr + 0x28' to ensure that data out of bounds of the preimage is zero, if the part
            // offset requires a partial read.
            mstore(add(ptr, 0x28), 0x00)
            // put size (32) as a big-endian uint64 at start of pre-image
            mstore(ptr, shl(192, 0x20))
            // copy preimage payload into memory so we can hash and read it.
            mstore(add(ptr, 0x08), _y)
            // Note that it includes the 8-byte big-endian uint64 length prefix. This will be zero-padded at the end,
            // since memory at end is guaranteed to be clean.
            part := mload(add(ptr, _partOffset))

            // Compute the key: 'keccak256(commitment ++ z)'. Since the exact number of btyes that is copied into
            // scratch space is the same size as the hash input, there's no concern of dirty memory being read into
            // the hash input.
            calldatacopy(ptr, _commitment.offset, 0x30)
            mstore(add(ptr, 0x30), _z)
            let h := keccak256(ptr, 0x50)
            // mask out prefix byte, replace with type 5 byte
            key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x05))
        }
        preimagePartOk[key][_partOffset] = true;
        preimageParts[key][_partOffset] = part;
        preimageLengths[key] = 32;
    }

    function loadPrecompilePreimagePart(uint256 _partOffset, address _precompile, uint64 _requiredGas, bytes calldata _input)
        external
    {
        bytes32 res;
        bytes32 key;
        bytes32 part;
        uint256 size;
        assembly {
            // we leave solidity slots 0x40 and 0x60 untouched, and everything after as scratch-memory.
            let ptr := 0x80

            // copy precompile address, requiredGas, and input into memory to compute the key
            mstore(ptr, shl(96, _precompile))
            mstore(add(ptr, 20), shl(192, _requiredGas))
            calldatacopy(add(28, ptr), _input.offset, _input.length)
            // compute the hash
            let h := keccak256(ptr, add(28, _input.length))
            // mask out prefix byte, replace with type 6 byte
            key := or(and(h, not(shl(248, 0xFF))), shl(248, 0x06))

            // Check if the precompile call has at least the required gas.
            // This assumes there are no further memory expansion costs until after the staticall on the precompile
            // Also assumes that the gas expended in setting up the staticcall is less than PRECOMPILE_CALL_RESERVED_GAS
            // require(gas() >= (requiredGas * 64 / 63) + reservedGas)
            if lt(mul(gas(), 63), add(mul(_requiredGas, 64), mul(PRECOMPILE_CALL_RESERVED_GAS, 63))) {
                // Store "NotEnoughGas()"
                mstore(0, 0xdd629f86)
                revert(0x1c, 4)
            }

            // Call the precompile to get the result.
            // SAFETY: Given the above gas check, the staticall cannot fail due to insufficient gas.
            res :=
                staticcall(
                    gas(), // forward all gas
                    _precompile,
                    add(28, ptr), // input ptr
                    _input.length,
                    0x0, // Unused as we don't copy anything
                    0x00 // don't copy anything
                )

            size := add(1, returndatasize())
            // revert if part offset >= size+8 (i.e. parts must be within bounds)
            if iszero(lt(_partOffset, add(size, 8))) {
                // Store "PartOffsetOOB()"
                mstore(0, 0xfe254987)
                // Revert with "PartOffsetOOB()"
                revert(0x1c, 4)
            }

            // Reuse the 'ptr' to store the preimage part: <sizePrefix ++ precompileStatus ++ returrnData>
            // put size as big-endian uint64 at start of pre-image
            mstore(ptr, shl(192, size))
            ptr := add(ptr, 0x08)

            // write precompile result status to the first byte of 'ptr'
            mstore8(ptr, res)
            // write precompile return data to the rest of 'ptr'
            returndatacopy(add(ptr, 0x01), 0x0, returndatasize())

            // compute part given ofset
            part := mload(add(sub(ptr, 0x08), _partOffset))
        }
        preimagePartOk[key][_partOffset] = true;
        preimageParts[key][_partOffset] = part;
        preimageLengths[key] = size;
    }

    function proposalCount() external view returns (uint256 count_) {
        count_ = proposals.length;
    }

    function proposalBlocksLen(address _claimant, uint256 _uuid)
        external
        view
        returns (uint256 len_)
    {
        len_ = proposalBlocks[_claimant][_uuid].length;
    }

    function challengePeriod() external view returns (uint256 challengePeriod_) {
        challengePeriod_ = CHALLENGE_PERIOD;
    }

    function minProposalSize() external view returns (uint256 minProposalSize_) {
        minProposalSize_ = MIN_LPP_SIZE_BYTES;
    }

    function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize)
        external payable
    {
        // The bond provided must be at least 'MIN_BOND_SIZE'.
        if (msg.value < MIN_BOND_SIZE) revert InsufficientBond();

        // The caller of 'addLeavesLPP' must be an EOA, so that the call inputs are always available in block bodies.
        if (msg.sender != tx.origin) revert NotEOA();

        // The part offset must be within the bounds of the claimed size + 8.
        if (_partOffset >= _claimedSize + 8) revert PartOffsetOOB();

        // The claimed size must be at least 'MIN_LPP_SIZE_BYTES'.
        if (_claimedSize < MIN_LPP_SIZE_BYTES) revert InvalidInputSize();

        // Initialize the proposal metadata.
        LPPMetaData metaData = proposalMetadata[msg.sender][_uuid];

        // Revert if the proposal has already been initialized. 0-size preimages are *not* allowed.
        if (metaData.claimedSize() != 0) revert AlreadyInitialized();

        proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize);
        proposals.push(LargePreimageProposalKeys(msg.sender, _uuid));

        // Assign the bond to the proposal.
        proposalBonds[msg.sender][_uuid] = msg.value;
    }

    function addLeavesLPP(uint256 _uuid, uint256 _inputStartBlock, bytes calldata _input, bytes32[] calldata _stateCommitments, bool _finalize)
        external
    {
        // If we're finalizing, pad the input for the submitter. If not, copy the input into memory verbatim.
        bytes memory input;
        if (_finalize) {
            input = LibKeccak.pad(_input);
        } else {
            input = _input;
        }

        // Pull storage variables onto the stack / into memory for operations.
        bytes32[KECCAK_TREE_DEPTH] memory branch = proposalBranches[msg.sender][_uuid];
        LPPMetaData metaData = proposalMetadata[msg.sender][_uuid];
        uint256 blocksProcessed = metaData.blocksProcessed();

        // The caller of 'addLeavesLPP' must be an EOA.
        // Note: This check may break if EIPs like EIP-3074 are introduced. We may query the data in the logs if this
        // is the case.
        if (msg.sender != tx.origin) revert NotEOA();

        // Revert if the proposal has not been initialized. 0-size preimages are *not* allowed.
        if (metaData.claimedSize() == 0) revert NotInitialized();

        // Revert if the proposal has already been finalized. No leaves can be added after this point.
        if (metaData.timestamp() != 0) revert AlreadyFinalized();

        // Revert if the starting block is not the next block to be added. This is to aid submitters in ensuring that
        // they don't corrupt an in-progress proposal by submitting input out of order.
        if (blocksProcessed != _inputStartBlock) revert WrongStartingBlock();

        // Attempt to extract the preimage part from the input data, if the part offset is present in the current
        // chunk of input. This function has side effects, and will persist the preimage part to the caller's large
        // preimage proposal storage if the part offset is present in the input data.
        _extractPreimagePart(_input, _uuid, _finalize, metaData);

        assembly {
            let inputLen := mload(input)
            let inputPtr := add(input, 0x20)

            // The input length must be a multiple of 136 bytes
            // The input length / 136 must be equal to the number of state commitments.
            if or(mod(inputLen, 136), iszero(eq(_stateCommitments.length, div(inputLen, 136)))) {
                // Store "InvalidInputSize()" error selector
                mstore(0x00, 0x7b1daf1)
                revert(0x1C, 0x04)
            }

            // Allocate a hashing buffer the size of the leaf preimage.
            let hashBuf := mload(0x40)
            mstore(0x40, add(hashBuf, 0xC8))

            for { let i := 0 } lt(i, inputLen) { i := add(i, 136) } {
                // Copy the leaf preimage into the hashing buffer.
                let inputStartPtr := add(inputPtr, i)
                mstore(hashBuf, mload(inputStartPtr))
                mstore(add(hashBuf, 0x20), mload(add(inputStartPtr, 0x20)))
                mstore(add(hashBuf, 0x40), mload(add(inputStartPtr, 0x40)))
                mstore(add(hashBuf, 0x60), mload(add(inputStartPtr, 0x60)))
                mstore(add(hashBuf, 0x80), mload(add(inputStartPtr, 0x80)))
                mstore(add(hashBuf, 136), blocksProcessed)
                mstore(add(hashBuf, 168), calldataload(add(_stateCommitments.offset, shl(0x05, div(i, 136)))))

                // Hash the leaf preimage to get the node to add.
                let node := keccak256(hashBuf, 0xC8)

                // Increment the number of blocks processed.
                blocksProcessed := add(blocksProcessed, 0x01)

                // Add the node to the tree.
                let size := blocksProcessed
                for { let height := 0x00 } lt(height, shl(0x05, KECCAK_TREE_DEPTH)) { height := add(height, 0x20) } {
                    if and(size, 0x01) {
                        mstore(add(branch, height), node)
                        break
                    }

                    // Hash the node at 'height' in the branch and the node together.
                    mstore(0x00, mload(add(branch, height)))
                    mstore(0x20, node)
                    node := keccak256(0x00, 0x40)
                    size := shr(0x01, size)
                }
            }
        }

        // Do not allow for posting preimages larger than the merkle tree can support. The incremental merkle tree
        // algorithm only supports 2**height - 1 leaves, the right most leaf must always be kept empty.
        // Reference: https://daejunpark.github.io/papers/deposit.pdf - Page 10, Section 5.1.
        if (blocksProcessed > MAX_LEAF_COUNT) revert TreeSizeOverflow();

        // Update the proposal metadata to include the number of blocks processed and total bytes processed.
        metaData = metaData.setBlocksProcessed(uint32(blocksProcessed)).setBytesProcessed(
            uint32(_input.length + metaData.bytesProcessed())
        );
        // If the proposal is being finalized, set the timestamp to the current block timestamp. This begins the
        // challenge period, which must be waited out before the proposal can be finalized.
        if (_finalize) {
            metaData = metaData.setTimestamp(uint64(block.timestamp));

            // If the number of bytes processed is not equal to the claimed size, the proposal cannot be finalized.
            if (metaData.bytesProcessed() != metaData.claimedSize()) revert InvalidInputSize();
        }

        // Perist the latest branch to storage.
        proposalBranches[msg.sender][_uuid] = branch;
        // Persist the block number that these leaves were added in. This assists off-chain observers in reconstructing
        // the proposal merkle tree by querying block bodies.
        proposalBlocks[msg.sender][_uuid].push(uint64(block.number));
        // Persist the updated metadata to storage.
        proposalMetadata[msg.sender][_uuid] = metaData;

        // Clobber memory and 'log0' all calldata. This is safe because there is no execution afterwards within
        // this callframe.
        assembly {
            mstore(0x00, shl(96, caller()))
            calldatacopy(0x14, 0x00, calldatasize())
            log0(0x00, add(0x14, calldatasize()))
        }
    }

    function challengeLPP(address _claimant, uint256 _uuid, LibKeccak.StateMatrix memory _stateMatrix, Leaf calldata _preState, bytes32[] calldata _preStateProof, Leaf calldata _postState, bytes32[] calldata _postStateProof)
        external
    {
        // Verify that both leaves are present in the merkle tree.
        bytes32 root = getTreeRootLPP(_claimant, _uuid);
        if (
            !(
                _verify(_preStateProof, root, _preState.index, _hashLeaf(_preState))
                    && _verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))
            )
        ) revert InvalidProof();

        // Verify that the prestate passed matches the intermediate state claimed in the leaf.
        if (keccak256(abi.encode(_stateMatrix)) != _preState.stateCommitment) revert InvalidPreimage();

        // Verify that the pre/post state are contiguous.
        if (_preState.index + 1 != _postState.index) revert StatesNotContiguous();

        // Absorb and permute the input bytes.
        LibKeccak.absorb(_stateMatrix, _postState.input);
        LibKeccak.permutation(_stateMatrix);

        // Verify that the post state hash doesn't match the expected hash.
        if (keccak256(abi.encode(_stateMatrix)) == _postState.stateCommitment) revert PostStateMatches();

        // Mark the keccak claim as countered.
        proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true);

        // Pay out the bond to the challenger.
        _payoutBond(_claimant, _uuid, msg.sender);
    }

    function squeezeLPP(address _claimant, uint256 _uuid, LibKeccak.StateMatrix memory _stateMatrix, Leaf calldata _preState, bytes32[] calldata _preStateProof, Leaf calldata _postState, bytes32[] calldata _postStateProof)
        external
    {
        LPPMetaData metaData = proposalMetadata[_claimant][_uuid];

        // Check if the proposal was countered.
        if (metaData.countered()) revert BadProposal();

        // Check if the proposal has been finalized at all.
        if (metaData.timestamp() == 0) revert ActiveProposal();

        // Check if the challenge period has passed since the proposal was finalized.
        if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal();

        // Verify that both leaves are present in the merkle tree.
        bytes32 root = getTreeRootLPP(_claimant, _uuid);
        if (
            !(
                _verify(_preStateProof, root, _preState.index, _hashLeaf(_preState))
                    && _verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))
            )
        ) revert InvalidProof();

        // Verify that the prestate passed matches the intermediate state claimed in the leaf.
        if (keccak256(abi.encode(_stateMatrix)) != _preState.stateCommitment) revert InvalidPreimage();

        // Verify that the pre/post state are contiguous.
        if (_preState.index + 1 != _postState.index || _postState.index != metaData.blocksProcessed() - 1) {
            revert StatesNotContiguous();
        }

        // Absorb and permute the input bytes. We perform no final verification on the state matrix here, since the
        // proposal has passed the challenge period and is considered valid.
        LibKeccak.absorb(_stateMatrix, _postState.input);
        LibKeccak.permutation(_stateMatrix);
        bytes32 finalDigest = LibKeccak.squeeze(_stateMatrix);
        assembly {
            finalDigest := or(and(finalDigest, not(shl(248, 0xFF))), shl(248, 0x02))
        }

        // Write the preimage part to the authorized preimage parts mapping.
        uint256 partOffset = metaData.partOffset();
        preimagePartOk[finalDigest][partOffset] = true;
        preimageParts[finalDigest][partOffset] = proposalParts[_claimant][_uuid];
        preimageLengths[finalDigest] = metaData.claimedSize();

        // Pay out the bond to the claimant.
        _payoutBond(_claimant, _uuid, _claimant);
    }

    function getTreeRootLPP(address _owner, uint256 _uuid)
        public
        view
        returns (bytes32 treeRoot_)
    {
        uint256 size = proposalMetadata[_owner][_uuid].blocksProcessed();
        for (uint256 height = 0; height < KECCAK_TREE_DEPTH; height++) {
            if ((size & 1) == 1) {
                treeRoot_ = keccak256(abi.encode(proposalBranches[_owner][_uuid][height], treeRoot_));
            } else {
                treeRoot_ = keccak256(abi.encode(treeRoot_, zeroHashes[height]));
            }
            size >>= 1;
        }
    }

    function _extractPreimagePart(bytes calldata _input, uint256 _uuid, bool _finalize, LPPMetaData _metaData)
        internal
    {
        uint256 offset = _metaData.partOffset();
        uint256 claimedSize = _metaData.claimedSize();
        uint256 currentSize = _metaData.bytesProcessed();

        // Check if the part offset is present in the input data being posted. If it is, assign the part to the mapping.
        if (offset < 8 && currentSize == 0) {
            bytes32 preimagePart;
            assembly {
                mstore(0x00, shl(192, claimedSize))
                mstore(0x08, calldataload(_input.offset))
                preimagePart := mload(offset)
            }
            proposalParts[msg.sender][_uuid] = preimagePart;
        } else if (offset >= 8 && (offset = offset - 8) >= currentSize && offset < currentSize + _input.length) {
            uint256 relativeOffset = offset - currentSize;

            // Revert if the full preimage part is not available in the data we're absorbing. The submitter must
            // supply data that contains the full preimage part so that no partial preimage parts are stored in the
            // oracle. Partial parts are *only* allowed at the tail end of the preimage, where no more data is available
            // to be absorbed.
            if (relativeOffset + 32 >= _input.length && !_finalize) revert PartOffsetOOB();

            // If the preimage part is in the data we're about to absorb, persist the part to the caller's large
            // preimaage metadata.
            bytes32 preimagePart;
            assembly {
                preimagePart := calldataload(add(_input.offset, relativeOffset))
            }
            proposalParts[msg.sender][_uuid] = preimagePart;
        }
    }

    function _verify(bytes32[] calldata _proof, bytes32 _root, uint256 _index, bytes32 _leaf)
        internal
        pure
        returns (bool isValid_)
    {
        /// @solidity memory-safe-assembly
        assembly {
            function hashTwo(a, b) -> hash {
                mstore(0x00, a)
                mstore(0x20, b)
                hash := keccak256(0x00, 0x40)
            }

            let value := _leaf
            for { let i := 0x00 } lt(i, KECCAK_TREE_DEPTH) { i := add(i, 0x01) } {
                let branchValue := calldataload(add(_proof.offset, shl(0x05, i)))

                switch and(shr(i, _index), 0x01)
                case 1 { value := hashTwo(branchValue, value) }
                default { value := hashTwo(value, branchValue) }
            }

            isValid_ := eq(value, _root)
        }
    }

    function _payoutBond(address _claimant, uint256 _uuid, address _to) internal {
        // Pay out the bond to the claimant.
        uint256 bond = proposalBonds[_claimant][_uuid];
        proposalBonds[_claimant][_uuid] = 0;
        (bool success,) = _to.call{ value: bond }("");
        if (!success) revert BondTransferFailed();
    }

    function _hashLeaf(Leaf calldata _leaf) internal pure returns (bytes32 leaf_) {
        leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment));
    }
}

        
      
Deploy Script:
402K_DeployPreimageOracleScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {PreimageOracle} from "@redprint-core/cannon/PreimageOracle.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployPreimageOracleScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    PreimageOracle preimageOracle;

    function deploy() external returns (PreimageOracle) {
        DeployConfig cfg = deployer.getConfig();

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        preimageOracle = deployer.deploy_PreimageOracle("PreimageOracle", cfg.preimageOracleMinProposalSize(), cfg.preimageOracleChallengePeriod(), options);

        return preimageOracle;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2L : Deploy MIPS Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402L_DeployMIPSScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
MIPS.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IPreimageOracle} from "@redprint-core/cannon/interfaces/IPreimageOracle.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {InvalidRMWInstruction} from "@redprint-core/cannon/libraries/CannonErrors.sol";
import {MIPSInstructions as ins} from "@redprint-core/cannon/libraries/MIPSInstructions.sol";
import {MIPSMemory} from "@redprint-core/cannon/libraries/MIPSMemory.sol";
import {MIPSState as st} from "@redprint-core/cannon/libraries/MIPSState.sol";
import {MIPSSyscalls as sys} from "@redprint-core/cannon/libraries/MIPSSyscalls.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/cannon/MIPS.sol
contract MIPS is ISemver {
    /// @notice Stores the VM state.
    ///         Total state size: 32 + 32 + 6 * 4 + 1 + 1 + 8 + 32 * 4 = 226 bytes
    ///         If nextPC != pc + 4, then the VM is executing a branch/jump delay slot.
    struct State {
        bytes32 memRoot;
        bytes32 preimageKey;
        uint32 preimageOffset;
        uint32 pc;
        uint32 nextPC;
        uint32 lo;
        uint32 hi;
        uint32 heap;
        uint8 exitCode;
        bool exited;
        uint64 step;
        uint32[32] registers;
    }
    /// @notice The semantic version of the MIPS contract.
    /// @custom:semver 1.2.1-beta.3
    string public constant version = "1.2.1-beta.3";
    /// @notice The preimage oracle contract.
    IPreimageOracle internal immutable ORACLE;
    // The offset of the start of proof calldata (_proof.offset) in the step() function
    uint256 internal constant STEP_PROOF_OFFSET = 420;

    constructor(IPreimageOracle _oracle) {
        ORACLE = _oracle;
    }

    function oracle() external view returns (IPreimageOracle oracle_) {
        oracle_ = ORACLE;
    }

    function outputState() internal returns (bytes32 out_) {
        assembly {
            // copies 'size' bytes, right-aligned in word at 'from', to 'to', incl. trailing data
            function copyMem(from, to, size) -> fromOut, toOut {
                mstore(to, mload(add(from, sub(32, size))))
                fromOut := add(from, 32)
                toOut := add(to, size)
            }

            // From points to the MIPS State
            let from := 0x80

            // Copy to the free memory pointer
            let start := mload(0x40)
            let to := start

            // Copy state to free memory
            from, to := copyMem(from, to, 32) // memRoot
            from, to := copyMem(from, to, 32) // preimageKey
            from, to := copyMem(from, to, 4) // preimageOffset
            from, to := copyMem(from, to, 4) // pc
            from, to := copyMem(from, to, 4) // nextPC
            from, to := copyMem(from, to, 4) // lo
            from, to := copyMem(from, to, 4) // hi
            from, to := copyMem(from, to, 4) // heap
            let exitCode := mload(from)
            from, to := copyMem(from, to, 1) // exitCode
            let exited := mload(from)
            from, to := copyMem(from, to, 1) // exited
            from, to := copyMem(from, to, 8) // step
            from := add(from, 32) // offset to registers

            // Verify that the value of exited is valid (0 or 1)
            if gt(exited, 1) {
                // revert InvalidExitedValue();
                let ptr := mload(0x40)
                mstore(ptr, shl(224, 0x0136cc76))
                revert(ptr, 0x04)
            }

            // Copy registers
            for { let i := 0 } lt(i, 32) { i := add(i, 1) } { from, to := copyMem(from, to, 4) }

            // Clean up end of memory
            mstore(to, 0)

            // Log the resulting MIPS state, for debugging
            log0(start, sub(to, start))

            // Determine the VM status
            let status := 0
            switch exited
            case 1 {
                switch exitCode
                // VMStatusValid
                case 0 { status := 0 }
                // VMStatusInvalid
                case 1 { status := 1 }
                // VMStatusPanic
                default { status := 2 }
            }
            // VMStatusUnfinished
            default { status := 3 }

            // Compute the hash of the resulting MIPS state and set the status byte
            out_ := keccak256(start, sub(to, start))
            out_ := or(and(not(shl(248, 0xFF)), out_), shl(248, status))
        }
    }

    function handleSyscall(bytes32 _localContext)
        internal
        returns (bytes32 out_)
    {
        unchecked {
            // Load state from memory
            State memory state;
            assembly {
                state := 0x80
            }

            // Load the syscall numbers and args from the registers
            (uint32 syscall_no, uint32 a0, uint32 a1, uint32 a2,) = sys.getSyscallArgs(state.registers);

            uint32 v0 = 0;
            uint32 v1 = 0;

            if (syscall_no == sys.SYS_MMAP) {
                (v0, v1, state.heap) = sys.handleSysMmap(a0, a1, state.heap);
            } else if (syscall_no == sys.SYS_BRK) {
                // brk: Returns a fixed address for the program break at 0x40000000
                v0 = sys.PROGRAM_BREAK;
            } else if (syscall_no == sys.SYS_CLONE) {
                // clone (not supported) returns 1
                v0 = 1;
            } else if (syscall_no == sys.SYS_EXIT_GROUP) {
                // exit group: Sets the Exited and ExitCode states to true and argument 0.
                state.exited = true;
                state.exitCode = uint8(a0);
                return outputState();
            } else if (syscall_no == sys.SYS_READ) {
                sys.SysReadParams memory args = sys.SysReadParams({
                    a0: a0,
                    a1: a1,
                    a2: a2,
                    preimageKey: state.preimageKey,
                    preimageOffset: state.preimageOffset,
                    localContext: _localContext,
                    oracle: ORACLE,
                    proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
                    memRoot: state.memRoot
                });
                (v0, v1, state.preimageOffset, state.memRoot,,) = sys.handleSysRead(args);
            } else if (syscall_no == sys.SYS_WRITE) {
                (v0, v1, state.preimageKey, state.preimageOffset) = sys.handleSysWrite({
                    _a0: a0,
                    _a1: a1,
                    _a2: a2,
                    _preimageKey: state.preimageKey,
                    _preimageOffset: state.preimageOffset,
                    _proofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
                    _memRoot: state.memRoot
                });
            } else if (syscall_no == sys.SYS_FCNTL) {
                (v0, v1) = sys.handleSysFcntl(a0, a1);
            }

            st.CpuScalars memory cpu = getCpuScalars(state);
            sys.handleSyscallUpdates(cpu, state.registers, v0, v1);
            setStateCpuScalars(state, cpu);

            out_ = outputState();
        }
    }

    function step(bytes calldata _stateData, bytes calldata _proof, bytes32 _localContext)
        public
        returns (bytes32)
    {
        unchecked {
            State memory state;

            // Packed calldata is ~6 times smaller than state size
            assembly {
                if iszero(eq(state, 0x80)) {
                    // expected state mem offset check
                    revert(0, 0)
                }
                if iszero(eq(mload(0x40), shl(5, 48))) {
                    // expected memory check
                    revert(0, 0)
                }
                if iszero(eq(_stateData.offset, 132)) {
                    // 32*4+4=132 expected state data offset
                    revert(0, 0)
                }
                if iszero(eq(_proof.offset, STEP_PROOF_OFFSET)) {
                    // 132+32+256=420 expected proof offset
                    revert(0, 0)
                }

                function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut {
                    // calldata is packed, thus starting left-aligned, shift-right to pad and right-align
                    let w := shr(shl(3, sub(32, size)), calldataload(callOffset))
                    mstore(memOffset, w)
                    callOffsetOut := add(callOffset, size)
                    memOffsetOut := add(memOffset, 32)
                }

                // Unpack state from calldata into memory
                let c := _stateData.offset // calldata offset
                let m := 0x80 // mem offset
                c, m := putField(c, m, 32) // memRoot
                c, m := putField(c, m, 32) // preimageKey
                c, m := putField(c, m, 4) // preimageOffset
                c, m := putField(c, m, 4) // pc
                c, m := putField(c, m, 4) // nextPC
                c, m := putField(c, m, 4) // lo
                c, m := putField(c, m, 4) // hi
                c, m := putField(c, m, 4) // heap
                c, m := putField(c, m, 1) // exitCode
                c, m := putField(c, m, 1) // exited
                let exited := mload(sub(m, 32))
                c, m := putField(c, m, 8) // step

                // Verify that the value of exited is valid (0 or 1)
                if gt(exited, 1) {
                    // revert InvalidExitedValue();
                    let ptr := mload(0x40)
                    mstore(ptr, shl(224, 0x0136cc76))
                    revert(ptr, 0x04)
                }

                // Compiler should have done this already
                if iszero(eq(mload(m), add(m, 32))) {
                    // expected registers offset check
                    revert(0, 0)
                }

                // Unpack register calldata into memory
                m := add(m, 32)
                for { let i := 0 } lt(i, 32) { i := add(i, 1) } { c, m := putField(c, m, 4) }
            }

            // Don't change state once exited
            if (state.exited) {
                return outputState();
            }

            state.step += 1;

            // instruction fetch
            uint256 insnProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 0);
            (uint32 insn, uint32 opcode, uint32 fun) =
                ins.getInstructionDetails(state.pc, state.memRoot, insnProofOffset);

            // Handle syscall separately
            // syscall (can read and write)
            if (opcode == 0 && fun == 0xC) {
                return handleSyscall(_localContext);
            }

            // Handle RMW (read-modify-write) ops
            if (opcode == ins.OP_LOAD_LINKED || opcode == ins.OP_STORE_CONDITIONAL) {
                return handleRMWOps(state, insn, opcode);
            }

            // Exec the rest of the step logic
            st.CpuScalars memory cpu = getCpuScalars(state);
            ins.CoreStepLogicParams memory coreStepArgs = ins.CoreStepLogicParams({
                cpu: cpu,
                registers: state.registers,
                memRoot: state.memRoot,
                memProofOffset: MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1),
                insn: insn,
                opcode: opcode,
                fun: fun
            });
            (state.memRoot,,) = ins.execMipsCoreStepLogic(coreStepArgs);
            setStateCpuScalars(state, cpu);

            return outputState();
        }
    }

    function handleRMWOps(State memory _state, uint32 _insn, uint32 _opcode)
        internal
        returns (bytes32)
    {
        unchecked {
            uint32 baseReg = (_insn >> 21) & 0x1F;
            uint32 base = _state.registers[baseReg];
            uint32 rtReg = (_insn >> 16) & 0x1F;
            uint32 offset = ins.signExtendImmediate(_insn);

            uint32 effAddr = (base + offset) & 0xFFFFFFFC;
            uint256 memProofOffset = MIPSMemory.memoryProofOffset(STEP_PROOF_OFFSET, 1);
            uint32 mem = MIPSMemory.readMem(_state.memRoot, effAddr, memProofOffset);

            uint32 retVal;
            if (_opcode == ins.OP_LOAD_LINKED) {
                retVal = mem;
            } else if (_opcode == ins.OP_STORE_CONDITIONAL) {
                uint32 val = _state.registers[rtReg];
                _state.memRoot = MIPSMemory.writeMem(effAddr, memProofOffset, val);
                retVal = 1; // 1 for success
            } else {
                revert InvalidRMWInstruction();
            }

            st.CpuScalars memory cpu = getCpuScalars(_state);
            ins.handleRd(cpu, _state.registers, rtReg, retVal, true);
            setStateCpuScalars(_state, cpu);

            return outputState();
        }
    }

    function getCpuScalars(State memory _state)
        internal
        pure
        returns (st.CpuScalars memory)
    {
        return st.CpuScalars({ pc: _state.pc, nextPC: _state.nextPC, lo: _state.lo, hi: _state.hi });
    }

    function setStateCpuScalars(State memory _state, st.CpuScalars memory _cpu)
        internal
        pure
    {
        _state.pc = _cpu.pc;
        _state.nextPC = _cpu.nextPC;
        _state.lo = _cpu.lo;
        _state.hi = _cpu.hi;
    }
}

        
      
Deploy Script:
402L_DeployMIPSScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {IPreimageOracle} from "@redprint-core/cannon/interfaces/IPreimageOracle.sol";
import {MIPS} from "@redprint-core/cannon/MIPS.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployMIPSScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    MIPS mips;

    function deploy() external returns (MIPS) {
        address preimageOracle = deployer.mustGetAddress("PreimageOracle");

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        mips = deployer.deploy_MIPS("Mips", IPreimageOracle(preimageOracle), options);

        return mips;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2M : Deploy AnchorStateRegistry Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402M_DeployAnchorStateRegistryScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
AnchorStateRegistry.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@redprint-core/dispute/lib/Types.sol";
import {IDisputeGame} from "@redprint-core/dispute/interfaces/IDisputeGame.sol";
import {IDisputeGameFactory} from "@redprint-core/dispute/interfaces/IDisputeGameFactory.sol";
import {IFaultDisputeGame} from "@redprint-core/dispute/interfaces/IFaultDisputeGame.sol";
import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {Unauthorized} from "@redprint-core/libraries/errors/CommonErrors.sol";
import {UnregisteredGame, InvalidGameStatus} from "@redprint-core/dispute/lib/Errors.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol
contract AnchorStateRegistry is Initializable, ISemver {
    /// @notice Describes an initial anchor state for a game type.
    struct StartingAnchorRoot {
        GameType gameType;
        OutputRoot outputRoot;
    }
    /// @notice Semantic version.
    /// @custom:semver 2.0.1-beta.3
    string public constant version = "2.0.1-beta.3";
    /// @notice DisputeGameFactory address.
    IDisputeGameFactory internal immutable DISPUTE_GAME_FACTORY;
    /// @notice Returns the anchor state for the given game type.
    mapping(GameType => OutputRoot) public anchors;
    /// @notice Address of the SuperchainConfig contract.
    ISuperchainConfig public superchainConfig;

    constructor(IDisputeGameFactory _disputeGameFactory) {
        DISPUTE_GAME_FACTORY = _disputeGameFactory;
        _disableInitializers();
    }

    function initialize(StartingAnchorRoot[] memory _startingAnchorRoots, ISuperchainConfig _superchainConfig)
        public
        initializer
    {
        for (uint256 i = 0; i < _startingAnchorRoots.length; i++) {
            StartingAnchorRoot memory startingAnchorRoot = _startingAnchorRoots[i];
            anchors[startingAnchorRoot.gameType] = startingAnchorRoot.outputRoot;
        }
        superchainConfig = _superchainConfig;
    }

    function disputeGameFactory() external view returns (IDisputeGameFactory) {
        return DISPUTE_GAME_FACTORY;
    }

    function tryUpdateAnchorState() external {
        // Grab the game and game data.
        IFaultDisputeGame game = IFaultDisputeGame(msg.sender);
        (GameType gameType, Claim rootClaim, bytes memory extraData) = game.gameData();

        // Grab the verified address of the game based on the game data.
        // slither-disable-next-line unused-return
        (IDisputeGame factoryRegisteredGame,) =
            DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData });

        // Must be a valid game.
        if (address(factoryRegisteredGame) != address(game)) revert UnregisteredGame();

        // No need to update anything if the anchor state is already newer.
        if (game.l2BlockNumber() <= anchors[gameType].l2BlockNumber) {
            return;
        }

        // Must be a game that resolved in favor of the state.
        if (game.status() != GameStatus.DEFENDER_WINS) {
            return;
        }

        // Actually update the anchor state.
        anchors[gameType] = OutputRoot({ l2BlockNumber: game.l2BlockNumber(), root: Hash.wrap(game.rootClaim().raw()) });
    }

    function setAnchorState(IFaultDisputeGame _game) external {
        if (msg.sender != superchainConfig.guardian()) revert Unauthorized();

        // Get the metadata of the game.
        (GameType gameType, Claim rootClaim, bytes memory extraData) = _game.gameData();

        // Grab the verified address of the game based on the game data.
        // slither-disable-next-line unused-return
        (IDisputeGame factoryRegisteredGame,) =
            DISPUTE_GAME_FACTORY.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData });

        // Must be a valid game.
        if (address(factoryRegisteredGame) != address(_game)) revert UnregisteredGame();

        // The game must have resolved in favor of the root claim.
        if (_game.status() != GameStatus.DEFENDER_WINS) revert InvalidGameStatus();

        // Update the anchor.
        anchors[gameType] =
            OutputRoot({ l2BlockNumber: _game.l2BlockNumber(), root: Hash.wrap(_game.rootClaim().raw()) });
    }
}

        
      
Deploy Script:
402M_DeployAnchorStateRegistryScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AnchorStateRegistry} from "@redprint-core/dispute/AnchorStateRegistry.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {IDisputeGameFactory} from "@redprint-core/dispute/interfaces/IDisputeGameFactory.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAnchorStateRegistryScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    AnchorStateRegistry anchorStateRegistry;

    function deploy() external returns (AnchorStateRegistry) {
        address disputeGameFactory = deployer.mustGetAddress("DisputeGameFactory");

        bytes32 _salt = DeployScript.implSalt();
        DeployOptions memory options = DeployOptions({salt:_salt});

        anchorStateRegistry = deployer.deploy_AnchorStateRegistry("AnchorStateRegistry", IDisputeGameFactory(disputeGameFactory), options);

        return anchorStateRegistry;
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

4.2N : Initialize Implementations

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402N_InitializeImplementationsScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

When configuring useFaultProofs==false, we will initialize OptimismPortal(Default). Otherwise, it is OptimismPortal2.

When configuring useCustomGasToken==false, we will use Ethereum as gas (Default) . Otherwise, dont forget to config it as same as cfg.customGasTokenAddress() in genesis file

When configuring l2OutputOracleStartingTimestamp in genesis file, dont forget to set it as lowerer than the current blocktimestamp on your environment.

Contract Settings

Initialize Implementations

OptimismPortal

SystemConfig

L1StandardBridge

L1ERC721Bridge

OptimismMintableERC20Factory

L1CrossDomainMessenger

L2OutputOracle

DisputeGameFactory

DelayedWETH

PermissionedDelayedWETH

AnchorStateRegistry

OpSec Management

Owner

Deploy Script:
402N_InitializeImplementationsScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AnchorStateRegistry} from "@redprint-core/dispute/AnchorStateRegistry.sol";
import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {Constants} from "@redprint-core/libraries/Constants.sol";
import {DelayedWETH} from "@redprint-core/dispute/DelayedWETH.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DisputeGameFactory} from "@redprint-core/dispute/DisputeGameFactory.sol";
import {GameTypes, OutputRoot, Hash} from "@redprint-core/dispute/lib/Types.sol";
import {GnosisSafe as Safe} from "@redprint-safe-contracts/GnosisSafe.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {IL1CrossDomainMessenger} from "@redprint-core/L1/interfaces/IL1CrossDomainMessenger.sol";
import {IL2OutputOracle} from "@redprint-core/L1/interfaces/IL2OutputOracle.sol";
import {IOptimismPortal} from "@redprint-core/L1/interfaces/IOptimismPortal.sol";
import {ISuperchainConfig} from "@redprint-core/L1/interfaces/ISuperchainConfig.sol";
import {ISystemConfig} from "@redprint-core/L1/interfaces/ISystemConfig.sol";
import {L1CrossDomainMessenger} from "@redprint-core/L1/L1CrossDomainMessenger.sol";
import {L1ERC721Bridge} from "@redprint-core/L1/L1ERC721Bridge.sol";
import {L1StandardBridge} from "@redprint-core/L1/L1StandardBridge.sol";
import {L2OutputOracle} from "@redprint-core/L1/L2OutputOracle.sol";
import {OptimismMintableERC20Factory} from "@redprint-core/universal/OptimismMintableERC20Factory.sol";
import {OptimismPortal} from "@redprint-core/L1/OptimismPortal.sol";
import {ProxyAdmin} from "@redprint-core/universal/ProxyAdmin.sol";
import {SafeScript} from "@redprint-deploy/safe-management/SafeScript.sol";
import {Script} from "@redprint-forge-std/Script.sol";
import {SystemConfig} from "@redprint-core/L1/SystemConfig.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {console} from "@redprint-forge-std/console.sol";

contract InitializeImplementationsScript is Script, SafeScript {
    IDeployer deployerProcedue;
    address public constant customGasTokenAddress = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function run() public {
        deployerProcedue = getDeployer();
        deployerProcedue.setAutoSave(true);

        console.log("Initializing implementations");

        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
            console.log("Pranking owner ...");
            vm.startPrank(owner);
            initializeOptimismPortal();
            initializeSystemConfig();
            initializeL1StandardBridge();
            initializeL1ERC721Bridge();
            initializeOptimismMintableERC20Factory();
            initializeL1CrossDomainMessenger();
            initializeL2OutputOracle();
            initializeDisputeGameFactory();
            initializeDelayedWETH();
            initializePermissionedDelayedWETH();
            initializeAnchorStateRegistry();
            console.log("Pranking Stopped ...");

            vm.stopPrank();
        } else {
            console.log("Broadcasting ...");
            vm.startBroadcast(owner);
            initializeOptimismPortal();
            initializeSystemConfig();
            initializeL1StandardBridge();
            initializeL1ERC721Bridge();
            initializeOptimismMintableERC20Factory();
            initializeL1CrossDomainMessenger();
            initializeL2OutputOracle();
            initializeDisputeGameFactory();
            initializeDelayedWETH();
            initializePermissionedDelayedWETH();
            initializeAnchorStateRegistry();
            console.log("Broadcasted");

            vm.stopBroadcast();
        }
    }

    function initializeOptimismPortal() internal {
        console.log("Upgrading and initializing OptimismPortal2 proxy");

        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address optimismPortalProxy = deployerProcedue.mustGetAddress("OptimismPortalProxy");
        address optimismPortal = deployerProcedue.mustGetAddress("OptimismPortal");
        address l2OutputOracleProxy = deployerProcedue.mustGetAddress("L2OutputOracleProxy");
        address systemConfigProxy = deployerProcedue.mustGetAddress("SystemConfigProxy");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");

        DeployConfig cfg = deployerProcedue.getConfig();

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,   
            _proxy: payable(optimismPortalProxy),
            _implementation: optimismPortal,
            _innerCallData: abi.encodeCall(
                OptimismPortal.initialize,
                    (
                        IL2OutputOracle(l2OutputOracleProxy),
                        ISystemConfig(systemConfigProxy),
                        ISuperchainConfig(superchainConfigProxy)
                    )
            )
        });

        OptimismPortal portal = OptimismPortal(payable(optimismPortalProxy));
        string memory version = portal.version();
        console.log("OptimismPortal2 version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkOptimismPortal({ _contracts: proxies, _cfg: cfg, _isProxy: true });
    }

    function initializeSystemConfig() internal {
        console.log("Upgrading and initializing SystemConfig proxy");

        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address systemConfigProxy = deployerProcedue.mustGetAddress("SystemConfigProxy");
        address systemConfig = deployerProcedue.mustGetAddress("SystemConfig");

        DeployConfig cfg = deployerProcedue.getConfig();

        bytes32 batcherHash = bytes32(uint256(uint160(cfg.batchSenderAddress())));

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,   
            _proxy: payable(systemConfigProxy),
            _implementation: systemConfig,
            _innerCallData: abi.encodeCall(
                SystemConfig.initialize,
                (
                    cfg.finalSystemOwner(),
                    cfg.basefeeScalar(),
                    cfg.blobbasefeeScalar(),
                    batcherHash,
                    uint64(cfg.l2GenesisBlockGasLimit()),
                    cfg.p2pSequencerAddress(),
                    Constants.DEFAULT_RESOURCE_CONFIG(),
                    cfg.batchInboxAddress(),
                    SystemConfig.Addresses({
                        l1CrossDomainMessenger: deployerProcedue.mustGetAddress("L1CrossDomainMessengerProxy"),
                        l1ERC721Bridge: deployerProcedue.mustGetAddress("L1ERC721BridgeProxy"),
                        l1StandardBridge: deployerProcedue.mustGetAddress("L1StandardBridgeProxy"),
                        disputeGameFactory: deployerProcedue.mustGetAddress("DisputeGameFactoryProxy"),
                        optimismPortal: deployerProcedue.mustGetAddress("OptimismPortalProxy"),
                        optimismMintableERC20Factory: deployerProcedue.mustGetAddress("OptimismMintableERC20FactoryProxy"),
                        gasPayingToken: customGasTokenAddress 
                    })
                )
            )
        });

        SystemConfig config = SystemConfig(systemConfigProxy);
        string memory version = config.version();
        console.log("SystemConfig version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkSystemConfig({ _contracts: proxies, _cfg: cfg, _isProxy: true });
    }

    function initializeL1StandardBridge() internal {
        console.log("Upgrading and initializing L1StandardBridge proxy");
        address proxyAdminAddress = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safeAddress = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address l1StandardBridgeProxy = deployerProcedue.mustGetAddress("L1StandardBridgeProxy");
        address l1StandardBridge = deployerProcedue.mustGetAddress("L1StandardBridge");
        address l1CrossDomainMessengerProxy = deployerProcedue.mustGetAddress("L1CrossDomainMessengerProxy");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");
        address systemConfigProxy = deployerProcedue.mustGetAddress("SystemConfigProxy");

        uint256 proxyType = uint256(ProxyAdmin(proxyAdminAddress).proxyType(l1StandardBridgeProxy));

        ProxyAdmin proxyAdmin = ProxyAdmin(proxyAdminAddress);
        Safe safe = Safe(payable(safeAddress));

        if (proxyType != uint256(ProxyAdmin.ProxyType.CHUGSPLASH)) {
            _callViaSafe({
                _safe: safe,
                _owner: owner,
                _target: address(proxyAdmin),
                _data: abi.encodeCall(ProxyAdmin.setProxyType, (l1StandardBridgeProxy, ProxyAdmin.ProxyType.CHUGSPLASH))
            });
        }

        require(uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)) == uint256(ProxyAdmin.ProxyType.CHUGSPLASH),"Type not CHUGSPLASH");

        _upgradeAndCallViaSafe({
            _proxyAdmin: address(proxyAdmin),
            _safe: address(safe),
            _owner: owner,
            _proxy: payable(l1StandardBridgeProxy),
            _implementation: l1StandardBridge,
            _innerCallData: abi.encodeCall(
                L1StandardBridge.initialize,
                (
                    IL1CrossDomainMessenger(l1CrossDomainMessengerProxy),
                    ISuperchainConfig(superchainConfigProxy),
                    ISystemConfig(systemConfigProxy)
                )
            )
        });

        string memory version = L1StandardBridge(payable(l1StandardBridgeProxy)).version();
        console.log("L1StandardBridge version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkL1StandardBridge({ _contracts: proxies, _isProxy: true });
    }

    function initializeL1ERC721Bridge() internal {
        console.log("Upgrading and initializing L1ERC721Bridge proxy");
        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address l1ERC721BridgeProxy = deployerProcedue.mustGetAddress("L1ERC721BridgeProxy");
        address l1ERC721Bridge = deployerProcedue.mustGetAddress("L1ERC721Bridge");
        address l1CrossDomainMessengerProxy = deployerProcedue.mustGetAddress("L1CrossDomainMessengerProxy");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(l1ERC721BridgeProxy),
            _implementation: l1ERC721Bridge,
            _innerCallData: abi.encodeCall(
                L1ERC721Bridge.initialize,
                (
                    IL1CrossDomainMessenger(l1CrossDomainMessengerProxy),
                    ISuperchainConfig(superchainConfigProxy)
                )
            )
        });

        L1ERC721Bridge bridge = L1ERC721Bridge(l1ERC721BridgeProxy);
        string memory version = bridge.version();
        console.log("L1ERC721Bridge version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();

        ChainAssertions.checkL1ERC721Bridge({ _contracts: proxies, _isProxy: true });
    }

    function initializeOptimismMintableERC20Factory() internal {
        console.log("Upgrading and initializing OptimismMintableERC20Factory proxy");
        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address optimismMintableERC20FactoryProxy = deployerProcedue.mustGetAddress("OptimismMintableERC20FactoryProxy");
        address optimismMintableERC20Factory = deployerProcedue.mustGetAddress("OptimismMintableERC20Factory");
        address l1StandardBridgeProxy = deployerProcedue.mustGetAddress("L1StandardBridgeProxy");

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(optimismMintableERC20FactoryProxy),
            _implementation: optimismMintableERC20Factory,
            _innerCallData: abi.encodeCall(OptimismMintableERC20Factory.initialize, (l1StandardBridgeProxy))
        });

        OptimismMintableERC20Factory factory = OptimismMintableERC20Factory(optimismMintableERC20FactoryProxy);
        string memory version = factory.version();
        console.log("OptimismMintableERC20Factory version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: proxies, _isProxy: true });
    }

    function initializeL1CrossDomainMessenger() internal {
        console.log("Upgrading and initializing L1CrossDomainMessenger Proxy");
        address proxyAdminAddress = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safeAddress = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address l1CrossDomainMessengerProxy = deployerProcedue.mustGetAddress("L1CrossDomainMessengerProxy");
        address l1CrossDomainMessenger = deployerProcedue.mustGetAddress("L1CrossDomainMessenger");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");
        address optimismPortalProxy = deployerProcedue.mustGetAddress("OptimismPortalProxy");
        address systemConfigProxy = deployerProcedue.mustGetAddress("SystemConfigProxy");

        ProxyAdmin proxyAdmin = ProxyAdmin(proxyAdminAddress);
        Safe safe = Safe(payable(safeAddress));

        uint256 proxyType = uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy));
        
        if (proxyType != uint256(ProxyAdmin.ProxyType.RESOLVED)) {
            _callViaSafe({
                _safe: safe,
                _owner: owner,
                _target: address(proxyAdmin),
                _data: abi.encodeCall(
                    ProxyAdmin.setProxyType,
                    (
                        l1CrossDomainMessengerProxy,
                        ProxyAdmin.ProxyType.RESOLVED
                    )
                )
            });
        }
        require(uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)) == uint256(ProxyAdmin.ProxyType.RESOLVED));

        string memory contractName = "OVM_L1CrossDomainMessenger";
        string memory implName = proxyAdmin.implementationName(l1CrossDomainMessenger);
        if (keccak256(bytes(contractName)) != keccak256(bytes(implName))) {
            _callViaSafe({
                _safe: safe,
                _owner: owner,
                _target: address(proxyAdmin),
                _data: abi.encodeCall(
                    ProxyAdmin.setImplementationName,
                    (
                        l1CrossDomainMessengerProxy,
                        contractName
                    )
                )
            });
        }
        require(
            keccak256(bytes(proxyAdmin.implementationName(l1CrossDomainMessengerProxy)))
                == keccak256(bytes(contractName))
        );

        _upgradeAndCallViaSafe({
            _proxyAdmin: address(proxyAdmin),
            _safe: address(safe),
            _owner: owner,
            _proxy: payable(l1CrossDomainMessengerProxy),
            _implementation: l1CrossDomainMessenger,
            _innerCallData: abi.encodeCall(
                L1CrossDomainMessenger.initialize,
                (
                    ISuperchainConfig(superchainConfigProxy),
                    IOptimismPortal(payable(optimismPortalProxy)),
                    ISystemConfig(systemConfigProxy)
                )
            )
        });

        L1CrossDomainMessenger messenger = L1CrossDomainMessenger(l1CrossDomainMessengerProxy);
        string memory version = messenger.version();
        console.log("L1CrossDomainMessenger version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkL1CrossDomainMessenger({ _contracts: proxies, _vm: vm, _isProxy: true });
    }

    function initializeL2OutputOracle() internal {
        console.log("Upgrading and initializing L2OutputOracle proxy");
        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address l2OutputOracleProxy = deployerProcedue.mustGetAddress("L2OutputOracleProxy");
        address l2OutputOracle = deployerProcedue.mustGetAddress("L2OutputOracle");

        DeployConfig cfg = deployerProcedue.getConfig();

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: address(safe),
            _owner: owner,
            _proxy: payable(l2OutputOracleProxy),
            _implementation: l2OutputOracle,
            _innerCallData: abi.encodeCall(
                L2OutputOracle.initialize,
                (
                    cfg.l2OutputOracleSubmissionInterval(),
                    cfg.l2BlockTime(),
                    cfg.l2OutputOracleStartingBlockNumber(),
                    cfg.l2OutputOracleStartingTimestamp(),
                    cfg.l2OutputOracleProposer(),
                    cfg.l2OutputOracleChallenger(),
                    cfg.finalizationPeriodSeconds()
                )
            )
        });

        L2OutputOracle oracle = L2OutputOracle(l2OutputOracleProxy);
        string memory version = oracle.version();
        console.log("L2OutputOracle version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();

        ChainAssertions.checkL2OutputOracle({
            _contracts: proxies,
            _cfg: cfg,
            _l2OutputOracleStartingTimestamp: cfg.l2OutputOracleStartingTimestamp(),
            _isProxy: true
        });
    }

    function initializeDisputeGameFactory() internal {
        console.log("Upgrading and initializing DisputeGameFactory proxy");
        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address disputeGameFactoryProxy = deployerProcedue.mustGetAddress("DisputeGameFactoryProxy");
        address disputeGameFactory = deployerProcedue.mustGetAddress("DisputeGameFactory");

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(disputeGameFactoryProxy),
            _implementation: disputeGameFactory,
            _innerCallData: abi.encodeCall(
                DisputeGameFactory.initialize,
                (owner)
            )
        });

        string memory version = DisputeGameFactory(disputeGameFactoryProxy).version();
        console.log("DisputeGameFactory version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkDisputeGameFactory({ _contracts: proxies, _expectedOwner: owner, _isProxy: true });
    }

    function initializeDelayedWETH() internal {
        console.log("Upgrading and initializing DelayedWETH proxy");
        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address delayedWETHProxy = deployerProcedue.mustGetAddress("DelayedWETHProxy");
        address delayedWETH = deployerProcedue.mustGetAddress("DelayedWETH");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");

        DeployConfig cfg = deployerProcedue.getConfig();

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(delayedWETHProxy),
            _implementation: delayedWETH,
            _innerCallData: abi.encodeCall(
                DelayedWETH.initialize, (
                    owner,
                    ISuperchainConfig(superchainConfigProxy)
                )
            )
        });

        string memory version = DelayedWETH(payable(delayedWETHProxy)).version();
        console.log("DelayedWETH version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkDelayedWETH({
            _contracts: proxies,
            _cfg: cfg,
            _isProxy: true,
            _expectedOwner: owner
        });
    }

    function initializePermissionedDelayedWETH() internal {
        console.log("Upgrading and initializing permissioned DelayedWETH proxy");

        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address delayedWETHProxy = deployerProcedue.mustGetAddress("PermissionedDelayedWETHProxy");
        address delayedWETH = deployerProcedue.mustGetAddress("DelayedWETH");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");

        DeployConfig cfg = deployerProcedue.getConfig();

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(delayedWETHProxy),
            _implementation: delayedWETH,
            _innerCallData: abi.encodeCall(
                DelayedWETH.initialize, (
                    owner,
                    ISuperchainConfig(superchainConfigProxy)
                )
            )
        });

        string memory version = DelayedWETH(payable(delayedWETHProxy)).version();
        console.log("DelayedWETH version: %s", version);

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();

        ChainAssertions.checkPermissionedDelayedWETH({
            _contracts: proxies,
            _cfg: cfg,
            _isProxy: true,
            _expectedOwner: owner
        });
    }

    function initializeAnchorStateRegistry() internal {
        console.log("Upgrading and initializing AnchorStateRegistry proxy");

        address proxyAdmin = deployerProcedue.mustGetAddress("ProxyAdmin");
        address safe = deployerProcedue.mustGetAddress("SystemOwnerSafe");

        address anchorStateRegistryProxy = deployerProcedue.mustGetAddress("AnchorStateRegistryProxy");
        address anchorStateRegistry = deployerProcedue.mustGetAddress("AnchorStateRegistry");
        address superchainConfigProxy = deployerProcedue.mustGetAddress("SuperchainConfigProxy");

        DeployConfig cfg = deployerProcedue.getConfig();

        AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](5);
        roots[0] = AnchorStateRegistry.StartingAnchorRoot({
            gameType: GameTypes.CANNON,
            outputRoot: OutputRoot({
                root: Hash.wrap(cfg.faultGameGenesisOutputRoot()),
                l2BlockNumber: cfg.faultGameGenesisBlock()
            })
        });
        roots[1] = AnchorStateRegistry.StartingAnchorRoot({
            gameType: GameTypes.PERMISSIONED_CANNON,
            outputRoot: OutputRoot({
                root: Hash.wrap(cfg.faultGameGenesisOutputRoot()),
                l2BlockNumber: cfg.faultGameGenesisBlock()
            })
        });
        roots[2] = AnchorStateRegistry.StartingAnchorRoot({
            gameType: GameTypes.ALPHABET,
            outputRoot: OutputRoot({
                root: Hash.wrap(cfg.faultGameGenesisOutputRoot()),
                l2BlockNumber: cfg.faultGameGenesisBlock()
            })
        });
        roots[3] = AnchorStateRegistry.StartingAnchorRoot({
            gameType: GameTypes.ASTERISC,
            outputRoot: OutputRoot({
                root: Hash.wrap(cfg.faultGameGenesisOutputRoot()),
                l2BlockNumber: cfg.faultGameGenesisBlock()
            })
        });
        roots[4] = AnchorStateRegistry.StartingAnchorRoot({
            gameType: GameTypes.FAST,
            outputRoot: OutputRoot({
                root: Hash.wrap(cfg.faultGameGenesisOutputRoot()),
                l2BlockNumber: cfg.faultGameGenesisBlock()
            })
        });

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(anchorStateRegistryProxy),
            _implementation: anchorStateRegistry,
            _innerCallData: abi.encodeCall(AnchorStateRegistry.initialize, (roots, ISuperchainConfig(superchainConfigProxy)))
        });

        string memory version = AnchorStateRegistry(payable(anchorStateRegistryProxy)).version();
        console.log("AnchorStateRegistry version: %s", version);
    }
}

        
      

4.2O : Set FaultGameImplementation

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/402O_SetFaultGameImplementationScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

When running setCannonFaultGameImplementation, it is required to generate MipsAbsolutePrestatejson files. The instruction is here.

Contract Settings

set AlphabetFaultGame Implementation

set FastFaultGame Implementation

set CannonFaultGame Implementation

set Permissioned CannonFaultGame Implementation

Transfer DisputeGameFactory Ownership

Transfer DelayedWETH Ownership

Deploy Script:
402O_SetFaultGameImplementationScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AlphabetVM} from "@redprint-test/mocks/AlphabetVM.sol";
import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {Chains} from "@redprint-deploy/libraries/Chains.sol";
import {Config} from "@redprint-deploy/libraries/Config.sol";
import {DeployConfig} from "@redprint-deploy/deployer/DeployConfig.s.sol";
import {DisputeGameFactory} from "@redprint-core/dispute/DisputeGameFactory.sol";
import {FaultDisputeGame} from "@redprint-core/dispute/FaultDisputeGame.sol";
import {GameType, GameTypes, Claim, Duration} from "@redprint-core/dispute/lib/Types.sol";
import {IAnchorStateRegistry} from "@redprint-core/dispute/interfaces/IAnchorStateRegistry.sol";
import {IBigStepper, IPreimageOracle} from "@redprint-core/dispute/interfaces/IBigStepper.sol";
import {IDelayedWETH} from "@redprint-core/dispute/interfaces/IDelayedWETH.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {IDisputeGame} from "@redprint-core/dispute/interfaces/IDisputeGame.sol";
import {IDisputeGameFactory} from "@redprint-core/dispute/interfaces/IDisputeGameFactory.sol";
import {PermissionedDisputeGame} from "@redprint-core/dispute/PermissionedDisputeGame.sol";
import {PreimageOracle} from "@redprint-core/cannon/PreimageOracle.sol";
import {Process} from "@redprint-deploy/libraries/Process.sol";
import {Script} from "@redprint-forge-std/Script.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {console} from "@redprint-forge-std/console.sol";

contract SetFaultGameImplementationScript is Script {
    IDeployer deployerProcedue;
    struct FaultDisputeGameParams {
        IAnchorStateRegistry anchorStateRegistry;
        IDelayedWETH weth;
        GameType gameType;
        Claim absolutePrestate;
        IBigStepper faultVm;
        uint256 maxGameDepth;
        Duration maxClockDuration;
    }
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function run() public {
        deployerProcedue = getDeployer();
        deployerProcedue.setAutoSave(true);

        console.log("Set FaultGameImplementations Ccontract");

        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
            console.log("Pranking owner ...");
            vm.startPrank(owner);
            setAlphabetFaultGameImplementation({ _allowUpgrade: false });
            setFastFaultGameImplementation({ _allowUpgrade: false });
            setCannonFaultGameImplementation({ _allowUpgrade: false });
            transferDisputeGameFactoryOwnership();
            transferDelayedWETHOwnership();
  
            console.log("Pranking Stopped ...");

            vm.stopPrank();
        } else {
            console.log("Broadcasting ...");
            vm.startBroadcast(owner);
            setAlphabetFaultGameImplementation({ _allowUpgrade: false });
            setFastFaultGameImplementation({ _allowUpgrade: false });
            setCannonFaultGameImplementation({ _allowUpgrade: false });
            transferDisputeGameFactoryOwnership();
            transferDelayedWETHOwnership();
   
            console.log("Broadcasted");

            vm.stopBroadcast();
        }
    }

    function setAlphabetFaultGameImplementation(bool _allowUpgrade) internal {
        console.log("Setting Alphabet FaultDisputeGame implementation");
        DisputeGameFactory factory = DisputeGameFactory(deployerProcedue.mustGetAddress("DisputeGameFactoryProxy"));
        IDelayedWETH weth = IDelayedWETH(deployerProcedue.mustGetAddress("DelayedWETHProxy"));

        DeployConfig cfg = deployerProcedue.getConfig();

        Claim outputAbsolutePrestate = Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate()));
        _setFaultGameImplementation({
            _factory: factory,
            _allowUpgrade: _allowUpgrade,
            _params: FaultDisputeGameParams({
                anchorStateRegistry: IAnchorStateRegistry(deployerProcedue.mustGetAddress("AnchorStateRegistryProxy")),
                weth: weth,
                gameType: GameTypes.ALPHABET,
                absolutePrestate: outputAbsolutePrestate,
                faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, IPreimageOracle(deployerProcedue.mustGetAddress("PreimageOracle")))),
                // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive.
                maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1,
                maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
            })
        });
    }

    function setFastFaultGameImplementation(bool _allowUpgrade) internal {
        console.log("Setting Fast FaultDisputeGame implementation");
        DisputeGameFactory factory = DisputeGameFactory(deployerProcedue.mustGetAddress("DisputeGameFactoryProxy"));
        IDelayedWETH weth = IDelayedWETH(deployerProcedue.mustGetAddress("DelayedWETHProxy"));

        DeployConfig cfg = deployerProcedue.getConfig();

        Claim outputAbsolutePrestate = Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate()));
        PreimageOracle fastOracle = new PreimageOracle(cfg.preimageOracleMinProposalSize(), 0);
        _setFaultGameImplementation({
            _factory: factory,
            _allowUpgrade: _allowUpgrade,
            _params: FaultDisputeGameParams({
                anchorStateRegistry: IAnchorStateRegistry(deployerProcedue.mustGetAddress("AnchorStateRegistryProxy")),
                weth: weth,
                gameType: GameTypes.FAST,
                absolutePrestate: outputAbsolutePrestate,
                faultVm: IBigStepper(new AlphabetVM(outputAbsolutePrestate, IPreimageOracle(address(fastOracle)))),
                // The max depth for the alphabet trace is always 3. Add 1 because split depth is fully inclusive.
                maxGameDepth: cfg.faultGameSplitDepth() + 3 + 1,
                maxClockDuration: Duration.wrap(0) // Resolvable immediately
             })
        });
    }

    function setCannonFaultGameImplementation(bool _allowUpgrade) internal {
        console.log("Setting Cannon FaultDisputeGame implementation");
        DisputeGameFactory factory = DisputeGameFactory(deployerProcedue.mustGetAddress("DisputeGameFactoryProxy"));
        IDelayedWETH weth = IDelayedWETH(deployerProcedue.mustGetAddress("DelayedWETHProxy"));

        DeployConfig cfg = deployerProcedue.getConfig();

        // Set the Cannon FaultDisputeGame implementation in the factory.
        _setFaultGameImplementation({
            _factory: factory,
            _allowUpgrade: _allowUpgrade,
            _params: FaultDisputeGameParams({
                anchorStateRegistry: IAnchorStateRegistry(deployerProcedue.mustGetAddress("AnchorStateRegistryProxy")),
                weth: weth,
                gameType: GameTypes.CANNON,
                absolutePrestate: loadMipsAbsolutePrestate(),
                faultVm: IBigStepper(deployerProcedue.mustGetAddress("Mips")),
                maxGameDepth: cfg.faultGameMaxDepth(),
                maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
            })
        });
    }

    function setPermissionedCannonFaultGameImplementation(bool _allowUpgrade)
        internal
    {
        console.log("Setting Cannon PermissionedDisputeGame implementation");
        DisputeGameFactory factory = DisputeGameFactory(deployerProcedue.mustGetAddress("DisputeGameFactoryProxy"));
        IDelayedWETH weth = IDelayedWETH(deployerProcedue.mustGetAddress("PermissionedDelayedWETHProxy"));

        DeployConfig cfg = deployerProcedue.getConfig();

        // Set the Cannon FaultDisputeGame implementation in the factory.
        _setFaultGameImplementation({
            _factory: factory,
            _allowUpgrade: _allowUpgrade,
            _params: FaultDisputeGameParams({
                anchorStateRegistry: IAnchorStateRegistry(deployerProcedue.mustGetAddress("AnchorStateRegistryProxy")),
                weth: weth,
                gameType: GameTypes.PERMISSIONED_CANNON,
                absolutePrestate: loadMipsAbsolutePrestate(),
                faultVm: IBigStepper(deployerProcedue.mustGetAddress("Mips")),
                maxGameDepth: cfg.faultGameMaxDepth(),
                maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
            })
        });
    }

    function loadMipsAbsolutePrestate()
        internal
        returns (Claim mipsAbsolutePrestate_)
    {
        DeployConfig cfg = deployerProcedue.getConfig();

        if (block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet) {
            if (Config.useMultithreadedCannon()) {
                return _loadDevnetMtMipsAbsolutePrestate();
            } else {
                return _loadDevnetStMipsAbsolutePrestate();
            }
        } else {
            console.log(
                "[Cannon Dispute Game] Using absolute prestate from config: %x", cfg.faultGameAbsolutePrestate()
            );
            mipsAbsolutePrestate_ = Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate()));
        }
    }

    function _loadDevnetMtMipsAbsolutePrestate()
        internal
        returns (Claim mipsAbsolutePrestate_)
    {
        // Fetch the absolute prestate dump
        string memory filePath = string.concat(vm.projectRoot(), "/../op-program/bin/prestate-proof-mt.json");
        string[] memory commands = new string[](3);
        commands[0] = "bash";
        commands[1] = "-c";
        commands[2] = string.concat("[[ -f ", filePath, " ]] && echo \"present\"");
        if (Process.run(commands).length == 0) {
            revert(
                "Deploy: MT-Cannon prestate dump not found, generate it with 'make cannon-prestate-mt' in the monorepo root"
            );
        }
        commands[2] = string.concat("cat ", filePath, " | jq -r .pre");
        mipsAbsolutePrestate_ = Claim.wrap(abi.decode(Process.run(commands), (bytes32)));
        console.log(
            "[MT-Cannon Dispute Game] Using devnet MIPS2 Absolute prestate: %s",
            vm.toString(Claim.unwrap(mipsAbsolutePrestate_))
        );
    }

    function _loadDevnetStMipsAbsolutePrestate()
        internal
        returns (Claim mipsAbsolutePrestate_)
    {
        // Fetch the absolute prestate dump
        string memory filePath = string.concat(vm.projectRoot(), "/../op-program/bin/prestate-proof.json");
        string[] memory commands = new string[](3);
        commands[0] = "bash";
        commands[1] = "-c";
        commands[2] = string.concat("[[ -f ", filePath, " ]] && echo \"present\"");
        if (Process.run(commands).length == 0) {
            revert(
                "Deploy: cannon prestate dump not found, generate it with 'make cannon-prestate' in the monorepo root"
            );
        }
        commands[2] = string.concat("cat ", filePath, " | jq -r .pre");
        mipsAbsolutePrestate_ = Claim.wrap(abi.decode(Process.run(commands), (bytes32)));
        console.log(
            "[Cannon Dispute Game] Using devnet MIPS Absolute prestate: %s",
            vm.toString(Claim.unwrap(mipsAbsolutePrestate_))
        );
    }

    function _setFaultGameImplementation(DisputeGameFactory _factory, bool _allowUpgrade, FaultDisputeGameParams memory _params)
        internal
    {
        if (address(_factory.gameImpls(_params.gameType)) != address(0) && !_allowUpgrade) {
            console.log(
                "[WARN] DisputeGameFactoryProxy: 'FaultDisputeGame' implementation already set for game type: %s",
                vm.toString(GameType.unwrap(_params.gameType))
            );
            return;
        }

        DeployConfig cfg = deployerProcedue.getConfig();
        uint32 rawGameType = GameType.unwrap(_params.gameType);
        if (rawGameType != GameTypes.PERMISSIONED_CANNON.raw()) {

            address faultDisputeGameAddress = address(new FaultDisputeGame({
                _gameType: _params.gameType,
                _absolutePrestate: _params.absolutePrestate,
                _maxGameDepth: _params.maxGameDepth,
                _splitDepth: cfg.faultGameSplitDepth(),
                _clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())),
                _maxClockDuration: _params.maxClockDuration,
                _vm: _params.faultVm,
                _weth: _params.weth,
                _anchorStateRegistry: _params.anchorStateRegistry,
                _l2ChainId: cfg.l2ChainID()
            }));

            _factory.setImplementation(
                _params.gameType,
                IDisputeGame(faultDisputeGameAddress)
            );
           
        } else {
            address permissionedDisputeGameAddress = address(new PermissionedDisputeGame({
                    _gameType: _params.gameType,
                    _absolutePrestate: _params.absolutePrestate,
                    _maxGameDepth: _params.maxGameDepth,
                    _splitDepth: cfg.faultGameSplitDepth(),
                    _clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())),
                    _maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())),
                    _vm: _params.faultVm,
                    _weth: _params.weth,
                    _anchorStateRegistry: _params.anchorStateRegistry,
                    _l2ChainId: cfg.l2ChainID(),
                    _proposer: cfg.l2OutputOracleProposer(),
                    _challenger: cfg.l2OutputOracleChallenger()
            }));
            
            _factory.setImplementation(
                _params.gameType,
                IDisputeGame(permissionedDisputeGameAddress)
            );
        }

        string memory gameTypeString;
        if (rawGameType == GameTypes.CANNON.raw()) {
            gameTypeString = "Cannon";
        } else if (rawGameType == GameTypes.PERMISSIONED_CANNON.raw()) {
            gameTypeString = "PermissionedCannon";
        } else if (rawGameType == GameTypes.ALPHABET.raw()) {
            gameTypeString = "Alphabet";
        } else {
            gameTypeString = "Unknown";
        }

        console.log(
            "DisputeGameFactoryProxy: set 'FaultDisputeGame' implementation (Backend: %s | GameType: %s)",
            gameTypeString,
            vm.toString(rawGameType)
        );
    }

    function transferDisputeGameFactoryOwnership() internal {
        console.log("Transferring DisputeGameFactory ownership to Safe");
        IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(deployerProcedue.mustGetAddress("DisputeGameFactoryProxy"));
        address _owner = disputeGameFactory.owner();

        DeployConfig cfg = deployerProcedue.getConfig();
        address finalSystemOwner = cfg.finalSystemOwner();

        if (_owner != finalSystemOwner) {
            disputeGameFactory.transferOwnership(finalSystemOwner);
            console.log("DisputeGameFactory ownership transferred to final system owner at: %s", finalSystemOwner);
        }

        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkDisputeGameFactory({
            _contracts: proxies,
            _expectedOwner: finalSystemOwner,
            _isProxy: true
        });
    }

    function transferDelayedWETHOwnership() internal {
        console.log("Transferring DelayedWETH ownership to Safe");
        IDelayedWETH weth = IDelayedWETH(deployerProcedue.mustGetAddress("DelayedWETHProxy"));
        address _owner = weth.owner();

        DeployConfig cfg = deployerProcedue.getConfig();
        address finalSystemOwner = cfg.finalSystemOwner();
        if (_owner != finalSystemOwner) {
            weth.transferOwnership(finalSystemOwner);
            console.log("DelayedWETH ownership transferred to final system owner at: %s", finalSystemOwner);
        }
        
        Types.ContractSet memory proxies =  deployerProcedue.getProxies();
        ChainAssertions.checkDelayedWETH({
            _contracts: proxies,
            _cfg: cfg,
            _isProxy: true,
            _expectedOwner: finalSystemOwner
        });
    }
}

        
      

After running the script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.