From 208c5767e1bf8fa72a762d872886cea022e9cacb Mon Sep 17 00:00:00 2001 From: AztecBot Date: Mon, 21 Sep 2026 22:08:34 +0000 Subject: [PATCH 1/4] fix(l1): make the generic deployer refuse unusable rollup configuration --- .../deploy/DeployRollupForUpgrade.s.sol | 5 + .../script/deploy/DeployRollupLib.sol | 38 +++ .../script/deploy/RollupConfiguration.sol | 25 +- l1-contracts/scripts/run_rollup_upgrade.sh | 1 - l1-contracts/scripts/stress_test_deploy.sh | 6 + l1-contracts/scripts/test_rollup_upgrade.sh | 9 +- .../test/script/DeployAztecL1Contracts.t.sol | 5 + .../test/script/DeployConfigValidation.t.sol | 245 ++++++++++++++++++ .../test/script/DeployRollupForUpgrade.t.sol | 5 + 9 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 l1-contracts/test/script/DeployConfigValidation.t.sol diff --git a/l1-contracts/script/deploy/DeployRollupForUpgrade.s.sol b/l1-contracts/script/deploy/DeployRollupForUpgrade.s.sol index 97f1ea3ac2c..e09e8bf3ece 100644 --- a/l1-contracts/script/deploy/DeployRollupForUpgrade.s.sol +++ b/l1-contracts/script/deploy/DeployRollupForUpgrade.s.sol @@ -73,9 +73,14 @@ contract DeployRollupForUpgrade is Script { console.log("JSON DEPLOY RESULT:", finalJson); } + function _requireRegistryHasCode(Registry _registry) internal view { + require(address(_registry).code.length > 0, "DeployRollupForUpgrade: REGISTRY_ADDRESS has no code on this chain"); + } + /// @notice Parse existing L1 infrastructure from environment variables function _getRollupAddressInput() internal returns (RollupAddressInput memory) { Registry registry = Registry(vm.envAddress("REGISTRY_ADDRESS")); + _requireRegistryHasCode(registry); // Load existing addresses from the registry and canonical rollup. Governance governance = Governance(registry.getGovernance()); diff --git a/l1-contracts/script/deploy/DeployRollupLib.sol b/l1-contracts/script/deploy/DeployRollupLib.sol index d6347408a21..c10c36055e1 100644 --- a/l1-contracts/script/deploy/DeployRollupLib.sol +++ b/l1-contracts/script/deploy/DeployRollupLib.sol @@ -22,6 +22,7 @@ import {MultiAdder, CheatDepositArgs} from "@aztec/mock/MultiAdder.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {HonkVerifier} from "@generated/HonkVerifier.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {IRollupConfiguration} from "./RollupConfiguration.sol"; @@ -85,12 +86,49 @@ library DeployRollupLib { GenesisState memory genesisState = config.getGenesisState(); RollupConfigInput memory rollupConfigInput = config.getRollupConfiguration(IRewardDistributor(address(input.rewardDistributor))); + validateRollupConfig(input, rollupConfigInput); return new Rollup( input.feeAsset, input.stakingAsset, input.gse, verifier, input.deployer, genesisState, rollupConfigInput ); } + /// @notice Reject configuration the Rollup constructor accepts but that leaves the deployed instance unusable. + /// @dev Every value here is fixed at construction, so a bad one can only be corrected by redeploying. + function validateRollupConfig(RollupAddressInput memory input, RollupConfigInput memory config) internal view { + require(config.ethereumSlotDuration > 0, "DeployRollupLib: ethereumSlotDuration is zero"); + require(config.aztecSlotDuration > 0, "DeployRollupLib: aztecSlotDuration is zero"); + require(config.aztecEpochDuration > 0, "DeployRollupLib: aztecEpochDuration is zero"); + require( + config.aztecEpochDuration <= Constants.MAX_CHECKPOINTS_PER_EPOCH, + "DeployRollupLib: aztecEpochDuration exceeds MAX_CHECKPOINTS_PER_EPOCH" + ); + // TimeLib multiplies the two uint32 durations without widening. + uint256 epochDurationSeconds = config.aztecSlotDuration * config.aztecEpochDuration; + require(epochDurationSeconds <= type(uint32).max, "DeployRollupLib: epoch duration in seconds overflows uint32"); + require(config.targetCommitteeSize > 0, "DeployRollupLib: targetCommitteeSize is zero"); + require(config.exitDelaySeconds > 0, "DeployRollupLib: exitDelaySeconds is zero"); + // A zero RANDAO lag samples the same epoch it seeds, and the sample-time subtraction is done in uint32. + require(config.lagInEpochsForRandao >= 1, "DeployRollupLib: lagInEpochsForRandao is zero"); + require( + config.lagInEpochsForValidatorSet >= config.lagInEpochsForRandao, + "DeployRollupLib: lagInEpochsForValidatorSet below lagInEpochsForRandao" + ); + require( + config.lagInEpochsForValidatorSet * epochDurationSeconds <= type(uint32).max, + "DeployRollupLib: lag in seconds overflows uint32" + ); + // SlashingProposer encodes each amount as uint96 when it builds a payload. + for (uint256 i = 0; i < config.slashAmounts.length; i++) { + require(config.slashAmounts[i] <= type(uint96).max, "DeployRollupLib: slash amount exceeds uint96"); + } + require(address(config.rewardConfig.rewardDistributor) != address(0), "DeployRollupLib: rewardDistributor is zero"); + // The GSE only accounts stake in its own asset. + require( + address(input.stakingAsset) == address(input.gse.ASSET()), "DeployRollupLib: stakingAsset is not GSE.ASSET()" + ); + } + function _maybeMintInitialFeeAsset(RollupAddressInput memory input, Rollup rollup, IRollupConfiguration config) private { diff --git a/l1-contracts/script/deploy/RollupConfiguration.sol b/l1-contracts/script/deploy/RollupConfiguration.sol index f6730887098..7dc3753cfca 100644 --- a/l1-contracts/script/deploy/RollupConfiguration.sol +++ b/l1-contracts/script/deploy/RollupConfiguration.sol @@ -38,22 +38,43 @@ contract RollupConfiguration is IRollupConfiguration, Test { function loadConfig() external { networkName = vm.envOr("NETWORK", string("local")); validatorsJson = vm.envOr("INITIAL_VALIDATORS", string("[]")); + + _requireNetworkMatchesChain(networkName, block.chainid); + } + + /// @dev NETWORK selects the contract defaults and the chain id selects RPC and keys; a mismatch means one of + /// them points at the wrong network. + function _requireNetworkMatchesChain(string memory _networkName, uint256 _chainId) internal pure { + bool isMainnetName = keccak256(bytes(_networkName)) == keccak256("mainnet"); + require(!isMainnetName || _chainId == 1, "RollupConfiguration: NETWORK=mainnet requires chain id 1"); + require(_chainId != 1 || isMainnetName, "RollupConfiguration: chain id 1 requires NETWORK=mainnet"); } + /// @dev A stub verifier accepts any proof, so it must be requested explicitly with REAL_VERIFIER=false. function useRealVerifier() external view returns (bool) { - return vm.envOr("REAL_VERIFIER", false); + return vm.envOr("REAL_VERIFIER", true); } function getFeeJuicePortalInitialBalance() external view returns (uint256) { return vm.envOr("FEE_JUICE_PORTAL_INITIAL_BALANCE", uint256(0)); } + /// @dev The genesis roots are immutable once the rollup is constructed and a zero root cannot match any node's + /// view of the chain, so each one must be supplied explicitly. function getGenesisState() external view returns (GenesisState memory) { - return GenesisState({ + GenesisState memory genesisState = GenesisState({ vkTreeRoot: bytes32(vm.envOr("VK_TREE_ROOT", uint256(0))), protocolContractsHash: bytes32(vm.envOr("PROTOCOL_CONTRACTS_HASH", uint256(0))), genesisArchiveRoot: bytes32(vm.envOr("GENESIS_ARCHIVE_ROOT", uint256(0))) }); + _requireGenesisState(genesisState); + return genesisState; + } + + function _requireGenesisState(GenesisState memory _genesisState) internal pure { + require(_genesisState.vkTreeRoot != bytes32(0), "RollupConfiguration: VK_TREE_ROOT is unset"); + require(_genesisState.protocolContractsHash != bytes32(0), "RollupConfiguration: PROTOCOL_CONTRACTS_HASH is unset"); + require(_genesisState.genesisArchiveRoot != bytes32(0), "RollupConfiguration: GENESIS_ARCHIVE_ROOT is unset"); } function getRewardConfiguration(IRewardDistributor _rewardDistributor) external pure returns (RewardConfig memory) { diff --git a/l1-contracts/scripts/run_rollup_upgrade.sh b/l1-contracts/scripts/run_rollup_upgrade.sh index 3cabda3ecf0..1d3e09fd17c 100755 --- a/l1-contracts/scripts/run_rollup_upgrade.sh +++ b/l1-contracts/scripts/run_rollup_upgrade.sh @@ -20,7 +20,6 @@ echo "=== Deploying rollup upgrade ===" echo "Registry: $registry_address" REGISTRY_ADDRESS="$registry_address" \ -REAL_VERIFIER="${REAL_VERIFIER:-true}" \ ./scripts/forge_broadcast.js \ script/deploy/DeployRollupForUpgrade.s.sol:DeployRollupForUpgrade \ --rpc-url "$L1_RPC_URL" \ diff --git a/l1-contracts/scripts/stress_test_deploy.sh b/l1-contracts/scripts/stress_test_deploy.sh index 7207ba420b7..5ce1f5b94ff 100755 --- a/l1-contracts/scripts/stress_test_deploy.sh +++ b/l1-contracts/scripts/stress_test_deploy.sh @@ -21,6 +21,12 @@ echo "=== Results dir: $RESULTS_DIR ===" source ./scripts/load_network_defaults.sh +# The deployer requires explicit genesis roots. Only 31 random bytes (top byte zero) so each value is always +# below the BN254 scalar field modulus. +export VK_TREE_ROOT="0x00$(openssl rand -hex 31)" +export PROTOCOL_CONTRACTS_HASH="0x00$(openssl rand -hex 31)" +export GENESIS_ARCHIVE_ROOT="0x00$(openssl rand -hex 31)" + PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Pre-compile so workers skip compilation. diff --git a/l1-contracts/scripts/test_rollup_upgrade.sh b/l1-contracts/scripts/test_rollup_upgrade.sh index 209ae0bb0e2..775cc04fb3e 100755 --- a/l1-contracts/scripts/test_rollup_upgrade.sh +++ b/l1-contracts/scripts/test_rollup_upgrade.sh @@ -20,6 +20,12 @@ trap cleanup EXIT # Clean stale broadcast artifacts from previous runs to avoid nonce conflicts. rm -rf broadcast/ +# The deployer requires explicit genesis roots. Only 31 random bytes (top byte zero) so each value is always +# below the BN254 scalar field modulus. +export VK_TREE_ROOT="0x00$(openssl rand -hex 31)" +export PROTOCOL_CONTRACTS_HASH="0x00$(openssl rand -hex 31)" +export GENESIS_ARCHIVE_ROOT="0x00$(openssl rand -hex 31)" + # Fixed port — this test runs with ISOLATE=1 so no conflicts. ANVIL_PORT="${ANVIL_PORT:-8545}" @@ -51,8 +57,7 @@ if [[ -z "$registry_address" || "$registry_address" == "null" ]]; then fi echo "=== Testing run_rollup_upgrade.sh ===" -# Use a different genesis to get a different rollup version. Only 31 random bytes (top byte zero) so the value is -# always below the BN254 scalar field modulus; the rollup rejects a genesis archive root >= the field modulus. +# Use a different genesis to get a different rollup version. export GENESIS_ARCHIVE_ROOT="0x00$(openssl rand -hex 31)" ./scripts/run_rollup_upgrade.sh "$registry_address" diff --git a/l1-contracts/test/script/DeployAztecL1Contracts.t.sol b/l1-contracts/test/script/DeployAztecL1Contracts.t.sol index 599b0178524..bbf10a3c154 100644 --- a/l1-contracts/test/script/DeployAztecL1Contracts.t.sol +++ b/l1-contracts/test/script/DeployAztecL1Contracts.t.sol @@ -36,6 +36,11 @@ contract DeployAztecL1ContractsTest is Test { string memory path = string.concat(root, "/scripts/network-defaults.json"); string memory json = vm.readFile(path); + // Genesis roots are required by the deployer; any non-zero field element will do here. + vm.setEnv("VK_TREE_ROOT", vm.toString(uint256(keccak256("vk_tree_root")) >> 8)); + vm.setEnv("PROTOCOL_CONTRACTS_HASH", vm.toString(uint256(keccak256("protocol_contracts_hash")) >> 8)); + vm.setEnv("GENESIS_ARCHIVE_ROOT", vm.toString(uint256(keccak256("genesis_archive_root")) >> 8)); + // Timing config vm.setEnv("ETHEREUM_SLOT_DURATION", vm.toString(json.readUint(".ETHEREUM_SLOT_DURATION"))); vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION"))); diff --git a/l1-contracts/test/script/DeployConfigValidation.t.sol b/l1-contracts/test/script/DeployConfigValidation.t.sol new file mode 100644 index 00000000000..4c7d29a5e0d --- /dev/null +++ b/l1-contracts/test/script/DeployConfigValidation.t.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {stdJson} from "forge-std/StdJson.sol"; + +import {DeployRollupForUpgrade} from "../../script/deploy/DeployRollupForUpgrade.s.sol"; +import {DeployRollupLib, RollupAddressInput} from "../../script/deploy/DeployRollupLib.sol"; +import {RollupConfiguration} from "../../script/deploy/RollupConfiguration.sol"; +import {GenesisState, RollupConfigInput} from "@aztec/core/interfaces/IRollup.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {Registry} from "@aztec/governance/Registry.sol"; +import {GSE} from "@aztec/governance/GSE.sol"; +import {Governance} from "@aztec/governance/Governance.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +contract RollupConfigurationHarness is RollupConfiguration { + function requireGenesisState(GenesisState memory _genesisState) external pure { + _requireGenesisState(_genesisState); + } + + function requireNetworkMatchesChain(string memory _networkName, uint256 _chainId) external pure { + _requireNetworkMatchesChain(_networkName, _chainId); + } +} + +contract DeployRollupForUpgradeHarness is DeployRollupForUpgrade { + function requireRegistryHasCode(Registry _registry) external view { + _requireRegistryHasCode(_registry); + } +} + +contract DeployRollupLibHarness { + function validateRollupConfig(RollupAddressInput memory _input, RollupConfigInput memory _config) external view { + DeployRollupLib.validateRollupConfig(_input, _config); + } +} + +/// @dev Only the asset binding is consulted by the deployer's checks. +contract GseAssetStub { + IERC20 public immutable ASSET; + + constructor(IERC20 _asset) { + ASSET = _asset; + } +} + +/** + * @title DeployConfigValidationTest + * @notice The env-driven deployer refuses configuration that the Rollup constructor would accept but that leaves + * the deployed instance unusable or mis-bound. The checks are exercised on values rather than through + * `vm.setEnv`, because the process environment is shared by tests running in parallel. + */ +contract DeployConfigValidationTest is Test { + using stdJson for string; + + RollupConfigurationHarness internal configuration; + DeployRollupLibHarness internal lib; + RollupAddressInput internal input; + IRewardDistributor internal rewardDistributor = IRewardDistributor(makeAddr("rewardDistributor")); + IERC20 internal stakingAsset = IERC20(makeAddr("stakingAsset")); + + modifier skipWhenCoverage() { + if (vm.envOr("FORGE_COVERAGE", false)) { + vm.skip(true); + } + _; + } + + // Load the canonical L1 config defaults so `getRollupConfiguration` yields a valid baseline. + function setUp() public skipWhenCoverage { + string memory root = vm.projectRoot(); + string memory json = vm.readFile(string.concat(root, "/scripts/network-defaults.json")); + string[] memory keys = vm.parseJsonKeys(json, "$"); + for (uint256 i = 0; i < keys.length; i++) { + vm.setEnv(keys[i], _readAsString(json, keys[i])); + } + vm.setEnv("VK_TREE_ROOT", vm.toString(uint256(keccak256("vk_tree_root")) >> 8)); + vm.setEnv("PROTOCOL_CONTRACTS_HASH", vm.toString(uint256(keccak256("protocol_contracts_hash")) >> 8)); + vm.setEnv("GENESIS_ARCHIVE_ROOT", vm.toString(uint256(keccak256("genesis_archive_root")) >> 8)); + + configuration = new RollupConfigurationHarness(); + configuration.loadConfig(); + lib = new DeployRollupLibHarness(); + + input = RollupAddressInput({ + deployer: address(this), + registry: Registry(makeAddr("registry")), + gse: GSE(address(new GseAssetStub(stakingAsset))), + governance: Governance(makeAddr("governance")), + feeAsset: IERC20(makeAddr("feeAsset")), + stakingAsset: stakingAsset, + rewardDistributor: rewardDistributor + }); + } + + function _readAsString(string memory _json, string memory _key) internal pure returns (string memory) { + bytes memory raw = vm.parseJson(_json, string.concat(".", _key)); + // Numbers decode to a single word; strings, addresses and booleans are stored as strings in the defaults file. + if (raw.length == 32) { + return vm.toString(abi.decode(raw, (uint256))); + } + return abi.decode(raw, (string)); + } + + function _baseConfig() internal view returns (RollupConfigInput memory) { + return configuration.getRollupConfiguration(rewardDistributor); + } + + function _expectInvalid(RollupConfigInput memory _config, string memory _reason) internal { + vm.expectRevert(bytes(_reason)); + lib.validateRollupConfig(input, _config); + } + + function test_DefaultsAreValid() public view { + lib.validateRollupConfig(input, _baseConfig()); + } + + function test_RealVerifierUnlessDisabled() public view { + assertTrue(configuration.useRealVerifier()); + } + + function test_RevertWhenGenesisRootUnset() public { + GenesisState memory genesisState = configuration.getGenesisState(); + genesisState.vkTreeRoot = bytes32(0); + vm.expectRevert(bytes("RollupConfiguration: VK_TREE_ROOT is unset")); + configuration.requireGenesisState(genesisState); + } + + function test_RevertWhenProtocolContractsHashUnset() public { + GenesisState memory genesisState = configuration.getGenesisState(); + genesisState.protocolContractsHash = bytes32(0); + vm.expectRevert(bytes("RollupConfiguration: PROTOCOL_CONTRACTS_HASH is unset")); + configuration.requireGenesisState(genesisState); + } + + function test_RevertWhenGenesisArchiveRootUnset() public { + GenesisState memory genesisState = configuration.getGenesisState(); + genesisState.genesisArchiveRoot = bytes32(0); + vm.expectRevert(bytes("RollupConfiguration: GENESIS_ARCHIVE_ROOT is unset")); + configuration.requireGenesisState(genesisState); + } + + function test_NetworkAndChainIdMustAgree() public { + configuration.requireNetworkMatchesChain("mainnet", 1); + configuration.requireNetworkMatchesChain("testnet", 11_155_111); + configuration.requireNetworkMatchesChain("local", 31_337); + + vm.expectRevert(bytes("RollupConfiguration: chain id 1 requires NETWORK=mainnet")); + configuration.requireNetworkMatchesChain("testnet", 1); + + vm.expectRevert(bytes("RollupConfiguration: NETWORK=mainnet requires chain id 1")); + configuration.requireNetworkMatchesChain("mainnet", 11_155_111); + } + + function test_RevertWhenRegistryHasNoCode() public { + DeployRollupForUpgradeHarness upgrade = new DeployRollupForUpgradeHarness(); + vm.expectRevert(bytes("DeployRollupForUpgrade: REGISTRY_ADDRESS has no code on this chain")); + upgrade.requireRegistryHasCode(Registry(makeAddr("not-a-registry"))); + } + + function test_RevertWhenCommitteeSizeZero() public { + RollupConfigInput memory config = _baseConfig(); + config.targetCommitteeSize = 0; + _expectInvalid(config, "DeployRollupLib: targetCommitteeSize is zero"); + } + + function test_RevertWhenExitDelayZero() public { + RollupConfigInput memory config = _baseConfig(); + config.exitDelaySeconds = 0; + _expectInvalid(config, "DeployRollupLib: exitDelaySeconds is zero"); + } + + function test_RevertWhenDurationsZero() public { + RollupConfigInput memory config = _baseConfig(); + config.ethereumSlotDuration = 0; + _expectInvalid(config, "DeployRollupLib: ethereumSlotDuration is zero"); + + config = _baseConfig(); + config.aztecSlotDuration = 0; + _expectInvalid(config, "DeployRollupLib: aztecSlotDuration is zero"); + + config = _baseConfig(); + config.aztecEpochDuration = 0; + _expectInvalid(config, "DeployRollupLib: aztecEpochDuration is zero"); + } + + function test_RevertWhenEpochDurationAboveProofCap() public { + RollupConfigInput memory config = _baseConfig(); + config.aztecEpochDuration = 33; + _expectInvalid(config, "DeployRollupLib: aztecEpochDuration exceeds MAX_CHECKPOINTS_PER_EPOCH"); + } + + function test_RevertWhenEpochSecondsOverflowUint32() public { + RollupConfigInput memory config = _baseConfig(); + // 32 checkpoints of 2^27 seconds is exactly 2^32 seconds. + config.aztecEpochDuration = 32; + config.aztecSlotDuration = uint256(1) << 27; + _expectInvalid(config, "DeployRollupLib: epoch duration in seconds overflows uint32"); + } + + function test_RevertWhenRandaoLagZero() public { + RollupConfigInput memory config = _baseConfig(); + config.lagInEpochsForRandao = 0; + _expectInvalid(config, "DeployRollupLib: lagInEpochsForRandao is zero"); + } + + function test_RevertWhenValidatorSetLagBelowRandaoLag() public { + RollupConfigInput memory config = _baseConfig(); + config.lagInEpochsForRandao = 2; + config.lagInEpochsForValidatorSet = 1; + _expectInvalid(config, "DeployRollupLib: lagInEpochsForValidatorSet below lagInEpochsForRandao"); + } + + function test_RevertWhenLagSecondsOverflowUint32() public { + RollupConfigInput memory config = _baseConfig(); + // 2304 seconds per epoch (72 x 32); 1_864_136 epochs of lag is just over 2^32 seconds. + config.aztecSlotDuration = 72; + config.aztecEpochDuration = 32; + config.lagInEpochsForRandao = 1; + config.lagInEpochsForValidatorSet = 1_864_136; + _expectInvalid(config, "DeployRollupLib: lag in seconds overflows uint32"); + } + + function test_RevertWhenSlashAmountExceedsUint96() public { + RollupConfigInput memory config = _baseConfig(); + config.slashAmounts[2] = uint256(type(uint96).max) + 1; + _expectInvalid(config, "DeployRollupLib: slash amount exceeds uint96"); + } + + function test_RevertWhenRewardDistributorZero() public { + RollupConfigInput memory config = _baseConfig(); + config.rewardConfig.rewardDistributor = IRewardDistributor(address(0)); + _expectInvalid(config, "DeployRollupLib: rewardDistributor is zero"); + } + + function test_RevertWhenStakingAssetIsNotGseAsset() public { + RollupAddressInput memory other = input; + other.stakingAsset = IERC20(makeAddr("other-asset")); + RollupConfigInput memory config = _baseConfig(); + vm.expectRevert(bytes("DeployRollupLib: stakingAsset is not GSE.ASSET()")); + lib.validateRollupConfig(other, config); + } +} diff --git a/l1-contracts/test/script/DeployRollupForUpgrade.t.sol b/l1-contracts/test/script/DeployRollupForUpgrade.t.sol index 0a0b93da073..5d17d25e829 100644 --- a/l1-contracts/test/script/DeployRollupForUpgrade.t.sol +++ b/l1-contracts/test/script/DeployRollupForUpgrade.t.sol @@ -38,6 +38,11 @@ contract DeployRollupForUpgradeTest is Test { string memory path = string.concat(root, "/scripts/network-defaults.json"); string memory json = vm.readFile(path); + // Genesis roots are required by the deployer; any non-zero field element will do here. + vm.setEnv("VK_TREE_ROOT", vm.toString(uint256(keccak256("vk_tree_root")) >> 8)); + vm.setEnv("PROTOCOL_CONTRACTS_HASH", vm.toString(uint256(keccak256("protocol_contracts_hash")) >> 8)); + vm.setEnv("GENESIS_ARCHIVE_ROOT", vm.toString(uint256(keccak256("genesis_archive_root")) >> 8)); + // Timing config vm.setEnv("ETHEREUM_SLOT_DURATION", vm.toString(json.readUint(".ETHEREUM_SLOT_DURATION"))); vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION"))); From 0289c34f5a6f4d1d2d11ac7bfff9dd9f854ec4f7 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Mon, 21 Sep 2026 22:33:34 +0000 Subject: [PATCH 2/4] fix(l1): allow a zero target committee size in the generic deployer --- l1-contracts/script/deploy/DeployRollupLib.sol | 5 +++-- l1-contracts/test/script/DeployConfigValidation.t.sol | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupLib.sol b/l1-contracts/script/deploy/DeployRollupLib.sol index c10c36055e1..d2c3b53d762 100644 --- a/l1-contracts/script/deploy/DeployRollupLib.sol +++ b/l1-contracts/script/deploy/DeployRollupLib.sol @@ -94,7 +94,9 @@ library DeployRollupLib { } /// @notice Reject configuration the Rollup constructor accepts but that leaves the deployed instance unusable. - /// @dev Every value here is fixed at construction, so a bad one can only be corrected by redeploying. + /// @dev Every value here is fixed at construction, so a bad one can only be corrected by redeploying. A zero + /// `targetCommitteeSize` is not rejected: `ValidatorSelectionLib` treats it as "no committee", which local + /// networks and end-to-end tests rely on. function validateRollupConfig(RollupAddressInput memory input, RollupConfigInput memory config) internal view { require(config.ethereumSlotDuration > 0, "DeployRollupLib: ethereumSlotDuration is zero"); require(config.aztecSlotDuration > 0, "DeployRollupLib: aztecSlotDuration is zero"); @@ -106,7 +108,6 @@ library DeployRollupLib { // TimeLib multiplies the two uint32 durations without widening. uint256 epochDurationSeconds = config.aztecSlotDuration * config.aztecEpochDuration; require(epochDurationSeconds <= type(uint32).max, "DeployRollupLib: epoch duration in seconds overflows uint32"); - require(config.targetCommitteeSize > 0, "DeployRollupLib: targetCommitteeSize is zero"); require(config.exitDelaySeconds > 0, "DeployRollupLib: exitDelaySeconds is zero"); // A zero RANDAO lag samples the same epoch it seeds, and the sample-time subtraction is done in uint32. require(config.lagInEpochsForRandao >= 1, "DeployRollupLib: lagInEpochsForRandao is zero"); diff --git a/l1-contracts/test/script/DeployConfigValidation.t.sol b/l1-contracts/test/script/DeployConfigValidation.t.sol index 4c7d29a5e0d..99af18b5043 100644 --- a/l1-contracts/test/script/DeployConfigValidation.t.sol +++ b/l1-contracts/test/script/DeployConfigValidation.t.sol @@ -160,10 +160,10 @@ contract DeployConfigValidationTest is Test { upgrade.requireRegistryHasCode(Registry(makeAddr("not-a-registry"))); } - function test_RevertWhenCommitteeSizeZero() public { + function test_ZeroCommitteeSizeIsAllowed() public view { RollupConfigInput memory config = _baseConfig(); config.targetCommitteeSize = 0; - _expectInvalid(config, "DeployRollupLib: targetCommitteeSize is zero"); + lib.validateRollupConfig(input, config); } function test_RevertWhenExitDelayZero() public { From ec0cd33c953986640b1846794586dbd7589b6680 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Mon, 21 Sep 2026 23:11:19 +0000 Subject: [PATCH 3/4] fix(l1): allow long epochs in the generic deployer and set test env exactly as the other script tests --- .../script/deploy/DeployRollupLib.sol | 8 +- .../test/script/DeployConfigValidation.t.sol | 85 ++++++++++++++----- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/l1-contracts/script/deploy/DeployRollupLib.sol b/l1-contracts/script/deploy/DeployRollupLib.sol index d2c3b53d762..e10e5aa100f 100644 --- a/l1-contracts/script/deploy/DeployRollupLib.sol +++ b/l1-contracts/script/deploy/DeployRollupLib.sol @@ -22,7 +22,6 @@ import {MultiAdder, CheatDepositArgs} from "@aztec/mock/MultiAdder.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {HonkVerifier} from "@generated/HonkVerifier.sol"; -import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {IRollupConfiguration} from "./RollupConfiguration.sol"; @@ -96,15 +95,12 @@ library DeployRollupLib { /// @notice Reject configuration the Rollup constructor accepts but that leaves the deployed instance unusable. /// @dev Every value here is fixed at construction, so a bad one can only be corrected by redeploying. A zero /// `targetCommitteeSize` is not rejected: `ValidatorSelectionLib` treats it as "no committee", which local - /// networks and end-to-end tests rely on. + /// networks and end-to-end tests rely on. An epoch longer than `MAX_CHECKPOINTS_PER_EPOCH` is not rejected + /// either: end-to-end tests use long epochs to keep a run inside epoch 0. function validateRollupConfig(RollupAddressInput memory input, RollupConfigInput memory config) internal view { require(config.ethereumSlotDuration > 0, "DeployRollupLib: ethereumSlotDuration is zero"); require(config.aztecSlotDuration > 0, "DeployRollupLib: aztecSlotDuration is zero"); require(config.aztecEpochDuration > 0, "DeployRollupLib: aztecEpochDuration is zero"); - require( - config.aztecEpochDuration <= Constants.MAX_CHECKPOINTS_PER_EPOCH, - "DeployRollupLib: aztecEpochDuration exceeds MAX_CHECKPOINTS_PER_EPOCH" - ); // TimeLib multiplies the two uint32 durations without widening. uint256 epochDurationSeconds = config.aztecSlotDuration * config.aztecEpochDuration; require(epochDurationSeconds <= type(uint32).max, "DeployRollupLib: epoch duration in seconds overflows uint32"); diff --git a/l1-contracts/test/script/DeployConfigValidation.t.sol b/l1-contracts/test/script/DeployConfigValidation.t.sol index 99af18b5043..94d8e152dfc 100644 --- a/l1-contracts/test/script/DeployConfigValidation.t.sol +++ b/l1-contracts/test/script/DeployConfigValidation.t.sol @@ -68,18 +68,70 @@ contract DeployConfigValidationTest is Test { _; } - // Load the canonical L1 config defaults so `getRollupConfiguration` yields a valid baseline. + // Load the canonical L1 config defaults so `getRollupConfiguration` yields a valid baseline. The values are set + // exactly as the other script tests set them, because the process environment is shared across parallel tests. function setUp() public skipWhenCoverage { string memory root = vm.projectRoot(); - string memory json = vm.readFile(string.concat(root, "/scripts/network-defaults.json")); - string[] memory keys = vm.parseJsonKeys(json, "$"); - for (uint256 i = 0; i < keys.length; i++) { - vm.setEnv(keys[i], _readAsString(json, keys[i])); - } + string memory path = string.concat(root, "/scripts/network-defaults.json"); + string memory json = vm.readFile(path); + + // Genesis roots are required by the deployer; any non-zero field element will do here. vm.setEnv("VK_TREE_ROOT", vm.toString(uint256(keccak256("vk_tree_root")) >> 8)); vm.setEnv("PROTOCOL_CONTRACTS_HASH", vm.toString(uint256(keccak256("protocol_contracts_hash")) >> 8)); vm.setEnv("GENESIS_ARCHIVE_ROOT", vm.toString(uint256(keccak256("genesis_archive_root")) >> 8)); + // Timing config + vm.setEnv("ETHEREUM_SLOT_DURATION", vm.toString(json.readUint(".ETHEREUM_SLOT_DURATION"))); + vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION"))); + vm.setEnv("AZTEC_EPOCH_DURATION", vm.toString(json.readUint(".AZTEC_EPOCH_DURATION"))); + vm.setEnv("AZTEC_PROOF_SUBMISSION_EPOCHS", vm.toString(json.readUint(".AZTEC_PROOF_SUBMISSION_EPOCHS"))); + + // Validator config + vm.setEnv("AZTEC_TARGET_COMMITTEE_SIZE", vm.toString(json.readUint(".AZTEC_TARGET_COMMITTEE_SIZE"))); + vm.setEnv( + "AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET", vm.toString(json.readUint(".AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET")) + ); + vm.setEnv("AZTEC_LAG_IN_EPOCHS_FOR_RANDAO", vm.toString(json.readUint(".AZTEC_LAG_IN_EPOCHS_FOR_RANDAO"))); + vm.setEnv("AZTEC_LOCAL_EJECTION_THRESHOLD", json.readString(".AZTEC_LOCAL_EJECTION_THRESHOLD")); + vm.setEnv("AZTEC_EXIT_DELAY_SECONDS", vm.toString(json.readUint(".AZTEC_EXIT_DELAY_SECONDS"))); + + // Entry queue config + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE", + vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE")) + ); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN"))); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE"))); + + // Fees config + vm.setEnv("AZTEC_MANA_TARGET", vm.toString(json.readUint(".AZTEC_MANA_TARGET"))); + vm.setEnv("AZTEC_PROVING_COST_PER_MANA", vm.toString(json.readUint(".AZTEC_PROVING_COST_PER_MANA"))); + vm.setEnv("AZTEC_INITIAL_ETH_PER_FEE_ASSET", vm.toString(json.readUint(".AZTEC_INITIAL_ETH_PER_FEE_ASSET"))); + + vm.setEnv("AZTEC_REGISTRY_REWARD_OVERRIDE_0", json.readString(".AZTEC_REGISTRY_REWARD_OVERRIDE_0")); + vm.setEnv("AZTEC_REGISTRY_REWARD_OVERRIDE_1", json.readString(".AZTEC_REGISTRY_REWARD_OVERRIDE_1")); + + // Slashing config + vm.setEnv("AZTEC_SLASHER_ENABLED", vm.toString(json.readBool(".AZTEC_SLASHER_ENABLED"))); + vm.setEnv("AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS", vm.toString(json.readUint(".AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS"))); + vm.setEnv("AZTEC_SLASHING_OFFSET_IN_ROUNDS", vm.toString(json.readUint(".AZTEC_SLASHING_OFFSET_IN_ROUNDS"))); + vm.setEnv("AZTEC_SLASHING_LIFETIME_IN_ROUNDS", vm.toString(json.readUint(".AZTEC_SLASHING_LIFETIME_IN_ROUNDS"))); + vm.setEnv( + "AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS", + vm.toString(json.readUint(".AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS")) + ); + vm.setEnv("AZTEC_SLASHING_DISABLE_DURATION", vm.toString(json.readUint(".AZTEC_SLASHING_DISABLE_DURATION"))); + vm.setEnv("AZTEC_SLASHING_VETOER", json.readString(".AZTEC_SLASHING_VETOER")); + vm.setEnv("AZTEC_SLASH_AMOUNT_SMALL", json.readString(".AZTEC_SLASH_AMOUNT_SMALL")); + vm.setEnv("AZTEC_SLASH_AMOUNT_MEDIUM", json.readString(".AZTEC_SLASH_AMOUNT_MEDIUM")); + vm.setEnv("AZTEC_SLASH_AMOUNT_LARGE", json.readString(".AZTEC_SLASH_AMOUNT_LARGE")); + configuration = new RollupConfigurationHarness(); configuration.loadConfig(); lib = new DeployRollupLibHarness(); @@ -95,15 +147,6 @@ contract DeployConfigValidationTest is Test { }); } - function _readAsString(string memory _json, string memory _key) internal pure returns (string memory) { - bytes memory raw = vm.parseJson(_json, string.concat(".", _key)); - // Numbers decode to a single word; strings, addresses and booleans are stored as strings in the defaults file. - if (raw.length == 32) { - return vm.toString(abi.decode(raw, (uint256))); - } - return abi.decode(raw, (string)); - } - function _baseConfig() internal view returns (RollupConfigInput memory) { return configuration.getRollupConfiguration(rewardDistributor); } @@ -166,6 +209,12 @@ contract DeployConfigValidationTest is Test { lib.validateRollupConfig(input, config); } + function test_LongEpochIsAllowed() public view { + RollupConfigInput memory config = _baseConfig(); + config.aztecEpochDuration = 1000; + lib.validateRollupConfig(input, config); + } + function test_RevertWhenExitDelayZero() public { RollupConfigInput memory config = _baseConfig(); config.exitDelaySeconds = 0; @@ -186,12 +235,6 @@ contract DeployConfigValidationTest is Test { _expectInvalid(config, "DeployRollupLib: aztecEpochDuration is zero"); } - function test_RevertWhenEpochDurationAboveProofCap() public { - RollupConfigInput memory config = _baseConfig(); - config.aztecEpochDuration = 33; - _expectInvalid(config, "DeployRollupLib: aztecEpochDuration exceeds MAX_CHECKPOINTS_PER_EPOCH"); - } - function test_RevertWhenEpochSecondsOverflowUint32() public { RollupConfigInput memory config = _baseConfig(); // 32 checkpoints of 2^27 seconds is exactly 2^32 seconds. From 8c9d583deea046c5d55ddf79a866a05221e01e55 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Mon, 21 Sep 2026 23:34:14 +0000 Subject: [PATCH 4/4] test(l1): build the deployer validation baseline in the test instead of the environment --- .../test/script/DeployConfigValidation.t.sol | 123 +++++------------- 1 file changed, 35 insertions(+), 88 deletions(-) diff --git a/l1-contracts/test/script/DeployConfigValidation.t.sol b/l1-contracts/test/script/DeployConfigValidation.t.sol index 94d8e152dfc..7c827ce088a 100644 --- a/l1-contracts/test/script/DeployConfigValidation.t.sol +++ b/l1-contracts/test/script/DeployConfigValidation.t.sol @@ -3,12 +3,13 @@ pragma solidity >=0.8.27; import {Test} from "forge-std/Test.sol"; -import {stdJson} from "forge-std/StdJson.sol"; import {DeployRollupForUpgrade} from "../../script/deploy/DeployRollupForUpgrade.s.sol"; import {DeployRollupLib, RollupAddressInput} from "../../script/deploy/DeployRollupLib.sol"; import {RollupConfiguration} from "../../script/deploy/RollupConfiguration.sol"; import {GenesisState, RollupConfigInput} from "@aztec/core/interfaces/IRollup.sol"; +import {RewardConfig, Bps} from "@aztec/core/libraries/rollup/RewardLib.sol"; +import {IBoosterCore} from "@aztec/core/reward-boost/RewardBooster.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import {Registry} from "@aztec/governance/Registry.sol"; import {GSE} from "@aztec/governance/GSE.sol"; @@ -49,93 +50,20 @@ contract GseAssetStub { /** * @title DeployConfigValidationTest * @notice The env-driven deployer refuses configuration that the Rollup constructor would accept but that leaves - * the deployed instance unusable or mis-bound. The checks are exercised on values rather than through - * `vm.setEnv`, because the process environment is shared by tests running in parallel. + * the deployed instance unusable or mis-bound. The checks are exercised on values built in the test; the + * suite never writes environment variables, because forge runs tests in parallel and the process + * environment is shared with the other script tests. */ contract DeployConfigValidationTest is Test { - using stdJson for string; - RollupConfigurationHarness internal configuration; DeployRollupLibHarness internal lib; RollupAddressInput internal input; IRewardDistributor internal rewardDistributor = IRewardDistributor(makeAddr("rewardDistributor")); IERC20 internal stakingAsset = IERC20(makeAddr("stakingAsset")); - modifier skipWhenCoverage() { - if (vm.envOr("FORGE_COVERAGE", false)) { - vm.skip(true); - } - _; - } - - // Load the canonical L1 config defaults so `getRollupConfiguration` yields a valid baseline. The values are set - // exactly as the other script tests set them, because the process environment is shared across parallel tests. - function setUp() public skipWhenCoverage { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/scripts/network-defaults.json"); - string memory json = vm.readFile(path); - - // Genesis roots are required by the deployer; any non-zero field element will do here. - vm.setEnv("VK_TREE_ROOT", vm.toString(uint256(keccak256("vk_tree_root")) >> 8)); - vm.setEnv("PROTOCOL_CONTRACTS_HASH", vm.toString(uint256(keccak256("protocol_contracts_hash")) >> 8)); - vm.setEnv("GENESIS_ARCHIVE_ROOT", vm.toString(uint256(keccak256("genesis_archive_root")) >> 8)); - - // Timing config - vm.setEnv("ETHEREUM_SLOT_DURATION", vm.toString(json.readUint(".ETHEREUM_SLOT_DURATION"))); - vm.setEnv("AZTEC_SLOT_DURATION", vm.toString(json.readUint(".AZTEC_SLOT_DURATION"))); - vm.setEnv("AZTEC_EPOCH_DURATION", vm.toString(json.readUint(".AZTEC_EPOCH_DURATION"))); - vm.setEnv("AZTEC_PROOF_SUBMISSION_EPOCHS", vm.toString(json.readUint(".AZTEC_PROOF_SUBMISSION_EPOCHS"))); - - // Validator config - vm.setEnv("AZTEC_TARGET_COMMITTEE_SIZE", vm.toString(json.readUint(".AZTEC_TARGET_COMMITTEE_SIZE"))); - vm.setEnv( - "AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET", vm.toString(json.readUint(".AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET")) - ); - vm.setEnv("AZTEC_LAG_IN_EPOCHS_FOR_RANDAO", vm.toString(json.readUint(".AZTEC_LAG_IN_EPOCHS_FOR_RANDAO"))); - vm.setEnv("AZTEC_LOCAL_EJECTION_THRESHOLD", json.readString(".AZTEC_LOCAL_EJECTION_THRESHOLD")); - vm.setEnv("AZTEC_EXIT_DELAY_SECONDS", vm.toString(json.readUint(".AZTEC_EXIT_DELAY_SECONDS"))); - - // Entry queue config - vm.setEnv( - "AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE", - vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE")) - ); - vm.setEnv( - "AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE")) - ); - vm.setEnv("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN"))); - vm.setEnv( - "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT")) - ); - vm.setEnv("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE"))); - - // Fees config - vm.setEnv("AZTEC_MANA_TARGET", vm.toString(json.readUint(".AZTEC_MANA_TARGET"))); - vm.setEnv("AZTEC_PROVING_COST_PER_MANA", vm.toString(json.readUint(".AZTEC_PROVING_COST_PER_MANA"))); - vm.setEnv("AZTEC_INITIAL_ETH_PER_FEE_ASSET", vm.toString(json.readUint(".AZTEC_INITIAL_ETH_PER_FEE_ASSET"))); - - vm.setEnv("AZTEC_REGISTRY_REWARD_OVERRIDE_0", json.readString(".AZTEC_REGISTRY_REWARD_OVERRIDE_0")); - vm.setEnv("AZTEC_REGISTRY_REWARD_OVERRIDE_1", json.readString(".AZTEC_REGISTRY_REWARD_OVERRIDE_1")); - - // Slashing config - vm.setEnv("AZTEC_SLASHER_ENABLED", vm.toString(json.readBool(".AZTEC_SLASHER_ENABLED"))); - vm.setEnv("AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS", vm.toString(json.readUint(".AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS"))); - vm.setEnv("AZTEC_SLASHING_OFFSET_IN_ROUNDS", vm.toString(json.readUint(".AZTEC_SLASHING_OFFSET_IN_ROUNDS"))); - vm.setEnv("AZTEC_SLASHING_LIFETIME_IN_ROUNDS", vm.toString(json.readUint(".AZTEC_SLASHING_LIFETIME_IN_ROUNDS"))); - vm.setEnv( - "AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS", - vm.toString(json.readUint(".AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS")) - ); - vm.setEnv("AZTEC_SLASHING_DISABLE_DURATION", vm.toString(json.readUint(".AZTEC_SLASHING_DISABLE_DURATION"))); - vm.setEnv("AZTEC_SLASHING_VETOER", json.readString(".AZTEC_SLASHING_VETOER")); - vm.setEnv("AZTEC_SLASH_AMOUNT_SMALL", json.readString(".AZTEC_SLASH_AMOUNT_SMALL")); - vm.setEnv("AZTEC_SLASH_AMOUNT_MEDIUM", json.readString(".AZTEC_SLASH_AMOUNT_MEDIUM")); - vm.setEnv("AZTEC_SLASH_AMOUNT_LARGE", json.readString(".AZTEC_SLASH_AMOUNT_LARGE")); - + function setUp() public { configuration = new RollupConfigurationHarness(); - configuration.loadConfig(); lib = new DeployRollupLibHarness(); - input = RollupAddressInput({ deployer: address(this), registry: Registry(makeAddr("registry")), @@ -147,8 +75,30 @@ contract DeployConfigValidationTest is Test { }); } - function _baseConfig() internal view returns (RollupConfigInput memory) { - return configuration.getRollupConfiguration(rewardDistributor); + /// @dev The mainnet timing and slashing values; the fields the deployer does not check are left zeroed. + function _baseConfig() internal view returns (RollupConfigInput memory config) { + config.ethereumSlotDuration = 12; + config.aztecSlotDuration = 72; + config.aztecEpochDuration = 32; + config.targetCommitteeSize = 48; + config.lagInEpochsForValidatorSet = 2; + config.lagInEpochsForRandao = 1; + config.exitDelaySeconds = 345_600; + config.slashAmounts = [uint256(2000e18), 5000e18, 5000e18]; + config.rewardConfig = RewardConfig({ + rewardDistributor: rewardDistributor, + sequencerBps: Bps.wrap(7000), + booster: IBoosterCore(address(0)), + checkpointReward: 500e18 + }); + } + + function _validGenesis() internal pure returns (GenesisState memory) { + return GenesisState({ + vkTreeRoot: bytes32(uint256(keccak256("vk_tree_root")) >> 8), + protocolContractsHash: bytes32(uint256(keccak256("protocol_contracts_hash")) >> 8), + genesisArchiveRoot: bytes32(uint256(keccak256("genesis_archive_root")) >> 8) + }); } function _expectInvalid(RollupConfigInput memory _config, string memory _reason) internal { @@ -156,8 +106,9 @@ contract DeployConfigValidationTest is Test { lib.validateRollupConfig(input, _config); } - function test_DefaultsAreValid() public view { + function test_BaselineIsValid() public view { lib.validateRollupConfig(input, _baseConfig()); + configuration.requireGenesisState(_validGenesis()); } function test_RealVerifierUnlessDisabled() public view { @@ -165,21 +116,21 @@ contract DeployConfigValidationTest is Test { } function test_RevertWhenGenesisRootUnset() public { - GenesisState memory genesisState = configuration.getGenesisState(); + GenesisState memory genesisState = _validGenesis(); genesisState.vkTreeRoot = bytes32(0); vm.expectRevert(bytes("RollupConfiguration: VK_TREE_ROOT is unset")); configuration.requireGenesisState(genesisState); } function test_RevertWhenProtocolContractsHashUnset() public { - GenesisState memory genesisState = configuration.getGenesisState(); + GenesisState memory genesisState = _validGenesis(); genesisState.protocolContractsHash = bytes32(0); vm.expectRevert(bytes("RollupConfiguration: PROTOCOL_CONTRACTS_HASH is unset")); configuration.requireGenesisState(genesisState); } function test_RevertWhenGenesisArchiveRootUnset() public { - GenesisState memory genesisState = configuration.getGenesisState(); + GenesisState memory genesisState = _validGenesis(); genesisState.genesisArchiveRoot = bytes32(0); vm.expectRevert(bytes("RollupConfiguration: GENESIS_ARCHIVE_ROOT is unset")); configuration.requireGenesisState(genesisState); @@ -238,7 +189,6 @@ contract DeployConfigValidationTest is Test { function test_RevertWhenEpochSecondsOverflowUint32() public { RollupConfigInput memory config = _baseConfig(); // 32 checkpoints of 2^27 seconds is exactly 2^32 seconds. - config.aztecEpochDuration = 32; config.aztecSlotDuration = uint256(1) << 27; _expectInvalid(config, "DeployRollupLib: epoch duration in seconds overflows uint32"); } @@ -259,9 +209,6 @@ contract DeployConfigValidationTest is Test { function test_RevertWhenLagSecondsOverflowUint32() public { RollupConfigInput memory config = _baseConfig(); // 2304 seconds per epoch (72 x 32); 1_864_136 epochs of lag is just over 2^32 seconds. - config.aztecSlotDuration = 72; - config.aztecEpochDuration = 32; - config.lagInEpochsForRandao = 1; config.lagInEpochsForValidatorSet = 1_864_136; _expectInvalid(config, "DeployRollupLib: lag in seconds overflows uint32"); }