From cbb829a7900eb44fb29bdb09d43c5b59607eae40 Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:05:11 +0000 Subject: [PATCH 1/3] feat!: leonidas library --- .../guides/developer_guides/js_apps/test.md | 2 +- l1-contracts/src/core/Rollup.sol | 1056 ++++++----------- l1-contracts/src/core/RollupCore.sol | 790 ++++++++++++ l1-contracts/src/core/ValidatorSelection.sol | 453 ------- l1-contracts/src/core/interfaces/IRollup.sol | 72 +- l1-contracts/src/core/interfaces/IStaking.sol | 4 +- .../core/interfaces/IValidatorSelection.sol | 9 +- l1-contracts/src/core/libraries/Errors.sol | 1 + .../libraries/RollupLibs/ValidationLib.sol | 4 +- .../ValidatorSelectionLib.sol | 226 ++-- .../src/core/libraries/staking/StakingLib.sol | 16 +- l1-contracts/src/periphery/SlashPayload.sol | 4 +- l1-contracts/test/Rollup.t.sol | 19 +- l1-contracts/test/base/Base.sol | 2 +- l1-contracts/test/fees/FeeRollup.t.sol | 1 - .../governance-proposer/executeProposal.t.sol | 8 +- .../governance-proposer/mocks/Fakerollup.sol | 42 + .../governance/governance-proposer/vote.t.sol | 8 +- .../test/harnesses/ValidatorSelection.sol | 21 - l1-contracts/test/portals/UniswapPortal.t.sol | 5 + l1-contracts/test/staking/deposit.t.sol | 4 +- .../test/staking/finaliseWithdraw.t.sol | 8 +- .../test/staking/initiateWithdraw.t.sol | 10 +- l1-contracts/test/staking/slash.t.sol | 12 +- .../ValidatorSelection.t.sol | 19 +- yarn-project/epoch-cache/src/epoch_cache.ts | 2 +- yarn-project/ethereum/src/contracts/rollup.ts | 2 +- 27 files changed, 1402 insertions(+), 1398 deletions(-) create mode 100644 l1-contracts/src/core/RollupCore.sol delete mode 100644 l1-contracts/src/core/ValidatorSelection.sol create mode 100644 l1-contracts/test/governance/governance-proposer/mocks/Fakerollup.sol delete mode 100644 l1-contracts/test/harnesses/ValidatorSelection.sol diff --git a/docs/docs/guides/developer_guides/js_apps/test.md b/docs/docs/guides/developer_guides/js_apps/test.md index 19f10a7c56d..d2f90b39668 100644 --- a/docs/docs/guides/developer_guides/js_apps/test.md +++ b/docs/docs/guides/developer_guides/js_apps/test.md @@ -134,7 +134,7 @@ The [`CheatCodes`](../../../reference/developer_references/sandbox_reference/che ### Set next block timestamp Since the rollup time is dependent on what "slot" the block is included in, time can be progressed by progressing slots. -The duration of a slot is available by calling `getSlotDuration()` on the Rollup (code in Leonidas.sol). +The duration of a slot is available by calling `getSlotDuration()` on the Rollup (code in Rollup.sol). You can then use the `warp` function on the EthCheatCodes to progress the underlying chain. diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 98811e1c8b6..cd5345c95d4 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -2,102 +2,57 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; +import {IRollup, ChainTips} from "@aztec/core/interfaces/IRollup.sol"; import { - IRollup, - ITestRollup, - CheatDepositArgs, + IStaking, + ValidatorInfo, + Exit, + OperatorInfo, + EnumerableSet +} from "@aztec/core/interfaces/IStaking.sol"; +import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; + +// We allow the unused imports here as they make it much simpler to import the Rollup later +// solhint-disable no-unused-import +import { + RollupCore, + Config, + IRewardDistributor, + IFeeJuicePortal, + IERC20, + BlockLog, FeeHeader, ManaBaseFeeComponents, - BlockLog, - ChainTips, - RollupStore, - L1GasOracleValues, + SubmitEpochRootProofArgs, L1FeeData, - SubmitEpochRootProofArgs -} from "@aztec/core/interfaces/IRollup.sol"; -import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; -import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; -import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; -import {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 { + ValidatorSelectionLib, + StakingLib, + TimeLib, + Slot, + Epoch, + Timestamp, + Errors, + Signature, + DataStructures, ExtRollupLib, - ValidateHeaderArgs, - Header, - SignedEpochProofQuote, - SubmitEpochRootProofInterimValues -} from "@aztec/core/libraries/RollupLibs/ExtRollupLib.sol"; -import {IntRollupLib, EpochProofQuote} from "@aztec/core/libraries/RollupLibs/IntRollupLib.sol"; -import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; -import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; -import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; -import {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol"; -import {ValidatorSelection} from "@aztec/core/ValidatorSelection.sol"; -import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; -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"; - -struct Config { - uint256 aztecSlotDuration; - uint256 aztecEpochDuration; - uint256 targetCommitteeSize; - uint256 aztecEpochProofClaimWindowInL2Slots; - uint256 minimumStake; - uint256 slashingQuorum; - uint256 slashingRoundSize; -} + IntRollupLib +} from "./RollupCore.sol"; +// solhint-enable no-unused-import /** * @title Rollup * @author Aztec Labs - * @notice Rollup contract that is concerned about readability and velocity of development - * not giving a damn about gas costs. - * @dev WARNING: This contract is VERY close to the size limit (500B at time of writing). + * @notice A wrapper contract around the RollupCore which provides additional view functions + * which are not needed by the rollup itself to function, but makes it easy to reason + * about the state of the rollup and test it. */ -contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRollup, ITestRollup { - using ProposeLib for ProposeArgs; - using IntRollupLib for uint256; - using IntRollupLib for ManaBaseFeeComponents; - - Slot public constant LIFETIME = Slot.wrap(5); - Slot public constant LAG = Slot.wrap(2); - - // See https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8401-proof-timeliness/proof-timeliness.ipynb - // for justification of CLAIM_DURATION_IN_L2_SLOTS. - uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; - - // 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; - // @note Always true, exists to override to false for testing only - bool public checkBlob = true; - - uint256 public immutable CLAIM_DURATION_IN_L2_SLOTS; - uint256 public immutable L1_BLOCK_AT_GENESIS; - IInbox public immutable INBOX; - IOutbox public immutable OUTBOX; - IProofCommitmentEscrow public immutable PROOF_COMMITMENT_ESCROW; - uint256 public immutable VERSION; - IFeeJuicePortal public immutable FEE_JUICE_PORTAL; - IRewardDistributor public immutable REWARD_DISTRIBUTOR; - IERC20 public immutable ASSET; +contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { + using EnumerableSet for EnumerableSet.AddressSet; - RollupStore internal rollupStore; - - // @note Assume that all blocks up to this value (inclusive) are automatically proven. Speeds up bootstrapping. - // Testing only. This should be removed eventually. - uint256 private assumeProvenThroughBlockNumber; + using TimeLib for Timestamp; + using TimeLib for Slot; + using TimeLib for Epoch; + using IntRollupLib for ManaBaseFeeComponents; constructor( IFeeJuicePortal _fpcJuicePortal, @@ -108,207 +63,51 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo address _ares, Config memory _config ) - Ownable(_ares) - ValidatorSelection( + RollupCore( + _fpcJuicePortal, + _rewardDistributor, _stakingAsset, - _config.minimumStake, - _config.slashingQuorum, - _config.slashingRoundSize, - _config.aztecSlotDuration, - _config.aztecEpochDuration, - _config.targetCommitteeSize + _vkTreeRoot, + _protocolContractTreeRoot, + _ares, + _config ) - { - FEE_JUICE_PORTAL = _fpcJuicePortal; - REWARD_DISTRIBUTOR = _rewardDistributor; - ASSET = _fpcJuicePortal.UNDERLYING(); - PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow( - ASSET, address(this), _config.aztecSlotDuration, _config.aztecEpochDuration - ); - INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); - OUTBOX = IOutbox(address(new Outbox(address(this)))); - VERSION = 1; - L1_BLOCK_AT_GENESIS = block.number; - CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots; - - IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; - - rollupStore.epochProofVerifier = new MockVerifier(); - rollupStore.vkTreeRoot = _vkTreeRoot; - rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; - - // Genesis block - rollupStore.blocks[0] = BlockLog({ - feeHeader: FeeHeader({ - excessMana: 0, - feeAssetPriceNumerator: 0, - manaUsed: 0, - provingCostPerManaNumerator: 0, - congestionCost: 0 - }), - archive: bytes32(Constants.GENESIS_ARCHIVE_ROOT), - blockHash: bytes32(Constants.GENESIS_BLOCK_HASH), - slotNumber: Slot.wrap(0) - }); - rollupStore.l1GasOracleValues = L1GasOracleValues({ - pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), - post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee(VM_ADDRESS)}), - slotOfChange: LIFETIME - }); - } - - function cheat__InitialiseValidatorSet(CheatDepositArgs[] memory _args) - external - override(ITestRollup) - onlyOwner - { - for (uint256 i = 0; i < _args.length; i++) { - _cheat__Deposit(_args[i].attester, _args[i].proposer, _args[i].withdrawer, _args[i].amount); - } - setupEpoch(); + {} + + function getTargetCommitteeSize() external view override(IValidatorSelection) returns (uint256) { + return ValidatorSelectionLib.getStorage().targetCommitteeSize; } - /** - * @notice Prune the pending chain up to the last proven block - * - * @dev Will revert if there is nothing to prune or if the chain is not ready to be pruned - */ - function prune() external override(IRollup) { - require(canPrune(), Errors.Rollup__NothingToPrune()); - _prune(); + function getGenesisTime() external view override(IValidatorSelection) returns (Timestamp) { + return Timestamp.wrap(TimeLib.getStorage().genesisTime); } - /** - * Sets the assumeProvenThroughBlockNumber. Only the contract deployer can set it. - * @param _blockNumber - New value. - */ - function setAssumeProvenThroughBlockNumber(uint256 _blockNumber) - external - override(ITestRollup) - onlyOwner - { - _fakeBlockNumberAsProven(_blockNumber); - assumeProvenThroughBlockNumber = _blockNumber; + function getSlotDuration() external view override(IValidatorSelection) returns (uint256) { + return TimeLib.getStorage().slotDuration; } - /** - * @notice Set the verifier contract - * - * @dev This is only needed for testing, and should be removed - * - * @param _verifier - The new verifier contract - */ - function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner { - rollupStore.epochProofVerifier = IVerifier(_verifier); + function getEpochDuration() external view override(IValidatorSelection) returns (uint256) { + return TimeLib.getStorage().epochDuration; } - /** - * @notice Set the vkTreeRoot - * - * @dev This is only needed for testing, and should be removed - * - * @param _vkTreeRoot - The new vkTreeRoot to be used by proofs - */ - function setVkTreeRoot(bytes32 _vkTreeRoot) external override(ITestRollup) onlyOwner { - rollupStore.vkTreeRoot = _vkTreeRoot; + function getSlasher() external view override(IStaking) returns (address) { + return StakingLib.getStorage().slasher; } - /** - * @notice Set the protocolContractTreeRoot - * - * @dev This is only needed for testing, and should be removed - * - * @param _protocolContractTreeRoot - The new protocolContractTreeRoot to be used by proofs - */ - function setProtocolContractTreeRoot(bytes32 _protocolContractTreeRoot) - external - override(ITestRollup) - onlyOwner - { - rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + function getStakingAsset() external view override(IStaking) returns (IERC20) { + return StakingLib.getStorage().stakingAsset; } - /** - * @notice Publishes the body and propose the block - * @dev `eth_log_handlers` rely on this function - * - * @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 _body - The body of the L2 block - * @param _blobInput - The blob evaluation KZG proof, challenge, and opening required for the precompile. - */ - function proposeAndClaim( - ProposeArgs calldata _args, - Signature[] memory _signatures, - bytes calldata _body, - bytes calldata _blobInput, - SignedEpochProofQuote calldata _quote - ) external override(IRollup) { - propose(_args, _signatures, _body, _blobInput); - claimEpochProofRight(_quote); + function getMinimumStake() external view override(IStaking) returns (uint256) { + return StakingLib.getStorage().minimumStake; } - /** - * @notice Submit a proof for an epoch in the pending chain - * - * @dev Will emit `L2ProofVerified` if the proof is valid - * - * @dev Will throw if: - * - The block number is past the pending chain - * - The last archive root of the header does not match the archive root of parent block - * - The archive root of the header does not match the archive root of the proposed block - * - The proof is invalid - * - * @dev We provide the `_archive` and `_blockHash` even if it could be read from storage itself because it allow for - * better error messages. Without passing it, we would just have a proof verification failure. - * - * @param _args - The arguments to submit the epoch root proof: - * _epochSize - The size of the epoch (to be promoted to a constant) - * _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) - * _fees - Array of recipient-value pairs with fees to be distributed for the epoch - * _blobPublicInputs - The blob public inputs for the proof - * _aggregationObject - The aggregation object for the proof - * _proof - The proof to verify - */ - function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external override(IRollup) { - if (canPrune()) { - _prune(); - } - - // We want to compute the two epoch values before hand. Could we do partial interim? - // We compute these in here to avoid a lot of pain with linking libraries and passing - // external functions into internal functions as args. - SubmitEpochRootProofInterimValues memory interimValues; - interimValues.previousBlockNumber = rollupStore.tips.provenBlockNumber; - interimValues.endBlockNumber = interimValues.previousBlockNumber + _args.epochSize; - - // @note The _getEpochForBlock is expected to revert if the block is beyond pending. - // If this changes you are gonna get so rekt you won't believe it. - // I mean proving blocks that have been pruned rekt. - interimValues.startEpoch = getEpochForBlock(interimValues.previousBlockNumber + 1); - interimValues.epochToProve = getEpochForBlock(interimValues.endBlockNumber); - - uint256 endBlockNumber = ExtRollupLib.submitEpochRootProof( - rollupStore, - _args, - interimValues, - PROOF_COMMITMENT_ESCROW, - FEE_JUICE_PORTAL, - REWARD_DISTRIBUTOR, - ASSET, - CUAUHXICALLI - ); - emit L2ProofVerified(endBlockNumber, _args.args[6]); + function getExitDelay() external view override(IStaking) returns (Timestamp) { + return StakingLib.getStorage().exitDelay; } - function getProofClaim() - external - view - override(IRollup) - returns (DataStructures.EpochProofClaim memory) - { - return rollupStore.proofClaim; + function getActiveAttesterCount() external view override(IStaking) returns (uint256) { + return StakingLib.getStorage().attesters.length(); } function getTips() external view override(IRollup) returns (ChainTips memory) { @@ -338,6 +137,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo ); } + function getProofClaim() + external + view + override(IRollup) + returns (DataStructures.EpochProofClaim memory) + { + return rollupStore.proofClaim; + } + /** * @notice Returns the computed public inputs for the given epoch proof. * @@ -363,40 +171,22 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo } /** - * @notice Check if msg.sender can propose at a given time - * - * @param _ts - The timestamp to check - * @param _archive - The archive to check (should be the latest archive) - * - * @return uint256 - The slot at the given timestamp - * @return uint256 - The block number at the given timestamp + * @notice Get the next epoch that can be claimed + * @dev Will revert if the epoch has already been claimed or if there is no epoch to prove */ - function canProposeAtTime(Timestamp _ts, bytes32 _archive) - external - view - override(IRollup) - returns (Slot, uint256) - { - Slot slot = getSlotAt(_ts); - - // 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; - - 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)); - - Signature[] memory sigs = new Signature[](0); - DataStructures.ExecutionFlags memory flags = - DataStructures.ExecutionFlags({ignoreDA: true, ignoreSignatures: true}); - _validateValidatorSelection(slot, sigs, _archive, flags); - - return (slot, pendingBlockNumber + 1); + function getClaimableEpoch() external view override(IRollup) returns (Epoch) { + Epoch epochToProve = getEpochToProve(); + require( + // If the epoch has been claimed, it cannot be claimed again + rollupStore.proofClaim.epochToProve != epochToProve + // Edge case for if no claim has been made yet. + // We know that the bondProvider is always set, + // Since otherwise the claimEpochProofRight would have reverted, + // because the zero address cannot have deposited funds into escrow. + || rollupStore.proofClaim.bondProvider == address(0), + Errors.Rollup__ProofRightAlreadyClaimed() + ); + return epochToProve; } /** @@ -409,7 +199,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo * @param _signatures - The signatures to validate * @param _digest - The digest to validate * @param _currentTime - The current time - * @param _blobsHashesCommitment - The blobs hash for this block + * @param _blobsHash - The blobs hash for this block * @param _flags - The flags to validate */ function validateHeader( @@ -417,7 +207,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo Signature[] memory _signatures, bytes32 _digest, Timestamp _currentTime, - bytes32 _blobsHashesCommitment, + bytes32 _blobsHash, DataStructures.ExecutionFlags memory _flags ) external view override(IRollup) { _validateHeader( @@ -426,13 +216,13 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo _digest, _currentTime, getManaBaseFeeAt(_currentTime, true), - _blobsHashesCommitment, + _blobsHash, _flags ); } /** - * @notice Validate blob transactions against given inputs + * @notice Validate blob transactions against given inputs. * @dev Only exists here for gas estimation. */ function validateBlobs(bytes calldata _blobsInput) @@ -445,316 +235,281 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo } /** - * @notice Get the next epoch that can be claimed - * @dev Will revert if the epoch has already been claimed or if there is no epoch to prove + * @notice Get the current archive root + * + * @return bytes32 - The current archive root */ - function getClaimableEpoch() external view override(IRollup) returns (Epoch) { - Epoch epochToProve = getEpochToProve(); + function archive() external view override(IRollup) returns (bytes32) { + return rollupStore.blocks[rollupStore.tips.pendingBlockNumber].archive; + } + + function getProvenBlockNumber() external view override(IRollup) returns (uint256) { + return rollupStore.tips.provenBlockNumber; + } + + function getPendingBlockNumber() external view override(IRollup) returns (uint256) { + return rollupStore.tips.pendingBlockNumber; + } + + function getBlock(uint256 _blockNumber) external view override(IRollup) returns (BlockLog memory) { require( - // If the epoch has been claimed, it cannot be claimed again - rollupStore.proofClaim.epochToProve != epochToProve - // Edge case for if no claim has been made yet. - // We know that the bondProvider is always set, - // Since otherwise the claimEpochProofRight would have reverted, - // because the zero address cannot have deposited funds into escrow. - || rollupStore.proofClaim.bondProvider == address(0), - Errors.Rollup__ProofRightAlreadyClaimed() + _blockNumber <= rollupStore.tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) ); - return epochToProve; + return rollupStore.blocks[_blockNumber]; } - function claimEpochProofRight(SignedEpochProofQuote calldata _quote) public override(IRollup) { - validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), _quote); + function getBlobPublicInputsHash(uint256 _blockNumber) + external + view + override(IRollup) + returns (bytes32) + { + return rollupStore.blobPublicInputsHashes[_blockNumber]; + } - Slot currentSlot = getCurrentSlot(); - Epoch epochToProve = getEpochToProve(); + function getProposerForAttester(address _attester) + external + view + override(IStaking) + returns (address) + { + return StakingLib.getStorage().info[_attester].proposer; + } - // We don't currently unstake, - // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. - // Blocked on submitting epoch proofs to this contract. - PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.prover, _quote.quote.bondAmount); - - rollupStore.proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epochToProve, - basisPointFee: _quote.quote.basisPointFee, - bondAmount: _quote.quote.bondAmount, - bondProvider: _quote.quote.prover, - proposerClaimant: msg.sender - }); - - emit ProofRightClaimed( - epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot - ); + function getAttesterAtIndex(uint256 _index) external view override(IStaking) returns (address) { + return StakingLib.getStorage().attesters.at(_index); } - /** - * @notice Publishes the body and propose the block - * @dev `eth_log_handlers` rely on this function - * - * @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 _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 _blobInput - ) public override(IRollup) { - 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); - - 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; - - { - rollupStore.blocks[blockNumber] = _toBlockLog(_args, blockNumber, components.congestionCost); - } - - 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); - - emit L2BlockProposed(blockNumber, _args.archive, blobHashes); - - // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. - if (blockNumber <= assumeProvenThroughBlockNumber) { - _fakeBlockNumberAsProven(blockNumber); - - bool isFeeCanonical = address(this) == FEE_JUICE_PORTAL.canonicalRollup(); - bool isRewardDistributorCanonical = address(this) == REWARD_DISTRIBUTOR.canonicalRollup(); - - if (isFeeCanonical && header.globalVariables.coinbase != address(0) && header.totalFees > 0) { - // @note This will currently fail if there are insufficient funds in the bridge - // which WILL happen for the old version after an upgrade where the bridge follow. - // Consider allowing a failure. See #7938. - FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); - } - if (isRewardDistributorCanonical && header.globalVariables.coinbase != address(0)) { - REWARD_DISTRIBUTOR.claim(header.globalVariables.coinbase); - } - - emit L2ProofVerified(blockNumber, "CHEAT"); - } + function getProposerAtIndex(uint256 _index) external view override(IStaking) returns (address) { + return StakingLib.getStorage().info[StakingLib.getStorage().attesters.at(_index)].proposer; } - /** - * @notice Updates the l1 gas fee oracle - * @dev This function is called by the `propose` function - */ - function updateL1GasFeeOracle() public override(IRollup) { - Slot slot = getCurrentSlot(); - // The slot where we find a new queued value acceptable - Slot acceptableSlot = rollupStore.l1GasOracleValues.slotOfChange + (LIFETIME - LAG); + function getInfo(address _attester) + external + view + override(IStaking) + returns (ValidatorInfo memory) + { + return StakingLib.getStorage().info[_attester]; + } - if (slot < acceptableSlot) { - return; - } + function getExit(address _attester) external view override(IStaking) returns (Exit memory) { + return StakingLib.getStorage().exits[_attester]; + } - rollupStore.l1GasOracleValues.pre = rollupStore.l1GasOracleValues.post; - rollupStore.l1GasOracleValues.post = - L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee(VM_ADDRESS)}); - rollupStore.l1GasOracleValues.slotOfChange = slot + LAG; + function getOperatorAtIndex(uint256 _index) + external + view + override(IStaking) + returns (OperatorInfo memory) + { + address attester = StakingLib.getStorage().attesters.at(_index); + return + OperatorInfo({proposer: StakingLib.getStorage().info[attester].proposer, attester: attester}); } /** - * @notice Gets the fee asset price as fee_asset / eth with 1e9 precision + * @notice Get the validator set for a given epoch + * + * @dev Consider removing this to replace with a `size` and individual getter. + * + * @param _epoch The epoch number to get the validator set for * - * @return The fee asset price + * @return The validator set for the given epoch */ - function getFeeAssetPrice() public view override(IRollup) returns (uint256) { - return IntRollupLib.feeAssetPriceModifier( - rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator - ); + function getEpochCommittee(Epoch _epoch) + external + view + override(IValidatorSelection) + returns (address[] memory) + { + return ValidatorSelectionLib.getStorage().epochs[_epoch].committee; } - function getL1FeesAt(Timestamp _timestamp) - public + /** + * @notice Get the validator set for the current epoch + * @return The validator set for the current epoch + */ + function getCurrentEpochCommittee() + external view - override(IRollup) - returns (L1FeeData memory) + override(IValidatorSelection) + returns (address[] memory) { - return getSlotAt(_timestamp) < rollupStore.l1GasOracleValues.slotOfChange - ? rollupStore.l1GasOracleValues.pre - : rollupStore.l1GasOracleValues.post; + return ValidatorSelectionLib.getCommitteeAt(StakingLib.getStorage(), getCurrentEpoch()); } /** - * @notice Gets the mana base fee + * @notice Get the committee for a given timestamp * - * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH + * @param _ts - The timestamp to get the committee for * - * @return The mana base fee + * @return The committee for the given timestamp */ - function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset) - public + function getCommitteeAt(Timestamp _ts) + external view - override(IRollup) - returns (uint256) + override(IValidatorSelection) + returns (address[] memory) { - return getManaBaseFeeComponentsAt(_timestamp, _inFeeAsset).summedBaseFee(); + return ValidatorSelectionLib.getCommitteeAt(StakingLib.getStorage(), getEpochAt(_ts)); } /** - * @notice Gets the mana base fee components - * For more context, consult: - * https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md + * @notice Get the sample seed for a given timestamp * - * @dev TODO #10004 - As part of the refactor, will likely get rid of this function or make it private - * keeping it public for now makes it simpler to test. + * @param _ts - The timestamp to get the sample seed for * - * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH - * - * @return The mana base fee components + * @return The sample seed for the given timestamp */ - function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset) - public + function getSampleSeedAt(Timestamp _ts) + external view - override(ITestRollup) - returns (ManaBaseFeeComponents memory) + override(IValidatorSelection) + returns (uint256) { - // 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), - _inFeeAsset ? getFeeAssetPrice() : 1e9, - TimeLib.getStorage().epochDuration - ); + return ValidatorSelectionLib.getSampleSeed(getEpochAt(_ts)); } - function quoteToDigest(EpochProofQuote memory _quote) - public - view - override(IRollup) - returns (bytes32) - { - return _hashTypedDataV4(IntRollupLib.computeQuoteHash(_quote)); + /** + * @notice Get the sample seed for the current epoch + * + * @return The sample seed for the current epoch + */ + function getCurrentSampleSeed() external view override(IValidatorSelection) returns (uint256) { + return ValidatorSelectionLib.getSampleSeed(getCurrentEpoch()); } - function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) - public - view - override(IRollup) - { - Slot currentSlot = getSlotAt(_ts); - address currentProposer = getProposerAt(_ts); - Epoch epochToProve = getEpochToProve(); - uint256 posInEpoch = TimeLib.positionInEpoch(currentSlot); - bytes32 digest = quoteToDigest(_quote.quote); - - ExtRollupLib.validateEpochProofRightClaimAtTime( - currentSlot, - currentProposer, - epochToProve, - posInEpoch, - _quote, - digest, - rollupStore.proofClaim, - CLAIM_DURATION_IN_L2_SLOTS, - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, - PROOF_COMMITMENT_ESCROW - ); + /** + * @notice Get the attester set + * + * @dev Consider removing this to replace with a `size` and individual getter. + * + * @return The validator set + */ + function getAttesters() external view override(IValidatorSelection) returns (address[] memory) { + return StakingLib.getStorage().attesters.values(); } /** - * @notice Get the current archive root + * @notice Get the current slot number * - * @return bytes32 - The current archive root + * @return The current slot number */ - function archive() public view override(IRollup) returns (bytes32) { - return rollupStore.blocks[rollupStore.tips.pendingBlockNumber].archive; + function getCurrentSlot() external view override(IValidatorSelection) returns (Slot) { + return Timestamp.wrap(block.timestamp).slotFromTimestamp(); } - function getProvenBlockNumber() public view override(IRollup) returns (uint256) { - return rollupStore.tips.provenBlockNumber; + /** + * @notice Get the timestamp for a given slot + * + * @param _slotNumber - The slot number to get the timestamp for + * + * @return The timestamp for the given slot + */ + function getTimestampForSlot(Slot _slotNumber) + external + view + override(IValidatorSelection) + returns (Timestamp) + { + return _slotNumber.toTimestamp(); } - function getPendingBlockNumber() public view override(IRollup) returns (uint256) { - return rollupStore.tips.pendingBlockNumber; + /** + * @notice Get the proposer for the current slot + * + * @dev Calls `getCurrentProposer(uint256)` with the current timestamp + * + * @return The address of the proposer + */ + function getCurrentProposer() external view override(IValidatorSelection) returns (address) { + return getProposerAt(Timestamp.wrap(block.timestamp)); } - function getBlock(uint256 _blockNumber) public view override(IRollup) returns (BlockLog memory) { - require( - _blockNumber <= rollupStore.tips.pendingBlockNumber, - Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) - ); - return rollupStore.blocks[_blockNumber]; + /** + * @notice Computes the slot at a specific time + * + * @param _ts - The timestamp to compute the slot for + * + * @return The computed slot + */ + function getSlotAt(Timestamp _ts) external view override(IValidatorSelection) returns (Slot) { + return _ts.slotFromTimestamp(); } - function getBlobPublicInputsHash(uint256 _blockNumber) - public + /** + * @notice Computes the epoch at a specific slot + * + * @param _slotNumber - The slot number to compute the epoch for + * + * @return The computed epoch + */ + function getEpochAtSlot(Slot _slotNumber) + external view - override(IRollup) - returns (bytes32) + override(IValidatorSelection) + returns (Epoch) { - return rollupStore.blobPublicInputsHashes[_blockNumber]; + return _slotNumber.epochFromSlot(); } - function getEpochForBlock(uint256 _blockNumber) public view override(IRollup) returns (Epoch) { - require( - _blockNumber <= rollupStore.tips.pendingBlockNumber, - Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) + /** + * @notice Check if msg.sender can propose at a given time + * + * @param _ts - The timestamp to check + * @param _archive - The archive to check (should be the latest archive) + * + * @return uint256 - The slot at the given timestamp + * @return uint256 - The block number at the given timestamp + */ + function canProposeAtTime(Timestamp _ts, bytes32 _archive) + external + view + override(IRollup) + returns (Slot, uint256) + { + Slot slot = _ts.slotFromTimestamp(); + + // 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; + + 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)); + + Signature[] memory sigs = new Signature[](0); + DataStructures.ExecutionFlags memory flags = + DataStructures.ExecutionFlags({ignoreDA: true, ignoreSignatures: true}); + + Epoch currentEpoch = slot.epochFromSlot(); + ValidatorSelectionLib.validateValidatorSelection( + StakingLib.getStorage(), slot, currentEpoch, sigs, _archive, flags ); - return getEpochAt(getTimestampForSlot(rollupStore.blocks[_blockNumber].slotNumber)); + + return (slot, pendingBlockNumber + 1); } /** - * @notice Get the epoch that should be proven + * @notice Gets the mana base fee * - * @dev This is the epoch that should be proven. It does so by getting the epoch of the block - * following the last proven block. If there is no such block (i.e. the pending chain is - * the same as the proven chain), then revert. + * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH * - * @return uint256 - The epoch to prove + * @return The mana base fee */ - function getEpochToProve() public view override(IRollup) returns (Epoch) { - require( - rollupStore.tips.provenBlockNumber != rollupStore.tips.pendingBlockNumber, - Errors.Rollup__NoEpochToProve() - ); - return getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); + function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset) + public + view + override(IRollup) + returns (uint256) + { + return getManaBaseFeeComponentsAt(_timestamp, _inFeeAsset).summedBaseFee(); } /** @@ -770,185 +525,50 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, ValidatorSelection, IRo : bytes32(0); } - function canPrune() public view override(IRollup) returns (bool) { - return canPruneAtTime(Timestamp.wrap(block.timestamp)); - } - - function canPruneAtTime(Timestamp _ts) public view override(IRollup) returns (bool) { - if ( - rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber - || rollupStore.tips.pendingBlockNumber <= assumeProvenThroughBlockNumber - ) { - return false; - } - - Slot currentSlot = getSlotAt(_ts); - Epoch oldestPendingEpoch = getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); - Slot startSlotOfPendingEpoch = TimeLib.toSlots(oldestPendingEpoch); - - // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. - // we prune the pending chain back to the end of epoch 1 if: - // - the proof claim phase of epoch 3 has ended without a claim to prove epoch 2 (or proof of epoch 2) - // - we reach epoch 4 without a proof of epoch 2 (regardless of whether a proof claim was submitted) - bool inClaimPhase = currentSlot - < startSlotOfPendingEpoch + TimeLib.toSlots(Epoch.wrap(1)) - + Slot.wrap(CLAIM_DURATION_IN_L2_SLOTS); - - bool claimExists = currentSlot < startSlotOfPendingEpoch + TimeLib.toSlots(Epoch.wrap(2)) - && rollupStore.proofClaim.epochToProve == oldestPendingEpoch - && rollupStore.proofClaim.proposerClaimant != address(0); - - if (inClaimPhase || claimExists) { - // If we are in the claim phase, do not prune - return false; - } - return true; - } - - function _prune() internal { - // TODO #8656 - delete rollupStore.proofClaim; - - 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 Computes the epoch at a specific time + * + * @param _ts - The timestamp to compute the epoch for + * + * @return The computed epoch + */ + function getEpochAt(Timestamp _ts) public view override(IValidatorSelection) returns (Epoch) { + return _ts.epochFromTimestamp(); } /** - * @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 + * @notice Get the current epoch number + * + * @return The current epoch number */ - 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, - getTimestampForSlot: this.getTimestampForSlot - }), - rollupStore.blocks - ); - _validateHeaderForSubmissionSequencerSelection( - Slot.wrap(_header.globalVariables.slotNumber), _signatures, _digest, _currentTime, _flags - ); + function getCurrentEpoch() public view override(IValidatorSelection) returns (Epoch) { + return Timestamp.wrap(block.timestamp).epochFromTimestamp(); } /** - * @notice Validate a header for submission to the pending chain (sequencer selection checks) + * @notice Get the proposer for the slot at a specific timestamp * - * These validation checks are directly related to ValidatorSelection. - * Note that while these checks are strict, they can be relaxed with some changes to - * message boxes. + * @dev This function is very useful for off-chain usage, as it easily allow a client to + * determine who will be the proposer at the NEXT ethereum block. + * Should not be trusted when moving beyond the current epoch, since changes to the + * validator set might not be reflected when we actually reach that epoch (more changes + * might have happened). * - * 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 + * @dev The proposer is selected from the validator set of 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 + * @dev Should only be access on-chain if epoch is setup, otherwise very expensive. + * + * @dev A return value of address(0) means that the proposer is "open" and can be anyone. + * + * @dev If the current epoch is the first epoch, returns address(0) + * If the current epoch is setup, we will return the proposer for the current slot + * If the current epoch is not setup, we will perform a sample as if it was (gas heavy) + * + * @return The address of the proposer */ - 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 = getSlotAt(_currentTime); - 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 = getEpochAt(getTimestampForSlot(_slot)); - Epoch currentEpoch = getEpochAt(_currentTime); - require(epochNumber == currentEpoch, Errors.Rollup__InvalidEpoch(currentEpoch, epochNumber)); - - _validateValidatorSelection(_slot, _signatures, _digest, _flags); - } - - // Helper to avoid stack too deep - function _toBlockLog(ProposeArgs calldata _args, uint256 _blockNumber, uint256 _congestionCost) - 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])), - provingCostPerManaNumerator: parentFeeHeader.provingCostPerManaNumerator.clampedAdd( - _args.oracleInput.provingCostModifier - ), - congestionCost: _congestionCost - }) - }); - } - - function _fakeBlockNumberAsProven(uint256 _blockNumber) private { - if ( - _blockNumber > rollupStore.tips.provenBlockNumber - && _blockNumber <= rollupStore.tips.pendingBlockNumber - ) { - rollupStore.tips.provenBlockNumber = _blockNumber; - - // If this results on a new epoch, create a fake claim for it - // Otherwise nextEpochToProve will report an old epoch - Epoch epoch = getEpochForBlock(_blockNumber); - if ( - Epoch.unwrap(epoch) == 0 - || Epoch.unwrap(epoch) > Epoch.unwrap(rollupStore.proofClaim.epochToProve) - ) { - rollupStore.proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epoch, - basisPointFee: 0, - bondAmount: 0, - bondProvider: address(0), - proposerClaimant: msg.sender - }); - } - } + function getProposerAt(Timestamp _ts) public view override(IValidatorSelection) returns (address) { + Slot slot = _ts.slotFromTimestamp(); + Epoch epochNumber = slot.epochFromSlot(); + return ValidatorSelectionLib.getProposerAt(StakingLib.getStorage(), slot, epochNumber); } } diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol new file mode 100644 index 00000000000..8a9a8c33362 --- /dev/null +++ b/l1-contracts/src/core/RollupCore.sol @@ -0,0 +1,790 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; +import { + IRollupInner, + ITestRollup, + CheatDepositArgs, + FeeHeader, + ManaBaseFeeComponents, + BlockLog, + RollupStore, + L1GasOracleValues, + L1FeeData, + SubmitEpochRootProofArgs +} from "@aztec/core/interfaces/IRollup.sol"; +import {IStakingInner} from "@aztec/core/interfaces/IStaking.sol"; +import {IValidatorSelectionInner} from "@aztec/core/interfaces/IValidatorSelection.sol"; +import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; +import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; +import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; +import {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, + SignedEpochProofQuote, + SubmitEpochRootProofInterimValues +} from "@aztec/core/libraries/RollupLibs/ExtRollupLib.sol"; +import {IntRollupLib, EpochProofQuote} 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 {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol"; +import {Slasher} from "@aztec/core/staking/Slasher.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +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"; + +struct Config { + uint256 aztecSlotDuration; + uint256 aztecEpochDuration; + uint256 targetCommitteeSize; + uint256 aztecEpochProofClaimWindowInL2Slots; + uint256 minimumStake; + uint256 slashingQuorum; + uint256 slashingRoundSize; +} + +/** + * @title Rollup + * @author Aztec Labs + * @notice Rollup contract that is concerned about readability and velocity of development + * not giving a damn about gas costs. + * @dev WARNING: This contract is VERY close to the size limit (500B at time of writing). + */ +abstract contract RollupCore is + EIP712("Aztec Rollup", "1"), + Ownable, + IStakingInner, + IValidatorSelectionInner, + IRollupInner, + ITestRollup +{ + using ProposeLib for ProposeArgs; + using IntRollupLib for uint256; + using IntRollupLib for ManaBaseFeeComponents; + + using TimeLib for Timestamp; + using TimeLib for Slot; + using TimeLib for Epoch; + + Slot public constant LIFETIME = Slot.wrap(5); + Slot public constant LAG = Slot.wrap(2); + + // See https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8401-proof-timeliness/proof-timeliness.ipynb + // for justification of CLAIM_DURATION_IN_L2_SLOTS. + uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; + + // 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; + + uint256 public immutable CLAIM_DURATION_IN_L2_SLOTS; + uint256 public immutable L1_BLOCK_AT_GENESIS; + IInbox public immutable INBOX; + IOutbox public immutable OUTBOX; + IProofCommitmentEscrow public immutable PROOF_COMMITMENT_ESCROW; + 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 + uint256 private gap = 0; + + // @note Always true, exists to override to false for testing only + bool public checkBlob = true; + + RollupStore internal rollupStore; + + // @note Assume that all blocks up to this value (inclusive) are automatically proven. Speeds up bootstrapping. + // Testing only. This should be removed eventually. + uint256 private assumeProvenThroughBlockNumber; + + constructor( + IFeeJuicePortal _fpcJuicePortal, + IRewardDistributor _rewardDistributor, + IERC20 _stakingAsset, + bytes32 _vkTreeRoot, + bytes32 _protocolContractTreeRoot, + address _ares, + Config memory _config + ) Ownable(_ares) { + TimeLib.initialize(block.timestamp, _config.aztecSlotDuration, _config.aztecEpochDuration); + + 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(); + PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow( + ASSET, address(this), _config.aztecSlotDuration, _config.aztecEpochDuration + ); + INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); + OUTBOX = IOutbox(address(new Outbox(address(this)))); + VERSION = 1; + L1_BLOCK_AT_GENESIS = block.number; + CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots; + + IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; + + rollupStore.epochProofVerifier = new MockVerifier(); + rollupStore.vkTreeRoot = _vkTreeRoot; + rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + + // Genesis block + rollupStore.blocks[0] = BlockLog({ + feeHeader: FeeHeader({ + excessMana: 0, + feeAssetPriceNumerator: 0, + manaUsed: 0, + provingCostPerManaNumerator: 0, + congestionCost: 0 + }), + archive: bytes32(Constants.GENESIS_ARCHIVE_ROOT), + blockHash: bytes32(Constants.GENESIS_BLOCK_HASH), + slotNumber: Slot.wrap(0) + }); + rollupStore.l1GasOracleValues = L1GasOracleValues({ + pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), + post: L1FeeData({baseFee: block.basefee, blobFee: ExtRollupLib.getBlobBaseFee(VM_ADDRESS)}), + slotOfChange: LIFETIME + }); + } + + function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) + external + override(IStakingInner) + { + setupEpoch(); + StakingLib.deposit(_attester, _proposer, _withdrawer, _amount); + } + + function initiateWithdraw(address _attester, address _recipient) + external + override(IStakingInner) + returns (bool) + { + // @note The attester might be chosen for the epoch, so the delay must be long enough + // to allow for that. + setupEpoch(); + return StakingLib.initiateWithdraw(_attester, _recipient); + } + + function finaliseWithdraw(address _attester) external override(IStakingInner) { + StakingLib.finaliseWithdraw(_attester); + } + + function slash(address _attester, uint256 _amount) external override(IStakingInner) { + StakingLib.slash(_attester, _amount); + } + + function cheat__InitialiseValidatorSet(CheatDepositArgs[] memory _args) + external + override(ITestRollup) + onlyOwner + { + for (uint256 i = 0; i < _args.length; i++) { + StakingLib.deposit(_args[i].attester, _args[i].proposer, _args[i].withdrawer, _args[i].amount); + } + setupEpoch(); + } + + /** + * @notice Prune the pending chain up to the last proven block + * + * @dev Will revert if there is nothing to prune or if the chain is not ready to be pruned + */ + function prune() external override(IRollupInner) { + require(canPrune(), Errors.Rollup__NothingToPrune()); + _prune(); + } + + /** + * Sets the assumeProvenThroughBlockNumber. Only the contract deployer can set it. + * @param _blockNumber - New value. + */ + function setAssumeProvenThroughBlockNumber(uint256 _blockNumber) + external + override(ITestRollup) + onlyOwner + { + _fakeBlockNumberAsProven(_blockNumber); + assumeProvenThroughBlockNumber = _blockNumber; + } + + /** + * @notice Set the verifier contract + * + * @dev This is only needed for testing, and should be removed + * + * @param _verifier - The new verifier contract + */ + function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner { + rollupStore.epochProofVerifier = IVerifier(_verifier); + } + + /** + * @notice Set the vkTreeRoot + * + * @dev This is only needed for testing, and should be removed + * + * @param _vkTreeRoot - The new vkTreeRoot to be used by proofs + */ + function setVkTreeRoot(bytes32 _vkTreeRoot) external override(ITestRollup) onlyOwner { + rollupStore.vkTreeRoot = _vkTreeRoot; + } + + /** + * @notice Set the protocolContractTreeRoot + * + * @dev This is only needed for testing, and should be removed + * + * @param _protocolContractTreeRoot - The new protocolContractTreeRoot to be used by proofs + */ + function setProtocolContractTreeRoot(bytes32 _protocolContractTreeRoot) + external + override(ITestRollup) + onlyOwner + { + rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; + } + + /** + * @notice Publishes the body and propose the block + * @dev `eth_log_handlers` rely on this function + * + * @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 _body - The body of the L2 block + * @param _blobInput - The blob evaluation KZG proof, challenge, and opening required for the precompile. + */ + function proposeAndClaim( + ProposeArgs calldata _args, + Signature[] memory _signatures, + bytes calldata _body, + bytes calldata _blobInput, + SignedEpochProofQuote calldata _quote + ) external override(IRollupInner) { + propose(_args, _signatures, _body, _blobInput); + claimEpochProofRight(_quote); + } + + /** + * @notice Submit a proof for an epoch in the pending chain + * + * @dev Will emit `L2ProofVerified` if the proof is valid + * + * @dev Will throw if: + * - The block number is past the pending chain + * - The last archive root of the header does not match the archive root of parent block + * - The archive root of the header does not match the archive root of the proposed block + * - The proof is invalid + * + * @dev We provide the `_archive` and `_blockHash` even if it could be read from storage itself because it allow for + * better error messages. Without passing it, we would just have a proof verification failure. + * + * @param _args - The arguments to submit the epoch root proof: + * _epochSize - The size of the epoch (to be promoted to a constant) + * _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) + * _fees - Array of recipient-value pairs with fees to be distributed for the epoch + * _blobPublicInputs - The blob public inputs for the proof + * _aggregationObject - The aggregation object for the proof + * _proof - The proof to verify + */ + function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) + external + override(IRollupInner) + { + if (canPrune()) { + _prune(); + } + + // We want to compute the two epoch values before hand. Could we do partial interim? + // We compute these in here to avoid a lot of pain with linking libraries and passing + // external functions into internal functions as args. + SubmitEpochRootProofInterimValues memory interimValues; + interimValues.previousBlockNumber = rollupStore.tips.provenBlockNumber; + interimValues.endBlockNumber = interimValues.previousBlockNumber + _args.epochSize; + + // @note The _getEpochForBlock is expected to revert if the block is beyond pending. + // If this changes you are gonna get so rekt you won't believe it. + // I mean proving blocks that have been pruned rekt. + interimValues.startEpoch = getEpochForBlock(interimValues.previousBlockNumber + 1); + interimValues.epochToProve = getEpochForBlock(interimValues.endBlockNumber); + + uint256 endBlockNumber = ExtRollupLib.submitEpochRootProof( + rollupStore, + _args, + interimValues, + PROOF_COMMITMENT_ESCROW, + FEE_JUICE_PORTAL, + REWARD_DISTRIBUTOR, + ASSET, + CUAUHXICALLI + ); + emit L2ProofVerified(endBlockNumber, _args.args[6]); + } + + function setupEpoch() public override(IValidatorSelectionInner) { + ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); + } + + function claimEpochProofRight(SignedEpochProofQuote calldata _quote) + public + override(IRollupInner) + { + validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), _quote); + + Slot currentSlot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); + Epoch epochToProve = getEpochToProve(); + + // We don't currently unstake, + // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. + // Blocked on submitting epoch proofs to this contract. + PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.prover, _quote.quote.bondAmount); + + rollupStore.proofClaim = DataStructures.EpochProofClaim({ + epochToProve: epochToProve, + basisPointFee: _quote.quote.basisPointFee, + bondAmount: _quote.quote.bondAmount, + bondProvider: _quote.quote.prover, + proposerClaimant: msg.sender + }); + + emit ProofRightClaimed( + epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot + ); + } + + /** + * @notice Publishes the body and propose the block + * @dev `eth_log_handlers` rely on this function + * + * @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 _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 _blobInput + ) public override(IRollupInner) { + 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); + + 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; + + { + rollupStore.blocks[blockNumber] = _toBlockLog(_args, blockNumber, components.congestionCost); + } + + 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); + + emit L2BlockProposed(blockNumber, _args.archive, blobHashes); + + // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. + if (blockNumber <= assumeProvenThroughBlockNumber) { + _fakeBlockNumberAsProven(blockNumber); + + bool isFeeCanonical = address(this) == FEE_JUICE_PORTAL.canonicalRollup(); + bool isRewardDistributorCanonical = address(this) == REWARD_DISTRIBUTOR.canonicalRollup(); + + if (isFeeCanonical && header.globalVariables.coinbase != address(0) && header.totalFees > 0) { + // @note This will currently fail if there are insufficient funds in the bridge + // which WILL happen for the old version after an upgrade where the bridge follow. + // Consider allowing a failure. See #7938. + FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); + } + if (isRewardDistributorCanonical && header.globalVariables.coinbase != address(0)) { + REWARD_DISTRIBUTOR.claim(header.globalVariables.coinbase); + } + + emit L2ProofVerified(blockNumber, "CHEAT"); + } + } + + /** + * @notice Updates the l1 gas fee oracle + * @dev This function is called by the `propose` function + */ + function updateL1GasFeeOracle() public override(IRollupInner) { + 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; + } + + /** + * @notice Gets the fee asset price as fee_asset / eth with 1e9 precision + * + * @return The fee asset price + */ + function getFeeAssetPrice() public view override(IRollupInner) returns (uint256) { + return IntRollupLib.feeAssetPriceModifier( + rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator + ); + } + + function getL1FeesAt(Timestamp _timestamp) + public + view + override(IRollupInner) + returns (L1FeeData memory) + { + return _timestamp.slotFromTimestamp() < rollupStore.l1GasOracleValues.slotOfChange + ? rollupStore.l1GasOracleValues.pre + : rollupStore.l1GasOracleValues.post; + } + + /** + * @notice Gets the mana base fee components + * For more context, consult: + * https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md + * + * @dev TODO #10004 - As part of the refactor, will likely get rid of this function or make it private + * keeping it public for now makes it simpler to test. + * + * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH + * + * @return The mana base fee components + */ + function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset) + public + view + 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), + _inFeeAsset ? getFeeAssetPrice() : 1e9, + TimeLib.getStorage().epochDuration + ); + } + + function quoteToDigest(EpochProofQuote memory _quote) + public + view + override(IRollupInner) + returns (bytes32) + { + return _hashTypedDataV4(IntRollupLib.computeQuoteHash(_quote)); + } + + function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) + public + view + override(IRollupInner) + { + Slot currentSlot = _ts.slotFromTimestamp(); + address currentProposer = ValidatorSelectionLib.getProposerAt( + StakingLib.getStorage(), currentSlot, currentSlot.epochFromSlot() + ); + Epoch epochToProve = getEpochToProve(); + uint256 posInEpoch = TimeLib.positionInEpoch(currentSlot); + bytes32 digest = quoteToDigest(_quote.quote); + + ExtRollupLib.validateEpochProofRightClaimAtTime( + currentSlot, + currentProposer, + epochToProve, + posInEpoch, + _quote, + digest, + rollupStore.proofClaim, + CLAIM_DURATION_IN_L2_SLOTS, + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, + PROOF_COMMITMENT_ESCROW + ); + } + + function getEpochForBlock(uint256 _blockNumber) + public + view + override(IRollupInner) + returns (Epoch) + { + require( + _blockNumber <= rollupStore.tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) + ); + return rollupStore.blocks[_blockNumber].slotNumber.epochFromSlot(); + } + + /** + * @notice Get the epoch that should be proven + * + * @dev This is the epoch that should be proven. It does so by getting the epoch of the block + * following the last proven block. If there is no such block (i.e. the pending chain is + * the same as the proven chain), then revert. + * + * @return uint256 - The epoch to prove + */ + function getEpochToProve() public view override(IRollupInner) returns (Epoch) { + require( + rollupStore.tips.provenBlockNumber != rollupStore.tips.pendingBlockNumber, + Errors.Rollup__NoEpochToProve() + ); + return getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); + } + + function canPrune() public view override(IRollupInner) returns (bool) { + return canPruneAtTime(Timestamp.wrap(block.timestamp)); + } + + function canPruneAtTime(Timestamp _ts) public view override(IRollupInner) returns (bool) { + if ( + rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber + || rollupStore.tips.pendingBlockNumber <= assumeProvenThroughBlockNumber + ) { + return false; + } + + Slot currentSlot = _ts.slotFromTimestamp(); + Epoch oldestPendingEpoch = getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); + Slot startSlotOfPendingEpoch = oldestPendingEpoch.toSlots(); + + // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. + // we prune the pending chain back to the end of epoch 1 if: + // - the proof claim phase of epoch 3 has ended without a claim to prove epoch 2 (or proof of epoch 2) + // - we reach epoch 4 without a proof of epoch 2 (regardless of whether a proof claim was submitted) + bool inClaimPhase = currentSlot + < startSlotOfPendingEpoch + TimeLib.toSlots(Epoch.wrap(1)) + + Slot.wrap(CLAIM_DURATION_IN_L2_SLOTS); + + bool claimExists = currentSlot < startSlotOfPendingEpoch + TimeLib.toSlots(Epoch.wrap(2)) + && rollupStore.proofClaim.epochToProve == oldestPendingEpoch + && rollupStore.proofClaim.proposerClaimant != address(0); + + if (inClaimPhase || claimExists) { + // If we are in the claim phase, do not prune + return false; + } + return true; + } + + function _prune() internal { + // TODO #8656 + delete rollupStore.proofClaim; + + 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) + 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])), + provingCostPerManaNumerator: parentFeeHeader.provingCostPerManaNumerator.clampedAdd( + _args.oracleInput.provingCostModifier + ), + congestionCost: _congestionCost + }) + }); + } + + function _fakeBlockNumberAsProven(uint256 _blockNumber) private { + if ( + _blockNumber > rollupStore.tips.provenBlockNumber + && _blockNumber <= rollupStore.tips.pendingBlockNumber + ) { + rollupStore.tips.provenBlockNumber = _blockNumber; + + // If this results on a new epoch, create a fake claim for it + // Otherwise nextEpochToProve will report an old epoch + Epoch epoch = getEpochForBlock(_blockNumber); + if ( + Epoch.unwrap(epoch) == 0 + || Epoch.unwrap(epoch) > Epoch.unwrap(rollupStore.proofClaim.epochToProve) + ) { + rollupStore.proofClaim = DataStructures.EpochProofClaim({ + epochToProve: epoch, + basisPointFee: 0, + bondAmount: 0, + bondProvider: address(0), + proposerClaimant: msg.sender + }); + } + } + } +} diff --git a/l1-contracts/src/core/ValidatorSelection.sol b/l1-contracts/src/core/ValidatorSelection.sol deleted file mode 100644 index 9dc6b172652..00000000000 --- a/l1-contracts/src/core/ValidatorSelection.sol +++ /dev/null @@ -1,453 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {IStaking, ValidatorInfo, Exit, OperatorInfo} from "@aztec/core/interfaces/IStaking.sol"; -import { - IValidatorSelection, - EpochData, - ValidatorSelectionStorage -} from "@aztec/core/interfaces/IValidatorSelection.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 {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; -import { - Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeLib -} from "@aztec/core/libraries/TimeLib.sol"; -import {ValidatorSelectionLib} from - "@aztec/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol"; -import {Slasher} from "@aztec/core/staking/Slasher.sol"; -import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; - -/** - * @title Validator Selection - * - * @dev Validator Selection has one thing in mind, he provide a reference of the LOGIC going on for the spartan selection. - * It is a reference implementation, it is not optimized for gas. - * - */ -contract ValidatorSelection is IValidatorSelection, IStaking { - using EnumerableSet for EnumerableSet.AddressSet; - - using SlotLib for Slot; - using EpochLib for Epoch; - using TimeLib for Timestamp; - using TimeLib for Slot; - using TimeLib for Epoch; - - // The target number of validators in a committee - // @todo #8021 - uint256 public immutable TARGET_COMMITTEE_SIZE; - - ValidatorSelectionStorage private validatorSelectionStore; - - constructor( - IERC20 _stakingAsset, - uint256 _minimumStake, - uint256 _slashingQuorum, - uint256 _roundSize, - uint256 _slotDuration, - uint256 _epochDuration, - uint256 _targetCommitteeSize - ) { - TARGET_COMMITTEE_SIZE = _targetCommitteeSize; - - TimeLib.initialize(block.timestamp, _slotDuration, _epochDuration); - - Timestamp exitDelay = Timestamp.wrap(60 * 60 * 24); - Slasher slasher = new Slasher(_slashingQuorum, _roundSize); - StakingLib.initialize(_stakingAsset, _minimumStake, exitDelay, address(slasher)); - } - - function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) - external - override(IStaking) - { - setupEpoch(); - require( - _attester != address(0) && _proposer != address(0), - Errors.ValidatorSelection__InvalidDeposit(_attester, _proposer) - ); - StakingLib.deposit(_attester, _proposer, _withdrawer, _amount); - } - - function initiateWithdraw(address _attester, address _recipient) - external - override(IStaking) - returns (bool) - { - // @note The attester might be chosen for the epoch, so the delay must be long enough - // to allow for that. - setupEpoch(); - return StakingLib.initiateWithdraw(_attester, _recipient); - } - - function finaliseWithdraw(address _attester) external override(IStaking) { - StakingLib.finaliseWithdraw(_attester); - } - - function slash(address _attester, uint256 _amount) external override(IStaking) { - StakingLib.slash(_attester, _amount); - } - - function getGenesisTime() external view override(IValidatorSelection) returns (Timestamp) { - return Timestamp.wrap(TimeLib.getStorage().genesisTime); - } - - function getSlotDuration() external view override(IValidatorSelection) returns (uint256) { - return TimeLib.getStorage().slotDuration; - } - - function getEpochDuration() external view override(IValidatorSelection) returns (uint256) { - return TimeLib.getStorage().epochDuration; - } - - function getSlasher() external view override(IStaking) returns (address) { - return StakingLib.getStorage().slasher; - } - - function getStakingAsset() external view override(IStaking) returns (IERC20) { - return StakingLib.getStorage().stakingAsset; - } - - function getMinimumStake() external view override(IStaking) returns (uint256) { - return StakingLib.getStorage().minimumStake; - } - - function getExitDelay() external view override(IStaking) returns (Timestamp) { - return StakingLib.getStorage().exitDelay; - } - - function getActiveAttesterCount() external view override(IStaking) returns (uint256) { - return StakingLib.getStorage().attesters.length(); - } - - function getProposerForAttester(address _attester) - external - view - override(IStaking) - returns (address) - { - return StakingLib.getStorage().info[_attester].proposer; - } - - function getAttesterAtIndex(uint256 _index) external view override(IStaking) returns (address) { - return StakingLib.getStorage().attesters.at(_index); - } - - function getProposerAtIndex(uint256 _index) external view override(IStaking) returns (address) { - return StakingLib.getStorage().info[StakingLib.getStorage().attesters.at(_index)].proposer; - } - - function getInfo(address _attester) - external - view - override(IStaking) - returns (ValidatorInfo memory) - { - return StakingLib.getStorage().info[_attester]; - } - - function getExit(address _attester) external view override(IStaking) returns (Exit memory) { - return StakingLib.getStorage().exits[_attester]; - } - - function getOperatorAtIndex(uint256 _index) - external - view - override(IStaking) - returns (OperatorInfo memory) - { - address attester = StakingLib.getStorage().attesters.at(_index); - return - OperatorInfo({proposer: StakingLib.getStorage().info[attester].proposer, attester: attester}); - } - - /** - * @notice Get the validator set for a given epoch - * - * @dev Consider removing this to replace with a `size` and individual getter. - * - * @param _epoch The epoch number to get the validator set for - * - * @return The validator set for the given epoch - */ - function getEpochCommittee(Epoch _epoch) - external - view - override(IValidatorSelection) - returns (address[] memory) - { - return validatorSelectionStore.epochs[_epoch].committee; - } - - /** - * @notice Get the validator set for the current epoch - * @return The validator set for the current epoch - */ - function getCurrentEpochCommittee() - external - view - override(IValidatorSelection) - returns (address[] memory) - { - return ValidatorSelectionLib.getCommitteeAt( - validatorSelectionStore, StakingLib.getStorage(), getCurrentEpoch(), TARGET_COMMITTEE_SIZE - ); - } - - /** - * @notice Get the committee for a given timestamp - * - * @param _ts - The timestamp to get the committee for - * - * @return The committee for the given timestamp - */ - function getCommitteeAt(Timestamp _ts) - external - view - override(IValidatorSelection) - returns (address[] memory) - { - return ValidatorSelectionLib.getCommitteeAt( - validatorSelectionStore, StakingLib.getStorage(), getEpochAt(_ts), TARGET_COMMITTEE_SIZE - ); - } - - /** - * @notice Get the sample seed for a given timestamp - * - * @param _ts - The timestamp to get the sample seed for - * - * @return The sample seed for the given timestamp - */ - function getSampleSeedAt(Timestamp _ts) - external - view - override(IValidatorSelection) - returns (uint256) - { - return ValidatorSelectionLib.getSampleSeed(validatorSelectionStore, getEpochAt(_ts)); - } - - /** - * @notice Get the sample seed for the current epoch - * - * @return The sample seed for the current epoch - */ - function getCurrentSampleSeed() external view override(IValidatorSelection) returns (uint256) { - return ValidatorSelectionLib.getSampleSeed(validatorSelectionStore, getCurrentEpoch()); - } - - /** - * @notice Performs a setup of an epoch if needed. The setup will - * - Sample the validator set for the epoch - * - Set the seed for the epoch - * - Update the last seed - * - * @dev Since this is a reference optimising for simplicity, we ValidatorSelectionStore the actual validator set in the epoch structure. - * 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() public override(IValidatorSelection) { - Epoch epochNumber = getCurrentEpoch(); - EpochData storage epoch = validatorSelectionStore.epochs[epochNumber]; - - if (epoch.sampleSeed == 0) { - epoch.sampleSeed = ValidatorSelectionLib.getSampleSeed(validatorSelectionStore, epochNumber); - epoch.nextSeed = validatorSelectionStore.lastSeed = _computeNextSeed(epochNumber); - epoch.committee = ValidatorSelectionLib.sampleValidators( - StakingLib.getStorage(), epoch.sampleSeed, TARGET_COMMITTEE_SIZE - ); - } - } - - /** - * @notice Get the attester set - * - * @dev Consider removing this to replace with a `size` and individual getter. - * - * @return The validator set - */ - function getAttesters() public view override(IValidatorSelection) returns (address[] memory) { - return StakingLib.getStorage().attesters.values(); - } - - /** - * @notice Get the current epoch number - * - * @return The current epoch number - */ - function getCurrentEpoch() public view override(IValidatorSelection) returns (Epoch) { - return getEpochAt(Timestamp.wrap(block.timestamp)); - } - - /** - * @notice Get the current slot number - * - * @return The current slot number - */ - function getCurrentSlot() public view override(IValidatorSelection) returns (Slot) { - return getSlotAt(Timestamp.wrap(block.timestamp)); - } - - /** - * @notice Get the timestamp for a given slot - * - * @param _slotNumber - The slot number to get the timestamp for - * - * @return The timestamp for the given slot - */ - function getTimestampForSlot(Slot _slotNumber) - public - view - override(IValidatorSelection) - returns (Timestamp) - { - return _slotNumber.toTimestamp(); - } - - /** - * @notice Get the proposer for the current slot - * - * @dev Calls `getCurrentProposer(uint256)` with the current timestamp - * - * @return The address of the proposer - */ - function getCurrentProposer() public view override(IValidatorSelection) returns (address) { - return getProposerAt(Timestamp.wrap(block.timestamp)); - } - - /** - * @notice Get the proposer for the slot at a specific timestamp - * - * @dev This function is very useful for off-chain usage, as it easily allow a client to - * determine who will be the proposer at the NEXT ethereum block. - * Should not be trusted when moving beyond the current epoch, since changes to the - * validator set might not be reflected when we actually reach that epoch (more changes - * might have happened). - * - * @dev The proposer is selected from the validator set of the current epoch. - * - * @dev Should only be access on-chain if epoch is setup, otherwise very expensive. - * - * @dev A return value of address(0) means that the proposer is "open" and can be anyone. - * - * @dev If the current epoch is the first epoch, returns address(0) - * If the current epoch is setup, we will return the proposer for the current slot - * If the current epoch is not setup, we will perform a sample as if it was (gas heavy) - * - * @return The address of the proposer - */ - function getProposerAt(Timestamp _ts) public view override(IValidatorSelection) returns (address) { - Slot slot = getSlotAt(_ts); - Epoch epochNumber = getEpochAtSlot(slot); - return ValidatorSelectionLib.getProposerAt( - validatorSelectionStore, StakingLib.getStorage(), slot, epochNumber, TARGET_COMMITTEE_SIZE - ); - } - - /** - * @notice Computes the epoch at a specific time - * - * @param _ts - The timestamp to compute the epoch for - * - * @return The computed epoch - */ - function getEpochAt(Timestamp _ts) public view override(IValidatorSelection) returns (Epoch) { - return _ts.epochFromTimestamp(); - } - - /** - * @notice Computes the slot at a specific time - * - * @param _ts - The timestamp to compute the slot for - * - * @return The computed slot - */ - function getSlotAt(Timestamp _ts) public view override(IValidatorSelection) returns (Slot) { - return _ts.slotFromTimestamp(); - } - - /** - * @notice Computes the epoch at a specific slot - * - * @param _slotNumber - The slot number to compute the epoch for - * - * @return The computed epoch - */ - function getEpochAtSlot(Slot _slotNumber) - public - view - override(IValidatorSelection) - returns (Epoch) - { - return _slotNumber.epochFromSlot(); - } - - // Can be used to add validators without setting up the epoch, useful for the initial set. - function _cheat__Deposit( - address _attester, - address _proposer, - address _withdrawer, - uint256 _amount - ) internal { - require( - _attester != address(0) && _proposer != address(0), - Errors.ValidatorSelection__InvalidDeposit(_attester, _proposer) - ); - - StakingLib.deposit(_attester, _proposer, _withdrawer, _amount); - } - - /** - * @notice Propose a pending block from the point-of-view of sequencer selection. Will: - * - Setup the epoch if needed (if epoch committee is empty skips the rest) - * - Validate that the proposer is the proposer of the slot - * - Validate that the signatures for attestations are indeed from the validatorset - * - Validate that the number of valid attestations is sufficient - * - * @dev Cases where errors are thrown: - * - If the epoch is not setup - * - If the proposer is not the real proposer AND the proposer is not open - * - If the number of valid attestations is insufficient - * - * @param _slot - The slot of the block - * @param _signatures - The signatures of the committee members - * @param _digest - The digest of the block - */ - function _validateValidatorSelection( - Slot _slot, - Signature[] memory _signatures, - bytes32 _digest, - DataStructures.ExecutionFlags memory _flags - ) internal view { - Epoch epochNumber = getEpochAtSlot(_slot); - ValidatorSelectionLib.validateValidatorSelection( - validatorSelectionStore, - StakingLib.getStorage(), - _slot, - epochNumber, - _signatures, - _digest, - _flags, - TARGET_COMMITTEE_SIZE - ); - } - - /** - * @notice Computes the nextSeed for an epoch - * - * @dev We include the `_epoch` instead of using the randao directly to avoid issues with foundry testing - * where randao == 0. - * - * @param _epoch - The epoch to compute the seed for - * - * @return The computed seed - */ - function _computeNextSeed(Epoch _epoch) private view returns (uint256) { - return uint256(keccak256(abi.encode(_epoch, block.prevrandao))); - } -} diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index c8216166cf0..df1c1e8b034 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -76,7 +76,7 @@ interface ITestRollup { returns (ManaBaseFeeComponents memory); } -interface IRollup { +interface IRollupInner { event L2BlockProposed( uint256 indexed blockNumber, bytes32 indexed archive, bytes32[] versionedBlobHashes ); @@ -112,17 +112,6 @@ interface IRollup { function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external; - function canProposeAtTime(Timestamp _ts, bytes32 _archive) external view returns (Slot, uint256); - - function validateHeader( - bytes calldata _header, - Signature[] memory _signatures, - bytes32 _digest, - Timestamp _currentTime, - bytes32 _blobsHash, - DataStructures.ExecutionFlags memory _flags - ) external view; - // solhint-disable-next-line func-name-mixedcase function INBOX() external view returns (IInbox); @@ -132,7 +121,22 @@ interface IRollup { // solhint-disable-next-line func-name-mixedcase function L1_BLOCK_AT_GENESIS() external view returns (uint256); - function getProofClaim() external view returns (DataStructures.EpochProofClaim memory); + function quoteToDigest(EpochProofQuote memory _quote) external view returns (bytes32); + + function getFeeAssetPrice() external view returns (uint256); + function getL1FeesAt(Timestamp _timestamp) external view returns (L1FeeData memory); + + function canPrune() external view returns (bool); + function canPruneAtTime(Timestamp _ts) external view returns (bool); + function getEpochToProve() external view returns (Epoch); + + function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) + external + view; + function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); +} + +interface IRollup is IRollupInner { function getTips() external view returns (ChainTips memory); function status(uint256 _myHeaderBlockNumber) @@ -147,25 +151,8 @@ interface IRollup { Epoch provenEpochNumber ); - function quoteToDigest(EpochProofQuote memory _quote) external view returns (bytes32); - function getBlock(uint256 _blockNumber) external view returns (BlockLog memory); - function getFeeAssetPrice() external view returns (uint256); - function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset) external view returns (uint256); - function getL1FeesAt(Timestamp _timestamp) external view returns (L1FeeData memory); + function getProofClaim() external view returns (DataStructures.EpochProofClaim memory); - function archive() external view returns (bytes32); - function archiveAt(uint256 _blockNumber) external view returns (bytes32); - function canPrune() external view returns (bool); - function canPruneAtTime(Timestamp _ts) external view returns (bool); - function getProvenBlockNumber() external view returns (uint256); - function getPendingBlockNumber() external view returns (uint256); - function getBlobPublicInputsHash(uint256 _blockNumber) external view returns (bytes32); - function getEpochToProve() external view returns (Epoch); - function getClaimableEpoch() external view returns (Epoch); - function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) - external - view; - function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); function getEpochProofPublicInputs( uint256 _epochSize, bytes32[7] calldata _args, @@ -173,8 +160,31 @@ interface IRollup { bytes calldata _blobPublicInputs, bytes calldata _aggregationObject ) external view returns (bytes32[] memory); + + function getClaimableEpoch() external view returns (Epoch); + + function validateHeader( + bytes calldata _header, + Signature[] memory _signatures, + bytes32 _digest, + Timestamp _currentTime, + bytes32 _blobsHash, + DataStructures.ExecutionFlags memory _flags + ) external view; + + function canProposeAtTime(Timestamp _ts, bytes32 _archive) external view returns (Slot, uint256); + function validateBlobs(bytes calldata _blobsInputs) external view returns (bytes32[] memory, bytes32, bytes32); + + function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset) external view returns (uint256); + + function archive() external view returns (bytes32); + function archiveAt(uint256 _blockNumber) external view returns (bytes32); + function getProvenBlockNumber() external view returns (uint256); + function getPendingBlockNumber() external view returns (uint256); + function getBlock(uint256 _blockNumber) external view returns (BlockLog memory); + function getBlobPublicInputsHash(uint256 _blockNumber) external view returns (bytes32); } diff --git a/l1-contracts/src/core/interfaces/IStaking.sol b/l1-contracts/src/core/interfaces/IStaking.sol index a3a7414f605..650605ae91e 100644 --- a/l1-contracts/src/core/interfaces/IStaking.sol +++ b/l1-contracts/src/core/interfaces/IStaking.sol @@ -45,7 +45,7 @@ struct StakingStorage { mapping(address attester => Exit) exits; } -interface IStaking { +interface IStakingInner { event Deposit( address indexed attester, address indexed proposer, address indexed withdrawer, uint256 amount ); @@ -58,7 +58,9 @@ interface IStaking { function initiateWithdraw(address _attester, address _recipient) external returns (bool); function finaliseWithdraw(address _attester) external; function slash(address _attester, uint256 _amount) external; +} +interface IStaking is IStakingInner { function getInfo(address _attester) external view returns (ValidatorInfo memory); function getExit(address _attester) external view returns (Exit memory); function getActiveAttesterCount() external view returns (uint256); diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 07dab165693..56bb6b2bc18 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -21,11 +21,15 @@ struct ValidatorSelectionStorage { mapping(Epoch => EpochData) epochs; // The last stored randao value, same value as `seed` in the last inserted epoch uint256 lastSeed; + uint256 targetCommitteeSize; } -interface IValidatorSelection { - // Likely changing to optimize in Pleistarchus +interface IValidatorSelectionInner { function setupEpoch() external; +} + +interface IValidatorSelection is IValidatorSelectionInner { + // Likely changing to optimize in Pleistarchus function getCurrentProposer() external view returns (address); function getProposerAt(Timestamp _ts) external view returns (address); @@ -53,4 +57,5 @@ interface IValidatorSelection { function getGenesisTime() external view returns (Timestamp); function getSlotDuration() external view returns (uint256); function getEpochDuration() external view returns (uint256); + function getTargetCommitteeSize() external view returns (uint256); } diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 9084c90fa4b..afd700cc04a 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -109,6 +109,7 @@ library Errors { error Staking__AlreadyRegistered(address); // 0x18047699 error Staking__CannotSlashExitedStake(address); // 0x45bf4940 error Staking__FailedToRemove(address); // 0xa7d7baab + error Staking__InvalidDeposit(address attester, address proposer); // 0xf33fe8c6 error Staking__InsufficientStake(uint256, uint256); // 0x903aee24 error Staking__NoOneToSlash(address); // 0x7e2f7f1c error Staking__NotExiting(address); // 0xef566ee0 diff --git a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol index 3738b5f7920..72931ba060e 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol @@ -9,6 +9,7 @@ import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {DataStructures} from "./../DataStructures.sol"; import {Errors} from "./../Errors.sol"; import {Timestamp, Slot, Epoch} from "./../TimeLib.sol"; +import {TimeLib} from "./../TimeLib.sol"; import {SignedEpochProofQuote} from "./EpochProofQuoteLib.sol"; import {Header} from "./HeaderLib.sol"; @@ -21,7 +22,6 @@ struct ValidateHeaderArgs { DataStructures.ExecutionFlags flags; uint256 version; IFeeJuicePortal feeJuicePortal; - function(Slot) external view returns (Timestamp) getTimestampForSlot; } library ValidationLib { @@ -56,7 +56,7 @@ library ValidationLib { Slot lastSlot = _blocks[_args.pendingBlockNumber].slotNumber; require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); - Timestamp timestamp = _args.getTimestampForSlot(slot); + Timestamp timestamp = TimeLib.toTimestamp(slot); require( Timestamp.wrap(_args.header.globalVariables.timestamp) == timestamp, Errors.Rollup__InvalidTimestamp( diff --git a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol index 981b7d92b2e..d2830f4a8cd 100644 --- a/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol @@ -10,7 +10,7 @@ import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; import {SignatureLib, 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 {Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; @@ -18,43 +18,31 @@ library ValidatorSelectionLib { using EnumerableSet for EnumerableSet.AddressSet; using MessageHashUtils for bytes32; using SignatureLib for Signature; + using TimeLib for Timestamp; + + bytes32 private constant VALIDATOR_SELECTION_STORAGE_POSITION = + keccak256("aztec.validator_selection.storage"); /** - * @notice Samples a validator set for a specific epoch - * - * @dev Only used internally, should never be called for anything but the "next" epoch - * Allowing us to always use `lastSeed`. + * @notice Performs a setup of an epoch if needed. The setup will + * - Sample the validator set for the epoch + * - Set the seed for the epoch + * - Update the last seed * - * @return The validators for the given epoch + * @dev Since this is a reference optimising for simplicity, we store the actual validator set in the epoch structure. + * This is very heavy on gas, so start crying because the gas here will melt the poles + * https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp */ - function sampleValidators( - StakingStorage storage _stakingStore, - uint256 _seed, - uint256 _targetCommitteeSize - ) external view returns (address[] memory) { - return _sampleValidators(_stakingStore, _seed, _targetCommitteeSize); - } + function setupEpoch(StakingStorage storage _stakingStore) external { + Epoch epochNumber = Timestamp.wrap(block.timestamp).epochFromTimestamp(); + ValidatorSelectionStorage storage store = getStorage(); + EpochData storage epoch = store.epochs[epochNumber]; - function getProposerAt( - ValidatorSelectionStorage storage _validatorSelectionStore, - StakingStorage storage _stakingStore, - Slot _slot, - Epoch _epochNumber, - uint256 _targetCommitteeSize - ) external view returns (address) { - return _getProposerAt( - _validatorSelectionStore, _stakingStore, _slot, _epochNumber, _targetCommitteeSize - ); - } - - function getCommitteeAt( - ValidatorSelectionStorage storage _validatorSelectionStore, - StakingStorage storage _stakingStore, - Epoch _epochNumber, - uint256 _targetCommitteeSize - ) external view returns (address[] memory) { - return - _getCommitteeAt(_validatorSelectionStore, _stakingStore, _epochNumber, _targetCommitteeSize); + if (epoch.sampleSeed == 0) { + epoch.sampleSeed = getSampleSeed(epochNumber); + epoch.nextSeed = store.lastSeed = computeNextSeed(epochNumber); + epoch.committee = sampleValidators(_stakingStore, epoch.sampleSeed); + } } /** @@ -74,23 +62,20 @@ library ValidatorSelectionLib { * @param _digest - The digest of the block */ function validateValidatorSelection( - ValidatorSelectionStorage storage _validatorSelectionStore, StakingStorage storage _stakingStore, Slot _slot, Epoch _epochNumber, Signature[] memory _signatures, bytes32 _digest, - DataStructures.ExecutionFlags memory _flags, - uint256 _targetCommitteeSize + DataStructures.ExecutionFlags memory _flags ) external view { // Same logic as we got in getProposerAt // Done do avoid duplicate computing the committee - address[] memory committee = - _getCommitteeAt(_validatorSelectionStore, _stakingStore, _epochNumber, _targetCommitteeSize); + address[] memory committee = getCommitteeAt(_stakingStore, _epochNumber); address attester = committee.length == 0 ? address(0) : committee[computeProposerIndex( - _epochNumber, _slot, getSampleSeed(_validatorSelectionStore, _epochNumber), committee.length + _epochNumber, _slot, getSampleSeed(_epochNumber), committee.length )]; address proposer = _stakingStore.info[attester].proposer; @@ -137,39 +122,25 @@ library ValidatorSelectionLib { ); } - /** - * @notice Get the sample seed for an epoch - * - * @dev This should behave as walking past the line, but it does not currently do that. - * If there are entire skips, e.g., 1, 2, 5 and we then go back and try executing - * for 4 we will get an invalid value because we will read lastSeed which is from 5. - * - * @dev The `_epoch` will never be 0 nor in the future - * - * @dev The return value will be equal to keccak256(n, block.prevrandao) for n being the last epoch - * setup. - * - * @return The sample seed for the epoch - */ - function getSampleSeed(ValidatorSelectionStorage storage _validatorSelectionStore, Epoch _epoch) - internal + function getProposerAt(StakingStorage storage _stakingStore, Slot _slot, Epoch _epochNumber) + external view - returns (uint256) + returns (address) { - if (Epoch.unwrap(_epoch) == 0) { - return type(uint256).max; - } - uint256 sampleSeed = _validatorSelectionStore.epochs[_epoch].sampleSeed; - if (sampleSeed != 0) { - return sampleSeed; + // @note this is deliberately "bad" for the simple reason of code reduction. + // it does not need to actually return the full committee and then draw from it + // it can just return the proposer directly, but then we duplicate the code + // which we just don't have room for right now... + address[] memory committee = getCommitteeAt(_stakingStore, _epochNumber); + if (committee.length == 0) { + return address(0); } - sampleSeed = _validatorSelectionStore.epochs[_epoch - Epoch.wrap(1)].nextSeed; - if (sampleSeed != 0) { - return sampleSeed; - } + address attester = committee[computeProposerIndex( + _epochNumber, _slot, getSampleSeed(_epochNumber), committee.length + )]; - return _validatorSelectionStore.lastSeed; + return _stakingStore.info[attester].proposer; } /** @@ -180,62 +151,41 @@ library ValidatorSelectionLib { * * @return The validators for the given epoch */ - function _sampleValidators( - StakingStorage storage _stakingStore, - uint256 _seed, - uint256 _targetCommitteeSize - ) private view returns (address[] memory) { + function sampleValidators(StakingStorage storage _stakingStore, uint256 _seed) + public + view + returns (address[] memory) + { uint256 validatorSetSize = _stakingStore.attesters.length(); if (validatorSetSize == 0) { return new address[](0); } + ValidatorSelectionStorage storage store = getStorage(); + uint256 targetCommitteeSize = store.targetCommitteeSize; + // If we have less validators than the target committee size, we just return the full set - if (validatorSetSize <= _targetCommitteeSize) { + if (validatorSetSize <= targetCommitteeSize) { return _stakingStore.attesters.values(); } uint256[] memory indices = - SampleLib.computeCommitteeClever(_targetCommitteeSize, validatorSetSize, _seed); + SampleLib.computeCommitteeClever(targetCommitteeSize, validatorSetSize, _seed); - address[] memory committee = new address[](_targetCommitteeSize); - for (uint256 i = 0; i < _targetCommitteeSize; i++) { + address[] memory committee = new address[](targetCommitteeSize); + for (uint256 i = 0; i < targetCommitteeSize; i++) { committee[i] = _stakingStore.attesters.at(indices[i]); } return committee; } - function _getProposerAt( - ValidatorSelectionStorage storage _validatorSelectionStore, - StakingStorage storage _stakingStore, - Slot _slot, - Epoch _epochNumber, - uint256 _targetCommitteeSize - ) private view returns (address) { - // @note this is deliberately "bad" for the simple reason of code reduction. - // it does not need to actually return the full committee and then draw from it - // it can just return the proposer directly, but then we duplicate the code - // which we just don't have room for right now... - address[] memory committee = - _getCommitteeAt(_validatorSelectionStore, _stakingStore, _epochNumber, _targetCommitteeSize); - if (committee.length == 0) { - return address(0); - } - - address attester = committee[computeProposerIndex( - _epochNumber, _slot, getSampleSeed(_validatorSelectionStore, _epochNumber), committee.length - )]; - - return _stakingStore.info[attester].proposer; - } - - function _getCommitteeAt( - ValidatorSelectionStorage storage _validatorSelectionStore, - StakingStorage storage _stakingStore, - Epoch _epochNumber, - uint256 _targetCommitteeSize - ) private view returns (address[] memory) { - EpochData storage epoch = _validatorSelectionStore.epochs[_epochNumber]; + function getCommitteeAt(StakingStorage storage _stakingStore, Epoch _epochNumber) + public + view + returns (address[] memory) + { + ValidatorSelectionStorage storage store = getStorage(); + EpochData storage epoch = store.epochs[_epochNumber]; if (epoch.sampleSeed != 0) { uint256 committeeSize = epoch.committee.length; @@ -251,8 +201,66 @@ library ValidatorSelectionLib { } // Emulate a sampling of the validators - uint256 sampleSeed = getSampleSeed(_validatorSelectionStore, _epochNumber); - return _sampleValidators(_stakingStore, sampleSeed, _targetCommitteeSize); + uint256 sampleSeed = getSampleSeed(_epochNumber); + return sampleValidators(_stakingStore, sampleSeed); + } + + function initialize(uint256 _targetCommitteeSize) internal { + ValidatorSelectionStorage storage store = getStorage(); + store.targetCommitteeSize = _targetCommitteeSize; + } + + /** + * @notice Get the sample seed for an epoch + * + * @dev This should behave as walking past the line, but it does not currently do that. + * If there are entire skips, e.g., 1, 2, 5 and we then go back and try executing + * for 4 we will get an invalid value because we will read lastSeed which is from 5. + * + * @dev The `_epoch` will never be 0 nor in the future + * + * @dev The return value will be equal to keccak256(n, block.prevrandao) for n being the last epoch + * setup. + * + * @return The sample seed for the epoch + */ + function getSampleSeed(Epoch _epoch) internal view returns (uint256) { + if (Epoch.unwrap(_epoch) == 0) { + return type(uint256).max; + } + ValidatorSelectionStorage storage store = getStorage(); + uint256 sampleSeed = store.epochs[_epoch].sampleSeed; + if (sampleSeed != 0) { + return sampleSeed; + } + + sampleSeed = store.epochs[_epoch - Epoch.wrap(1)].nextSeed; + if (sampleSeed != 0) { + return sampleSeed; + } + + return store.lastSeed; + } + + function getStorage() internal pure returns (ValidatorSelectionStorage storage storageStruct) { + bytes32 position = VALIDATOR_SELECTION_STORAGE_POSITION; + assembly { + storageStruct.slot := position + } + } + + /** + * @notice Computes the nextSeed for an epoch + * + * @dev We include the `_epoch` instead of using the randao directly to avoid issues with foundry testing + * where randao == 0. + * + * @param _epoch - The epoch to compute the seed for + * + * @return The computed seed + */ + function computeNextSeed(Epoch _epoch) private view returns (uint256) { + return uint256(keccak256(abi.encode(_epoch, block.prevrandao))); } /** diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 0071f09b626..5a6feaca8da 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -7,9 +7,9 @@ import { ValidatorInfo, Exit, Timestamp, - StakingStorage + StakingStorage, + IStakingInner } from "@aztec/core/interfaces/IStaking.sol"; -import {IStaking} from "@aztec/core/interfaces/IStaking.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; @@ -53,7 +53,7 @@ library StakingLib { store.stakingAsset.transfer(recipient, amount); - emit IStaking.WithdrawFinalised(_attester, recipient, amount); + emit IStakingInner.WithdrawFinalised(_attester, recipient, amount); } function slash(address _attester, uint256 _amount) external { @@ -81,12 +81,16 @@ library StakingLib { validator.status = Status.LIVING; } - emit IStaking.Slashed(_attester, _amount); + emit IStakingInner.Slashed(_attester, _amount); } function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) external { + require( + _attester != address(0) && _proposer != address(0), + Errors.Staking__InvalidDeposit(_attester, _proposer) + ); StakingStorage storage store = getStorage(); require( _amount >= store.minimumStake, Errors.Staking__InsufficientStake(_amount, store.minimumStake) @@ -106,7 +110,7 @@ library StakingLib { status: Status.VALIDATING }); - emit IStaking.Deposit(_attester, _proposer, _withdrawer, _amount); + emit IStakingInner.Deposit(_attester, _proposer, _withdrawer, _amount); } function initiateWithdraw(address _attester, address _recipient) external returns (bool) { @@ -131,7 +135,7 @@ library StakingLib { Exit({exitableAt: Timestamp.wrap(block.timestamp) + store.exitDelay, recipient: _recipient}); validator.status = Status.EXITING; - emit IStaking.WithdrawInitiated(_attester, _recipient, validator.stake); + emit IStakingInner.WithdrawInitiated(_attester, _recipient, validator.stake); return true; } diff --git a/l1-contracts/src/periphery/SlashPayload.sol b/l1-contracts/src/periphery/SlashPayload.sol index b39fcb8301c..2ab08be0494 100644 --- a/l1-contracts/src/periphery/SlashPayload.sol +++ b/l1-contracts/src/periphery/SlashPayload.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {IStaking} from "@aztec/core/interfaces/IStaking.sol"; +import {IStakingInner} from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; @@ -28,7 +28,7 @@ contract SlashPayload is IPayload { for (uint256 i = 0; i < attesters.length; i++) { actions[i] = IPayload.Action({ target: address(VALIDATOR_SELECTION), - data: abi.encodeWithSelector(IStaking.slash.selector, attesters[i], AMOUNT) + data: abi.encodeWithSelector(IStakingInner.slash.selector, attesters[i], AMOUNT) }); } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index ad454786e91..1d113ce9d5d 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -18,10 +18,11 @@ 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} from "./harnesses/Rollup.sol"; -import {IRollup, BlockLog, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import { + IRollupInner, BlockLog, SubmitEpochRootProofArgs +} from "@aztec/core/interfaces/IRollup.sol"; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; -import {ValidatorSelection} from "@aztec/core/ValidatorSelection.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; import {MerkleTestUtil} from "./merkle/TestUtil.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; @@ -54,7 +55,6 @@ contract RollupTest is DecoderBase { Inbox internal inbox; Outbox internal outbox; Rollup internal rollup; - ValidatorSelection internal leo; MerkleTestUtil internal merkleTestUtil; TestERC20 internal testERC20; FeeJuicePortal internal feeJuicePortal; @@ -86,15 +86,6 @@ contract RollupTest is DecoderBase { { testERC20 = new TestERC20("test", "TEST", address(this)); - leo = new ValidatorSelection( - testERC20, - TestConstants.AZTEC_MINIMUM_STAKE, - TestConstants.AZTEC_SLASHING_QUORUM, - TestConstants.AZTEC_SLASHING_ROUND_SIZE, - TestConstants.AZTEC_SLOT_DURATION, - TestConstants.AZTEC_EPOCH_DURATION, - TestConstants.AZTEC_TARGET_COMMITTEE_SIZE - ); DecoderBase.Full memory full = load(_name); uint256 slotNumber = full.block.decodedHeader.globalVariables.slotNumber; uint256 initialTime = @@ -273,7 +264,7 @@ contract RollupTest is DecoderBase { _testBlock("mixed_block_1", false, 1); vm.expectEmit(true, true, true, true); - emit IRollup.ProofRightClaimed( + emit IRollupInner.ProofRightClaimed( quote.epochToProve, quote.prover, address(this), quote.bondAmount, Slot.wrap(1) ); rollup.claimEpochProofRight(signedQuote); @@ -455,7 +446,7 @@ contract RollupTest is DecoderBase { signedQuote = _quoteToSignedQuote(quote); vm.expectEmit(true, true, true, true); - emit IRollup.ProofRightClaimed( + emit IRollupInner.ProofRightClaimed( quote.epochToProve, quote.prover, address(this), quote.bondAmount, Epoch.wrap(3).toSlots() ); rollup.claimEpochProofRight(signedQuote); diff --git a/l1-contracts/test/base/Base.sol b/l1-contracts/test/base/Base.sol index 52d175faeab..5d646d20cea 100644 --- a/l1-contracts/test/base/Base.sol +++ b/l1-contracts/test/base/Base.sol @@ -230,6 +230,6 @@ contract TestBase is Test { // and looking in the logs. Interesting. // Alternative, run forge inspect src/core/Rollup.sol:Rollup storageLayout --pretty // uint256 slot = stdstore.target(address(rollup)).sig("checkBlob()").find(); - vm.store(address(rollup), bytes32(uint256(5)), bytes32(uint256(0))); + vm.store(address(rollup), bytes32(uint256(4)), bytes32(uint256(0))); } } diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index c79eaa05032..8093c87f261 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -26,7 +26,6 @@ import { import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; -import {ValidatorSelection} from "@aztec/core/ValidatorSelection.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MerkleTestUtil} from "../merkle/TestUtil.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; diff --git a/l1-contracts/test/governance/governance-proposer/executeProposal.t.sol b/l1-contracts/test/governance/governance-proposer/executeProposal.t.sol index 9ccdcc234ef..817269c4c20 100644 --- a/l1-contracts/test/governance/governance-proposer/executeProposal.t.sol +++ b/l1-contracts/test/governance/governance-proposer/executeProposal.t.sol @@ -4,17 +4,17 @@ pragma solidity >=0.8.27; import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol"; import {GovernanceProposerBase} from "./Base.t.sol"; -import {ValidatorSelection} from "../../harnesses/ValidatorSelection.sol"; import {Errors} from "@aztec/governance/libraries/Errors.sol"; import {Slot, SlotLib, Timestamp} from "@aztec/core/libraries/TimeLib.sol"; import {FaultyGovernance} from "./mocks/FaultyGovernance.sol"; import {FalsyGovernance} from "./mocks/FalsyGovernance.sol"; +import {Fakerollup} from "./mocks/Fakerollup.sol"; contract ExecuteProposalTest is GovernanceProposerBase { using SlotLib for Slot; - ValidatorSelection internal validatorSelection; + Fakerollup internal validatorSelection; IPayload internal proposal = IPayload(address(this)); address internal proposer = address(0); @@ -30,7 +30,7 @@ contract ExecuteProposalTest is GovernanceProposerBase { } modifier givenCanonicalInstanceHoldCode() { - validatorSelection = new ValidatorSelection(); + validatorSelection = new Fakerollup(); vm.prank(registry.getGovernance()); registry.upgrade(address(validatorSelection)); @@ -221,7 +221,7 @@ contract ExecuteProposalTest is GovernanceProposerBase { // it revert // When using a new registry we change the governanceProposer's interpetation of time :O - ValidatorSelection freshInstance = new ValidatorSelection(); + Fakerollup freshInstance = new Fakerollup(); vm.prank(registry.getGovernance()); registry.upgrade(address(freshInstance)); diff --git a/l1-contracts/test/governance/governance-proposer/mocks/Fakerollup.sol b/l1-contracts/test/governance/governance-proposer/mocks/Fakerollup.sol new file mode 100644 index 00000000000..cc4ee196a75 --- /dev/null +++ b/l1-contracts/test/governance/governance-proposer/mocks/Fakerollup.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import { + Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeLib +} from "@aztec/core/libraries/TimeLib.sol"; +import {TestConstants} from "../../../harnesses/TestConstants.sol"; + +contract Fakerollup { + using TimeLib for Slot; + using TimeLib for Timestamp; + + constructor() { + TimeLib.initialize( + block.timestamp, TestConstants.AZTEC_SLOT_DURATION, TestConstants.AZTEC_EPOCH_DURATION + ); + } + + function getTimestampForSlot(Slot _slot) external view returns (Timestamp) { + return _slot.toTimestamp(); + } + + function getCurrentSlot() external view returns (Slot) { + return Timestamp.wrap(block.timestamp).slotFromTimestamp(); + } + + function getGenesisTime() external view returns (Timestamp) { + return Timestamp.wrap(TimeLib.getStorage().genesisTime); + } + + function getSlotDuration() external view returns (uint256) { + return TimeLib.getStorage().slotDuration; + } + + function getEpochDuration() external view returns (uint256) { + return TimeLib.getStorage().epochDuration; + } + + function getCurrentProposer() external pure returns (address) { + return address(0); + } +} diff --git a/l1-contracts/test/governance/governance-proposer/vote.t.sol b/l1-contracts/test/governance/governance-proposer/vote.t.sol index 00144b652b5..89ba7e4a6ad 100644 --- a/l1-contracts/test/governance/governance-proposer/vote.t.sol +++ b/l1-contracts/test/governance/governance-proposer/vote.t.sol @@ -4,16 +4,16 @@ pragma solidity >=0.8.27; import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol"; import {GovernanceProposerBase} from "./Base.t.sol"; -import {ValidatorSelection} from "../../harnesses/ValidatorSelection.sol"; import {Errors} from "@aztec/governance/libraries/Errors.sol"; import {Slot, SlotLib, Timestamp} from "@aztec/core/libraries/TimeLib.sol"; +import {Fakerollup} from "./mocks/Fakerollup.sol"; contract VoteTest is GovernanceProposerBase { using SlotLib for Slot; IPayload internal proposal = IPayload(address(0xdeadbeef)); address internal proposer = address(0); - ValidatorSelection internal validatorSelection; + Fakerollup internal validatorSelection; // Skipping this test since the it matches the for now skipped check in `EmpireBase::vote` function skip__test_WhenProposalHoldNoCode() external { @@ -40,7 +40,7 @@ contract VoteTest is GovernanceProposerBase { } modifier givenCanonicalRollupHoldCode() { - validatorSelection = new ValidatorSelection(); + validatorSelection = new Fakerollup(); vm.prank(registry.getGovernance()); registry.upgrade(address(validatorSelection)); @@ -144,7 +144,7 @@ contract VoteTest is GovernanceProposerBase { uint256 yeaBefore = governanceProposer.yeaCount(address(validatorSelection), validatorSelectionRound, proposal); - ValidatorSelection freshInstance = new ValidatorSelection(); + Fakerollup freshInstance = new Fakerollup(); vm.prank(registry.getGovernance()); registry.upgrade(address(freshInstance)); diff --git a/l1-contracts/test/harnesses/ValidatorSelection.sol b/l1-contracts/test/harnesses/ValidatorSelection.sol deleted file mode 100644 index 1ff93546cfa..00000000000 --- a/l1-contracts/test/harnesses/ValidatorSelection.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {ValidatorSelection as RealValidatorSelection} from "@aztec/core/ValidatorSelection.sol"; -import {TestConstants} from "./TestConstants.sol"; -import {TestERC20} from "@aztec/mock/TestERC20.sol"; - -contract ValidatorSelection is RealValidatorSelection { - constructor() - RealValidatorSelection( - new TestERC20("test", "TEST", address(this)), - 100e18, - TestConstants.AZTEC_SLASHING_QUORUM, - TestConstants.AZTEC_SLASHING_ROUND_SIZE, - TestConstants.AZTEC_SLOT_DURATION, - TestConstants.AZTEC_EPOCH_DURATION, - TestConstants.AZTEC_TARGET_COMMITTEE_SIZE - ) - {} -} diff --git a/l1-contracts/test/portals/UniswapPortal.t.sol b/l1-contracts/test/portals/UniswapPortal.t.sol index 7f4060be639..b74fab88761 100644 --- a/l1-contracts/test/portals/UniswapPortal.t.sol +++ b/l1-contracts/test/portals/UniswapPortal.t.sol @@ -22,7 +22,10 @@ import {UniswapPortal} from "./UniswapPortal.sol"; import {MockFeeJuicePortal} from "@aztec/mock/MockFeeJuicePortal.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; +import {stdStorage, StdStorage} from "forge-std/Test.sol"; + contract UniswapPortalTest is Test { + using stdStorage for StdStorage; using Hash for DataStructures.L2ToL1Msg; IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); @@ -70,6 +73,8 @@ contract UniswapPortalTest is Test { // Modify the proven block count vm.store(address(rollup), bytes32(uint256(13)), bytes32(l2BlockNumber + 1)); + + stdstore.target(address(rollup)).sig("getProvenBlockNumber()").checked_write(l2BlockNumber + 1); assertEq(rollup.getProvenBlockNumber(), l2BlockNumber + 1); // have DAI locked in portal that can be moved when funds are withdrawn diff --git a/l1-contracts/test/staking/deposit.t.sol b/l1-contracts/test/staking/deposit.t.sol index ac2f51be3ec..b789b4a180e 100644 --- a/l1-contracts/test/staking/deposit.t.sol +++ b/l1-contracts/test/staking/deposit.t.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; -import {IStaking, Status, ValidatorInfo} from "@aztec/core/interfaces/IStaking.sol"; +import {IStakingInner, Status, ValidatorInfo} from "@aztec/core/interfaces/IStaking.sol"; contract DepositTest is StakingBase { uint256 internal depositAmount; @@ -147,7 +147,7 @@ contract DepositTest is StakingBase { assertEq(stakingAsset.balanceOf(address(staking)), 0); vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.Deposit(ATTESTER, PROPOSER, WITHDRAWER, depositAmount); + emit IStakingInner.Deposit(ATTESTER, PROPOSER, WITHDRAWER, depositAmount); staking.deposit({ _attester: ATTESTER, diff --git a/l1-contracts/test/staking/finaliseWithdraw.t.sol b/l1-contracts/test/staking/finaliseWithdraw.t.sol index 1cdde4fcddb..ac824186331 100644 --- a/l1-contracts/test/staking/finaliseWithdraw.t.sol +++ b/l1-contracts/test/staking/finaliseWithdraw.t.sol @@ -4,7 +4,11 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import { - Timestamp, Status, ValidatorInfo, Exit, IStaking + Timestamp, + Status, + ValidatorInfo, + Exit, + IStakingInner } from "@aztec/core/interfaces/IStaking.sol"; contract FinaliseWithdrawTest is StakingBase { @@ -69,7 +73,7 @@ contract FinaliseWithdrawTest is StakingBase { vm.warp(Timestamp.unwrap(exit.exitableAt)); vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.WithdrawFinalised(ATTESTER, RECIPIENT, MINIMUM_STAKE); + emit IStakingInner.WithdrawFinalised(ATTESTER, RECIPIENT, MINIMUM_STAKE); staking.finaliseWithdraw(ATTESTER); exit = staking.getExit(ATTESTER); diff --git a/l1-contracts/test/staking/initiateWithdraw.t.sol b/l1-contracts/test/staking/initiateWithdraw.t.sol index fe69c47c7d1..9d7deaa99fa 100644 --- a/l1-contracts/test/staking/initiateWithdraw.t.sol +++ b/l1-contracts/test/staking/initiateWithdraw.t.sol @@ -4,7 +4,11 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import { - Timestamp, Status, ValidatorInfo, Exit, IStaking + Timestamp, + Status, + ValidatorInfo, + Exit, + IStakingInner } from "@aztec/core/interfaces/IStaking.sol"; contract InitiateWithdrawTest is StakingBase { @@ -105,7 +109,7 @@ contract InitiateWithdrawTest is StakingBase { assertEq(staking.getActiveAttesterCount(), 1); vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); + emit IStakingInner.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, RECIPIENT); @@ -138,7 +142,7 @@ contract InitiateWithdrawTest is StakingBase { assertEq(staking.getActiveAttesterCount(), 0); vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); + emit IStakingInner.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, RECIPIENT); diff --git a/l1-contracts/test/staking/slash.t.sol b/l1-contracts/test/staking/slash.t.sol index d0cfe2e4364..c96034a2fa3 100644 --- a/l1-contracts/test/staking/slash.t.sol +++ b/l1-contracts/test/staking/slash.t.sol @@ -4,7 +4,11 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import { - IStaking, Status, ValidatorInfo, Exit, Timestamp + IStakingInner, + Status, + ValidatorInfo, + Exit, + Timestamp } from "@aztec/core/interfaces/IStaking.sol"; contract SlashTest is StakingBase { @@ -83,7 +87,7 @@ contract SlashTest is StakingBase { assertTrue(info.status == Status.EXITING); vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.Slashed(ATTESTER, 1); + emit IStakingInner.Slashed(ATTESTER, 1); vm.prank(SLASHER); staking.slash(ATTESTER, 1); @@ -113,7 +117,7 @@ contract SlashTest is StakingBase { uint256 balance = info.stake; vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.Slashed(ATTESTER, 1); + emit IStakingInner.Slashed(ATTESTER, 1); vm.prank(SLASHER); staking.slash(ATTESTER, 1); @@ -163,7 +167,7 @@ contract SlashTest is StakingBase { uint256 balance = info.stake; vm.expectEmit(true, true, true, true, address(staking)); - emit IStaking.Slashed(ATTESTER, slashingAmount); + emit IStakingInner.Slashed(ATTESTER, slashingAmount); vm.prank(SLASHER); staking.slash(ATTESTER, slashingAmount); diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index adcf5ceef4e..46fd1bea36c 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -13,7 +13,6 @@ import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Registry} from "@aztec/governance/Registry.sol"; import {Rollup, Config} from "@aztec/core/Rollup.sol"; -import {ValidatorSelection} from "@aztec/core/ValidatorSelection.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MerkleTestUtil} from "../merkle/TestUtil.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; @@ -69,20 +68,10 @@ contract ValidatorSelectionTest is DecoderBase { modifier setup(uint256 _validatorCount) { string memory _name = "mixed_block_1"; { - ValidatorSelection validatorSelection = new ValidatorSelection( - testERC20, - TestConstants.AZTEC_MINIMUM_STAKE, - TestConstants.AZTEC_SLASHING_QUORUM, - TestConstants.AZTEC_SLASHING_ROUND_SIZE, - TestConstants.AZTEC_SLOT_DURATION, - TestConstants.AZTEC_EPOCH_DURATION, - TestConstants.AZTEC_TARGET_COMMITTEE_SIZE - ); - DecoderBase.Full memory full = load(_name); uint256 slotNumber = full.block.decodedHeader.globalVariables.slotNumber; uint256 initialTime = full.block.decodedHeader.globalVariables.timestamp - - slotNumber * validatorSelection.getSlotDuration(); + - slotNumber * TestConstants.AZTEC_SLOT_DURATION; vm.warp(initialTime); } @@ -187,14 +176,14 @@ contract ValidatorSelectionTest is DecoderBase { } function testValidatorSetLargerThanCommittee(bool _insufficientSigs) public setup(100) { - assertGt(rollup.getAttesters().length, rollup.TARGET_COMMITTEE_SIZE(), "Not enough validators"); - uint256 committeeSize = rollup.TARGET_COMMITTEE_SIZE() * 2 / 3 + (_insufficientSigs ? 0 : 1); + assertGt(rollup.getAttesters().length, rollup.getTargetCommitteeSize(), "Not enough validators"); + uint256 committeeSize = rollup.getTargetCommitteeSize() * 2 / 3 + (_insufficientSigs ? 0 : 1); _testBlock("mixed_block_1", _insufficientSigs, committeeSize, false); assertEq( rollup.getEpochCommittee(rollup.getCurrentEpoch()).length, - rollup.TARGET_COMMITTEE_SIZE(), + rollup.getTargetCommitteeSize(), "Invalid committee size" ); } diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index 1cbe315c2bb..eaa2fca5d45 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -142,7 +142,7 @@ export class EpochCache extends EventEmitter<{ committeeChanged: [EthAddress[], } /** - * Get the ABI encoding of the proposer index - see Leonidas.sol _computeProposerIndex + * Get the ABI encoding of the proposer index - see ValidatorSelectionLib.sol computeProposerIndex */ getProposerIndexEncoding(epoch: bigint, slot: bigint, seed: bigint): `0x${string}` { return encodeAbiParameters( diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 45013f3b30a..6fc021d316d 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -113,7 +113,7 @@ export class RollupContract { @memoize getTargetCommitteeSize() { - return this.rollup.read.TARGET_COMMITTEE_SIZE(); + return this.rollup.read.getTargetCommitteeSize(); } @memoize From 74b4acee8354d3d6077668b68a8e5c2e9bfc1a85 Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Tue, 4 Feb 2025 18:21:18 +0000 Subject: [PATCH 2/3] chore: fix forwarder --- l1-contracts/src/periphery/Forwarder.sol | 6 +++++- l1-contracts/test/Forwarder.t.sol | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/l1-contracts/src/periphery/Forwarder.sol b/l1-contracts/src/periphery/Forwarder.sol index a921b2ab500..0f3673336e2 100644 --- a/l1-contracts/src/periphery/Forwarder.sol +++ b/l1-contracts/src/periphery/Forwarder.sol @@ -11,7 +11,11 @@ contract Forwarder is Ownable, IForwarder { constructor(address __owner) Ownable(__owner) {} - function forward(address[] calldata _to, bytes[] calldata _data) external override onlyOwner { + function forward(address[] calldata _to, bytes[] calldata _data) + external + override(IForwarder) + onlyOwner + { require( _to.length == _data.length, IForwarder.ForwarderLengthMismatch(_to.length, _data.length) ); diff --git a/l1-contracts/test/Forwarder.t.sol b/l1-contracts/test/Forwarder.t.sol index 8599dcca51f..0470dbb4c8f 100644 --- a/l1-contracts/test/Forwarder.t.sol +++ b/l1-contracts/test/Forwarder.t.sol @@ -66,9 +66,8 @@ contract ForwarderTest is Test { } function testRevertWhenCallToInvalidAddress(address _invalidAddress) public { - vm.assume(_invalidAddress != address(token1)); - vm.assume(_invalidAddress != address(token2)); - vm.assume(_invalidAddress != address(forwarder)); + vm.assume(_invalidAddress.code.length == 0); + vm.assume(uint160(_invalidAddress) > uint160(0x0a)); address[] memory targets = new address[](1); targets[0] = _invalidAddress; From ffdd56800336f3f956a248099709f7dfb76e8666 Mon Sep 17 00:00:00 2001 From: LHerskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:16:10 +0000 Subject: [PATCH 3/3] chore: rename interfaces --- l1-contracts/src/core/RollupCore.sol | 60 ++++++++----------- l1-contracts/src/core/interfaces/IRollup.sol | 4 +- l1-contracts/src/core/interfaces/IStaking.sol | 4 +- .../core/interfaces/IValidatorSelection.sol | 4 +- .../src/core/libraries/staking/StakingLib.sol | 10 ++-- l1-contracts/src/periphery/SlashPayload.sol | 4 +- l1-contracts/test/Rollup.t.sol | 8 +-- l1-contracts/test/staking/deposit.t.sol | 4 +- .../test/staking/finaliseWithdraw.t.sol | 8 +-- .../test/staking/initiateWithdraw.t.sol | 10 +--- l1-contracts/test/staking/slash.t.sol | 12 ++-- 11 files changed, 53 insertions(+), 75 deletions(-) diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 8a9a8c33362..34a5d6d0100 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import { - IRollupInner, + IRollupCore, ITestRollup, CheatDepositArgs, FeeHeader, @@ -16,8 +16,8 @@ import { L1FeeData, SubmitEpochRootProofArgs } from "@aztec/core/interfaces/IRollup.sol"; -import {IStakingInner} from "@aztec/core/interfaces/IStaking.sol"; -import {IValidatorSelectionInner} from "@aztec/core/interfaces/IValidatorSelection.sol"; +import {IStakingCore} from "@aztec/core/interfaces/IStaking.sol"; +import {IValidatorSelectionCore} from "@aztec/core/interfaces/IValidatorSelection.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"; @@ -66,12 +66,12 @@ struct Config { * not giving a damn about gas costs. * @dev WARNING: This contract is VERY close to the size limit (500B at time of writing). */ -abstract contract RollupCore is +contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, - IStakingInner, - IValidatorSelectionInner, - IRollupInner, + IStakingCore, + IValidatorSelectionCore, + IRollupCore, ITestRollup { using ProposeLib for ProposeArgs; @@ -174,7 +174,7 @@ abstract contract RollupCore is function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) external - override(IStakingInner) + override(IStakingCore) { setupEpoch(); StakingLib.deposit(_attester, _proposer, _withdrawer, _amount); @@ -182,7 +182,7 @@ abstract contract RollupCore is function initiateWithdraw(address _attester, address _recipient) external - override(IStakingInner) + override(IStakingCore) returns (bool) { // @note The attester might be chosen for the epoch, so the delay must be long enough @@ -191,11 +191,11 @@ abstract contract RollupCore is return StakingLib.initiateWithdraw(_attester, _recipient); } - function finaliseWithdraw(address _attester) external override(IStakingInner) { + function finaliseWithdraw(address _attester) external override(IStakingCore) { StakingLib.finaliseWithdraw(_attester); } - function slash(address _attester, uint256 _amount) external override(IStakingInner) { + function slash(address _attester, uint256 _amount) external override(IStakingCore) { StakingLib.slash(_attester, _amount); } @@ -215,7 +215,7 @@ abstract contract RollupCore is * * @dev Will revert if there is nothing to prune or if the chain is not ready to be pruned */ - function prune() external override(IRollupInner) { + function prune() external override(IRollupCore) { require(canPrune(), Errors.Rollup__NothingToPrune()); _prune(); } @@ -286,7 +286,7 @@ abstract contract RollupCore is bytes calldata _body, bytes calldata _blobInput, SignedEpochProofQuote calldata _quote - ) external override(IRollupInner) { + ) external override(IRollupCore) { propose(_args, _signatures, _body, _blobInput); claimEpochProofRight(_quote); } @@ -315,7 +315,7 @@ abstract contract RollupCore is */ function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external - override(IRollupInner) + override(IRollupCore) { if (canPrune()) { _prune(); @@ -347,14 +347,11 @@ abstract contract RollupCore is emit L2ProofVerified(endBlockNumber, _args.args[6]); } - function setupEpoch() public override(IValidatorSelectionInner) { + function setupEpoch() public override(IValidatorSelectionCore) { ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); } - function claimEpochProofRight(SignedEpochProofQuote calldata _quote) - public - override(IRollupInner) - { + function claimEpochProofRight(SignedEpochProofQuote calldata _quote) public override(IRollupCore) { validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), _quote); Slot currentSlot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); @@ -394,7 +391,7 @@ abstract contract RollupCore is // TODO(#9101): Extract blobs from beacon chain => remove below body input bytes calldata, bytes calldata _blobInput - ) public override(IRollupInner) { + ) public override(IRollupCore) { if (canPrune()) { _prune(); } @@ -471,7 +468,7 @@ abstract contract RollupCore is * @notice Updates the l1 gas fee oracle * @dev This function is called by the `propose` function */ - function updateL1GasFeeOracle() public override(IRollupInner) { + 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); @@ -491,7 +488,7 @@ abstract contract RollupCore is * * @return The fee asset price */ - function getFeeAssetPrice() public view override(IRollupInner) returns (uint256) { + function getFeeAssetPrice() public view override(IRollupCore) returns (uint256) { return IntRollupLib.feeAssetPriceModifier( rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator ); @@ -500,7 +497,7 @@ abstract contract RollupCore is function getL1FeesAt(Timestamp _timestamp) public view - override(IRollupInner) + override(IRollupCore) returns (L1FeeData memory) { return _timestamp.slotFromTimestamp() < rollupStore.l1GasOracleValues.slotOfChange @@ -542,7 +539,7 @@ abstract contract RollupCore is function quoteToDigest(EpochProofQuote memory _quote) public view - override(IRollupInner) + override(IRollupCore) returns (bytes32) { return _hashTypedDataV4(IntRollupLib.computeQuoteHash(_quote)); @@ -551,7 +548,7 @@ abstract contract RollupCore is function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) public view - override(IRollupInner) + override(IRollupCore) { Slot currentSlot = _ts.slotFromTimestamp(); address currentProposer = ValidatorSelectionLib.getProposerAt( @@ -575,12 +572,7 @@ abstract contract RollupCore is ); } - function getEpochForBlock(uint256 _blockNumber) - public - view - override(IRollupInner) - returns (Epoch) - { + function getEpochForBlock(uint256 _blockNumber) public view override(IRollupCore) returns (Epoch) { require( _blockNumber <= rollupStore.tips.pendingBlockNumber, Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) @@ -597,7 +589,7 @@ abstract contract RollupCore is * * @return uint256 - The epoch to prove */ - function getEpochToProve() public view override(IRollupInner) returns (Epoch) { + function getEpochToProve() public view override(IRollupCore) returns (Epoch) { require( rollupStore.tips.provenBlockNumber != rollupStore.tips.pendingBlockNumber, Errors.Rollup__NoEpochToProve() @@ -605,11 +597,11 @@ abstract contract RollupCore is return getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); } - function canPrune() public view override(IRollupInner) returns (bool) { + function canPrune() public view override(IRollupCore) returns (bool) { return canPruneAtTime(Timestamp.wrap(block.timestamp)); } - function canPruneAtTime(Timestamp _ts) public view override(IRollupInner) returns (bool) { + function canPruneAtTime(Timestamp _ts) public view override(IRollupCore) returns (bool) { if ( rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber || rollupStore.tips.pendingBlockNumber <= assumeProvenThroughBlockNumber diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index df1c1e8b034..edda9efec3c 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -76,7 +76,7 @@ interface ITestRollup { returns (ManaBaseFeeComponents memory); } -interface IRollupInner { +interface IRollupCore { event L2BlockProposed( uint256 indexed blockNumber, bytes32 indexed archive, bytes32[] versionedBlobHashes ); @@ -136,7 +136,7 @@ interface IRollupInner { function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); } -interface IRollup is IRollupInner { +interface IRollup is IRollupCore { function getTips() external view returns (ChainTips memory); function status(uint256 _myHeaderBlockNumber) diff --git a/l1-contracts/src/core/interfaces/IStaking.sol b/l1-contracts/src/core/interfaces/IStaking.sol index 650605ae91e..3460af47db1 100644 --- a/l1-contracts/src/core/interfaces/IStaking.sol +++ b/l1-contracts/src/core/interfaces/IStaking.sol @@ -45,7 +45,7 @@ struct StakingStorage { mapping(address attester => Exit) exits; } -interface IStakingInner { +interface IStakingCore { event Deposit( address indexed attester, address indexed proposer, address indexed withdrawer, uint256 amount ); @@ -60,7 +60,7 @@ interface IStakingInner { function slash(address _attester, uint256 _amount) external; } -interface IStaking is IStakingInner { +interface IStaking is IStakingCore { function getInfo(address _attester) external view returns (ValidatorInfo memory); function getExit(address _attester) external view returns (Exit memory); function getActiveAttesterCount() external view returns (uint256); diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 56bb6b2bc18..387abe16b66 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -24,11 +24,11 @@ struct ValidatorSelectionStorage { uint256 targetCommitteeSize; } -interface IValidatorSelectionInner { +interface IValidatorSelectionCore { function setupEpoch() external; } -interface IValidatorSelection is IValidatorSelectionInner { +interface IValidatorSelection is IValidatorSelectionCore { // Likely changing to optimize in Pleistarchus function getCurrentProposer() external view returns (address); function getProposerAt(Timestamp _ts) external view returns (address); diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 5a6feaca8da..6c02dfdf54d 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -8,7 +8,7 @@ import { Exit, Timestamp, StakingStorage, - IStakingInner + IStakingCore } from "@aztec/core/interfaces/IStaking.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; @@ -53,7 +53,7 @@ library StakingLib { store.stakingAsset.transfer(recipient, amount); - emit IStakingInner.WithdrawFinalised(_attester, recipient, amount); + emit IStakingCore.WithdrawFinalised(_attester, recipient, amount); } function slash(address _attester, uint256 _amount) external { @@ -81,7 +81,7 @@ library StakingLib { validator.status = Status.LIVING; } - emit IStakingInner.Slashed(_attester, _amount); + emit IStakingCore.Slashed(_attester, _amount); } function deposit(address _attester, address _proposer, address _withdrawer, uint256 _amount) @@ -110,7 +110,7 @@ library StakingLib { status: Status.VALIDATING }); - emit IStakingInner.Deposit(_attester, _proposer, _withdrawer, _amount); + emit IStakingCore.Deposit(_attester, _proposer, _withdrawer, _amount); } function initiateWithdraw(address _attester, address _recipient) external returns (bool) { @@ -135,7 +135,7 @@ library StakingLib { Exit({exitableAt: Timestamp.wrap(block.timestamp) + store.exitDelay, recipient: _recipient}); validator.status = Status.EXITING; - emit IStakingInner.WithdrawInitiated(_attester, _recipient, validator.stake); + emit IStakingCore.WithdrawInitiated(_attester, _recipient, validator.stake); return true; } diff --git a/l1-contracts/src/periphery/SlashPayload.sol b/l1-contracts/src/periphery/SlashPayload.sol index 2ab08be0494..e9c429dc5e8 100644 --- a/l1-contracts/src/periphery/SlashPayload.sol +++ b/l1-contracts/src/periphery/SlashPayload.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {IStakingInner} from "@aztec/core/interfaces/IStaking.sol"; +import {IStakingCore} from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; @@ -28,7 +28,7 @@ contract SlashPayload is IPayload { for (uint256 i = 0; i < attesters.length; i++) { actions[i] = IPayload.Action({ target: address(VALIDATOR_SELECTION), - data: abi.encodeWithSelector(IStakingInner.slash.selector, attesters[i], AMOUNT) + data: abi.encodeWithSelector(IStakingCore.slash.selector, attesters[i], AMOUNT) }); } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 1d113ce9d5d..3439bd957cf 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -18,9 +18,7 @@ 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} from "./harnesses/Rollup.sol"; -import { - IRollupInner, BlockLog, SubmitEpochRootProofArgs -} from "@aztec/core/interfaces/IRollup.sol"; +import {IRollupCore, BlockLog, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; @@ -264,7 +262,7 @@ contract RollupTest is DecoderBase { _testBlock("mixed_block_1", false, 1); vm.expectEmit(true, true, true, true); - emit IRollupInner.ProofRightClaimed( + emit IRollupCore.ProofRightClaimed( quote.epochToProve, quote.prover, address(this), quote.bondAmount, Slot.wrap(1) ); rollup.claimEpochProofRight(signedQuote); @@ -446,7 +444,7 @@ contract RollupTest is DecoderBase { signedQuote = _quoteToSignedQuote(quote); vm.expectEmit(true, true, true, true); - emit IRollupInner.ProofRightClaimed( + emit IRollupCore.ProofRightClaimed( quote.epochToProve, quote.prover, address(this), quote.bondAmount, Epoch.wrap(3).toSlots() ); rollup.claimEpochProofRight(signedQuote); diff --git a/l1-contracts/test/staking/deposit.t.sol b/l1-contracts/test/staking/deposit.t.sol index b789b4a180e..0c743693492 100644 --- a/l1-contracts/test/staking/deposit.t.sol +++ b/l1-contracts/test/staking/deposit.t.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; -import {IStakingInner, Status, ValidatorInfo} from "@aztec/core/interfaces/IStaking.sol"; +import {IStakingCore, Status, ValidatorInfo} from "@aztec/core/interfaces/IStaking.sol"; contract DepositTest is StakingBase { uint256 internal depositAmount; @@ -147,7 +147,7 @@ contract DepositTest is StakingBase { assertEq(stakingAsset.balanceOf(address(staking)), 0); vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.Deposit(ATTESTER, PROPOSER, WITHDRAWER, depositAmount); + emit IStakingCore.Deposit(ATTESTER, PROPOSER, WITHDRAWER, depositAmount); staking.deposit({ _attester: ATTESTER, diff --git a/l1-contracts/test/staking/finaliseWithdraw.t.sol b/l1-contracts/test/staking/finaliseWithdraw.t.sol index ac824186331..4dc11259cae 100644 --- a/l1-contracts/test/staking/finaliseWithdraw.t.sol +++ b/l1-contracts/test/staking/finaliseWithdraw.t.sol @@ -4,11 +4,7 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import { - Timestamp, - Status, - ValidatorInfo, - Exit, - IStakingInner + Timestamp, Status, ValidatorInfo, Exit, IStakingCore } from "@aztec/core/interfaces/IStaking.sol"; contract FinaliseWithdrawTest is StakingBase { @@ -73,7 +69,7 @@ contract FinaliseWithdrawTest is StakingBase { vm.warp(Timestamp.unwrap(exit.exitableAt)); vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.WithdrawFinalised(ATTESTER, RECIPIENT, MINIMUM_STAKE); + emit IStakingCore.WithdrawFinalised(ATTESTER, RECIPIENT, MINIMUM_STAKE); staking.finaliseWithdraw(ATTESTER); exit = staking.getExit(ATTESTER); diff --git a/l1-contracts/test/staking/initiateWithdraw.t.sol b/l1-contracts/test/staking/initiateWithdraw.t.sol index 9d7deaa99fa..cb93ad8ed3f 100644 --- a/l1-contracts/test/staking/initiateWithdraw.t.sol +++ b/l1-contracts/test/staking/initiateWithdraw.t.sol @@ -4,11 +4,7 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import { - Timestamp, - Status, - ValidatorInfo, - Exit, - IStakingInner + Timestamp, Status, ValidatorInfo, Exit, IStakingCore } from "@aztec/core/interfaces/IStaking.sol"; contract InitiateWithdrawTest is StakingBase { @@ -109,7 +105,7 @@ contract InitiateWithdrawTest is StakingBase { assertEq(staking.getActiveAttesterCount(), 1); vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); + emit IStakingCore.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, RECIPIENT); @@ -142,7 +138,7 @@ contract InitiateWithdrawTest is StakingBase { assertEq(staking.getActiveAttesterCount(), 0); vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); + emit IStakingCore.WithdrawInitiated(ATTESTER, RECIPIENT, MINIMUM_STAKE); vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, RECIPIENT); diff --git a/l1-contracts/test/staking/slash.t.sol b/l1-contracts/test/staking/slash.t.sol index c96034a2fa3..1d3279ac7a8 100644 --- a/l1-contracts/test/staking/slash.t.sol +++ b/l1-contracts/test/staking/slash.t.sol @@ -4,11 +4,7 @@ pragma solidity >=0.8.27; import {StakingBase} from "./base.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import { - IStakingInner, - Status, - ValidatorInfo, - Exit, - Timestamp + IStakingCore, Status, ValidatorInfo, Exit, Timestamp } from "@aztec/core/interfaces/IStaking.sol"; contract SlashTest is StakingBase { @@ -87,7 +83,7 @@ contract SlashTest is StakingBase { assertTrue(info.status == Status.EXITING); vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.Slashed(ATTESTER, 1); + emit IStakingCore.Slashed(ATTESTER, 1); vm.prank(SLASHER); staking.slash(ATTESTER, 1); @@ -117,7 +113,7 @@ contract SlashTest is StakingBase { uint256 balance = info.stake; vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.Slashed(ATTESTER, 1); + emit IStakingCore.Slashed(ATTESTER, 1); vm.prank(SLASHER); staking.slash(ATTESTER, 1); @@ -167,7 +163,7 @@ contract SlashTest is StakingBase { uint256 balance = info.stake; vm.expectEmit(true, true, true, true, address(staking)); - emit IStakingInner.Slashed(ATTESTER, slashingAmount); + emit IStakingCore.Slashed(ATTESTER, slashingAmount); vm.prank(SLASHER); staking.slash(ATTESTER, slashingAmount);