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
{
"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.
forge script script/000_DeployAllScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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();
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>",
"DisputeGameFactory": "<ADDRESS_31>",
"DelayedWETH": "<ADDRESS_32>",
"PreimageOracle" : "<ADDRESS_33>",
"Mips" : "<ADDRESS_34>",
"AnchorStateRegistry" : "<ADDRESS_35>"
}
click on ✕ button to close
4.2 - Part 1 : Deploy Implementations Contracts
4.2A : Deploy L1CrossDomainMessenger Contract
forge script script/401A_DeployL1CrossDomainMessengerScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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();
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>"
}
click on ✕ button to close
4.2B : Deploy OptimismMintableERC20Factory Contract
forge script script/402B_DeployOptimismMintableERC20FactoryScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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;
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>"
}
click on ✕ button to close
4.2C : Deploy SystemConfig Contract
forge script script/402C_DeploySystemConfigScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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;
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>"
}
click on ✕ button to close
4.2D : Deploy L1StandardBridge Contract
forge script script/402D_DeployL1StandardBridgeScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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);
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>"
}
click on ✕ button to close
4.2E : Deploy L1ERC721Bridge Contract
forge script script/402E_DeployL1ERC721BridgeScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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);
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>"
}
click on ✕ button to close
4.2F : Deploy OptimismPortal Contract
forge script script/402F_DeployOptimismPortalScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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();
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>"
}
click on ✕ button to close
4.2G : Deploy L2OutputOracle Contract
forge script script/402G_DeployL2OutputOracleScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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);
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>"
}
click on ✕ button to close
4.2 - Part 2 : Deploy and Set up Fault proofs System
4.2H : Deploy OptimismPortal2 Contract
forge script script/402H_DeployOptimismPortal2Script.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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;
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>"
}
click on ✕ button to close
4.2I : Deploy DisputeGameFactory Contract
forge script script/402I_DeployDisputeGameFactoryScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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);
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>",
"DisputeGameFactory": "<ADDRESS_31>"
}
click on ✕ button to close
4.2J : Deploy DelayedWETH Contract
forge script script/402J_DeployDelayedWETHScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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);
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>",
"DisputeGameFactory": "<ADDRESS_31>",
"DelayedWETH": "<ADDRESS_32>"
}
click on ✕ button to close
4.2K : Deploy PreimageOracle Contract
forge script script/402K_DeployPreimageOracleScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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));
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>",
"DisputeGameFactory": "<ADDRESS_31>",
"DelayedWETH": "<ADDRESS_32>",
"PreimageOracle" : "<ADDRESS_33>"
}
click on ✕ button to close
4.2L : Deploy MIPS Contract
forge script script/402L_DeployMIPSScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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;
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>",
"DisputeGameFactory": "<ADDRESS_31>",
"DelayedWETH": "<ADDRESS_32>",
"PreimageOracle" : "<ADDRESS_33>",
"Mips" : "<ADDRESS_34>"
}
click on ✕ button to close
4.2M : Deploy AnchorStateRegistry Contract
forge script script/402M_DeployAnchorStateRegistryScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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()) });
}
}
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"L1CrossDomainMessenger": "<ADDRESS_23>",
"OptimismMintableERC20Factory": "<ADDRESS_24>",
"SystemConfig": "<ADDRESS_25>",
"L1StandardBridge": "<ADDRESS_26>",
"L1ERC721Bridge": "<ADDRESS_27>",
"OptimismPortal": "<ADDRESS_28>",
"L2OutputOracle": "<ADDRESS_29>",
"OptimismPortal2": "<ADDRESS_30>",
"DisputeGameFactory": "<ADDRESS_31>",
"DelayedWETH": "<ADDRESS_32>",
"PreimageOracle" : "<ADDRESS_33>",
"Mips" : "<ADDRESS_34>",
"AnchorStateRegistry" : "<ADDRESS_35>"
}
click on ✕ button to close
4.2N : Initialize Implementations
forge script script/402N_InitializeImplementationsScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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
forge script script/402O_SetFaultGameImplementationScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast
--mnemonic-derivation-paths "m/44'/60'/0'/0/0"
// 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.
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"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>",
"OptimismPortalProxy": "<ADDRESS_10>",
"SystemConfigProxy": "<ADDRESS_11>",
"L1StandardBridgeProxy": "<ADDRESS_12>",
"L1CrossDomainMessengerProxy": "<ADDRESS_13>",
"OptimismMintableERC20FactoryProxy": "<ADDRESS_14>",
"L1ERC721BridgeProxy": "<ADDRESS_15>",
"DisputeGameFactoryProxy": "<ADDRESS_16>",
"L2OutputOracleProxy": "<ADDRESS_17>",
"DelayedWETHProxy": "<ADDRESS_18>",
"PermissionedDelayedWETHProxy": "<ADDRESS_19>",
"AnchorStateRegistryProxy": "<ADDRESS_20>",
"L1CrossDomainMessenger": "<ADDRESS_21>",
"OptimismMintableERC20Factory": "<ADDRESS_22>",
"SystemConfig": "<ADDRESS_23>",
"L1StandardBridge": "<ADDRESS_24>",
"L1ERC721Bridge": "<ADDRESS_25>",
"OptimismPortal": "<ADDRESS_26>",
"L2OutputOracle": "<ADDRESS_27>",
"OptimismPortal2": "<ADDRESS_28>",
"DisputeGameFactory": "<ADDRESS_29>",
"DelayedWETH": "<ADDRESS_30>",
"PreimageOracle" : "<ADDRESS_31>",
"Mips" : "<ADDRESS_32>",
"AnchorStateRegistry" : "<ADDRESS_33>",
"FaultDisputeGame" : "<ADDRESS_34>",
"PermissionedDisputeGame" : "<ADDRESS_35>"
}
click on ✕ button to close