Commit 2f2554af authored by Maurelian's avatar Maurelian Committed by GitHub

Add AnchorStateRegistry Implementation to OPSM (#11955)

* feat: Add ASR proxy to OPStackManager

* feat: Add AnchorStateRegistry blueprint

* feat: Add AnchorStateRegistry Implementation

* feat: Return startingAnchorInputs as bytes

The op-deployer tooling does not support structs, therefore we need to
return a more generic type for compatibility.

* rebuild snapshots

* fix: ASR initializer encoding

* handoff commit with op-deployer debugging

debugging op-deployer test
wip: literal anchor roots

* test and golang fixes

* hardcode permissioned state

* hardcode 0xdead as the starting anchor root

* chore: fix semver lock

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

* fix: use 0xdead root properly

* fix: set the override in the input contract

* Fix tests and accidental mutation of `Implementation` struct

* lint

* semver

* Update op-chain-ops/deployer/opsm/opchain.go
Co-authored-by: default avatarMaurelian <john@oplabs.co>

* Update packages/contracts-bedrock/scripts/DeployOPChain.s.sol

---------
Co-authored-by: default avatarMatthew Slipper <me@matthewslipper.com>
Co-authored-by: default avatarMatt Solomon <matt@mattsolomon.dev>
parent 6933bfea
......@@ -9,6 +9,12 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/script"
)
// PermissionedGameStartingAnchorRoots is a root of bytes32(hex"dead") for the permissioned game at block 0,
// and no root for the permissionless game.
var PermissionedGameStartingAnchorRoots = []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
type DeployOPChainInput struct {
OpChainProxyAdminOwner common.Address
SystemConfigOwner common.Address
......@@ -27,6 +33,10 @@ func (input *DeployOPChainInput) InputSet() bool {
return true
}
func (input *DeployOPChainInput) StartingAnchorRoots() []byte {
return PermissionedGameStartingAnchorRoots
}
type DeployOPChainOutput struct {
OpChainProxyAdmin common.Address
AddressManager common.Address
......@@ -65,6 +75,7 @@ func DeployOPChain(host *script.Host, input DeployOPChainInput) (DeployOPChainOu
return dco, fmt.Errorf("failed to insert DeployOPChainInput precompile: %w", err)
}
defer cleanupInput()
host.Label(inputAddr, "DeployOPChainInput")
cleanupOutput, err := script.WithPrecompileAtAddress[*DeployOPChainOutput](host, outputAddr, &dco,
script.WithFieldSetter[*DeployOPChainOutput])
......@@ -72,6 +83,7 @@ func DeployOPChain(host *script.Host, input DeployOPChainInput) (DeployOPChainOu
return dco, fmt.Errorf("failed to insert DeployOPChainOutput precompile: %w", err)
}
defer cleanupOutput()
host.Label(outputAddr, "DeployOPChainOutput")
deployScript, cleanupDeploy, err := script.WithScript[DeployOPChainScript](host, "DeployOPChain.s.sol", "DeployOPChain")
if err != nil {
......
......@@ -617,14 +617,14 @@ contract DeployImplementations is Script {
// The fault proofs contracts are configured as follows:
// | Contract | Proxied | Deployment | MCP Ready |
// |-------------------------|---------|-----------------------------------|------------|
// | DisputeGameFactory | Yes | Bespoke | Yes |
// | AnchorStateRegistry | Yes | Bespoke | No |
// | FaultDisputeGame | No | Bespoke | No |
// | 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 |
// | 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
//
// 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
......
......@@ -15,6 +15,7 @@ import { Constants } from "src/libraries/Constants.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol";
import { AddressManager } from "src/legacy/AddressManager.sol";
import { DelayedWETH } from "src/dispute/DelayedWETH.sol";
......@@ -22,6 +23,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 { OPStackManager } from "src/L1/OPStackManager.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
......@@ -116,6 +118,26 @@ contract DeployOPChainInput is BaseDeployIO {
return _l2ChainId;
}
function startingAnchorRoots() public pure returns (bytes memory) {
// WARNING: For now always hardcode the starting permissioned game anchor root to 0xdead,
// and we do not set anything for the permissioned game. This is because we currently only
// support deploying straight to permissioned games, and the starting root does not
// matter for that, as long as it is non-zero, since no games will be played. We do not
// deploy the permissionless game (and therefore do not set a starting root for it here)
// because to to update to the permissionless game, we will need to update its starting
// anchor root and deploy a new permissioned dispute game contract anyway.
//
// You can `console.logBytes(abi.encode(defaultStartingAnchorRoots))` to get the bytes that
// are hardcoded into `op-chain-ops/deployer/opsm/opchain.go`
AnchorStateRegistry.StartingAnchorRoot[] memory defaultStartingAnchorRoots =
new AnchorStateRegistry.StartingAnchorRoot[](1);
defaultStartingAnchorRoots[0] = AnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: OutputRoot({ root: Hash.wrap(bytes32(hex"dead")), l2BlockNumber: 0 })
});
return abi.encode(defaultStartingAnchorRoots);
}
// TODO: Check that opsm is proxied and it has an implementation.
function opsmProxy() public view returns (OPStackManager) {
require(address(_opsmProxy) != address(0), "DeployOPChainInput: not set");
......@@ -162,7 +184,7 @@ contract DeployOPChainOutput is BaseDeployIO {
// forgefmt: disable-end
}
function checkOutput(DeployOPChainInput _doi) public view {
function checkOutput(DeployOPChainInput _doi) public {
// With 16 addresses, we'd get a stack too deep error if we tried to do this inline as a
// single call to `Solarray.addresses`. So we split it into two calls.
address[] memory addrs1 = Solarray.addresses(
......@@ -266,7 +288,9 @@ contract DeployOPChainOutput is BaseDeployIO {
// -------- Deployment Assertions --------
function assertValidDeploy(DeployOPChainInput _doi) internal view {
function assertValidDeploy(DeployOPChainInput _doi) internal {
assertValidAnchorStateRegistryProxy(_doi);
assertValidAnchorStateRegistryImpl(_doi);
assertValidDelayedWETHs(_doi);
assertValidDisputeGameFactory(_doi);
assertValidL1CrossDomainMessenger(_doi);
......@@ -279,6 +303,32 @@ contract DeployOPChainOutput is BaseDeployIO {
// TODO add initialization assertions
}
function assertValidAnchorStateRegistryProxy(DeployOPChainInput) internal {
// First we check the proxy as itself.
Proxy proxy = Proxy(payable(address(anchorStateRegistryProxy())));
vm.prank(address(0));
address admin = proxy.admin();
require(admin == address(opChainProxyAdmin()), "ANCHORP-10");
// Then we check the proxy as ASR.
DeployUtils.assertInitialized({ _contractAddress: address(anchorStateRegistryProxy()), _slot: 0, _offset: 0 });
vm.prank(address(0));
address impl = proxy.implementation();
require(impl == address(anchorStateRegistryImpl()), "ANCHORP-20");
require(
address(anchorStateRegistryProxy().disputeGameFactory()) == address(disputeGameFactoryProxy()), "ANCHORP-30"
);
}
function assertValidAnchorStateRegistryImpl(DeployOPChainInput) internal view {
AnchorStateRegistry registry = anchorStateRegistryImpl();
DeployUtils.assertInitialized({ _contractAddress: address(registry), _slot: 0, _offset: 0 });
require(address(registry.disputeGameFactory()) == address(disputeGameFactoryProxy()), "ANCHORI-10");
}
function assertValidSystemConfig(DeployOPChainInput _doi) internal view {
SystemConfig systemConfig = systemConfigProxy();
......@@ -412,7 +462,8 @@ contract DeployOPChain is Script {
roles: roles,
basefeeScalar: _doi.basefeeScalar(),
blobBasefeeScalar: _doi.blobBaseFeeScalar(),
l2ChainId: _doi.l2ChainId()
l2ChainId: _doi.l2ChainId(),
startingAnchorRoots: _doi.startingAnchorRoots()
});
vm.broadcast(msg.sender);
......
......@@ -32,8 +32,8 @@
"sourceCodeHash": "0xde4df0f9633dc0cdb1c9f634003ea5b0f7c5c1aebc407bc1b2f44c0ecf938649"
},
"src/L1/OPStackManager.sol": {
"initCodeHash": "0x022b3f6a80eb637972dd0d9ce8666a037c4b916889f44f86771d8c3add9d615d",
"sourceCodeHash": "0xb085725e18c1a0cc1826b770e403ecad765fce686bb80555bf0f6c3c67b21cba"
"initCodeHash": "0x4bffecbd95e63f9bd04ab8e3c6a804cc25e0cd151ebeb7f8d6b9330332e6eb20",
"sourceCodeHash": "0x850f1eacc77f1a5c680625196618bc4b4332cb68924d9eddd57c749bedcd7c94"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0xbe2c0c81b3459014f287d8c89cdc0d27dde5d1f44e5d024fa1e4773ddc47c190",
......
......@@ -134,6 +134,11 @@
"internalType": "uint256",
"name": "l2ChainId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "startingAnchorRoots",
"type": "bytes"
}
],
"internalType": "struct OPStackManager.DeployInput",
......
......@@ -134,6 +134,11 @@
"internalType": "uint256",
"name": "l2ChainId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "startingAnchorRoots",
"type": "bytes"
}
],
"internalType": "struct OPStackManager.DeployInput",
......
......@@ -55,6 +55,9 @@ contract OPStackManager is ISemver, Initializable {
uint32 basefeeScalar;
uint32 blobBasefeeScalar;
uint256 l2ChainId;
// The correct type is AnchorStateRegistry.StartingAnchorRoot[] memory,
// but OP Deployer does not yet support structs.
bytes startingAnchorRoots;
}
/// @notice The full set of outputs from deploying a new OP Stack chain.
......@@ -115,8 +118,8 @@ contract OPStackManager is ISemver, Initializable {
// -------- Constants and Variables --------
/// @custom:semver 1.0.0-beta.3
string public constant version = "1.0.0-beta.3";
/// @custom:semver 1.0.0-beta.4
string public constant version = "1.0.0-beta.4";
/// @notice Address of the SuperchainConfig contract shared by all chains.
SuperchainConfig public immutable superchainConfig;
......@@ -206,8 +209,6 @@ contract OPStackManager is ISemver, Initializable {
// -------- TODO: Placeholders --------
// For contracts we don't yet deploy, we set the outputs to dummy proxies so they have code to pass assertions.
// We do these first, that way the disputeGameFactoryProxy is set when passed to the SystemConfig input.
output.anchorStateRegistryProxy = AnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, "3"));
output.anchorStateRegistryImpl = AnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, "4"));
output.faultDisputeGame = FaultDisputeGame(deployProxy(l2ChainId, output.opChainProxyAdmin, "5"));
output.permissionedDisputeGame = PermissionedDisputeGame(deployProxy(l2ChainId, output.opChainProxyAdmin, "6"));
output.delayedWETHPermissionedGameProxy =
......@@ -266,7 +267,7 @@ contract OPStackManager is ISemver, Initializable {
);
// -------- Set and Initialize Proxy Implementations --------
Implementation storage impl;
Implementation memory impl;
bytes memory data;
impl = getLatestImplementation("L1ERC721Bridge");
......@@ -293,10 +294,16 @@ contract OPStackManager is ISemver, Initializable {
data = encodeL1StandardBridgeInitializer(impl.initializer, output);
upgradeAndCall(output.opChainProxyAdmin, address(output.l1StandardBridgeProxy), impl.logic, data);
// TODO: also call setImplementation() once the dispute games are deployed.
impl = getLatestImplementation("DisputeGameFactory");
data = encodeDisputeGameFactoryInitializer(impl.initializer, _input);
upgradeAndCall(output.opChainProxyAdmin, address(output.disputeGameFactoryProxy), impl.logic, data);
impl.logic = address(output.anchorStateRegistryImpl);
impl.initializer = AnchorStateRegistry.initialize.selector;
data = encodeAnchorStateRegistryInitializer(impl.initializer, _input);
upgradeAndCall(output.opChainProxyAdmin, address(output.anchorStateRegistryProxy), impl.logic, data);
// -------- Finalize Deployment --------
// Transfer ownership of the ProxyAdmin from this contract to the specified owner.
output.opChainProxyAdmin.transferOwnership(_input.roles.opChainProxyAdminOwner);
......@@ -345,9 +352,11 @@ contract OPStackManager is ISemver, Initializable {
return Blueprint.deployFrom(blueprint.proxy, salt, abi.encode(_proxyAdmin));
}
/// @notice Returns the implementation data for a contract name.
function getLatestImplementation(string memory _name) internal view returns (Implementation storage) {
return implementations[latestRelease][_name];
/// @notice Returns the implementation data for a contract name. Makes a copy of the internal
// Implementation struct in storage to prevent accidental mutation of the internal data.
function getLatestImplementation(string memory _name) internal view returns (Implementation memory) {
Implementation storage impl = implementations[latestRelease][_name];
return Implementation({ logic: impl.logic, initializer: impl.initializer });
}
// -------- Initializer Encoding --------
......@@ -464,6 +473,21 @@ contract OPStackManager is ISemver, Initializable {
return abi.encodeWithSelector(_selector, _input.roles.opChainProxyAdminOwner);
}
function encodeAnchorStateRegistryInitializer(
bytes4 _selector,
DeployInput memory _input
)
internal
view
virtual
returns (bytes memory)
{
// this line fails in the op-deployer tests because it is not passing in any data
AnchorStateRegistry.StartingAnchorRoot[] memory startingAnchorRoots =
abi.decode(_input.startingAnchorRoots, (AnchorStateRegistry.StartingAnchorRoot[]));
return abi.encodeWithSelector(_selector, startingAnchorRoots, superchainConfig);
}
/// @notice Returns default, standard config arguments for the SystemConfig initializer.
/// This is used by subclasses to reduce code duplication.
function defaultSystemConfigParams(
......
......@@ -31,6 +31,8 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import { GameType, GameTypes, Hash, OutputRoot } from "src/dispute/lib/Types.sol";
contract DeployOPChainInput_Test is Test {
DeployOPChainInput doi;
......@@ -336,9 +338,29 @@ contract DeployOPChain_TestBase is Test {
uint32 basefeeScalar = 100;
uint32 blobBaseFeeScalar = 200;
uint256 l2ChainId = 300;
AnchorStateRegistry.StartingAnchorRoot[] startingAnchorRoots;
OPStackManager opsm = OPStackManager(address(0));
function setUp() public virtual {
// Set defaults for reference types
uint256 cannonBlock = 400;
uint256 permissionedBlock = 500;
startingAnchorRoots.push(
AnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.CANNON,
outputRoot: OutputRoot({ root: Hash.wrap(keccak256("defaultOutputRootCannon")), l2BlockNumber: cannonBlock })
})
);
startingAnchorRoots.push(
AnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: OutputRoot({
root: Hash.wrap(keccak256("defaultOutputRootPermissioned")),
l2BlockNumber: permissionedBlock
})
})
);
// Initialize deploy scripts.
DeploySuperchain deploySuperchain = new DeploySuperchain();
(DeploySuperchainInput dsi, DeploySuperchainOutput dso) = deploySuperchain.etchIOContracts();
......@@ -389,7 +411,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
return keccak256(abi.encode(_seed, _i));
}
function testFuzz_run_memory_succeeds(bytes32 _seed) public {
function testFuzz_run_memory_succeed(bytes32 _seed) public {
opChainProxyAdminOwner = address(uint160(uint256(hash(_seed, 0))));
systemConfigOwner = address(uint160(uint256(hash(_seed, 1))));
batcher = address(uint160(uint256(hash(_seed, 2))));
......@@ -398,7 +420,26 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
challenger = address(uint160(uint256(hash(_seed, 5))));
basefeeScalar = uint32(uint256(hash(_seed, 6)));
blobBaseFeeScalar = uint32(uint256(hash(_seed, 7)));
l2ChainId = uint256(uint256(hash(_seed, 8)));
l2ChainId = uint256(hash(_seed, 8));
// Set the initial anchor states. The typical usage we expect is to pass in one root per game type.
uint256 cannonBlock = uint256(hash(_seed, 9));
uint256 permissionedBlock = uint256(hash(_seed, 10));
startingAnchorRoots.push(
AnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.CANNON,
outputRoot: OutputRoot({ root: Hash.wrap(keccak256(abi.encode(_seed, 11))), l2BlockNumber: cannonBlock })
})
);
startingAnchorRoots.push(
AnchorStateRegistry.StartingAnchorRoot({
gameType: GameTypes.PERMISSIONED_CANNON,
outputRoot: OutputRoot({
root: Hash.wrap(keccak256(abi.encode(_seed, 12))),
l2BlockNumber: permissionedBlock
})
})
);
doi.set(doi.opChainProxyAdminOwner.selector, opChainProxyAdminOwner);
doi.set(doi.systemConfigOwner.selector, systemConfigOwner);
......
......@@ -62,7 +62,8 @@ contract OPStackManager_Deploy_Test is DeployOPChain_TestBase {
}),
basefeeScalar: _doi.basefeeScalar(),
blobBasefeeScalar: _doi.blobBaseFeeScalar(),
l2ChainId: _doi.l2ChainId()
l2ChainId: _doi.l2ChainId(),
startingAnchorRoots: _doi.startingAnchorRoots()
});
}
......
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