From bd93b3697ec1fb4006ef4d707384d2dd02ff4426 Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:49:23 +0000 Subject: [PATCH 1/5] chore: coarse cleanup --- l1-contracts/src/core/Rollup.sol | 42 ++++++++++++------- l1-contracts/src/core/RollupCore.sol | 42 ++++++++++--------- .../src/core/libraries/RollupLibs/BlobLib.sol | 8 ++-- .../libraries/RollupLibs/ExtRollupLib.sol | 4 +- .../src/core/libraries/RollupLibs/STFLib.sol | 29 +++++++++++++ 5 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 l1-contracts/src/core/libraries/RollupLibs/STFLib.sol diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 841943579620..44bade383a09 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -40,7 +40,9 @@ import { EpochRewards, FeeAssetPerEthE9, EthValue, - PriceLib + PriceLib, + STFLib, + RollupStore } from "./RollupCore.sol"; // solhint-enable no-unused-import @@ -121,7 +123,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { } function getTips() external view override(IRollup) returns (ChainTips memory) { - return rollupStore.tips; + return STFLib.getStorage().tips; } function status(uint256 _myHeaderBlockNumber) @@ -137,6 +139,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { Epoch provenEpochNumber ) { + RollupStore storage rollupStore = STFLib.getStorage(); return ( rollupStore.tips.provenBlockNumber, rollupStore.blocks[rollupStore.tips.provenBlockNumber].archive, @@ -169,7 +172,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { bytes calldata _aggregationObject ) external view override(IRollup) returns (bytes32[] memory) { return ExtRollupLib.getEpochProofPublicInputs( - rollupStore, _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject + STFLib.getStorage(), _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject ); } @@ -224,18 +227,20 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { * @return bytes32 - The current archive root */ function archive() external view override(IRollup) returns (bytes32) { + RollupStore storage rollupStore = STFLib.getStorage(); return rollupStore.blocks[rollupStore.tips.pendingBlockNumber].archive; } function getProvenBlockNumber() external view override(IRollup) returns (uint256) { - return rollupStore.tips.provenBlockNumber; + return STFLib.getStorage().tips.provenBlockNumber; } function getPendingBlockNumber() external view override(IRollup) returns (uint256) { - return rollupStore.tips.pendingBlockNumber; + return STFLib.getStorage().tips.pendingBlockNumber; } function getBlock(uint256 _blockNumber) external view override(IRollup) returns (BlockLog memory) { + RollupStore storage rollupStore = STFLib.getStorage(); require( _blockNumber <= rollupStore.tips.pendingBlockNumber, Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) @@ -249,7 +254,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (bytes32) { - return rollupStore.blobPublicInputsHashes[_blockNumber]; + return STFLib.getStorage().blobPublicInputsHashes[_blockNumber]; } function getProposerForAttester(address _attester) @@ -449,7 +454,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (uint256) { - return rollupStore.sequencerRewards[_sequencer]; + return STFLib.getStorage().sequencerRewards[_sequencer]; } function getCollectiveProverRewardsForEpoch(Epoch _epoch) @@ -458,7 +463,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (uint256) { - return rollupStore.epochRewards[_epoch].rewards; + return STFLib.getStorage().epochRewards[_epoch].rewards; } /** @@ -477,6 +482,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (uint256) { + RollupStore storage rollupStore = STFLib.getStorage(); if (rollupStore.proverClaimed[_prover][_epoch]) { return 0; } @@ -497,11 +503,11 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (bool) { - return rollupStore.epochRewards[_epoch].subEpoch[_length].hasSubmitted[_prover]; + return STFLib.getStorage().epochRewards[_epoch].subEpoch[_length].hasSubmitted[_prover]; } function getProvingCostPerManaInEth() external view override(IRollup) returns (EthValue) { - return rollupStore.provingCostPerMana; + return STFLib.getStorage().provingCostPerMana; } function getProvingCostPerManaInFeeAsset() @@ -510,7 +516,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (FeeAssetValue) { - return rollupStore.provingCostPerMana.toFeeAsset(getFeeAssetPerEth()); + return STFLib.getStorage().provingCostPerMana.toFeeAsset(getFeeAssetPerEth()); } /** @@ -529,18 +535,21 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { returns (Slot, uint256) { Slot slot = _ts.slotFromTimestamp(); + RollupStore storage rollupStore = STFLib.getStorage(); // Consider if a prune will hit in this slot uint256 pendingBlockNumber = canPruneAtTime(_ts) ? rollupStore.tips.provenBlockNumber : rollupStore.tips.pendingBlockNumber; - Slot lastSlot = rollupStore.blocks[pendingBlockNumber].slotNumber; + { + Slot lastSlot = rollupStore.blocks[pendingBlockNumber].slotNumber; - require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); + require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); - // Make sure that the proposer is up to date and on the right chain (ie no reorgs) - bytes32 tipArchive = rollupStore.blocks[pendingBlockNumber].archive; - require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive)); + // Make sure that the proposer is up to date and on the right chain (ie no reorgs) + bytes32 tipArchive = rollupStore.blocks[pendingBlockNumber].archive; + require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive)); + } Signature[] memory sigs = new Signature[](0); DataStructures.ExecutionFlags memory flags = @@ -578,6 +587,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { * @return bytes32 - The archive root of the block */ function archiveAt(uint256 _blockNumber) public view override(IRollup) returns (bytes32) { + RollupStore storage rollupStore = STFLib.getStorage(); return _blockNumber <= rollupStore.tips.pendingBlockNumber ? rollupStore.blocks[_blockNumber].archive : bytes32(0); diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 66218c04d892..14e12faff40e 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -48,6 +48,7 @@ import {Ownable} from "@oz/access/Ownable.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; import {Math} from "@oz/utils/math/Math.sol"; +import {STFLib} from "@aztec/core/libraries/RollupLibs/STFLib.sol"; struct Config { uint256 aztecSlotDuration; @@ -111,9 +112,6 @@ contract RollupCore is // such as sacrificial hearts, during rituals performed within temples. address public constant CUAUHXICALLI = address(bytes20("CUAUHXICALLI")); - address public constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); - bool public immutable IS_FOUNDRY_TEST; - // The number of slots, measured from the beginning on an epoch, that a proof will be accepted within. uint256 internal immutable PROOF_SUBMISSION_WINDOW; @@ -131,8 +129,6 @@ contract RollupCore is // @note Always true, exists to override to false for testing only bool public checkBlob = true; - RollupStore internal rollupStore; - constructor( IFeeJuicePortal _fpcJuicePortal, IRewardDistributor _rewardDistributor, @@ -161,7 +157,7 @@ contract RollupCore is VERSION = 1; L1_BLOCK_AT_GENESIS = block.number; - IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; + RollupStore storage rollupStore = STFLib.getStorage(); rollupStore.epochProofVerifier = new MockVerifier(); rollupStore.vkTreeRoot = _vkTreeRoot; @@ -183,7 +179,7 @@ contract RollupCore is }); rollupStore.l1GasOracleValues = L1GasOracleValues({ pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), - post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee(VM_ADDRESS)}), + post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee()}), slotOfChange: LIFETIME }); } @@ -193,7 +189,7 @@ contract RollupCore is override(IRollupCore) onlyOwner { - rollupStore.provingCostPerMana = _provingCostPerMana; + STFLib.getStorage().provingCostPerMana = _provingCostPerMana; } function claimSequencerRewards(address _recipient) @@ -201,6 +197,7 @@ contract RollupCore is override(IRollupCore) returns (uint256) { + RollupStore storage rollupStore = STFLib.getStorage(); uint256 amount = rollupStore.sequencerRewards[msg.sender]; rollupStore.sequencerRewards[msg.sender] = 0; ASSET.transfer(_recipient, amount); @@ -219,6 +216,8 @@ contract RollupCore is Slot deadline = _epochs[i].toSlots() + Slot.wrap(PROOF_SUBMISSION_WINDOW); require(deadline < currentSlot, Errors.Rollup__NotPastDeadline(deadline, currentSlot)); + RollupStore storage rollupStore = STFLib.getStorage(); + // We can use fancier bitmaps for performance require( !rollupStore.proverClaimed[msg.sender][_epochs[i]], @@ -293,7 +292,7 @@ contract RollupCore is * @param _verifier - The new verifier contract */ function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner { - rollupStore.epochProofVerifier = IVerifier(_verifier); + STFLib.getStorage().epochProofVerifier = IVerifier(_verifier); } /** @@ -304,7 +303,7 @@ contract RollupCore is * @param _vkTreeRoot - The new vkTreeRoot to be used by proofs */ function setVkTreeRoot(bytes32 _vkTreeRoot) external override(ITestRollup) onlyOwner { - rollupStore.vkTreeRoot = _vkTreeRoot; + STFLib.getStorage().vkTreeRoot = _vkTreeRoot; } /** @@ -319,7 +318,7 @@ contract RollupCore is override(ITestRollup) onlyOwner { - rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + STFLib.getStorage().protocolContractTreeRoot = _protocolContractTreeRoot; } /** @@ -378,6 +377,8 @@ contract RollupCore is bool isStartOfEpoch = _args.start == 1 || parentEpoch <= startEpoch - Epoch.wrap(1); require(isStartOfEpoch, Errors.Rollup__StartIsNotFirstBlockOfEpoch()); + RollupStore storage rollupStore = STFLib.getStorage(); + bool isStartBuildingOnProven = _args.start - 1 <= rollupStore.tips.provenBlockNumber; require(isStartBuildingOnProven, Errors.Rollup__StartIsNotBuildingOnProven()); @@ -441,8 +442,6 @@ contract RollupCore is FEE_JUICE_PORTAL.distributeFees(address(this), interim.feesToClaim); } - // @todo Get the block rewards for - if (interim.totalBurn > 0 && interim.isFeeCanonical) { ASSET.transfer(CUAUHXICALLI, interim.totalBurn); } @@ -507,6 +506,7 @@ contract RollupCore is _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) }); + RollupStore storage rollupStore = STFLib.getStorage(); uint256 blockNumber = ++rollupStore.tips.pendingBlockNumber; { @@ -541,6 +541,7 @@ contract RollupCore is function updateL1GasFeeOracle() public override(IRollupCore) { Slot slot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); // The slot where we find a new queued value acceptable + RollupStore storage rollupStore = STFLib.getStorage(); Slot acceptableSlot = rollupStore.l1GasOracleValues.slotOfChange + (LIFETIME - LAG); if (slot < acceptableSlot) { @@ -549,7 +550,7 @@ contract RollupCore is rollupStore.l1GasOracleValues.pre = rollupStore.l1GasOracleValues.post; rollupStore.l1GasOracleValues.post = - L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee(VM_ADDRESS)}); + L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee()}); rollupStore.l1GasOracleValues.slotOfChange = slot + LAG; } @@ -559,6 +560,7 @@ contract RollupCore is * @return The fee asset price */ function getFeeAssetPerEth() public view override(IRollupCore) returns (FeeAssetPerEthE9) { + RollupStore storage rollupStore = STFLib.getStorage(); return IntRollupLib.getFeeAssetPerEth( rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator ); @@ -570,6 +572,7 @@ contract RollupCore is override(IRollupCore) returns (L1FeeData memory) { + RollupStore storage rollupStore = STFLib.getStorage(); return _timestamp.slotFromTimestamp() < rollupStore.l1GasOracleValues.slotOfChange ? rollupStore.l1GasOracleValues.pre : rollupStore.l1GasOracleValues.post; @@ -593,6 +596,7 @@ contract RollupCore is override(ITestRollup) returns (ManaBaseFeeComponents memory) { + RollupStore storage rollupStore = STFLib.getStorage(); // If we can prune, we use the proven block, otherwise the pending block uint256 blockOfInterest = canPruneAtTime(_timestamp) ? rollupStore.tips.provenBlockNumber @@ -608,11 +612,7 @@ contract RollupCore is } function getEpochForBlock(uint256 _blockNumber) public view override(IRollupCore) returns (Epoch) { - require( - _blockNumber <= rollupStore.tips.pendingBlockNumber, - Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) - ); - return rollupStore.blocks[_blockNumber].slotNumber.epochFromSlot(); + return STFLib.getEpochForBlock(_blockNumber); } function canPrune() public view override(IRollupCore) returns (bool) { @@ -620,6 +620,7 @@ contract RollupCore is } function canPruneAtTime(Timestamp _ts) public view override(IRollupCore) returns (bool) { + RollupStore storage rollupStore = STFLib.getStorage(); if (rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber) { return false; } @@ -631,6 +632,7 @@ contract RollupCore is } function _prune() internal { + RollupStore storage rollupStore = STFLib.getStorage(); uint256 pending = rollupStore.tips.pendingBlockNumber; // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. @@ -662,6 +664,7 @@ contract RollupCore is bytes32 _blobsHashesCommitment, DataStructures.ExecutionFlags memory _flags ) internal view { + RollupStore storage rollupStore = STFLib.getStorage(); uint256 pendingBlockNumber = canPruneAtTime(_currentTime) ? rollupStore.tips.provenBlockNumber : rollupStore.tips.pendingBlockNumber; @@ -732,6 +735,7 @@ contract RollupCore is uint256 _congestionCost, uint256 _provingCost ) internal view returns (BlockLog memory) { + RollupStore storage rollupStore = STFLib.getStorage(); FeeHeader memory parentFeeHeader = rollupStore.blocks[_blockNumber - 1].feeHeader; return BlockLog({ archive: _args.archive, diff --git a/l1-contracts/src/core/libraries/RollupLibs/BlobLib.sol b/l1-contracts/src/core/libraries/RollupLibs/BlobLib.sol index 9d51f35b12d2..4e48e4878eed 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/BlobLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/BlobLib.sol @@ -8,6 +8,8 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Vm} from "forge-std/Vm.sol"; library BlobLib { + address public constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + /** * @notice Get the blob base fee * @@ -16,9 +18,9 @@ library BlobLib { * * @return uint256 - The blob base fee */ - function getBlobBaseFee(address _vmAddress) internal view returns (uint256) { - if (_vmAddress.code.length > 0) { - return Vm(_vmAddress).getBlobBaseFee(); + function getBlobBaseFee() internal view returns (uint256) { + if (VM_ADDRESS.code.length > 0) { + return Vm(VM_ADDRESS).getBlobBaseFee(); } return block.blobbasefee; } diff --git a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol index a5bbadbb88fd..1cb88be5d6f0 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -73,8 +73,8 @@ library ExtRollupLib { return BlobLib.validateBlobs(_blobsInput, _checkBlob); } - function getBlobBaseFee(address _vmAddress) external view returns (uint256) { - return BlobLib.getBlobBaseFee(_vmAddress); + function getBlobBaseFee() external view returns (uint256) { + return BlobLib.getBlobBaseFee(); } function decodeHeader(bytes calldata _header) external pure returns (Header memory) { diff --git a/l1-contracts/src/core/libraries/RollupLibs/STFLib.sol b/l1-contracts/src/core/libraries/RollupLibs/STFLib.sol new file mode 100644 index 000000000000..4c03504e0093 --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/STFLib.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {RollupStore} from "@aztec/core/interfaces/IRollup.sol"; +import {Epoch, Slot, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; + +library STFLib { + using TimeLib for Slot; + + bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage"); + + function getEpochForBlock(uint256 _blockNumber) internal view returns (Epoch) { + RollupStore storage rollupStore = STFLib.getStorage(); + require( + _blockNumber <= rollupStore.tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) + ); + return rollupStore.blocks[_blockNumber].slotNumber.epochFromSlot(); + } + + function getStorage() internal pure returns (RollupStore storage storageStruct) { + bytes32 position = STF_STORAGE_POSITION; + assembly { + storageStruct.slot := position + } + } +} From b12302083428d503e3293f2431814413448cb6b6 Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Mon, 17 Feb 2025 23:14:33 +0000 Subject: [PATCH 2/5] chore: create state transition function lib + move submit into lib --- l1-contracts/src/core/Rollup.sol | 21 +- l1-contracts/src/core/RollupCore.sol | 182 ++--------- l1-contracts/src/core/interfaces/IRollup.sol | 26 +- .../libraries/RollupLibs/EpochProofLib.sol | 287 +++++++++++++----- .../libraries/RollupLibs/ExtRollupLib.sol | 16 +- .../src/core/libraries/RollupLibs/STFLib.sol | 29 -- .../core/libraries/RollupLibs/core/STFLib.sol | 57 ++++ l1-contracts/test/fees/FeeRollup.t.sol | 6 +- yarn-project/ethereum/src/contracts/rollup.ts | 6 +- 9 files changed, 350 insertions(+), 280 deletions(-) delete mode 100644 l1-contracts/src/core/libraries/RollupLibs/STFLib.sol create mode 100644 l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 44bade383a09..f81b69ee08e3 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -44,6 +44,7 @@ import { STFLib, RollupStore } from "./RollupCore.sol"; +import {EpochProofLib} from "./libraries/RollupLibs/EpochProofLib.sol"; // solhint-enable no-unused-import /** @@ -172,7 +173,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { bytes calldata _aggregationObject ) external view override(IRollup) returns (bytes32[] memory) { return ExtRollupLib.getEpochProofPublicInputs( - STFLib.getStorage(), _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject + _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject ); } @@ -445,7 +446,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { } function getProofSubmissionWindow() external view override(IRollup) returns (uint256) { - return PROOF_SUBMISSION_WINDOW; + return STFLib.getStorage().config.proofSubmissionWindow; } function getSequencerRewards(address _sequencer) @@ -519,6 +520,22 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return STFLib.getStorage().provingCostPerMana.toFeeAsset(getFeeAssetPerEth()); } + function getCuauhxicalli() external view override(IRollup) returns (address) { + return EpochProofLib.CUAUHXICALLI; + } + + function getFeeAsset() external view override(IRollup) returns (IERC20) { + return STFLib.getStorage().config.feeAsset; + } + + function getFeeAssetPortal() external view override(IRollup) returns (IFeeJuicePortal) { + return STFLib.getStorage().config.feeAssetPortal; + } + + function getRewardDistributor() external view override(IRollup) returns (IRewardDistributor) { + return STFLib.getStorage().config.rewardDistributor; + } + /** * @notice Check if msg.sender can propose at a given time * diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 14e12faff40e..799fa30ca723 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -48,7 +48,7 @@ import {Ownable} from "@oz/access/Ownable.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; import {Math} from "@oz/utils/math/Math.sol"; -import {STFLib} from "@aztec/core/libraries/RollupLibs/STFLib.sol"; +import {STFLib} from "@aztec/core/libraries/RollupLibs/core/STFLib.sol"; struct Config { uint256 aztecSlotDuration; @@ -108,22 +108,12 @@ contract RollupCore is Slot public constant LIFETIME = Slot.wrap(5); Slot public constant LAG = Slot.wrap(2); - // A Cuauhxicalli [kʷaːʍʃiˈkalːi] ("eagle gourd bowl") is a ceremonial Aztec vessel or altar used to hold offerings, - // such as sacrificial hearts, during rituals performed within temples. - address public constant CUAUHXICALLI = address(bytes20("CUAUHXICALLI")); - - // The number of slots, measured from the beginning on an epoch, that a proof will be accepted within. - uint256 internal immutable PROOF_SUBMISSION_WINDOW; - uint256 public immutable L1_BLOCK_AT_GENESIS; IInbox public immutable INBOX; IOutbox public immutable OUTBOX; uint256 public immutable VERSION; - IFeeJuicePortal public immutable FEE_JUICE_PORTAL; - IRewardDistributor public immutable REWARD_DISTRIBUTOR; - IERC20 public immutable ASSET; - // To push checkblock into its own slot so we don't have the trouble of being in the middle of a slot + // To push checkBlob into its own slot so we don't have the trouble of being in the middle of a slot uint256 private gap = 0; // @note Always true, exists to override to false for testing only @@ -142,16 +132,11 @@ contract RollupCore is ) Ownable(_ares) { TimeLib.initialize(block.timestamp, _config.aztecSlotDuration, _config.aztecEpochDuration); - PROOF_SUBMISSION_WINDOW = _config.aztecProofSubmissionWindow; - Timestamp exitDelay = Timestamp.wrap(60 * 60 * 24); Slasher slasher = new Slasher(_config.slashingQuorum, _config.slashingRoundSize); StakingLib.initialize(_stakingAsset, _config.minimumStake, exitDelay, address(slasher)); ValidatorSelectionLib.initialize(_config.targetCommitteeSize); - FEE_JUICE_PORTAL = _fpcJuicePortal; - REWARD_DISTRIBUTOR = _rewardDistributor; - ASSET = _fpcJuicePortal.UNDERLYING(); INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); OUTBOX = IOutbox(address(new Outbox(address(this)))); VERSION = 1; @@ -159,9 +144,15 @@ contract RollupCore is RollupStore storage rollupStore = STFLib.getStorage(); - rollupStore.epochProofVerifier = new MockVerifier(); - rollupStore.vkTreeRoot = _vkTreeRoot; - rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + rollupStore.config.proofSubmissionWindow = _config.aztecProofSubmissionWindow; + rollupStore.config.feeAsset = _fpcJuicePortal.UNDERLYING(); + rollupStore.config.feeAssetPortal = _fpcJuicePortal; + rollupStore.config.rewardDistributor = _rewardDistributor; + + rollupStore.config.epochProofVerifier = new MockVerifier(); + rollupStore.config.vkTreeRoot = _vkTreeRoot; + rollupStore.config.protocolContractTreeRoot = _protocolContractTreeRoot; + rollupStore.provingCostPerMana = EthValue.wrap(100); // Genesis block @@ -200,7 +191,7 @@ contract RollupCore is RollupStore storage rollupStore = STFLib.getStorage(); uint256 amount = rollupStore.sequencerRewards[msg.sender]; rollupStore.sequencerRewards[msg.sender] = 0; - ASSET.transfer(_recipient, amount); + rollupStore.config.feeAsset.transfer(_recipient, amount); return amount; } @@ -211,13 +202,14 @@ contract RollupCore is returns (uint256) { Slot currentSlot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); + RollupStore storage rollupStore = STFLib.getStorage(); + uint256 proofSubmissionWindow = rollupStore.config.proofSubmissionWindow; + uint256 accumulatedRewards = 0; for (uint256 i = 0; i < _epochs.length; i++) { - Slot deadline = _epochs[i].toSlots() + Slot.wrap(PROOF_SUBMISSION_WINDOW); + Slot deadline = _epochs[i].toSlots() + Slot.wrap(proofSubmissionWindow); require(deadline < currentSlot, Errors.Rollup__NotPastDeadline(deadline, currentSlot)); - RollupStore storage rollupStore = STFLib.getStorage(); - // We can use fancier bitmaps for performance require( !rollupStore.proverClaimed[msg.sender][_epochs[i]], @@ -231,7 +223,7 @@ contract RollupCore is } } - ASSET.transfer(_recipient, accumulatedRewards); + rollupStore.config.feeAsset.transfer(_recipient, accumulatedRewards); return accumulatedRewards; } @@ -281,7 +273,7 @@ contract RollupCore is */ function prune() external override(IRollupCore) { require(canPrune(), Errors.Rollup__NothingToPrune()); - _prune(); + STFLib.prune(); } /** @@ -292,7 +284,7 @@ contract RollupCore is * @param _verifier - The new verifier contract */ function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner { - STFLib.getStorage().epochProofVerifier = IVerifier(_verifier); + STFLib.getStorage().config.epochProofVerifier = IVerifier(_verifier); } /** @@ -303,7 +295,7 @@ contract RollupCore is * @param _vkTreeRoot - The new vkTreeRoot to be used by proofs */ function setVkTreeRoot(bytes32 _vkTreeRoot) external override(ITestRollup) onlyOwner { - STFLib.getStorage().vkTreeRoot = _vkTreeRoot; + STFLib.getStorage().config.vkTreeRoot = _vkTreeRoot; } /** @@ -318,7 +310,7 @@ contract RollupCore is override(ITestRollup) onlyOwner { - STFLib.getStorage().protocolContractTreeRoot = _protocolContractTreeRoot; + STFLib.getStorage().config.protocolContractTreeRoot = _protocolContractTreeRoot; } /** @@ -347,110 +339,7 @@ contract RollupCore is external override(IRollupCore) { - if (canPrune()) { - _prune(); - } - - SubmitProofInterim memory interim; - - // Start of `isAcceptable` - Epoch startEpoch = getEpochForBlock(_args.start); - // This also checks for existence of the block. - Epoch endEpoch = getEpochForBlock(_args.end); - - require(startEpoch == endEpoch, Errors.Rollup__StartAndEndNotSameEpoch(startEpoch, endEpoch)); - - interim.deadline = startEpoch.toSlots() + Slot.wrap(PROOF_SUBMISSION_WINDOW); - require( - interim.deadline >= Timestamp.wrap(block.timestamp).slotFromTimestamp(), - Errors.Rollup__PastDeadline( - interim.deadline, Timestamp.wrap(block.timestamp).slotFromTimestamp() - ) - ); - - // By making sure that the previous block is in another epoch, we know that we were - // at the start. - Epoch parentEpoch = getEpochForBlock(_args.start - 1); - - require(startEpoch > Epoch.wrap(0) || _args.start == 1, "invalid first epoch proof"); - - bool isStartOfEpoch = _args.start == 1 || parentEpoch <= startEpoch - Epoch.wrap(1); - require(isStartOfEpoch, Errors.Rollup__StartIsNotFirstBlockOfEpoch()); - - RollupStore storage rollupStore = STFLib.getStorage(); - - bool isStartBuildingOnProven = _args.start - 1 <= rollupStore.tips.provenBlockNumber; - require(isStartBuildingOnProven, Errors.Rollup__StartIsNotBuildingOnProven()); - - // End of `isAcceptable` - - // Start of verifying the proof - require(ExtRollupLib.verifyEpochRootProof(rollupStore, _args), "proof is invalid"); - // End of verifying the proof - - interim.isFeeCanonical = address(this) == FEE_JUICE_PORTAL.canonicalRollup(); - interim.isRewardDistributorCanonical = address(this) == REWARD_DISTRIBUTOR.canonicalRollup(); - - // Mark that the prover has submitted a proof - // Only do this if we are canonical for both fees and block rewards. - if (interim.isFeeCanonical && interim.isRewardDistributorCanonical) { - interim.prover = address(bytes20(_args.args[6] << 96)); // The address is left padded within the bytes32 - - interim.length = _args.end - _args.start + 1; - EpochRewards storage er = rollupStore.epochRewards[endEpoch]; - SubEpochRewards storage sr = er.subEpoch[interim.length]; - sr.summedCount += 1; - - // Using the prover id to ensure proof only gets added once - require(!sr.hasSubmitted[interim.prover], "go away"); - sr.hasSubmitted[interim.prover] = true; - - if (interim.length > er.longestProvenLength) { - interim.added = interim.length - er.longestProvenLength; - interim.blockRewardsAvailable = interim.isRewardDistributorCanonical - ? REWARD_DISTRIBUTOR.claimBlockRewards(address(this), interim.added) - : 0; - interim.sequencerShare = interim.blockRewardsAvailable / 2; - interim.blockRewardSequencer = interim.sequencerShare / interim.added; - interim.blockRewardProver = interim.blockRewardsAvailable - interim.sequencerShare; - - for (uint256 i = er.longestProvenLength; i < interim.length; i++) { - FeeHeader storage feeHeader = rollupStore.blocks[_args.start + i].feeHeader; - - (interim.fee, interim.burn) = interim.isFeeCanonical - ? (uint256(_args.fees[1 + i * 2]), feeHeader.congestionCost * feeHeader.manaUsed) - : (0, 0); - - interim.feesToClaim += interim.fee; - interim.fee -= interim.burn; - interim.totalBurn += interim.burn; - - // Compute the proving fee in the fee asset - interim.proverFee = Math.min(feeHeader.manaUsed * feeHeader.provingCost, interim.fee); - interim.fee -= interim.proverFee; - - er.rewards += interim.proverFee; - // The address is left padded within the bytes32 - rollupStore.sequencerRewards[address(bytes20(_args.fees[i * 2] << 96))] += - (interim.blockRewardSequencer + interim.fee); - } - - er.rewards += interim.blockRewardProver; - - er.longestProvenLength = interim.length; - - FEE_JUICE_PORTAL.distributeFees(address(this), interim.feesToClaim); - } - - if (interim.totalBurn > 0 && interim.isFeeCanonical) { - ASSET.transfer(CUAUHXICALLI, interim.totalBurn); - } - } - - // Update the proven block number - rollupStore.tips.provenBlockNumber = Math.max(rollupStore.tips.provenBlockNumber, _args.end); - - emit L2ProofVerified(_args.end, _args.args[6]); + ExtRollupLib.submitEpochRootProof(_args); } function setupEpoch() public override(IValidatorSelectionCore) { @@ -475,7 +364,7 @@ contract RollupCore is bytes calldata _blobInput ) public override(IRollupCore) { if (canPrune()) { - _prune(); + STFLib.prune(); } updateL1GasFeeOracle(); @@ -620,28 +509,7 @@ contract RollupCore is } function canPruneAtTime(Timestamp _ts) public view override(IRollupCore) returns (bool) { - RollupStore storage rollupStore = STFLib.getStorage(); - if (rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber) { - return false; - } - - Epoch oldestPendingEpoch = getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); - Slot deadline = oldestPendingEpoch.toSlots() + Slot.wrap(PROOF_SUBMISSION_WINDOW); - - return deadline < _ts.slotFromTimestamp(); - } - - function _prune() internal { - RollupStore storage rollupStore = STFLib.getStorage(); - uint256 pending = rollupStore.tips.pendingBlockNumber; - - // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. - // We can do because any new block proposed will overwrite a previous block in the block log, - // so no values should "survive". - // People must therefore read the chain using the pendingTip as a boundary. - rollupStore.tips.pendingBlockNumber = rollupStore.tips.provenBlockNumber; - - emit PrunedPending(rollupStore.tips.provenBlockNumber, pending); + return STFLib.canPruneAtTime(_ts); } /** @@ -678,7 +546,7 @@ contract RollupCore is pendingBlockNumber: pendingBlockNumber, flags: _flags, version: VERSION, - feeJuicePortal: FEE_JUICE_PORTAL + feeJuicePortal: rollupStore.config.feeAssetPortal }), rollupStore.blocks ); diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 54e6471295c7..51969b501951 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -15,6 +15,9 @@ import { } from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import {ProposeArgs} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; struct SubmitEpochRootProofArgs { uint256 start; // inclusive @@ -55,22 +58,32 @@ struct EpochRewards { mapping(uint256 length => SubEpochRewards) subEpoch; } +// @todo Ideally we should pull these from the code for immutable values +// to save gas. Consider using constants or more fancy deployments. +struct RollupConfig { + uint256 proofSubmissionWindow; + IERC20 feeAsset; + IFeeJuicePortal feeAssetPortal; + IRewardDistributor rewardDistributor; + bytes32 vkTreeRoot; + bytes32 protocolContractTreeRoot; + IVerifier epochProofVerifier; +} + // The below blobPublicInputsHashes are filled when proposing a block, then used to verify an epoch proof. // TODO(#8955): When implementing batched kzg proofs, store one instance per epoch rather than block struct RollupStore { ChainTips tips; // put first such that the struct slot structure is easy to follow for cheatcodes mapping(uint256 blockNumber => BlockLog log) blocks; mapping(uint256 blockNumber => bytes32) blobPublicInputsHashes; - bytes32 vkTreeRoot; - bytes32 protocolContractTreeRoot; L1GasOracleValues l1GasOracleValues; - IVerifier epochProofVerifier; + EthValue provingCostPerMana; mapping(address => uint256) sequencerRewards; mapping(Epoch => EpochRewards) epochRewards; // @todo Below can be optimised with a bitmap as we can benefit from provers likely proving for epochs close // to one another. mapping(address prover => mapping(Epoch epoch => bool claimed)) proverClaimed; - EthValue provingCostPerMana; + RollupConfig config; } struct CheatDepositArgs { @@ -200,4 +213,9 @@ interface IRollup is IRollupCore { function getProvingCostPerManaInEth() external view returns (EthValue); function getProvingCostPerManaInFeeAsset() external view returns (FeeAssetValue); + + function getFeeAsset() external view returns (IERC20); + function getFeeAssetPortal() external view returns (IFeeJuicePortal); + function getRewardDistributor() external view returns (IRewardDistributor); + function getCuauhxicalli() external view returns (address); } diff --git a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol index 3c65d312507b..4b27e76ffa0f 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol @@ -2,69 +2,65 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import { + SubmitEpochRootProofArgs, + IRollupCore, + EpochRewards, + SubEpochRewards +} from "@aztec/core/interfaces/IRollup.sol"; + +import {STFLib, RollupStore} from "@aztec/core/libraries/RollupLibs/core/STFLib.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {FeeHeader} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; + +import {Math} from "@oz/utils/math/Math.sol"; + import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; -import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; -struct SubmitEpochRootProofAddresses { - IFeeJuicePortal feeJuicePortal; - IRewardDistributor rewardDistributor; - IERC20 asset; - address cuauhxicalli; -} - -struct SubmitEpochRootProofInterimValues { - uint256 previousBlockNumber; - uint256 endBlockNumber; - Epoch epochToProve; - Epoch startEpoch; - bool isFeeCanonical; - bool isRewardDistributorCanonical; - uint256 totalProverReward; - uint256 totalBurn; -} - library EpochProofLib { using SafeERC20 for IERC20; - function verifyEpochRootProof( - RollupStore storage _rollupStore, - SubmitEpochRootProofArgs calldata _args - ) internal view returns (bool) { - uint256 size = _args.end - _args.start + 1; + using TimeLib for Slot; + using TimeLib for Epoch; + using TimeLib for Timestamp; - for (uint256 i = 0; i < size; i++) { - uint256 blobOffset = i * Constants.BLOB_PUBLIC_INPUTS_BYTES + i; - uint8 blobsInBlock = uint8(_args.blobPublicInputs[blobOffset++]); - checkBlobPublicInputsHashes( - _args.blobPublicInputs, - _rollupStore.blobPublicInputsHashes[_args.start + i], - blobOffset, - blobsInBlock - ); + struct Values { + address sequencer; + uint256 proverFee; + uint256 sequencerFee; + uint256 sequencerBlockReward; + } + + struct Totals { + uint256 feesToClaim; + uint256 totalBurn; + } + + // A Cuauhxicalli [kʷaːʍʃiˈkalːi] ("eagle gourd bowl") is a ceremonial Aztec vessel or altar used to hold offerings, + // such as sacrificial hearts, during rituals performed within temples. + address public constant CUAUHXICALLI = address(bytes20("CUAUHXICALLI")); + + function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) internal { + if (STFLib.canPruneAtTime(Timestamp.wrap(block.timestamp))) { + STFLib.prune(); } - bytes32[] memory publicInputs = getEpochProofPublicInputs( - _rollupStore, - _args.start, - _args.end, - _args.args, - _args.fees, - _args.blobPublicInputs, - _args.aggregationObject - ); + Epoch endEpoch = assertAcceptable(_args.start, _args.end); - require( - _rollupStore.epochProofVerifier.verify(_args.proof, publicInputs), - Errors.Rollup__InvalidProof() - ); + require(verifyEpochRootProof(_args), "proof is invalid"); - return true; + RollupStore storage rollupStore = STFLib.getStorage(); + rollupStore.tips.provenBlockNumber = Math.max(rollupStore.tips.provenBlockNumber, _args.end); + + handleRewardsAndFees(_args, endEpoch); + + emit IRollupCore.L2ProofVerified(_args.end, _args.args[6]); } /** @@ -82,7 +78,6 @@ library EpochProofLib { * @param _aggregationObject - The aggregation object for the proof */ function getEpochProofPublicInputs( - RollupStore storage _rollupStore, uint256 _start, uint256 _end, bytes32[7] calldata _args, @@ -90,6 +85,7 @@ library EpochProofLib { bytes calldata _blobPublicInputs, bytes calldata _aggregationObject ) internal view returns (bytes32[] memory) { + RollupStore storage rollupStore = STFLib.getStorage(); // Args are defined as an array because Solidity complains with "stack too deep" otherwise // 0 bytes32 _previousArchive, // 1 bytes32 _endArchive, @@ -103,28 +99,37 @@ library EpochProofLib { { // We do it this way to provide better error messages than passing along the storage values - bytes32 expectedPreviousArchive = _rollupStore.blocks[_start - 1].archive; - require( - expectedPreviousArchive == _args[0], - Errors.Rollup__InvalidPreviousArchive(expectedPreviousArchive, _args[0]) - ); + { + bytes32 expectedPreviousArchive = rollupStore.blocks[_start - 1].archive; + require( + expectedPreviousArchive == _args[0], + Errors.Rollup__InvalidPreviousArchive(expectedPreviousArchive, _args[0]) + ); + } - bytes32 expectedEndArchive = _rollupStore.blocks[_end].archive; - require( - expectedEndArchive == _args[1], Errors.Rollup__InvalidArchive(expectedEndArchive, _args[1]) - ); + { + bytes32 expectedEndArchive = rollupStore.blocks[_end].archive; + require( + expectedEndArchive == _args[1], + Errors.Rollup__InvalidArchive(expectedEndArchive, _args[1]) + ); + } - bytes32 expectedPreviousBlockHash = _rollupStore.blocks[_start - 1].blockHash; - require( - expectedPreviousBlockHash == _args[2], - Errors.Rollup__InvalidPreviousBlockHash(expectedPreviousBlockHash, _args[2]) - ); + { + bytes32 expectedPreviousBlockHash = rollupStore.blocks[_start - 1].blockHash; + require( + expectedPreviousBlockHash == _args[2], + Errors.Rollup__InvalidPreviousBlockHash(expectedPreviousBlockHash, _args[2]) + ); + } - bytes32 expectedEndBlockHash = _rollupStore.blocks[_end].blockHash; - require( - expectedEndBlockHash == _args[3], - Errors.Rollup__InvalidBlockHash(expectedEndBlockHash, _args[3]) - ); + { + bytes32 expectedEndBlockHash = rollupStore.blocks[_end].blockHash; + require( + expectedEndBlockHash == _args[3], + Errors.Rollup__InvalidBlockHash(expectedEndBlockHash, _args[3]) + ); + } } bytes32[] memory publicInputs = new bytes32[]( @@ -186,11 +191,11 @@ library EpochProofLib { uint256 offset = 9 + feesLength; // vk_tree_root - publicInputs[offset] = _rollupStore.vkTreeRoot; + publicInputs[offset] = rollupStore.config.vkTreeRoot; offset += 1; // protocol_contract_tree_root - publicInputs[offset] = _rollupStore.protocolContractTreeRoot; + publicInputs[offset] = rollupStore.config.protocolContractTreeRoot; offset += 1; // prover_id: id of current epoch's prover @@ -242,6 +247,144 @@ library EpochProofLib { return publicInputs; } + function handleRewardsAndFees(SubmitEpochRootProofArgs memory _args, Epoch _endEpoch) private { + RollupStore storage rollupStore = STFLib.getStorage(); + + bool isFeeCanonical = address(this) == rollupStore.config.feeAssetPortal.canonicalRollup(); + bool isRewardDistributorCanonical = + address(this) == rollupStore.config.rewardDistributor.canonicalRollup(); + + if (isFeeCanonical || isRewardDistributorCanonical) { + uint256 length = _args.end - _args.start + 1; + EpochRewards storage $er = rollupStore.epochRewards[_endEpoch]; + SubEpochRewards storage $sr = $er.subEpoch[length]; + + { + // The address is left padded within the bytes32 + address prover = address(bytes20(_args.args[6] << 96)); + require(!$sr.hasSubmitted[prover], "go away"); + $sr.hasSubmitted[prover] = true; + } + $sr.summedCount += 1; + + if (length > $er.longestProvenLength) { + Values memory v; + Totals memory t; + + { + uint256 added = length - $er.longestProvenLength; + uint256 blockRewardsAvailable = isRewardDistributorCanonical + ? rollupStore.config.rewardDistributor.claimBlockRewards(address(this), added) + : 0; + uint256 sequencerShare = blockRewardsAvailable / 2; + v.sequencerBlockReward = sequencerShare / added; + + $er.rewards += (blockRewardsAvailable - sequencerShare); + } + + for (uint256 i = $er.longestProvenLength; i < length; i++) { + FeeHeader storage feeHeader = rollupStore.blocks[_args.start + i].feeHeader; + + (uint256 fee, uint256 burn) = isFeeCanonical + ? (uint256(_args.fees[1 + i * 2]), feeHeader.congestionCost * feeHeader.manaUsed) + : (0, 0); + + t.feesToClaim += fee; + t.totalBurn += burn; + + // Compute the proving fee in the fee asset + v.proverFee = Math.min(feeHeader.manaUsed * feeHeader.provingCost, fee - burn); + $er.rewards += v.proverFee; + + v.sequencerFee = fee - burn - v.proverFee; + + { + // The address is left padded within the bytes32 + v.sequencer = address(bytes20(_args.fees[i * 2] << 96)); + rollupStore.sequencerRewards[v.sequencer] += (v.sequencerBlockReward + v.sequencerFee); + } + } + + $er.longestProvenLength = length; + + if (t.feesToClaim > 0) { + rollupStore.config.feeAssetPortal.distributeFees(address(this), t.feesToClaim); + } + + if (t.totalBurn > 0) { + rollupStore.config.feeAsset.transfer(CUAUHXICALLI, t.totalBurn); + } + } + } + } + + function assertAcceptable(uint256 _start, uint256 _end) private view returns (Epoch) { + RollupStore storage rollupStore = STFLib.getStorage(); + + Epoch startEpoch = STFLib.getEpochForBlock(_start); + // This also checks for existence of the block. + Epoch endEpoch = STFLib.getEpochForBlock(_end); + + require(startEpoch == endEpoch, Errors.Rollup__StartAndEndNotSameEpoch(startEpoch, endEpoch)); + + Slot deadline = startEpoch.toSlots() + Slot.wrap(rollupStore.config.proofSubmissionWindow); + require( + deadline >= Timestamp.wrap(block.timestamp).slotFromTimestamp(), + Errors.Rollup__PastDeadline(deadline, Timestamp.wrap(block.timestamp).slotFromTimestamp()) + ); + + // By making sure that the previous block is in another epoch, we know that we were + // at the start. + Epoch parentEpoch = STFLib.getEpochForBlock(_start - 1); + + require(startEpoch > Epoch.wrap(0) || _start == 1, "invalid first epoch proof"); + + bool isStartOfEpoch = _start == 1 || parentEpoch <= startEpoch - Epoch.wrap(1); + require(isStartOfEpoch, Errors.Rollup__StartIsNotFirstBlockOfEpoch()); + + bool isStartBuildingOnProven = _start - 1 <= rollupStore.tips.provenBlockNumber; + require(isStartBuildingOnProven, Errors.Rollup__StartIsNotBuildingOnProven()); + + return endEpoch; + } + + function verifyEpochRootProof(SubmitEpochRootProofArgs calldata _args) + private + view + returns (bool) + { + RollupStore storage rollupStore = STFLib.getStorage(); + + uint256 size = _args.end - _args.start + 1; + + for (uint256 i = 0; i < size; i++) { + uint256 blobOffset = i * Constants.BLOB_PUBLIC_INPUTS_BYTES + i; + uint8 blobsInBlock = uint8(_args.blobPublicInputs[blobOffset++]); + checkBlobPublicInputsHashes( + _args.blobPublicInputs, + rollupStore.blobPublicInputsHashes[_args.start + i], + blobOffset, + blobsInBlock + ); + } + + bytes32[] memory publicInputs = getEpochProofPublicInputs( + _args.start, + _args.end, + _args.args, + _args.fees, + _args.blobPublicInputs, + _args.aggregationObject + ); + + require( + rollupStore.config.epochProofVerifier.verify(_args.proof, publicInputs), + Errors.Rollup__InvalidProof() + ); + + return true; + } + /** * Helper fn to prevent stack too deep. Checks blob public input hashes match for a block: * @param _blobPublicInputs - The provided blob public inputs bytes array @@ -254,7 +397,7 @@ library EpochProofLib { bytes32 _blobPublicInputsHash, uint256 _index, uint8 _blobsInBlock - ) internal pure { + ) private pure { bytes32 calcBlobPublicInputsHash = sha256( abi.encodePacked( _blobPublicInputs[_index:_index + Constants.BLOB_PUBLIC_INPUTS_BYTES * _blobsInBlock] @@ -276,7 +419,7 @@ library EpochProofLib { * @param _input - The field in bytes32 */ function bytes32ToBigNum(bytes32 _input) - internal + private pure returns (bytes32 firstLimb, bytes32 secondLimb, bytes32 thirdLimb) { diff --git a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol index 1cb88be5d6f0..e971e1e79be2 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -17,10 +17,14 @@ import { } from "./FeeMath.sol"; import {HeaderLib, Header} from "./HeaderLib.sol"; import {ValidationLib, ValidateHeaderArgs} from "./ValidationLib.sol"; + // We are using this library such that we can more easily "link" just a larger external library // instead of a few smaller ones. - library ExtRollupLib { + function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external { + EpochProofLib.submitEpochRootProof(_args); + } + function validateHeaderForSubmissionBase( ValidateHeaderArgs memory _args, mapping(uint256 blockNumber => BlockLog log) storage _blocks @@ -28,13 +32,6 @@ library ExtRollupLib { ValidationLib.validateHeaderForSubmissionBase(_args, _blocks); } - function verifyEpochRootProof( - RollupStore storage _rollupStore, - SubmitEpochRootProofArgs calldata _args - ) external view returns (bool) { - return EpochProofLib.verifyEpochRootProof(_rollupStore, _args); - } - function getManaBaseFeeComponentsAt( FeeHeader storage _parentFeeHeader, L1FeeData memory _fees, @@ -48,7 +45,6 @@ library ExtRollupLib { } function getEpochProofPublicInputs( - RollupStore storage _rollupStore, uint256 _start, uint256 _end, bytes32[7] calldata _args, @@ -57,7 +53,7 @@ library ExtRollupLib { bytes calldata _aggregationObject ) external view returns (bytes32[] memory) { return EpochProofLib.getEpochProofPublicInputs( - _rollupStore, _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject + _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject ); } diff --git a/l1-contracts/src/core/libraries/RollupLibs/STFLib.sol b/l1-contracts/src/core/libraries/RollupLibs/STFLib.sol deleted file mode 100644 index 4c03504e0093..000000000000 --- a/l1-contracts/src/core/libraries/RollupLibs/STFLib.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {RollupStore} from "@aztec/core/interfaces/IRollup.sol"; -import {Epoch, Slot, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import {Errors} from "@aztec/core/libraries/Errors.sol"; - -library STFLib { - using TimeLib for Slot; - - bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage"); - - function getEpochForBlock(uint256 _blockNumber) internal view returns (Epoch) { - RollupStore storage rollupStore = STFLib.getStorage(); - require( - _blockNumber <= rollupStore.tips.pendingBlockNumber, - Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) - ); - return rollupStore.blocks[_blockNumber].slotNumber.epochFromSlot(); - } - - function getStorage() internal pure returns (RollupStore storage storageStruct) { - bytes32 position = STF_STORAGE_POSITION; - assembly { - storageStruct.slot := position - } - } -} diff --git a/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol new file mode 100644 index 000000000000..e5f86e9f3ba4 --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {RollupStore, IRollupCore} from "@aztec/core/interfaces/IRollup.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; + +library STFLib { + using TimeLib for Slot; + using TimeLib for Epoch; + using TimeLib for Timestamp; + + bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage"); + + function getEpochForBlock(uint256 _blockNumber) internal view returns (Epoch) { + RollupStore storage rollupStore = STFLib.getStorage(); + require( + _blockNumber <= rollupStore.tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) + ); + return rollupStore.blocks[_blockNumber].slotNumber.epochFromSlot(); + } + + function canPruneAtTime(Timestamp _ts) internal view returns (bool) { + RollupStore storage rollupStore = STFLib.getStorage(); + if (rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber) { + return false; + } + + Epoch oldestPendingEpoch = getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); + Slot deadline = + oldestPendingEpoch.toSlots() + Slot.wrap(rollupStore.config.proofSubmissionWindow); + + return deadline < _ts.slotFromTimestamp(); + } + + function prune() internal { + RollupStore storage rollupStore = STFLib.getStorage(); + uint256 pending = rollupStore.tips.pendingBlockNumber; + + // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. + // We can do because any new block proposed will overwrite a previous block in the block log, + // so no values should "survive". + // People must therefore read the chain using the pendingTip as a boundary. + rollupStore.tips.pendingBlockNumber = rollupStore.tips.provenBlockNumber; + + emit IRollupCore.PrunedPending(rollupStore.tips.provenBlockNumber, pending); + } + + function getStorage() internal pure returns (RollupStore storage storageStruct) { + bytes32 position = STF_STORAGE_POSITION; + assembly { + storageStruct.slot := position + } + } +} diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index 847b796da535..d1c3997f3dd0 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -157,7 +157,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { vm.label(address(rollup), "ROLLUP"); vm.label(address(fakeCanonical), "FAKE CANONICAL"); vm.label(address(asset), "ASSET"); - vm.label(rollup.CUAUHXICALLI(), "CUAUHXICALLI"); + vm.label(rollup.getCuauhxicalli(), "CUAUHXICALLI"); } function _loadL1Metadata(uint256 index) internal { @@ -457,7 +457,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { fees[feeIndex * 2 + 1] = bytes32(fee); } - uint256 cuauhxicalliBalanceBefore = asset.balanceOf(rollup.CUAUHXICALLI()); + uint256 cuauhxicalliBalanceBefore = asset.balanceOf(rollup.getCuauhxicalli()); uint256 sequencerRewardsBefore = rollup.getSequencerRewards(coinbase); bytes32[7] memory args = [ @@ -492,7 +492,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { ); } - uint256 burned = asset.balanceOf(rollup.CUAUHXICALLI()) - cuauhxicalliBalanceBefore; + uint256 burned = asset.balanceOf(rollup.getCuauhxicalli()) - cuauhxicalliBalanceBefore; assertEq(burnSum, burned, "Sum of burned does not match"); // The reward is not yet distributed, but only accumulated. diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index cb5764811269..f77a94bc9188 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -188,9 +188,9 @@ export class RollupContract { await Promise.all([ this.rollup.read.INBOX(), this.rollup.read.OUTBOX(), - this.rollup.read.FEE_JUICE_PORTAL(), - this.rollup.read.REWARD_DISTRIBUTOR(), - this.rollup.read.ASSET(), + this.rollup.read.getFeeAssetPortal(), + this.rollup.read.getRewardDistributor(), + this.rollup.read.getFeeAsset(), this.rollup.read.getStakingAsset(), ] as const) ).map(EthAddress.fromString); From f4f3c1c9ae75463189e6ce4672f75ddcebd1319b Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Tue, 25 Feb 2025 16:32:47 +0000 Subject: [PATCH 3/5] refactor: compartmentalise --- l1-contracts/src/core/FeeJuicePortal.sol | 2 +- l1-contracts/src/core/Rollup.sol | 42 ++- l1-contracts/src/core/RollupCore.sol | 268 +------------- l1-contracts/src/core/interfaces/IRollup.sol | 15 +- l1-contracts/src/core/libraries/Errors.sol | 1 + .../libraries/RollupLibs/EpochProofLib.sol | 17 +- .../libraries/RollupLibs/ExtRollupLib.sol | 46 +-- .../src/core/libraries/RollupLibs/FeeMath.sol | 4 + .../libraries/RollupLibs/IntRollupLib.sol | 27 -- .../core/libraries/RollupLibs/ProposeLib.sol | 340 +++++++++++++++++- .../libraries/RollupLibs/ValidationLib.sol | 95 ----- .../core/libraries/RollupLibs/core/STFLib.sol | 28 +- l1-contracts/test/Rollup.t.sol | 4 +- l1-contracts/test/base/RollupBase.sol | 4 +- .../fee_portal/depositToAztecPublic.t.sol | 2 +- l1-contracts/test/fees/FeeRollup.t.sol | 17 +- l1-contracts/test/portals/TokenPortal.sol | 6 +- l1-contracts/test/portals/TokenPortal.t.sol | 4 +- l1-contracts/test/portals/UniswapPortal.sol | 4 +- l1-contracts/test/portals/UniswapPortal.t.sol | 2 +- .../ValidatorSelection.t.sol | 4 +- yarn-project/ethereum/src/contracts/rollup.ts | 4 +- .../ethereum/src/deploy_l1_contracts.ts | 4 +- .../global_variable_builder/global_builder.ts | 2 +- 24 files changed, 455 insertions(+), 487 deletions(-) delete mode 100644 l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol delete mode 100644 l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol diff --git a/l1-contracts/src/core/FeeJuicePortal.sol b/l1-contracts/src/core/FeeJuicePortal.sol index 860e3bef2fc6..e619f54d792e 100644 --- a/l1-contracts/src/core/FeeJuicePortal.sol +++ b/l1-contracts/src/core/FeeJuicePortal.sol @@ -68,7 +68,7 @@ contract FeeJuicePortal is IFeeJuicePortal { // Preamble address rollup = canonicalRollup(); uint256 version = REGISTRY.getVersionFor(rollup); - IInbox inbox = IRollup(rollup).INBOX(); + IInbox inbox = IRollup(rollup).getInbox(); DataStructures.L2Actor memory actor = DataStructures.L2Actor(L2_TOKEN_ADDRESS, version); // Hash the message content to be reconstructed in the receiving contract diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index f81b69ee08e3..7937622923f1 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -11,10 +11,11 @@ import { EnumerableSet } from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {FeeAssetValue} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; - -// We allow the unused imports here as they make it much simpler to import the Rollup later -// solhint-disable no-unused-import +import {FeeMath} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import {HeaderLib} from "@aztec/core/libraries/RollupLibs/HeaderLib.sol"; +import {EpochProofLib} from "./libraries/RollupLibs/EpochProofLib.sol"; import { RollupCore, Config, @@ -22,10 +23,6 @@ import { IFeeJuicePortal, IERC20, BlockLog, - FeeHeader, - ManaBaseFeeComponents, - SubmitEpochRootProofArgs, - L1FeeData, ValidatorSelectionLib, StakingLib, TimeLib, @@ -34,18 +31,16 @@ import { Timestamp, Errors, Signature, - DataStructures, ExtRollupLib, - IntRollupLib, - EpochRewards, - FeeAssetPerEthE9, EthValue, PriceLib, STFLib, - RollupStore + RollupStore, + IInbox, + IOutbox, + ProposeLib, + EpochRewards } from "./RollupCore.sol"; -import {EpochProofLib} from "./libraries/RollupLibs/EpochProofLib.sol"; -// solhint-enable no-unused-import /** * @title Rollup @@ -60,7 +55,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { using TimeLib for Timestamp; using TimeLib for Slot; using TimeLib for Epoch; - using IntRollupLib for ManaBaseFeeComponents; using PriceLib for EthValue; constructor( @@ -198,8 +192,8 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { bytes32 _blobsHash, DataStructures.ExecutionFlags memory _flags ) external view override(IRollup) { - _validateHeader( - ExtRollupLib.decodeHeader(_header), + ProposeLib.validateHeader( + HeaderLib.decode(_header), _signatures, _digest, _currentTime, @@ -524,6 +518,18 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return EpochProofLib.CUAUHXICALLI; } + function getVersion() external view override(IRollup) returns (uint256) { + return STFLib.getStorage().config.version; + } + + function getInbox() external view override(IRollup) returns (IInbox) { + return STFLib.getStorage().config.inbox; + } + + function getOutbox() external view override(IRollup) returns (IOutbox) { + return STFLib.getStorage().config.outbox; + } + function getFeeAsset() external view override(IRollup) returns (IERC20) { return STFLib.getStorage().config.feeAsset; } @@ -593,7 +599,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (uint256) { - return getManaBaseFeeComponentsAt(_timestamp, _inFeeAsset).summedBaseFee(); + return FeeMath.summedBaseFee(getManaBaseFeeComponentsAt(_timestamp, _inFeeAsset)); } /** diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 799fa30ca723..eee458ffb3a9 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -14,7 +14,6 @@ import { L1GasOracleValues, L1FeeData, SubmitEpochRootProofArgs, - SubEpochRewards, EpochRewards } from "@aztec/core/interfaces/IRollup.sol"; import {IStakingCore} from "@aztec/core/interfaces/IStaking.sol"; @@ -23,17 +22,11 @@ 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 {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; -import {MerkleLib} from "@aztec/core/libraries/crypto/MerkleLib.sol"; import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; -import { - ExtRollupLib, - ValidateHeaderArgs, - Header -} from "@aztec/core/libraries/RollupLibs/ExtRollupLib.sol"; +import {STFLib} from "@aztec/core/libraries/RollupLibs/core/STFLib.sol"; +import {ExtRollupLib} from "@aztec/core/libraries/RollupLibs/ExtRollupLib.sol"; import {EthValue, FeeAssetPerEthE9, PriceLib} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; -import {IntRollupLib} from "@aztec/core/libraries/RollupLibs/IntRollupLib.sol"; import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; @@ -47,8 +40,6 @@ import {MockVerifier} from "@aztec/mock/MockVerifier.sol"; import {Ownable} from "@oz/access/Ownable.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; -import {Math} from "@oz/utils/math/Math.sol"; -import {STFLib} from "@aztec/core/libraries/RollupLibs/core/STFLib.sol"; struct Config { uint256 aztecSlotDuration; @@ -60,26 +51,6 @@ struct Config { uint256 slashingRoundSize; } -// @note https://www.youtube.com/watch?v=glN0W8WogK8 -struct SubmitProofInterim { - Slot deadline; - uint256 length; - uint256 totalBurn; - address prover; - uint256 feesToClaim; - uint256 fee; - uint256 proverFee; - uint256 burn; - uint256 blockRewardsAvailable; - uint256 blockRewardSequencer; - uint256 blockRewardProver; - uint256 added; - uint256 sequencerShare; - bool isFeeCanonical; - bool isRewardDistributorCanonical; - FeeAssetPerEthE9 feeAssetPrice; -} - /** * @title Rollup * @author Aztec Labs @@ -96,8 +67,6 @@ contract RollupCore is ITestRollup { using ProposeLib for ProposeArgs; - using IntRollupLib for uint256; - using IntRollupLib for ManaBaseFeeComponents; using PriceLib for EthValue; @@ -105,13 +74,7 @@ contract RollupCore is using TimeLib for Slot; using TimeLib for Epoch; - Slot public constant LIFETIME = Slot.wrap(5); - Slot public constant LAG = Slot.wrap(2); - uint256 public immutable L1_BLOCK_AT_GENESIS; - IInbox public immutable INBOX; - IOutbox public immutable OUTBOX; - uint256 public immutable VERSION; // To push checkBlob into its own slot so we don't have the trouble of being in the middle of a slot uint256 private gap = 0; @@ -137,9 +100,6 @@ contract RollupCore is StakingLib.initialize(_stakingAsset, _config.minimumStake, exitDelay, address(slasher)); ValidatorSelectionLib.initialize(_config.targetCommitteeSize); - INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); - OUTBOX = IOutbox(address(new Outbox(address(this)))); - VERSION = 1; L1_BLOCK_AT_GENESIS = block.number; RollupStore storage rollupStore = STFLib.getStorage(); @@ -152,7 +112,11 @@ contract RollupCore is rollupStore.config.epochProofVerifier = new MockVerifier(); rollupStore.config.vkTreeRoot = _vkTreeRoot; rollupStore.config.protocolContractTreeRoot = _protocolContractTreeRoot; + rollupStore.config.version = 1; + rollupStore.config.inbox = + IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); + rollupStore.config.outbox = IOutbox(address(new Outbox(address(this)))); rollupStore.provingCostPerMana = EthValue.wrap(100); // Genesis block @@ -171,7 +135,7 @@ contract RollupCore is rollupStore.l1GasOracleValues = L1GasOracleValues({ pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee()}), - slotOfChange: LIFETIME + slotOfChange: ProposeLib.LIFETIME }); } @@ -342,10 +306,6 @@ contract RollupCore is ExtRollupLib.submitEpochRootProof(_args); } - function setupEpoch() public override(IValidatorSelectionCore) { - ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); - } - /** * @notice Publishes the body and propose the block * @dev `eth_log_handlers` rely on this function @@ -353,74 +313,21 @@ contract RollupCore is * @param _args - The arguments to propose the block * @param _signatures - Signatures from the validators * // TODO(#9101): The below _body should be removed once we can extract blobs. It's only here so the archiver can extract tx effects. - * @param - The body of the L2 block + * @param _body - The body of the L2 block * @param _blobInput - The blob evaluation KZG proof, challenge, and opening required for the precompile. */ function propose( ProposeArgs calldata _args, Signature[] memory _signatures, // TODO(#9101): Extract blobs from beacon chain => remove below body input - bytes calldata, + bytes calldata _body, bytes calldata _blobInput - ) public override(IRollupCore) { - if (canPrune()) { - STFLib.prune(); - } - updateL1GasFeeOracle(); - - // Since an invalid blob hash here would fail the consensus checks of - // the header, the `blobInput` is implicitly accepted by consensus as well. - (bytes32[] memory blobHashes, bytes32 blobsHashesCommitment, bytes32 blobPublicInputsHash) = - ExtRollupLib.validateBlobs(_blobInput, checkBlob); - - // Decode and validate header - Header memory header = ExtRollupLib.decodeHeader(_args.header); - - // @todo As part of a refactor of the core for propose and submit, we should - // be able to set it up such that we don't need compute the fee components - // unless needed. - // Would be part of joining the header validation. - - setupEpoch(); - ManaBaseFeeComponents memory components = - getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true); - uint256 manaBaseFee = components.summedBaseFee(); - _validateHeader({ - _header: header, - _signatures: _signatures, - _digest: _args.digest(), - _currentTime: Timestamp.wrap(block.timestamp), - _manaBaseFee: manaBaseFee, - _blobsHashesCommitment: blobsHashesCommitment, - _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) - }); - - RollupStore storage rollupStore = STFLib.getStorage(); - uint256 blockNumber = ++rollupStore.tips.pendingBlockNumber; - - { - // @note The components are measured in the fee asset. - rollupStore.blocks[blockNumber] = - _toBlockLog(_args, blockNumber, components.congestionCost, components.provingCost); - } - - rollupStore.blobPublicInputsHashes[blockNumber] = blobPublicInputsHash; - - // @note The block number here will always be >=1 as the genesis block is at 0 - { - bytes32 inHash = INBOX.consume(blockNumber); - require( - header.contentCommitment.inHash == inHash, - Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash) - ); - } - - // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim - // Min size = smallest path of the rollup tree + 1 - (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); - OUTBOX.insert(blockNumber, header.contentCommitment.outHash, min + 1); + ) external override(IRollupCore) { + ExtRollupLib.propose(_args, _signatures, _body, _blobInput, checkBlob); + } - emit L2BlockProposed(blockNumber, _args.archive, blobHashes); + function setupEpoch() public override(IValidatorSelectionCore) { + ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); } /** @@ -428,19 +335,7 @@ contract RollupCore is * @dev This function is called by the `propose` function */ function updateL1GasFeeOracle() public override(IRollupCore) { - Slot slot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); - // The slot where we find a new queued value acceptable - RollupStore storage rollupStore = STFLib.getStorage(); - Slot acceptableSlot = rollupStore.l1GasOracleValues.slotOfChange + (LIFETIME - LAG); - - if (slot < acceptableSlot) { - return; - } - - rollupStore.l1GasOracleValues.pre = rollupStore.l1GasOracleValues.post; - rollupStore.l1GasOracleValues.post = - L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee()}); - rollupStore.l1GasOracleValues.slotOfChange = slot + LAG; + ProposeLib.updateL1GasFeeOracle(); } /** @@ -449,10 +344,7 @@ contract RollupCore is * @return The fee asset price */ function getFeeAssetPerEth() public view override(IRollupCore) returns (FeeAssetPerEthE9) { - RollupStore storage rollupStore = STFLib.getStorage(); - return IntRollupLib.getFeeAssetPerEth( - rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator - ); + return ProposeLib.getFeeAssetPerEth(); } function getL1FeesAt(Timestamp _timestamp) @@ -461,10 +353,7 @@ contract RollupCore is override(IRollupCore) returns (L1FeeData memory) { - RollupStore storage rollupStore = STFLib.getStorage(); - return _timestamp.slotFromTimestamp() < rollupStore.l1GasOracleValues.slotOfChange - ? rollupStore.l1GasOracleValues.pre - : rollupStore.l1GasOracleValues.post; + return ProposeLib.getL1FeesAt(_timestamp); } /** @@ -485,19 +374,7 @@ contract RollupCore is override(ITestRollup) returns (ManaBaseFeeComponents memory) { - RollupStore storage rollupStore = STFLib.getStorage(); - // If we can prune, we use the proven block, otherwise the pending block - uint256 blockOfInterest = canPruneAtTime(_timestamp) - ? rollupStore.tips.provenBlockNumber - : rollupStore.tips.pendingBlockNumber; - - return ExtRollupLib.getManaBaseFeeComponentsAt( - rollupStore.blocks[blockOfInterest].feeHeader, - getL1FeesAt(_timestamp), - rollupStore.provingCostPerMana, - _inFeeAsset ? getFeeAssetPerEth() : FeeAssetPerEthE9.wrap(1e9), - TimeLib.getStorage().epochDuration - ); + return ProposeLib.getManaBaseFeeComponentsAt(_timestamp, _inFeeAsset); } function getEpochForBlock(uint256 _blockNumber) public view override(IRollupCore) returns (Epoch) { @@ -511,113 +388,4 @@ contract RollupCore is function canPruneAtTime(Timestamp _ts) public view override(IRollupCore) returns (bool) { return STFLib.canPruneAtTime(_ts); } - - /** - * @notice Validates the header for submission - * - * @param _header - The proposed block header - * @param _signatures - The signatures for the attestations - * @param _digest - The digest that signatures signed - * @param _currentTime - The time of execution - * @param _blobsHashesCommitment - The blobs hash for this block - * @dev - This value is provided to allow for simple simulation of future - * @param _flags - Flags specific to the execution, whether certain checks should be skipped - */ - function _validateHeader( - Header memory _header, - Signature[] memory _signatures, - bytes32 _digest, - Timestamp _currentTime, - uint256 _manaBaseFee, - bytes32 _blobsHashesCommitment, - DataStructures.ExecutionFlags memory _flags - ) internal view { - RollupStore storage rollupStore = STFLib.getStorage(); - uint256 pendingBlockNumber = canPruneAtTime(_currentTime) - ? rollupStore.tips.provenBlockNumber - : rollupStore.tips.pendingBlockNumber; - - ExtRollupLib.validateHeaderForSubmissionBase( - ValidateHeaderArgs({ - header: _header, - currentTime: _currentTime, - manaBaseFee: _manaBaseFee, - blobsHashesCommitment: _blobsHashesCommitment, - pendingBlockNumber: pendingBlockNumber, - flags: _flags, - version: VERSION, - feeJuicePortal: rollupStore.config.feeAssetPortal - }), - rollupStore.blocks - ); - _validateHeaderForSubmissionSequencerSelection( - Slot.wrap(_header.globalVariables.slotNumber), _signatures, _digest, _currentTime, _flags - ); - } - - /** - * @notice Validate a header for submission to the pending chain (sequencer selection checks) - * - * These validation checks are directly related to sequencer selection. - * Note that while these checks are strict, they can be relaxed with some changes to - * message boxes. - * - * Each of the following validation checks must pass, otherwise an error is thrown and we revert. - * - The slot MUST be the current slot - * This might be relaxed for allow consensus set to better handle short-term bursts of L1 congestion - * - The slot MUST be in the current epoch - * - * @param _slot - The slot of the header to validate - * @param _signatures - The signatures to validate - * @param _digest - The digest that signatures sign over - */ - function _validateHeaderForSubmissionSequencerSelection( - Slot _slot, - Signature[] memory _signatures, - bytes32 _digest, - Timestamp _currentTime, - DataStructures.ExecutionFlags memory _flags - ) internal view { - // Ensure that the slot proposed is NOT in the future - Slot currentSlot = _currentTime.slotFromTimestamp(); - require(_slot == currentSlot, Errors.HeaderLib__InvalidSlotNumber(currentSlot, _slot)); - - // @note We are currently enforcing that the slot is in the current epoch - // If this is not the case, there could potentially be a weird reorg - // of an entire epoch if no-one from the new epoch committee have seen - // those blocks or behaves as if they did not. - - Epoch epochNumber = _slot.epochFromSlot(); - Epoch currentEpoch = _currentTime.epochFromTimestamp(); - require(epochNumber == currentEpoch, Errors.Rollup__InvalidEpoch(currentEpoch, epochNumber)); - - ValidatorSelectionLib.validateValidatorSelection( - StakingLib.getStorage(), _slot, epochNumber, _signatures, _digest, _flags - ); - } - - // Helper to avoid stack too deep - function _toBlockLog( - ProposeArgs calldata _args, - uint256 _blockNumber, - uint256 _congestionCost, - uint256 _provingCost - ) internal view returns (BlockLog memory) { - RollupStore storage rollupStore = STFLib.getStorage(); - FeeHeader memory parentFeeHeader = rollupStore.blocks[_blockNumber - 1].feeHeader; - return BlockLog({ - archive: _args.archive, - blockHash: _args.blockHash, - slotNumber: Slot.wrap(uint256(bytes32(_args.header[0x0194:0x01b4]))), - feeHeader: FeeHeader({ - excessMana: IntRollupLib.computeExcessMana(parentFeeHeader), - feeAssetPriceNumerator: parentFeeHeader.feeAssetPriceNumerator.clampedAdd( - _args.oracleInput.feeAssetPriceModifier - ), - manaUsed: uint256(bytes32(_args.header[0x0268:0x0288])), - congestionCost: _congestionCost, - provingCost: _provingCost - }) - }); - } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 51969b501951..ce80c1b06e18 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -2,6 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.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"; @@ -15,7 +16,6 @@ import { } from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import {ProposeArgs} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; -import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; @@ -68,6 +68,9 @@ struct RollupConfig { bytes32 vkTreeRoot; bytes32 protocolContractTreeRoot; IVerifier epochProofVerifier; + IInbox inbox; + IOutbox outbox; + uint256 version; } // The below blobPublicInputsHashes are filled when proposing a block, then used to verify an epoch proof. @@ -130,12 +133,6 @@ interface IRollupCore { function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external; - // solhint-disable-next-line func-name-mixedcase - function INBOX() external view returns (IInbox); - - // solhint-disable-next-line func-name-mixedcase - function OUTBOX() external view returns (IOutbox); - // solhint-disable-next-line func-name-mixedcase function L1_BLOCK_AT_GENESIS() external view returns (uint256); @@ -218,4 +215,8 @@ interface IRollup is IRollupCore { function getFeeAssetPortal() external view returns (IFeeJuicePortal); function getRewardDistributor() external view returns (IRewardDistributor); function getCuauhxicalli() external view returns (address); + + function getInbox() external view returns (IInbox); + function getOutbox() external view returns (IOutbox); + function getVersion() external view returns (uint256); } diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 7cfb6338be0e..1c3473ec5148 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -82,6 +82,7 @@ library Errors { error Rollup__AlreadyClaimed(address prover, Epoch epoch); error Rollup__NotPastDeadline(Slot deadline, Slot currentSlot); error Rollup__PastDeadline(Slot deadline, Slot currentSlot); + error Rollup__ProverHaveAlreadySubmitted(address prover, Epoch epoch); // HeaderLib error HeaderLib__InvalidHeaderSize(uint256 expected, uint256 actual); // 0xf3ccb247 diff --git a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol index 4b27e76ffa0f..3ea88008b2d2 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol @@ -8,20 +8,17 @@ import { EpochRewards, SubEpochRewards } from "@aztec/core/interfaces/IRollup.sol"; - -import {STFLib, RollupStore} from "@aztec/core/libraries/RollupLibs/core/STFLib.sol"; -import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import {Errors} from "@aztec/core/libraries/Errors.sol"; -import {FeeHeader} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; - -import {Math} from "@oz/utils/math/Math.sol"; - import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {STFLib, RollupStore} from "@aztec/core/libraries/RollupLibs/core/STFLib.sol"; +import {FeeHeader} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "@oz/utils/math/Math.sol"; library EpochProofLib { using SafeERC20 for IERC20; @@ -262,7 +259,9 @@ library EpochProofLib { { // The address is left padded within the bytes32 address prover = address(bytes20(_args.args[6] << 96)); - require(!$sr.hasSubmitted[prover], "go away"); + require( + !$sr.hasSubmitted[prover], Errors.Rollup__ProverHaveAlreadySubmitted(prover, _endEpoch) + ); $sr.hasSubmitted[prover] = true; } $sr.summedCount += 1; diff --git a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol index e971e1e79be2..10fc5a5ced3b 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -2,46 +2,28 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; -import {BlockLog, RollupStore} from "@aztec/core/interfaces/IRollup.sol"; import {BlobLib} from "./BlobLib.sol"; import {EpochProofLib} from "./EpochProofLib.sol"; -import { - FeeMath, - ManaBaseFeeComponents, - FeeHeader, - L1FeeData, - EthValue, - FeeAssetPerEthE9 -} from "./FeeMath.sol"; -import {HeaderLib, Header} from "./HeaderLib.sol"; -import {ValidationLib, ValidateHeaderArgs} from "./ValidationLib.sol"; - +import {ProposeLib, ProposeArgs, Signature} from "./ProposeLib.sol"; // We are using this library such that we can more easily "link" just a larger external library // instead of a few smaller ones. + library ExtRollupLib { function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external { EpochProofLib.submitEpochRootProof(_args); } - function validateHeaderForSubmissionBase( - ValidateHeaderArgs memory _args, - mapping(uint256 blockNumber => BlockLog log) storage _blocks - ) external view { - ValidationLib.validateHeaderForSubmissionBase(_args, _blocks); - } - - function getManaBaseFeeComponentsAt( - FeeHeader storage _parentFeeHeader, - L1FeeData memory _fees, - EthValue _provingCostPerMana, - FeeAssetPerEthE9 _feeAssetPrice, - uint256 _epochDuration - ) external view returns (ManaBaseFeeComponents memory) { - return FeeMath.getManaBaseFeeComponentsAt( - _parentFeeHeader, _fees, _provingCostPerMana, _feeAssetPrice, _epochDuration - ); + function propose( + ProposeArgs calldata _args, + Signature[] memory _signatures, + // TODO(#9101): Extract blobs from beacon chain => remove below body input + bytes calldata _body, + bytes calldata _blobInput, + bool _checkBlob + ) external { + ProposeLib.propose(_args, _signatures, _body, _blobInput, _checkBlob); } function getEpochProofPublicInputs( @@ -72,8 +54,4 @@ library ExtRollupLib { function getBlobBaseFee() external view returns (uint256) { return BlobLib.getBlobBaseFee(); } - - function decodeHeader(bytes calldata _header) external pure returns (Header memory) { - return HeaderLib.decode(_header); - } } diff --git a/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol b/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol index a4e1b3e4b4c9..f9c7bfc3e9b6 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol @@ -181,6 +181,10 @@ library FeeMath { ); } + function computeExcessMana(FeeHeader memory _feeHeader) internal pure returns (uint256) { + return clampedAdd(_feeHeader.excessMana + _feeHeader.manaUsed, -int256(MANA_TARGET)); + } + function congestionMultiplier(uint256 _numerator) internal pure returns (uint256) { return fakeExponential(MINIMUM_CONGESTION_MULTIPLIER, _numerator, CONGESTION_UPDATE_FRACTION); } diff --git a/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol deleted file mode 100644 index adf9afce6112..000000000000 --- a/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import { - FeeMath, ManaBaseFeeComponents, FeeHeader, MANA_TARGET, FeeAssetPerEthE9 -} from "./FeeMath.sol"; - -// We are using this library such that we can more easily "link" just a larger external library -// instead of a few smaller ones. -library IntRollupLib { - function summedBaseFee(ManaBaseFeeComponents memory _components) internal pure returns (uint256) { - return FeeMath.summedBaseFee(_components); - } - - function clampedAdd(uint256 _a, int256 _b) internal pure returns (uint256) { - return FeeMath.clampedAdd(_a, _b); - } - - function getFeeAssetPerEth(uint256 _numerator) internal pure returns (FeeAssetPerEthE9) { - return FeeMath.getFeeAssetPerEth(_numerator); - } - - function computeExcessMana(FeeHeader memory _feeHeader) internal pure returns (uint256) { - return clampedAdd(_feeHeader.excessMana + _feeHeader.manaUsed, -int256(MANA_TARGET)); - } -} diff --git a/l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol index 5dbc8b27dae1..32f67e228332 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol @@ -2,8 +2,27 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +import {RollupStore, IRollupCore, BlockLog} from "@aztec/core/interfaces/IRollup.sol"; +import {MerkleLib} from "@aztec/core/libraries/crypto/MerkleLib.sol"; import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {OracleInput} from "./FeeMath.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import { + OracleInput, + FeeMath, + ManaBaseFeeComponents, + L1FeeData, + FeeAssetPerEthE9, + FeeHeader +} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {ValidatorSelectionLib} from + "@aztec/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol"; +import {BlobLib} from "./BlobLib.sol"; +import {STFLib} from "./core/STFLib.sol"; +import {Header, HeaderLib} from "./HeaderLib.sol"; struct ProposeArgs { bytes32 archive; @@ -13,8 +32,327 @@ struct ProposeArgs { bytes32[] txHashes; } +struct ValidateHeaderArgs { + Header header; + Timestamp currentTime; + uint256 manaBaseFee; + bytes32 blobsHashesCommitment; + uint256 pendingBlockNumber; + DataStructures.ExecutionFlags flags; +} + +struct InterimProposeValues { + bytes32[] blobHashes; + bytes32 blobsHashesCommitment; + bytes32 blobPublicInputsHash; +} + library ProposeLib { + using TimeLib for Timestamp; + using TimeLib for Slot; + using TimeLib for Epoch; + + Slot internal constant LIFETIME = Slot.wrap(5); + Slot internal constant LAG = Slot.wrap(2); + + function updateL1GasFeeOracle() internal { + Slot slot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); + // The slot where we find a new queued value acceptable + RollupStore storage rollupStore = STFLib.getStorage(); + Slot acceptableSlot = rollupStore.l1GasOracleValues.slotOfChange + (LIFETIME - LAG); + + if (slot < acceptableSlot) { + return; + } + + rollupStore.l1GasOracleValues.pre = rollupStore.l1GasOracleValues.post; + rollupStore.l1GasOracleValues.post = + L1FeeData({baseFee: block.basefee, blobFee: BlobLib.getBlobBaseFee()}); + rollupStore.l1GasOracleValues.slotOfChange = slot + LAG; + } + + function propose( + ProposeArgs calldata _args, + Signature[] memory _signatures, + // TODO(#9101): Extract blobs from beacon chain => remove below body input + bytes calldata, + bytes calldata _blobInput, + bool _checkBlob + ) internal { + if (STFLib.canPruneAtTime(Timestamp.wrap(block.timestamp))) { + STFLib.prune(); + } + updateL1GasFeeOracle(); + + InterimProposeValues memory v; + // Since an invalid blob hash here would fail the consensus checks of + // the header, the `blobInput` is implicitly accepted by consensus as well. + (v.blobHashes, v.blobsHashesCommitment, v.blobPublicInputsHash) = + BlobLib.validateBlobs(_blobInput, _checkBlob); + + Header memory header = HeaderLib.decode(_args.header); + + ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); + + ManaBaseFeeComponents memory components = + getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true); + + { + uint256 manaBaseFee = FeeMath.summedBaseFee(components); + validateHeader({ + _header: header, + _signatures: _signatures, + _digest: digest(_args), + _currentTime: Timestamp.wrap(block.timestamp), + _manaBaseFee: manaBaseFee, + _blobsHashesCommitment: v.blobsHashesCommitment, + _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) + }); + } + + RollupStore storage rollupStore = STFLib.getStorage(); + uint256 blockNumber = ++rollupStore.tips.pendingBlockNumber; + + { + // @note The components are measured in the fee asset. + rollupStore.blocks[blockNumber] = + toBlockLog(_args, blockNumber, components.congestionCost, components.provingCost); + } + + rollupStore.blobPublicInputsHashes[blockNumber] = v.blobPublicInputsHash; + + // @note The block number here will always be >=1 as the genesis block is at 0 + { + bytes32 inHash = rollupStore.config.inbox.consume(blockNumber); + require( + header.contentCommitment.inHash == inHash, + Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash) + ); + } + { + // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim + // Min size = smallest path of the rollup tree + 1 + (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); + rollupStore.config.outbox.insert(blockNumber, header.contentCommitment.outHash, min + 1); + } + + emit IRollupCore.L2BlockProposed(blockNumber, _args.archive, v.blobHashes); + } + + function getL1FeesAt(Timestamp _timestamp) internal view returns (L1FeeData memory) { + RollupStore storage rollupStore = STFLib.getStorage(); + return _timestamp.slotFromTimestamp() < rollupStore.l1GasOracleValues.slotOfChange + ? rollupStore.l1GasOracleValues.pre + : rollupStore.l1GasOracleValues.post; + } + + function getFeeAssetPerEth() internal view returns (FeeAssetPerEthE9) { + RollupStore storage rollupStore = STFLib.getStorage(); + return FeeMath.getFeeAssetPerEth( + rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator + ); + } + + function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset) + internal + view + returns (ManaBaseFeeComponents memory) + { + // @todo If we are not canonical we can just return mostly zeros here. + + RollupStore storage rollupStore = STFLib.getStorage(); + // If we can prune, we use the proven block, otherwise the pending block + uint256 blockOfInterest = STFLib.canPruneAtTime(_timestamp) + ? rollupStore.tips.provenBlockNumber + : rollupStore.tips.pendingBlockNumber; + + return FeeMath.getManaBaseFeeComponentsAt( + rollupStore.blocks[blockOfInterest].feeHeader, + getL1FeesAt(_timestamp), + rollupStore.provingCostPerMana, + _inFeeAsset ? getFeeAssetPerEth() : FeeAssetPerEthE9.wrap(1e9), + TimeLib.getStorage().epochDuration + ); + } + + /** + * @notice Validates the header for submission + * + * @param _header - The proposed block header + * @param _signatures - The signatures for the attestations + * @param _digest - The digest that signatures signed + * @param _currentTime - The time of execution + * @param _blobsHashesCommitment - The blobs hash for this block + * @dev - This value is provided to allow for simple simulation of future + * @param _flags - Flags specific to the execution, whether certain checks should be skipped + */ + function validateHeader( + Header memory _header, + Signature[] memory _signatures, + bytes32 _digest, + Timestamp _currentTime, + uint256 _manaBaseFee, + bytes32 _blobsHashesCommitment, + DataStructures.ExecutionFlags memory _flags + ) internal view { + RollupStore storage rollupStore = STFLib.getStorage(); + uint256 pendingBlockNumber = STFLib.canPruneAtTime(_currentTime) + ? rollupStore.tips.provenBlockNumber + : rollupStore.tips.pendingBlockNumber; + + validateHeaderForSubmissionBase( + ValidateHeaderArgs({ + header: _header, + currentTime: _currentTime, + manaBaseFee: _manaBaseFee, + blobsHashesCommitment: _blobsHashesCommitment, + pendingBlockNumber: pendingBlockNumber, + flags: _flags + }) + ); + validateHeaderForSubmissionSequencerSelection( + Slot.wrap(_header.globalVariables.slotNumber), _signatures, _digest, _currentTime, _flags + ); + } + function digest(ProposeArgs memory _args) internal pure returns (bytes32) { return keccak256(abi.encode(SignatureLib.SignatureDomainSeparator.blockAttestation, _args)); } + + function validateHeaderForSubmissionBase(ValidateHeaderArgs memory _args) private view { + RollupStore storage rollupStore = STFLib.getStorage(); + require( + block.chainid == _args.header.globalVariables.chainId, + Errors.Rollup__InvalidChainId(block.chainid, _args.header.globalVariables.chainId) + ); + + require( + _args.header.globalVariables.version == rollupStore.config.version, + Errors.Rollup__InvalidVersion( + rollupStore.config.version, _args.header.globalVariables.version + ) + ); + + require( + _args.header.globalVariables.blockNumber == _args.pendingBlockNumber + 1, + Errors.Rollup__InvalidBlockNumber( + _args.pendingBlockNumber + 1, _args.header.globalVariables.blockNumber + ) + ); + + bytes32 tipArchive = rollupStore.blocks[_args.pendingBlockNumber].archive; + require( + tipArchive == _args.header.lastArchive.root, + Errors.Rollup__InvalidArchive(tipArchive, _args.header.lastArchive.root) + ); + + Slot slot = Slot.wrap(_args.header.globalVariables.slotNumber); + Slot lastSlot = rollupStore.blocks[_args.pendingBlockNumber].slotNumber; + require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); + + Timestamp timestamp = TimeLib.toTimestamp(slot); + require( + Timestamp.wrap(_args.header.globalVariables.timestamp) == timestamp, + Errors.Rollup__InvalidTimestamp( + timestamp, Timestamp.wrap(_args.header.globalVariables.timestamp) + ) + ); + + // @note If you are hitting this error, it is likely because the chain you use have a blocktime that differs + // from the value that we have in the constants. + // When you are encountering this, it will likely be as the sequencer expects to be able to include + // an Aztec block in the "next" ethereum block based on a timestamp that is 12 seconds in the future + // from the last block. However, if the actual will only be 1 second in the future, you will end up + // expecting this value to be in the future. + require( + timestamp <= _args.currentTime, Errors.Rollup__TimestampInFuture(_args.currentTime, timestamp) + ); + + // Check if the data is available + require( + _args.flags.ignoreDA + || _args.header.contentCommitment.blobsHash == _args.blobsHashesCommitment, + Errors.Rollup__UnavailableTxs(_args.header.contentCommitment.blobsHash) + ); + + // If not canonical rollup, require that the fees are zero + if (address(this) != rollupStore.config.feeAssetPortal.canonicalRollup()) { + require(_args.header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); + require(_args.header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee()); + } else { + require(_args.header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); + require( + _args.header.globalVariables.gasFees.feePerL2Gas == _args.manaBaseFee, + Errors.Rollup__InvalidManaBaseFee( + _args.manaBaseFee, _args.header.globalVariables.gasFees.feePerL2Gas + ) + ); + } + } + + /** + * @notice Validate a header for submission to the pending chain (sequencer selection checks) + * + * These validation checks are directly related to sequencer selection. + * Note that while these checks are strict, they can be relaxed with some changes to + * message boxes. + * + * Each of the following validation checks must pass, otherwise an error is thrown and we revert. + * - The slot MUST be the current slot + * This might be relaxed for allow consensus set to better handle short-term bursts of L1 congestion + * - The slot MUST be in the current epoch + * + * @param _slot - The slot of the header to validate + * @param _signatures - The signatures to validate + * @param _digest - The digest that signatures sign over + */ + function validateHeaderForSubmissionSequencerSelection( + Slot _slot, + Signature[] memory _signatures, + bytes32 _digest, + Timestamp _currentTime, + DataStructures.ExecutionFlags memory _flags + ) private view { + // Ensure that the slot proposed is NOT in the future + Slot currentSlot = _currentTime.slotFromTimestamp(); + require(_slot == currentSlot, Errors.HeaderLib__InvalidSlotNumber(currentSlot, _slot)); + + // @note We are currently enforcing that the slot is in the current epoch + // If this is not the case, there could potentially be a weird reorg + // of an entire epoch if no-one from the new epoch committee have seen + // those blocks or behaves as if they did not. + + Epoch epochNumber = _slot.epochFromSlot(); + Epoch currentEpoch = _currentTime.epochFromTimestamp(); + require(epochNumber == currentEpoch, Errors.Rollup__InvalidEpoch(currentEpoch, epochNumber)); + + ValidatorSelectionLib.validateValidatorSelection( + StakingLib.getStorage(), _slot, epochNumber, _signatures, _digest, _flags + ); + } + + // Helper to avoid stack too deep + function toBlockLog( + ProposeArgs calldata _args, + uint256 _blockNumber, + uint256 _congestionCost, + uint256 _provingCost + ) private view returns (BlockLog memory) { + RollupStore storage rollupStore = STFLib.getStorage(); + FeeHeader memory parentFeeHeader = rollupStore.blocks[_blockNumber - 1].feeHeader; + return BlockLog({ + archive: _args.archive, + blockHash: _args.blockHash, + slotNumber: Slot.wrap(uint256(bytes32(_args.header[0x0194:0x01b4]))), + feeHeader: FeeHeader({ + excessMana: FeeMath.computeExcessMana(parentFeeHeader), + feeAssetPriceNumerator: FeeMath.clampedAdd( + parentFeeHeader.feeAssetPriceNumerator, _args.oracleInput.feeAssetPriceModifier + ), + manaUsed: uint256(bytes32(_args.header[0x0268:0x0288])), + congestionCost: _congestionCost, + provingCost: _provingCost + }) + }); + } } diff --git a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol deleted file mode 100644 index 2d357a22eff9..000000000000 --- a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {BlockLog} from "@aztec/core/interfaces/IRollup.sol"; -import {DataStructures} from "./../DataStructures.sol"; -import {Errors} from "./../Errors.sol"; -import {Timestamp, Slot} from "./../TimeLib.sol"; -import {TimeLib} from "./../TimeLib.sol"; -import {Header} from "./HeaderLib.sol"; - -struct ValidateHeaderArgs { - Header header; - Timestamp currentTime; - uint256 manaBaseFee; - bytes32 blobsHashesCommitment; - uint256 pendingBlockNumber; - DataStructures.ExecutionFlags flags; - uint256 version; - IFeeJuicePortal feeJuicePortal; -} - -library ValidationLib { - function validateHeaderForSubmissionBase( - ValidateHeaderArgs memory _args, - mapping(uint256 blockNumber => BlockLog log) storage _blocks - ) internal view { - require( - block.chainid == _args.header.globalVariables.chainId, - Errors.Rollup__InvalidChainId(block.chainid, _args.header.globalVariables.chainId) - ); - - require( - _args.header.globalVariables.version == _args.version, - Errors.Rollup__InvalidVersion(_args.version, _args.header.globalVariables.version) - ); - - require( - _args.header.globalVariables.blockNumber == _args.pendingBlockNumber + 1, - Errors.Rollup__InvalidBlockNumber( - _args.pendingBlockNumber + 1, _args.header.globalVariables.blockNumber - ) - ); - - bytes32 tipArchive = _blocks[_args.pendingBlockNumber].archive; - require( - tipArchive == _args.header.lastArchive.root, - Errors.Rollup__InvalidArchive(tipArchive, _args.header.lastArchive.root) - ); - - Slot slot = Slot.wrap(_args.header.globalVariables.slotNumber); - Slot lastSlot = _blocks[_args.pendingBlockNumber].slotNumber; - require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); - - Timestamp timestamp = TimeLib.toTimestamp(slot); - require( - Timestamp.wrap(_args.header.globalVariables.timestamp) == timestamp, - Errors.Rollup__InvalidTimestamp( - timestamp, Timestamp.wrap(_args.header.globalVariables.timestamp) - ) - ); - - // @note If you are hitting this error, it is likely because the chain you use have a blocktime that differs - // from the value that we have in the constants. - // When you are encountering this, it will likely be as the sequencer expects to be able to include - // an Aztec block in the "next" ethereum block based on a timestamp that is 12 seconds in the future - // from the last block. However, if the actual will only be 1 second in the future, you will end up - // expecting this value to be in the future. - require( - timestamp <= _args.currentTime, Errors.Rollup__TimestampInFuture(_args.currentTime, timestamp) - ); - - // Check if the data is available - require( - _args.flags.ignoreDA - || _args.header.contentCommitment.blobsHash == _args.blobsHashesCommitment, - Errors.Rollup__UnavailableTxs(_args.header.contentCommitment.blobsHash) - ); - - // If not canonical rollup, require that the fees are zero - if (address(this) != _args.feeJuicePortal.canonicalRollup()) { - require(_args.header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); - require(_args.header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee()); - } else { - require(_args.header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); - require( - _args.header.globalVariables.gasFees.feePerL2Gas == _args.manaBaseFee, - Errors.Rollup__InvalidManaBaseFee( - _args.manaBaseFee, _args.header.globalVariables.gasFees.feePerL2Gas - ) - ); - } - } -} diff --git a/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol index e5f86e9f3ba4..10b5e185a764 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol @@ -3,8 +3,8 @@ pragma solidity >=0.8.27; import {RollupStore, IRollupCore} from "@aztec/core/interfaces/IRollup.sol"; -import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; library STFLib { using TimeLib for Slot; @@ -13,6 +13,19 @@ library STFLib { bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage"); + function prune() internal { + RollupStore storage rollupStore = STFLib.getStorage(); + uint256 pending = rollupStore.tips.pendingBlockNumber; + + // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. + // We can do because any new block proposed will overwrite a previous block in the block log, + // so no values should "survive". + // People must therefore read the chain using the pendingTip as a boundary. + rollupStore.tips.pendingBlockNumber = rollupStore.tips.provenBlockNumber; + + emit IRollupCore.PrunedPending(rollupStore.tips.provenBlockNumber, pending); + } + function getEpochForBlock(uint256 _blockNumber) internal view returns (Epoch) { RollupStore storage rollupStore = STFLib.getStorage(); require( @@ -35,19 +48,6 @@ library STFLib { return deadline < _ts.slotFromTimestamp(); } - function prune() internal { - RollupStore storage rollupStore = STFLib.getStorage(); - uint256 pending = rollupStore.tips.pendingBlockNumber; - - // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. - // We can do because any new block proposed will overwrite a previous block in the block log, - // so no values should "survive". - // People must therefore read the chain using the pendingTip as a boundary. - rollupStore.tips.pendingBlockNumber = rollupStore.tips.provenBlockNumber; - - emit IRollupCore.PrunedPending(rollupStore.tips.provenBlockNumber, pending); - } - function getStorage() internal pure returns (RollupStore storage storageStruct) { bytes32 position = STF_STORAGE_POSITION; assembly { diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 9a5346fc8ea9..a1153e990c7d 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -107,8 +107,8 @@ contract RollupTest is RollupBase { ) ) ); - inbox = Inbox(address(rollup.INBOX())); - outbox = Outbox(address(rollup.OUTBOX())); + inbox = Inbox(address(rollup.getInbox())); + outbox = Outbox(address(rollup.getOutbox())); registry.upgrade(address(rollup)); diff --git a/l1-contracts/test/base/RollupBase.sol b/l1-contracts/test/base/RollupBase.sol index c3b3aeb5db46..b1219881f636 100644 --- a/l1-contracts/test/base/RollupBase.sol +++ b/l1-contracts/test/base/RollupBase.sol @@ -228,7 +228,7 @@ contract RollupBase is DecoderBase { l2ToL1MessageTreeRoot = tree.computeRoot(); } - outbox = Outbox(address(rollup.OUTBOX())); + outbox = Outbox(address(rollup.getOutbox())); (bytes32 root,) = outbox.getRootData(full.block.decodedHeader.globalVariables.blockNumber); // If we are trying to read a block beyond the proven chain, we should see "nothing". @@ -242,7 +242,7 @@ contract RollupBase is DecoderBase { } function _populateInbox(address _sender, bytes32 _recipient, bytes32[] memory _contents) internal { - inbox = Inbox(address(rollup.INBOX())); + inbox = Inbox(address(rollup.getInbox())); for (uint256 i = 0; i < _contents.length; i++) { vm.prank(_sender); diff --git a/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol b/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol index f6737875e9a4..dae43aea230d 100644 --- a/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol +++ b/l1-contracts/test/fee_portal/depositToAztecPublic.t.sol @@ -110,7 +110,7 @@ contract DepositToAztecPublic is Test { token.mint(address(this), amount); token.approve(address(feeJuicePortal), amount); - Inbox inbox = Inbox(address(Rollup(address(registry.getRollup())).INBOX())); + Inbox inbox = Inbox(address(Rollup(address(registry.getRollup())).getInbox())); assertEq(inbox.totalMessagesInserted(), 0); vm.expectEmit(true, true, true, true, address(inbox)); diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index d1c3997f3dd0..be752ffc0ba9 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -15,16 +15,8 @@ import {Registry} from "@aztec/governance/Registry.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; -import { - Rollup, - Config, - BlockLog, - L1FeeData, - FeeHeader, - ManaBaseFeeComponents, - SubmitEpochRootProofArgs -} from "@aztec/core/Rollup.sol"; -import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; +import {Rollup, Config, BlockLog} from "@aztec/core/Rollup.sol"; +import {IRollup, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MerkleTestUtil} from "../merkle/TestUtil.sol"; @@ -43,7 +35,10 @@ import { MANA_TARGET, MINIMUM_CONGESTION_MULTIPLIER, FeeAssetPerEthE9, - EthValue + EthValue, + FeeHeader, + L1FeeData, + ManaBaseFeeComponents } from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import { diff --git a/l1-contracts/test/portals/TokenPortal.sol b/l1-contracts/test/portals/TokenPortal.sol index 0b8e82924bb6..0c89cc05664b 100644 --- a/l1-contracts/test/portals/TokenPortal.sol +++ b/l1-contracts/test/portals/TokenPortal.sol @@ -56,7 +56,7 @@ contract TokenPortal { // docs:end:deposit_public { // Preamble - IInbox inbox = IRollup(registry.getRollup()).INBOX(); + IInbox inbox = IRollup(registry.getRollup()).getInbox(); DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, 1); // Hash the message content to be reconstructed in the receiving contract @@ -90,7 +90,7 @@ contract TokenPortal { // docs:end:deposit_private { // Preamble - IInbox inbox = IRollup(registry.getRollup()).INBOX(); + IInbox inbox = IRollup(registry.getRollup()).getInbox(); DataStructures.L2Actor memory actor = DataStructures.L2Actor(l2Bridge, 1); // Hash the message content to be reconstructed in the receiving contract - the signature below does not correspond @@ -146,7 +146,7 @@ contract TokenPortal { ) }); - IOutbox outbox = IRollup(registry.getRollup()).OUTBOX(); + IOutbox outbox = IRollup(registry.getRollup()).getOutbox(); outbox.consume(message, _l2BlockNumber, _leafIndex, _path); diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index 14eb68439885..a9c737ac7186 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -72,8 +72,8 @@ contract TokenPortalTest is Test { bytes32(0), address(this) ); - inbox = rollup.INBOX(); - outbox = rollup.OUTBOX(); + inbox = rollup.getInbox(); + outbox = rollup.getOutbox(); registry.upgrade(address(rollup)); diff --git a/l1-contracts/test/portals/UniswapPortal.sol b/l1-contracts/test/portals/UniswapPortal.sol index 37e02ee69bb6..cd169a5fad4a 100644 --- a/l1-contracts/test/portals/UniswapPortal.sol +++ b/l1-contracts/test/portals/UniswapPortal.sol @@ -106,7 +106,7 @@ contract UniswapPortal { // Consume the message from the outbox { - IOutbox outbox = IRollup(registry.getRollup()).OUTBOX(); + IOutbox outbox = IRollup(registry.getRollup()).getOutbox(); outbox.consume( DataStructures.L2ToL1Msg({ @@ -211,7 +211,7 @@ contract UniswapPortal { // Consume the message from the outbox { - IOutbox outbox = IRollup(registry.getRollup()).OUTBOX(); + IOutbox outbox = IRollup(registry.getRollup()).getOutbox(); outbox.consume( DataStructures.L2ToL1Msg({ diff --git a/l1-contracts/test/portals/UniswapPortal.t.sol b/l1-contracts/test/portals/UniswapPortal.t.sol index 475a8f63ac91..7edaabe9c81c 100644 --- a/l1-contracts/test/portals/UniswapPortal.t.sol +++ b/l1-contracts/test/portals/UniswapPortal.t.sol @@ -87,7 +87,7 @@ contract UniswapPortalTest is Test { // have DAI locked in portal that can be moved when funds are withdrawn deal(address(DAI), address(daiTokenPortal), amount); - outbox = rollup.OUTBOX(); + outbox = rollup.getOutbox(); } /** diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index c4ae9c5330ce..92a522630d41 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -124,8 +124,8 @@ contract ValidatorSelectionTest is DecoderBase { testERC20.approve(address(rollup), TestConstants.AZTEC_MINIMUM_STAKE * _validatorCount); rollup.cheat__InitialiseValidatorSet(initialValidators); - inbox = Inbox(address(rollup.INBOX())); - outbox = Outbox(address(rollup.OUTBOX())); + inbox = Inbox(address(rollup.getInbox())); + outbox = Outbox(address(rollup.getOutbox())); merkleTestUtil = new MerkleTestUtil(); diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index f77a94bc9188..8b2fc6d561f9 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -186,8 +186,8 @@ export class RollupContract { stakingAssetAddress, ] = ( await Promise.all([ - this.rollup.read.INBOX(), - this.rollup.read.OUTBOX(), + this.rollup.read.getInbox(), + this.rollup.read.getOutbox(), this.rollup.read.getFeeAssetPortal(), this.rollup.read.getRewardDistributor(), this.rollup.read.getFeeAsset(), diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 2fb5a9bf5f09..79cd96fefd00 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -502,10 +502,10 @@ export const deployL1Contracts = async ( } // Inbox and Outbox are immutable and are deployed from Rollup's constructor so we just fetch them from the contract. - const inboxAddress = EthAddress.fromString((await rollup.read.INBOX()) as any); + const inboxAddress = EthAddress.fromString((await rollup.read.getInbox()) as any); logger.verbose(`Inbox available at ${inboxAddress}`); - const outboxAddress = EthAddress.fromString((await rollup.read.OUTBOX()) as any); + const outboxAddress = EthAddress.fromString((await rollup.read.getOutbox()) as any); logger.verbose(`Outbox available at ${outboxAddress}`); // We need to call a function on the registry to set the various contract addresses. diff --git a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts index 66c44a148e3f..7a1036fbd8e3 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts @@ -80,7 +80,7 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { feeRecipient: AztecAddress, slotNumber?: bigint, ): Promise { - const version = new Fr(await this.rollupContract.read.VERSION()); + const version = new Fr(await this.rollupContract.read.getVersion()); const chainId = new Fr(this.publicClient.chain.id); if (slotNumber === undefined) { From 7248ce85677a6df8dead3bbb9dbffc6cab8edc0d Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 26 Feb 2025 13:39:57 +0000 Subject: [PATCH 4/5] chore: update library linking and storage slot in markAsProven cheatcode --- l1-contracts/src/core/Rollup.sol | 2 +- l1-contracts/src/core/RollupCore.sol | 6 ++---- .../src/core/libraries/RollupLibs/ExtRollupLib.sol | 11 ++++++++++- .../src/core/libraries/RollupLibs/core/STFLib.sol | 1 + .../ValidatorSelectionLib/ValidatorSelectionLib.sol | 10 +++++----- .../src/core/libraries/staking/StakingLib.sol | 12 ++++++------ .../aztec.js/src/api/ethereum/cheat_codes.ts | 13 +++++-------- yarn-project/ethereum/src/deploy_l1_contracts.ts | 6 ------ .../l1-artifacts/scripts/generate-artifacts.sh | 1 - 9 files changed, 30 insertions(+), 32 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 7937622923f1..665a64bd07e1 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -16,6 +16,7 @@ import {FeeAssetValue} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import {FeeMath} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import {HeaderLib} from "@aztec/core/libraries/RollupLibs/HeaderLib.sol"; import {EpochProofLib} from "./libraries/RollupLibs/EpochProofLib.sol"; +import {ValidatorSelectionLib} from "./libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol"; import { RollupCore, Config, @@ -23,7 +24,6 @@ import { IFeeJuicePortal, IERC20, BlockLog, - ValidatorSelectionLib, StakingLib, TimeLib, Slot, diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index eee458ffb3a9..45c6c4c34a65 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -30,8 +30,6 @@ import {EthValue, FeeAssetPerEthE9, PriceLib} from "@aztec/core/libraries/Rollup import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import {ValidatorSelectionLib} from - "@aztec/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; import {Slasher} from "@aztec/core/staking/Slasher.sol"; @@ -98,7 +96,7 @@ contract RollupCore is Timestamp exitDelay = Timestamp.wrap(60 * 60 * 24); Slasher slasher = new Slasher(_config.slashingQuorum, _config.slashingRoundSize); StakingLib.initialize(_stakingAsset, _config.minimumStake, exitDelay, address(slasher)); - ValidatorSelectionLib.initialize(_config.targetCommitteeSize); + ExtRollupLib.initializeValidatorSelection(_config.targetCommitteeSize); L1_BLOCK_AT_GENESIS = block.number; @@ -327,7 +325,7 @@ contract RollupCore is } function setupEpoch() public override(IValidatorSelectionCore) { - ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); + ExtRollupLib.setupEpoch(); } /** diff --git a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol index 10fc5a5ced3b..c94ab46fb0d2 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -3,7 +3,8 @@ pragma solidity >=0.8.27; import {SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; - +import {StakingLib} from "./../staking/StakingLib.sol"; +import {ValidatorSelectionLib} from "./../ValidatorSelectionLib/ValidatorSelectionLib.sol"; import {BlobLib} from "./BlobLib.sol"; import {EpochProofLib} from "./EpochProofLib.sol"; import {ProposeLib, ProposeArgs, Signature} from "./ProposeLib.sol"; @@ -26,6 +27,14 @@ library ExtRollupLib { ProposeLib.propose(_args, _signatures, _body, _blobInput, _checkBlob); } + function initializeValidatorSelection(uint256 _targetCommitteeSize) external { + ValidatorSelectionLib.initialize(_targetCommitteeSize); + } + + function setupEpoch() external { + ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); + } + function getEpochProofPublicInputs( uint256 _start, uint256 _end, diff --git a/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol index 10b5e185a764..cfd9539d8fdd 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol @@ -11,6 +11,7 @@ library STFLib { using TimeLib for Epoch; using TimeLib for Timestamp; + // @note This is also used in the cheatcodes, so if updating, please also update the cheatcode. bytes32 private constant STF_STORAGE_POSITION = keccak256("aztec.stf.storage"); function prune() internal { diff --git a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol index d2830f4a8cd5..6a1afb2fa5b1 100644 --- a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol @@ -33,7 +33,7 @@ library ValidatorSelectionLib { * This is very heavy on gas, so start crying because the gas here will melt the poles * https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp */ - function setupEpoch(StakingStorage storage _stakingStore) external { + function setupEpoch(StakingStorage storage _stakingStore) internal { Epoch epochNumber = Timestamp.wrap(block.timestamp).epochFromTimestamp(); ValidatorSelectionStorage storage store = getStorage(); EpochData storage epoch = store.epochs[epochNumber]; @@ -68,7 +68,7 @@ library ValidatorSelectionLib { Signature[] memory _signatures, bytes32 _digest, DataStructures.ExecutionFlags memory _flags - ) external view { + ) internal view { // Same logic as we got in getProposerAt // Done do avoid duplicate computing the committee address[] memory committee = getCommitteeAt(_stakingStore, _epochNumber); @@ -123,7 +123,7 @@ library ValidatorSelectionLib { } function getProposerAt(StakingStorage storage _stakingStore, Slot _slot, Epoch _epochNumber) - external + internal view returns (address) { @@ -152,7 +152,7 @@ library ValidatorSelectionLib { * @return The validators for the given epoch */ function sampleValidators(StakingStorage storage _stakingStore, uint256 _seed) - public + internal view returns (address[] memory) { @@ -180,7 +180,7 @@ library ValidatorSelectionLib { } function getCommitteeAt(StakingStorage storage _stakingStore, Epoch _epochNumber) - public + internal view returns (address[] memory) { diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 6c02dfdf54d6..1c0c24ff0802 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -26,7 +26,7 @@ library StakingLib { uint256 _minimumStake, Timestamp _exitDelay, address _slasher - ) external { + ) internal { StakingStorage storage store = getStorage(); store.stakingAsset = _stakingAsset; store.minimumStake = _minimumStake; @@ -34,7 +34,7 @@ library StakingLib { store.slasher = _slasher; } - function finaliseWithdraw(address _attester) external { + function finaliseWithdraw(address _attester) internal { StakingStorage storage store = getStorage(); ValidatorInfo storage validator = store.info[_attester]; require(validator.status == Status.EXITING, Errors.Staking__NotExiting(_attester)); @@ -56,7 +56,7 @@ library StakingLib { emit IStakingCore.WithdrawFinalised(_attester, recipient, amount); } - function slash(address _attester, uint256 _amount) external { + function slash(address _attester, uint256 _amount) internal { StakingStorage storage store = getStorage(); require(msg.sender == store.slasher, Errors.Staking__NotSlasher(store.slasher, msg.sender)); @@ -85,7 +85,7 @@ library StakingLib { } function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) - external + internal { require( _attester != address(0) && _proposer != address(0), @@ -113,7 +113,7 @@ library StakingLib { emit IStakingCore.Deposit(_attester, _proposer, _withdrawer, _amount); } - function initiateWithdraw(address _attester, address _recipient) external returns (bool) { + function initiateWithdraw(address _attester, address _recipient) internal returns (bool) { StakingStorage storage store = getStorage(); ValidatorInfo storage validator = store.info[_attester]; @@ -140,7 +140,7 @@ library StakingLib { return true; } - function getStorage() public pure returns (StakingStorage storage storageStruct) { + function getStorage() internal pure returns (StakingStorage storage storageStruct) { bytes32 position = STAKING_SLOT; assembly { storageStruct.slot := position diff --git a/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts b/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts index c51d0389ed73..1bb16e11ff01 100644 --- a/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts +++ b/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts @@ -2,7 +2,7 @@ import { EthCheatCodes } from '@aztec/ethereum/eth-cheatcodes'; import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; -import { RollupAbi, RollupStorage } from '@aztec/l1-artifacts'; +import { RollupAbi } from '@aztec/l1-artifacts'; import { type GetContractReturnType, @@ -12,6 +12,7 @@ import { createWalletClient, getContract, http, + keccak256, publicActions, } from 'viem'; import { foundry } from 'viem/chains'; @@ -124,13 +125,9 @@ export class RollupCheatCodes { // @note @LHerskind this is heavily dependent on the storage layout and size of values // The rollupStore is a struct and if the size of elements or the struct changes, this can break - const storageSlot = RollupStorage.find( - // eslint-disable-next-line jsdoc/require-jsdoc - (storage: { label: string; slot: string }) => storage.label === 'rollupStore', - )?.slot; - if (storageSlot === undefined) { - throw new Error('rollupStoreStorageSlot not found'); - } + + // Convert string to bytes and then compute keccak256 + const storageSlot = keccak256(Buffer.from('aztec.stf.storage', 'utf-8')); const provenBlockNumberSlot = BigInt(storageSlot) + 1n; const tipsBefore = await this.getTips(); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 79cd96fefd00..b3eeaee8dab8 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -27,8 +27,6 @@ import { RollupLinkReferences, SlashFactoryAbi, SlashFactoryBytecode, - StakingLibAbi, - StakingLibBytecode, TestERC20Abi, TestERC20Bytecode, ValidatorSelectionLibAbi, @@ -143,10 +141,6 @@ export const l1Artifacts = { contractAbi: ExtRollupLibAbi, contractBytecode: ExtRollupLibBytecode as Hex, }, - StakingLib: { - contractAbi: StakingLibAbi, - contractBytecode: StakingLibBytecode as Hex, - }, }, }, }, diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index ae44c3ad20da..3c8a26f15d9f 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -35,7 +35,6 @@ contracts=( "SlashFactory" "Forwarder" "HonkVerifier" - "StakingLib" ) # Combine error ABIs once, removing duplicates by {type, name}. From 2084d1224961758bb2115ce6a33c4eeac37957b6 Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 26 Feb 2025 14:41:00 +0000 Subject: [PATCH 5/5] chore: add cheatcode test + fix function ordering --- .../ValidatorSelectionLib.sol | 10 ++++---- .../end-to-end/src/e2e_cheat_codes.test.ts | 25 +++++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol index 6a1afb2fa5b1..32e87d134369 100644 --- a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol @@ -23,6 +23,11 @@ library ValidatorSelectionLib { bytes32 private constant VALIDATOR_SELECTION_STORAGE_POSITION = keccak256("aztec.validator_selection.storage"); + function initialize(uint256 _targetCommitteeSize) internal { + ValidatorSelectionStorage storage store = getStorage(); + store.targetCommitteeSize = _targetCommitteeSize; + } + /** * @notice Performs a setup of an epoch if needed. The setup will * - Sample the validator set for the epoch @@ -205,11 +210,6 @@ library ValidatorSelectionLib { return sampleValidators(_stakingStore, sampleSeed); } - function initialize(uint256 _targetCommitteeSize) internal { - ValidatorSelectionStorage storage store = getStorage(); - store.targetCommitteeSize = _targetCommitteeSize; - } - /** * @notice Get the sample seed for an epoch * diff --git a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts index c8d610544542..46c59a0754c7 100644 --- a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts +++ b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts @@ -1,4 +1,5 @@ -import { type AztecAddress, type CheatCodes, EthAddress, Fr, type Wallet } from '@aztec/aztec.js'; +import { AnvilTestWatcher, type AztecAddress, type CheatCodes, EthAddress, Fr, type Wallet } from '@aztec/aztec.js'; +import { RollupContract } from '@aztec/ethereum/contracts'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { type Account, type Chain, type HttpTransport, type PublicClient, type WalletClient, parseEther } from 'viem'; @@ -15,15 +16,23 @@ describe('e2e_cheat_codes', () => { let walletClient: WalletClient; let publicClient: PublicClient; let token: TokenContract; + let rollup: RollupContract; + let watcher: AnvilTestWatcher | undefined; beforeAll(async () => { let deployL1ContractsValues; - ({ teardown, wallet, cheatCodes: cc, deployL1ContractsValues } = await setup()); + ({ teardown, wallet, cheatCodes: cc, deployL1ContractsValues, watcher } = await setup()); walletClient = deployL1ContractsValues.walletClient; publicClient = deployL1ContractsValues.publicClient; admin = wallet.getAddress(); + rollup = RollupContract.getFromL1ContractsValues(deployL1ContractsValues); + + if (watcher) { + watcher.setIsMarkingAsProven(false); + } + token = await TokenContract.deploy(wallet, admin, 'TokenName', 'TokenSymbol', 18).send().deployed(); }); @@ -170,5 +179,17 @@ describe('e2e_cheat_codes', () => { expect(balance).toEqual(mintAmount); // docs:end:load_private_cheatcode }); + + it('markAsProven', async () => { + const { pendingBlockNumber, provenBlockNumber } = await rollup.getTips(); + expect(pendingBlockNumber).toBeGreaterThan(provenBlockNumber); + + await cc.rollup.markAsProven(); + + const { pendingBlockNumber: pendingBlockNumber2, provenBlockNumber: provenBlockNumber2 } = await rollup.getTips(); + expect(pendingBlockNumber2).toBe(provenBlockNumber2); + + // If this test fails, it is likely because the storage updated and is not updated in the cheatcodes. + }); }); });