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 841943579620..665a64bd07e1 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -11,10 +11,12 @@ 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 {ValidatorSelectionLib} from "./libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol"; import { RollupCore, Config, @@ -22,11 +24,6 @@ import { IFeeJuicePortal, IERC20, BlockLog, - FeeHeader, - ManaBaseFeeComponents, - SubmitEpochRootProofArgs, - L1FeeData, - ValidatorSelectionLib, StakingLib, TimeLib, Slot, @@ -34,15 +31,16 @@ import { Timestamp, Errors, Signature, - DataStructures, ExtRollupLib, - IntRollupLib, - EpochRewards, - FeeAssetPerEthE9, EthValue, - PriceLib + PriceLib, + STFLib, + RollupStore, + IInbox, + IOutbox, + ProposeLib, + EpochRewards } from "./RollupCore.sol"; -// solhint-enable no-unused-import /** * @title Rollup @@ -57,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( @@ -121,7 +118,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 +134,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 +167,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 + _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject ); } @@ -194,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, @@ -224,18 +222,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 +249,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) @@ -440,7 +440,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) @@ -449,7 +449,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 +458,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 +477,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 +498,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 +511,35 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { override(IRollup) returns (FeeAssetValue) { - return rollupStore.provingCostPerMana.toFeeAsset(getFeeAssetPerEth()); + return STFLib.getStorage().provingCostPerMana.toFeeAsset(getFeeAssetPerEth()); + } + + function getCuauhxicalli() external view override(IRollup) returns (address) { + 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; + } + + 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; } /** @@ -529,18 +558,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 = @@ -567,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)); } /** @@ -578,6 +610,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..45c6c4c34a65 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,22 +22,14 @@ 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"; -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"; @@ -47,7 +38,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"; struct Config { uint256 aztecSlotDuration; @@ -59,26 +49,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 @@ -95,8 +65,6 @@ contract RollupCore is ITestRollup { using ProposeLib for ProposeArgs; - using IntRollupLib for uint256; - using IntRollupLib for ManaBaseFeeComponents; using PriceLib for EthValue; @@ -104,35 +72,14 @@ 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); - - // 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")); - - 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; - 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 bool public checkBlob = true; - RollupStore internal rollupStore; - constructor( IFeeJuicePortal _fpcJuicePortal, IRewardDistributor _rewardDistributor, @@ -146,26 +93,28 @@ 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; + ExtRollupLib.initializeValidatorSelection(_config.targetCommitteeSize); + L1_BLOCK_AT_GENESIS = block.number; - IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; + RollupStore storage rollupStore = STFLib.getStorage(); + + rollupStore.config.proofSubmissionWindow = _config.aztecProofSubmissionWindow; + rollupStore.config.feeAsset = _fpcJuicePortal.UNDERLYING(); + rollupStore.config.feeAssetPortal = _fpcJuicePortal; + rollupStore.config.rewardDistributor = _rewardDistributor; - rollupStore.epochProofVerifier = new MockVerifier(); - rollupStore.vkTreeRoot = _vkTreeRoot; - rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + 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 @@ -183,8 +132,8 @@ contract RollupCore is }); rollupStore.l1GasOracleValues = L1GasOracleValues({ pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), - post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee(VM_ADDRESS)}), - slotOfChange: LIFETIME + post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee()}), + slotOfChange: ProposeLib.LIFETIME }); } @@ -193,7 +142,7 @@ contract RollupCore is override(IRollupCore) onlyOwner { - rollupStore.provingCostPerMana = _provingCostPerMana; + STFLib.getStorage().provingCostPerMana = _provingCostPerMana; } function claimSequencerRewards(address _recipient) @@ -201,9 +150,10 @@ 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); + rollupStore.config.feeAsset.transfer(_recipient, amount); return amount; } @@ -214,9 +164,12 @@ 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)); // We can use fancier bitmaps for performance @@ -232,7 +185,7 @@ contract RollupCore is } } - ASSET.transfer(_recipient, accumulatedRewards); + rollupStore.config.feeAsset.transfer(_recipient, accumulatedRewards); return accumulatedRewards; } @@ -282,7 +235,7 @@ contract RollupCore is */ function prune() external override(IRollupCore) { require(canPrune(), Errors.Rollup__NothingToPrune()); - _prune(); + STFLib.prune(); } /** @@ -293,7 +246,7 @@ contract RollupCore is * @param _verifier - The new verifier contract */ function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner { - rollupStore.epochProofVerifier = IVerifier(_verifier); + STFLib.getStorage().config.epochProofVerifier = IVerifier(_verifier); } /** @@ -304,7 +257,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().config.vkTreeRoot = _vkTreeRoot; } /** @@ -319,7 +272,7 @@ contract RollupCore is override(ITestRollup) onlyOwner { - rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + STFLib.getStorage().config.protocolContractTreeRoot = _protocolContractTreeRoot; } /** @@ -348,114 +301,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()); - - 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); - } - - // @todo Get the block rewards for - - 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]); - } - - function setupEpoch() public override(IValidatorSelectionCore) { - ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); + ExtRollupLib.submitEpochRootProof(_args); } /** @@ -465,73 +311,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()) { - _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}) - }); - - 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) { + ExtRollupLib.setupEpoch(); } /** @@ -539,18 +333,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 - 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(VM_ADDRESS)}); - rollupStore.l1GasOracleValues.slotOfChange = slot + LAG; + ProposeLib.updateL1GasFeeOracle(); } /** @@ -559,9 +342,7 @@ contract RollupCore is * @return The fee asset price */ function getFeeAssetPerEth() public view override(IRollupCore) returns (FeeAssetPerEthE9) { - return IntRollupLib.getFeeAssetPerEth( - rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator - ); + return ProposeLib.getFeeAssetPerEth(); } function getL1FeesAt(Timestamp _timestamp) @@ -570,9 +351,7 @@ contract RollupCore is override(IRollupCore) returns (L1FeeData memory) { - return _timestamp.slotFromTimestamp() < rollupStore.l1GasOracleValues.slotOfChange - ? rollupStore.l1GasOracleValues.pre - : rollupStore.l1GasOracleValues.post; + return ProposeLib.getL1FeesAt(_timestamp); } /** @@ -593,26 +372,11 @@ contract RollupCore is override(ITestRollup) returns (ManaBaseFeeComponents memory) { - // 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) { - 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,132 +384,6 @@ contract RollupCore is } function canPruneAtTime(Timestamp _ts) public view override(IRollupCore) returns (bool) { - 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 { - 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); - } - - /** - * @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 { - 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: FEE_JUICE_PORTAL - }), - 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) { - 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 - }) - }); + return STFLib.canPruneAtTime(_ts); } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 54e6471295c7..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,6 +16,8 @@ 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 {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; struct SubmitEpochRootProofArgs { uint256 start; // inclusive @@ -55,22 +58,35 @@ 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; + IInbox inbox; + IOutbox outbox; + uint256 version; +} + // 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 { @@ -117,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); @@ -200,4 +210,13 @@ 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); + + 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/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/EpochProofLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol index 3c65d312507b..3ea88008b2d2 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol @@ -2,69 +2,62 @@ // 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 {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 {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; -} +import {Math} from "@oz/utils/math/Math.sol"; 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 +75,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 +82,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 +96,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 +188,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 +244,146 @@ 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], Errors.Rollup__ProverHaveAlreadySubmitted(prover, _endEpoch) + ); + $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 +396,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 +418,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 a5bbadbb88fd..c94ab46fb0d2 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -2,53 +2,40 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; - -import {BlockLog, RollupStore} from "@aztec/core/interfaces/IRollup.sol"; +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 { - 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 validateHeaderForSubmissionBase( - ValidateHeaderArgs memory _args, - mapping(uint256 blockNumber => BlockLog log) storage _blocks - ) external view { - ValidationLib.validateHeaderForSubmissionBase(_args, _blocks); + function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external { + EpochProofLib.submitEpochRootProof(_args); } - function verifyEpochRootProof( - RollupStore storage _rollupStore, - SubmitEpochRootProofArgs calldata _args - ) external view returns (bool) { - return EpochProofLib.verifyEpochRootProof(_rollupStore, _args); + 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 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 initializeValidatorSelection(uint256 _targetCommitteeSize) external { + ValidatorSelectionLib.initialize(_targetCommitteeSize); + } + + function setupEpoch() external { + ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); } function getEpochProofPublicInputs( - RollupStore storage _rollupStore, uint256 _start, uint256 _end, bytes32[7] calldata _args, @@ -57,7 +44,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 ); } @@ -73,11 +60,7 @@ library ExtRollupLib { return BlobLib.validateBlobs(_blobsInput, _checkBlob); } - function getBlobBaseFee(address _vmAddress) external view returns (uint256) { - return BlobLib.getBlobBaseFee(_vmAddress); - } - - function decodeHeader(bytes calldata _header) external pure returns (Header memory) { - return HeaderLib.decode(_header); + function getBlobBaseFee() external view returns (uint256) { + return BlobLib.getBlobBaseFee(); } } 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 new file mode 100644 index 000000000000..cfd9539d8fdd --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/core/STFLib.sol @@ -0,0 +1,58 @@ +// 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 {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; + +library STFLib { + using TimeLib for Slot; + 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 { + 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( + _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 getStorage() internal pure returns (RollupStore storage storageStruct) { + bytes32 position = STF_STORAGE_POSITION; + assembly { + storageStruct.slot := position + } + } +} diff --git a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol index d2830f4a8cd5..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 @@ -33,7 +38,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 +73,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 +128,7 @@ library ValidatorSelectionLib { } function getProposerAt(StakingStorage storage _stakingStore, Slot _slot, Epoch _epochNumber) - external + internal view returns (address) { @@ -152,7 +157,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 +185,7 @@ library ValidatorSelectionLib { } function getCommitteeAt(StakingStorage storage _stakingStore, Epoch _epochNumber) - public + internal view returns (address[] memory) { @@ -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/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/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 847b796da535..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 { @@ -157,7 +152,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 +452,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 +487,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/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/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/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. + }); }); }); diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index cb5764811269..8b2fc6d561f9 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -186,11 +186,11 @@ export class RollupContract { stakingAssetAddress, ] = ( 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.getInbox(), + this.rollup.read.getOutbox(), + this.rollup.read.getFeeAssetPortal(), + this.rollup.read.getRewardDistributor(), + this.rollup.read.getFeeAsset(), this.rollup.read.getStakingAsset(), ] as const) ).map(EthAddress.fromString); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 2fb5a9bf5f09..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, - }, }, }, }, @@ -502,10 +496,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/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}. 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) {