Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions scripts/Artifacts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ contract Artifacts {
_predeploys[keccak256("EAS")] = payable(Predeploys.EAS);
}

/// @notice Loads previously-saved deployments from the outfile back into memory.
/// @dev `setUp()` only seeds predeploys; named deployments are otherwise only populated by
/// `save()` within the same process. A re-entrant script run (e.g.
/// `registerAggregateVerifier` after L2 genesis) is a fresh process, so it must call
/// this to read addresses from an earlier deploy. Re-seeding forge's stateful JSON
/// object also ensures a later `save()` appends to — rather than clobbers — the file.
function load() public {
if (!vm.exists(deploymentOutfile)) return;
string memory json = vm.readFile(deploymentOutfile);
if (bytes(json).length == 0) return;
string[] memory keys = vm.parseJsonKeys(json, "$");
for (uint256 i = 0; i < keys.length; i++) {
address payable addr = payable(vm.parseJsonAddress(json, string.concat(".", keys[i])));
_namedDeployments[keys[i]] = addr;
stdJson.serialize("", keys[i], addr);
}
}

/// @notice Returns the address of a deployment. Also handles the predeploys.
/// @param _name The name of the deployment.
/// @return The address of the deployment. May be `address(0)` if the deployment does not
Expand Down
117 changes: 96 additions & 21 deletions scripts/deploy/SystemDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,62 @@ contract SystemDeploy is Script {
});
}

/// @notice Deploys and registers the AggregateVerifier for a dev multiproof devnet after L2
/// genesis, once the real config_hash is finally known.
/// @dev Pairs with MULTIPROOF_DEFER_REGISTRATION: the main deploy skips the AggregateVerifier,
/// then this entrypoint deploys it with `_multiproofConfigHash` and points the
/// DisputeGameFactory's game-type implementation at it. All other inputs are reloaded from
/// the deploy config (`cfg`) and the saved deployment artifacts, so the verifier is built
/// identically to the inline path. Must be broadcast by the DisputeGameFactory owner
/// (finalSystemOwner). Run with:
/// forge script ... --sig "registerAggregateVerifier(bytes32)" <hash> --broadcast
function registerAggregateVerifier(bytes32 _multiproofConfigHash) public {
require(_multiproofConfigHash != bytes32(0), "SystemDeploy: multiproofConfigHash not set");

// Re-entrant run in a fresh process: reload addresses from the prior deploy's outfile so
// mustGetAddress resolves them and the subsequent save() appends instead of clobbering.
artifacts.load();

// This entrypoint hardcodes the dev ZK sentinel (0xdead) and exists only for dev multiproof
// (devnet) deployments, where config_hash is unknown until after genesis. Reject any config
// that wires a real nitroEnclaveVerifier (production). _assertValidMultiproofInput also
// blocks production chain IDs and validates the multiproof parameters.
ImplementationInput memory implInput = _configuredImplementationsInput();
require(_isDevMultiproof(implInput), "SystemDeploy: registerAggregateVerifier is dev-multiproof only");
_assertValidMultiproofInput(implInput);

GameType gameType = GameType.wrap(uint32(cfg.multiproofGameType()));

// zkVerifier is the dev sentinel (0xdead); this entrypoint is dev-multiproof only.
IVerifier aggregateVerifier = _newAggregateVerifier(
AggregateVerifierInput({
multiproofGameType: gameType,
anchorStateRegistry: IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")),
delayedWETH: IDelayedWETH(artifacts.mustGetAddress("DelayedWETHProxy")),
teeVerifier: IVerifier(artifacts.mustGetAddress("TEEVerifier")),
zkVerifier: IVerifier(address(0xdead)),
teeImageHash: cfg.teeImageHash(),
zkRangeHash: cfg.zkRangeHash(),
zkAggregationHash: cfg.zkAggregationHash(),
multiproofConfigHash: _multiproofConfigHash,
l2ChainId: cfg.l2ChainId(),
multiproofBlockInterval: cfg.multiproofBlockInterval(),
multiproofIntermediateBlockInterval: cfg.multiproofIntermediateBlockInterval(),
slowFinalizationDelay: cfg.slowFinalizationDelay(),
fastFinalizationDelay: cfg.fastFinalizationDelay()
})
);

IDisputeGameFactory disputeGameFactory =
IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy"));
vm.broadcast(msg.sender);
disputeGameFactory.setImplementation(gameType, IDisputeGame(address(aggregateVerifier)));

artifacts.save("AggregateVerifier", address(aggregateVerifier));
vm.label(address(aggregateVerifier), "AggregateVerifier");
console.log("Registered AggregateVerifier at:", address(aggregateVerifier));
}

