Commit 3f4d94a4 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

ctb: Implement addGameType method on OPCM (#13653)

parent 3d3deab1
......@@ -9,8 +9,6 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol";
import { Bytes } from "src/libraries/Bytes.sol";
import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
import { IMIPS } from "interfaces/cannon/IMIPS.sol";
......@@ -493,14 +491,22 @@ contract DeployImplementations is Script {
bytes32 salt = _dii.salt();
OPContractsManager.Blueprints memory blueprints;
address checkAddress;
vm.startBroadcast(msg.sender);
blueprints.addressManager = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("AddressManager")), salt);
blueprints.proxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("Proxy")), salt);
blueprints.proxyAdmin = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("ProxyAdmin")), salt);
blueprints.l1ChugSplashProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("L1ChugSplashProxy")), salt);
blueprints.resolvedDelegateProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("ResolvedDelegateProxy")), salt);
blueprints.anchorStateRegistry = deployBytecode(Blueprint.blueprintDeployerBytecode(vm.getCode("AnchorStateRegistry")), salt);
(blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = deployBigBytecode(vm.getCode("PermissionedDisputeGame"), salt);
(blueprints.addressManager, checkAddress) = Blueprint.create(vm.getCode("AddressManager"), salt);
require(checkAddress == address(0), "OPCM-10");
(blueprints.proxy, checkAddress) = Blueprint.create(vm.getCode("Proxy"), salt);
require(checkAddress == address(0), "OPCM-20");
(blueprints.proxyAdmin, checkAddress) = Blueprint.create(vm.getCode("ProxyAdmin"), salt);
require(checkAddress == address(0), "OPCM-30");
(blueprints.l1ChugSplashProxy, checkAddress) = Blueprint.create(vm.getCode("L1ChugSplashProxy"), salt);
require(checkAddress == address(0), "OPCM-40");
(blueprints.resolvedDelegateProxy, checkAddress) = Blueprint.create(vm.getCode("ResolvedDelegateProxy"), salt);
require(checkAddress == address(0), "OPCM-50");
(blueprints.anchorStateRegistry, checkAddress) = Blueprint.create(vm.getCode("AnchorStateRegistry"), salt);
require(checkAddress == address(0), "OPCM-60");
(blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = Blueprint.create(vm.getCode("PermissionedDisputeGame"), salt);
(blueprints.permissionlessDisputeGame1, blueprints.permissionlessDisputeGame2) = Blueprint.create(vm.getCode("FaultDisputeGame"), salt);
vm.stopBroadcast();
// forgefmt: disable-end
......@@ -862,33 +868,6 @@ contract DeployImplementations is Script {
dio_ = DeployImplementationsOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployImplementationsOutput"));
}
function deployBytecode(bytes memory _bytecode, bytes32 _salt) public returns (address newContract_) {
assembly ("memory-safe") {
newContract_ := create2(0, add(_bytecode, 0x20), mload(_bytecode), _salt)
}
require(newContract_ != address(0), "DeployImplementations: create2 failed");
}
function deployBigBytecode(
bytes memory _bytecode,
bytes32 _salt
)
public
returns (address newContract1_, address newContract2_)
{
// Preamble needs 3 bytes.
uint256 maxInitCodeSize = 24576 - 3;
require(_bytecode.length > maxInitCodeSize, "DeployImplementations: Use deployBytecode instead");
bytes memory part1Slice = Bytes.slice(_bytecode, 0, maxInitCodeSize);
bytes memory part1 = Blueprint.blueprintDeployerBytecode(part1Slice);
bytes memory part2Slice = Bytes.slice(_bytecode, maxInitCodeSize, _bytecode.length - maxInitCodeSize);
bytes memory part2 = Blueprint.blueprintDeployerBytecode(part2Slice);
newContract1_ = deployBytecode(part1, _salt);
newContract2_ = deployBytecode(part2, _salt);
}
// Zero address is returned if the address is not found in '_standardVersionsToml'.
function getReleaseAddress(
string memory _version,
......
......@@ -198,7 +198,9 @@ contract DeployOPCM is Script {
resolvedDelegateProxy: _doi.resolvedDelegateProxyBlueprint(),
anchorStateRegistry: _doi.anchorStateRegistryBlueprint(),
permissionedDisputeGame1: _doi.permissionedDisputeGame1Blueprint(),
permissionedDisputeGame2: _doi.permissionedDisputeGame2Blueprint()
permissionedDisputeGame2: _doi.permissionedDisputeGame2Blueprint(),
permissionlessDisputeGame1: address(0),
permissionlessDisputeGame2: address(0)
});
OPContractsManager.Implementations memory implementations = OPContractsManager.Implementations({
l1ERC721BridgeImpl: address(_doi.l1ERC721BridgeImpl()),
......
......@@ -57,6 +57,16 @@
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPContractsManager.Blueprints",
......@@ -132,6 +142,104 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "saltMixer",
"type": "string"
},
{
"internalType": "contract ISystemConfig",
"name": "systemConfig",
"type": "address"
},
{
"internalType": "contract IProxyAdmin",
"name": "proxyAdmin",
"type": "address"
},
{
"internalType": "contract IDelayedWETH",
"name": "delayedWETH",
"type": "address"
},
{
"internalType": "GameType",
"name": "disputeGameType",
"type": "uint32"
},
{
"internalType": "Claim",
"name": "disputeAbsolutePrestate",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "disputeMaxGameDepth",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "disputeSplitDepth",
"type": "uint256"
},
{
"internalType": "Duration",
"name": "disputeClockExtension",
"type": "uint64"
},
{
"internalType": "Duration",
"name": "disputeMaxClockDuration",
"type": "uint64"
},
{
"internalType": "uint256",
"name": "initialBond",
"type": "uint256"
},
{
"internalType": "contract IBigStepper",
"name": "vm",
"type": "address"
},
{
"internalType": "bool",
"name": "permissioned",
"type": "bool"
}
],
"internalType": "struct OPContractsManager.AddGameInput[]",
"name": "_gameConfigs",
"type": "tuple[]"
}
],
"name": "addGameType",
"outputs": [
{
"components": [
{
"internalType": "contract IDelayedWETH",
"name": "delayedWETH",
"type": "address"
},
{
"internalType": "contract IFaultDisputeGame",
"name": "faultDisputeGame",
"type": "address"
}
],
"internalType": "struct OPContractsManager.AddGameOutput[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "blueprints",
......@@ -177,6 +285,16 @@
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPContractsManager.Blueprints",
......@@ -596,6 +714,11 @@
"name": "InvalidChainId",
"type": "error"
},
{
"inputs": [],
"name": "InvalidGameConfigs",
"type": "error"
},
{
"inputs": [
{
......@@ -622,6 +745,11 @@
"name": "NotABlueprint",
"type": "error"
},
{
"inputs": [],
"name": "OnlyDelegatecall",
"type": "error"
},
{
"inputs": [],
"name": "ReservedBitsSet",
......
......@@ -57,6 +57,16 @@
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPContractsManager.Blueprints",
......@@ -132,6 +142,104 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "saltMixer",
"type": "string"
},
{
"internalType": "contract ISystemConfig",
"name": "systemConfig",
"type": "address"
},
{
"internalType": "contract IProxyAdmin",
"name": "proxyAdmin",
"type": "address"
},
{
"internalType": "contract IDelayedWETH",
"name": "delayedWETH",
"type": "address"
},
{
"internalType": "GameType",
"name": "disputeGameType",
"type": "uint32"
},
{
"internalType": "Claim",
"name": "disputeAbsolutePrestate",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "disputeMaxGameDepth",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "disputeSplitDepth",
"type": "uint256"
},
{
"internalType": "Duration",
"name": "disputeClockExtension",
"type": "uint64"
},
{
"internalType": "Duration",
"name": "disputeMaxClockDuration",
"type": "uint64"
},
{
"internalType": "uint256",
"name": "initialBond",
"type": "uint256"
},
{
"internalType": "contract IBigStepper",
"name": "vm",
"type": "address"
},
{
"internalType": "bool",
"name": "permissioned",
"type": "bool"
}
],
"internalType": "struct OPContractsManager.AddGameInput[]",
"name": "_gameConfigs",
"type": "tuple[]"
}
],
"name": "addGameType",
"outputs": [
{
"components": [
{
"internalType": "contract IDelayedWETH",
"name": "delayedWETH",
"type": "address"
},
{
"internalType": "contract IFaultDisputeGame",
"name": "faultDisputeGame",
"type": "address"
}
],
"internalType": "struct OPContractsManager.AddGameOutput[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "blueprints",
......@@ -177,6 +285,16 @@
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionlessDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPContractsManager.Blueprints",
......@@ -596,6 +714,11 @@
"name": "InvalidChainId",
"type": "error"
},
{
"inputs": [],
"name": "InvalidGameConfigs",
"type": "error"
},
{
"inputs": [
{
......@@ -622,6 +745,11 @@
"name": "NotABlueprint",
"type": "error"
},
{
"inputs": [],
"name": "OnlyDelegatecall",
"type": "error"
},
{
"inputs": [],
"name": "ReservedBitsSet",
......
......@@ -16,8 +16,8 @@
"sourceCodeHash": "0xa91b445bdc666a02ba18e3b91ba94b6d54bbe65da714002fc734814201319d57"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x4b413cbe79bd10d41d8f3e9f0408e773dd49ced823d457b9f9aa92f446828105",
"sourceCodeHash": "0xe5179a20ae40d4e4773c52df98bac67e73e04044bec9e8750073b4e2f14fe81b"
"initCodeHash": "0xe0c14a8fee7ad4c4e28a3ff6ca4e726721a6c3fea0a74ab7eac7ef07fe4da0ae",
"sourceCodeHash": "0xe0f5413e0a0a335016d773f02ef6bd25551416635981e83b7a4da601b9b65bc4"
},
"src/L1/OptimismPortal2.sol": {
"initCodeHash": "0x7e533474310583593c2d57d30fcd1ec11e1568dbaaf37a2dd28c5cc574068bac",
......
......@@ -7,7 +7,7 @@
"type": "string"
},
{
"bytes": "256",
"bytes": "320",
"label": "blueprint",
"offset": 0,
"slot": "1",
......@@ -17,7 +17,7 @@
"bytes": "288",
"label": "implementation",
"offset": 0,
"slot": "9",
"slot": "11",
"type": "struct OPContractsManager.Implementations"
}
]
\ No newline at end of file
......@@ -7,7 +7,7 @@
"type": "string"
},
{
"bytes": "256",
"bytes": "320",
"label": "blueprint",
"offset": 0,
"slot": "1",
......@@ -17,7 +17,7 @@
"bytes": "288",
"label": "implementation",
"offset": 0,
"slot": "9",
"slot": "11",
"type": "struct OPContractsManager.Implementations"
}
]
\ No newline at end of file
......@@ -4,6 +4,7 @@ pragma solidity 0.8.15;
// Libraries
import { Blueprint } from "src/libraries/Blueprint.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Bytes } from "src/libraries/Bytes.sol";
import { Claim, Duration, GameType, GameTypes } from "src/dispute/lib/Types.sol";
// Interfaces
......@@ -97,6 +98,8 @@ contract OPContractsManager is ISemver {
address anchorStateRegistry;
address permissionedDisputeGame1;
address permissionedDisputeGame2;
address permissionlessDisputeGame1;
address permissionlessDisputeGame2;
}
/// @notice The latest implementation contracts for the OP Stack.
......@@ -112,10 +115,31 @@ contract OPContractsManager is ISemver {
address mipsImpl;
}
struct AddGameInput {
string saltMixer;
ISystemConfig systemConfig;
IProxyAdmin proxyAdmin;
IDelayedWETH delayedWETH;
GameType disputeGameType;
Claim disputeAbsolutePrestate;
uint256 disputeMaxGameDepth;
uint256 disputeSplitDepth;
Duration disputeClockExtension;
Duration disputeMaxClockDuration;
uint256 initialBond;
IBigStepper vm;
bool permissioned;
}
struct AddGameOutput {
IDelayedWETH delayedWETH;
IFaultDisputeGame faultDisputeGame;
}
// -------- Constants and Variables --------
/// @custom:semver 1.0.0-beta.27
string public constant version = "1.0.0-beta.27";
/// @custom:semver 1.0.0-beta.28
string public constant version = "1.0.0-beta.28";
/// @notice Represents the interface version so consumers know how to decode the DeployOutput struct
/// that's emitted in the `Deployed` event. Whenever that struct changes, a new version should be used.
......@@ -139,6 +163,10 @@ contract OPContractsManager is ISemver {
/// @notice Addresses of the latest implementation contracts.
Implementations internal implementation;
/// @notice The OPContractsManager contract that is currently being used. This is needed in the upgrade function
/// which is intended to be DELEGATECALLed.
OPContractsManager internal immutable thisOPCM;
// -------- Events --------
/// @notice Emitted when a new OP Stack chain is deployed.
......@@ -173,6 +201,12 @@ contract OPContractsManager is ISemver {
/// @notice Thrown when the starting anchor roots are not provided.
error InvalidStartingAnchorRoots();
/// @notice Thrown when certain methods are called outside of a DELEGATECALL.
error OnlyDelegatecall();
/// @notice Thrown when game configs passed to addGameType are invalid.
error InvalidGameConfigs();
// -------- Methods --------
constructor(
......@@ -190,6 +224,7 @@ contract OPContractsManager is ISemver {
blueprint = _blueprints;
implementation = _implementations;
thisOPCM = this;
}
function deploy(DeployInput calldata _input) external returns (DeployOutput memory) {
......@@ -279,7 +314,22 @@ contract OPContractsManager is ISemver {
blueprint.permissionedDisputeGame1,
blueprint.permissionedDisputeGame2,
computeSalt(l2ChainId, saltMixer, "PermissionedDisputeGame"),
encodePermissionedDisputeGameConstructor(_input, output)
encodePermissionedFDGConstructor(
IFaultDisputeGame.GameConstructorParams({
gameType: _input.disputeGameType,
absolutePrestate: _input.disputeAbsolutePrestate,
maxGameDepth: _input.disputeMaxGameDepth,
splitDepth: _input.disputeSplitDepth,
clockExtension: _input.disputeClockExtension,
maxClockDuration: _input.disputeMaxClockDuration,
vm: IBigStepper(implementation.mipsImpl),
weth: IDelayedWETH(payable(address(output.delayedWETHPermissionedGameProxy))),
anchorStateRegistry: IAnchorStateRegistry(address(output.anchorStateRegistryProxy)),
l2ChainId: _input.l2ChainId
}),
_input.roles.proposer,
_input.roles.challenger
)
)
);
......@@ -363,6 +413,118 @@ contract OPContractsManager is ISemver {
return output;
}
/// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs
/// must be added in ascending GameType order.
function addGameType(AddGameInput[] memory _gameConfigs) external returns (AddGameOutput[] memory) {
if (address(this) == address(thisOPCM)) revert OnlyDelegatecall();
if (_gameConfigs.length == 0) revert InvalidGameConfigs();
AddGameOutput[] memory outputs = new AddGameOutput[](_gameConfigs.length);
Blueprints memory bps = thisOPCM.blueprints();
// Store last game config as an int256 so that we can ensure that the same game config is not added twice.
// Using int256 generates cheaper, simpler bytecode.
int256 lastGameConfig = -1;
for (uint256 i = 0; i < _gameConfigs.length; i++) {
AddGameInput memory gameConfig = _gameConfigs[i];
// This conversion is safe because the GameType is a uint32, which will always fit in an int256.
int256 gameTypeInt = int256(uint256(gameConfig.disputeGameType.raw()));
// Ensure that the game configs are added in ascending order, and not duplicated.
if (lastGameConfig >= gameTypeInt) revert InvalidGameConfigs();
lastGameConfig = gameTypeInt;
// Grab the FDG from the SystemConfig.
IFaultDisputeGame fdg = IFaultDisputeGame(
address(
IDisputeGameFactory(gameConfig.systemConfig.disputeGameFactory()).gameImpls(
GameTypes.PERMISSIONED_CANNON
)
)
);
// Pull out the chain ID.
uint256 l2ChainId = fdg.l2ChainId();
// Deploy a new DelayedWETH proxy for this game if one hasn't already been specified. Leaving
/// gameConfig.delayedWETH as the zero address will cause a new DelayedWETH to be deployed for this game.
if (address(gameConfig.delayedWETH) == address(0)) {
outputs[i].delayedWETH = IDelayedWETH(
payable(deployProxy(l2ChainId, gameConfig.proxyAdmin, gameConfig.saltMixer, "DelayedWETH"))
);
// Initialize the proxy.
upgradeAndCall(
gameConfig.proxyAdmin,
address(outputs[i].delayedWETH),
thisOPCM.implementations().delayedWETHImpl,
abi.encodeCall(IDelayedWETH.initialize, (gameConfig.proxyAdmin.owner(), superchainConfig))
);
} else {
outputs[i].delayedWETH = gameConfig.delayedWETH;
}
// The below sections are functionally the same. Both deploy a new dispute game. The dispute game type is
// either permissioned or permissionless depending on game config.
if (gameConfig.permissioned) {
IPermissionedDisputeGame pdg = IPermissionedDisputeGame(address(fdg));
outputs[i].faultDisputeGame = IFaultDisputeGame(
Blueprint.deployFrom(
bps.permissionedDisputeGame1,
bps.permissionedDisputeGame2,
computeSalt(l2ChainId, gameConfig.saltMixer, "PermissionedDisputeGame"),
encodePermissionedFDGConstructor(
IFaultDisputeGame.GameConstructorParams(
gameConfig.disputeGameType,
gameConfig.disputeAbsolutePrestate,
gameConfig.disputeMaxGameDepth,
gameConfig.disputeSplitDepth,
gameConfig.disputeClockExtension,
gameConfig.disputeMaxClockDuration,
gameConfig.vm,
outputs[i].delayedWETH,
pdg.anchorStateRegistry(),
l2ChainId
),
pdg.proposer(),
pdg.challenger()
)
)
);
} else {
outputs[i].faultDisputeGame = IFaultDisputeGame(
Blueprint.deployFrom(
bps.permissionlessDisputeGame1,
bps.permissionlessDisputeGame2,
computeSalt(l2ChainId, gameConfig.saltMixer, "PermissionlessDisputeGame"),
encodePermissionlessFDGConstructor(
IFaultDisputeGame.GameConstructorParams(
gameConfig.disputeGameType,
gameConfig.disputeAbsolutePrestate,
gameConfig.disputeMaxGameDepth,
gameConfig.disputeSplitDepth,
gameConfig.disputeClockExtension,
gameConfig.disputeMaxClockDuration,
gameConfig.vm,
outputs[i].delayedWETH,
fdg.anchorStateRegistry(),
l2ChainId
)
)
)
);
}
// As a last step, register the new game type with the DisputeGameFactory. If the game type already exists,
// then its implementation will be overwritten.
IDisputeGameFactory dgf = IDisputeGameFactory(gameConfig.systemConfig.disputeGameFactory());
dgf.setImplementation(gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame)));
dgf.setInitBond(gameConfig.disputeGameType, gameConfig.initialBond);
}
return outputs;
}
// -------- Utilities --------
/// @notice Verifies that all inputs are valid and reverts if any are invalid.
......@@ -420,7 +582,7 @@ contract OPContractsManager is ISemver {
returns (address)
{
bytes32 salt = computeSalt(_l2ChainId, _saltMixer, _contractName);
return Blueprint.deployFrom(blueprint.proxy, salt, abi.encode(_proxyAdmin));
return Blueprint.deployFrom(thisOPCM.blueprints().proxy, salt, abi.encode(_proxyAdmin));
}
// -------- Initializer Encoding --------
......@@ -539,29 +701,29 @@ contract OPContractsManager is ISemver {
return abi.encodeCall(IDelayedWETH.initialize, (_input.roles.opChainProxyAdminOwner, superchainConfig));
}
function encodePermissionedDisputeGameConstructor(
DeployInput memory _input,
DeployOutput memory _output
function encodePermissionlessFDGConstructor(IFaultDisputeGame.GameConstructorParams memory _params)
internal
view
virtual
returns (bytes memory)
{
bytes memory dataWithSelector = abi.encodeCall(IFaultDisputeGame.__constructor__, (_params));
return Bytes.slice(dataWithSelector, 4);
}
function encodePermissionedFDGConstructor(
IFaultDisputeGame.GameConstructorParams memory _params,
address _proposer,
address _challenger
)
internal
view
virtual
returns (bytes memory)
{
return abi.encode(
_input.disputeGameType,
_input.disputeAbsolutePrestate,
_input.disputeMaxGameDepth,
_input.disputeSplitDepth,
_input.disputeClockExtension,
_input.disputeMaxClockDuration,
IBigStepper(implementation.mipsImpl),
IDelayedWETH(payable(address(_output.delayedWETHPermissionedGameProxy))),
IAnchorStateRegistry(address(_output.anchorStateRegistryProxy)),
_input.l2ChainId,
_input.roles.proposer,
_input.roles.challenger
);
bytes memory dataWithSelector =
abi.encodeCall(IPermissionedDisputeGame.__constructor__, (_params, _proposer, _challenger));
return Bytes.slice(dataWithSelector, 4);
}
/// @notice Returns default, standard config arguments for the SystemConfig initializer.
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Bytes } from "src/libraries/Bytes.sol";
/// @notice Methods for working with ERC-5202 blueprint contracts.
/// https://eips.ethereum.org/EIPS/eip-5202
library Blueprint {
......@@ -157,6 +159,50 @@ library Blueprint {
if (newContract_ == address(0)) revert DeploymentFailed();
}
/// @notice Deploys a blueprint contract with the given `_rawBytecode` and `_salt`. If the blueprint is too large to
/// fit in a single deployment, it is split across two addresses. It is the responsibility of the caller to handle
/// large contracts by checking if the second return value is not address(0).
function create(
bytes memory _rawBytecode,
bytes32 _salt
)
internal
returns (address newContract1_, address newContract2_)
{
if (_rawBytecode.length <= maxInitCodeSize()) {
newContract1_ = deploySmallBytecode(blueprintDeployerBytecode(_rawBytecode), _salt);
return (newContract1_, address(0));
}
(newContract1_, newContract2_) = deployBigBytecode(_rawBytecode, _salt);
}
/// @notice Deploys a blueprint contract that can fit in a single address.
function deploySmallBytecode(bytes memory _bytecode, bytes32 _salt) internal returns (address newContract_) {
assembly ("memory-safe") {
newContract_ := create2(0, add(_bytecode, 0x20), mload(_bytecode), _salt)
}
require(newContract_ != address(0), "Blueprint: create2 failed");
}
/// @notice Deploys a two blueprint contracts, splitting the bytecode across both of them.
function deployBigBytecode(
bytes memory _bytecode,
bytes32 _salt
)
internal
returns (address newContract1_, address newContract2_)
{
uint32 maxSize = maxInitCodeSize();
bytes memory part1Slice = Bytes.slice(_bytecode, 0, maxSize);
bytes memory part1 = blueprintDeployerBytecode(part1Slice);
bytes memory part2Slice = Bytes.slice(_bytecode, maxSize, _bytecode.length - maxSize);
bytes memory part2 = blueprintDeployerBytecode(part2Slice);
newContract1_ = deploySmallBytecode(part1, _salt);
newContract2_ = deploySmallBytecode(part2, _salt);
}
/// @notice Convert a bytes array to a uint256.
function bytesToUint(bytes memory _b) internal pure returns (uint256) {
if (_b.length > 32) revert BytesArrayTooLong();
......@@ -166,4 +212,9 @@ library Blueprint {
}
return number;
}
/// @notice Returns the maximum init code size for each blueprint. The preamble needs 3 bytes.
function maxInitCodeSize() internal pure returns (uint32) {
return 24576 - 3;
}
}
......@@ -9,6 +9,26 @@ import { DeployOPChain_TestBase } from "test/opcm/DeployOPChain.t.sol";
import { OPContractsManager } from "src/L1/OPContractsManager.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol";
import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol";
import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol";
import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
import { Blueprint } from "src/libraries/Blueprint.sol";
import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol";
import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol";
import { IBigStepper } from "interfaces/dispute/IBigStepper.sol";
import { DelayedWETH } from "src/dispute/DelayedWETH.sol";
import { MIPS } from "src/cannon/MIPS.sol";
import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol";
import { OutputRoot } from "src/dispute/lib/Types.sol";
import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol";
import { PreimageOracle } from "src/cannon/PreimageOracle.sol";
// Exposes internal functions for testing.
contract OPContractsManager_Harness is OPContractsManager {
......@@ -148,3 +168,235 @@ contract OPContractsManager_InternalMethods_Test is Test {
vm.assertEq(expected, actual);
}
}
contract OPContractsManager_AddGameType_Test is Test {
OPContractsManager internal opcm;
OPContractsManager.DeployOutput internal chainDeployOutput;
function setUp() public {
ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig"));
IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions"));
bytes32 salt = hex"01";
OPContractsManager.Blueprints memory blueprints;
(blueprints.addressManager,) = Blueprint.create(vm.getCode("AddressManager"), salt);
(blueprints.proxy,) = Blueprint.create(vm.getCode("Proxy"), salt);
(blueprints.proxyAdmin,) = Blueprint.create(vm.getCode("ProxyAdmin"), salt);
(blueprints.l1ChugSplashProxy,) = Blueprint.create(vm.getCode("L1ChugSplashProxy"), salt);
(blueprints.resolvedDelegateProxy,) = Blueprint.create(vm.getCode("ResolvedDelegateProxy"), salt);
(blueprints.anchorStateRegistry,) = Blueprint.create(vm.getCode("AnchorStateRegistry"), salt);
(blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) =
Blueprint.create(vm.getCode("PermissionedDisputeGame"), salt);
(blueprints.permissionlessDisputeGame1, blueprints.permissionlessDisputeGame2) =
Blueprint.create(vm.getCode("FaultDisputeGame"), salt);
IPreimageOracle oracle = IPreimageOracle(address(new PreimageOracle(126000, 86400)));
OPContractsManager.Implementations memory impls = OPContractsManager.Implementations({
l1ERC721BridgeImpl: address(new L1ERC721Bridge()),
optimismPortalImpl: address(new OptimismPortal2(1, 1)),
systemConfigImpl: address(new SystemConfig()),
optimismMintableERC20FactoryImpl: address(new OptimismMintableERC20Factory()),
l1CrossDomainMessengerImpl: address(new L1CrossDomainMessenger()),
l1StandardBridgeImpl: address(new L1StandardBridge()),
disputeGameFactoryImpl: address(new DisputeGameFactory()),
delayedWETHImpl: address(new DelayedWETH(3)),
mipsImpl: address(new MIPS(oracle))
});
vm.etch(address(superchainConfigProxy), hex"01");
vm.etch(address(protocolVersionsProxy), hex"01");
opcm = new OPContractsManager(superchainConfigProxy, protocolVersionsProxy, "dev", blueprints, impls);
AnchorStateRegistry.StartingAnchorRoot[] memory roots = new AnchorStateRegistry.StartingAnchorRoot[](1);
roots[0] = AnchorStateRegistry.StartingAnchorRoot({
outputRoot: OutputRoot({ root: Hash.wrap(hex"dead"), l2BlockNumber: 0 }),
gameType: GameType.wrap(1)
});
chainDeployOutput = opcm.deploy(
OPContractsManager.DeployInput({
roles: OPContractsManager.Roles({
opChainProxyAdminOwner: address(this),
systemConfigOwner: address(this),
batcher: address(this),
unsafeBlockSigner: address(this),
proposer: address(this),
challenger: address(this)
}),
basefeeScalar: 1,
blobBasefeeScalar: 1,
startingAnchorRoots: abi.encode(roots),
l2ChainId: 100,
saltMixer: "hello",
gasLimit: 30_000_000,
disputeGameType: GameType.wrap(1),
disputeAbsolutePrestate: Claim.wrap(
bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c")
),
disputeMaxGameDepth: 73,
disputeSplitDepth: 30,
disputeClockExtension: Duration.wrap(10800),
disputeMaxClockDuration: Duration.wrap(302400)
})
);
}
function test_addGameType_permissioned_succeeds() public {
OPContractsManager.AddGameInput memory input = newGameInputFactory(true);
OPContractsManager.AddGameOutput memory output = addGameType(input);
assertValidGameType(input, output);
IPermissionedDisputeGame newPDG = IPermissionedDisputeGame(address(output.faultDisputeGame));
IPermissionedDisputeGame oldPDG = chainDeployOutput.permissionedDisputeGame;
assertEq(newPDG.proposer(), oldPDG.proposer(), "proposer mismatch");
assertEq(newPDG.challenger(), oldPDG.challenger(), "challenger mismatch");
}
function test_addGameType_permissionless_succeeds() public {
OPContractsManager.AddGameInput memory input = newGameInputFactory(false);
OPContractsManager.AddGameOutput memory output = addGameType(input);
assertValidGameType(input, output);
IPermissionedDisputeGame notPDG = IPermissionedDisputeGame(address(output.faultDisputeGame));
vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args
notPDG.proposer();
}
function test_addGameType_reusedDelayedWETH_succeeds() public {
IDelayedWETH delayedWETH = IDelayedWETH(payable(address(new DelayedWETH(1))));
vm.etch(address(delayedWETH), hex"01");
OPContractsManager.AddGameInput memory input = newGameInputFactory(false);
input.delayedWETH = delayedWETH;
OPContractsManager.AddGameOutput memory output = addGameType(input);
assertValidGameType(input, output);
assertEq(address(output.delayedWETH), address(delayedWETH), "delayedWETH address mismatch");
}
function test_addGameType_outOfOrderInputs_reverts() public {
OPContractsManager.AddGameInput memory input1 = newGameInputFactory(false);
input1.disputeGameType = GameType.wrap(2);
OPContractsManager.AddGameInput memory input2 = newGameInputFactory(false);
input2.disputeGameType = GameType.wrap(1);
OPContractsManager.AddGameInput[] memory inputs = new OPContractsManager.AddGameInput[](2);
inputs[0] = input1;
inputs[1] = input2;
// For the sake of completeness, we run the call again to validate the success behavior.
(bool success,) = address(opcm).delegatecall(abi.encodeCall(OPContractsManager.addGameType, (inputs)));
assertFalse(success, "addGameType should have failed");
}
function test_addGameType_duplicateGameType_reverts() public {
OPContractsManager.AddGameInput memory input = newGameInputFactory(false);
OPContractsManager.AddGameInput[] memory inputs = new OPContractsManager.AddGameInput[](2);
inputs[0] = input;
inputs[1] = input;
// See test above for why we run the call twice.
(bool success, bytes memory revertData) =
address(opcm).delegatecall(abi.encodeCall(OPContractsManager.addGameType, (inputs)));
assertFalse(success, "addGameType should have failed");
assertEq(bytes4(revertData), OPContractsManager.InvalidGameConfigs.selector, "revertData mismatch");
}
function test_addGameType_zeroLengthInput_reverts() public {
OPContractsManager.AddGameInput[] memory inputs = new OPContractsManager.AddGameInput[](0);
(bool success, bytes memory revertData) =
address(opcm).delegatecall(abi.encodeCall(OPContractsManager.addGameType, (inputs)));
assertFalse(success, "addGameType should have failed");
assertEq(bytes4(revertData), OPContractsManager.InvalidGameConfigs.selector, "revertData mismatch");
}
function test_addGameType_notDelegateCall_reverts() public {
OPContractsManager.AddGameInput memory input = newGameInputFactory(true);
OPContractsManager.AddGameInput[] memory inputs = new OPContractsManager.AddGameInput[](1);
inputs[0] = input;
vm.expectRevert(OPContractsManager.OnlyDelegatecall.selector);
opcm.addGameType(inputs);
}
function addGameType(OPContractsManager.AddGameInput memory input)
internal
returns (OPContractsManager.AddGameOutput memory)
{
OPContractsManager.AddGameInput[] memory inputs = new OPContractsManager.AddGameInput[](1);
inputs[0] = input;
(bool success, bytes memory rawGameOut) =
address(opcm).delegatecall(abi.encodeCall(OPContractsManager.addGameType, (inputs)));
assertTrue(success, "addGameType failed");
OPContractsManager.AddGameOutput[] memory addGameOutAll =
abi.decode(rawGameOut, (OPContractsManager.AddGameOutput[]));
return addGameOutAll[0];
}
function newGameInputFactory(bool permissioned) internal view returns (OPContractsManager.AddGameInput memory) {
return OPContractsManager.AddGameInput({
saltMixer: "hello",
systemConfig: chainDeployOutput.systemConfigProxy,
proxyAdmin: chainDeployOutput.opChainProxyAdmin,
delayedWETH: IDelayedWETH(payable(address(0))),
disputeGameType: GameType.wrap(2000),
disputeAbsolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")),
disputeMaxGameDepth: 73,
disputeSplitDepth: 30,
disputeClockExtension: Duration.wrap(10800),
disputeMaxClockDuration: Duration.wrap(302400),
initialBond: 1 ether,
vm: IBigStepper(address(opcm.implementations().mipsImpl)),
permissioned: permissioned
});
}
function assertValidGameType(
OPContractsManager.AddGameInput memory agi,
OPContractsManager.AddGameOutput memory ago
)
internal
view
{
// Check the config for the game itself
assertEq(ago.faultDisputeGame.gameType().raw(), agi.disputeGameType.raw(), "gameType mismatch");
assertEq(
ago.faultDisputeGame.absolutePrestate().raw(),
agi.disputeAbsolutePrestate.raw(),
"absolutePrestate mismatch"
);
assertEq(ago.faultDisputeGame.maxGameDepth(), agi.disputeMaxGameDepth, "maxGameDepth mismatch");
assertEq(ago.faultDisputeGame.splitDepth(), agi.disputeSplitDepth, "splitDepth mismatch");
assertEq(
ago.faultDisputeGame.clockExtension().raw(), agi.disputeClockExtension.raw(), "clockExtension mismatch"
);
assertEq(
ago.faultDisputeGame.maxClockDuration().raw(),
agi.disputeMaxClockDuration.raw(),
"maxClockDuration mismatch"
);
assertEq(address(ago.faultDisputeGame.vm()), address(agi.vm), "vm address mismatch");
assertEq(address(ago.faultDisputeGame.weth()), address(ago.delayedWETH), "delayedWETH address mismatch");
assertEq(
address(ago.faultDisputeGame.anchorStateRegistry()),
address(chainDeployOutput.anchorStateRegistryProxy),
"ASR address mismatch"
);
// Check the DGF
assertEq(
chainDeployOutput.disputeGameFactoryProxy.gameImpls(agi.disputeGameType).gameType().raw(),
agi.disputeGameType.raw(),
"gameType mismatch"
);
assertEq(
address(chainDeployOutput.disputeGameFactoryProxy.gameImpls(agi.disputeGameType)),
address(ago.faultDisputeGame),
"gameImpl address mismatch"
);
assertEq(address(ago.faultDisputeGame.weth()), address(ago.delayedWETH), "weth address mismatch");
assertEq(
chainDeployOutput.disputeGameFactoryProxy.initBonds(agi.disputeGameType), agi.initialBond, "bond mismatch"
);
}
}
......@@ -8,6 +8,8 @@ import { FaultDisputeGame_Init, _changeClaimStatus } from "test/dispute/FaultDis
import "src/dispute/lib/Types.sol";
import "src/dispute/lib/Errors.sol";
import { Hash } from "src/dispute/lib/Types.sol";
contract AnchorStateRegistry_Init is FaultDisputeGame_Init {
function setUp() public virtual override {
// Duplicating the initialization/setup logic of FaultDisputeGame_Test.
......
......@@ -781,6 +781,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.blueprints.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.implementations.selector });
_addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.addGameType.selector });
// OPContractsManagerInterop
_addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("version()") });
......@@ -792,6 +793,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.blueprints.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.implementations.selector });
_addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.addGameType.selector });
// DeputyGuardianModule
_addSpec({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment