From 59dea9923bb594833080966bdbf9e7a39631cdf9 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Thu, 21 Nov 2024 16:58:21 +0000 Subject: [PATCH 1/2] feat: integrate fee into rollup --- l1-contracts/src/core/Rollup.sol | 240 ++++++++++++++-- l1-contracts/src/core/interfaces/IRollup.sol | 36 +++ .../src/core/libraries/ConstantsGen.sol | 2 +- l1-contracts/src/core/libraries/Errors.sol | 1 + l1-contracts/src/core/libraries/HeaderLib.sol | 27 +- .../src/core/libraries/ProposeLib.sol | 2 + l1-contracts/test/Rollup.t.sol | 148 +++++++--- l1-contracts/test/fees/FeeRollup.t.sol | 271 ++++++++++++++++++ l1-contracts/test/harnesses/Rollup.sol | 4 +- l1-contracts/test/sparta/Sparta.t.sol | 3 +- .../contracts/fee_juice_contract/src/main.nr | 8 +- .../crates/types/src/constants.nr | 2 +- .../archiver/src/archiver/archiver.test.ts | 6 +- .../archiver/src/archiver/data_retrieval.ts | 4 + .../aztec-node/src/aztec-node/server.ts | 9 + .../aztec.js/src/utils/anvil_test_watcher.ts | 4 +- .../aztec.js/src/wallet/base_wallet.ts | 4 + .../src/global_variable_builder.ts | 4 +- .../src/interfaces/aztec-node.test.ts | 9 + .../src/interfaces/aztec-node.ts | 9 + .../circuit-types/src/interfaces/pxe.test.ts | 9 + .../circuit-types/src/interfaces/pxe.ts | 9 + .../src/p2p/consensus_payload.ts | 10 +- yarn-project/circuits.js/src/constants.gen.ts | 2 +- yarn-project/cli-wallet/test/flows/profile.sh | 8 +- .../cli/src/cmds/misc/setup_contracts.ts | 4 +- .../cli/src/cmds/pxe/get_current_base_fee.ts | 8 + yarn-project/cli/src/cmds/pxe/index.ts | 9 + .../composed/integration_l1_publisher.test.ts | 15 +- .../src/e2e_fees/account_init.test.ts | 11 +- .../src/e2e_fees/dapp_subscription.test.ts | 16 +- .../end-to-end/src/e2e_fees/failures.test.ts | 15 +- .../end-to-end/src/e2e_fees/fees_test.ts | 6 +- .../src/e2e_fees/gas_estimation.test.ts | 19 +- .../src/e2e_fees/private_payments.test.ts | 11 +- .../end-to-end/src/e2e_synching.test.ts | 12 +- yarn-project/end-to-end/src/fixtures/utils.ts | 10 +- .../ethereum/src/deploy_l1_contracts.ts | 2 +- .../pxe/src/pxe_service/pxe_service.ts | 5 + .../global_variable_builder/global_builder.ts | 7 +- .../src/publisher/l1-publisher.test.ts | 4 + .../src/publisher/l1-publisher.ts | 27 +- .../simulator/src/public/public_processor.ts | 9 +- 43 files changed, 884 insertions(+), 137 deletions(-) create mode 100644 l1-contracts/test/fees/FeeRollup.t.sol create mode 100644 yarn-project/cli/src/cmds/pxe/get_current_base_fee.ts diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 47e27678fcf..cf5d63523f1 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -4,7 +4,14 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; -import {IRollup, ITestRollup} from "@aztec/core/interfaces/IRollup.sol"; +import { + IRollup, + ITestRollup, + FeeHeader, + ManaBaseFeeComponents, + BlockLog, + L1FeeData +} from "@aztec/core/interfaces/IRollup.sol"; import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; @@ -15,6 +22,7 @@ import {SignatureLib} 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 {Timestamp, Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; @@ -29,6 +37,19 @@ 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; + uint256 targetCommitteeSize; + uint256 aztecEpochProofClaimWindowInL2Slots; +} /** * @title Rollup @@ -42,29 +63,27 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { using EpochLib for Epoch; using SafeERC20 for IERC20; using ProposeLib for ProposeArgs; + using FeeMath for uint256; - struct ChainTips { - uint256 pendingBlockNumber; - uint256 provenBlockNumber; + struct L1GasOracleValues { + L1FeeData pre; + L1FeeData post; + Slot slotOfChange; } - struct BlockLog { - bytes32 archive; - bytes32 blockHash; - Slot slotNumber; - } + uint256 internal constant BLOB_GAS_PER_BLOB = 2 ** 17; + uint256 internal constant GAS_PER_BLOB_POINT_EVALUATION = 50_000; - struct Config { - uint256 aztecSlotDuration; - uint256 aztecEpochDuration; - uint256 targetCommitteeSize; - uint256 aztecEpochProofClaimWindowInL2Slots; - } + Slot public constant LIFETIME = Slot.wrap(5); + Slot public constant LAG = Slot.wrap(2); // See https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8401-proof-timeliness/proof-timeliness.ipynb // for justification of CLAIM_DURATION_IN_L2_SLOTS. uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; + address public constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); + bool public immutable IS_FOUNDRY_TEST; + uint256 public immutable CLAIM_DURATION_IN_L2_SLOTS; uint256 public immutable L1_BLOCK_AT_GENESIS; IInbox public immutable INBOX; @@ -85,7 +104,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { // 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) public blocks; + mapping(uint256 blockNumber => BlockLog log) internal blocks; bytes32 public vkTreeRoot; bytes32 public protocolContractTreeRoot; @@ -94,6 +113,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { // Testing only. This should be removed eventually. uint256 private assumeProvenThroughBlockNumber; + L1GasOracleValues public l1GasOracleValues; + constructor( IFeeJuicePortal _fpcJuicePortal, IRewardDistributor _rewardDistributor, @@ -125,12 +146,25 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { L1_BLOCK_AT_GENESIS = block.number; CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots; + IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; + // Genesis block blocks[0] = BlockLog({ + feeHeader: FeeHeader({ + excessMana: 0, + feeAssetPriceNumerator: 0, + manaUsed: 0, + provingCostPerManaNumerator: 0 + }), archive: bytes32(Constants.GENESIS_ARCHIVE_ROOT), blockHash: bytes32(0), // TODO(palla/prover): The first block does not have hash zero slotNumber: Slot.wrap(0) }); + l1GasOracleValues = L1GasOracleValues({ + pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}), + post: L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}), + slotOfChange: LIFETIME + }); for (uint256 i = 0; i < _validators.length; i++) { _addValidator(_validators[i]); } @@ -398,8 +432,11 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { bytes32 _txsEffectsHash, DataStructures.ExecutionFlags memory _flags ) external view override(IRollup) { + uint256 manaBaseFee = getManaBaseFee(true); HeaderLib.Header memory header = HeaderLib.decode(_header); - _validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags); + _validateHeader( + header, _signatures, _digest, _currentTime, manaBaseFee, _txsEffectsHash, _flags + ); } /** @@ -473,6 +510,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { if (canPrune()) { _prune(); } + updateL1GasFeeOracle(); + // 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. @@ -483,22 +522,41 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { bytes32 digest = _args.digest(); setupEpoch(); + uint256 manaBaseFee = getManaBaseFee(true); _validateHeader({ _header: header, _signatures: _signatures, _digest: digest, _currentTime: Timestamp.wrap(block.timestamp), + _manaBaseFee: manaBaseFee, _txEffectsHash: txsEffectsHash, _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) }); uint256 blockNumber = ++tips.pendingBlockNumber; - blocks[blockNumber] = BlockLog({ - archive: _args.archive, - blockHash: _args.blockHash, - slotNumber: Slot.wrap(header.globalVariables.slotNumber) - }); + { + FeeHeader memory parentFeeHeader = blocks[blockNumber - 1].feeHeader; + uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd( + -int256(FeeMath.MANA_TARGET) + ); + + blocks[blockNumber] = BlockLog({ + archive: _args.archive, + blockHash: _args.blockHash, + slotNumber: Slot.wrap(header.globalVariables.slotNumber), + feeHeader: FeeHeader({ + excessMana: excessMana, + feeAssetPriceNumerator: parentFeeHeader.feeAssetPriceNumerator.clampedAdd( + _args.oracleInput.feeAssetPriceModifier + ), + manaUsed: header.totalManaUsed, + provingCostPerManaNumerator: parentFeeHeader.provingCostPerManaNumerator.clampedAdd( + _args.oracleInput.provingCostModifier + ) + }) + }); + } // @note The block number here will always be >=1 as the genesis block is at 0 bytes32 inHash = INBOX.consume(blockNumber); @@ -536,6 +594,111 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { } } + /** + * @notice Updates the l1 gas fee oracle + * @dev This function is called by the `propose` function + */ + function updateL1GasFeeOracle() public override(IRollup) { + Slot slot = getCurrentSlot(); + // The slot where we find a new queued value acceptable + Slot acceptableSlot = l1GasOracleValues.slotOfChange + (LIFETIME - LAG); + + if (slot < acceptableSlot) { + return; + } + + l1GasOracleValues.pre = l1GasOracleValues.post; + l1GasOracleValues.post = L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}); + l1GasOracleValues.slotOfChange = slot + LAG; + } + + /** + * @notice Gets the fee asset price as fee_asset / eth with 1e9 precision + * + * @return The fee asset price + */ + function getFeeAssetPrice() public view override(IRollup) returns (uint256) { + return FeeMath.feeAssetPriceModifier( + blocks[tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator + ); + } + + /** + * @notice Gets the current l1 fees + * + * @return The current l1 fees + */ + function getCurrentL1Fees() public view override(IRollup) returns (L1FeeData memory) { + Slot slot = getCurrentSlot(); + if (slot < l1GasOracleValues.slotOfChange) { + return l1GasOracleValues.pre; + } + return l1GasOracleValues.post; + } + + /** + * @notice Gets the mana base fee + * + * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH + * + * @return The mana base fee + */ + function getManaBaseFee(bool _inFeeAsset) public view override(IRollup) returns (uint256) { + ManaBaseFeeComponents memory components = manaBaseFeeComponents(_inFeeAsset); + return + components.dataCost + components.gasCost + components.provingCost + components.congestionCost; + } + + /** + * @notice Gets the mana base fee components + * + * @dev TODO #10004 - As part of the refactor, will likely get rid of this function or make it private + * keeping it public for now makes it simpler to test. + * + * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH + * + * @return The mana base fee components + */ + function manaBaseFeeComponents(bool _inFeeAsset) + public + view + override(ITestRollup) + returns (ManaBaseFeeComponents memory) + { + FeeHeader storage parentFeeHeader = blocks[tips.pendingBlockNumber].feeHeader; + uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd( + -int256(FeeMath.MANA_TARGET) + ); + + L1FeeData memory fees = getCurrentL1Fees(); + uint256 dataCost = + Math.mulDiv(3 * BLOB_GAS_PER_BLOB, fees.blobFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); + uint256 casUsed = FeeMath.L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION + + FeeMath.L1_GAS_PER_EPOCH_VERIFIED / EPOCH_DURATION; + uint256 gasCost = Math.mulDiv(casUsed, 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) public view @@ -757,6 +920,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } + function getBlock(uint256 _blockNumber) public view override(IRollup) returns (BlockLog memory) { + require( + _blockNumber <= tips.pendingBlockNumber, + Errors.Rollup__InvalidBlockNumber(tips.pendingBlockNumber, _blockNumber) + ); + return blocks[_blockNumber]; + } + function getEpochForBlock(uint256 _blockNumber) public view override(IRollup) returns (Epoch) { require( _blockNumber <= tips.pendingBlockNumber, @@ -856,13 +1027,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { SignatureLib.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, _txEffectsHash, pendingBlockNumber, _flags + _header, _currentTime, _manaBaseFee, _txEffectsHash, pendingBlockNumber, _flags ); _validateHeaderForSubmissionSequencerSelection( Slot.wrap(_header.globalVariables.slotNumber), _signatures, _digest, _currentTime, _flags @@ -928,6 +1100,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { function _validateHeaderForSubmissionBase( HeaderLib.Header memory _header, Timestamp _currentTime, + uint256 _manaBaseFee, bytes32 _txsEffectsHash, uint256 _pendingBlockNumber, DataStructures.ExecutionFlags memory _flags @@ -983,6 +1156,12 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { 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) + ); } } @@ -1004,4 +1183,19 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { } } } + + /** + * @notice Get the blob base fee + * + * @dev If we are in a foundry test, we use the cheatcode to get the blob base fee. + * Otherwise, we use the `block.blobbasefee` + * + * @return uint256 - The blob base fee + */ + function _getBlobBaseFee() private view returns (uint256) { + if (IS_FOUNDRY_TEST) { + return Vm(VM_ADDRESS).getBlobBaseFee(); + } + return block.blobbasefee; + } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 3f128d7db5a..00981c23eaf 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -10,11 +10,42 @@ import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol"; import {ProposeArgs} from "@aztec/core/libraries/ProposeLib.sol"; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; +struct FeeHeader { + uint256 excessMana; + uint256 feeAssetPriceNumerator; + uint256 manaUsed; + uint256 provingCostPerManaNumerator; +} + +struct BlockLog { + FeeHeader feeHeader; + bytes32 archive; + bytes32 blockHash; + Slot slotNumber; +} + +struct L1FeeData { + uint256 baseFee; + uint256 blobFee; +} + +struct ManaBaseFeeComponents { + uint256 congestionCost; + uint256 congestionMultiplier; + uint256 dataCost; + uint256 gasCost; + uint256 provingCost; +} + interface ITestRollup { function setEpochVerifier(address _verifier) external; function setVkTreeRoot(bytes32 _vkTreeRoot) external; function setProtocolContractTreeRoot(bytes32 _protocolContractTreeRoot) external; function setAssumeProvenThroughBlockNumber(uint256 _blockNumber) external; + function manaBaseFeeComponents(bool _inFeeAsset) + external + view + returns (ManaBaseFeeComponents memory); } interface IRollup { @@ -30,6 +61,7 @@ interface IRollup { ); function prune() external; + function updateL1GasFeeOracle() external; function claimEpochProofRight(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) external; @@ -90,6 +122,10 @@ interface IRollup { external view returns (bytes32); + function getBlock(uint256 _blockNumber) external view returns (BlockLog memory); + function getFeeAssetPrice() external view returns (uint256); + function getManaBaseFee(bool _inFeeAsset) external view returns (uint256); + function getCurrentL1Fees() external view returns (L1FeeData memory); function archive() external view returns (bytes32); function archiveAt(uint256 _blockNumber) external view returns (bytes32); diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 4aea0600b23..862d733f4f3 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -97,7 +97,7 @@ library Constants { uint256 internal constant AZTEC_MAX_EPOCH_DURATION = 32; uint256 internal constant GENESIS_ARCHIVE_ROOT = 19007378675971183768036762391356802220352606103602592933942074152320327194720; - uint256 internal constant FEE_JUICE_INITIAL_MINT = 200000000000000; + uint256 internal constant FEE_JUICE_INITIAL_MINT = 20000000000000000000; uint256 internal constant PUBLIC_DISPATCH_SELECTOR = 3578010381; uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 3000; uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000; diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index a53b8c10f62..85d684b77b4 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -75,6 +75,7 @@ library Errors { error Rollup__NonZeroDaFee(); // 0xd9c75f52 error Rollup__NonZeroL2Fee(); // 0x7e728abc error Rollup__InvalidBasisPointFee(uint256 basisPointFee); // 0x4292d136 + error Rollup__InvalidManaBaseFee(uint256 expected, uint256 actual); // 0x73b6d896 //TxsDecoder error TxsDecoder__InvalidLogsLength(uint256 expected, uint256 actual); // 0x829ca981 diff --git a/l1-contracts/src/core/libraries/HeaderLib.sol b/l1-contracts/src/core/libraries/HeaderLib.sol index 4f29c431039..3df6acfdc7a 100644 --- a/l1-contracts/src/core/libraries/HeaderLib.sol +++ b/l1-contracts/src/core/libraries/HeaderLib.sol @@ -11,6 +11,24 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; * @notice Decoding and validating an L2 block header * Concerned with readability and velocity of development not giving a damn about gas costs. * + * ,ggg, ,ggg,_,ggg, ,ggg, gg + * I8 I8 ,dPYb,dP""Y8dP""Y88P""Y8b dP""Y8a 88 8I ,dPYb, ,dPYb, + * I8 I8 IP'`YbYb, `88' `88' `88 Yb, `88 88 8I IP'`Yb IP'`Yb + * 88888888 88888888 I8 8I `" 88 88 88 `" 88 88 8I gg I8 8I I8 8I + * I8 I8 I8 8' 88 88 88 88 88 8I "" I8 8' I8 8bgg, + * I8 ,ggggg, I8 ,gggg,gg I8 dP 88 88 88 ,gggg,gg ,ggg,,ggg, ,gggg,gg 88 88 ,g, ,ggg, ,gggg,8I gg ,g, I8 dP ,gggg,gg I8 dP" "8 ,ggg, + * I8 dP" "Y8ggg I8 dP" "Y8I I8dP 88 88 88 dP" "Y8I ,8" "8P" "8, dP" "Y8I 88 88 ,8'8, i8" "8i dP" "Y8I 88 ,8'8, I8dP dP" "Y8I I8d8bggP" i8" "8i + * ,I8, i8' ,8I ,I8, i8' ,8I I8P 88 88 88 i8' ,8I I8 8I 8I i8' ,8I 88 88 ,8' Yb I8, ,8I i8' ,8I 88 ,8' Yb I8P i8' ,8I I8P' "Yb, I8, ,8I + * ,d88b, ,d8, ,d8' ,d88b, ,d8, ,d8b,,d8b,_ 88 88 Y8,,d8, ,d8b,,dP 8I Yb,,d8, ,d8b, Y8b,____,d88,,8'_ 8) `YbadP' ,d8, ,d8b, _,88,_,8'_ 8) ,d8b,_ ,d8, ,d8b,,d8 `Yb, `YbadP' + * 88P""Y88P"Y8888P" 88P""Y88P"Y8888P"`Y88P'"Y88 88 88 `Y8P"Y8888P"`Y88P' 8I `Y8P"Y8888P"`Y8 "Y888888P"Y8P' "YY8P8P888P"Y888P"Y8888P"`Y8 8P""Y8P' "YY8P8P PI8"8888P"Y8888P"`Y888P Y8888P"Y888 + * I8 `8, + * I8 `8, + * The `totalManaUsed` value is not yet part of the "real" header, but can be passed by extending + * the header struct with an additional 256-bit value. + * It will be better supported as part of #9716, right now there is a few hacks going on in here. + * + * + * * ------------------- * You can use https://gist.github.com/LHerskind/724a7e362c97e8ac2902c6b961d36830 to generate the below outline. * ------------------- @@ -52,6 +70,7 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; * | | | } * | | | } * | 0x0248 | 0x20 | total_fees + * | 0x0268 | 0x20 | total_mana_used * | --- | --- | --- */ library HeaderLib { @@ -102,6 +121,7 @@ library HeaderLib { StateReference stateReference; GlobalVariables globalVariables; uint256 totalFees; + uint256 totalManaUsed; } uint256 private constant HEADER_LENGTH = 0x268; // Header byte length @@ -112,8 +132,9 @@ library HeaderLib { * @return The decoded header */ function decode(bytes calldata _header) internal pure returns (Header memory) { + bool hasTotalManaUsed = _header.length == HEADER_LENGTH + 0x20; require( - _header.length == HEADER_LENGTH, + _header.length == HEADER_LENGTH || hasTotalManaUsed, Errors.HeaderLib__InvalidHeaderSize(HEADER_LENGTH, _header.length) ); @@ -158,6 +179,10 @@ library HeaderLib { // Reading totalFees header.totalFees = uint256(bytes32(_header[0x0248:0x0268])); + if (hasTotalManaUsed) { + header.totalManaUsed = uint256(bytes32(_header[0x0268:0x0288])); + } + return header; } diff --git a/l1-contracts/src/core/libraries/ProposeLib.sol b/l1-contracts/src/core/libraries/ProposeLib.sol index 6abffad0939..ab5330661f7 100644 --- a/l1-contracts/src/core/libraries/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/ProposeLib.sol @@ -3,10 +3,12 @@ pragma solidity >=0.8.27; import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {OracleInput} from "@aztec/core/libraries/FeeMath.sol"; struct ProposeArgs { bytes32 archive; bytes32 blockHash; + OracleInput oracleInput; bytes header; bytes32[] txHashes; } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 740f361b6b1..3c7883aa54d 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -14,6 +14,7 @@ import {Registry} from "@aztec/governance/Registry.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {BlockLog} from "@aztec/core/interfaces/IRollup.sol"; import {Rollup} from "./harnesses/Rollup.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; @@ -26,7 +27,7 @@ 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, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; +import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; import { Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeFns @@ -260,21 +261,28 @@ contract RollupTest is DecoderBase, TimeFns { // We jump to the time of the block. (unless it is in the past) vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); - ProposeArgs memory args = - ProposeArgs({header: header, archive: archive, blockHash: blockHash, txHashes: txHashes}); + header = _updateHeaderBaseFee(header); + + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + blockHash: blockHash, + oracleInput: OracleInput(0, 0), + txHashes: txHashes + }); rollup.propose(args, signatures, body); quote.epochToProve = Epoch.wrap(1); quote.validUntilSlot = toSlots(Epoch.wrap(2)); signedQuote = _quoteToSignedQuote(quote); rollup.claimEpochProofRight(signedQuote); - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); + BlockLog memory blockLog = rollup.getBlock(0); assertEq( proofCommitmentEscrow.deposits(quote.prover), quote.bondAmount * 9, "Invalid escrow balance" ); - _submitEpochProof(rollup, 1, preArchive, archive, preBlockHash, blockHash, proverId); + _submitEpochProof(rollup, 1, blockLog.archive, archive, blockLog.blockHash, blockHash, proverId); assertEq( proofCommitmentEscrow.deposits(quote.prover), quote.bondAmount * 10, "Invalid escrow balance" @@ -428,15 +436,22 @@ contract RollupTest is DecoderBase, TimeFns { // We jump to the time of the block. (unless it is in the past) vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); - ProposeArgs memory args = - ProposeArgs({header: header, archive: archive, blockHash: blockHash, txHashes: txHashes}); + header = _updateHeaderBaseFee(header); + + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + blockHash: blockHash, + oracleInput: OracleInput(0, 0), + txHashes: txHashes + }); rollup.propose(args, signatures, body); - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); - _submitEpochProof(rollup, 1, preArchive, archive, preBlockHash, blockHash, proverId); + BlockLog memory blockLog = rollup.getBlock(0); + _submitEpochProof(rollup, 1, blockLog.archive, archive, blockLog.blockHash, blockHash, proverId); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidBlockNumber.selector, 1, 2)); - _submitEpochProof(rollup, 1, preArchive, archive, preBlockHash, blockHash, proverId); + _submitEpochProof(rollup, 1, blockLog.archive, archive, blockLog.blockHash, blockHash, proverId); } function testTimestamp() public setUpFor("mixed_block_1") { @@ -471,8 +486,8 @@ contract RollupTest is DecoderBase, TimeFns { // Even if we end up reverting block 1, we should still see the same root in the inbox. bytes32 inboxRoot2 = inbox.getRoot(2); - (,, Slot slot) = rollup.blocks(1); - Slot prunableAt = slot + toSlots(Epoch.wrap(2)); + BlockLog memory blockLog = rollup.getBlock(1); + Slot prunableAt = blockLog.slotNumber + toSlots(Epoch.wrap(2)); Timestamp timeOfPrune = rollup.getTimestampForSlot(prunableAt); vm.warp(Timestamp.unwrap(timeOfPrune)); @@ -566,6 +581,7 @@ contract RollupTest is DecoderBase, TimeFns { header: header, archive: data.archive, blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), txHashes: txHashes }); rollup.propose(args, signatures, data.body); @@ -590,13 +606,14 @@ contract RollupTest is DecoderBase, TimeFns { header: header, archive: data.archive, blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), txHashes: txHashes }); rollup.propose(args, signatures, data.body); } function testBlockFee() public setUpFor("mixed_block_1") { - uint256 feeAmount = 2e18; + uint256 feeAmount = Constants.FEE_JUICE_INITIAL_MINT + 0.5e18; DecoderBase.Data memory data = load("mixed_block_1").block; bytes32[] memory txHashes = new bytes32[](0); @@ -620,18 +637,21 @@ contract RollupTest is DecoderBase, TimeFns { uint256 coinbaseBalance = testERC20.balanceOf(coinbase); assertEq(coinbaseBalance, 0, "invalid initial coinbase balance"); + header = _updateHeaderBaseFee(header); + // Assert that balance have NOT been increased by proposing the block ProposeArgs memory args = ProposeArgs({ header: header, archive: data.archive, blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), txHashes: txHashes }); rollup.propose(args, signatures, data.body); assertEq(testERC20.balanceOf(coinbase), 0, "invalid coinbase balance"); } - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); + BlockLog memory blockLog = rollup.getBlock(0); quote.epochToProve = Epoch.wrap(1); quote.validUntilSlot = toSlots(Epoch.wrap(2)); @@ -652,9 +672,9 @@ contract RollupTest is DecoderBase, TimeFns { _submitEpochProofWithFee( rollup, 1, - preArchive, + blockLog.archive, data.archive, - preBlockHash, + blockLog.blockHash, data.blockHash, bytes32(uint256(42)), coinbase, @@ -671,9 +691,9 @@ contract RollupTest is DecoderBase, TimeFns { _submitEpochProofWithFee( rollup, 1, - preArchive, + blockLog.archive, data.archive, - preBlockHash, + blockLog.blockHash, data.blockHash, bytes32(uint256(42)), coinbase, @@ -713,8 +733,10 @@ contract RollupTest is DecoderBase, TimeFns { DecoderBase.Data memory data = load("mixed_block_2").block; assertEq(rollup.getProvenBlockNumber(), 0, "Invalid initial proven block number"); - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); - _submitEpochProof(rollup, 2, preArchive, data.archive, preBlockHash, data.blockHash, bytes32(0)); + BlockLog memory blockLog = rollup.getBlock(0); + _submitEpochProof( + rollup, 2, blockLog.archive, data.archive, blockLog.blockHash, data.blockHash, bytes32(0) + ); assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), 2, "Invalid proven block number"); @@ -729,18 +751,19 @@ contract RollupTest is DecoderBase, TimeFns { vm.warp(max(block.timestamp, data2.decodedHeader.globalVariables.timestamp)); ProposeArgs memory args = ProposeArgs({ - header: data2.header, + header: _updateHeaderBaseFee(data2.header), archive: data2.archive, blockHash: data2.blockHash, + oracleInput: OracleInput(0, 0), txHashes: txHashes }); rollup.propose(args, signatures, data2.body); // Skips proving of block 1 - (bytes32 preArchive,,) = rollup.blocks(0); + BlockLog memory blockLog = rollup.getBlock(0); vm.expectRevert( abi.encodeWithSelector( - Errors.Rollup__InvalidPreviousArchive.selector, preArchive, data1.archive + Errors.Rollup__InvalidPreviousArchive.selector, blockLog.archive, data1.archive ) ); _submitEpochProof( @@ -779,8 +802,13 @@ contract RollupTest is DecoderBase, TimeFns { } vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidBlockNumber.selector, 1, 0x420)); - ProposeArgs memory args = - ProposeArgs({header: header, archive: archive, blockHash: data.blockHash, txHashes: txHashes}); + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), + txHashes: txHashes + }); rollup.propose(args, signatures, body); } @@ -796,8 +824,13 @@ contract RollupTest is DecoderBase, TimeFns { } vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidChainId.selector, 31337, 0x420)); - ProposeArgs memory args = - ProposeArgs({header: header, archive: archive, blockHash: data.blockHash, txHashes: txHashes}); + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), + txHashes: txHashes + }); rollup.propose(args, signatures, body); } @@ -813,8 +846,13 @@ contract RollupTest is DecoderBase, TimeFns { } vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidVersion.selector, 1, 0x420)); - ProposeArgs memory args = - ProposeArgs({header: header, archive: archive, blockHash: data.blockHash, txHashes: txHashes}); + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), + txHashes: txHashes + }); rollup.propose(args, signatures, body); } @@ -835,8 +873,13 @@ contract RollupTest is DecoderBase, TimeFns { } vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidTimestamp.selector, realTs, badTs)); - ProposeArgs memory args = - ProposeArgs({header: header, archive: archive, blockHash: data.blockHash, txHashes: txHashes}); + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: archive, + blockHash: data.blockHash, + oracleInput: OracleInput(0, 0), + txHashes: txHashes + }); rollup.propose(args, signatures, body); } @@ -868,12 +911,16 @@ contract RollupTest is DecoderBase, TimeFns { _testBlock("empty_block_1", false); DecoderBase.Data memory data = load("empty_block_1").block; - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); + BlockLog memory blockLog = rollup.getBlock(0); bytes32 wrong = bytes32(uint256(0xdeadbeef)); vm.expectRevert( - abi.encodeWithSelector(Errors.Rollup__InvalidPreviousArchive.selector, preArchive, wrong) + abi.encodeWithSelector( + Errors.Rollup__InvalidPreviousArchive.selector, blockLog.archive, wrong + ) + ); + _submitEpochProof( + rollup, 1, wrong, data.archive, blockLog.blockHash, data.blockHash, bytes32(0) ); - _submitEpochProof(rollup, 1, wrong, data.archive, preBlockHash, data.blockHash, bytes32(0)); // TODO: Reenable when we setup proper initial block hash // vm.expectRevert( @@ -888,11 +935,13 @@ contract RollupTest is DecoderBase, TimeFns { DecoderBase.Data memory data = load("empty_block_1").block; bytes32 wrongArchive = bytes32(uint256(0xdeadbeef)); - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); + BlockLog memory blockLog = rollup.getBlock(0); vm.expectRevert( abi.encodeWithSelector(Errors.Rollup__InvalidArchive.selector, data.archive, 0xdeadbeef) ); - _submitEpochProof(rollup, 1, preArchive, wrongArchive, preBlockHash, data.blockHash, bytes32(0)); + _submitEpochProof( + rollup, 1, blockLog.archive, wrongArchive, blockLog.blockHash, data.blockHash, bytes32(0) + ); } function testSubmitProofInvalidBlockHash() public setUpFor("empty_block_1") { @@ -901,19 +950,29 @@ contract RollupTest is DecoderBase, TimeFns { DecoderBase.Data memory data = load("empty_block_1").block; bytes32 wrongBlockHash = bytes32(uint256(0xdeadbeef)); - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); + BlockLog memory blockLog = rollup.getBlock(0); vm.expectRevert( abi.encodeWithSelector( Errors.Rollup__InvalidBlockHash.selector, data.blockHash, wrongBlockHash ) ); - _submitEpochProof(rollup, 1, preArchive, data.archive, preBlockHash, wrongBlockHash, bytes32(0)); + _submitEpochProof( + rollup, 1, blockLog.archive, data.archive, blockLog.blockHash, wrongBlockHash, bytes32(0) + ); } function _testBlock(string memory name, bool _submitProof) public { _testBlock(name, _submitProof, 0); } + function _updateHeaderBaseFee(bytes memory _header) internal view returns (bytes memory) { + uint256 baseFee = rollup.getManaBaseFee(true); + assembly { + mstore(add(_header, add(0x20, 0x0228)), baseFee) + } + return _header; + } + function _testBlock(string memory name, bool _submitProof, uint256 _slotNumber) public { DecoderBase.Full memory full = load(name); bytes memory header = full.block.header; @@ -939,20 +998,29 @@ contract RollupTest is DecoderBase, TimeFns { _populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content); + header = _updateHeaderBaseFee(header); + ProposeArgs memory args = ProposeArgs({ header: header, archive: full.block.archive, blockHash: full.block.blockHash, + oracleInput: OracleInput(0, 0), txHashes: txHashes }); rollup.propose(args, signatures, full.block.body); if (_submitProof) { uint256 pre = rollup.getProvenBlockNumber(); - (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(pre); + BlockLog memory blockLog = rollup.getBlock(pre); _submitEpochProof( - rollup, 1, preArchive, args.archive, preBlockHash, full.block.blockHash, bytes32(0) + rollup, + 1, + blockLog.archive, + args.archive, + blockLog.blockHash, + full.block.blockHash, + bytes32(0) ); assertEq(pre + 1, rollup.getProvenBlockNumber(), "Block not proven"); } diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol new file mode 100644 index 00000000000..0f38f8e1a43 --- /dev/null +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +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 {Math} from "@oz/utils/math/Math.sol"; + +import {Registry} from "@aztec/governance/Registry.sol"; +import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; +import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import { + Rollup, + Config, + BlockLog, + L1FeeData, + FeeHeader, + ManaBaseFeeComponents +} from "@aztec/core/Rollup.sol"; +import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; +import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; +import {Leonidas} from "@aztec/core/Leonidas.sol"; +import {NaiveMerkle} from "../merkle/Naive.sol"; +import {MerkleTestUtil} from "../merkle/TestUtil.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +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 {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 { + FeeHeader as FeeHeaderModel, + ManaBaseFeeComponents as ManaBaseFeeComponentsModel +} from "./FeeModelTestPoints.t.sol"; + +import { + Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeFns +} from "@aztec/core/libraries/TimeMath.sol"; + +import {FeeModelTestPoints, TestPoint} from "./FeeModelTestPoints.t.sol"; +import {MinimalFeeModel} from "./MinimalFeeModel.sol"; +// solhint-disable comprehensive-interface + +contract FakeCanonical { + function canonicalRollup() external view returns (address) { + return msg.sender; + } + + function UNDERLYING() external pure returns (address) { + return address(0); + } +} + +contract FeeRollupTest is FeeModelTestPoints, DecoderBase { + using SlotLib for Slot; + // We need to build a block that we can submit. We will be using some values from + // the empty blocks, but otherwise populate using the fee model test points. + + struct Block { + bytes32 archive; + bytes32 blockHash; + bytes header; + bytes body; + bytes32[] txHashes; + SignatureLib.Signature[] signatures; + } + + DecoderBase.Full full = load("empty_block_1"); + + uint256 internal constant SLOT_DURATION = 36; + uint256 internal constant EPOCH_DURATION = 32; + + Rollup internal rollup; + + function setUp() public { + // We deploy a the rollup and sets the time and all to + + vm.warp(l1Metadata[0].timestamp - SLOT_DURATION); + vm.fee(l1Metadata[0].base_fee); + vm.blobBaseFee(l1Metadata[0].blob_fee); + + FakeCanonical fakeCanonical = new FakeCanonical(); + rollup = new Rollup( + IFeeJuicePortal(address(fakeCanonical)), + IRewardDistributor(address(fakeCanonical)), + bytes32(0), + bytes32(0), + address(this), + new address[](0), + Config({ + aztecSlotDuration: SLOT_DURATION, + aztecEpochDuration: EPOCH_DURATION, + targetCommitteeSize: 48, + aztecEpochProofClaimWindowInL2Slots: 16 + }) + ); + } + + function _loadL1Metadata(uint256 index) internal { + vm.roll(l1Metadata[index].block_number); + vm.warp(l1Metadata[index].timestamp); + vm.fee(l1Metadata[index].base_fee); + vm.blobBaseFee(l1Metadata[index].blob_fee); + } + + /** + * @notice Constructs a fake block that is not possible to prove, but passes the L1 checks. + */ + function getBlock() internal view returns (Block memory) { + // We will be using the genesis for both before and after. This will be impossible + // to prove, but we don't need to prove anything here. + bytes32 archiveRoot = bytes32(Constants.GENESIS_ARCHIVE_ROOT); + bytes32 blockHash = 0x267f79fe7e757b20e924fac9f78264a0d1c8c4b481fea21d0bbe74650d87a1f1; + + bytes32[] memory txHashes = new bytes32[](0); + SignatureLib.Signature[] memory signatures = new SignatureLib.Signature[](0); + + bytes memory body = full.block.body; + bytes memory header = full.block.header; + + Slot slotNumber = rollup.getCurrentSlot(); + TestPoint memory point = points[slotNumber.unwrap() - 1]; + + Timestamp ts = rollup.getTimestampForSlot(slotNumber); + uint256 bn = rollup.getPendingBlockNumber() + 1; + + uint256 manaBaseFee = ( + point.outputs.mana_base_fee_components_in_fee_asset.data_cost + + point.outputs.mana_base_fee_components_in_fee_asset.gas_cost + + point.outputs.mana_base_fee_components_in_fee_asset.proving_cost + + point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost + ); + + assertEq(manaBaseFee, rollup.getManaBaseFee(true), "mana base fee mismatch"); + + // Updating the header with important information! + assembly { + let headerRef := add(header, 0x20) + + mstore(add(headerRef, 0x0000), archiveRoot) + // Load the full word at 0x20 (which contains lastArchive.nextAvailableLeafIndex and start of numTxs) + let word := mload(add(headerRef, 0x20)) + // Clear just the first 4 bytes from the left (most significant bytes) + word := and(word, 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + // Set the new value for nextAvailableLeafIndex (bn) in the first 4 bytes from left + word := or(word, shl(224, bn)) + // Store the modified word back + mstore(add(headerRef, 0x20), word) + + mstore(add(headerRef, 0x0174), bn) + mstore(add(headerRef, 0x0194), slotNumber) + mstore(add(headerRef, 0x01b4), ts) + mstore(add(headerRef, 0x01d4), 0) + mstore(add(headerRef, 0x01e8), 0) + mstore(add(headerRef, 0x0208), 0) + mstore(add(headerRef, 0x0228), manaBaseFee) + } + + // We extend the with 20 bytes of mana spent information. + header = bytes.concat(header, bytes32(point.block_header.mana_spent)); + + return Block({ + archive: archiveRoot, + blockHash: blockHash, + header: header, + body: body, + txHashes: txHashes, + signatures: signatures + }); + } + + function test_BigBrainTime() public { + rollup.setAssumeProvenThroughBlockNumber(10000); + + Slot nextSlot = Slot.wrap(1); + + // Loop through all of the L1 metadata + for (uint256 i = 0; i < l1Metadata.length; i++) { + _loadL1Metadata(i); + + // For every "new" slot we encounter, we construct a block using current L1 Data + // and part of the `empty_block_1.json` file. The block cannot be proven, but it + // will be accepted as a proposal so very useful for testing a long range of blocks. + if (rollup.getCurrentSlot() == nextSlot) { + TestPoint memory point = points[nextSlot.unwrap() - 1]; + + L1FeeData memory fees = rollup.getCurrentL1Fees(); + uint256 feeAssetPrice = rollup.getFeeAssetPrice(); + + ManaBaseFeeComponents memory components = rollup.manaBaseFeeComponents(false); + ManaBaseFeeComponents memory componentsFeeAsset = rollup.manaBaseFeeComponents(true); + BlockLog memory parentBlockLog = rollup.getBlock(nextSlot.unwrap() - 1); + + Block memory b = getBlock(); + + rollup.propose( + ProposeArgs({ + header: b.header, + archive: b.archive, + blockHash: b.blockHash, + oracleInput: OracleInput({ + provingCostModifier: point.oracle_input.proving_cost_modifier, + feeAssetPriceModifier: point.oracle_input.fee_asset_price_modifier + }), + txHashes: b.txHashes + }), + b.signatures, + b.body + ); + + BlockLog memory blockLog = rollup.getBlock(nextSlot.unwrap()); + + // Want to check the fee header to see if they are as we want them. + + assertEq(point.block_header.block_number, nextSlot, "invalid l2 block number"); + assertEq(point.block_header.l1_block_number, block.number, "invalid l1 block number"); + assertEq(point.block_header.slot_number, nextSlot, "invalid l2 slot number"); + assertEq(point.block_header.timestamp, block.timestamp, "invalid timestamp"); + + assertEq(point.fee_header, blockLog.feeHeader); + + assertEq( + point.outputs.fee_asset_price_at_execution, feeAssetPrice, "fee asset price mismatch" + ); + assertEq(point.outputs.l1_fee_oracle_output.base_fee, fees.baseFee, "base fee mismatch"); + assertEq(point.outputs.l1_fee_oracle_output.blob_fee, fees.blobFee, "blob fee mismatch"); + + assertEq(point.outputs.mana_base_fee_components_in_wei, components); + assertEq(point.outputs.mana_base_fee_components_in_fee_asset, componentsFeeAsset); + + assertEq(point.parent_fee_header, parentBlockLog.feeHeader); + + nextSlot = nextSlot + Slot.wrap(1); + } + } + } + + function assertEq(FeeHeaderModel memory a, FeeHeader memory b) internal pure { + FeeHeaderModel memory bModel = FeeHeaderModel({ + excess_mana: b.excessMana, + fee_asset_price_numerator: b.feeAssetPriceNumerator, + mana_used: b.manaUsed, + proving_cost_per_mana_numerator: b.provingCostPerManaNumerator + }); + assertEq(a, bModel); + } + + function assertEq(ManaBaseFeeComponentsModel memory a, ManaBaseFeeComponents memory b) + internal + pure + { + ManaBaseFeeComponentsModel memory bModel = ManaBaseFeeComponentsModel({ + congestion_cost: b.congestionCost, + congestion_multiplier: b.congestionMultiplier, + data_cost: b.dataCost, + gas_cost: b.gasCost, + proving_cost: b.provingCost + }); + assertEq(a, bModel); + } +} diff --git a/l1-contracts/test/harnesses/Rollup.sol b/l1-contracts/test/harnesses/Rollup.sol index 7897a61ad17..27f78d3864d 100644 --- a/l1-contracts/test/harnesses/Rollup.sol +++ b/l1-contracts/test/harnesses/Rollup.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; -import {Rollup as RealRollup} from "@aztec/core/Rollup.sol"; +import {Rollup as RealRollup, Config} from "@aztec/core/Rollup.sol"; import {TestConstants} from "./TestConstants.sol"; contract Rollup is RealRollup { @@ -23,7 +23,7 @@ contract Rollup is RealRollup { _protocolContractTreeRoot, _ares, _validators, - RealRollup.Config({ + Config({ aztecSlotDuration: TestConstants.AZTEC_SLOT_DURATION, aztecEpochDuration: TestConstants.AZTEC_EPOCH_DURATION, targetCommitteeSize: TestConstants.AZTEC_TARGET_COMMITTEE_SIZE, diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 1d5642e60fa..d94238a3a4f 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -20,7 +20,7 @@ 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, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; +import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol"; import {Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; @@ -186,6 +186,7 @@ contract SpartaTest is DecoderBase { header: header, archive: full.block.archive, blockHash: bytes32(0), + oracleInput: OracleInput(0, 0), txHashes: txHashes }); diff --git a/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr index 065427f87d1..5742b3cbd83 100644 --- a/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr @@ -6,7 +6,7 @@ use dep::aztec::macros::aztec; contract FeeJuice { use dep::aztec::{ macros::{functions::{internal, private, public, view}, storage::storage}, - protocol_types::{address::{AztecAddress, EthAddress}, constants::FEE_JUICE_INITIAL_MINT}, + protocol_types::address::{AztecAddress, EthAddress}, state_vars::{Map, PublicMutable, SharedImmutable}, }; @@ -23,14 +23,12 @@ contract FeeJuice { // Not flagged as initializer to reduce cost of checking init nullifier in all functions. // This function should be called as entrypoint to initialize the contract by minting itself funds. #[private] - fn initialize(portal_address: EthAddress) { + fn initialize(portal_address: EthAddress, initial_mint: Field) { // Validate contract class parameters are correct let self = context.this_address(); // Increase self balance and set as fee payer, and end setup - FeeJuice::at(self)._increase_public_balance(self, FEE_JUICE_INITIAL_MINT).enqueue( - &mut context, - ); + FeeJuice::at(self)._increase_public_balance(self, initial_mint).enqueue(&mut context); context.set_as_fee_payer(); context.end_setup(); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index fcf4127112f..f3b07779b6c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -138,7 +138,7 @@ pub global GENESIS_ARCHIVE_ROOT: Field = 0x2a05cb8aeefe9b9797f90650eae072f5ab7437807e62f9724ce1900467779860; // The following and the value in `deploy_l1_contracts` must match. We should not have the code both places, but // we are running into circular dependency issues. #3342 -global FEE_JUICE_INITIAL_MINT: Field = 200000000000000; +global FEE_JUICE_INITIAL_MINT: Field = 20000000000000000000; // Last 4 bytes of the Poseidon2 hash of 'public_dispatch(Field)'. pub global PUBLIC_DISPATCH_SELECTOR: Field = 0xd5441b0d; diff --git a/yarn-project/archiver/src/archiver/archiver.test.ts b/yarn-project/archiver/src/archiver/archiver.test.ts index b542ee2475f..33255e88afa 100644 --- a/yarn-project/archiver/src/archiver/archiver.test.ts +++ b/yarn-project/archiver/src/archiver/archiver.test.ts @@ -456,7 +456,11 @@ function makeRollupTx(l2Block: L2Block) { const input = encodeFunctionData({ abi: RollupAbi, functionName: 'propose', - args: [{ header, archive, blockHash, txHashes: [] }, [], body], + args: [ + { header, archive, blockHash, oracleInput: { provingCostModifier: 0n, feeAssetPriceModifier: 0n }, txHashes: [] }, + [], + body, + ], }); return { input } as Transaction; } diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index ce3c6cadbd5..d6ce1653c4c 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -144,6 +144,10 @@ async function getBlockFromRollupTx( header: Hex; archive: Hex; blockHash: Hex; + oracleInput: { + provingCostModifier: bigint; + feeAssetPriceModifier: bigint; + }; txHashes: Hex[]; }, ViemSignature[], diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 279638eb734..936e134eeb9 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -42,6 +42,7 @@ import { type ContractInstanceWithAddress, EthAddress, Fr, + type GasFees, type Header, INITIAL_L2_BLOCK_NUM, type L1_TO_L2_MSG_TREE_HEIGHT, @@ -258,6 +259,14 @@ export class AztecNodeService implements AztecNode { return (await this.blockSource.getBlocks(from, limit)) ?? []; } + /** + * Method to fetch the current base fees. + * @returns The current base fees. + */ + public async getCurrentBaseFees(): Promise { + return await this.globalVariableBuilder.getCurrentBaseFees(); + } + /** * Method to fetch the current block number. * @returns The block number. diff --git a/yarn-project/aztec.js/src/utils/anvil_test_watcher.ts b/yarn-project/aztec.js/src/utils/anvil_test_watcher.ts index 859799fd130..79f4705449b 100644 --- a/yarn-project/aztec.js/src/utils/anvil_test_watcher.ts +++ b/yarn-project/aztec.js/src/utils/anvil_test_watcher.ts @@ -62,9 +62,9 @@ export class AnvilTestWatcher { try { const currentSlot = await this.rollup.read.getCurrentSlot(); const pendingBlockNumber = BigInt(await this.rollup.read.getPendingBlockNumber()); - const [, , lastSlotNumber] = await this.rollup.read.blocks([pendingBlockNumber]); + const blockLog = await this.rollup.read.getBlock([pendingBlockNumber]); - if (currentSlot === lastSlotNumber) { + if (currentSlot === blockLog.slotNumber) { // We should jump to the next slot const timestamp = await this.rollup.read.getTimestampForSlot([currentSlot + 1n]); try { diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 94d7e479504..aaf2bea1954 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -26,6 +26,7 @@ import { type ContractClassWithId, type ContractInstanceWithAddress, type Fr, + type GasFees, type L1_TO_L2_MSG_TREE_HEIGHT, type NodeInfo, type PartialAddress, @@ -145,6 +146,9 @@ export abstract class BaseWallet implements Wallet { getBlock(number: number): Promise { return this.pxe.getBlock(number); } + getCurrentBaseFees(): Promise { + return this.pxe.getCurrentBaseFees(); + } simulateUnconstrained( functionName: string, args: any[], diff --git a/yarn-project/circuit-types/src/global_variable_builder.ts b/yarn-project/circuit-types/src/global_variable_builder.ts index 72ca71272c7..50b218b1236 100644 --- a/yarn-project/circuit-types/src/global_variable_builder.ts +++ b/yarn-project/circuit-types/src/global_variable_builder.ts @@ -1,9 +1,11 @@ -import type { AztecAddress, EthAddress, Fr, GlobalVariables } from '@aztec/circuits.js'; +import type { AztecAddress, EthAddress, Fr, GasFees, GlobalVariables } from '@aztec/circuits.js'; /** * Interface for building global variables for Aztec blocks. */ export interface GlobalVariableBuilder { + getCurrentBaseFees(): Promise; + /** * Builds global variables for a given block. * @param blockNumber - The block number to build global variables for. diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts index 5b32cdd1d1e..2f2b6a0ab06 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts @@ -5,6 +5,7 @@ import { type ContractInstanceWithAddress, EthAddress, Fr, + GasFees, Header, L1_TO_L2_MSG_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT, @@ -159,6 +160,11 @@ describe('AztecNodeApiSchema', () => { expect(response).toBeInstanceOf(L2Block); }); + it('getCurrentBaseFees', async () => { + const response = await context.client.getCurrentBaseFees(); + expect(response).toEqual(GasFees.default()); + }); + it('getBlockNumber', async () => { const response = await context.client.getBlockNumber(); expect(response).toBe(1); @@ -435,6 +441,9 @@ class MockAztecNode implements AztecNode { getBlock(number: number): Promise { return Promise.resolve(L2Block.random(number)); } + getCurrentBaseFees(): Promise { + return Promise.resolve(GasFees.default()); + } getBlockNumber(): Promise { return Promise.resolve(1); } diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index deeae772391..34f9383c83c 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -4,6 +4,7 @@ import { ContractClassPublicSchema, type ContractInstanceWithAddress, ContractInstanceWithAddressSchema, + GasFees, Header, L1_TO_L2_MSG_TREE_HEIGHT, NOTE_HASH_TREE_HEIGHT, @@ -226,6 +227,12 @@ export interface AztecNode */ getBlocks(from: number, limit: number): Promise; + /** + * Method to fetch the current base fees. + * @returns The current base fees. + */ + getCurrentBaseFees(): Promise; + /** * Method to fetch the version of the package. * @returns The node package version @@ -492,6 +499,8 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getBlocks: z.function().args(z.number(), z.number()).returns(z.array(L2Block.schema)), + getCurrentBaseFees: z.function().returns(GasFees.schema), + getNodeVersion: z.function().returns(z.string()), getVersion: z.function().returns(z.number()), diff --git a/yarn-project/circuit-types/src/interfaces/pxe.test.ts b/yarn-project/circuit-types/src/interfaces/pxe.test.ts index e2aa6c1cca5..62510642101 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.test.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.test.ts @@ -6,6 +6,7 @@ import { type ContractInstanceWithAddress, EthAddress, Fr, + GasFees, L1_TO_L2_MSG_TREE_HEIGHT, type NodeInfo, Point, @@ -218,6 +219,11 @@ describe('PXESchema', () => { expect(result).toBeInstanceOf(L2Block); }); + it('getCurrentBaseFees', async () => { + const result = await context.client.getCurrentBaseFees(); + expect(result).toEqual(GasFees.default()); + }); + it('simulateUnconstrained', async () => { const result = await context.client.simulateUnconstrained('function', [], address, address, [address]); expect(result).toEqual(10n); @@ -443,6 +449,9 @@ class MockPXE implements PXE { getBlock(number: number): Promise { return Promise.resolve(L2Block.random(number)); } + getCurrentBaseFees(): Promise { + return Promise.resolve(GasFees.default()); + } simulateUnconstrained( _functionName: string, _args: any[], diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index a2a6f6940fe..0ff0f322ecf 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -6,6 +6,7 @@ import { type ContractInstanceWithAddress, ContractInstanceWithAddressSchema, type Fr, + GasFees, L1_TO_L2_MSG_TREE_HEIGHT, type NodeInfo, NodeInfoSchema, @@ -285,6 +286,12 @@ export interface PXE { */ getBlock(number: number): Promise; + /** + * Method to fetch the current base fees. + * @returns The current base fees. + */ + getCurrentBaseFees(): Promise; + /** * Simulate the execution of an unconstrained function on a deployed contract without actually modifying state. * This is useful to inspect contract state, for example fetching a variable value or calling a getter function. @@ -515,6 +522,8 @@ export const PXESchema: ApiSchemaFor = { .function() .args(z.number()) .returns(z.union([L2Block.schema, z.undefined()])), + getCurrentBaseFees: z.function().returns(GasFees.schema), + simulateUnconstrained: z .function() .args( diff --git a/yarn-project/circuit-types/src/p2p/consensus_payload.ts b/yarn-project/circuit-types/src/p2p/consensus_payload.ts index 8b020397e4d..a5ce2fbed50 100644 --- a/yarn-project/circuit-types/src/p2p/consensus_payload.ts +++ b/yarn-project/circuit-types/src/p2p/consensus_payload.ts @@ -25,11 +25,17 @@ export class ConsensusPayload implements Signable { } getPayloadToSign(domainSeperator: SignatureDomainSeperator): Buffer { - const abi = parseAbiParameters('uint8, (bytes32, bytes32, bytes, bytes32[])'); + const abi = parseAbiParameters('uint8, (bytes32, bytes32, (uint256, uint256), bytes, bytes32[])'); const txArray = this.txHashes.map(tx => tx.to0xString()); const encodedData = encodeAbiParameters(abi, [ domainSeperator, - [this.archive.toString(), this.header.hash().toString(), `0x${this.header.toString()}`, txArray], + [ + this.archive.toString(), + this.header.hash().toString(), + [0n, 0n] /* @todo See #9963 */, + `0x${this.header.toString()}`, + txArray, + ], ] as const); return Buffer.from(encodedData.slice(2), 'hex'); diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index ff6911cf4f3..a3a256a7d94 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -83,7 +83,7 @@ export const PRIVATE_LOG_SIZE_IN_BYTES = 576; export const BLOB_SIZE_IN_BYTES = 126976; export const AZTEC_MAX_EPOCH_DURATION = 32; export const GENESIS_ARCHIVE_ROOT = 19007378675971183768036762391356802220352606103602592933942074152320327194720n; -export const FEE_JUICE_INITIAL_MINT = 200000000000000; +export const FEE_JUICE_INITIAL_MINT = 20000000000000000000n; export const PUBLIC_DISPATCH_SELECTOR = 3578010381; export const MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 3000; export const MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000; diff --git a/yarn-project/cli-wallet/test/flows/profile.sh b/yarn-project/cli-wallet/test/flows/profile.sh index b00bffba73b..cca6b2eed1c 100755 --- a/yarn-project/cli-wallet/test/flows/profile.sh +++ b/yarn-project/cli-wallet/test/flows/profile.sh @@ -22,14 +22,14 @@ aztec-wallet send mint_to_private -ca token --args accounts:owner accounts:user # Create an authwit for the operator to transfer tokens from the user's account (to operator's own acc) aztec-wallet create-secret -a auth_nonce -aztec-wallet create-authwit transfer_from operator -ca token --args accounts:user accounts:operator 100 secrets:auth_nonce -f user +aztec-wallet create-authwit transfer_in_private operator -ca token --args accounts:user accounts:operator 100 secrets:auth_nonce -f user aztec-wallet add-authwit authwits:last user -f operator -# Simulate and profile `transfer_from` -aztec-wallet simulate --profile transfer_from -ca token --args accounts:user accounts:operator 100 secrets:auth_nonce -f operator +# Simulate and profile `transfer_in_private` +aztec-wallet simulate --profile transfer_in_private -ca token --args accounts:user accounts:operator 100 secrets:auth_nonce -f operator # Verify gate count is present in the output -GATE_COUNT=$(aztec-wallet simulate --profile transfer_from -ca token --args accounts:user accounts:operator 100 secrets:auth_nonce -f operator | grep "Total gates:" | awk '{print $3}') +GATE_COUNT=$(aztec-wallet simulate --profile transfer_in_private -ca token --args accounts:user accounts:operator 100 secrets:auth_nonce -f operator | grep "Total gates:" | awk '{print $3}') if [ -z "$GATE_COUNT" ]; then GATE_COUNT_SET=0 else diff --git a/yarn-project/cli/src/cmds/misc/setup_contracts.ts b/yarn-project/cli/src/cmds/misc/setup_contracts.ts index 88ec693d7c0..fd352bdf333 100644 --- a/yarn-project/cli/src/cmds/misc/setup_contracts.ts +++ b/yarn-project/cli/src/cmds/misc/setup_contracts.ts @@ -1,5 +1,5 @@ import { DefaultWaitOpts, type EthAddress, NoFeePaymentMethod, type Wallet } from '@aztec/aztec.js'; -import { GasSettings } from '@aztec/circuits.js'; +import { FEE_JUICE_INITIAL_MINT, GasSettings } from '@aztec/circuits.js'; import { type LogFn } from '@aztec/foundation/log'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; @@ -26,7 +26,7 @@ export async function setupCanonicalL2FeeJuice( if (portalAddress.isZero()) { log('setupCanonicalL2FeeJuice: Calling initialize on fee juice contract...'); await feeJuiceContract.methods - .initialize(feeJuicePortalAddress) + .initialize(feeJuicePortalAddress, FEE_JUICE_INITIAL_MINT) .send({ fee: { paymentMethod: new NoFeePaymentMethod(), gasSettings: GasSettings.teardownless() } }) .wait(waitOpts); } else { diff --git a/yarn-project/cli/src/cmds/pxe/get_current_base_fee.ts b/yarn-project/cli/src/cmds/pxe/get_current_base_fee.ts new file mode 100644 index 00000000000..1d64ad585f2 --- /dev/null +++ b/yarn-project/cli/src/cmds/pxe/get_current_base_fee.ts @@ -0,0 +1,8 @@ +import { createCompatibleClient } from '@aztec/aztec.js'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +export async function getCurrentBaseFee(rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { + const client = await createCompatibleClient(rpcUrl, debugLogger); + const fees = await client.getCurrentBaseFees(); + log(`Current fees: ${JSON.stringify(fees.toJSON())}`); +} diff --git a/yarn-project/cli/src/cmds/pxe/index.ts b/yarn-project/cli/src/cmds/pxe/index.ts index bc3e4969a88..ad1d9ed59c3 100644 --- a/yarn-project/cli/src/cmds/pxe/index.ts +++ b/yarn-project/cli/src/cmds/pxe/index.ts @@ -60,6 +60,15 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL await getBlock(options.rpcUrl, blockNumber, options.follow, debugLogger, log); }); + program + .command('get-current-base-fee') + .description('Gets the current base fee.') + .addOption(pxeOption) + .action(async options => { + const { getCurrentBaseFee } = await import('./get_current_base_fee.js'); + await getCurrentBaseFee(options.rpcUrl, debugLogger, log); + }); + program .command('get-contract-data') .description('Gets information about the Aztec contract deployed at the specified address.') diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index 7b47386c6ac..38ff078bee9 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -342,6 +342,7 @@ describe('L1Publisher integration', () => { const ts = (await publicClient.getBlock()).timestamp; const slot = await rollup.read.getSlotAt([ts + BigInt(config.ethereumSlotDuration)]); + const globalVariables = new GlobalVariables( new Fr(chainId), new Fr(config.version), @@ -350,7 +351,7 @@ describe('L1Publisher integration', () => { new Fr(await rollup.read.getTimestampForSlot([slot])), coinbase, feeRecipient, - GasFees.empty(), + new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFee([true]))), ); const block = await buildBlock(globalVariables, txs, currentL1ToL2Messages); @@ -392,6 +393,10 @@ describe('L1Publisher integration', () => { header: `0x${block.header.toBuffer().toString('hex')}`, archive: `0x${block.archive.root.toBuffer().toString('hex')}`, blockHash: `0x${block.header.hash().toBuffer().toString('hex')}`, + oracleInput: { + provingCostModifier: 0n, + feeAssetPriceModifier: 0n, + }, txHashes: [], }, [], @@ -457,7 +462,7 @@ describe('L1Publisher integration', () => { new Fr(await rollup.read.getTimestampForSlot([slot])), coinbase, feeRecipient, - GasFees.empty(), + new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFee([true]))), ); const block = await buildBlock(globalVariables, txs, l1ToL2Messages); prevHeader = block.header; @@ -491,6 +496,10 @@ describe('L1Publisher integration', () => { header: `0x${block.header.toBuffer().toString('hex')}`, archive: `0x${block.archive.root.toBuffer().toString('hex')}`, blockHash: `0x${block.header.hash().toBuffer().toString('hex')}`, + oracleInput: { + provingCostModifier: 0n, + feeAssetPriceModifier: 0n, + }, txHashes: [], }, [], @@ -529,7 +538,7 @@ describe('L1Publisher integration', () => { new Fr(await rollup.read.getTimestampForSlot([slot])), coinbase, feeRecipient, - GasFees.empty(), + new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFee([true]))), ); const block = await buildBlock(globalVariables, txs, l1ToL2Messages); prevHeader = block.header; diff --git a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts index 6685ff2f5c2..c36202f3bf8 100644 --- a/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/account_init.test.ts @@ -13,7 +13,7 @@ import { type Wallet, deriveKeys, } from '@aztec/aztec.js'; -import { type AztecAddress, type CompleteAddress, Fq, type GasSettings } from '@aztec/circuits.js'; +import { type AztecAddress, type CompleteAddress, Fq, GasSettings } from '@aztec/circuits.js'; import { type TokenContract as BananaCoin, type FPCContract, SchnorrAccountContract } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -76,7 +76,10 @@ describe('e2e_fees account_init', () => { bobsAddress = bobsCompleteAddress.address; bobsWallet = await bobsAccountManager.getWallet(); - gasSettings = t.gasSettings; + gasSettings = GasSettings.from({ + ...t.gasSettings, + maxFeesPerGas: await aliceWallet.getCurrentBaseFees(), + }); await bobsAccountManager.register(); await initBalances(); @@ -105,7 +108,7 @@ describe('e2e_fees account_init', () => { it('pays privately through an FPC', async () => { // Alice mints bananas to Bob - const mintedBananas = BigInt(1e12); + const mintedBananas = t.INITIAL_GAS_BALANCE; await t.mintPrivateBananas(mintedBananas, bobsAddress); // Bob deploys his account through the private FPC @@ -133,7 +136,7 @@ describe('e2e_fees account_init', () => { }); it('pays publicly through an FPC', async () => { - const mintedBananas = BigInt(1e12); + const mintedBananas = t.INITIAL_GAS_BALANCE; await bananaCoin.methods.mint_to_public(bobsAddress, mintedBananas).send().wait(); const paymentMethod = new PublicFeePaymentMethod(bananaCoin.address, bananaFPC.address, bobsWallet); diff --git a/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts b/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts index b567a385c43..96b69c84e75 100644 --- a/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/dapp_subscription.test.ts @@ -8,6 +8,7 @@ import { PublicFeePaymentMethod, SentTx, } from '@aztec/aztec.js'; +import { GasSettings } from '@aztec/circuits.js'; import { DefaultDappEntrypoint } from '@aztec/entrypoints/dapp'; import { type AppSubscriptionContract, @@ -40,6 +41,7 @@ describe('e2e_fees dapp_subscription', () => { let initialFPCGasBalance: bigint; let initialBananasPublicBalances: Balances; // alice, bob, fpc let initialBananasPrivateBalances: Balances; // alice, bob, fpc + let gasSettings: GasSettings; const t = new FeesTest('dapp_subscription'); @@ -90,6 +92,11 @@ describe('e2e_fees dapp_subscription', () => { }); beforeEach(async () => { + gasSettings = GasSettings.from({ + ...t.gasSettings, + maxFeesPerGas: await aliceWallet.getCurrentBaseFees(), + }); + [initialSubscriptionContractGasBalance, initialSequencerGasBalance, initialFPCGasBalance] = (await t.getGasBalanceFn(subscriptionContract, sequencerAddress, bananaFPC)) as Balances; initialBananasPublicBalances = (await t.getBananaPublicBalanceFn(aliceAddress, bobAddress, bananaFPC)) as Balances; @@ -165,6 +172,7 @@ describe('e2e_fees dapp_subscription', () => { it('should call dapp subscription entrypoint', async () => { // Subscribe again, so this test does not depend on the previous ones being run. + await subscribe(new PrivateFeePaymentMethod(bananaCoin.address, bananaFPC.address, aliceWallet, feeRecipient)); expect(await subscriptionContract.methods.is_initialized(aliceAddress).simulate()).toBe(true); @@ -173,9 +181,13 @@ describe('e2e_fees dapp_subscription', () => { // Emitting the outgoing logs to Alice below const action = counterContract.methods.increment(bobAddress, aliceAddress).request(); const txExReq = await dappPayload.createTxExecutionRequest({ calls: [action] }); + const txSimulationResult = await pxe.simulateTx(txExReq, true); + const txProvingResult = await pxe.proveTx(txExReq, txSimulationResult.privateExecutionResult); + const sentTx = new SentTx(pxe, pxe.sendTx(txProvingResult.toTx())); + const { transactionFee } = await sentTx.wait(); expect(await counterContract.methods.get_counter(bobAddress).simulate()).toBe(1n); @@ -207,13 +219,15 @@ describe('e2e_fees dapp_subscription', () => { async function subscribe(paymentMethod: FeePaymentMethod, blockDelta: number = 5, txCount: number = 4) { const nonce = Fr.random(); + // This authwit is made because the subscription recipient is Bob, so we are approving the contract to send funds + // to him, on our behalf, as part of the subscription process. const action = bananaCoin.methods.transfer_in_private(aliceAddress, bobAddress, t.SUBSCRIPTION_AMOUNT, nonce); await aliceWallet.createAuthWit({ caller: subscriptionContract.address, action }); return subscriptionContract .withWallet(aliceWallet) .methods.subscribe(aliceAddress, nonce, (await pxe.getBlockNumber()) + blockDelta, txCount) - .send({ fee: { gasSettings: t.gasSettings, paymentMethod } }) + .send({ fee: { gasSettings, paymentMethod } }) .wait(); } diff --git a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts index 18058dba944..dcacb4ca441 100644 --- a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts @@ -35,9 +35,16 @@ describe('e2e_fees failures', () => { await t.teardown(); }); + beforeEach(async () => { + gasSettings = GasSettings.from({ + ...t.gasSettings, + maxFeesPerGas: await aliceWallet.getCurrentBaseFees(), + }); + }); + it('reverts transactions but still pays fees using PrivateFeePaymentMethod', async () => { - const outrageousPublicAmountAliceDoesNotHave = BigInt(1e8); - const privateMintedAlicePrivateBananas = BigInt(1e15); + const outrageousPublicAmountAliceDoesNotHave = t.ALICE_INITIAL_BANANAS * 5n; + const privateMintedAlicePrivateBananas = t.ALICE_INITIAL_BANANAS; const [initialAlicePrivateBananas, initialSequencerPrivateBananas] = await t.getBananaPrivateBalanceFn( aliceAddress, @@ -126,8 +133,8 @@ describe('e2e_fees failures', () => { }); it('reverts transactions but still pays fees using PublicFeePaymentMethod', async () => { - const outrageousPublicAmountAliceDoesNotHave = BigInt(1e15); - const publicMintedAlicePublicBananas = BigInt(1e12); + const outrageousPublicAmountAliceDoesNotHave = t.ALICE_INITIAL_BANANAS * 5n; + const publicMintedAlicePublicBananas = t.ALICE_INITIAL_BANANAS; const [initialAlicePrivateBananas, initialSequencerPrivateBananas] = await t.getBananaPrivateBalanceFn( aliceAddress, diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index d69ff14f11b..ca79d2d64f3 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -79,9 +79,9 @@ export class FeesTest { public getBananaPublicBalanceFn!: BalancesFn; public getBananaPrivateBalanceFn!: BalancesFn; - public readonly INITIAL_GAS_BALANCE = BigInt(1e15); - public readonly ALICE_INITIAL_BANANAS = BigInt(1e12); - public readonly SUBSCRIPTION_AMOUNT = 10_000n; + public readonly INITIAL_GAS_BALANCE = BigInt(1e22); + public readonly ALICE_INITIAL_BANANAS = BigInt(1e22); + public readonly SUBSCRIPTION_AMOUNT = BigInt(1e19); public readonly APP_SPONSORED_TX_GAS_LIMIT = BigInt(10e9); constructor(testName: string) { diff --git a/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts b/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts index 28e20fc79e5..57089425c7c 100644 --- a/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/gas_estimation.test.ts @@ -5,7 +5,7 @@ import { type FeePaymentMethod, PublicFeePaymentMethod, } from '@aztec/aztec.js'; -import { Gas, GasFees, type GasSettings } from '@aztec/circuits.js'; +import { Gas, GasSettings } from '@aztec/circuits.js'; import { type Logger } from '@aztec/foundation/log'; import { TokenContract as BananaCoin, type FPCContract } from '@aztec/noir-contracts.js'; @@ -20,7 +20,6 @@ describe('e2e_fees gas_estimation', () => { let bananaCoin: BananaCoin; let bananaFPC: FPCContract; let gasSettings: GasSettings; - let teardownFixedFee: bigint; let logger: Logger; const t = new FeesTest('gas_estimation'); @@ -32,12 +31,19 @@ describe('e2e_fees gas_estimation', () => { await t.applyFundAliceWithFeeJuice(); ({ aliceWallet, aliceAddress, bobAddress, bananaCoin, bananaFPC, gasSettings, logger } = await t.setup()); - teardownFixedFee = gasSettings.teardownGasLimits.computeFee(GasFees.default()).toBigInt(); - // We let Alice see Bob's notes because the expect uses Alice's wallet to interact with the contracts to "get" state. aliceWallet.setScopes([aliceAddress, bobAddress]); }); + beforeEach(async () => { + // Load the gas fees at the start of each test, use those exactly as the max fees per gas + const gasFees = await aliceWallet.getCurrentBaseFees(); + gasSettings = GasSettings.from({ + ...gasSettings, + maxFeesPerGas: gasFees, + }); + }); + afterAll(async () => { await t.teardown(); }); @@ -62,10 +68,10 @@ describe('e2e_fees gas_estimation', () => { estimatedGas: Pick, actualFee: bigint, ) => { - const feeFromEstimatedGas = estimatedGas.gasLimits.computeFee(GasFees.default()).toBigInt(); + const feeFromEstimatedGas = estimatedGas.gasLimits.computeFee(gasSettings.maxFeesPerGas).toBigInt(); // The actual fee should be under the estimate, since we add 10% by default to the estimated gas (see aztec.js/src/contract/get_gas_limits.ts). - const adjustedForFloatingPoint = new Gas(1, 1).computeFee(GasFees.default()).toBigInt(); + const adjustedForFloatingPoint = new Gas(1, 1).computeFee(gasSettings.maxFeesPerGas).toBigInt(); expect(feeFromEstimatedGas).toBeLessThanOrEqual((actualFee * 110n) / 100n + adjustedForFloatingPoint); expect(feeFromEstimatedGas).toBeGreaterThan(actualFee); }; @@ -90,6 +96,7 @@ describe('e2e_fees gas_estimation', () => { }); it('estimates gas with public payment method', async () => { + const teardownFixedFee = gasSettings.teardownGasLimits.computeFee(gasSettings.maxFeesPerGas).toBigInt(); const paymentMethod = new PublicFeePaymentMethod(bananaCoin.address, bananaFPC.address, aliceWallet); const estimatedGas = await makeTransferRequest().estimateGas({ fee: { gasSettings, paymentMethod } }); logGasEstimate(estimatedGas); diff --git a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts index 870f40707c9..fd59e1f2140 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts @@ -1,5 +1,5 @@ import { type AccountWallet, type AztecAddress, BatchCall, PrivateFeePaymentMethod, sleep } from '@aztec/aztec.js'; -import { type GasSettings } from '@aztec/circuits.js'; +import { GasSettings } from '@aztec/circuits.js'; import { type TokenContract as BananaCoin, FPCContract } from '@aztec/noir-contracts.js'; import { expectMapping } from '../fixtures/utils.js'; @@ -43,12 +43,11 @@ describe('e2e_fees private_payment', () => { let initialSequencerGas: bigint; - let maxFee: bigint; - beforeEach(async () => { - maxFee = BigInt(20e9); - - expect(gasSettings.getFeeLimit().toBigInt()).toEqual(maxFee); + gasSettings = GasSettings.from({ + ...gasSettings, + maxFeesPerGas: await aliceWallet.getCurrentBaseFees(), + }); initialSequencerL1Gas = await t.getCoinbaseBalance(); diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 37c9237ab0c..9f15525f094 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -497,8 +497,8 @@ describe('e2e_synching', () => { await rollup.write.setAssumeProvenThroughBlockNumber([assumeProvenThrough]); const timeliness = (await rollup.read.EPOCH_DURATION()) * 2n; - const [, , slot] = await rollup.read.blocks([(await rollup.read.getProvenBlockNumber()) + 1n]); - const timeJumpTo = await rollup.read.getTimestampForSlot([slot + timeliness]); + const blockLog = await rollup.read.getBlock([(await rollup.read.getProvenBlockNumber()) + 1n]); + const timeJumpTo = await rollup.read.getTimestampForSlot([blockLog.slotNumber + timeliness]); await opts.cheatCodes!.eth.warp(Number(timeJumpTo)); @@ -581,8 +581,8 @@ describe('e2e_synching', () => { const blockBeforePrune = await aztecNode.getBlockNumber(); const timeliness = (await rollup.read.EPOCH_DURATION()) * 2n; - const [, , slot] = await rollup.read.blocks([(await rollup.read.getProvenBlockNumber()) + 1n]); - const timeJumpTo = await rollup.read.getTimestampForSlot([slot + timeliness]); + const blockLog = await rollup.read.getBlock([(await rollup.read.getProvenBlockNumber()) + 1n]); + const timeJumpTo = await rollup.read.getTimestampForSlot([blockLog.slotNumber + timeliness]); await opts.cheatCodes!.eth.warp(Number(timeJumpTo)); @@ -641,8 +641,8 @@ describe('e2e_synching', () => { await rollup.write.setAssumeProvenThroughBlockNumber([pendingBlockNumber - BigInt(variant.blockCount) / 2n]); const timeliness = (await rollup.read.EPOCH_DURATION()) * 2n; - const [, , slot] = await rollup.read.blocks([(await rollup.read.getProvenBlockNumber()) + 1n]); - const timeJumpTo = await rollup.read.getTimestampForSlot([slot + timeliness]); + const blockLog = await rollup.read.getBlock([(await rollup.read.getProvenBlockNumber()) + 1n]); + const timeJumpTo = await rollup.read.getTimestampForSlot([blockLog.slotNumber + timeliness]); await opts.cheatCodes!.eth.warp(Number(timeJumpTo)); diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 85666248f53..5381148d3c1 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -30,7 +30,13 @@ import { import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; import { DefaultMultiCallEntrypoint } from '@aztec/aztec.js/entrypoint'; import { type BBNativePrivateKernelProver } from '@aztec/bb-prover'; -import { type EthAddress, Fr, GasSettings, getContractClassFromArtifact } from '@aztec/circuits.js'; +import { + type EthAddress, + FEE_JUICE_INITIAL_MINT, + Fr, + GasSettings, + getContractClassFromArtifact, +} from '@aztec/circuits.js'; import { type DeployL1ContractsArgs, NULL_KEY, @@ -657,7 +663,7 @@ export async function setupCanonicalFeeJuice(pxe: PXE) { try { await feeJuice.methods - .initialize(feeJuicePortalAddress) + .initialize(feeJuicePortalAddress, FEE_JUICE_INITIAL_MINT) .send({ fee: { paymentMethod: new NoFeePaymentMethod(), gasSettings: GasSettings.teardownless() } }) .wait(); getLogger().info(`Fee Juice successfully setup. Portal address: ${feeJuicePortalAddress}`); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index fae1608e821..d1b8dc91687 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -391,7 +391,7 @@ export const deployL1Contracts = async ( // because there is circular dependency hell. This is a temporary solution. #3342 // @todo #8084 // fund the portal contract with Fee Juice - const FEE_JUICE_INITIAL_MINT = 200000000000000; + const FEE_JUICE_INITIAL_MINT = 20000000000000000000; const mintTxHash = await feeJuice.write.mint([feeJuicePortalAddress.toString(), FEE_JUICE_INITIAL_MINT], {} as any); // @note This is used to ensure we fully wait for the transaction when running against a real chain diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index b7ed140aa73..fa81c902d7e 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -36,6 +36,7 @@ import { type CompleteAddress, type ContractClassWithId, type ContractInstanceWithAddress, + type GasFees, type L1_TO_L2_MSG_TREE_HEIGHT, type NodeInfo, type PartialAddress, @@ -491,6 +492,10 @@ export class PXEService implements PXE { return await this.node.getBlock(blockNumber); } + public async getCurrentBaseFees(): Promise { + return await this.node.getCurrentBaseFees(); + } + async #simulateKernels( txRequest: TxExecutionRequest, privateExecutionResult: PrivateExecutionResult, diff --git a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts index d030aa502f9..adb1cb4f9e5 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts @@ -46,6 +46,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { }); } + public async getCurrentBaseFees(): Promise { + return new GasFees(Fr.ZERO, new Fr(await this.rollupContract.read.getManaBaseFee([true]))); + } + /** * Simple builder of global variables that use the minimum time possible. * @param blockNumber - The block number to build global variables for. @@ -73,7 +77,8 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface { const slotFr = new Fr(slotNumber); const timestampFr = new Fr(timestamp); - const gasFees = GasFees.default(); + const gasFees = await this.getCurrentBaseFees(); + const globalVariables = new GlobalVariables( chainId, version, diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts index 6c817d70a2c..d1916020719 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts @@ -124,6 +124,10 @@ describe('L1Publisher', () => { header: `0x${header.toString('hex')}`, archive: `0x${archive.toString('hex')}`, blockHash: `0x${blockHash.toString('hex')}`, + oracleInput: { + feeAssetPriceModifier: 0n, + provingCostModifier: 0n, + }, txHashes: [], }, [], diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 9226059ab6c..29b4c8d1de3 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -642,26 +642,28 @@ export class L1Publisher { } // Check the block hash and archive for the immediate block before the epoch - const [previousArchive, previousBlockHash] = await this.rollupContract.read.blocks([proven]); - if (publicInputs.previousArchive.root.toString() !== previousArchive) { + const blockLog = await this.rollupContract.read.getBlock([proven]); + if (publicInputs.previousArchive.root.toString() !== blockLog.archive) { throw new Error( - `Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${previousArchive}`, + `Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${blockLog.archive}`, ); } // TODO: Remove zero check once we inject the proper zero blockhash - if (previousBlockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== previousBlockHash) { + if (blockLog.blockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== blockLog.blockHash) { throw new Error( - `Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${previousBlockHash}`, + `Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${blockLog.blockHash}`, ); } // Check the block hash and archive for the last block in the epoch - const [endArchive, endBlockHash] = await this.rollupContract.read.blocks([BigInt(toBlock)]); - if (publicInputs.endArchive.root.toString() !== endArchive) { - throw new Error(`End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endArchive}`); + const endBlockLog = await this.rollupContract.read.getBlock([BigInt(toBlock)]); + if (publicInputs.endArchive.root.toString() !== endBlockLog.archive) { + throw new Error( + `End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endBlockLog.archive}`, + ); } - if (publicInputs.endBlockHash.toString() !== endBlockHash) { - throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockHash}`); + if (publicInputs.endBlockHash.toString() !== endBlockLog.blockHash) { + throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockLog.blockHash}`); } // Compare the public inputs computed by the contract with the ones injected @@ -741,6 +743,11 @@ export class L1Publisher { { header: `0x${encodedData.header.toString('hex')}`, archive: `0x${encodedData.archive.toString('hex')}`, + oracleInput: { + // We are currently not modifying these. See #9963 + feeAssetPriceModifier: 0n, + provingCostModifier: 0n, + }, blockHash: `0x${encodedData.blockHash.toString('hex')}`, txHashes, }, diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 7e1e35b8710..6d903ebafc7 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -80,7 +80,7 @@ export class PublicProcessor { protected worldStateDB: WorldStateDB, protected publicTxSimulator: PublicTxSimulator, telemetryClient: TelemetryClient, - private log = createDebugLogger('aztec:sequencer:public-processor'), + private log = createDebugLogger('aztec:simulator:public-processor'), ) { this.metrics = new PublicProcessorMetrics(telemetryClient, 'PublicProcessor'); } @@ -201,6 +201,7 @@ export class PublicProcessor { feePayer: AztecAddress, ): Promise { if (feePayer.isZero()) { + this.log.debug(`No one is paying the fee of ${txFee.toBigInt()}`); return; } @@ -208,7 +209,7 @@ export class PublicProcessor { const balanceSlot = computeFeePayerBalanceStorageSlot(feePayer); const leafSlot = computeFeePayerBalanceLeafSlot(feePayer); - this.log.debug(`Deducting ${txFee} balance in Fee Juice for ${feePayer}`); + this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${feePayer}`); const existingBalanceWrite = publicDataWrites.find(write => write.leafSlot.equals(leafSlot)); @@ -217,7 +218,9 @@ export class PublicProcessor { : await this.worldStateDB.storageRead(feeJuiceAddress, balanceSlot); if (balance.lt(txFee)) { - throw new Error(`Not enough balance for fee payer to pay for transaction (got ${balance} needs ${txFee})`); + throw new Error( + `Not enough balance for fee payer to pay for transaction (got ${balance.toBigInt()} needs ${txFee.toBigInt()})`, + ); } const updatedBalance = balance.sub(txFee); From dd30fb68a4937833c9b4e981fbcd9dd86a59a00a Mon Sep 17 00:00:00 2001 From: LHerskind Date: Fri, 22 Nov 2024 15:30:47 +0000 Subject: [PATCH 2/2] chore: add comment + fix typo --- l1-contracts/src/core/Rollup.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index cf5d63523f1..80ec25e9e18 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -651,6 +651,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { /** * @notice Gets the mana base fee components + * For more context, consult: + * https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md * * @dev TODO #10004 - As part of the refactor, will likely get rid of this function or make it private * keeping it public for now makes it simpler to test. @@ -673,9 +675,9 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { L1FeeData memory fees = getCurrentL1Fees(); uint256 dataCost = Math.mulDiv(3 * BLOB_GAS_PER_BLOB, fees.blobFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); - uint256 casUsed = FeeMath.L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION + 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(casUsed, fees.baseFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); + uint256 gasCost = Math.mulDiv(gasUsed, fees.baseFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil); uint256 provingCost = FeeMath.provingCostPerMana( blocks[tips.pendingBlockNumber].feeHeader.provingCostPerManaNumerator );