diff --git a/l1-contracts/.solhint.json b/l1-contracts/.solhint.json index f3b1b7f84df..8c347972f9c 100644 --- a/l1-contracts/.solhint.json +++ b/l1-contracts/.solhint.json @@ -27,7 +27,7 @@ "no-unused-vars": "error", "state-visibility": "error", "var-name-mixedcase": "error", - "private-func-leading-underscore": "error", + "private-func-leading-underscore": "warn", "private-vars-no-leading-underscore": "error", "func-param-name-leading-underscore": "error", "func-param-name-mixedcase": "error", diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 6602a0085e6..01899cd3003 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -2,16 +2,14 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {ILeonidas} from "@aztec/core/interfaces/ILeonidas.sol"; -import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {ILeonidas, EpochData, LeonidasStorage} from "@aztec/core/interfaces/ILeonidas.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 {LeonidasLib} from "@aztec/core/libraries/LeonidasLib/LeonidasLib.sol"; import { Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeFns } from "@aztec/core/libraries/TimeMath.sol"; import {Ownable} from "@oz/access/Ownable.sol"; -import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; /** @@ -30,24 +28,11 @@ import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; */ contract Leonidas is Ownable, TimeFns, ILeonidas { using EnumerableSet for EnumerableSet.AddressSet; - using SignatureLib for SignatureLib.Signature; - using MessageHashUtils for bytes32; + using LeonidasLib for LeonidasStorage; using SlotLib for Slot; using EpochLib for Epoch; - /** - * @notice The data structure for an epoch - * @param committee - The validator set for the epoch - * @param sampleSeed - The seed used to sample the validator set of the epoch - * @param nextSeed - The seed used to influence the NEXT epoch - */ - struct EpochData { - address[] committee; - uint256 sampleSeed; - uint256 nextSeed; - } - // The target number of validators in a committee // @todo #8021 uint256 public immutable TARGET_COMMITTEE_SIZE; @@ -55,14 +40,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { // The time that the contract was deployed Timestamp public immutable GENESIS_TIME; - // An enumerable set of validators that are up to date - EnumerableSet.AddressSet private validatorSet; - - // A mapping to snapshots of the validator set - mapping(Epoch => EpochData) public epochs; - - // The last stored randao value, same value as `seed` in the last inserted epoch - uint256 private lastSeed; + LeonidasStorage private store; constructor( address _ares, @@ -103,7 +81,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { */ function removeValidator(address _validator) external override(ILeonidas) onlyOwner { setupEpoch(); - validatorSet.remove(_validator); + store.validatorSet.remove(_validator); } /** @@ -121,7 +99,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { override(ILeonidas) returns (address[] memory) { - return epochs[_epoch].committee; + return store.epochs[_epoch].committee; } /** @@ -129,7 +107,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @return The validator set for the current epoch */ function getCurrentEpochCommittee() external view override(ILeonidas) returns (address[] memory) { - return _getCommitteeAt(Timestamp.wrap(block.timestamp)); + return store.getCommitteeAt(getCurrentEpoch(), TARGET_COMMITTEE_SIZE); } /** @@ -140,7 +118,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @return The validator set */ function getValidators() external view override(ILeonidas) returns (address[] memory) { - return validatorSet.values(); + return store.validatorSet.values(); } /** @@ -155,13 +133,13 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { */ function setupEpoch() public override(ILeonidas) { Epoch epochNumber = getCurrentEpoch(); - EpochData storage epoch = epochs[epochNumber]; + EpochData storage epoch = store.epochs[epochNumber]; if (epoch.sampleSeed == 0) { - epoch.sampleSeed = _getSampleSeed(epochNumber); - epoch.nextSeed = lastSeed = _computeNextSeed(epochNumber); + epoch.sampleSeed = store.getSampleSeed(epochNumber); + epoch.nextSeed = store.lastSeed = _computeNextSeed(epochNumber); - epoch.committee = _sampleValidators(epoch.sampleSeed); + epoch.committee = store.sampleValidators(epoch.sampleSeed, TARGET_COMMITTEE_SIZE); } } @@ -171,7 +149,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @return The number of validators in the validator set */ function getValidatorCount() public view override(ILeonidas) returns (uint256) { - return validatorSet.length(); + return store.validatorSet.length(); } /** @@ -180,7 +158,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @return The number of validators in the validator set */ function getValidatorAt(uint256 _index) public view override(ILeonidas) returns (address) { - return validatorSet.at(_index); + return store.validatorSet.at(_index); } /** @@ -191,7 +169,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @return True if the address is in the validator set, false otherwise */ function isValidator(address _validator) public view override(ILeonidas) returns (bool) { - return validatorSet.contains(_validator); + return store.validatorSet.contains(_validator); } /** @@ -261,31 +239,9 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @return The address of the proposer */ function getProposerAt(Timestamp _ts) public view override(ILeonidas) returns (address) { - Epoch epochNumber = getEpochAt(_ts); Slot slot = getSlotAt(_ts); - - EpochData storage epoch = epochs[epochNumber]; - - // If the epoch is setup, we can just return the proposer. Otherwise we have to emulate sampling - if (epoch.sampleSeed != 0) { - uint256 committeeSize = epoch.committee.length; - if (committeeSize == 0) { - return address(0); - } - - return - epoch.committee[_computeProposerIndex(epochNumber, slot, epoch.sampleSeed, committeeSize)]; - } - - // Allow anyone if there is no validator set - if (validatorSet.length() == 0) { - return address(0); - } - - // Emulate a sampling of the validators - uint256 sampleSeed = _getSampleSeed(epochNumber); - address[] memory committee = _sampleValidators(sampleSeed); - return committee[_computeProposerIndex(epochNumber, slot, sampleSeed, committee.length)]; + Epoch epochNumber = getEpochAtSlot(slot); + return store.getProposerAt(slot, epochNumber, TARGET_COMMITTEE_SIZE); } /** @@ -326,29 +282,7 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { * @param _validator - The validator to add */ function _addValidator(address _validator) internal { - validatorSet.add(_validator); - } - - function _getCommitteeAt(Timestamp _ts) internal view returns (address[] memory) { - Epoch epochNumber = getEpochAt(_ts); - EpochData storage epoch = epochs[epochNumber]; - - if (epoch.sampleSeed != 0) { - uint256 committeeSize = epoch.committee.length; - if (committeeSize == 0) { - return new address[](0); - } - return epoch.committee; - } - - // Allow anyone if there is no validator set - if (validatorSet.length() == 0) { - return new address[](0); - } - - // Emulate a sampling of the validators - uint256 sampleSeed = _getSampleSeed(epochNumber); - return _sampleValidators(sampleSeed); + store.validatorSet.add(_validator); } /** @@ -369,57 +303,12 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { */ function _validateLeonidas( Slot _slot, - SignatureLib.Signature[] memory _signatures, + Signature[] memory _signatures, bytes32 _digest, DataStructures.ExecutionFlags memory _flags ) internal view { - Timestamp ts = getTimestampForSlot(_slot); - address proposer = getProposerAt(ts); - - // @todo Consider getting rid of this option. - // If the proposer is open, we allow anyone to propose without needing any signatures - if (proposer == address(0)) { - return; - } - - // @todo We should allow to provide a signature instead of needing the proposer to broadcast. - require(proposer == msg.sender, Errors.Leonidas__InvalidProposer(proposer, msg.sender)); - - // @note This is NOT the efficient way to do it, but it is a very convenient way for us to do it - // that allows us to reduce the number of code paths. Also when changed with optimistic for - // pleistarchus, this will be changed, so we can live with it. - - if (_flags.ignoreSignatures) { - return; - } - - address[] memory committee = _getCommitteeAt(ts); - - uint256 needed = committee.length * 2 / 3 + 1; - require( - _signatures.length >= needed, - Errors.Leonidas__InsufficientAttestationsProvided(needed, _signatures.length) - ); - - // Validate the attestations - uint256 validAttestations = 0; - - bytes32 digest = _digest.toEthSignedMessageHash(); - for (uint256 i = 0; i < _signatures.length; i++) { - SignatureLib.Signature memory signature = _signatures[i]; - if (signature.isEmpty) { - continue; - } - - // The verification will throw if invalid - signature.verify(committee[i], digest); - validAttestations++; - } - - require( - validAttestations >= needed, - Errors.Leonidas__InsufficientAttestations(needed, validAttestations) - ); + Epoch epochNumber = getEpochAtSlot(_slot); + store.validateLeonidas(_slot, epochNumber, _signatures, _digest, _flags, TARGET_COMMITTEE_SIZE); } /** @@ -435,82 +324,4 @@ contract Leonidas is Ownable, TimeFns, ILeonidas { function _computeNextSeed(Epoch _epoch) private view returns (uint256) { return uint256(keccak256(abi.encode(_epoch, block.prevrandao))); } - - /** - * @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`. - * - * @return The validators for the given epoch - */ - function _sampleValidators(uint256 _seed) private view returns (address[] memory) { - uint256 validatorSetSize = validatorSet.length(); - if (validatorSetSize == 0) { - return new address[](0); - } - - // If we have less validators than the target committee size, we just return the full set - if (validatorSetSize <= TARGET_COMMITTEE_SIZE) { - return validatorSet.values(); - } - - uint256[] memory indicies = - SampleLib.computeCommitteeClever(TARGET_COMMITTEE_SIZE, validatorSetSize, _seed); - - address[] memory committee = new address[](TARGET_COMMITTEE_SIZE); - for (uint256 i = 0; i < TARGET_COMMITTEE_SIZE; i++) { - committee[i] = validatorSet.at(indicies[i]); - } - return committee; - } - - /** - * @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) private view returns (uint256) { - if (Epoch.unwrap(_epoch) == 0) { - return type(uint256).max; - } - uint256 sampleSeed = epochs[_epoch].sampleSeed; - if (sampleSeed != 0) { - return sampleSeed; - } - - sampleSeed = epochs[_epoch - Epoch.wrap(1)].nextSeed; - if (sampleSeed != 0) { - return sampleSeed; - } - - return lastSeed; - } - - /** - * @notice Computes the index of the committee member that acts as proposer for a given slot - * - * @param _epoch - The epoch to compute the proposer index for - * @param _slot - The slot to compute the proposer index for - * @param _seed - The seed to use for the computation - * @param _size - The size of the committee - * - * @return The index of the proposer - */ - function _computeProposerIndex(Epoch _epoch, Slot _slot, uint256 _seed, uint256 _size) - private - pure - returns (uint256) - { - return uint256(keccak256(abi.encode(_epoch, _slot, _seed))) % _size; - } } diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index ef98e18d6cc..f706f40d523 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -10,6 +10,9 @@ import { FeeHeader, ManaBaseFeeComponents, BlockLog, + ChainTips, + RollupStore, + L1GasOracleValues, L1FeeData, SubmitEpochRootProofArgs } from "@aztec/core/interfaces/IRollup.sol"; @@ -19,32 +22,28 @@ import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {Leonidas} from "@aztec/core/Leonidas.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {MerkleLib} from "@aztec/core/libraries/crypto/MerkleLib.sol"; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; -import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; -import {FeeMath} from "@aztec/core/libraries/FeeMath.sol"; -import {HeaderLib} from "@aztec/core/libraries/HeaderLib.sol"; -import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/ProposeLib.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 {Timestamp, Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; -import {TxsDecoder} from "@aztec/core/libraries/TxsDecoder.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 {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import {MockVerifier} from "@aztec/mock/MockVerifier.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; -import {Math} from "@oz/utils/math/Math.sol"; -import {SafeCast} from "@oz/utils/math/SafeCast.sol"; import {Vm} from "forge-std/Vm.sol"; -struct ChainTips { - uint256 pendingBlockNumber; - uint256 provenBlockNumber; -} - struct Config { uint256 aztecSlotDuration; uint256 aztecEpochDuration; @@ -52,15 +51,6 @@ struct Config { uint256 aztecEpochProofClaimWindowInL2Slots; } -struct SubmitEpochRootProofInterimValues { - uint256 previousBlockNumber; - uint256 endBlockNumber; - Epoch epochToProve; - Epoch startEpoch; - bool isFeeCanonical; - bool isRewardDistributorCanonical; -} - /** * @title Rollup * @author Aztec Labs @@ -68,22 +58,11 @@ struct SubmitEpochRootProofInterimValues { * not giving a damn about gas costs. */ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { - using SafeCast for uint256; using SlotLib for Slot; using EpochLib for Epoch; - using SafeERC20 for IERC20; using ProposeLib for ProposeArgs; - using FeeMath for uint256; - using FeeMath for ManaBaseFeeComponents; - - struct L1GasOracleValues { - L1FeeData pre; - L1FeeData post; - Slot slotOfChange; - } - - uint256 internal constant BLOB_GAS_PER_BLOB = 2 ** 17; - uint256 internal constant GAS_PER_BLOB_POINT_EVALUATION = 50_000; + using IntRollupLib for uint256; + using IntRollupLib for ManaBaseFeeComponents; Slot public constant LIFETIME = Slot.wrap(5); Slot public constant LAG = Slot.wrap(2); @@ -109,27 +88,12 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { IRewardDistributor public immutable REWARD_DISTRIBUTOR; IERC20 public immutable ASSET; - IVerifier public epochProofVerifier; - - ChainTips public tips; - DataStructures.EpochProofClaim public proofClaim; - - // @todo Validate assumption: - // Currently we assume that the archive root following a block is specific to the block - // e.g., changing any values in the block or header should in the end make its way to the archive - // - // More direct approach would be storing keccak256(header) as well - mapping(uint256 blockNumber => BlockLog log) internal blocks; - - bytes32 public vkTreeRoot; - bytes32 public protocolContractTreeRoot; + 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; - L1GasOracleValues public l1GasOracleValues; - constructor( IFeeJuicePortal _fpcJuicePortal, IRewardDistributor _rewardDistributor, @@ -146,7 +110,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { _config.targetCommitteeSize ) { - epochProofVerifier = new MockVerifier(); + rollupStore.epochProofVerifier = new MockVerifier(); FEE_JUICE_PORTAL = _fpcJuicePortal; REWARD_DISTRIBUTOR = _rewardDistributor; ASSET = _fpcJuicePortal.UNDERLYING(); @@ -155,8 +119,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { ); INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); OUTBOX = IOutbox(address(new Outbox(address(this)))); - vkTreeRoot = _vkTreeRoot; - protocolContractTreeRoot = _protocolContractTreeRoot; + rollupStore.vkTreeRoot = _vkTreeRoot; + rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; VERSION = 1; L1_BLOCK_AT_GENESIS = block.number; CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots; @@ -164,7 +128,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; // Genesis block - blocks[0] = BlockLog({ + rollupStore.blocks[0] = BlockLog({ feeHeader: FeeHeader({ excessMana: 0, feeAssetPriceNumerator: 0, @@ -176,7 +140,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { blockHash: bytes32(0), // TODO(palla/prover): The first block does not have hash zero slotNumber: Slot.wrap(0) }); - l1GasOracleValues = L1GasOracleValues({ + rollupStore.l1GasOracleValues = L1GasOracleValues({ pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), post: L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}), slotOfChange: LIFETIME @@ -218,7 +182,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @param _verifier - The new verifier contract */ function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner { - epochProofVerifier = IVerifier(_verifier); + rollupStore.epochProofVerifier = IVerifier(_verifier); } /** @@ -229,7 +193,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @param _vkTreeRoot - The new vkTreeRoot to be used by proofs */ function setVkTreeRoot(bytes32 _vkTreeRoot) external override(ITestRollup) onlyOwner { - vkTreeRoot = _vkTreeRoot; + rollupStore.vkTreeRoot = _vkTreeRoot; } /** @@ -244,7 +208,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { override(ITestRollup) onlyOwner { - protocolContractTreeRoot = _protocolContractTreeRoot; + rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; } /** @@ -257,9 +221,9 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { */ function proposeAndClaim( ProposeArgs calldata _args, - SignatureLib.Signature[] memory _signatures, + Signature[] memory _signatures, bytes calldata _body, - EpochProofQuoteLib.SignedEpochProofQuote calldata _quote + SignedEpochProofQuote calldata _quote ) external override(IRollup) { propose(_args, _signatures, _body); claimEpochProofRight(_quote); @@ -291,99 +255,43 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { _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 = tips.provenBlockNumber; + interimValues.previousBlockNumber = rollupStore.tips.provenBlockNumber; interimValues.endBlockNumber = interimValues.previousBlockNumber + _args.epochSize; - // @note The getEpochForBlock is expected to revert if the block is beyond pending. + // @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.epochToProve = getEpochForBlock(interimValues.endBlockNumber); interimValues.startEpoch = getEpochForBlock(interimValues.previousBlockNumber + 1); + interimValues.epochToProve = getEpochForBlock(interimValues.endBlockNumber); - // Ensure that the proof is not across epochs - require( - interimValues.startEpoch == interimValues.epochToProve, - Errors.Rollup__InvalidEpoch(interimValues.startEpoch, interimValues.epochToProve) + uint256 endBlockNumber = ExtRollupLib.submitEpochRootProof( + rollupStore, + _args, + interimValues, + PROOF_COMMITMENT_ESCROW, + FEE_JUICE_PORTAL, + REWARD_DISTRIBUTOR, + ASSET, + CUAUHXICALLI ); + emit L2ProofVerified(endBlockNumber, _args.args[6]); + } - bytes32[] memory publicInputs = - getEpochProofPublicInputs(_args.epochSize, _args.args, _args.fees, _args.aggregationObject); - - require(epochProofVerifier.verify(_args.proof, publicInputs), Errors.Rollup__InvalidProof()); - - if (proofClaim.epochToProve == interimValues.epochToProve) { - PROOF_COMMITMENT_ESCROW.unstakeBond(proofClaim.bondProvider, proofClaim.bondAmount); - } - - tips.provenBlockNumber = interimValues.endBlockNumber; - - // @note Only if the rollup is the canonical will it be able to meaningfully claim fees - // Otherwise, the fees are unbacked #7938. - interimValues.isFeeCanonical = address(this) == FEE_JUICE_PORTAL.canonicalRollup(); - interimValues.isRewardDistributorCanonical = - address(this) == REWARD_DISTRIBUTOR.canonicalRollup(); - - uint256 totalProverReward = 0; - uint256 totalBurn = 0; - - if (interimValues.isFeeCanonical || interimValues.isRewardDistributorCanonical) { - for (uint256 i = 0; i < _args.epochSize; i++) { - address coinbase = address(uint160(uint256(publicInputs[9 + i * 2]))); - uint256 reward = 0; - uint256 toProver = 0; - uint256 burn = 0; - - if (interimValues.isFeeCanonical) { - uint256 fees = uint256(publicInputs[10 + i * 2]); - if (fees > 0) { - // This is insanely expensive, and will be fixed as part of the general storage cost reduction. - // See #9826. - FeeHeader storage feeHeader = - blocks[interimValues.previousBlockNumber + 1 + i].feeHeader; - burn += feeHeader.congestionCost * feeHeader.manaUsed; - - reward += (fees - burn); - FEE_JUICE_PORTAL.distributeFees(address(this), fees); - } - } - - if (interimValues.isRewardDistributorCanonical) { - reward += REWARD_DISTRIBUTOR.claim(address(this)); - } - - if (coinbase == address(0)) { - toProver = reward; - } else { - // @note We are getting value from the `proofClaim`, which are not cleared. - // So if someone is posting the proof before a new claim is made, - // the reward will calculated based on the previous values. - toProver = Math.mulDiv(reward, proofClaim.basisPointFee, 10_000); - } - - uint256 toCoinbase = reward - toProver; - if (toCoinbase > 0) { - ASSET.safeTransfer(coinbase, toCoinbase); - } - - totalProverReward += toProver; - totalBurn += burn; - } - - if (totalProverReward > 0) { - // If there is a bond-provider give him the reward, otherwise give it to the submitter. - address proofRewardRecipient = - proofClaim.bondProvider == address(0) ? msg.sender : proofClaim.bondProvider; - ASSET.safeTransfer(proofRewardRecipient, totalProverReward); - } - - if (totalBurn > 0) { - ASSET.safeTransfer(CUAUHXICALLI, totalBurn); - } - } + function getProofClaim() + external + view + override(IRollup) + returns (DataStructures.EpochProofClaim memory) + { + return rollupStore.proofClaim; + } - emit L2ProofVerified(interimValues.endBlockNumber, _args.args[6]); + function getTips() external view override(IRollup) returns (ChainTips memory) { + return rollupStore.tips; } function status(uint256 _myHeaderBlockNumber) @@ -400,12 +308,35 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { ) { return ( - tips.provenBlockNumber, - blocks[tips.provenBlockNumber].archive, - tips.pendingBlockNumber, - blocks[tips.pendingBlockNumber].archive, + rollupStore.tips.provenBlockNumber, + rollupStore.blocks[rollupStore.tips.provenBlockNumber].archive, + rollupStore.tips.pendingBlockNumber, + rollupStore.blocks[rollupStore.tips.pendingBlockNumber].archive, archiveAt(_myHeaderBlockNumber), - getEpochForBlock(tips.provenBlockNumber) + getEpochForBlock(rollupStore.tips.provenBlockNumber) + ); + } + + /** + * @notice Returns the computed public inputs for the given epoch proof. + * + * @dev Useful for debugging and testing. Allows submitter to compare their + * own public inputs used for generating the proof vs the ones assembled + * by this contract when verifying it. + * + * @param _epochSize - The size of the epoch (to be promoted to a constant) + * @param _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) + * @param _fees - Array of recipient-value pairs with fees to be distributed for the epoch + * @param _aggregationObject - The aggregation object for the proof + */ + function getEpochProofPublicInputs( + uint256 _epochSize, + bytes32[7] calldata _args, + bytes32[] calldata _fees, + bytes calldata _aggregationObject + ) external view override(IRollup) returns (bytes32[] memory) { + return ExtRollupLib.getEpochProofPublicInputs( + rollupStore, _epochSize, _args, _fees, _aggregationObject ); } @@ -428,17 +359,17 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { // Consider if a prune will hit in this slot uint256 pendingBlockNumber = - canPruneAtTime(_ts) ? tips.provenBlockNumber : tips.pendingBlockNumber; + canPruneAtTime(_ts) ? rollupStore.tips.provenBlockNumber : rollupStore.tips.pendingBlockNumber; - Slot lastSlot = blocks[pendingBlockNumber].slotNumber; + 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 = blocks[pendingBlockNumber].archive; + bytes32 tipArchive = rollupStore.blocks[pendingBlockNumber].archive; require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive)); - SignatureLib.Signature[] memory sigs = new SignatureLib.Signature[](0); + Signature[] memory sigs = new Signature[](0); DataStructures.ExecutionFlags memory flags = DataStructures.ExecutionFlags({ignoreDA: true, ignoreSignatures: true}); _validateLeonidas(slot, sigs, _archive, flags); @@ -460,14 +391,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { */ function validateHeader( bytes calldata _header, - SignatureLib.Signature[] memory _signatures, + Signature[] memory _signatures, bytes32 _digest, Timestamp _currentTime, bytes32 _txsEffectsHash, DataStructures.ExecutionFlags memory _flags ) external view override(IRollup) { uint256 manaBaseFee = getManaBaseFeeAt(_currentTime, true); - HeaderLib.Header memory header = HeaderLib.decode(_header); + Header memory header = ExtRollupLib.decodeHeader(_header); _validateHeader( header, _signatures, _digest, _currentTime, manaBaseFee, _txsEffectsHash, _flags ); @@ -481,12 +412,12 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { Epoch epochToProve = getEpochToProve(); require( // If the epoch has been claimed, it cannot be claimed again - proofClaim.epochToProve != epochToProve + 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. - || proofClaim.bondProvider == address(0), + || rollupStore.proofClaim.bondProvider == address(0), Errors.Rollup__ProofRightAlreadyClaimed() ); return epochToProve; @@ -498,13 +429,10 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { override(IRollup) returns (bytes32) { - return TxsDecoder.decode(_body); + return ExtRollupLib.computeTxsEffectsHash(_body); } - function claimEpochProofRight(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) - public - override(IRollup) - { + function claimEpochProofRight(SignedEpochProofQuote calldata _quote) public override(IRollup) { validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), _quote); Slot currentSlot = getCurrentSlot(); @@ -515,7 +443,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { // Blocked on submitting epoch proofs to this contract. PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.prover, _quote.quote.bondAmount); - proofClaim = DataStructures.EpochProofClaim({ + rollupStore.proofClaim = DataStructures.EpochProofClaim({ epochToProve: epochToProve, basisPointFee: _quote.quote.basisPointFee, bondAmount: _quote.quote.bondAmount, @@ -536,11 +464,10 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @param _signatures - Signatures from the validators * @param _body - The body of the L2 block */ - function propose( - ProposeArgs calldata _args, - SignatureLib.Signature[] memory _signatures, - bytes calldata _body - ) public override(IRollup) { + function propose(ProposeArgs calldata _args, Signature[] memory _signatures, bytes calldata _body) + public + override(IRollup) + { if (canPrune()) { _prune(); } @@ -549,15 +476,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { // The `body` is passed outside the "args" as it does not directly need to be in the digest // as long as the `txsEffectsHash` is included and matches what is in the header. // Which we are checking in the `_validateHeader` call below. - bytes32 txsEffectsHash = TxsDecoder.decode(_body); + bytes32 txsEffectsHash = ExtRollupLib.computeTxsEffectsHash(_body); // Decode and validate header - HeaderLib.Header memory header = HeaderLib.decode(_args.header); + Header memory header = ExtRollupLib.decodeHeader(_args.header); setupEpoch(); ManaBaseFeeComponents memory components = getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true); - uint256 manaBaseFee = FeeMath.summedBaseFee(components); + uint256 manaBaseFee = components.summedBaseFee(); _validateHeader({ _header: header, _signatures: _signatures, @@ -568,15 +495,13 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) }); - uint256 blockNumber = ++tips.pendingBlockNumber; + uint256 blockNumber = ++rollupStore.tips.pendingBlockNumber; { - FeeHeader memory parentFeeHeader = blocks[blockNumber - 1].feeHeader; - uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd( - -int256(FeeMath.MANA_TARGET) - ); + FeeHeader memory parentFeeHeader = rollupStore.blocks[blockNumber - 1].feeHeader; + uint256 excessMana = IntRollupLib.computeExcessMana(parentFeeHeader); - blocks[blockNumber] = BlockLog({ + rollupStore.blocks[blockNumber] = BlockLog({ archive: _args.archive, blockHash: _args.blockHash, slotNumber: Slot.wrap(header.globalVariables.slotNumber), @@ -639,15 +564,16 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { function updateL1GasFeeOracle() public override(IRollup) { Slot slot = getCurrentSlot(); // The slot where we find a new queued value acceptable - Slot acceptableSlot = l1GasOracleValues.slotOfChange + (LIFETIME - LAG); + Slot acceptableSlot = rollupStore.l1GasOracleValues.slotOfChange + (LIFETIME - LAG); if (slot < acceptableSlot) { return; } - l1GasOracleValues.pre = l1GasOracleValues.post; - l1GasOracleValues.post = L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}); - l1GasOracleValues.slotOfChange = slot + LAG; + rollupStore.l1GasOracleValues.pre = rollupStore.l1GasOracleValues.post; + rollupStore.l1GasOracleValues.post = + L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}); + rollupStore.l1GasOracleValues.slotOfChange = slot + LAG; } /** @@ -656,8 +582,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @return The fee asset price */ function getFeeAssetPrice() public view override(IRollup) returns (uint256) { - return FeeMath.feeAssetPriceModifier( - blocks[tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator + return IntRollupLib.feeAssetPriceModifier( + rollupStore.blocks[rollupStore.tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator ); } @@ -668,10 +594,10 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { returns (L1FeeData memory) { Slot slot = getSlotAt(_timestamp); - if (slot < l1GasOracleValues.slotOfChange) { - return l1GasOracleValues.pre; + if (slot < rollupStore.l1GasOracleValues.slotOfChange) { + return rollupStore.l1GasOracleValues.pre; } - return l1GasOracleValues.post; + return rollupStore.l1GasOracleValues.post; } /** @@ -709,244 +635,49 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { returns (ManaBaseFeeComponents memory) { // If we can prune, we use the proven block, otherwise the pending block - uint256 blockOfInterest = - canPruneAtTime(_timestamp) ? tips.provenBlockNumber : tips.pendingBlockNumber; - - FeeHeader storage parentFeeHeader = blocks[blockOfInterest].feeHeader; - uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd( - -int256(FeeMath.MANA_TARGET) + uint256 blockOfInterest = canPruneAtTime(_timestamp) + ? rollupStore.tips.provenBlockNumber + : rollupStore.tips.pendingBlockNumber; + + return ExtRollupLib.getManaBaseFeeComponentsAt( + rollupStore.blocks[blockOfInterest].feeHeader, + getL1FeesAt(_timestamp), + _inFeeAsset ? getFeeAssetPrice() : 1e9, + EPOCH_DURATION ); - - L1FeeData memory fees = getL1FeesAt(_timestamp); - uint256 dataCost = - Math.mulDiv(3 * BLOB_GAS_PER_BLOB, fees.blobFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); - uint256 gasUsed = FeeMath.L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION - + FeeMath.L1_GAS_PER_EPOCH_VERIFIED / EPOCH_DURATION; - uint256 gasCost = Math.mulDiv(gasUsed, fees.baseFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); - uint256 provingCost = FeeMath.provingCostPerMana( - blocks[tips.pendingBlockNumber].feeHeader.provingCostPerManaNumerator - ); - - uint256 congestionMultiplier = FeeMath.congestionMultiplier(excessMana); - uint256 total = dataCost + gasCost + provingCost; - uint256 congestionCost = Math.mulDiv( - total, congestionMultiplier, FeeMath.MINIMUM_CONGESTION_MULTIPLIER, Math.Rounding.Floor - ) - total; - - uint256 feeAssetPrice = _inFeeAsset ? getFeeAssetPrice() : 1e9; - - // @todo @lherskind. The following is a crime against humanity, but it makes it - // very neat to plot etc from python, #10004 will fix it across the board - return ManaBaseFeeComponents({ - dataCost: Math.mulDiv(dataCost, feeAssetPrice, 1e9, Math.Rounding.Ceil), - gasCost: Math.mulDiv(gasCost, feeAssetPrice, 1e9, Math.Rounding.Ceil), - provingCost: Math.mulDiv(provingCost, feeAssetPrice, 1e9, Math.Rounding.Ceil), - congestionCost: Math.mulDiv(congestionCost, feeAssetPrice, 1e9, Math.Rounding.Ceil), - congestionMultiplier: congestionMultiplier - }); } - function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory _quote) + function quoteToDigest(EpochProofQuote memory _quote) public view override(IRollup) returns (bytes32) { - return _hashTypedDataV4(EpochProofQuoteLib.hash(_quote)); - } - - /** - * @notice Returns the computed public inputs for the given epoch proof. - * - * @dev Useful for debugging and testing. Allows submitter to compare their - * own public inputs used for generating the proof vs the ones assembled - * by this contract when verifying it. - * - * @param _epochSize - The size of the epoch (to be promoted to a constant) - * @param _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) - * @param _fees - Array of recipient-value pairs with fees to be distributed for the epoch - * @param _aggregationObject - The aggregation object for the proof - */ - function getEpochProofPublicInputs( - uint256 _epochSize, - bytes32[7] calldata _args, - bytes32[] calldata _fees, - bytes calldata _aggregationObject - ) public view override(IRollup) returns (bytes32[] memory) { - uint256 previousBlockNumber = tips.provenBlockNumber; - uint256 endBlockNumber = previousBlockNumber + _epochSize; - - // Args are defined as an array because Solidity complains with "stack too deep" otherwise - // 0 bytes32 _previousArchive, - // 1 bytes32 _endArchive, - // 2 bytes32 _previousBlockHash, - // 3 bytes32 _endBlockHash, - // 4 bytes32 _endTimestamp, - // 5 bytes32 _outHash, - // 6 bytes32 _proverId, - - // TODO(#7373): Public inputs are not fully verified - - { - // We do it this way to provide better error messages than passing along the storage values - bytes32 expectedPreviousArchive = blocks[previousBlockNumber].archive; - require( - expectedPreviousArchive == _args[0], - Errors.Rollup__InvalidPreviousArchive(expectedPreviousArchive, _args[0]) - ); - - bytes32 expectedEndArchive = blocks[endBlockNumber].archive; - require( - expectedEndArchive == _args[1], Errors.Rollup__InvalidArchive(expectedEndArchive, _args[1]) - ); - - bytes32 expectedPreviousBlockHash = blocks[previousBlockNumber].blockHash; - // TODO: Remove 0 check once we inject the proper genesis block hash - require( - expectedPreviousBlockHash == 0 || expectedPreviousBlockHash == _args[2], - Errors.Rollup__InvalidPreviousBlockHash(expectedPreviousBlockHash, _args[2]) - ); - - bytes32 expectedEndBlockHash = blocks[endBlockNumber].blockHash; - require( - expectedEndBlockHash == _args[3], - Errors.Rollup__InvalidBlockHash(expectedEndBlockHash, _args[3]) - ); - } - - bytes32[] memory publicInputs = new bytes32[]( - Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH - ); - - // Structure of the root rollup public inputs we need to reassemble: - // - // struct RootRollupPublicInputs { - // previous_archive: AppendOnlyTreeSnapshot, - // end_archive: AppendOnlyTreeSnapshot, - // previous_block_hash: Field, - // end_block_hash: Field, - // end_timestamp: u64, - // end_block_number: Field, - // out_hash: Field, - // fees: [FeeRecipient; Constants.AZTEC_EPOCH_DURATION], - // vk_tree_root: Field, - // protocol_contract_tree_root: Field, - // prover_id: Field - // } - - // previous_archive.root: the previous archive tree root - publicInputs[0] = _args[0]; - - // previous_archive.next_available_leaf_index: the previous archive next available index - // normally this should be equal to the block number (since leaves are 0-indexed and blocks 1-indexed) - // but in yarn-project/merkle-tree/src/new_tree.ts we prefill the tree so that block N is in leaf N - publicInputs[1] = bytes32(previousBlockNumber + 1); - - // end_archive.root: the new archive tree root - publicInputs[2] = _args[1]; - - // end_archive.next_available_leaf_index: the new archive next available index - publicInputs[3] = bytes32(endBlockNumber + 1); - - // previous_block_hash: the block hash just preceding this epoch - publicInputs[4] = _args[2]; - - // end_block_hash: the last block hash in the epoch - publicInputs[5] = _args[3]; - - // end_timestamp: the timestamp of the last block in the epoch - publicInputs[6] = _args[4]; - - // end_block_number: last block number in the epoch - publicInputs[7] = bytes32(endBlockNumber); - - // out_hash: root of this epoch's l2 to l1 message tree - publicInputs[8] = _args[5]; - - uint256 feesLength = Constants.AZTEC_MAX_EPOCH_DURATION * 2; - // fees[9 to (9+feesLength-1)]: array of recipient-value pairs - for (uint256 i = 0; i < feesLength; i++) { - publicInputs[9 + i] = _fees[i]; - } - uint256 feesEnd = 9 + feesLength; - - // vk_tree_root - publicInputs[feesEnd] = vkTreeRoot; - - // protocol_contract_tree_root - publicInputs[feesEnd + 1] = protocolContractTreeRoot; - - // prover_id: id of current epoch's prover - publicInputs[feesEnd + 2] = _args[6]; - - // the block proof is recursive, which means it comes with an aggregation object - // this snippet copies it into the public inputs needed for verification - // it also guards against empty _aggregationObject used with mocked proofs - uint256 aggregationLength = _aggregationObject.length / 32; - for (uint256 i = 0; i < Constants.AGGREGATION_OBJECT_LENGTH && i < aggregationLength; i++) { - bytes32 part; - assembly { - part := calldataload(add(_aggregationObject.offset, mul(i, 32))) - } - publicInputs[i + feesEnd + 3] = part; - } - - return publicInputs; + return _hashTypedDataV4(IntRollupLib.computeQuoteHash(_quote)); } - function validateEpochProofRightClaimAtTime( - Timestamp _ts, - EpochProofQuoteLib.SignedEpochProofQuote calldata _quote - ) public view override(IRollup) { - SignatureLib.verify(_quote.signature, _quote.quote.prover, quoteToDigest(_quote.quote)); - + function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) + public + view + override(IRollup) + { Slot currentSlot = getSlotAt(_ts); address currentProposer = getProposerAt(_ts); Epoch epochToProve = getEpochToProve(); - - require( - _quote.quote.validUntilSlot >= currentSlot, - Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot) - ); - - require( - _quote.quote.basisPointFee <= 10_000, - Errors.Rollup__InvalidBasisPointFee(_quote.quote.basisPointFee) - ); - - require( - currentProposer == address(0) || currentProposer == msg.sender, - Errors.Leonidas__InvalidProposer(currentProposer, msg.sender) - ); - - require( - _quote.quote.epochToProve == epochToProve, - Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve) - ); - - require( - positionInEpoch(currentSlot) < CLAIM_DURATION_IN_L2_SLOTS, - Errors.Rollup__NotInClaimPhase(positionInEpoch(currentSlot), CLAIM_DURATION_IN_L2_SLOTS) - ); - - // if the epoch to prove is not the one that has been claimed, - // then whatever is in the proofClaim is stale - require( - proofClaim.epochToProve != epochToProve || proofClaim.proposerClaimant == address(0), - Errors.Rollup__ProofRightAlreadyClaimed() - ); - - require( - _quote.quote.bondAmount >= PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, - Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount - ) - ); - - uint256 availableFundsInEscrow = PROOF_COMMITMENT_ESCROW.deposits(_quote.quote.prover); - require( - _quote.quote.bondAmount <= availableFundsInEscrow, - Errors.Rollup__InsufficientFundsInEscrow(_quote.quote.bondAmount, availableFundsInEscrow) + uint256 posInEpoch = 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 ); } @@ -956,31 +687,31 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @return bytes32 - The current archive root */ function archive() public view override(IRollup) returns (bytes32) { - return blocks[tips.pendingBlockNumber].archive; + return rollupStore.blocks[rollupStore.tips.pendingBlockNumber].archive; } function getProvenBlockNumber() public view override(IRollup) returns (uint256) { - return tips.provenBlockNumber; + return rollupStore.tips.provenBlockNumber; } function getPendingBlockNumber() public view override(IRollup) returns (uint256) { - return tips.pendingBlockNumber; + return rollupStore.tips.pendingBlockNumber; } function getBlock(uint256 _blockNumber) public view override(IRollup) returns (BlockLog memory) { require( - _blockNumber <= tips.pendingBlockNumber, - Errors.Rollup__InvalidBlockNumber(tips.pendingBlockNumber, _blockNumber) + _blockNumber <= rollupStore.tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) ); - return blocks[_blockNumber]; + return rollupStore.blocks[_blockNumber]; } function getEpochForBlock(uint256 _blockNumber) public view override(IRollup) returns (Epoch) { require( - _blockNumber <= tips.pendingBlockNumber, - Errors.Rollup__InvalidBlockNumber(tips.pendingBlockNumber, _blockNumber) + _blockNumber <= rollupStore.tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(rollupStore.tips.pendingBlockNumber, _blockNumber) ); - return getEpochAt(getTimestampForSlot(blocks[_blockNumber].slotNumber)); + return getEpochAt(getTimestampForSlot(rollupStore.blocks[_blockNumber].slotNumber)); } /** @@ -993,8 +724,11 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @return uint256 - The epoch to prove */ function getEpochToProve() public view override(IRollup) returns (Epoch) { - require(tips.provenBlockNumber != tips.pendingBlockNumber, Errors.Rollup__NoEpochToProve()); - return getEpochForBlock(getProvenBlockNumber() + 1); + require( + rollupStore.tips.provenBlockNumber != rollupStore.tips.pendingBlockNumber, + Errors.Rollup__NoEpochToProve() + ); + return getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); } /** @@ -1005,8 +739,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @return bytes32 - The archive root of the block */ function archiveAt(uint256 _blockNumber) public view override(IRollup) returns (bytes32) { - if (_blockNumber <= tips.pendingBlockNumber) { - return blocks[_blockNumber].archive; + if (_blockNumber <= rollupStore.tips.pendingBlockNumber) { + return rollupStore.blocks[_blockNumber].archive; } return bytes32(0); } @@ -1017,14 +751,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { function canPruneAtTime(Timestamp _ts) public view override(IRollup) returns (bool) { if ( - tips.pendingBlockNumber == tips.provenBlockNumber - || tips.pendingBlockNumber <= assumeProvenThroughBlockNumber + rollupStore.tips.pendingBlockNumber == rollupStore.tips.provenBlockNumber + || rollupStore.tips.pendingBlockNumber <= assumeProvenThroughBlockNumber ) { return false; } Slot currentSlot = getSlotAt(_ts); - Epoch oldestPendingEpoch = getEpochForBlock(tips.provenBlockNumber + 1); + Epoch oldestPendingEpoch = getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); Slot startSlotOfPendingEpoch = toSlots(oldestPendingEpoch); // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. @@ -1035,7 +769,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { < startSlotOfPendingEpoch + toSlots(Epoch.wrap(1)) + Slot.wrap(CLAIM_DURATION_IN_L2_SLOTS); bool claimExists = currentSlot < startSlotOfPendingEpoch + toSlots(Epoch.wrap(2)) - && proofClaim.epochToProve == oldestPendingEpoch && proofClaim.proposerClaimant != address(0); + && rollupStore.proofClaim.epochToProve == oldestPendingEpoch + && rollupStore.proofClaim.proposerClaimant != address(0); if (inClaimPhase || claimExists) { // If we are in the claim phase, do not prune @@ -1046,17 +781,17 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { function _prune() internal { // TODO #8656 - delete proofClaim; + delete rollupStore.proofClaim; - uint256 pending = tips.pendingBlockNumber; + 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. - tips.pendingBlockNumber = tips.provenBlockNumber; + rollupStore.tips.pendingBlockNumber = rollupStore.tips.provenBlockNumber; - emit PrunedPending(tips.provenBlockNumber, pending); + emit PrunedPending(rollupStore.tips.provenBlockNumber, pending); } /** @@ -1070,18 +805,31 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { * @param _flags - Flags specific to the execution, whether certain checks should be skipped */ function _validateHeader( - HeaderLib.Header memory _header, - SignatureLib.Signature[] memory _signatures, + Header memory _header, + Signature[] memory _signatures, bytes32 _digest, Timestamp _currentTime, uint256 _manaBaseFee, bytes32 _txEffectsHash, DataStructures.ExecutionFlags memory _flags ) internal view { - uint256 pendingBlockNumber = - canPruneAtTime(_currentTime) ? tips.provenBlockNumber : tips.pendingBlockNumber; - _validateHeaderForSubmissionBase( - _header, _currentTime, _manaBaseFee, _txEffectsHash, pendingBlockNumber, _flags + uint256 pendingBlockNumber = canPruneAtTime(_currentTime) + ? rollupStore.tips.provenBlockNumber + : rollupStore.tips.pendingBlockNumber; + + ExtRollupLib.validateHeaderForSubmissionBase( + ValidateHeaderArgs({ + header: _header, + currentTime: _currentTime, + manaBaseFee: _manaBaseFee, + txsEffectsHash: _txEffectsHash, + 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 @@ -1106,7 +854,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { */ function _validateHeaderForSubmissionSequencerSelection( Slot _slot, - SignatureLib.Signature[] memory _signatures, + Signature[] memory _signatures, bytes32 _digest, Timestamp _currentTime, DataStructures.ExecutionFlags memory _flags @@ -1127,100 +875,21 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { _validateLeonidas(_slot, _signatures, _digest, _flags); } - /** - * @notice Validate a header for submission to the pending chain (base checks) - * Base checks here being the checks that we wish to do regardless of the sequencer - * selection mechanism. - * - * Each of the following validation checks must pass, otherwise an error is thrown and we revert. - * - The chain ID MUST match the current chain ID - * - The version MUST match the current version - * - The block id MUST be the next block in the chain - * - The last archive root in the header MUST match the current archive - * - The slot MUST be larger than the slot of the previous block (ensures single block per slot) - * - The timestamp MUST be equal to GENESIS_TIME + slot * SLOT_DURATION - * - The `txsEffectsHash` of the header must match the computed `_txsEffectsHash` - * - This can be relaxed to happen at the time of `submitProof` instead - * - * @param _header - The header to validate - */ - function _validateHeaderForSubmissionBase( - HeaderLib.Header memory _header, - Timestamp _currentTime, - uint256 _manaBaseFee, - bytes32 _txsEffectsHash, - uint256 _pendingBlockNumber, - DataStructures.ExecutionFlags memory _flags - ) internal view { - require( - block.chainid == _header.globalVariables.chainId, - Errors.Rollup__InvalidChainId(block.chainid, _header.globalVariables.chainId) - ); - - require( - _header.globalVariables.version == VERSION, - Errors.Rollup__InvalidVersion(VERSION, _header.globalVariables.version) - ); - - require( - _header.globalVariables.blockNumber == _pendingBlockNumber + 1, - Errors.Rollup__InvalidBlockNumber( - _pendingBlockNumber + 1, _header.globalVariables.blockNumber - ) - ); - - bytes32 tipArchive = blocks[_pendingBlockNumber].archive; - require( - tipArchive == _header.lastArchive.root, - Errors.Rollup__InvalidArchive(tipArchive, _header.lastArchive.root) - ); - - Slot slot = Slot.wrap(_header.globalVariables.slotNumber); - Slot lastSlot = blocks[_pendingBlockNumber].slotNumber; - require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); - - Timestamp timestamp = getTimestampForSlot(slot); - require( - Timestamp.wrap(_header.globalVariables.timestamp) == timestamp, - Errors.Rollup__InvalidTimestamp(timestamp, Timestamp.wrap(_header.globalVariables.timestamp)) - ); - - // @note If you are hitting this error, it is likely because the chain you use have a blocktime that differs - // from the value that we have in the constants. - // When you are encountering this, it will likely be as the sequencer expects to be able to include - // an Aztec block in the "next" ethereum block based on a timestamp that is 12 seconds in the future - // from the last block. However, if the actual will only be 1 second in the future, you will end up - // expecting this value to be in the future. - require(timestamp <= _currentTime, Errors.Rollup__TimestampInFuture(_currentTime, timestamp)); - - // Check if the data is available - require( - _flags.ignoreDA || _header.contentCommitment.txsEffectsHash == _txsEffectsHash, - Errors.Rollup__UnavailableTxs(_header.contentCommitment.txsEffectsHash) - ); - - // If not canonical rollup, require that the fees are zero - if (address(this) != FEE_JUICE_PORTAL.canonicalRollup()) { - require(_header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); - require(_header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee()); - } else { - require(_header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); - require( - _header.globalVariables.gasFees.feePerL2Gas == _manaBaseFee, - Errors.Rollup__InvalidManaBaseFee(_manaBaseFee, _header.globalVariables.gasFees.feePerL2Gas) - ); - } - } - function _fakeBlockNumberAsProven(uint256 _blockNumber) private { - if (_blockNumber > tips.provenBlockNumber && _blockNumber <= tips.pendingBlockNumber) { - tips.provenBlockNumber = _blockNumber; + 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(proofClaim.epochToProve)) { - proofClaim = DataStructures.EpochProofClaim({ + if ( + Epoch.unwrap(epoch) == 0 + || Epoch.unwrap(epoch) > Epoch.unwrap(rollupStore.proofClaim.epochToProve) + ) { + rollupStore.proofClaim = DataStructures.EpochProofClaim({ epochToProve: epoch, basisPointFee: 0, bondAmount: 0, diff --git a/l1-contracts/src/core/interfaces/ILeonidas.sol b/l1-contracts/src/core/interfaces/ILeonidas.sol index ece101d7277..256abed990e 100644 --- a/l1-contracts/src/core/interfaces/ILeonidas.sol +++ b/l1-contracts/src/core/interfaces/ILeonidas.sol @@ -3,6 +3,27 @@ pragma solidity >=0.8.27; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; +import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; + +/** + * @notice The data structure for an epoch + * @param committee - The validator set for the epoch + * @param sampleSeed - The seed used to sample the validator set of the epoch + * @param nextSeed - The seed used to influence the NEXT epoch + */ +struct EpochData { + address[] committee; + uint256 sampleSeed; + uint256 nextSeed; +} + +struct LeonidasStorage { + EnumerableSet.AddressSet validatorSet; + // A mapping to snapshots of the validator set + mapping(Epoch => EpochData) epochs; + // The last stored randao value, same value as `seed` in the last inserted epoch + uint256 lastSeed; +} interface ILeonidas { // Changing depending on sybil mechanism and slashing enforcement diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index f52266dfe8a..387fe706175 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -2,13 +2,19 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +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 {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; -import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; -import {ManaBaseFeeComponents} from "@aztec/core/libraries/FeeMath.sol"; -import {ProposeArgs} from "@aztec/core/libraries/ProposeLib.sol"; +import { + EpochProofQuote, + SignedEpochProofQuote +} from "@aztec/core/libraries/RollupLibs/EpochProofQuoteLib.sol"; +import { + FeeHeader, L1FeeData, ManaBaseFeeComponents +} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import {ProposeArgs} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; struct SubmitEpochRootProofArgs { @@ -19,14 +25,6 @@ struct SubmitEpochRootProofArgs { bytes proof; } -struct FeeHeader { - uint256 excessMana; - uint256 feeAssetPriceNumerator; - uint256 manaUsed; - uint256 provingCostPerManaNumerator; - uint256 congestionCost; -} - struct BlockLog { FeeHeader feeHeader; bytes32 archive; @@ -34,9 +32,25 @@ struct BlockLog { Slot slotNumber; } -struct L1FeeData { - uint256 baseFee; - uint256 blobFee; +struct ChainTips { + uint256 pendingBlockNumber; + uint256 provenBlockNumber; +} + +struct L1GasOracleValues { + L1FeeData pre; + L1FeeData post; + Slot slotOfChange; +} + +struct RollupStore { + mapping(uint256 blockNumber => BlockLog log) blocks; + ChainTips tips; + bytes32 vkTreeRoot; + bytes32 protocolContractTreeRoot; + L1GasOracleValues l1GasOracleValues; + DataStructures.EpochProofClaim proofClaim; + IVerifier epochProofVerifier; } interface ITestRollup { @@ -65,19 +79,16 @@ interface IRollup { function prune() external; function updateL1GasFeeOracle() external; - function claimEpochProofRight(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) external; + function claimEpochProofRight(SignedEpochProofQuote calldata _quote) external; - function propose( - ProposeArgs calldata _args, - SignatureLib.Signature[] memory _signatures, - bytes calldata _body - ) external; + function propose(ProposeArgs calldata _args, Signature[] memory _signatures, bytes calldata _body) + external; function proposeAndClaim( ProposeArgs calldata _args, - SignatureLib.Signature[] memory _signatures, + Signature[] memory _signatures, bytes calldata _body, - EpochProofQuoteLib.SignedEpochProofQuote calldata _quote + SignedEpochProofQuote calldata _quote ) external; function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external; @@ -86,7 +97,7 @@ interface IRollup { function validateHeader( bytes calldata _header, - SignatureLib.Signature[] memory _signatures, + Signature[] memory _signatures, bytes32 _digest, Timestamp _currentTime, bytes32 _txsEffecstHash, @@ -102,6 +113,9 @@ 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 getTips() external view returns (ChainTips memory); + function status(uint256 _myHeaderBlockNumber) external view @@ -114,10 +128,7 @@ interface IRollup { Epoch provenEpochNumber ); - function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory _quote) - external - view - returns (bytes32); + 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); @@ -131,10 +142,9 @@ interface IRollup { function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (Epoch); function getClaimableEpoch() external view returns (Epoch); - function validateEpochProofRightClaimAtTime( - Timestamp _ts, - EpochProofQuoteLib.SignedEpochProofQuote calldata _quote - ) external view; + function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) + external + view; function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); function getEpochProofPublicInputs( uint256 _epochSize, diff --git a/l1-contracts/src/core/libraries/EpochProofQuoteLib.sol b/l1-contracts/src/core/libraries/EpochProofQuoteLib.sol deleted file mode 100644 index be838b7a7ad..00000000000 --- a/l1-contracts/src/core/libraries/EpochProofQuoteLib.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; - -library EpochProofQuoteLib { - /** - * @notice Struct encompassing an epoch proof quote - * @param epochToProve - The epoch number to prove - * @param validUntilSlot - The deadline of the quote, denoted in L2 slots - * @param bondAmount - The size of the bond - * @param prover - The address of the prover - * @param basisPointFee - The fee measured in basis points - */ - struct EpochProofQuote { - Epoch epochToProve; - Slot validUntilSlot; - uint256 bondAmount; - address prover; - uint32 basisPointFee; - } - - /** - * @notice A signed quote for the epoch proof - * @param quote - The Epoch Proof Quote - * @param signature - A signature on the quote - */ - struct SignedEpochProofQuote { - EpochProofQuote quote; - SignatureLib.Signature signature; - } - - bytes32 public constant EPOCH_PROOF_QUOTE_TYPEHASH = keccak256( - "EpochProofQuote(uint256 epochToProve,uint256 validUntilSlot,uint256 bondAmount,address prover,uint32 basisPointFee)" - ); - - function hash(EpochProofQuote memory _quote) internal pure returns (bytes32) { - return keccak256( - abi.encode( - EPOCH_PROOF_QUOTE_TYPEHASH, - _quote.epochToProve, - _quote.validUntilSlot, - _quote.bondAmount, - _quote.prover, - _quote.basisPointFee - ) - ); - } -} diff --git a/l1-contracts/src/core/libraries/LeonidasLib/LeonidasLib.sol b/l1-contracts/src/core/libraries/LeonidasLib/LeonidasLib.sol new file mode 100644 index 00000000000..28bc684fd84 --- /dev/null +++ b/l1-contracts/src/core/libraries/LeonidasLib/LeonidasLib.sol @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {EpochData, LeonidasStorage} from "@aztec/core/interfaces/ILeonidas.sol"; +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/TimeMath.sol"; +import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; +import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; + +library LeonidasLib { + using EnumerableSet for EnumerableSet.AddressSet; + using MessageHashUtils for bytes32; + using SignatureLib for Signature; + + /** + * @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`. + * + * @return The validators for the given epoch + */ + function sampleValidators( + LeonidasStorage storage _store, + uint256 _seed, + uint256 _targetCommitteeSize + ) external view returns (address[] memory) { + return _sampleValidators(_store, _seed, _targetCommitteeSize); + } + + function getProposerAt( + LeonidasStorage storage _store, + Slot _slot, + Epoch _epochNumber, + uint256 _targetCommitteeSize + ) external view returns (address) { + return _getProposerAt(_store, _slot, _epochNumber, _targetCommitteeSize); + } + + function getCommitteeAt( + LeonidasStorage storage _store, + Epoch _epochNumber, + uint256 _targetCommitteeSize + ) external view returns (address[] memory) { + return _getCommitteeAt(_store, _epochNumber, _targetCommitteeSize); + } + + /** + * @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 validateLeonidas( + LeonidasStorage storage _store, + Slot _slot, + Epoch _epochNumber, + Signature[] memory _signatures, + bytes32 _digest, + DataStructures.ExecutionFlags memory _flags, + uint256 _targetCommitteeSize + ) external view { + address proposer = _getProposerAt(_store, _slot, _epochNumber, _targetCommitteeSize); + + // @todo Consider getting rid of this option. + // If the proposer is open, we allow anyone to propose without needing any signatures + if (proposer == address(0)) { + return; + } + + // @todo We should allow to provide a signature instead of needing the proposer to broadcast. + require(proposer == msg.sender, Errors.Leonidas__InvalidProposer(proposer, msg.sender)); + + // @note This is NOT the efficient way to do it, but it is a very convenient way for us to do it + // that allows us to reduce the number of code paths. Also when changed with optimistic for + // pleistarchus, this will be changed, so we can live with it. + + if (_flags.ignoreSignatures) { + return; + } + + address[] memory committee = _getCommitteeAt(_store, _epochNumber, _targetCommitteeSize); + + uint256 needed = committee.length * 2 / 3 + 1; + require( + _signatures.length >= needed, + Errors.Leonidas__InsufficientAttestationsProvided(needed, _signatures.length) + ); + + // Validate the attestations + uint256 validAttestations = 0; + + bytes32 digest = _digest.toEthSignedMessageHash(); + for (uint256 i = 0; i < _signatures.length; i++) { + // To avoid stack too deep errors + Signature memory signature = _signatures[i]; + if (signature.isEmpty) { + continue; + } + + // The verification will throw if invalid + signature.verify(committee[i], digest); + validAttestations++; + } + + require( + validAttestations >= needed, + Errors.Leonidas__InsufficientAttestations(needed, validAttestations) + ); + } + + /** + * @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(LeonidasStorage storage _store, Epoch _epoch) + internal + view + returns (uint256) + { + if (Epoch.unwrap(_epoch) == 0) { + return type(uint256).max; + } + 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; + } + + /** + * @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`. + * + * @return The validators for the given epoch + */ + function _sampleValidators( + LeonidasStorage storage _store, + uint256 _seed, + uint256 _targetCommitteeSize + ) private view returns (address[] memory) { + uint256 validatorSetSize = _store.validatorSet.length(); + if (validatorSetSize == 0) { + return new address[](0); + } + + // If we have less validators than the target committee size, we just return the full set + if (validatorSetSize <= _targetCommitteeSize) { + return _store.validatorSet.values(); + } + + uint256[] memory indicies = + SampleLib.computeCommitteeClever(_targetCommitteeSize, validatorSetSize, _seed); + + address[] memory committee = new address[](_targetCommitteeSize); + for (uint256 i = 0; i < _targetCommitteeSize; i++) { + committee[i] = _store.validatorSet.at(indicies[i]); + } + return committee; + } + + function _getProposerAt( + LeonidasStorage storage _store, + 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(_store, _epochNumber, _targetCommitteeSize); + if (committee.length == 0) { + return address(0); + } + return committee[computeProposerIndex( + _epochNumber, _slot, getSampleSeed(_store, _epochNumber), committee.length + )]; + } + + function _getCommitteeAt( + LeonidasStorage storage _store, + Epoch _epochNumber, + uint256 _targetCommitteeSize + ) private view returns (address[] memory) { + EpochData storage epoch = _store.epochs[_epochNumber]; + + if (epoch.sampleSeed != 0) { + uint256 committeeSize = epoch.committee.length; + if (committeeSize == 0) { + return new address[](0); + } + return epoch.committee; + } + + // Allow anyone if there is no validator set + if (_store.validatorSet.length() == 0) { + return new address[](0); + } + + // Emulate a sampling of the validators + uint256 sampleSeed = getSampleSeed(_store, _epochNumber); + return _sampleValidators(_store, sampleSeed, _targetCommitteeSize); + } + + /** + * @notice Computes the index of the committee member that acts as proposer for a given slot + * + * @param _epoch - The epoch to compute the proposer index for + * @param _slot - The slot to compute the proposer index for + * @param _seed - The seed to use for the computation + * @param _size - The size of the committee + * + * @return The index of the proposer + */ + function computeProposerIndex(Epoch _epoch, Slot _slot, uint256 _seed, uint256 _size) + private + pure + returns (uint256) + { + return uint256(keccak256(abi.encode(_epoch, _slot, _seed))) % _size; + } +} diff --git a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol new file mode 100644 index 00000000000..6920c00cb4e --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol @@ -0,0 +1,276 @@ +// 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 { + RollupStore, SubmitEpochRootProofArgs, FeeHeader +} from "@aztec/core/interfaces/IRollup.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Epoch} from "@aztec/core/libraries/TimeMath.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "@oz/utils/math/Math.sol"; + +struct SubmitEpochRootProofAddresses { + IProofCommitmentEscrow proofCommitmentEscrow; + IFeeJuicePortal feeJuicePortal; + IRewardDistributor rewardDistributor; + IERC20 asset; + address cuauhxicalli; +} + +struct SubmitEpochRootProofInterimValues { + uint256 previousBlockNumber; + uint256 endBlockNumber; + Epoch epochToProve; + Epoch startEpoch; + bool isFeeCanonical; + bool isRewardDistributorCanonical; + uint256 totalProverReward; + uint256 totalBurn; +} + +library EpochProofLib { + using SafeERC20 for IERC20; + + function submitEpochRootProof( + RollupStore storage _rollupStore, + SubmitEpochRootProofArgs calldata _args, + SubmitEpochRootProofInterimValues memory _interimValues, + SubmitEpochRootProofAddresses memory _addresses + ) internal returns (uint256) { + // Ensure that the proof is not across epochs + require( + _interimValues.startEpoch == _interimValues.epochToProve, + Errors.Rollup__InvalidEpoch(_interimValues.startEpoch, _interimValues.epochToProve) + ); + + bytes32[] memory publicInputs = getEpochProofPublicInputs( + _rollupStore, _args.epochSize, _args.args, _args.fees, _args.aggregationObject + ); + + require( + _rollupStore.epochProofVerifier.verify(_args.proof, publicInputs), + Errors.Rollup__InvalidProof() + ); + + if (_rollupStore.proofClaim.epochToProve == _interimValues.epochToProve) { + _addresses.proofCommitmentEscrow.unstakeBond( + _rollupStore.proofClaim.bondProvider, _rollupStore.proofClaim.bondAmount + ); + } + + _rollupStore.tips.provenBlockNumber = _interimValues.endBlockNumber; + + // @note Only if the rollup is the canonical will it be able to meaningfully claim fees + // Otherwise, the fees are unbacked #7938. + _interimValues.isFeeCanonical = address(this) == _addresses.feeJuicePortal.canonicalRollup(); + _interimValues.isRewardDistributorCanonical = + address(this) == _addresses.rewardDistributor.canonicalRollup(); + + _interimValues.totalProverReward = 0; + _interimValues.totalBurn = 0; + + if (_interimValues.isFeeCanonical || _interimValues.isRewardDistributorCanonical) { + for (uint256 i = 0; i < _args.epochSize; i++) { + address coinbase = address(uint160(uint256(publicInputs[9 + i * 2]))); + uint256 reward = 0; + uint256 toProver = 0; + uint256 burn = 0; + + if (_interimValues.isFeeCanonical) { + uint256 fees = uint256(publicInputs[10 + i * 2]); + if (fees > 0) { + // This is insanely expensive, and will be fixed as part of the general storage cost reduction. + // See #9826. + FeeHeader storage feeHeader = + _rollupStore.blocks[_interimValues.previousBlockNumber + 1 + i].feeHeader; + burn += feeHeader.congestionCost * feeHeader.manaUsed; + + reward += (fees - burn); + _addresses.feeJuicePortal.distributeFees(address(this), fees); + } + } + + if (_interimValues.isRewardDistributorCanonical) { + reward += _addresses.rewardDistributor.claim(address(this)); + } + + if (coinbase == address(0)) { + toProver = reward; + } else { + // @note We are getting value from the `proofClaim`, which are not cleared. + // So if someone is posting the proof before a new claim is made, + // the reward will calculated based on the previous values. + toProver = Math.mulDiv(reward, _rollupStore.proofClaim.basisPointFee, 10_000); + } + + uint256 toCoinbase = reward - toProver; + if (toCoinbase > 0) { + _addresses.asset.safeTransfer(coinbase, toCoinbase); + } + + _interimValues.totalProverReward += toProver; + _interimValues.totalBurn += burn; + } + + if (_interimValues.totalProverReward > 0) { + // If there is a bond-provider give him the reward, otherwise give it to the submitter. + address proofRewardRecipient = _rollupStore.proofClaim.bondProvider == address(0) + ? msg.sender + : _rollupStore.proofClaim.bondProvider; + _addresses.asset.safeTransfer(proofRewardRecipient, _interimValues.totalProverReward); + } + + if (_interimValues.totalBurn > 0) { + _addresses.asset.safeTransfer(_addresses.cuauhxicalli, _interimValues.totalBurn); + } + } + + return _interimValues.endBlockNumber; + } + + /** + * @notice Returns the computed public inputs for the given epoch proof. + * + * @dev Useful for debugging and testing. Allows submitter to compare their + * own public inputs used for generating the proof vs the ones assembled + * by this contract when verifying it. + * + * @param _epochSize - The size of the epoch (to be promoted to a constant) + * @param _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) + * @param _fees - Array of recipient-value pairs with fees to be distributed for the epoch + * @param _aggregationObject - The aggregation object for the proof + */ + function getEpochProofPublicInputs( + RollupStore storage _rollupStore, + uint256 _epochSize, + bytes32[7] calldata _args, + bytes32[] calldata _fees, + bytes calldata _aggregationObject + ) internal view returns (bytes32[] memory) { + uint256 previousBlockNumber = _rollupStore.tips.provenBlockNumber; + uint256 endBlockNumber = previousBlockNumber + _epochSize; + + // Args are defined as an array because Solidity complains with "stack too deep" otherwise + // 0 bytes32 _previousArchive, + // 1 bytes32 _endArchive, + // 2 bytes32 _previousBlockHash, + // 3 bytes32 _endBlockHash, + // 4 bytes32 _endTimestamp, + // 5 bytes32 _outHash, + // 6 bytes32 _proverId, + + // TODO(#7373): Public inputs are not fully verified + + { + // We do it this way to provide better error messages than passing along the storage values + bytes32 expectedPreviousArchive = _rollupStore.blocks[previousBlockNumber].archive; + require( + expectedPreviousArchive == _args[0], + Errors.Rollup__InvalidPreviousArchive(expectedPreviousArchive, _args[0]) + ); + + bytes32 expectedEndArchive = _rollupStore.blocks[endBlockNumber].archive; + require( + expectedEndArchive == _args[1], Errors.Rollup__InvalidArchive(expectedEndArchive, _args[1]) + ); + + bytes32 expectedPreviousBlockHash = _rollupStore.blocks[previousBlockNumber].blockHash; + // TODO: Remove 0 check once we inject the proper genesis block hash + require( + expectedPreviousBlockHash == 0 || expectedPreviousBlockHash == _args[2], + Errors.Rollup__InvalidPreviousBlockHash(expectedPreviousBlockHash, _args[2]) + ); + + bytes32 expectedEndBlockHash = _rollupStore.blocks[endBlockNumber].blockHash; + require( + expectedEndBlockHash == _args[3], + Errors.Rollup__InvalidBlockHash(expectedEndBlockHash, _args[3]) + ); + } + + bytes32[] memory publicInputs = new bytes32[]( + Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH + ); + + // Structure of the root rollup public inputs we need to reassemble: + // + // struct RootRollupPublicInputs { + // previous_archive: AppendOnlyTreeSnapshot, + // end_archive: AppendOnlyTreeSnapshot, + // previous_block_hash: Field, + // end_block_hash: Field, + // end_timestamp: u64, + // end_block_number: Field, + // out_hash: Field, + // fees: [FeeRecipient; Constants.AZTEC_EPOCH_DURATION], + // vk_tree_root: Field, + // protocol_contract_tree_root: Field, + // prover_id: Field + // } + + // previous_archive.root: the previous archive tree root + publicInputs[0] = _args[0]; + + // previous_archive.next_available_leaf_index: the previous archive next available index + // normally this should be equal to the block number (since leaves are 0-indexed and blocks 1-indexed) + // but in yarn-project/merkle-tree/src/new_tree.ts we prefill the tree so that block N is in leaf N + publicInputs[1] = bytes32(previousBlockNumber + 1); + + // end_archive.root: the new archive tree root + publicInputs[2] = _args[1]; + + // end_archive.next_available_leaf_index: the new archive next available index + publicInputs[3] = bytes32(endBlockNumber + 1); + + // previous_block_hash: the block hash just preceding this epoch + publicInputs[4] = _args[2]; + + // end_block_hash: the last block hash in the epoch + publicInputs[5] = _args[3]; + + // end_timestamp: the timestamp of the last block in the epoch + publicInputs[6] = _args[4]; + + // end_block_number: last block number in the epoch + publicInputs[7] = bytes32(endBlockNumber); + + // out_hash: root of this epoch's l2 to l1 message tree + publicInputs[8] = _args[5]; + + uint256 feesLength = Constants.AZTEC_MAX_EPOCH_DURATION * 2; + // fees[9 to (9+feesLength-1)]: array of recipient-value pairs + for (uint256 i = 0; i < feesLength; i++) { + publicInputs[9 + i] = _fees[i]; + } + uint256 feesEnd = 9 + feesLength; + + // vk_tree_root + publicInputs[feesEnd] = _rollupStore.vkTreeRoot; + + // protocol_contract_tree_root + publicInputs[feesEnd + 1] = _rollupStore.protocolContractTreeRoot; + + // prover_id: id of current epoch's prover + publicInputs[feesEnd + 2] = _args[6]; + + // the block proof is recursive, which means it comes with an aggregation object + // this snippet copies it into the public inputs needed for verification + // it also guards against empty _aggregationObject used with mocked proofs + uint256 aggregationLength = _aggregationObject.length / 32; + for (uint256 i = 0; i < Constants.AGGREGATION_OBJECT_LENGTH && i < aggregationLength; i++) { + bytes32 part; + assembly { + part := calldataload(add(_aggregationObject.offset, mul(i, 32))) + } + publicInputs[i + feesEnd + 3] = part; + } + + return publicInputs; + } +} diff --git a/l1-contracts/src/core/libraries/RollupLibs/EpochProofQuoteLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofQuoteLib.sol new file mode 100644 index 00000000000..bfa865d6d4e --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/EpochProofQuoteLib.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; + +/** + * @notice Struct encompassing an epoch proof quote + * @param epochToProve - The epoch number to prove + * @param validUntilSlot - The deadline of the quote, denoted in L2 slots + * @param bondAmount - The size of the bond + * @param prover - The address of the prover + * @param basisPointFee - The fee measured in basis points + */ +struct EpochProofQuote { + Epoch epochToProve; + Slot validUntilSlot; + uint256 bondAmount; + address prover; + uint32 basisPointFee; +} + +/** + * @notice A signed quote for the epoch proof + * @param quote - The Epoch Proof Quote + * @param signature - A signature on the quote + */ +struct SignedEpochProofQuote { + EpochProofQuote quote; + Signature signature; +} + +library EpochProofQuoteLib { + bytes32 public constant EPOCH_PROOF_QUOTE_TYPEHASH = keccak256( + "EpochProofQuote(uint256 epochToProve,uint256 validUntilSlot,uint256 bondAmount,address prover,uint32 basisPointFee)" + ); + + function hash(EpochProofQuote memory _quote) internal pure returns (bytes32) { + return keccak256( + abi.encode( + EPOCH_PROOF_QUOTE_TYPEHASH, + _quote.epochToProve, + _quote.validUntilSlot, + _quote.bondAmount, + _quote.prover, + _quote.basisPointFee + ) + ); + } +} diff --git a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol new file mode 100644 index 00000000000..4b51f8efef0 --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -0,0 +1,113 @@ +// 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 {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; +import {BlockLog, RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {DataStructures} from "./../DataStructures.sol"; +import {Slot, Epoch} from "./../TimeMath.sol"; +import { + EpochProofLib, + SubmitEpochRootProofAddresses, + SubmitEpochRootProofInterimValues +} from "./EpochProofLib.sol"; +import {SignedEpochProofQuote} from "./EpochProofQuoteLib.sol"; +import {FeeMath, ManaBaseFeeComponents, FeeHeader, L1FeeData} from "./FeeMath.sol"; +import {HeaderLib, Header} from "./HeaderLib.sol"; +import {TxsDecoder} from "./TxsDecoder.sol"; +import {ValidationLib, ValidateHeaderArgs} from "./ValidationLib.sol"; +// We are using this library such that we can more easily "link" just a larger external library +// instead of a few smaller ones. + +library ExtRollupLib { + function submitEpochRootProof( + RollupStore storage _rollupStore, + SubmitEpochRootProofArgs calldata _args, + SubmitEpochRootProofInterimValues memory _interimValues, + IProofCommitmentEscrow _proofCommitmentEscrow, + IFeeJuicePortal _feeJuicePortal, + IRewardDistributor _rewardDistributor, + IERC20 _asset, + address _cuauhxicalli + ) external returns (uint256) { + return EpochProofLib.submitEpochRootProof( + _rollupStore, + _args, + _interimValues, + SubmitEpochRootProofAddresses({ + proofCommitmentEscrow: _proofCommitmentEscrow, + feeJuicePortal: _feeJuicePortal, + rewardDistributor: _rewardDistributor, + asset: _asset, + cuauhxicalli: _cuauhxicalli + }) + ); + } + + function validateHeaderForSubmissionBase( + ValidateHeaderArgs memory _args, + mapping(uint256 blockNumber => BlockLog log) storage _blocks + ) external view { + ValidationLib.validateHeaderForSubmissionBase(_args, _blocks); + } + + function validateEpochProofRightClaimAtTime( + Slot _currentSlot, + address _currentProposer, + Epoch _epochToProve, + uint256 _posInEpoch, + SignedEpochProofQuote calldata _quote, + bytes32 _digest, + DataStructures.EpochProofClaim storage _proofClaim, + uint256 _claimDurationInL2Slots, + uint256 _proofCommitmentMinBondAmountInTst, + IProofCommitmentEscrow _proofCommitmentEscrow + ) external view { + ValidationLib.validateEpochProofRightClaimAtTime( + _currentSlot, + _currentProposer, + _epochToProve, + _posInEpoch, + _quote, + _digest, + _proofClaim, + _claimDurationInL2Slots, + _proofCommitmentMinBondAmountInTst, + _proofCommitmentEscrow + ); + } + + function getManaBaseFeeComponentsAt( + FeeHeader storage _parentFeeHeader, + L1FeeData memory _fees, + uint256 _feeAssetPrice, + uint256 _epochDuration + ) external view returns (ManaBaseFeeComponents memory) { + return + FeeMath.getManaBaseFeeComponentsAt(_parentFeeHeader, _fees, _feeAssetPrice, _epochDuration); + } + + function getEpochProofPublicInputs( + RollupStore storage _rollupStore, + uint256 _epochSize, + bytes32[7] calldata _args, + bytes32[] calldata _fees, + bytes calldata _aggregationObject + ) external view returns (bytes32[] memory) { + return EpochProofLib.getEpochProofPublicInputs( + _rollupStore, _epochSize, _args, _fees, _aggregationObject + ); + } + + function decodeHeader(bytes calldata _header) external pure returns (Header memory) { + return HeaderLib.decode(_header); + } + + function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32) { + return TxsDecoder.decode(_body); + } +} diff --git a/l1-contracts/src/core/libraries/FeeMath.sol b/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol similarity index 64% rename from l1-contracts/src/core/libraries/FeeMath.sol rename to l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol index 215c2e4739a..ef3a27ffafb 100644 --- a/l1-contracts/src/core/libraries/FeeMath.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol @@ -6,7 +6,26 @@ import {Math} from "@oz/utils/math/Math.sol"; import {SafeCast} from "@oz/utils/math/SafeCast.sol"; import {SignedMath} from "@oz/utils/math/SignedMath.sol"; -import {Errors} from "./Errors.sol"; +import {Errors} from "../Errors.sol"; + +// These values are taken from the model, but mostly pulled out of the ass +uint256 constant MINIMUM_PROVING_COST_PER_MANA = 5415357955; +uint256 constant MAX_PROVING_COST_MODIFIER = 1000000000; +uint256 constant PROVING_UPDATE_FRACTION = 100000000000; + +uint256 constant MINIMUM_FEE_ASSET_PRICE = 10000000000; +uint256 constant MAX_FEE_ASSET_PRICE_MODIFIER = 1000000000; +uint256 constant FEE_ASSET_PRICE_UPDATE_FRACTION = 100000000000; + +uint256 constant L1_GAS_PER_BLOCK_PROPOSED = 150000; +uint256 constant L1_GAS_PER_EPOCH_VERIFIED = 1000000; + +uint256 constant MINIMUM_CONGESTION_MULTIPLIER = 1000000000; +uint256 constant MANA_TARGET = 100000000; +uint256 constant CONGESTION_UPDATE_FRACTION = 854700854; + +uint256 constant BLOB_GAS_PER_BLOB = 2 ** 17; +uint256 constant GAS_PER_BLOB_POINT_EVALUATION = 50_000; struct OracleInput { int256 provingCostModifier; @@ -21,27 +40,58 @@ struct ManaBaseFeeComponents { uint256 provingCost; } +struct FeeHeader { + uint256 excessMana; + uint256 feeAssetPriceNumerator; + uint256 manaUsed; + uint256 provingCostPerManaNumerator; + uint256 congestionCost; +} + +struct L1FeeData { + uint256 baseFee; + uint256 blobFee; +} + library FeeMath { using Math for uint256; using SafeCast for int256; using SafeCast for uint256; using SignedMath for int256; - // These values are taken from the model, but mostly pulled out of the ass - uint256 internal constant MINIMUM_PROVING_COST_PER_MANA = 5415357955; - uint256 internal constant MAX_PROVING_COST_MODIFIER = 1000000000; - uint256 internal constant PROVING_UPDATE_FRACTION = 100000000000; - - uint256 internal constant MINIMUM_FEE_ASSET_PRICE = 10000000000; - uint256 internal constant MAX_FEE_ASSET_PRICE_MODIFIER = 1000000000; - uint256 internal constant FEE_ASSET_PRICE_UPDATE_FRACTION = 100000000000; - - uint256 internal constant L1_GAS_PER_BLOCK_PROPOSED = 150000; - uint256 internal constant L1_GAS_PER_EPOCH_VERIFIED = 1000000; + function getManaBaseFeeComponentsAt( + FeeHeader storage _parentFeeHeader, + L1FeeData memory _fees, + uint256 _feeAssetPrice, + uint256 _epochDuration + ) internal view returns (ManaBaseFeeComponents memory) { + uint256 excessMana = FeeMath.clampedAdd( + _parentFeeHeader.excessMana + _parentFeeHeader.manaUsed, -int256(MANA_TARGET) + ); - uint256 internal constant MINIMUM_CONGESTION_MULTIPLIER = 1000000000; - uint256 internal constant MANA_TARGET = 100000000; - uint256 internal constant CONGESTION_UPDATE_FRACTION = 854700854; + uint256 dataCost = + Math.mulDiv(3 * BLOB_GAS_PER_BLOB, _fees.blobFee, MANA_TARGET, Math.Rounding.Ceil); + uint256 gasUsed = L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION + + L1_GAS_PER_EPOCH_VERIFIED / _epochDuration; + uint256 gasCost = Math.mulDiv(gasUsed, _fees.baseFee, MANA_TARGET, Math.Rounding.Ceil); + uint256 provingCost = FeeMath.provingCostPerMana(_parentFeeHeader.provingCostPerManaNumerator); + + uint256 congestionMultiplier_ = congestionMultiplier(excessMana); + uint256 total = dataCost + gasCost + provingCost; + uint256 congestionCost = Math.mulDiv( + total, congestionMultiplier_, MINIMUM_CONGESTION_MULTIPLIER, Math.Rounding.Floor + ) - total; + + // @todo @lherskind. The following is a crime against humanity, but it makes it + // very neat to plot etc from python, #10004 will fix it across the board + return ManaBaseFeeComponents({ + dataCost: Math.mulDiv(dataCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), + gasCost: Math.mulDiv(gasCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), + provingCost: Math.mulDiv(provingCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), + congestionCost: Math.mulDiv(congestionCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), + congestionMultiplier: congestionMultiplier_ + }); + } function assertValid(OracleInput memory _self) internal pure returns (bool) { require( diff --git a/l1-contracts/src/core/libraries/HeaderLib.sol b/l1-contracts/src/core/libraries/RollupLibs/HeaderLib.sol similarity index 90% rename from l1-contracts/src/core/libraries/HeaderLib.sol rename to l1-contracts/src/core/libraries/RollupLibs/HeaderLib.sol index 4cade7f20e6..15d26b46e74 100644 --- a/l1-contracts/src/core/libraries/HeaderLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/HeaderLib.sol @@ -5,6 +5,56 @@ pragma solidity >=0.8.27; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; +struct AppendOnlyTreeSnapshot { + bytes32 root; + uint32 nextAvailableLeafIndex; +} + +struct PartialStateReference { + AppendOnlyTreeSnapshot noteHashTree; + AppendOnlyTreeSnapshot nullifierTree; + AppendOnlyTreeSnapshot contractTree; + AppendOnlyTreeSnapshot publicDataTree; +} + +struct StateReference { + AppendOnlyTreeSnapshot l1ToL2MessageTree; + // Note: Can't use "partial" name here as in protocol specs because it is a reserved solidity keyword + PartialStateReference partialStateReference; +} + +struct GasFees { + uint256 feePerDaGas; + uint256 feePerL2Gas; +} + +struct GlobalVariables { + uint256 chainId; + uint256 version; + uint256 blockNumber; + uint256 slotNumber; + uint256 timestamp; + address coinbase; + bytes32 feeRecipient; + GasFees gasFees; +} + +struct ContentCommitment { + uint256 numTxs; + bytes32 txsEffectsHash; + bytes32 inHash; + bytes32 outHash; +} + +struct Header { + AppendOnlyTreeSnapshot lastArchive; + ContentCommitment contentCommitment; + StateReference stateReference; + GlobalVariables globalVariables; + uint256 totalFees; + uint256 totalManaUsed; +} + /** * @title Header Library * @author Aztec Labs @@ -56,56 +106,6 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; * | --- | --- | --- */ library HeaderLib { - struct AppendOnlyTreeSnapshot { - bytes32 root; - uint32 nextAvailableLeafIndex; - } - - struct PartialStateReference { - AppendOnlyTreeSnapshot noteHashTree; - AppendOnlyTreeSnapshot nullifierTree; - AppendOnlyTreeSnapshot contractTree; - AppendOnlyTreeSnapshot publicDataTree; - } - - struct StateReference { - AppendOnlyTreeSnapshot l1ToL2MessageTree; - // Note: Can't use "partial" name here as in protocol specs because it is a reserved solidity keyword - PartialStateReference partialStateReference; - } - - struct GasFees { - uint256 feePerDaGas; - uint256 feePerL2Gas; - } - - struct GlobalVariables { - uint256 chainId; - uint256 version; - uint256 blockNumber; - uint256 slotNumber; - uint256 timestamp; - address coinbase; - bytes32 feeRecipient; - GasFees gasFees; - } - - struct ContentCommitment { - uint256 numTxs; - bytes32 txsEffectsHash; - bytes32 inHash; - bytes32 outHash; - } - - struct Header { - AppendOnlyTreeSnapshot lastArchive; - ContentCommitment contentCommitment; - StateReference stateReference; - GlobalVariables globalVariables; - uint256 totalFees; - uint256 totalManaUsed; - } - uint256 private constant HEADER_LENGTH = 0x288; // Header byte length /** diff --git a/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol new file mode 100644 index 00000000000..f05eda331dd --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {EpochProofQuoteLib, EpochProofQuote} from "./EpochProofQuoteLib.sol"; + +import {FeeMath, ManaBaseFeeComponents, FeeHeader, MANA_TARGET} from "./FeeMath.sol"; + +// We are using this library such that we can more easily "link" just a larger external library +// instead of a few smaller ones. +library IntRollupLib { + function computeQuoteHash(EpochProofQuote memory _quote) internal pure returns (bytes32) { + return EpochProofQuoteLib.hash(_quote); + } + + function summedBaseFee(ManaBaseFeeComponents memory _components) internal pure returns (uint256) { + return FeeMath.summedBaseFee(_components); + } + + function clampedAdd(uint256 _a, int256 _b) internal pure returns (uint256) { + return FeeMath.clampedAdd(_a, _b); + } + + function feeAssetPriceModifier(uint256 _numerator) internal pure returns (uint256) { + return FeeMath.feeAssetPriceModifier(_numerator); + } + + function computeExcessMana(FeeHeader memory _feeHeader) internal pure returns (uint256) { + return clampedAdd(_feeHeader.excessMana + _feeHeader.manaUsed, -int256(MANA_TARGET)); + } +} diff --git a/l1-contracts/src/core/libraries/ProposeLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol similarity index 88% rename from l1-contracts/src/core/libraries/ProposeLib.sol rename to l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol index ab5330661f7..59b4e10a7a6 100644 --- a/l1-contracts/src/core/libraries/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ProposeLib.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.27; import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {OracleInput} from "@aztec/core/libraries/FeeMath.sol"; +import {OracleInput} from "./FeeMath.sol"; struct ProposeArgs { bytes32 archive; diff --git a/l1-contracts/src/core/libraries/TxsDecoder.sol b/l1-contracts/src/core/libraries/RollupLibs/TxsDecoder.sol similarity index 99% rename from l1-contracts/src/core/libraries/TxsDecoder.sol rename to l1-contracts/src/core/libraries/RollupLibs/TxsDecoder.sol index 4a7da2a7720..b3a12b47c2c 100644 --- a/l1-contracts/src/core/libraries/TxsDecoder.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/TxsDecoder.sol @@ -82,7 +82,7 @@ library TxsDecoder { * @param _body - The L2 block body calldata. * @return The txs effects hash. */ - function decode(bytes calldata _body) external pure returns (bytes32) { + function decode(bytes calldata _body) internal pure returns (bytes32) { ArrayOffsets memory offsets; Counts memory counts; ConsumablesVars memory vars; diff --git a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol new file mode 100644 index 00000000000..5db4c00b5a1 --- /dev/null +++ b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol @@ -0,0 +1,157 @@ +// 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 {BlockLog} from "@aztec/core/interfaces/IRollup.sol"; +import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {DataStructures} from "./../DataStructures.sol"; +import {Errors} from "./../Errors.sol"; +import {Timestamp, Slot, Epoch} from "./../TimeMath.sol"; +import {SignedEpochProofQuote} from "./EpochProofQuoteLib.sol"; +import {Header} from "./HeaderLib.sol"; + +struct ValidateHeaderArgs { + Header header; + Timestamp currentTime; + uint256 manaBaseFee; + bytes32 txsEffectsHash; + uint256 pendingBlockNumber; + DataStructures.ExecutionFlags flags; + uint256 version; + IFeeJuicePortal feeJuicePortal; + function(Slot) external view returns (Timestamp) getTimestampForSlot; +} + +library ValidationLib { + function validateHeaderForSubmissionBase( + ValidateHeaderArgs memory _args, + mapping(uint256 blockNumber => BlockLog log) storage _blocks + ) internal view { + require( + block.chainid == _args.header.globalVariables.chainId, + Errors.Rollup__InvalidChainId(block.chainid, _args.header.globalVariables.chainId) + ); + + require( + _args.header.globalVariables.version == _args.version, + Errors.Rollup__InvalidVersion(_args.version, _args.header.globalVariables.version) + ); + + require( + _args.header.globalVariables.blockNumber == _args.pendingBlockNumber + 1, + Errors.Rollup__InvalidBlockNumber( + _args.pendingBlockNumber + 1, _args.header.globalVariables.blockNumber + ) + ); + + bytes32 tipArchive = _blocks[_args.pendingBlockNumber].archive; + require( + tipArchive == _args.header.lastArchive.root, + Errors.Rollup__InvalidArchive(tipArchive, _args.header.lastArchive.root) + ); + + Slot slot = Slot.wrap(_args.header.globalVariables.slotNumber); + Slot lastSlot = _blocks[_args.pendingBlockNumber].slotNumber; + require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); + + Timestamp timestamp = _args.getTimestampForSlot(slot); + require( + Timestamp.wrap(_args.header.globalVariables.timestamp) == timestamp, + Errors.Rollup__InvalidTimestamp( + timestamp, Timestamp.wrap(_args.header.globalVariables.timestamp) + ) + ); + + // @note If you are hitting this error, it is likely because the chain you use have a blocktime that differs + // from the value that we have in the constants. + // When you are encountering this, it will likely be as the sequencer expects to be able to include + // an Aztec block in the "next" ethereum block based on a timestamp that is 12 seconds in the future + // from the last block. However, if the actual will only be 1 second in the future, you will end up + // expecting this value to be in the future. + require( + timestamp <= _args.currentTime, Errors.Rollup__TimestampInFuture(_args.currentTime, timestamp) + ); + + // Check if the data is available + require( + _args.flags.ignoreDA || _args.header.contentCommitment.txsEffectsHash == _args.txsEffectsHash, + Errors.Rollup__UnavailableTxs(_args.header.contentCommitment.txsEffectsHash) + ); + + // If not canonical rollup, require that the fees are zero + if (address(this) != _args.feeJuicePortal.canonicalRollup()) { + require(_args.header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); + require(_args.header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee()); + } else { + require(_args.header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee()); + require( + _args.header.globalVariables.gasFees.feePerL2Gas == _args.manaBaseFee, + Errors.Rollup__InvalidManaBaseFee( + _args.manaBaseFee, _args.header.globalVariables.gasFees.feePerL2Gas + ) + ); + } + } + + function validateEpochProofRightClaimAtTime( + Slot _currentSlot, + address _currentProposer, + Epoch _epochToProve, + uint256 _posInEpoch, + SignedEpochProofQuote calldata _quote, + bytes32 _digest, + DataStructures.EpochProofClaim storage _proofClaim, + uint256 _claimDurationInL2Slots, + uint256 _proofCommitmentMinBondAmountInTst, + IProofCommitmentEscrow _proofCommitmentEscrow + ) internal view { + SignatureLib.verify(_quote.signature, _quote.quote.prover, _digest); + + require( + _quote.quote.validUntilSlot >= _currentSlot, + Errors.Rollup__QuoteExpired(_currentSlot, _quote.quote.validUntilSlot) + ); + + require( + _quote.quote.basisPointFee <= 10_000, + Errors.Rollup__InvalidBasisPointFee(_quote.quote.basisPointFee) + ); + + require( + _currentProposer == address(0) || _currentProposer == msg.sender, + Errors.Leonidas__InvalidProposer(_currentProposer, msg.sender) + ); + + require( + _quote.quote.epochToProve == _epochToProve, + Errors.Rollup__NotClaimingCorrectEpoch(_epochToProve, _quote.quote.epochToProve) + ); + + require( + _posInEpoch < _claimDurationInL2Slots, + Errors.Rollup__NotInClaimPhase(_posInEpoch, _claimDurationInL2Slots) + ); + + // if the epoch to prove is not the one that has been claimed, + // then whatever is in the proofClaim is stale + require( + _proofClaim.epochToProve != _epochToProve || _proofClaim.proposerClaimant == address(0), + Errors.Rollup__ProofRightAlreadyClaimed() + ); + + require( + _quote.quote.bondAmount >= _proofCommitmentMinBondAmountInTst, + Errors.Rollup__InsufficientBondAmount( + _proofCommitmentMinBondAmountInTst, _quote.quote.bondAmount + ) + ); + + uint256 availableFundsInEscrow = _proofCommitmentEscrow.deposits(_quote.quote.prover); + require( + _quote.quote.bondAmount <= availableFundsInEscrow, + Errors.Rollup__InsufficientFundsInEscrow(_quote.quote.bondAmount, availableFundsInEscrow) + ); + } +} diff --git a/l1-contracts/src/core/libraries/crypto/SampleLib.sol b/l1-contracts/src/core/libraries/crypto/SampleLib.sol index a790dc6e56f..721aa7bd1be 100644 --- a/l1-contracts/src/core/libraries/crypto/SampleLib.sol +++ b/l1-contracts/src/core/libraries/crypto/SampleLib.sol @@ -33,7 +33,7 @@ library SampleLib { * @return indices - The indices of the committee */ function computeCommitteeStupid(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) - external + internal pure returns (uint256[] memory) { @@ -63,7 +63,7 @@ library SampleLib { * @return indices - The indices of the committee */ function computeCommitteeClever(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) - external + internal pure returns (uint256[] memory) { diff --git a/l1-contracts/src/core/libraries/crypto/SignatureLib.sol b/l1-contracts/src/core/libraries/crypto/SignatureLib.sol index 29e37357bc8..4223f5ddafe 100644 --- a/l1-contracts/src/core/libraries/crypto/SignatureLib.sol +++ b/l1-contracts/src/core/libraries/crypto/SignatureLib.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.27; import {Errors} from "@aztec/core/libraries/Errors.sol"; -library SignatureLib { - struct Signature { - bool isEmpty; - uint8 v; - bytes32 r; - bytes32 s; - } +struct Signature { + bool isEmpty; + uint8 v; + bytes32 r; + bytes32 s; +} +library SignatureLib { /** * @notice The domain seperator for the signatures */ diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 6c5abf747ee..80d85eb3115 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -6,8 +6,11 @@ import {DecoderBase} from "./decoders/Base.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import { + EpochProofQuote, + SignedEpochProofQuote +} from "@aztec/core/libraries/RollupLibs/EpochProofQuoteLib.sol"; import {Math} from "@oz/utils/math/Math.sol"; import {Registry} from "@aztec/governance/Registry.sol"; @@ -26,7 +29,9 @@ import {TestConstants} from "./harnesses/TestConstants.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; import {TxsDecoderHelper} from "./decoders/helpers/TxsDecoderHelper.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; -import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; +import { + ProposeArgs, OracleInput, ProposeLib +} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import { Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeFns @@ -54,10 +59,10 @@ contract RollupTest is DecoderBase, TimeFns { FeeJuicePortal internal feeJuicePortal; IProofCommitmentEscrow internal proofCommitmentEscrow; RewardDistributor internal rewardDistributor; - SignatureLib.Signature[] internal signatures; + Signature[] internal signatures; - EpochProofQuoteLib.EpochProofQuote internal quote; - EpochProofQuoteLib.SignedEpochProofQuote internal signedQuote; + EpochProofQuote internal quote; + SignedEpochProofQuote internal signedQuote; uint256 internal privateKey; address internal signer; @@ -107,7 +112,7 @@ contract RollupTest is DecoderBase, TimeFns { privateKey = 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234; signer = vm.addr(privateKey); uint256 bond = rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(); - quote = EpochProofQuoteLib.EpochProofQuote({ + quote = EpochProofQuote({ epochToProve: Epoch.wrap(0), validUntilSlot: Slot.wrap(1), bondAmount: bond, @@ -231,18 +236,14 @@ contract RollupTest is DecoderBase, TimeFns { ); rollup.claimEpochProofRight(signedQuote); - ( - Epoch epochToProve, - uint256 basisPointFee, - uint256 bondAmount, - address bondProvider, - address proposerClaimant - ) = rollup.proofClaim(); - assertEq(epochToProve, signedQuote.quote.epochToProve, "Invalid epoch to prove"); - assertEq(basisPointFee, signedQuote.quote.basisPointFee, "Invalid basis point fee"); - assertEq(bondAmount, signedQuote.quote.bondAmount, "Invalid bond amount"); - assertEq(bondProvider, quote.prover, "Invalid bond provider"); - assertEq(proposerClaimant, address(this), "Invalid proposer claimant"); + DataStructures.EpochProofClaim memory epochProofClaim = rollup.getProofClaim(); + assertEq(epochProofClaim.epochToProve, signedQuote.quote.epochToProve, "Invalid epoch to prove"); + assertEq( + epochProofClaim.basisPointFee, signedQuote.quote.basisPointFee, "Invalid basis point fee" + ); + assertEq(epochProofClaim.bondAmount, signedQuote.quote.bondAmount, "Invalid bond amount"); + assertEq(epochProofClaim.bondProvider, quote.prover, "Invalid bond provider"); + assertEq(epochProofClaim.proposerClaimant, address(this), "Invalid proposer claimant"); assertEq( proofCommitmentEscrow.deposits(quote.prover), quote.bondAmount * 9, "Invalid escrow balance" ); @@ -1181,16 +1182,16 @@ contract RollupTest is DecoderBase, TimeFns { ); } - function _quoteToSignedQuote(EpochProofQuoteLib.EpochProofQuote memory _quote) + function _quoteToSignedQuote(EpochProofQuote memory _quote) internal view - returns (EpochProofQuoteLib.SignedEpochProofQuote memory) + returns (SignedEpochProofQuote memory) { bytes32 digest = rollup.quoteToDigest(_quote); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - return EpochProofQuoteLib.SignedEpochProofQuote({ + return SignedEpochProofQuote({ quote: _quote, - signature: SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s}) + signature: Signature({isEmpty: false, v: v, r: r, s: s}) }); } } diff --git a/l1-contracts/test/decoders/Decoders.t.sol b/l1-contracts/test/decoders/Decoders.t.sol index c3d47db7bb8..2165f759911 100644 --- a/l1-contracts/test/decoders/Decoders.t.sol +++ b/l1-contracts/test/decoders/Decoders.t.sol @@ -8,7 +8,7 @@ import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {HeaderLibHelper} from "./helpers/HeaderLibHelper.sol"; import {TxsDecoderHelper} from "./helpers/TxsDecoderHelper.sol"; -import {HeaderLib} from "@aztec/core/libraries/HeaderLib.sol"; +import {HeaderLib, Header} from "@aztec/core/libraries/RollupLibs/HeaderLib.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; /** @@ -39,7 +39,7 @@ contract DecodersTest is DecoderBase { // Header { DecoderBase.DecodedHeader memory referenceHeader = data.block.decodedHeader; - HeaderLib.Header memory header = headerHelper.decode(data.block.header); + Header memory header = headerHelper.decode(data.block.header); // GlobalVariables { diff --git a/l1-contracts/test/decoders/helpers/HeaderLibHelper.sol b/l1-contracts/test/decoders/helpers/HeaderLibHelper.sol index 02528023a7c..8b81756fa77 100644 --- a/l1-contracts/test/decoders/helpers/HeaderLibHelper.sol +++ b/l1-contracts/test/decoders/helpers/HeaderLibHelper.sol @@ -2,11 +2,11 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {HeaderLib} from "@aztec/core/libraries/HeaderLib.sol"; +import {HeaderLib, Header} from "@aztec/core/libraries/RollupLibs/HeaderLib.sol"; contract HeaderLibHelper { // A wrapper used such that we get "calldata" and not memory - function decode(bytes calldata _header) public pure returns (HeaderLib.Header memory) { + function decode(bytes calldata _header) public pure returns (Header memory) { return HeaderLib.decode(_header); } } diff --git a/l1-contracts/test/decoders/helpers/TxsDecoderHelper.sol b/l1-contracts/test/decoders/helpers/TxsDecoderHelper.sol index 2f8db8d3378..45cd04ad139 100644 --- a/l1-contracts/test/decoders/helpers/TxsDecoderHelper.sol +++ b/l1-contracts/test/decoders/helpers/TxsDecoderHelper.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {TxsDecoder} from "@aztec/core/libraries/TxsDecoder.sol"; +import {TxsDecoder} from "@aztec/core/libraries/RollupLibs/TxsDecoder.sol"; import {MerkleLib} from "@aztec/core/libraries/crypto/MerkleLib.sol"; contract TxsDecoderHelper { diff --git a/l1-contracts/test/fees/FeeModelTestPoints.t.sol b/l1-contracts/test/fees/FeeModelTestPoints.t.sol index 3dd5b0de248..368df77e602 100644 --- a/l1-contracts/test/fees/FeeModelTestPoints.t.sol +++ b/l1-contracts/test/fees/FeeModelTestPoints.t.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.27; import {TestBase} from "../base/Base.sol"; -import {OracleInput as FeeMathOracleInput} from "@aztec/core/libraries/FeeMath.sol"; +import {OracleInput as FeeMathOracleInput} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; // Remember that foundry json parsing is alphabetically done, so you MUST // sort the struct fields alphabetically or prepare for a headache. diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index 7f131fb9da5..ba655ed2e74 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -6,8 +6,8 @@ import {DecoderBase} from "../decoders/Base.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; +import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {EpochProofQuoteLib} from "@aztec/core/libraries/RollupLibs/EpochProofQuoteLib.sol"; import {Math} from "@oz/utils/math/Math.sol"; import {Registry} from "@aztec/governance/Registry.sol"; @@ -36,10 +36,11 @@ import {TxsDecoderHelper} from "../decoders/helpers/TxsDecoderHelper.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; -import {OracleInput} from "@aztec/core/libraries/FeeMath.sol"; -import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; +import { + ProposeArgs, OracleInput, ProposeLib +} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {FeeMath} from "@aztec/core/libraries/FeeMath.sol"; +import {FeeMath, MANA_TARGET} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import { FeeHeader as FeeHeaderModel, @@ -92,7 +93,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { bytes header; bytes body; bytes32[] txHashes; - SignatureLib.Signature[] signatures; + Signature[] signatures; } DecoderBase.Full full = load("empty_block_1"); @@ -156,7 +157,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { bytes32 blockHash = 0x267f79fe7e757b20e924fac9f78264a0d1c8c4b481fea21d0bbe74650d87a1f1; bytes32[] memory txHashes = new bytes32[](0); - SignatureLib.Signature[] memory signatures = new SignatureLib.Signature[](0); + Signature[] memory signatures = new Signature[](0); bytes memory body = full.block.body; bytes memory header = full.block.header; @@ -252,11 +253,11 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { rollup.getBlock(rollup.getPendingBlockNumber()).feeHeader; uint256 excessManaNoPrune = ( parentFeeHeaderNoPrune.excessMana + parentFeeHeaderNoPrune.manaUsed - ).clampedAdd(-int256(FeeMath.MANA_TARGET)); + ).clampedAdd(-int256(MANA_TARGET)); FeeHeader memory parentFeeHeaderPrune = rollup.getBlock(rollup.getProvenBlockNumber()).feeHeader; uint256 excessManaPrune = (parentFeeHeaderPrune.excessMana + parentFeeHeaderPrune.manaUsed) - .clampedAdd(-int256(FeeMath.MANA_TARGET)); + .clampedAdd(-int256(MANA_TARGET)); assertGt(excessManaNoPrune, excessManaPrune, "excess mana should be lower if we prune"); diff --git a/l1-contracts/test/fees/MinimalFeeModel.sol b/l1-contracts/test/fees/MinimalFeeModel.sol index 0e5284d0b43..90849e20248 100644 --- a/l1-contracts/test/fees/MinimalFeeModel.sol +++ b/l1-contracts/test/fees/MinimalFeeModel.sol @@ -2,7 +2,14 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {FeeMath, OracleInput} from "@aztec/core/libraries/FeeMath.sol"; +import { + FeeMath, + OracleInput, + MANA_TARGET, + L1_GAS_PER_BLOCK_PROPOSED, + L1_GAS_PER_EPOCH_VERIFIED, + MINIMUM_CONGESTION_MULTIPLIER +} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import {Timestamp, TimeFns, Slot, SlotLib} from "@aztec/core/libraries/TimeMath.sol"; import {Vm} from "forge-std/Vm.sol"; import { @@ -60,19 +67,17 @@ contract MinimalFeeModel is TimeFns { returns (ManaBaseFeeComponents memory) { L1Fees memory fees = getCurrentL1Fees(); - uint256 dataCost = Math.mulDiv( - _blobsUsed * BLOB_GAS_PER_BLOB, fees.blob_fee, FeeMath.MANA_TARGET, Math.Rounding.Ceil - ); - uint256 gasUsed = FeeMath.L1_GAS_PER_BLOCK_PROPOSED + _blobsUsed * GAS_PER_BLOB_POINT_EVALUATION - + FeeMath.L1_GAS_PER_EPOCH_VERIFIED / EPOCH_DURATION; - uint256 gasCost = Math.mulDiv(gasUsed, fees.base_fee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); + uint256 dataCost = + Math.mulDiv(_blobsUsed * BLOB_GAS_PER_BLOB, fees.blob_fee, MANA_TARGET, Math.Rounding.Ceil); + uint256 gasUsed = L1_GAS_PER_BLOCK_PROPOSED + _blobsUsed * GAS_PER_BLOB_POINT_EVALUATION + + L1_GAS_PER_EPOCH_VERIFIED / EPOCH_DURATION; + uint256 gasCost = Math.mulDiv(gasUsed, fees.base_fee, MANA_TARGET, Math.Rounding.Ceil); uint256 provingCost = getProvingCost(); uint256 congestionMultiplier = FeeMath.congestionMultiplier(calcExcessMana()); uint256 total = dataCost + gasCost + provingCost; - uint256 congestionCost = - (total * congestionMultiplier / FeeMath.MINIMUM_CONGESTION_MULTIPLIER) - total; + uint256 congestionCost = (total * congestionMultiplier / MINIMUM_CONGESTION_MULTIPLIER) - total; uint256 feeAssetPrice = _inFeeAsset ? getFeeAssetPrice() : 1e9; @@ -91,7 +96,7 @@ contract MinimalFeeModel is TimeFns { function calcExcessMana() internal view returns (uint256) { FeeHeader storage parent = feeHeaders[populatedThrough]; - return (parent.excess_mana + parent.mana_used).clampedAdd(-int256(FeeMath.MANA_TARGET)); + return (parent.excess_mana + parent.mana_used).clampedAdd(-int256(MANA_TARGET)); } function addSlot(OracleInput memory _oracleInput) public { diff --git a/l1-contracts/test/fees/MinimalFeeModel.t.sol b/l1-contracts/test/fees/MinimalFeeModel.t.sol index cbc0149deff..f2b79e42f24 100644 --- a/l1-contracts/test/fees/MinimalFeeModel.t.sol +++ b/l1-contracts/test/fees/MinimalFeeModel.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.27; -import {OracleInput, FeeMath} from "@aztec/core/libraries/FeeMath.sol"; +import {OracleInput, FeeMath} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import { FeeModelTestPoints, TestPoint, @@ -12,6 +12,10 @@ import { import {MinimalFeeModel} from "./MinimalFeeModel.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {SlotLib, Slot} from "@aztec/core/libraries/TimeMath.sol"; +import { + MAX_PROVING_COST_MODIFIER, + MAX_FEE_ASSET_PRICE_MODIFIER +} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; contract MinimalFeeModelTest is FeeModelTestPoints { using SlotLib for Slot; @@ -78,8 +82,8 @@ contract MinimalFeeModelTest is FeeModelTestPoints { } function test_invalidOracleInput() public { - uint256 provingBoundary = FeeMath.MAX_PROVING_COST_MODIFIER + 1; - uint256 feeAssetPriceBoundary = FeeMath.MAX_FEE_ASSET_PRICE_MODIFIER + 1; + uint256 provingBoundary = MAX_PROVING_COST_MODIFIER + 1; + uint256 feeAssetPriceBoundary = MAX_FEE_ASSET_PRICE_MODIFIER + 1; vm.expectRevert(abi.encodeWithSelector(Errors.FeeMath__InvalidProvingCostModifier.selector)); model.addSlot( diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index da7af0eb534..990f3ec6512 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -21,9 +21,11 @@ import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MockFeeJuicePortal} from "@aztec/mock/MockFeeJuicePortal.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; +import {stdStorage, StdStorage} from "forge-std/Test.sol"; contract TokenPortalTest is Test { using Hash for DataStructures.L1ToL2Msg; + using stdStorage for StdStorage; event MessageConsumed(bytes32 indexed messageHash, address indexed recipient); @@ -77,7 +79,7 @@ contract TokenPortalTest is Test { tokenPortal.initialize(address(registry), address(testERC20), l2TokenAddress); // Modify the proven block count - vm.store(address(rollup), bytes32(uint256(9)), bytes32(l2BlockNumber)); + stdstore.target(address(rollup)).sig("getProvenBlockNumber()").checked_write(l2BlockNumber); assertEq(rollup.getProvenBlockNumber(), l2BlockNumber); vm.deal(address(this), 100 ether); diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 9dff6c5babb..ea0c94f7d49 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -6,7 +6,7 @@ import {DecoderBase} from "../decoders/Base.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; @@ -20,7 +20,9 @@ import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {TxsDecoderHelper} from "../decoders/helpers/TxsDecoderHelper.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {MockFeeJuicePortal} from "@aztec/mock/MockFeeJuicePortal.sol"; -import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; +import { + ProposeArgs, OracleInput, ProposeLib +} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; @@ -48,7 +50,7 @@ contract SpartaTest is DecoderBase { TxsDecoderHelper internal txsHelper; TestERC20 internal testERC20; RewardDistributor internal rewardDistributor; - SignatureLib.Signature internal emptySignature; + Signature internal emptySignature; mapping(address validator => uint256 privateKey) internal privateKeys; mapping(address => bool) internal _seenValidators; mapping(address => bool) internal _seenCommittee; @@ -199,7 +201,7 @@ contract SpartaTest is DecoderBase { address[] memory validators = rollup.getEpochCommittee(rollup.getCurrentEpoch()); ree.needed = validators.length * 2 / 3 + 1; - SignatureLib.Signature[] memory signatures = new SignatureLib.Signature[](_signatureCount); + Signature[] memory signatures = new Signature[](_signatureCount); bytes32 digest = ProposeLib.digest(args); for (uint256 i = 0; i < _signatureCount; i++) { @@ -239,7 +241,7 @@ contract SpartaTest is DecoderBase { return; } } else { - SignatureLib.Signature[] memory signatures = new SignatureLib.Signature[](0); + Signature[] memory signatures = new Signature[](0); rollup.propose(args, signatures, full.block.body); } @@ -298,13 +300,13 @@ contract SpartaTest is DecoderBase { function createSignature(address _signer, bytes32 _digest) internal view - returns (SignatureLib.Signature memory) + returns (Signature memory) { uint256 privateKey = privateKeys[_signer]; bytes32 digest = _digest.toEthSignedMessageHash(); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - return SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s}); + return Signature({isEmpty: false, v: v, r: r, s: s}); } } diff --git a/yarn-project/aztec.js/src/utils/cheat_codes.ts b/yarn-project/aztec.js/src/utils/cheat_codes.ts index 87048d1c0e1..10b837a9b04 100644 --- a/yarn-project/aztec.js/src/utils/cheat_codes.ts +++ b/yarn-project/aztec.js/src/utils/cheat_codes.ts @@ -82,8 +82,11 @@ export class RollupCheatCodes { /** The pending chain tip */ pending: bigint; /** The proven chain tip */ proven: bigint; }> { - const [pending, proven] = await this.rollup.read.tips(); - return { pending, proven }; + const res = await this.rollup.read.getTips(); + return { + pending: res.pendingBlockNumber, + proven: res.provenBlockNumber, + }; } /** Fetches the epoch and slot duration config from the rollup contract */ @@ -125,8 +128,13 @@ export class RollupCheatCodes { /** Returns the current proof claim (if any) */ public async getProofClaim(): Promise { // REFACTOR: This code is duplicated from l1-publisher - const [epochToProve, basisPointFee, bondAmount, bondProviderHex, proposerClaimantHex] = - await this.rollup.read.proofClaim(); + const { + epochToProve, + basisPointFee, + bondAmount, + bondProvider: bondProviderHex, + proposerClaimant: proposerClaimantHex, + } = await this.rollup.read.getProofClaim(); const bondProvider = EthAddress.fromString(bondProviderHex); const proposerClaimant = EthAddress.fromString(proposerClaimantHex); @@ -151,7 +159,7 @@ export class RollupCheatCodes { public async markAsProven(maybeBlockNumber?: number | bigint) { const blockNumber = maybeBlockNumber ? BigInt(maybeBlockNumber) - : await this.rollup.read.tips().then(([pending]) => pending); + : await this.rollup.read.getTips().then(({ pendingBlockNumber }) => pendingBlockNumber); await this.asOwner(async account => { await this.rollup.write.setAssumeProvenThroughBlockNumber([blockNumber], { account, chain: this.client.chain }); diff --git a/yarn-project/cli/src/cmds/l1/update_l1_validators.ts b/yarn-project/cli/src/cmds/l1/update_l1_validators.ts index e08231e5b7f..4f3d33eb574 100644 --- a/yarn-project/cli/src/cmds/l1/update_l1_validators.ts +++ b/yarn-project/cli/src/cmds/l1/update_l1_validators.ts @@ -171,8 +171,6 @@ export async function debugRollup({ rpcUrl, chainId, rollupAddress, log }: Rollu log(`Archive: ${archive}`); const epochNum = await rollup.read.getCurrentEpoch(); log(`Current epoch: ${epochNum}`); - const epoch = await rollup.read.epochs([epochNum]); - log(`Epoch Sample Seed: ${epoch[0].toString()}, Next Seed: ${epoch[1].toString()}`); const slot = await rollup.read.getCurrentSlot(); log(`Current slot: ${slot}`); const proposerDuringPrevL1Block = await rollup.read.getCurrentProposer(); diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts index bf0cf8934e0..899b52437f7 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts @@ -139,12 +139,13 @@ describe('e2e_prover_coordination', () => { proposer: EthAddress; prover: EthAddress; }) => { - const [epochToProve, basisPointFee, bondAmount, prover, proposer] = await rollupContract.read.proofClaim(); + const { epochToProve, basisPointFee, bondAmount, bondProvider, proposerClaimant } = + await rollupContract.read.getProofClaim(); expect(epochToProve).toEqual(expected.epochToProve); expect(basisPointFee).toEqual(BigInt(expected.basisPointFee)); expect(bondAmount).toEqual(expected.bondAmount); - expect(prover).toEqual(expected.prover.toChecksumString()); - expect(proposer).toEqual(expected.proposer.toChecksumString()); + expect(bondProvider).toEqual(expected.prover.toChecksumString()); + expect(proposerClaimant).toEqual(expected.proposer.toChecksumString()); }; const performEscrow = async (amount: bigint) => { diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 8d2f6b64245..32832708c2a 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -5,6 +5,8 @@ import { type DebugLogger } from '@aztec/foundation/log'; import { CoinIssuerAbi, CoinIssuerBytecode, + ExtRollupLibAbi, + ExtRollupLibBytecode, FeeJuicePortalAbi, FeeJuicePortalBytecode, GovernanceAbi, @@ -13,6 +15,8 @@ import { GovernanceProposerBytecode, InboxAbi, InboxBytecode, + LeonidasLibAbi, + LeonidasLibBytecode, OutboxAbi, OutboxBytecode, RegistryAbi, @@ -22,12 +26,8 @@ import { RollupAbi, RollupBytecode, RollupLinkReferences, - SampleLibAbi, - SampleLibBytecode, TestERC20Abi, TestERC20Bytecode, - TxsDecoderAbi, - TxsDecoderBytecode, } from '@aztec/l1-artifacts'; import type { Abi, Narrow } from 'abitype'; @@ -172,13 +172,13 @@ export const l1Artifacts: L1ContractArtifactsForDeployment = { libraries: { linkReferences: RollupLinkReferences, libraryCode: { - TxsDecoder: { - contractAbi: TxsDecoderAbi, - contractBytecode: TxsDecoderBytecode, + LeonidasLib: { + contractAbi: LeonidasLibAbi, + contractBytecode: LeonidasLibBytecode, }, - SampleLib: { - contractAbi: SampleLibAbi, - contractBytecode: SampleLibBytecode, + ExtRollupLib: { + contractAbi: ExtRollupLibAbi, + contractBytecode: ExtRollupLibBytecode, }, }, }, @@ -613,7 +613,16 @@ export async function deployL1Contract( const l1TxUtils = new L1TxUtils(publicClient, walletClient, logger); if (libraries) { - // @note Assumes that we wont have nested external libraries. + // Note that this does NOT work well for linked libraries having linked libraries. + + // Verify that all link references have corresponding code + for (const linkRef in libraries.linkReferences) { + for (const contractName in libraries.linkReferences[linkRef]) { + if (!libraries.libraryCode[contractName]) { + throw new Error(`Missing library code for ${contractName}`); + } + } + } const replacements: Record = {}; diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index ef0892de022..3b78a796f50 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -28,8 +28,8 @@ CONTRACTS=( "l1-contracts:GovernanceProposer" "l1-contracts:Governance" "l1-contracts:NewGovernanceProposerPayload" - "l1-contracts:TxsDecoder" - "l1-contracts:SampleLib" + "l1-contracts:LeonidasLib" + "l1-contracts:ExtRollupLib" ) diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 10e4b61f967..d7e139d4dde 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -307,8 +307,13 @@ export class L1Publisher { } public async getProofClaim(): Promise { - const [epochToProve, basisPointFee, bondAmount, bondProviderHex, proposerClaimantHex] = - await this.rollupContract.read.proofClaim(); + const { + epochToProve, + basisPointFee, + bondAmount, + bondProvider: bondProviderHex, + proposerClaimant: proposerClaimantHex, + } = await this.rollupContract.read.getProofClaim(); const bondProvider = EthAddress.fromString(bondProviderHex); const proposerClaimant = EthAddress.fromString(proposerClaimantHex); @@ -636,7 +641,7 @@ export class L1Publisher { const { fromBlock, toBlock, publicInputs, proof } = args; // Check that the block numbers match the expected epoch to be proven - const [pending, proven] = await this.rollupContract.read.tips(); + const { pendingBlockNumber: pending, provenBlockNumber: proven } = await this.rollupContract.read.getTips(); if (proven !== BigInt(fromBlock) - 1n) { throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`); }