Commit 308ce74c authored by Maurelian's avatar Maurelian Committed by GitHub

OPSM: Deploy Permissioned Game (#12064)

* chore: fix semver lock

* fix: no permissionless root, remove hash from 0xdead

* fix: use 0xdead root properly

* feat: add remaining fault proof support

* chore: Update semver-lock

* fix: Remove extra anchor root definition and restore aritfactsFs argument

* feat: Add wip big blueprint code

* Don't wrap input to deployBigBytecode with preamble

* fix: off by one in deployBigBytecode

* feat: more gas efficient blueprint deployment for permissioned game

* Get the big deployments working

* perf: more efficient preamble parsing

* chore: snapshots + fix revert

* test: skip FaultDisputeGameAddress since we don't deploy it yet

* chore: cleanup

---------
Co-authored-by: default avatarMatt Solomon <matt@mattsolomon.dev>
Co-authored-by: default avatarMatthew Slipper <me@matthewslipper.com>
parent ec3f6344
......@@ -27,6 +27,8 @@ import (
const TestParams = `
participants:
- el_type: geth
el_extra_params:
- "--gcmode=archive"
cl_type: lighthouse
network_params:
prefunded_accounts: '{ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { "balance": "1000000ETH" } }'
......@@ -41,6 +43,7 @@ network_params:
}'
network_id: "77799777"
seconds_per_slot: 3
genesis_delay: 0
`
type deployerKey struct{}
......@@ -56,7 +59,7 @@ func (d *deployerKey) String() string {
func TestEndToEndApply(t *testing.T) {
kurtosisutil.Test(t)
lgr := testlog.Logger(t, slog.LevelInfo)
lgr := testlog.Logger(t, slog.LevelDebug)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
......@@ -189,6 +192,10 @@ func TestEndToEndApply(t *testing.T) {
{"DelayedWETHPermissionlessGameProxyAddress", chainState.DelayedWETHPermissionlessGameProxyAddress},
}
for _, addr := range chainAddrs {
// TODO Delete this `if`` block once FaultDisputeGameAddress is deployed.
if addr.name == "FaultDisputeGameAddress" {
continue
}
t.Run(fmt.Sprintf("chain %s - %s", chainState.ID, addr.name), func(t *testing.T) {
code, err := l1Client.CodeAt(ctx, addr.addr, nil)
require.NoError(t, err)
......
......@@ -10,6 +10,7 @@ import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Bytes } from "src/libraries/Bytes.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol";
......@@ -23,6 +24,7 @@ import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
import { MIPS } from "src/cannon/MIPS.sol";
import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol";
import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol";
import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { ProtocolVersions } from "src/L1/ProtocolVersions.sol";
......@@ -514,10 +516,11 @@ contract DeployImplementations is Script {
blueprints.l1ChugSplashProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(type(L1ChugSplashProxy).creationCode), salt);
blueprints.resolvedDelegateProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(type(ResolvedDelegateProxy).creationCode), salt);
blueprints.anchorStateRegistry = deployBytecode(Blueprint.blueprintDeployerBytecode(type(AnchorStateRegistry).creationCode), salt);
(blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = deployBigBytecode(type(PermissionedDisputeGame).creationCode, salt);
vm.stopBroadcast();
// forgefmt: disable-end
OPStackManager.ImplementationSetter[] memory setters = new OPStackManager.ImplementationSetter[](7);
OPStackManager.ImplementationSetter[] memory setters = new OPStackManager.ImplementationSetter[](9);
setters[0] = OPStackManager.ImplementationSetter({
name: "L1ERC721Bridge",
info: OPStackManager.Implementation(address(_dio.l1ERC721BridgeImpl()), L1ERC721Bridge.initialize.selector)
......@@ -543,13 +546,22 @@ contract DeployImplementations is Script {
name: "L1StandardBridge",
info: OPStackManager.Implementation(address(_dio.l1StandardBridgeImpl()), L1StandardBridge.initialize.selector)
});
setters[6] = OPStackManager.ImplementationSetter({
name: "DisputeGameFactory",
info: OPStackManager.Implementation(
address(_dio.disputeGameFactoryImpl()), DisputeGameFactory.initialize.selector
)
});
setters[7] = OPStackManager.ImplementationSetter({
name: "DelayedWETH",
info: OPStackManager.Implementation(address(_dio.delayedWETHImpl()), DelayedWETH.initialize.selector)
});
setters[8] = OPStackManager.ImplementationSetter({
name: "MIPS",
// MIPS is a singleton for all chains, so it doesn't need to be initialized, so the
// selector is just `bytes4(0)`.
info: OPStackManager.Implementation(address(_dio.mipsSingleton()), bytes4(0))
});
// This call contains a broadcast to deploy OPSM which is proxied.
OPStackManager opsmProxy = createOPSMContract(_dii, _dio, blueprints, release, setters);
......@@ -617,14 +629,14 @@ contract DeployImplementations is Script {
// The fault proofs contracts are configured as follows:
// | Contract | Proxied | Deployment | MCP Ready |
// |-------------------------|---------|-----------------------------------|------------|
// | DisputeGameFactory | Yes | Bespoke | Yes | X
// | AnchorStateRegistry | Yes | Bespoke | No | X
// | FaultDisputeGame | No | Bespoke | No | Todo
// | PermissionedDisputeGame | No | Bespoke | No | Todo
// | DelayedWETH | Yes | Two bespoke (one per DisputeGame) | No | Todo: Proxies.
// | PreimageOracle | No | Shared | N/A | X
// | MIPS | No | Shared | N/A | X
// | OptimismPortal2 | Yes | Shared | No | X
// | DisputeGameFactory | Yes | Bespoke | Yes |
// | AnchorStateRegistry | Yes | Bespoke | No |
// | FaultDisputeGame | No | Bespoke | No | Not yet supported by OPCM
// | PermissionedDisputeGame | No | Bespoke | No |
// | DelayedWETH | Yes | Two bespoke (one per DisputeGame) | No |
// | PreimageOracle | No | Shared | N/A |
// | MIPS | No | Shared | N/A |
// | OptimismPortal2 | Yes | Shared | No |
//
// This script only deploys the shared contracts. The bespoke contracts are deployed by
// `DeployOPChain.s.sol`. When the shared contracts are proxied, the contracts deployed here are
......@@ -731,6 +743,26 @@ contract DeployImplementations is Script {
}
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);
}
}
// Similar to how DeploySuperchain.s.sol contains a lot of comments to thoroughly document the script
......
......@@ -11,6 +11,7 @@ import { BaseDeployIO } from "scripts/utils/BaseDeployIO.sol";
import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol";
import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol";
import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
......@@ -23,7 +24,7 @@ import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol";
import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol";
import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol";
import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol";
import { GameType, GameTypes, Hash, OutputRoot } from "src/dispute/lib/Types.sol";
import { Claim, GameType, GameTypes, Hash, OutputRoot } from "src/dispute/lib/Types.sol";
import { OPStackManager } from "src/L1/OPStackManager.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
......@@ -201,7 +202,7 @@ contract DeployOPChainOutput is BaseDeployIO {
address(_disputeGameFactoryProxy),
address(_anchorStateRegistryProxy),
address(_anchorStateRegistryImpl),
address(_faultDisputeGame),
// address(_faultDisputeGame),
address(_permissionedDisputeGame),
address(_delayedWETHPermissionedGameProxy),
address(_delayedWETHPermissionlessGameProxy)
......@@ -289,8 +290,8 @@ contract DeployOPChainOutput is BaseDeployIO {
// -------- Deployment Assertions --------
function assertValidDeploy(DeployOPChainInput _doi) internal {
assertValidAnchorStateRegistryProxy(_doi);
assertValidAnchorStateRegistryImpl(_doi);
assertValidAnchorStateRegistryProxy(_doi);
assertValidDelayedWETHs(_doi);
assertValidDisputeGameFactory(_doi);
assertValidL1CrossDomainMessenger(_doi);
......@@ -298,9 +299,23 @@ contract DeployOPChainOutput is BaseDeployIO {
assertValidL1StandardBridge(_doi);
assertValidOptimismMintableERC20Factory(_doi);
assertValidOptimismPortal(_doi);
assertValidPermissionedDisputeGame(_doi);
assertValidSystemConfig(_doi);
// TODO Other FP assertions like the dispute games, anchor state registry, etc.
// TODO add initialization assertions
}
function assertValidPermissionedDisputeGame(DeployOPChainInput _doi) internal view {
PermissionedDisputeGame game = permissionedDisputeGame();
require(GameType.unwrap(game.gameType()) == GameType.unwrap(GameTypes.PERMISSIONED_CANNON), "DPG-10");
require(Claim.unwrap(game.absolutePrestate()) == bytes32(hex"dead"), "DPG-20");
OPStackManager opsm = _doi.opsmProxy();
(address mips,) = opsm.implementations(opsm.latestRelease(), "MIPS");
require(game.vm() == IBigStepper(mips), "DPG-30");
require(address(game.weth()) == address(delayedWETHPermissionedGameProxy()), "DPG-40");
require(address(game.anchorStateRegistry()) == address(anchorStateRegistryProxy()), "DPG-50");
require(game.l2ChainId() == _doi.l2ChainId(), "DPG-60");
}
function assertValidAnchorStateRegistryProxy(DeployOPChainInput) internal {
......@@ -436,7 +451,14 @@ contract DeployOPChainOutput is BaseDeployIO {
}
function assertValidDisputeGameFactory(DeployOPChainInput) internal view {
// TODO add in once FP support is added.
DisputeGameFactory factory = disputeGameFactoryProxy();
DeployUtils.assertInitialized({ _contractAddress: address(factory), _slot: 0, _offset: 0 });
require(
address(factory.gameImpls(GameTypes.PERMISSIONED_CANNON)) == address(permissionedDisputeGame()), "DF-10"
);
require(factory.owner() == address(opChainProxyAdmin()), "DF-20");
}
function assertValidDelayedWETHs(DeployOPChainInput) internal view {
......@@ -480,7 +502,7 @@ contract DeployOPChain is Script {
vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy");
vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy");
vm.label(address(deployOutput.anchorStateRegistryImpl), "anchorStateRegistryImpl");
vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame");
// vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame");
vm.label(address(deployOutput.permissionedDisputeGame), "permissionedDisputeGame");
vm.label(address(deployOutput.delayedWETHPermissionedGameProxy), "delayedWETHPermissionedGameProxy");
vm.label(address(deployOutput.delayedWETHPermissionlessGameProxy), "delayedWETHPermissionlessGameProxy");
......@@ -498,7 +520,7 @@ contract DeployOPChain is Script {
_doo.set(_doo.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy));
_doo.set(_doo.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy));
_doo.set(_doo.anchorStateRegistryImpl.selector, address(deployOutput.anchorStateRegistryImpl));
_doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame));
// _doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame));
_doo.set(_doo.permissionedDisputeGame.selector, address(deployOutput.permissionedDisputeGame));
_doo.set(_doo.delayedWETHPermissionedGameProxy.selector, address(deployOutput.delayedWETHPermissionedGameProxy));
_doo.set(
......
......@@ -32,8 +32,8 @@
"sourceCodeHash": "0xde4df0f9633dc0cdb1c9f634003ea5b0f7c5c1aebc407bc1b2f44c0ecf938649"
},
"src/L1/OPStackManager.sol": {
"initCodeHash": "0x4bffecbd95e63f9bd04ab8e3c6a804cc25e0cd151ebeb7f8d6b9330332e6eb20",
"sourceCodeHash": "0x850f1eacc77f1a5c680625196618bc4b4332cb68924d9eddd57c749bedcd7c94"
"initCodeHash": "0x5b451782192b8429f6822c88270c4f0dbd10342518c5695ecf4dff7b5ebfb4e4",
"sourceCodeHash": "0x4a9c242ce96471437ec97662d2365a7bda376db765c630a41cbe238811f1df51"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0xbe2c0c81b3459014f287d8c89cdc0d27dde5d1f44e5d024fa1e4773ddc47c190",
......
......@@ -50,6 +50,16 @@
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPStackManager.Blueprints",
......@@ -298,6 +308,16 @@
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPStackManager.Blueprints",
......@@ -499,6 +519,11 @@
"name": "EmptyInitcode",
"type": "error"
},
{
"inputs": [],
"name": "IdentityPrecompileCallFailed",
"type": "error"
},
{
"inputs": [],
"name": "InvalidChainId",
......
......@@ -50,6 +50,16 @@
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPStackManager.Blueprints",
......@@ -298,6 +308,16 @@
"internalType": "address",
"name": "anchorStateRegistry",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame1",
"type": "address"
},
{
"internalType": "address",
"name": "permissionedDisputeGame2",
"type": "address"
}
],
"internalType": "struct OPStackManager.Blueprints",
......@@ -499,6 +519,11 @@
"name": "EmptyInitcode",
"type": "error"
},
{
"inputs": [],
"name": "IdentityPrecompileCallFailed",
"type": "error"
},
{
"inputs": [],
"name": "InvalidChainId",
......
......@@ -13,32 +13,39 @@
"slot": "0",
"type": "bool"
},
{
"bytes": "192",
"label": "blueprint",
"offset": 0,
"slot": "1",
"type": "struct OPStackManager.Blueprints"
},
{
"bytes": "32",
"label": "latestRelease",
"offset": 0,
"slot": "7",
"slot": "1",
"type": "string"
},
{
"bytes": "32",
"label": "implementations",
"offset": 0,
"slot": "8",
"slot": "2",
"type": "mapping(string => mapping(string => struct OPStackManager.Implementation))"
},
{
"bytes": "32",
"label": "systemConfigs",
"offset": 0,
"slot": "9",
"slot": "3",
"type": "mapping(uint256 => contract SystemConfig)"
},
{
"bytes": "256",
"label": "blueprint",
"offset": 0,
"slot": "4",
"type": "struct OPStackManager.Blueprints"
},
{
"bytes": "1600",
"label": "__gap",
"offset": 0,
"slot": "12",
"type": "uint256[50]"
}
]
\ No newline at end of file
......@@ -13,32 +13,39 @@
"slot": "0",
"type": "bool"
},
{
"bytes": "192",
"label": "blueprint",
"offset": 0,
"slot": "1",
"type": "struct OPStackManager.Blueprints"
},
{
"bytes": "32",
"label": "latestRelease",
"offset": 0,
"slot": "7",
"slot": "1",
"type": "string"
},
{
"bytes": "32",
"label": "implementations",
"offset": 0,
"slot": "8",
"slot": "2",
"type": "mapping(string => mapping(string => struct OPStackManager.Implementation))"
},
{
"bytes": "32",
"label": "systemConfigs",
"offset": 0,
"slot": "9",
"slot": "3",
"type": "mapping(uint256 => contract SystemConfig)"
},
{
"bytes": "256",
"label": "blueprint",
"offset": 0,
"slot": "4",
"type": "struct OPStackManager.Blueprints"
},
{
"bytes": "1600",
"label": "__gap",
"offset": 0,
"slot": "12",
"type": "uint256[50]"
}
]
\ No newline at end of file
......@@ -20,6 +20,9 @@ library Blueprint {
/// @notice Thrown when parsing a blueprint preamble and the resulting initcode is empty.
error EmptyInitcode();
/// @notice Thrown when call to the identity precompile fails.
error IdentityPrecompileCallFailed();
/// @notice Thrown when parsing a blueprint preamble and the bytecode does not contain the expected prefix bytes.
error NotABlueprint();
......@@ -56,7 +59,7 @@ library Blueprint {
/// @notice Given bytecode as a sequence of bytes, parse the blueprint preamble and deconstruct
/// the bytecode into the ERC version, preamble data and initcode. Reverts if the bytecode is
/// not a valid blueprint contract according to ERC-5202.
function parseBlueprintPreamble(bytes memory _bytecode) internal pure returns (Preamble memory) {
function parseBlueprintPreamble(bytes memory _bytecode) internal view returns (Preamble memory) {
if (_bytecode.length < 2 || _bytecode[0] != 0xFE || _bytecode[1] != 0x71) {
revert NotABlueprint();
}
......@@ -77,18 +80,34 @@ library Blueprint {
bytes memory preambleData = new bytes(dataLength);
if (nLengthBytes != 0) {
uint256 dataStart = 3 + nLengthBytes;
// This loop is very small, so not worth using the identity precompile like we do with initcode below.
for (uint256 i = 0; i < dataLength; i++) {
preambleData[i] = _bytecode[dataStart + i];
}
}
// Parsing the initcode byte-by-byte is too costly for long initcode, so we perform a staticcall
// to the identity precompile at address(0x04) to copy the initcode.
uint256 initcodeStart = 3 + nLengthBytes + dataLength;
bytes memory initcode = new bytes(_bytecode.length - initcodeStart);
for (uint256 i = 0; i < initcode.length; i++) {
initcode[i] = _bytecode[initcodeStart + i];
uint256 initcodeLength = _bytecode.length - initcodeStart;
if (initcodeLength == 0) revert EmptyInitcode();
bytes memory initcode = new bytes(initcodeLength);
bool success;
assembly ("memory-safe") {
// Calculate the memory address of the input data (initcode) within _bytecode.
// - add(_bytecode, 32): Moves past the length field to the start of _bytecode's data.
// - add(..., initcodeStart): Adds the offset to reach the initcode within _bytecode.
let inputData := add(add(_bytecode, 32), initcodeStart)
// Calculate the memory address for the output data in initcode.
let outputData := add(initcode, 32)
// Perform the staticcall to the identity precompile.
success := staticcall(gas(), 0x04, inputData, initcodeLength, outputData, initcodeLength)
}
if (initcode.length == 0) revert EmptyInitcode();
if (!success) revert IdentityPrecompileCallFailed();
return Preamble(ercVersion, preambleData, initcode);
}
......@@ -112,6 +131,32 @@ library Blueprint {
if (newContract_ == address(0)) revert DeploymentFailed();
}
/// @notice Parses the code at two target addresses as individual blueprints, concatentates them and then deploys
/// the resulting initcode with the given `_data` appended, i.e. `_data` is the ABI-encoded constructor arguments.
function deployFrom(
address _target1,
address _target2,
bytes32 _salt,
bytes memory _data
)
internal
returns (address newContract_)
{
Preamble memory preamble1 = parseBlueprintPreamble(address(_target1).code);
if (preamble1.ercVersion != 0) revert UnsupportedERCVersion(preamble1.ercVersion);
if (preamble1.preambleData.length != 0) revert UnexpectedPreambleData(preamble1.preambleData);
Preamble memory preamble2 = parseBlueprintPreamble(address(_target2).code);
if (preamble2.ercVersion != 0) revert UnsupportedERCVersion(preamble2.ercVersion);
if (preamble2.preambleData.length != 0) revert UnexpectedPreambleData(preamble2.preambleData);
bytes memory initcode = bytes.concat(preamble1.initcode, preamble2.initcode, _data);
assembly ("memory-safe") {
newContract_ := create2(0, add(initcode, 0x20), mload(initcode), _salt)
}
if (newContract_ == address(0)) revert DeploymentFailed();
}
/// @notice Convert a bytes array to a uint256.
function bytesToUint(bytes memory _b) internal pure returns (uint256) {
if (_b.length > 32) revert BytesArrayTooLong();
......
......@@ -22,7 +22,7 @@ contract BlueprintHarness {
return Blueprint.blueprintDeployerBytecode(_initcode);
}
function parseBlueprintPreamble(bytes memory _bytecode) public pure returns (Blueprint.Preamble memory) {
function parseBlueprintPreamble(bytes memory _bytecode) public view returns (Blueprint.Preamble memory) {
return Blueprint.parseBlueprintPreamble(_bytecode);
}
......
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