function _runConfigured() internal returns (DeployOutput memory output_) {
output_ = deploy(_deployInput());

Expand Down Expand Up @@ -1023,33 +1079,42 @@ contract SystemDeploy is Script {
IVerifier(address(new ZKVerifier(_input.sp1Verifier, _output.anchorStateRegistryProxy)));
}

output_.aggregateVerifier = _newAggregateVerifier(
AggregateVerifierInput({
multiproofGameType: gameType,
anchorStateRegistry: _output.anchorStateRegistryProxy,
delayedWETH: _output.delayedWETHProxy,
teeVerifier: output_.teeVerifier,
zkVerifier: output_.zkVerifier,
teeImageHash: _input.teeImageHash,
zkRangeHash: _input.zkRangeHash,
zkAggregationHash: _input.zkAggregationHash,
multiproofConfigHash: _input.multiproofConfigHash,
l2ChainId: _opChainInput.l2ChainId,
multiproofBlockInterval: _input.multiproofBlockInterval,
multiproofIntermediateBlockInterval: _input.multiproofIntermediateBlockInterval,
slowFinalizationDelay: _input.slowFinalizationDelay,
fastFinalizationDelay: _input.fastFinalizationDelay
})
);
if (_deferAggregateVerifierRegistration(_input)) {
// The multiproof config_hash commits to the L2 genesis block hash, which is only known
// after the L2 execution client initializes from the generated genesis. Defer
// AggregateVerifier deployment + registration to `registerAggregateVerifier(bytes32)`,
// invoked post-genesis with the real hash. output_.aggregateVerifier stays unset here.
console.log("Deferring AggregateVerifier: run registerAggregateVerifier(bytes32) after L2 genesis");
} else {
output_.aggregateVerifier = _newAggregateVerifier(
AggregateVerifierInput({
multiproofGameType: gameType,
anchorStateRegistry: _output.anchorStateRegistryProxy,
delayedWETH: _output.delayedWETHProxy,
teeVerifier: output_.teeVerifier,
zkVerifier: output_.zkVerifier,
teeImageHash: _input.teeImageHash,
zkRangeHash: _input.zkRangeHash,
zkAggregationHash: _input.zkAggregationHash,
multiproofConfigHash: _input.multiproofConfigHash,
l2ChainId: _opChainInput.l2ChainId,
multiproofBlockInterval: _input.multiproofBlockInterval,
multiproofIntermediateBlockInterval: _input.multiproofIntermediateBlockInterval,
slowFinalizationDelay: _input.slowFinalizationDelay,
fastFinalizationDelay: _input.fastFinalizationDelay
})
);

vm.broadcast(msg.sender);
_output.disputeGameFactoryProxy.setImplementation(gameType, IDisputeGame(address(output_.aggregateVerifier)));
vm.broadcast(msg.sender);
_output.disputeGameFactoryProxy
.setImplementation(gameType, IDisputeGame(address(output_.aggregateVerifier)));
vm.label(address(output_.aggregateVerifier), "AggregateVerifier");
}

vm.label(address(output_.teeProverRegistryImpl), "TEEProverRegistryImpl");
vm.label(address(output_.teeProverRegistryProxy), "TEEProverRegistryProxy");
vm.label(address(output_.teeVerifier), "TEEVerifier");
vm.label(address(output_.zkVerifier), "ZKVerifier");
vm.label(address(output_.aggregateVerifier), "AggregateVerifier");
}

function _newAggregateVerifier(AggregateVerifierInput memory _input) internal returns (IVerifier) {
Expand Down Expand Up @@ -1109,6 +1174,16 @@ contract SystemDeploy is Script {
return _multiproofEnabled(_input) && _input.nitroEnclaveVerifier == address(0);
}

/// @notice Whether AggregateVerifier deployment + registration should be deferred to a
/// post-genesis step instead of happening inline during the main deploy.
/// @dev Only meaningful for dev multiproof (devnet), where the config_hash commits to the L2
/// 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) {
return _isDevMultiproof(_input) && vm.envOr("MULTIPROOF_DEFER_REGISTRATION", false);
}

function _assertValidMultiproofInput(ImplementationInput memory _input) internal view {
require(_input.multiproofConfigHash != bytes32(0), "SystemDeploy: multiproofConfigHash not set");
require(_input.multiproofGameType != 0, "SystemDeploy: multiproofGameType not set");
Expand Down
48 changes: 48 additions & 0 deletions test/scripts/Artifacts.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Test } from "lib/forge-std/src/Test.sol";
import { Artifacts } from "scripts/Artifacts.s.sol";

/// @title Artifacts_load_Test
/// @notice Covers the re-entrant (fresh-process) path: a script run such as
/// `registerAggregateVerifier` after L2 genesis must reload addresses written by an
/// earlier deploy, and a subsequent `save()` must append to rather than clobber the file.
contract Artifacts_load_Test is Test {
Artifacts internal artifacts;
string internal outfile;

address internal constant FOO = address(0x1111111111111111111111111111111111111111);
address internal constant BAR = address(0x2222222222222222222222222222222222222222);
address internal constant BAZ = address(0x3333333333333333333333333333333333333333);

function setUp() public {
outfile = string.concat(vm.projectRoot(), "/deployments/artifacts-load-test.json");
try vm.removeFile(outfile) { } catch { }
vm.setEnv("DEPLOYMENT_OUTFILE", outfile);
artifacts = new Artifacts();
artifacts.setUp();
}

/// @dev Without load(), getAddress only knows predeploys, so reading a prior deployment would
/// revert; and save() does a whole-file writeJson, so it must not clobber existing keys.
function test_load_reloadsThenAppends_succeeds() public {
// Simulate an earlier deploy process having written the outfile.
vm.writeFile(outfile, string.concat('{"Foo":"', vm.toString(FOO), '","Bar":"', vm.toString(BAR), '"}'));

artifacts.load();

assertEq(artifacts.mustGetAddress("Foo"), payable(FOO));
assertEq(artifacts.mustGetAddress("Bar"), payable(BAR));

artifacts.save("Baz", BAZ);

string memory json = vm.readFile(outfile);
assertEq(vm.parseJsonAddress(json, ".Foo"), FOO);
assertEq(vm.parseJsonAddress(json, ".Bar"), BAR);
assertEq(vm.parseJsonAddress(json, ".Baz"), BAZ);
assertEq(vm.parseJsonKeys(json, "$").length, 3);

vm.removeFile(outfile);
}
}
Loading