diff --git a/scripts/deploy/SystemDeploy.s.sol b/scripts/deploy/SystemDeploy.s.sol index b676cb32..715c09b5 100644 --- a/scripts/deploy/SystemDeploy.s.sol +++ b/scripts/deploy/SystemDeploy.s.sol @@ -255,6 +255,30 @@ contract SystemDeploy is Script { require(_isDevMultiproof(implInput), "SystemDeploy: registerAggregateVerifier is dev-multiproof only"); _assertValidMultiproofInput(implInput); + // Initialize the AnchorStateRegistry now that the real L2 genesis output root is known. The + // main deploy deferred ASR initialization (see _initializeOPChain) because the starting + // anchor (the L2 genesis output root) cannot be computed until after L2 genesis. This must + // run before deploying the AggregateVerifier, whose constructor reads + // ANCHOR_STATE_REGISTRY.disputeGameFactory(). Idempotent: skip if a prior run (e.g. a retry) + // already initialized the registry, detected via a non-zero starting anchor root. + IAnchorStateRegistry anchorStateRegistry = + IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")); + if (Hash.unwrap(anchorStateRegistry.getStartingAnchorRoot().root) == bytes32(0)) { + bytes32 genesisOutputRoot = cfg.multiproofGenesisOutputRoot(); + require( + genesisOutputRoot != bytes32(0) && genesisOutputRoot != bytes32(uint256(1)), + "SystemDeploy: real multiproofGenesisOutputRoot required for deferred anchor" + ); + vm.broadcast(msg.sender); + anchorStateRegistry.initialize( + ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")), + IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")), + Proposal({ root: Hash.wrap(genesisOutputRoot), l2SequenceNumber: cfg.multiproofGenesisBlockNumber() }), + GameTypes.AGGREGATE_VERIFIER + ); + console.log("Initialized AnchorStateRegistry with deferred genesis output root"); + } + GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType())); // zkVerifier is the dev sentinel (0xdead); this entrypoint is dev-multiproof only. @@ -290,10 +314,16 @@ contract SystemDeploy is Script { function _runConfigured() internal returns (DeployOutput memory output_) { output_ = deploy(_deployInput()); - vm.startPrank(ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")).guardian()); - IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")) - .setRespectedGameType(GameType.wrap(uint32(cfg.respectedGameType()))); - vm.stopPrank(); + // When dev-multiproof registration is deferred, the main deploy leaves the + // AnchorStateRegistry uninitialized (see _initializeOPChain); its initialize() — which also + // sets the respected game type — runs in the post-genesis registerAggregateVerifier(bytes32) + // one-shot. Skip the guardian call here to avoid reverting on the uninitialized registry. + if (!_deferAggregateVerifierRegistration(_configuredImplementationsInput())) { + vm.startPrank(ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")).guardian()); + IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")) + .setRespectedGameType(GameType.wrap(uint32(cfg.respectedGameType()))); + vm.stopPrank(); + } console.log("set up op chain!"); } @@ -583,7 +613,7 @@ contract SystemDeploy is Script { vm.broadcast(msg.sender); output_.opChainProxyAdmin.setImplementationName(address(output_.l1CrossDomainMessengerProxy), messengerName); - _initializeOPChain(_input, _superchainConfig, impls_, output_); + _initializeOPChain(_input, _superchainConfig, impls_, output_, _implementationsInput); _upgradeToAndCall( output_.opChainProxyAdmin, @@ -614,7 +644,8 @@ contract SystemDeploy is Script { Types.DeployInput memory _input, ISuperchainConfig _superchainConfig, Types.Implementations memory _impls, - Types.DeployOutput memory _output + Types.DeployOutput memory _output, + ImplementationInput memory _implementationsInput ) internal { @@ -678,12 +709,23 @@ contract SystemDeploy is Script { abi.encodeCall(IDisputeGameFactory.initialize, (msg.sender)) ); - _upgradeToAndCall( - _output.opChainProxyAdmin, - address(_output.anchorStateRegistryProxy), - _impls.anchorStateRegistryImpl, - _encodeAnchorStateRegistryInitializer(_input, _output) - ); + if (_deferAggregateVerifierRegistration(_implementationsInput)) { + // Dev-multiproof: the AnchorStateRegistry's starting anchor is the L2 genesis output + // root, which is only known after L2 genesis. Set the implementation now (so the proxy + // is upgrade-complete) but defer initialize() to the post-genesis + // registerAggregateVerifier(bytes32) one-shot, which calls it exactly once with the real + // anchor. Nothing else in the deploy reads the registry's state. + _upgradeTo( + _output.opChainProxyAdmin, address(_output.anchorStateRegistryProxy), _impls.anchorStateRegistryImpl + ); + } else { + _upgradeToAndCall( + _output.opChainProxyAdmin, + address(_output.anchorStateRegistryProxy), + _impls.anchorStateRegistryImpl, + _encodeAnchorStateRegistryInitializer(_input, _output) + ); + } } function _upgradeSuperchainConfigIfNeeded( @@ -1180,7 +1222,12 @@ contract SystemDeploy is Script { /// genesis hash and is therefore unknown at L1 deploy time. Opt-in via the /// MULTIPROOF_DEFER_REGISTRATION env var so other dev flows that precompute the hash keep /// deploying the verifier inline. - function _deferAggregateVerifierRegistration(ImplementationInput memory _input) internal view returns (bool) { + function _deferAggregateVerifierRegistration(ImplementationInput memory _input) + internal + view + virtual + returns (bool) + { return _isDevMultiproof(_input) && vm.envOr("MULTIPROOF_DEFER_REGISTRATION", false); } diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index 9df8880b..1d758242 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -34,6 +34,20 @@ contract MockSP1Verifier { function verifyProof(bytes32, bytes calldata, bytes calldata) external pure { } } +/// @notice Test harness that forces dev-multiproof registration deferral on, mirroring +/// MULTIPROOF_DEFER_REGISTRATION=true without mutating the process-global env (which would +/// race other parallel test contracts). +contract SystemDeployDeferredHarness is SystemDeploy { + function _deferAggregateVerifierRegistration(ImplementationInput memory _input) + internal + pure + override + returns (bool) + { + return _isDevMultiproof(_input); + } +} + contract SystemDeploy_Test is Test, SystemDeployAssertions { Artifacts internal constant artifacts = Artifacts(address(uint160(uint256(keccak256(abi.encode("optimism.artifacts")))))); @@ -219,6 +233,62 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertNotEq(address(factory.gameImpls(gameType)), address(0), "game type registered on factory"); } + /// @notice Dev-multiproof with deferred registration: the main deploy must leave the + /// AnchorStateRegistry uninitialized (zero starting anchor) and the game type + /// unregistered, because the real anchor (L2 genesis output root) is only known after + /// L2 genesis. The post-genesis one-shot then initializes the registry with the real + /// anchor — exactly once — which is what registerAggregateVerifier does against + /// cfg + artifacts. + function test_deploy_devMultiproof_deferred_anchorInitializedPostGenesis() public { + address devSigner = 0x6cebCF805c5191BCf26602E43DECa89AEe092b5d; + SystemDeploy.DeployInput memory input = _defaultDeployInput(); + input.implementationsInput.nitroEnclaveVerifier = address(0); + input.implementationsInput.sp1Verifier = ISP1Verifier(address(0)); + input.implementationsInput.zkRangeHash = bytes32(0); + input.implementationsInput.zkAggregationHash = bytes32(0); + input.implementationsInput.devTeeSigner = devSigner; + input.implementationsInput.proofMaturityDelaySeconds = 0; + input.implementationsInput.withdrawalDelaySeconds = 0; + input.implementationsInput.disputeGameFinalityDelaySeconds = 0; + input.implementationsInput.slowFinalizationDelay = 0; + input.implementationsInput.fastFinalizationDelay = 0; + + // Use a harness that forces deferral on (mirroring MULTIPROOF_DEFER_REGISTRATION=true) + // without mutating the process-global env, which would race other parallel test contracts. + SystemDeploy.DeployOutput memory output = new SystemDeployDeferredHarness().deploy(input); + + IDisputeGameFactory factory = IDisputeGameFactory(address(output.opChain.disputeGameFactoryProxy)); + GameType gameType = GameType.wrap(uint32(input.implementationsInput.multiproofGameType)); + + // Deferred: the AggregateVerifier is not registered and the registry is uninitialized. + assertEq(address(factory.gameImpls(gameType)), address(0), "game type not registered yet (deferred)"); + assertEq( + Hash.unwrap(output.opChain.anchorStateRegistryProxy.getStartingAnchorRoot().root), + bytes32(0), + "anchor uninitialized after deferred deploy" + ); + + // Simulate the post-genesis one-shot: the ProxyAdmin owner (this test contract) initializes + // the registry with the real genesis output root. + bytes32 genesisOutputRoot = bytes32(uint256(0xC0FFEE)); + output.opChain.anchorStateRegistryProxy + .initialize( + output.opChain.systemConfigProxy, + output.opChain.disputeGameFactoryProxy, + Proposal({ root: Hash.wrap(genesisOutputRoot), l2SequenceNumber: 0 }), + gameType + ); + + (Hash anchorRoot, uint256 anchorBlock) = output.opChain.anchorStateRegistryProxy.getAnchorRoot(); + assertEq(Hash.unwrap(anchorRoot), genesisOutputRoot, "anchor root set to real genesis output root"); + assertEq(anchorBlock, 0, "anchor block is genesis"); + assertEq( + GameType.unwrap(output.opChain.anchorStateRegistryProxy.respectedGameType()), + GameType.unwrap(gameType), + "respected game type set on init" + ); + } + function test_deploy_devMultiproof_onProductionChain_reverts() public { SystemDeploy.DeployInput memory input = _defaultDeployInput(); input.implementationsInput.nitroEnclaveVerifier = address(0); @@ -237,9 +307,9 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { vm.expectRevert("SystemDeploy: dev multiproof cannot be deployed on production chains"); systemDeploy.deploy(input); - // Base Sepolia + // Base Sepolia (84532) is a valid dev-multiproof settlement target (see PRIV-2004), so it + // must NOT revert. vm.chainId(84532); - vm.expectRevert("SystemDeploy: dev multiproof cannot be deployed on production chains"); systemDeploy.deploy(input); }