From 50de961434cf7480a69eb223aa7e8f1a999f78a2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 10 Sep 2026 12:08:19 -0300 Subject: [PATCH] refactor(l1): hold the rollup config in immutables The deployment-time rollup configuration -- VK tree root, protocol contracts hash, version, fee asset, fee asset portal, epoch proof verifier, Inbox and Outbox -- never changes after construction, so it moves out of `RollupStore.config` storage into `Rollup` immutables. A new `_getRollupConfig` assembles a `RollupConfig` memory struct and threads it down into the libraries, which cannot read a contract's immutables themselves. The config getters route through that one accessor rather than reading their immutable directly: each direct read inlines a 32-byte push into `Rollup`'s runtime code, and `Rollup` sits close to the EIP-170 limit, so sharing one assembly across all of them is about 95 bytes cheaper. `ValidateHeaderArgs` is likewise assembled inside `RollupOperationsExtLib` instead of in the Rollup, which saves several hundred more bytes of runtime code. Immutables must be assigned directly in the constructor body, so the store setup can no longer be factored out into `_initializeStore`; `STFLib.initialize` now writes only the genesis archive root. `Rollup` links against `EpochProofExtLib`, which the generated artifact list was missing. Propose drops about 1.9k gas and an epoch proof submission about 10.8k. --- l1-contracts/gas_benchmark.md | 14 +-- l1-contracts/gas_benchmark_results.json | 40 +++--- l1-contracts/gas_report.json | 114 +++++++++--------- .../scripts/generate-artifacts.sh | 1 + .../partial_epoch_proof_gas_report.json | 42 +++---- .../partial_epoch_proof_gas_report.md | 12 +- l1-contracts/src/core/Rollup.sol | 45 +++---- l1-contracts/src/core/RollupCore.sol | 66 ++++++---- l1-contracts/src/core/interfaces/IRollup.sol | 7 +- .../libraries/rollup/EpochProofExtLib.sol | 11 +- .../core/libraries/rollup/EpochProofLib.sol | 46 ++++--- .../src/core/libraries/rollup/ProposeLib.sol | 20 ++- .../core/libraries/rollup/RewardExtLib.sol | 9 +- .../src/core/libraries/rollup/RewardLib.sol | 21 ++-- .../rollup/RollupOperationsExtLib.sol | 46 +++++-- .../src/core/libraries/rollup/STFLib.sol | 14 +-- l1-contracts/test/RollupWithPreheating.sol | 3 +- .../PartialEpochProofGasReporter.sol | 41 +++++-- l1-contracts/test/benchmark/happy.t.sol | 4 +- .../libraries/rewardlib/RewardLibWrapper.sol | 17 ++- ...m-read-rollup-config-through-contrac.patch | 108 +++++++++++++++++ 21 files changed, 446 insertions(+), 235 deletions(-) create mode 100644 labs-patches/0014-refactor-ethereum-read-rollup-config-through-contrac.patch diff --git a/l1-contracts/gas_benchmark.md b/l1-contracts/gas_benchmark.md index 127649a78e40..4876d3a8c051 100644 --- a/l1-contracts/gas_benchmark.md +++ b/l1-contracts/gas_benchmark.md @@ -14,22 +14,22 @@ | Function | Avg Gas | Max Gas | Calldata Size | Calldata Gas | |----------------------|---------|-----------|---------------|--------------| -| propose | 199,366 | 225,550 | 996 | 15,936 | -| submitEpochRootProof | 991,032 | 1,029,525 | 14,148 | 226,368 | +| propose | 197,433 | 223,617 | 996 | 15,936 | +| submitEpochRootProof | 980,213 | 1,018,677 | 14,148 | 226,368 | | setupEpoch | 32,042 | 113,837 | - | - | -**Avg Gas Cost per Second**: 3,643.2 gas/second +**Avg Gas Cost per Second**: 3,606.9 gas/second *Epoch duration*: 0h 38m 24s ## Validators | Function | Avg Gas | Max Gas | Calldata Size | Calldata Gas | |----------------------|-----------|-----------|---------------|--------------| -| propose | 327,774 | 355,591 | 4,516 | 72,256 | -| submitEpochRootProof | 1,572,081 | 1,669,921 | 16,644 | 266,304 | -| aggregate3 | 376,665 | 390,039 | - | - | +| propose | 325,842 | 353,616 | 4,516 | 72,256 | +| submitEpochRootProof | 1,561,264 | 1,659,137 | 16,644 | 266,304 | +| aggregate3 | 374,728 | 388,088 | - | - | | setupEpoch | 46,504 | 547,670 | - | - | -**Avg Gas Cost per Second**: 5,937.3 gas/second +**Avg Gas Cost per Second**: 5,901.0 gas/second *Epoch duration*: 0h 38m 24s diff --git a/l1-contracts/gas_benchmark_results.json b/l1-contracts/gas_benchmark_results.json index a742ea9be3a1..ad89400b1e3d 100644 --- a/l1-contracts/gas_benchmark_results.json +++ b/l1-contracts/gas_benchmark_results.json @@ -2,10 +2,10 @@ "no_validators": { "propose": { "calls": 150, - "min": 185722, - "mean": 199366, - "median": 195111, - "max": 225550, + "min": 183789, + "mean": 197433, + "median": 193178, + "max": 223617, "calldata_size": 996, "calldata_gas": 15936 }, @@ -18,10 +18,10 @@ }, "submitEpochRootProof": { "calls": 4, - "min": 972329, - "mean": 991032, - "median": 981138, - "max": 1029525, + "min": 961594, + "mean": 980213, + "median": 970292, + "max": 1018677, "calldata_size": 14148, "calldata_gas": 226368 } @@ -29,10 +29,10 @@ "validators": { "propose": { "calls": 150, - "min": 305434, - "mean": 327774, - "median": 327227, - "max": 355591, + "min": 303447, + "mean": 325842, + "median": 325288, + "max": 353616, "calldata_size": 4516, "calldata_gas": 72256 }, @@ -45,19 +45,19 @@ }, "submitEpochRootProof": { "calls": 4, - "min": 1460323, - "mean": 1572081, - "median": 1579041, - "max": 1669921, + "min": 1449433, + "mean": 1561264, + "median": 1568243, + "max": 1659137, "calldata_size": 16644, "calldata_gas": 266304 }, "aggregate3": { "calls": 55, - "min": 365547, - "mean": 376665, - "median": 376350, - "max": 390039 + "min": 363549, + "mean": 374728, + "median": 374471, + "max": 388088 } } } \ No newline at end of file diff --git a/l1-contracts/gas_report.json b/l1-contracts/gas_report.json index 6d7c967d5aef..2a3b6bc74778 100644 --- a/l1-contracts/gas_report.json +++ b/l1-contracts/gas_report.json @@ -76,7 +76,7 @@ }, "functions": { "getCanonicalRollup()": { - "calls": 1780, + "calls": 1708, "min": 1073, "mean": 4073, "median": 4073, @@ -106,7 +106,7 @@ }, "functions": { "availableTo(address)": { - "calls": 890, + "calls": 854, "min": 20573, "mean": 20573, "median": 20573, @@ -118,7 +118,7 @@ "contract": "test/RollupWithPreheating.sol:RollupWithPreheating", "deployment": { "gas": 0, - "size": 42950 + "size": 42916 }, "functions": { "archive()": { @@ -136,7 +136,7 @@ "max": 2509 }, "getCheckpoint(uint256)": { - "calls": 900, + "calls": 864, "min": 27185, "mean": 27185, "median": 27185, @@ -144,20 +144,20 @@ }, "getCheckpointReward()": { "calls": 2589, - "min": 1128, - "mean": 1133, - "median": 1128, - "max": 5628 + "min": 1040, + "mean": 1045, + "median": 1040, + "max": 5540 }, "getCollectiveProverRewardsForEpoch(uint256)": { "calls": 3, - "min": 5837, - "mean": 5837, - "median": 5837, - "max": 5837 + "min": 5859, + "mean": 5859, + "median": 5859, + "max": 5859 }, "getCurrentEpoch()": { - "calls": 891, + "calls": 855, "min": 915, "mean": 915, "median": 915, @@ -179,10 +179,10 @@ }, "getEpochProofPublicInputs(uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],bytes)": { "calls": 4, - "min": 16751, - "mean": 45593, - "median": 47624, - "max": 70374 + "min": 18578, + "mean": 44270, + "median": 46301, + "max": 65899 }, "getEthPerFeeAsset()": { "calls": 2, @@ -193,24 +193,24 @@ }, "getFeeAssetPortal()": { "calls": 4660, - "min": 566, - "mean": 1456, - "median": 566, - "max": 2566 + "min": 901, + "mean": 901, + "median": 901, + "max": 901 }, "getInbox()": { "calls": 9073, - "min": 2543, - "mean": 2543, - "median": 2543, - "max": 2543 + "min": 878, + "mean": 878, + "median": 878, + "max": 878 }, "getL1FeesAt(uint256)": { "calls": 2, - "min": 9086, - "mean": 9086, - "median": 9086, - "max": 9086 + "min": 9130, + "mean": 9130, + "median": 9130, + "max": 9130 }, "getManaMinFeeAt(uint256,bool)": { "calls": 2336, @@ -221,17 +221,17 @@ }, "getManaTarget()": { "calls": 1026, - "min": 5526, - "mean": 5526, - "median": 5526, - "max": 5526 + "min": 5548, + "mean": 5548, + "median": 5548, + "max": 5548 }, "getOutbox()": { "calls": 2, - "min": 2521, - "mean": 2521, - "median": 2521, - "max": 2521 + "min": 856, + "mean": 856, + "median": 856, + "max": 856 }, "getPendingCheckpointNumber()": { "calls": 1679, @@ -263,10 +263,10 @@ }, "getSequencerRewards(address)": { "calls": 2, - "min": 5981, - "mean": 5981, - "median": 5981, - "max": 5981 + "min": 6025, + "mean": 6025, + "median": 6025, + "max": 6025 }, "getTimestampForSlot(uint256)": { "calls": 2442, @@ -277,10 +277,10 @@ }, "getVersion()": { "calls": 4919, - "min": 499, - "mean": 1447, - "median": 499, - "max": 2499 + "min": 852, + "mean": 852, + "median": 852, + "max": 852 }, "owner()": { "calls": 5173, @@ -292,16 +292,16 @@ "propose((bytes32,(int256),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256),uint256),(bytes,bytes),address[],(uint8,bytes32,bytes32),bytes)": { "calls": 2339, "min": 0, - "mean": 266764, - "median": 284282, - "max": 325392 + "mean": 264835, + "median": 282349, + "max": 323459 }, "prune()": { "calls": 6, - "min": 26689, - "mean": 33247, - "median": 33682, - "max": 38274 + "min": 26711, + "mean": 33269, + "median": 33704, + "max": 38296 }, "setProvingCostPerMana(uint256)": { "calls": 1, @@ -311,11 +311,11 @@ "max": 52474 }, "submitEpochRootProof((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": { - "calls": 897, - "min": 58286, - "mean": 374005, - "median": 379619, - "max": 418311 + "calls": 861, + "min": 60171, + "mean": 366230, + "median": 370877, + "max": 407547 }, "updateManaTarget(uint256)": { "calls": 512, diff --git a/l1-contracts/l1-artifacts/scripts/generate-artifacts.sh b/l1-contracts/l1-artifacts/scripts/generate-artifacts.sh index 8a224afdcdb0..5c0245208477 100755 --- a/l1-contracts/l1-artifacts/scripts/generate-artifacts.sh +++ b/l1-contracts/l1-artifacts/scripts/generate-artifacts.sh @@ -15,6 +15,7 @@ contracts=( "EscapeHatch" "SlashingProposer" "EmpireBase" + "EpochProofExtLib" "RollupOperationsExtLib" "ValidatorOperationsExtLib" "RewardExtLib" diff --git a/l1-contracts/partial_epoch_proof_gas_report.json b/l1-contracts/partial_epoch_proof_gas_report.json index 10e5964f5a7c..53d1d3c3038c 100644 --- a/l1-contracts/partial_epoch_proof_gas_report.json +++ b/l1-contracts/partial_epoch_proof_gas_report.json @@ -3,43 +3,43 @@ "contract": "test/RollupWithPreheating.sol:RollupWithPreheating", "deployment": { "gas": 0, - "size": 42950 + "size": 42916 }, "functions": { "gasReportSubmit16Checkpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": { "calls": 1, - "min": 1291434, - "mean": 1291434, - "median": 1291434, - "max": 1291434 + "min": 1281009, + "mean": 1281009, + "median": 1281009, + "max": 1281009 }, "gasReportSubmit1Checkpoint((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": { "calls": 1, - "min": 661209, - "mean": 661209, - "median": 661209, - "max": 661209 + "min": 650880, + "mean": 650880, + "median": 650880, + "max": 650880 }, "gasReportSubmit32Checkpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": { "calls": 1, - "min": 1805036, - "mean": 1805036, - "median": 1805036, - "max": 1805036 + "min": 1794676, + "mean": 1794676, + "median": 1794676, + "max": 1794676 }, "gasReportSubmit8Checkpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": { "calls": 1, - "min": 980313, - "mean": 980313, - "median": 980313, - "max": 980313 + "min": 969891, + "mean": 969891, + "median": 969891, + "max": 969891 }, "gasReportSubmit8MoreCheckpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": { "calls": 1, - "min": 988027, - "mean": 988027, - "median": 988027, - "max": 988027 + "min": 977713, + "mean": 977713, + "median": 977713, + "max": 977713 } } } diff --git a/l1-contracts/partial_epoch_proof_gas_report.md b/l1-contracts/partial_epoch_proof_gas_report.md index 6867f0249955..2a020b9baf4d 100644 --- a/l1-contracts/partial_epoch_proof_gas_report.md +++ b/l1-contracts/partial_epoch_proof_gas_report.md @@ -2,10 +2,10 @@ | Proof submission | Gas | |---|---:| -| 1 Checkpoint | 661,209 | -| 8 Checkpoints | 980,313 | -| 8 More Checkpoints | 988,027 | -| 16 Checkpoints | 1,291,434 | -| 32 Checkpoints | 1,805,036 | +| 1 Checkpoint | 650,880 | +| 8 Checkpoints | 969,891 | +| 8 More Checkpoints | 977,713 | +| 16 Checkpoints | 1,281,009 | +| 32 Checkpoints | 1,794,676 | -Uses the mock epoch proof verifier. +_Uses the mock epoch proof verifier; real ZK verification and top-level transaction calldata gas are not included._ diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 3abd82078875..4321db2222f4 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -13,7 +13,8 @@ import { EthPerFeeAssetE12, CheckpointHeaderValidationFlags, FeeHeader, - RollupConfigInput + RollupConfigInput, + RollupStore } from "@aztec/core/interfaces/IRollup.sol"; import {IStaking, AttesterConfig, Exit, AttesterView, Status} from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelection, IEmperor} from "@aztec/core/interfaces/IValidatorSelection.sol"; @@ -28,7 +29,6 @@ import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributo import {CompressedSlot, CompressedTimestamp, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol"; import {Signature} from "@aztec/shared/libraries/SignatureLib.sol"; import {ChainTipsLib, CompressedChainTips} from "./libraries/compressed-data/Tips.sol"; -import {ValidateHeaderArgs} from "./libraries/rollup/ProposeLib.sol"; import {RewardExtLib, RewardConfig} from "./libraries/rollup/RewardExtLib.sol"; import {DepositArgs} from "./libraries/StakingQueue.sol"; import { @@ -46,7 +46,6 @@ import { ValidatorOperationsExtLib, EthValue, STFLib, - RollupStore, IInbox, IOutbox } from "./RollupCore.sol"; @@ -90,24 +89,15 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { */ function validateHeaderWithAttestations( ProposedHeader calldata _header, - CommitteeAttestations memory _attestations, + CommitteeAttestations calldata _attestations, address[] calldata _signers, - Signature memory _attestationsAndSignersSignature, + Signature calldata _attestationsAndSignersSignature, bytes32 _digest, bytes32 _blobsHash, - CheckpointHeaderValidationFlags memory _flags + CheckpointHeaderValidationFlags calldata _flags ) external override(IRollup) { RollupOperationsExtLib.validateHeaderWithAttestations( - ValidateHeaderArgs({ - header: _header, - digest: _digest, - manaMinFee: getManaMinFeeAt(Timestamp.wrap(block.timestamp), true), - blobsHashesCommitment: _blobsHash, - flags: _flags - }), - _attestations, - _signers, - _attestationsAndSignersSignature + _header, _attestations, _signers, _attestationsAndSignersSignature, _digest, _blobsHash, _flags ); } @@ -301,7 +291,9 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { ProposedHeader[] calldata _headers, bytes calldata _blobPublicInputs ) external view override(IRollup) returns (bytes32[] memory) { - return EpochProofExtLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs); + return EpochProofExtLib.getEpochProofPublicInputs( + _start, _end, _args, _headers, _blobPublicInputs, _getRollupConfig() + ); } /** @@ -540,36 +532,39 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return RewardExtLib.getProvingCostPerMana().toFeeAsset(getEthPerFeeAsset()); } + // The config getters below go through {_getRollupConfig} rather than reading their immutable + // directly. Each direct read inlines a 32-byte push into this contract's runtime code, and Rollup + // sits close to the EIP-170 limit; sharing one assembly across all of them is ~95 bytes cheaper. function getVersion() external view override(IHaveVersion) returns (uint256) { - return STFLib.getStorage().config.version; + return _getRollupConfig().version; } function getInbox() external view override(IRollup) returns (IInbox) { - return STFLib.getStorage().config.inbox; + return _getRollupConfig().inbox; } function getOutbox() external view override(IRollup) returns (IOutbox) { - return STFLib.getStorage().config.outbox; + return _getRollupConfig().outbox; } function getFeeAsset() external view override(IRollup) returns (IERC20) { - return STFLib.getStorage().config.feeAsset; + return _getRollupConfig().feeAsset; } function getFeeAssetPortal() external view override(IRollup) returns (IFeeJuicePortal) { - return STFLib.getStorage().config.feeAssetPortal; + return _getRollupConfig().feeAssetPortal; } function getVkTreeRoot() external view override(IRollup) returns (bytes32) { - return STFLib.getStorage().config.vkTreeRoot; + return _getRollupConfig().vkTreeRoot; } function getProtocolContractsHash() external view override(IRollup) returns (bytes32) { - return STFLib.getStorage().config.protocolContractsHash; + return _getRollupConfig().protocolContractsHash; } function getEpochProofVerifier() external view override(IRollup) returns (IVerifier) { - return STFLib.getStorage().config.epochProofVerifier; + return _getRollupConfig().epochProofVerifier; } function getRewardDistributor() external view override(IRollup) returns (IRewardDistributor) { diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 019b721bb4f9..9f65759d77ac 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import { IRollupCore, - RollupStore, + RollupConfig, SubmitEpochRootProofArgs, RollupConfigInput } from "@aztec/core/interfaces/IRollup.sol"; @@ -189,6 +189,18 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali */ uint256 public immutable L1_BLOCK_AT_GENESIS; + // The deployment-time rollup configuration. Every value is fixed at construction, so it is held in + // immutables rather than storage; {_getRollupConfig} assembles it for the libraries, which cannot read + // a contract's immutables themselves. + bytes32 internal immutable VK_TREE_ROOT; + bytes32 internal immutable PROTOCOL_CONTRACTS_HASH; + uint32 internal immutable VERSION; + IERC20 internal immutable FEE_ASSET; + IFeeJuicePortal internal immutable FEE_ASSET_PORTAL; + IVerifier internal immutable EPOCH_PROOF_VERIFIER; + IInbox internal immutable INBOX; + IOutbox internal immutable OUTBOX; + /** * @dev Storage gap to ensure checkBlob is in its own storage slot */ @@ -259,7 +271,20 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali L1_BLOCK_AT_GENESIS = block.number; - _initializeStore(_feeAsset, _epochProofVerifier, _genesisState, _config); + // Immutables must be assigned directly in the constructor body, so the store setup cannot be + // factored out into a helper the way the slasher and reward setup are. + VK_TREE_ROOT = _genesisState.vkTreeRoot; + PROTOCOL_CONTRACTS_HASH = _genesisState.protocolContractsHash; + VERSION = _config.version; + FEE_ASSET = _feeAsset; + EPOCH_PROOF_VERIFIER = _epochProofVerifier; + + IInbox inbox = IInbox(address(new Inbox(address(this), _feeAsset, _config.version, INBOX_BUCKET_RING_SIZE))); + INBOX = inbox; + OUTBOX = IOutbox(address(new Outbox(address(this), _config.version))); + FEE_ASSET_PORTAL = IFeeJuicePortal(inbox.getFeeAssetPortal()); + + STFLib.initialize(_genesisState); FeeLib.initialize(_config.manaTarget, _config.provingCostPerMana, _config.initialEthPerFeeAsset); } @@ -354,7 +379,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali * @return The amount of rewards claimed */ function claimSequencerRewards(address _coinbase) external override(IRollupCore) returns (uint256) { - return RewardExtLib.claimSequencerRewards(_coinbase); + return RewardExtLib.claimSequencerRewards(_coinbase, FEE_ASSET); } /** @@ -370,7 +395,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali override(IRollupCore) returns (uint256) { - return RewardExtLib.claimProverRewards(_coinbase, _epochs); + return RewardExtLib.claimProverRewards(_coinbase, _epochs, FEE_ASSET); } /** @@ -470,7 +495,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali * @param _args Contains the epoch range, public inputs, fees, attestations, and the ZK proof */ function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external override(IRollupCore) { - EpochProofExtLib.submitEpochRootProof(_args); + EpochProofExtLib.submitEpochRootProof(_args, _getRollupConfig()); } /** @@ -493,7 +518,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali bytes calldata _blobInput ) external override(IRollupCore) { RollupOperationsExtLib.propose( - _args, _attestations, _signers, _attestationsAndSignersSignature, _blobInput, checkBlob + _args, _attestations, _signers, _attestationsAndSignersSignature, _blobInput, checkBlob, INBOX ); } @@ -610,23 +635,16 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali RewardExtLib.initializeConfig(rewardConfig); } - function _initializeStore( - IERC20 _feeAsset, - IVerifier _epochProofVerifier, - GenesisState memory _genesisState, - RollupConfigInput memory _config - ) internal { - STFLib.initialize(_genesisState); - RollupStore storage rollupStore = STFLib.getStorage(); - - rollupStore.config.feeAsset = _feeAsset; - rollupStore.config.epochProofVerifier = _epochProofVerifier; - rollupStore.config.version = _config.version; - - IInbox inbox = IInbox(address(new Inbox(address(this), _feeAsset, _config.version, INBOX_BUCKET_RING_SIZE))); - - rollupStore.config.inbox = inbox; - rollupStore.config.outbox = IOutbox(address(new Outbox(address(this), _config.version))); - rollupStore.config.feeAssetPortal = IFeeJuicePortal(inbox.getFeeAssetPortal()); + function _getRollupConfig() internal view virtual returns (RollupConfig memory) { + return RollupConfig({ + vkTreeRoot: VK_TREE_ROOT, + protocolContractsHash: PROTOCOL_CONTRACTS_HASH, + version: VERSION, + feeAsset: FEE_ASSET, + feeAssetPortal: FEE_ASSET_PORTAL, + epochProofVerifier: EPOCH_PROOF_VERIFIER, + inbox: INBOX, + outbox: OUTBOX + }); } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index a15827dca52e..19a7293241f9 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -86,6 +86,12 @@ struct RollupConfigInput { uint256 ethereumSlotDuration; } +/** + * @notice The rollup's deployment-time configuration. + * @dev Every field is fixed at construction, so the values live in the Rollup's immutables rather than + * in storage. This struct is assembled in memory and threaded down into the libraries, which cannot + * read the contract's immutables themselves. + */ struct RollupConfig { bytes32 vkTreeRoot; bytes32 protocolContractsHash; @@ -102,7 +108,6 @@ struct RollupStore { mapping(uint256 checkpointNumber => bytes32 archive) archives; // The following represents a circular buffer. Key is `checkpointNumber % size`. mapping(uint256 circularIndex => CompressedTempCheckpointLog temp) tempCheckpointLogs; - RollupConfig config; } interface IRollupCore { diff --git a/l1-contracts/src/core/libraries/rollup/EpochProofExtLib.sol b/l1-contracts/src/core/libraries/rollup/EpochProofExtLib.sol index a71251c19a7b..bda2a0bf61d8 100644 --- a/l1-contracts/src/core/libraries/rollup/EpochProofExtLib.sol +++ b/l1-contracts/src/core/libraries/rollup/EpochProofExtLib.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {SubmitEpochRootProofArgs, PublicInputArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {SubmitEpochRootProofArgs, PublicInputArgs, RollupConfig} from "@aztec/core/interfaces/IRollup.sol"; import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol"; import {EpochProofLib} from "./EpochProofLib.sol"; @@ -20,8 +20,8 @@ import {EpochProofLib} from "./EpochProofLib.sol"; * - Epoch proof public input computation */ library EpochProofExtLib { - function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external { - EpochProofLib.submitEpochRootProof(_args); + function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args, RollupConfig memory _config) external { + EpochProofLib.submitEpochRootProof(_args, _config); } function getEpochProofPublicInputs( @@ -29,8 +29,9 @@ library EpochProofExtLib { uint256 _end, PublicInputArgs calldata _args, ProposedHeader[] calldata _headers, - bytes calldata _blobPublicInputs + bytes calldata _blobPublicInputs, + RollupConfig memory _config ) external view returns (bytes32[] memory) { - return EpochProofLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs); + return EpochProofLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs, _config); } } diff --git a/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol b/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol index dbbf21d55042..739258786dfa 100644 --- a/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol +++ b/l1-contracts/src/core/libraries/rollup/EpochProofLib.sol @@ -4,7 +4,13 @@ pragma solidity >=0.8.27; import {BlobLib} from "@aztec-blob-lib/BlobLib.sol"; import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol"; -import {SubmitEpochRootProofArgs, PublicInputArgs, IRollupCore, RollupStore} from "@aztec/core/interfaces/IRollup.sol"; +import { + SubmitEpochRootProofArgs, + PublicInputArgs, + IRollupCore, + RollupStore, + RollupConfig +} from "@aztec/core/interfaces/IRollup.sol"; import {CompressedTempCheckpointLog} from "@aztec/core/libraries/compressed-data/CheckpointLog.sol"; import {CompressedFeeHeader, FeeHeaderLib} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol"; import {ChainTipsLib, CompressedChainTips} from "@aztec/core/libraries/compressed-data/Tips.sol"; @@ -101,8 +107,9 @@ library EpochProofLib { * - attestations: Committee attestations for the last checkpoint in the epoch * - blobInputs: Batched blob data for EIP-4844 point evaluation precompile * - proof: The validity proof bytes for the root rollup circuit + * @param _config The rollup's deployment-time configuration */ - function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) internal { + function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args, RollupConfig memory _config) internal { if (STFLib.canPruneAtTime(Timestamp.wrap(block.timestamp))) { STFLib.prune(); } @@ -118,7 +125,7 @@ library EpochProofLib { // ensuring committee agreement on the epoch's validity alongside the cryptographic proof verification below. verifyLastCheckpointAttestationsAndOutHash(_args.end, _args.attestations, _args.args.outHash); - require(verifyEpochRootProof(_args), Errors.Rollup__InvalidProof()); + require(verifyEpochRootProof(_args, _config), Errors.Rollup__InvalidProof()); RollupStore storage rollupStore = STFLib.getStorage(); @@ -136,11 +143,11 @@ library EpochProofLib { // the number of checkpoints proven in this epoch so off-chain consumers can map a tx's // position-within-epoch directly to the smallest proof that covers it. uint256 numCheckpointsInEpoch = _args.end - _args.start + 1; - rollupStore.config.outbox.insert(endEpoch, numCheckpointsInEpoch, _args.args.outHash); + _config.outbox.insert(endEpoch, numCheckpointsInEpoch, _args.args.outHash); } } - RewardLib.handleRewardsAndFees(_args, endEpoch); + RewardLib.handleRewardsAndFees(_args, endEpoch, _config); emit IRollupCore.L2ProofVerified(_args.end, _args.args.proverId); } @@ -162,16 +169,18 @@ library EpochProofLib { * @param _args - Array of public inputs to the proof (previousArchive, endArchive, endTimestamp, outHash, proverId) * @param _headers - The proposed checkpoint headers supplying the fee recipient and value for each checkpoint * @param _blobPublicInputs- The blob public inputs for the proof + * @param _config - The rollup's deployment-time configuration */ function getEpochProofPublicInputs( uint256 _start, uint256 _end, PublicInputArgs calldata _args, ProposedHeader[] calldata _headers, - bytes calldata _blobPublicInputs + bytes calldata _blobPublicInputs, + RollupConfig memory _config ) internal view returns (bytes32[] memory) { verifyHeaders(_start, _end, _headers); - return computeEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs); + return computeEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs, _config); } /** @@ -240,13 +249,15 @@ library EpochProofLib { * @param _args - Array of public inputs to the proof (previousArchive, endArchive, endTimestamp, outHash, proverId) * @param _headers - The proposed checkpoint headers supplying the fee recipient and value for each checkpoint * @param _blobPublicInputs- The blob public inputs for the proof + * @param _config - The rollup's deployment-time configuration */ function computeEpochProofPublicInputs( uint256 _start, uint256 _end, PublicInputArgs calldata _args, ProposedHeader[] calldata _headers, - bytes calldata _blobPublicInputs + bytes calldata _blobPublicInputs, + RollupConfig memory _config ) private view returns (bytes32[] memory) { RollupStore storage rollupStore = STFLib.getStorage(); @@ -339,15 +350,15 @@ library EpochProofLib { publicInputs[offset] = bytes32(block.chainid); offset += 1; - publicInputs[offset] = bytes32(uint256(rollupStore.config.version)); + publicInputs[offset] = bytes32(uint256(_config.version)); offset += 1; // vk_tree_root - publicInputs[offset] = rollupStore.config.vkTreeRoot; + publicInputs[offset] = _config.vkTreeRoot; offset += 1; // protocol_contracts_hash - publicInputs[offset] = rollupStore.config.protocolContractsHash; + publicInputs[offset] = _config.protocolContractsHash; offset += 1; // prover_id: id of current epoch's prover @@ -486,17 +497,20 @@ library EpochProofLib { * - Rollup__InvalidArchive: End archive root mismatch in public inputs * * @param _args The epoch proof submission arguments containing proof data and public inputs + * @param _config The rollup's deployment-time configuration * @return True if both blob proof and validity proof verification succeed */ - function verifyEpochRootProof(SubmitEpochRootProofArgs calldata _args) private view returns (bool) { - RollupStore storage rollupStore = STFLib.getStorage(); - + function verifyEpochRootProof(SubmitEpochRootProofArgs calldata _args, RollupConfig memory _config) + private + view + returns (bool) + { BlobLib.validateBatchedBlob(_args.blobInputs); bytes32[] memory publicInputs = - computeEpochProofPublicInputs(_args.start, _args.end, _args.args, _args.headers, _args.blobInputs); + computeEpochProofPublicInputs(_args.start, _args.end, _args.args, _args.headers, _args.blobInputs, _config); - require(rollupStore.config.epochProofVerifier.verify(_args.proof, publicInputs), Errors.Rollup__InvalidProof()); + require(_config.epochProofVerifier.verify(_args.proof, publicInputs), Errors.Rollup__InvalidProof()); return true; } diff --git a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol index 6df3377d8259..171eb206e72f 100644 --- a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol @@ -39,6 +39,17 @@ struct ProposePayload { bytes32 headerHash; } +/** + * @notice The caller-supplied context for a proposal, bundled to keep `propose` off the stack limit. + * @param inbox - The Inbox the header's streaming message consumption is validated against + * @param checkBlob - Whether to run blob related checks. Hardcoded to true in RollupCore, exists only to be + * overridden in tests + */ +struct ProposeConfig { + IInbox inbox; + bool checkBlob; +} + struct InterimProposeValues { ProposedHeader header; bytes32[] blobHashes; @@ -168,8 +179,7 @@ library ProposeLib { * @param _blobsInput - The bytes to verify our input blob commitments match real blobs: * - input[:1] - num blobs in checkpoint * - input[1:] - blob commitments (48 bytes * num blobs in checkpoint) - * @param _checkBlob - Whether to skip blob related checks. Hardcoded to true in RollupCore, exists only to be - * overridden in tests + * @param _config - The Inbox to validate message consumption against, and the blob check flag */ function propose( ProposeArgs calldata _args, @@ -177,7 +187,7 @@ library ProposeLib { address[] memory _signers, Signature calldata _attestationsAndSignersSignature, bytes calldata _blobsInput, - bool _checkBlob + ProposeConfig memory _config ) internal { // Prune unproven checkpoints if the proof submission window has passed if (STFLib.canPruneAtTime(Timestamp.wrap(block.timestamp))) { @@ -192,7 +202,7 @@ library ProposeLib { // Validate blob commitments against actual blob data and extract hashes // TODO(#13430): The below blobsHashesCommitment known as blobsHash elsewhere in the code. The name is confusingly // similar to blobCommitmentsHash, see comment in BlobLib.sol -> validateBlobs(). - (v.blobHashes, v.blobsHashesCommitment, v.blobCommitments) = BlobLib.validateBlobs(_blobsInput, _checkBlob); + (v.blobHashes, v.blobsHashesCommitment, v.blobCommitments) = BlobLib.validateBlobs(_blobsInput, _config.checkBlob); v.header = _args.header; @@ -269,7 +279,7 @@ library ProposeLib { // child validates against it and, since temp-log records rewind with the pending chain on a prune, the record // stays prune-consistent. v.consumedInboxMsgTotal = validateInboxConsumption( - rollupStore.config.inbox, + _config.inbox, v.header.inboxRollingHash, _args.bucketHint, v.header.slotNumber, diff --git a/l1-contracts/src/core/libraries/rollup/RewardExtLib.sol b/l1-contracts/src/core/libraries/rollup/RewardExtLib.sol index 8f62815cde84..2935dc485636 100644 --- a/l1-contracts/src/core/libraries/rollup/RewardExtLib.sol +++ b/l1-contracts/src/core/libraries/rollup/RewardExtLib.sol @@ -20,6 +20,7 @@ import { IValidatorSelection } from "@aztec/core/reward-boost/RewardBooster.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; library RewardExtLib { function initializeConfig(RewardConfig memory _config) external { @@ -30,12 +31,12 @@ library RewardExtLib { RewardLib.updateConfig(_config); } - function claimSequencerRewards(address _sequencer) external returns (uint256) { - return RewardLib.claimSequencerRewards(_sequencer); + function claimSequencerRewards(address _sequencer, IERC20 _feeAsset) external returns (uint256) { + return RewardLib.claimSequencerRewards(_sequencer, _feeAsset); } - function claimProverRewards(address _prover, Epoch[] memory _epochs) external returns (uint256) { - return RewardLib.claimProverRewards(_prover, _epochs); + function claimProverRewards(address _prover, Epoch[] memory _epochs, IERC20 _feeAsset) external returns (uint256) { + return RewardLib.claimProverRewards(_prover, _epochs, _feeAsset); } function deployRewardBooster(RewardBoostConfig memory _config) external returns (IBoosterCore) { diff --git a/l1-contracts/src/core/libraries/rollup/RewardLib.sol b/l1-contracts/src/core/libraries/rollup/RewardLib.sol index 9858551510aa..648a3e8111e5 100644 --- a/l1-contracts/src/core/libraries/rollup/RewardLib.sol +++ b/l1-contracts/src/core/libraries/rollup/RewardLib.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {RollupConfig, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {CompressedFeeHeader, FeeHeaderLib} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol"; @@ -105,22 +105,20 @@ library RewardLib { rewardStorage.config.checkpointReward = _config.checkpointReward; } - function claimSequencerRewards(address _sequencer) internal returns (uint256) { + function claimSequencerRewards(address _sequencer, IERC20 _feeAsset) internal returns (uint256) { RewardStorage storage rewardStorage = getStorage(); - RollupStore storage rollupStore = STFLib.getStorage(); uint256 amount = rewardStorage.sequencerRewards[_sequencer]; if (amount > 0) { rewardStorage.sequencerRewards[_sequencer] = 0; - rollupStore.config.feeAsset.safeTransfer(_sequencer, amount); + _feeAsset.safeTransfer(_sequencer, amount); } return amount; } - function claimProverRewards(address _prover, Epoch[] memory _epochs) internal returns (uint256) { + function claimProverRewards(address _prover, Epoch[] memory _epochs, IERC20 _feeAsset) internal returns (uint256) { Epoch currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp(); - RollupStore storage rollupStore = STFLib.getStorage(); RewardStorage storage rewardStorage = getStorage(); @@ -145,14 +143,15 @@ library RewardLib { } if (accumulatedRewards > 0) { - rollupStore.config.feeAsset.safeTransfer(_prover, accumulatedRewards); + _feeAsset.safeTransfer(_prover, accumulatedRewards); } return accumulatedRewards; } - function handleRewardsAndFees(SubmitEpochRootProofArgs calldata _args, Epoch _endEpoch) internal { - RollupStore storage rollupStore = STFLib.getStorage(); + function handleRewardsAndFees(SubmitEpochRootProofArgs calldata _args, Epoch _endEpoch, RollupConfig memory _config) + internal + { RewardStorage storage rewardStorage = getStorage(); uint256 length = _args.end - _args.start + 1; @@ -241,11 +240,11 @@ library RewardLib { $er.longestProvenLength = length.toUint128(); if (t.feesToClaim > 0) { - rollupStore.config.feeAssetPortal.distributeFees(address(this), t.feesToClaim); + _config.feeAssetPortal.distributeFees(address(this), t.feesToClaim); } if (t.totalBurn > 0) { - rollupStore.config.feeAsset.safeTransfer(BURN_ADDRESS, t.totalBurn); + _config.feeAsset.safeTransfer(BURN_ADDRESS, t.totalBurn); } } } diff --git a/l1-contracts/src/core/libraries/rollup/RollupOperationsExtLib.sol b/l1-contracts/src/core/libraries/rollup/RollupOperationsExtLib.sol index 8d3a5a5e93b2..925f4153dad5 100644 --- a/l1-contracts/src/core/libraries/rollup/RollupOperationsExtLib.sol +++ b/l1-contracts/src/core/libraries/rollup/RollupOperationsExtLib.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.27; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol"; import {Timestamp, TimeLib, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {BlobLib} from "@aztec-blob-lib/BlobLib.sol"; @@ -11,10 +12,14 @@ import {AttestationLib} from "@aztec/core/libraries/rollup/AttestationLib.sol"; import { ProposeLib, ProposeArgs, + ProposeConfig, CommitteeAttestations, ValidateHeaderArgs, ValidatorSelectionLib } from "./ProposeLib.sol"; +import {CheckpointHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; +import {FeeLib} from "@aztec/core/libraries/rollup/FeeLib.sol"; +import {ProposedHeader} from "./ProposedHeaderLib.sol"; import {Signature} from "@aztec/shared/libraries/SignatureLib.sol"; /** @@ -36,22 +41,39 @@ library RollupOperationsExtLib { using TimeLib for Slot; using AttestationLib for CommitteeAttestations; + /** + * @dev Assembles `ValidateHeaderArgs` here rather than in the Rollup: building that struct + * (which embeds a full `ProposedHeader`) in the Rollup's own code costs several hundred + * bytes of runtime bytecode it cannot spare. + */ function validateHeaderWithAttestations( - ValidateHeaderArgs calldata _args, + ProposedHeader calldata _header, CommitteeAttestations calldata _attestations, address[] calldata _signers, - Signature calldata _attestationsAndSignersSignature + Signature calldata _attestationsAndSignersSignature, + bytes32 _digest, + bytes32 _blobsHash, + CheckpointHeaderValidationFlags calldata _flags ) external { - ProposeLib.validateHeader(_args); + ProposeLib.validateHeader( + ValidateHeaderArgs({ + header: _header, + digest: _digest, + manaMinFee: FeeLib.summedMinFee(ProposeLib.getManaMinFeeComponentsAt(Timestamp.wrap(block.timestamp), true)), + blobsHashesCommitment: _blobsHash, + flags: _flags + }) + ); + if (_attestations.isEmpty()) { return; // No attestations to validate } - Slot slot = _args.header.slotNumber; + Slot slot = _header.slotNumber; Epoch epoch = slot.epochFromSlot(); - ValidatorSelectionLib.verifyAttestations(epoch, _attestations, _args.digest); + ValidatorSelectionLib.verifyAttestations(epoch, _attestations, _digest); ValidatorSelectionLib.verifyProposer( - slot, epoch, _attestations, _signers, _args.digest, _attestationsAndSignersSignature, false + slot, epoch, _attestations, _signers, _digest, _attestationsAndSignersSignature, false ); } @@ -61,9 +83,17 @@ library RollupOperationsExtLib { address[] calldata _signers, Signature calldata _attestationsAndSignersSignature, bytes calldata _blobInput, - bool _checkBlob + bool _checkBlob, + IInbox _inbox ) external { - ProposeLib.propose(_args, _attestations, _signers, _attestationsAndSignersSignature, _blobInput, _checkBlob); + ProposeLib.propose( + _args, + _attestations, + _signers, + _attestationsAndSignersSignature, + _blobInput, + ProposeConfig({inbox: _inbox, checkBlob: _checkBlob}) + ); } function prune() external { diff --git a/l1-contracts/src/core/libraries/rollup/STFLib.sol b/l1-contracts/src/core/libraries/rollup/STFLib.sol index 44f23b2ae05d..c7ce7f84a242 100644 --- a/l1-contracts/src/core/libraries/rollup/STFLib.sol +++ b/l1-contracts/src/core/libraries/rollup/STFLib.sol @@ -93,21 +93,15 @@ library STFLib { bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage"); /** - * @notice Initializes the rollup state with genesis configuration - * @dev Sets up the initial state of the rollup including verification keys and the genesis archive root. - * This function should only be called once during rollup deployment. + * @notice Writes the genesis archive root at checkpoint 0 + * @dev Should only be called once during rollup deployment. The remaining genesis fields + * (vkTreeRoot, protocolContractsHash) are held in the Rollup's immutables. * - * @param _genesisState The initial state configuration containing: - * - vkTreeRoot: Root of the verification key tree for circuit verification - * - protocolContractsHash: Root containing protocol contract addresses and configurations - * - genesisArchiveRoot: Initial archive root representing the genesis state + * @param _genesisState The initial state configuration; only `genesisArchiveRoot` is read here */ function initialize(GenesisState memory _genesisState) internal { RollupStore storage rollupStore = STFLib.getStorage(); - rollupStore.config.vkTreeRoot = _genesisState.vkTreeRoot; - rollupStore.config.protocolContractsHash = _genesisState.protocolContractsHash; - // The genesis archive root is decoded as an Fr off chain and propagates into the first header's lastArchiveRoot, // so it must be a valid field element. FieldLib.requireValidFieldElement(_genesisState.genesisArchiveRoot); diff --git a/l1-contracts/test/RollupWithPreheating.sol b/l1-contracts/test/RollupWithPreheating.sol index 1636a6ea0a5d..a41e269b5d33 100644 --- a/l1-contracts/test/RollupWithPreheating.sol +++ b/l1-contracts/test/RollupWithPreheating.sol @@ -7,7 +7,8 @@ import {IERC20} from "@aztec/core/interfaces/IRollup.sol"; import {IRollupCore} from "@aztec/core/interfaces/IRollup.sol"; import {GSE} from "@aztec/governance/GSE.sol"; import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; -import {STFLib, RollupStore, RollupCore} from "@aztec/core/RollupCore.sol"; +import {STFLib, RollupCore} from "@aztec/core/RollupCore.sol"; +import {RollupStore} from "@aztec/core/interfaces/IRollup.sol"; import {CompressedFeeHeader, FeeHeaderLib} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol"; import { CompressedTempCheckpointLogLib, diff --git a/l1-contracts/test/benchmark/PartialEpochProofGasReporter.sol b/l1-contracts/test/benchmark/PartialEpochProofGasReporter.sol index 14fc704eecc3..be7e58dcb0ca 100644 --- a/l1-contracts/test/benchmark/PartialEpochProofGasReporter.sol +++ b/l1-contracts/test/benchmark/PartialEpochProofGasReporter.sol @@ -5,12 +5,23 @@ pragma solidity >=0.8.27; import {RollupWithPreheating} from "../RollupWithPreheating.sol"; import {GenesisState, RollupConfigInput} from "@aztec/core/Rollup.sol"; -import {IERC20, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {IERC20, RollupConfig, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {EpochProofExtLib} from "@aztec/core/libraries/rollup/EpochProofExtLib.sol"; import {GSE} from "@aztec/governance/GSE.sol"; contract PartialEpochProofGasReporter is RollupWithPreheating { + // This contract's runtime code is etched onto a live Rollup and runs against that rollup's storage, but a + // rollup's config lives in its immutables, which are part of the code. The Inbox and Outbox this constructor + // deploys would therefore travel with the etched code and displace the live rollup's, so the live ones are + // passed in and substituted back below. + IInbox private immutable LIVE_INBOX; + IOutbox private immutable LIVE_OUTBOX; + IFeeJuicePortal private immutable LIVE_FEE_ASSET_PORTAL; + constructor( IERC20 _feeAsset, IERC20 _stakingAsset, @@ -18,41 +29,55 @@ contract PartialEpochProofGasReporter is RollupWithPreheating { IVerifier _epochProofVerifier, address _governance, GenesisState memory _genesisState, - RollupConfigInput memory _config - ) RollupWithPreheating(_feeAsset, _stakingAsset, _gse, _epochProofVerifier, _governance, _genesisState, _config) {} + RollupConfigInput memory _config, + IInbox _liveInbox, + IOutbox _liveOutbox + ) RollupWithPreheating(_feeAsset, _stakingAsset, _gse, _epochProofVerifier, _governance, _genesisState, _config) { + LIVE_INBOX = _liveInbox; + LIVE_OUTBOX = _liveOutbox; + LIVE_FEE_ASSET_PORTAL = IFeeJuicePortal(_liveInbox.getFeeAssetPortal()); + } /** * Reports submission gas for a fresh one-checkpoint epoch prefix. */ function gasReportSubmit1Checkpoint(SubmitEpochRootProofArgs calldata _args) external { - EpochProofExtLib.submitEpochRootProof(_args); + EpochProofExtLib.submitEpochRootProof(_args, _getRollupConfig()); } /** * Reports submission gas for a fresh eight-checkpoint epoch prefix. */ function gasReportSubmit8Checkpoints(SubmitEpochRootProofArgs calldata _args) external { - EpochProofExtLib.submitEpochRootProof(_args); + EpochProofExtLib.submitEpochRootProof(_args, _getRollupConfig()); } /** * Reports submission gas for checkpoints nine through sixteen after an eight-checkpoint prefix. */ function gasReportSubmit8MoreCheckpoints(SubmitEpochRootProofArgs calldata _args) external { - EpochProofExtLib.submitEpochRootProof(_args); + EpochProofExtLib.submitEpochRootProof(_args, _getRollupConfig()); } /** * Reports submission gas for a fresh sixteen-checkpoint epoch prefix. */ function gasReportSubmit16Checkpoints(SubmitEpochRootProofArgs calldata _args) external { - EpochProofExtLib.submitEpochRootProof(_args); + EpochProofExtLib.submitEpochRootProof(_args, _getRollupConfig()); } /** * Reports submission gas for a complete thirty-two-checkpoint epoch. */ function gasReportSubmit32Checkpoints(SubmitEpochRootProofArgs calldata _args) external { - EpochProofExtLib.submitEpochRootProof(_args); + EpochProofExtLib.submitEpochRootProof(_args, _getRollupConfig()); + } + + function _getRollupConfig() internal view override returns (RollupConfig memory) { + RollupConfig memory config = super._getRollupConfig(); + config.inbox = LIVE_INBOX; + config.outbox = LIVE_OUTBOX; + config.feeAssetPortal = LIVE_FEE_ASSET_PORTAL; + return config; } } diff --git a/l1-contracts/test/benchmark/happy.t.sol b/l1-contracts/test/benchmark/happy.t.sol index 6acdb09b7a0c..886e249c987c 100644 --- a/l1-contracts/test/benchmark/happy.t.sol +++ b/l1-contracts/test/benchmark/happy.t.sol @@ -221,7 +221,9 @@ abstract contract BenchmarkRollupBase is FeeModelTestPoints, DecoderBase { rollup.getEpochProofVerifier(), address(this), config.genesisState, - config.rollupConfigInput + config.rollupConfigInput, + rollup.getInbox(), + rollup.getOutbox() ); // Keep the initialized rollup storage while exposing named gas-report entrypoints. vm.etch(address(rollup), address(reporter).code); diff --git a/l1-contracts/test/rollup/libraries/rewardlib/RewardLibWrapper.sol b/l1-contracts/test/rollup/libraries/rewardlib/RewardLibWrapper.sol index 6f54d87b95c1..7685f822d0c1 100644 --- a/l1-contracts/test/rollup/libraries/rewardlib/RewardLibWrapper.sol +++ b/l1-contracts/test/rollup/libraries/rewardlib/RewardLibWrapper.sol @@ -7,7 +7,7 @@ import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {RewardBooster, IBoosterCore, RewardBoostConfig} from "@aztec/core/reward-boost/RewardBooster.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import {Bps} from "@aztec/core/libraries/rollup/RewardLib.sol"; -import {SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {SubmitEpochRootProofArgs, RollupConfig} from "@aztec/core/interfaces/IRollup.sol"; import {STFLib, RollupStore} from "@aztec/core/libraries/rollup/STFLib.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; @@ -63,6 +63,8 @@ contract RewardLibWrapper { RewardBooster internal booster; Epoch internal currentEpoch; + IERC20 internal immutable FEE_ASSET; + IFeeJuicePortal internal immutable FEE_ASSET_PORTAL; FakeRewardDistributor public rewardDistributor; FakeFeePortal public feePortal; @@ -83,9 +85,8 @@ contract RewardLibWrapper { RewardLib.initializeConfig(config); - RollupStore storage rollupStore = STFLib.getStorage(); - rollupStore.config.feeAsset = _feeAsset; - rollupStore.config.feeAssetPortal = IFeeJuicePortal(address(feePortal)); + FEE_ASSET = _feeAsset; + FEE_ASSET_PORTAL = IFeeJuicePortal(address(feePortal)); TimeLib.initialize( block.timestamp, @@ -129,13 +130,19 @@ contract RewardLibWrapper { } function handleRewardsAndFees(SubmitEpochRootProofArgs calldata _args, Epoch _endEpoch) external { - RewardLib.handleRewardsAndFees(_args, _endEpoch); + RewardLib.handleRewardsAndFees(_args, _endEpoch, _rollupConfig()); } function getSequencerRewards(address _sequencer) external view returns (uint256) { return RewardLib.getSequencerRewards(_sequencer); } + // Only the fields handleRewardsAndFees reads are populated; the rest stay zero. + function _rollupConfig() internal view returns (RollupConfig memory config) { + config.feeAsset = FEE_ASSET; + config.feeAssetPortal = FEE_ASSET_PORTAL; + } + function getCollectiveProverRewardsForEpoch(Epoch _epoch) external view returns (uint256) { return RewardLib.getCollectiveProverRewardsForEpoch(_epoch); } diff --git a/labs-patches/0014-refactor-ethereum-read-rollup-config-through-contrac.patch b/labs-patches/0014-refactor-ethereum-read-rollup-config-through-contrac.patch new file mode 100644 index 000000000000..7e40016947bd --- /dev/null +++ b/labs-patches/0014-refactor-ethereum-read-rollup-config-through-contrac.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Santiago Palladino +Date: Thu, 10 Sep 2026 12:07:06 -0300 +Subject: [PATCH] refactor(ethereum): read rollup config through contract + getters + +The rollup's deployment-time configuration moved out of `RollupStore.config` +storage into the Rollup's immutables, so the raw storage reads at +`stfStorageSlot + 3` and `+ 4` no longer hold the VK tree root and the protocol +contracts hash. Both getters call the contract instead. + +`Rollup`'s link references list `EpochProofExtLib` and no longer include +`ValidatorSelectionLib`, so `RollupArtifact` supplies the former's code and +drops the latter. A test pins the two sides together: every link reference the +compiler emitted has code to deploy, and no library is carried that the rollup +does not link against. + +diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts +index af4ac517775b024a0bc1fa86f9ac8de0d3a2a6f7..5b283b5b89f6c04b478285d1287b9c4b7c697411 100644 +--- a/yarn-project/ethereum/src/contracts/rollup.ts ++++ b/yarn-project/ethereum/src/contracts/rollup.ts +@@ -449,16 +449,12 @@ export class RollupContract { + + @memoize + async getVkTreeRoot(): Promise { +- const slot = BigInt(RollupContract.stfStorageSlot) + 3n; +- const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` }); +- return Fr.fromString(value ?? '0x0'); ++ return Fr.fromString(await this.rollup.read.getVkTreeRoot()); + } + + @memoize + async getProtocolContractsHash(): Promise { +- const slot = BigInt(RollupContract.stfStorageSlot) + 4n; +- const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` }); +- return Fr.fromString(value ?? '0x0'); ++ return Fr.fromString(await this.rollup.read.getProtocolContractsHash()); + } + + /** +diff --git a/yarn-project/ethereum/src/l1_artifacts.test.ts b/yarn-project/ethereum/src/l1_artifacts.test.ts +new file mode 100644 +index 0000000000000000000000000000000000000000..26de03ce230b10f98fb503a765240cc50a1bcdb6 +--- /dev/null ++++ b/yarn-project/ethereum/src/l1_artifacts.test.ts +@@ -0,0 +1,25 @@ ++import { RollupArtifact } from './l1_artifacts.js'; ++ ++describe('l1 artifacts', () => { ++ describe('RollupArtifact libraries', () => { ++ const { linkReferences, libraryCode } = RollupArtifact.libraries; ++ const linkedNames = Object.values(linkReferences).flatMap(refs => Object.keys(refs)); ++ ++ // The generic deployer deploys exactly the libraries in libraryCode and links exactly the names in ++ // linkReferences, so a name in one but not the other is either a deployment failure or a wasted deployment. ++ it('supplies code for every link reference the compiler emitted', () => { ++ expect(linkedNames.filter(name => !(name in libraryCode))).toEqual([]); ++ }); ++ ++ it('carries no library the rollup bytecode does not link against', () => { ++ expect(Object.keys(libraryCode).filter(name => !linkedNames.includes(name))).toEqual([]); ++ }); ++ ++ it('exports nonempty bytecode for every library', () => { ++ for (const [name, lib] of Object.entries(libraryCode)) { ++ expect(`${name}:${lib.contractBytecode.slice(0, 2)}`).toEqual(`${name}:0x`); ++ expect(lib.contractBytecode.length).toBeGreaterThan(2); ++ } ++ }); ++ }); ++}); +diff --git a/yarn-project/ethereum/src/l1_artifacts.ts b/yarn-project/ethereum/src/l1_artifacts.ts +index d404aac730012d52c0c095fdddbf6a816b8ec705..55bd45dec2c110b63582c94030afb790a22530ac 100644 +--- a/yarn-project/ethereum/src/l1_artifacts.ts ++++ b/yarn-project/ethereum/src/l1_artifacts.ts +@@ -3,6 +3,8 @@ import { + CoinIssuerBytecode, + DateGatedRelayerAbi, + DateGatedRelayerBytecode, ++ EpochProofExtLibAbi, ++ EpochProofExtLibBytecode, + FeeAssetHandlerAbi, + FeeAssetHandlerBytecode, + FeeJuicePortalAbi, +@@ -50,8 +52,6 @@ import { + TestERC20Bytecode, + ValidatorOperationsExtLibAbi, + ValidatorOperationsExtLibBytecode, +- ValidatorSelectionLibAbi, +- ValidatorSelectionLibBytecode, + } from '@aztec-foundation/l1-artifacts'; + + import type { Hex } from 'viem'; +@@ -81,10 +81,10 @@ export const RollupArtifact = { + libraries: { + linkReferences: RollupLinkReferences, + libraryCode: { +- ValidatorSelectionLib: { +- name: 'ValidatorSelectionLib', +- contractAbi: ValidatorSelectionLibAbi, +- contractBytecode: ValidatorSelectionLibBytecode as Hex, ++ EpochProofExtLib: { ++ name: 'EpochProofExtLib', ++ contractAbi: EpochProofExtLibAbi, ++ contractBytecode: EpochProofExtLibBytecode as Hex, + }, + RollupOperationsExtLib: { + name: 'RollupOperationsExtLib',