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',