diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp index 6e4c9ad3fb8..80a49dafcec 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -377,10 +378,16 @@ std::vector Execution::gen_trace(AvmPublicInputs const& public_inputs, if (!is_ok(phase_error) && phase == TxExecutionPhase::SETUP) { // Stop processing phases. Halt TX. - info("A revert during SETUP phase halts the entire TX"); + info("A revert was encountered in the SETUP phase, killing the entire TX"); + throw std::runtime_error("A revert was encountered in the SETUP phase, killing the entire TX"); break; } } + + if (apply_e2e_assertions) { + trace_builder.pay_fee(); + } + auto trace = trace_builder.finalize(apply_e2e_assertions); returndata = trace_builder.get_all_returndata(); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp index 0b0bcfca482..e7b0f4d1ffe 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/public_inputs.hpp @@ -306,6 +306,7 @@ class AvmPublicInputs { TreeSnapshots start_tree_snapshots; Gas start_gas_used; GasSettings gas_settings; + FF fee_payer; std::array public_setup_call_requests; std::array public_app_logic_call_requests; PublicCallRequest public_teardown_call_request; @@ -330,6 +331,7 @@ class AvmPublicInputs { read(it, public_inputs.start_tree_snapshots); read(it, public_inputs.start_gas_used); read(it, public_inputs.gas_settings); + read(it, public_inputs.fee_payer); read(it, public_inputs.public_setup_call_requests); read(it, public_inputs.public_app_logic_call_requests); read(it, public_inputs.public_teardown_call_request); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index a370f42e5ba..a7b39dc5263 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include "barretenberg/vm/avm/trace/gadgets/cmp.hpp" #include "barretenberg/vm/avm/trace/gadgets/keccak.hpp" #include "barretenberg/vm/avm/trace/gadgets/merkle_tree.hpp" +#include "barretenberg/vm/avm/trace/gadgets/poseidon2.hpp" #include "barretenberg/vm/avm/trace/gadgets/slice_trace.hpp" #include "barretenberg/vm/avm/trace/helper.hpp" #include "barretenberg/vm/avm/trace/opcode.hpp" @@ -225,6 +227,63 @@ void AvmTraceBuilder::insert_private_state(const std::vector& siloed_nullifi } } +void AvmTraceBuilder::pay_fee() +{ + auto clk = static_cast(main_trace.size()) + 1; + + auto tx_fee = (public_inputs.global_variables.gas_fees.fee_per_da_gas * public_inputs.end_gas_used.da_gas) + + (public_inputs.global_variables.gas_fees.fee_per_l2_gas * public_inputs.end_gas_used.l2_gas); + + if (public_inputs.fee_payer == 0) { + vinfo("No one is paying the fee of ", tx_fee); + return; + } + + // ** Compute the storage slot ** + // using the base slot of the balances map and the fee payer address (map key) + // TS equivalent: + // computeFeePayerBalanceStorageSlot(fee_payer); + std::vector slot_hash_inputs = { FEE_JUICE_BALANCES_SLOT, public_inputs.fee_payer }; + const auto balance_slot = poseidon2_trace_builder.poseidon2_hash(slot_hash_inputs, clk, Poseidon2Caller::SILO); + + // ** Read the balance before fee payment ** + // TS equivalent: + // current_balance = readStorage(FEE_JUICE_ADDRESS, balance_slot); + PublicDataReadTreeHint read_hint = execution_hints.storage_read_hints.at(storage_read_counter++); + FF computed_tree_slot = + merkle_tree_trace_builder.compute_public_tree_leaf_slot(clk, FEE_JUICE_ADDRESS, balance_slot); + // Sanity check that the computed slot using the value read from slot_offset should match the read hint + ASSERT(computed_tree_slot == read_hint.leaf_preimage.slot); + + // ** Write the updated balance after fee payment ** + // TS equivalent: + // Check that the leaf is a member of the public data tree + bool is_member = merkle_tree_trace_builder.perform_storage_read( + clk, read_hint.leaf_preimage, read_hint.leaf_index, read_hint.sibling_path); + ASSERT(is_member); + FF current_balance = read_hint.leaf_preimage.value; + + const auto updated_balance = current_balance - tx_fee; + if (current_balance < tx_fee) { + info("Not enough balance for fee payer to pay for transaction (got ", current_balance, " needs ", tx_fee); + throw std::runtime_error("Not enough balance for fee payer to pay for transaction"); + } + + // writeStorage(FEE_JUICE_ADDRESS, balance_slot, updated_balance); + PublicDataWriteTreeHint write_hint = execution_hints.storage_write_hints.at(storage_write_counter++); + ASSERT(write_hint.new_leaf_preimage.value == updated_balance); + merkle_tree_trace_builder.perform_storage_write(clk, + write_hint.low_leaf_membership.leaf_preimage, + write_hint.low_leaf_membership.leaf_index, + write_hint.low_leaf_membership.sibling_path, + write_hint.new_leaf_preimage.slot, + write_hint.new_leaf_preimage.value, + write_hint.insertion_path); + + debug("pay fee side-effect cnt: ", side_effect_counter); + side_effect_counter++; +} + /** * @brief Loads a value from memory into a given intermediate register at a specified clock cycle. * Handles both direct and indirect memory access. @@ -2528,6 +2587,9 @@ AvmError AvmTraceBuilder::op_sstore(uint8_t indirect, uint32_t src_offset, uint3 auto clk = static_cast(main_trace.size()) + 1; if (storage_write_counter >= MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) { + // NOTE: the circuit constraint for this limit should only be applied + // for the storage writes performed by this opcode. An exception should before + // made for the fee juice storage write made after teardown. error = AvmError::SIDE_EFFECT_LIMIT_REACHED; auto row = Row{ .main_clk = clk, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp index 660b26e7484..5e6af27c2a9 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp @@ -235,6 +235,7 @@ class AvmTraceBuilder { std::vector get_bytecode(const FF contract_address, bool check_membership = false); std::unordered_set bytecode_membership_cache; void insert_private_state(const std::vector& siloed_nullifiers, const std::vector& siloed_note_hashes); + void pay_fee(); // These are used for testing only. AvmTraceBuilder& set_range_check_required(bool required) diff --git a/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp b/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp index 23b1cfc86f1..9bb09e93c7c 100644 --- a/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp @@ -26,6 +26,7 @@ #define MULTI_CALL_ENTRYPOINT_ADDRESS 4 #define FEE_JUICE_ADDRESS 5 #define ROUTER_ADDRESS 6 +#define FEE_JUICE_BALANCES_SLOT 1 #define AZTEC_ADDRESS_LENGTH 1 #define GAS_FEES_LENGTH 2 #define GAS_LENGTH 2 @@ -45,8 +46,8 @@ #define STATE_REFERENCE_LENGTH 8 #define TOTAL_FEES_LENGTH 1 #define PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH 867 -#define AVM_ACCUMULATED_DATA_LENGTH 318 -#define AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH 1006 +#define AVM_ACCUMULATED_DATA_LENGTH 320 +#define AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH 1008 #define AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS 86 #define AVM_PROOF_LENGTH_IN_FIELDS 4155 #define AVM_PUBLIC_COLUMN_MAX_SIZE 1024 diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 79803ccb63b..632544c44a4 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -138,6 +138,7 @@ library Constants { uint256 internal constant MULTI_CALL_ENTRYPOINT_ADDRESS = 4; uint256 internal constant FEE_JUICE_ADDRESS = 5; uint256 internal constant ROUTER_ADDRESS = 6; + uint256 internal constant FEE_JUICE_BALANCES_SLOT = 1; uint256 internal constant DEFAULT_NPK_M_X = 582240093077765400562621227108555700500271598878376310175765873770292988861; uint256 internal constant DEFAULT_NPK_M_Y = @@ -208,7 +209,7 @@ library Constants { uint256 internal constant SCOPED_READ_REQUEST_LEN = 3; uint256 internal constant PUBLIC_DATA_READ_LENGTH = 3; uint256 internal constant PRIVATE_VALIDATION_REQUESTS_LENGTH = 772; - uint256 internal constant COMBINED_ACCUMULATED_DATA_LENGTH = 900; + uint256 internal constant COMBINED_ACCUMULATED_DATA_LENGTH = 902; uint256 internal constant TX_CONSTANT_DATA_LENGTH = 35; uint256 internal constant COMBINED_CONSTANT_DATA_LENGTH = 44; uint256 internal constant PRIVATE_ACCUMULATED_DATA_LENGTH = 1412; @@ -217,7 +218,7 @@ library Constants { uint256 internal constant PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH = 160; uint256 internal constant NUM_PRIVATE_TO_AVM_ACCUMULATED_DATA_ARRAYS = 3; uint256 internal constant PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1845; - uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 956; + uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 958; uint256 internal constant CONSTANT_ROLLUP_DATA_LENGTH = 13; uint256 internal constant BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 31; uint256 internal constant BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 90; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr index 24f3d79ddc4..33e49dd4384 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr @@ -12,23 +12,23 @@ pub(crate) global MERGE_ROLLUP_TYPE: u32 = 1; pub struct BaseOrMergeRollupPublicInputs { // rollup_type is either 0 (base) or 1 (merge) // TODO(Kev): Why is this a u32 instead of a u8/u16? - pub rollup_type: u32, - pub num_txs: u32, - pub constants: ConstantRollupData, + pub(crate) rollup_type: u32, + pub(crate) num_txs: u32, + pub(crate) constants: ConstantRollupData, - pub start: PartialStateReference, - pub end: PartialStateReference, + pub(crate) start: PartialStateReference, + pub(crate) end: PartialStateReference, // We hash public inputs to make them constant-sized (to then be unpacked on-chain) // U128 isn't safe if it's an input to the circuit (it won't automatically constrain the witness) // So we want to constrain it when casting these fields to U128 // We hash public inputs to make them constant-sized (to then be unpacked on-chain) - pub txs_effects_hash: Field, - pub out_hash: Field, + pub(crate) txs_effects_hash: Field, + pub(crate) out_hash: Field, - pub accumulated_fees: Field, - pub accumulated_mana_used: Field, + pub(crate) accumulated_fees: Field, + pub(crate) accumulated_mana_used: Field, } impl Empty for BaseOrMergeRollupPublicInputs { diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr index 6c5d6d65b6c..a7bede53fa3 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr @@ -6,7 +6,6 @@ use crate::{ base::{ components::{ archive::perform_archive_membership_check, constants::validate_combined_constant_data, - fees::compute_fee_payer_fee_juice_balance_leaf_slot, nullifier_tree::nullifier_tree_batch_insert, public_data_tree::public_data_tree_insert, }, state_diff_hints::PublicBaseStateDiffHints, @@ -15,17 +14,18 @@ use crate::{ }; use dep::types::{ abis::{ - accumulated_data::CombinedAccumulatedData, - append_only_tree_snapshot::AppendOnlyTreeSnapshot, avm_circuit_public_inputs::AvmProofData, - combined_constant_data::CombinedConstantData, log_hash::ScopedLogHash, - nullifier_leaf_preimage::NullifierLeafPreimage, public_data_write::PublicDataWrite, + accumulated_data::{self, CombinedAccumulatedData}, + append_only_tree_snapshot::AppendOnlyTreeSnapshot, + avm_circuit_public_inputs::AvmProofData, + combined_constant_data::CombinedConstantData, + log_hash::ScopedLogHash, + nullifier_leaf_preimage::NullifierLeafPreimage, + public_data_write::PublicDataWrite, tube::PublicTubeData, }, constants::{ - ARCHIVE_HEIGHT, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_HEIGHT, + ARCHIVE_HEIGHT, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_HEIGHT, }, - data::{hash::compute_public_data_tree_value, public_data_hint::PublicDataHint}, hash::silo_l2_to_l1_message, merkle_tree::{ append_only_tree, calculate_empty_tree_root, calculate_subtree_root, MembershipWitness, @@ -33,7 +33,7 @@ use dep::types::{ messaging::l2_to_l1_message::ScopedL2ToL1Message, partial_state_reference::PartialStateReference, traits::is_empty, - utils::arrays::{array_merge, find_index_hint}, + utils::arrays::array_merge, }; pub struct PublicBaseRollupInputs { @@ -43,7 +43,6 @@ pub struct PublicBaseRollupInputs { start: PartialStateReference, state_diff_hints: PublicBaseStateDiffHints, - fee_payer_fee_juice_balance_read_hint: PublicDataHint, archive_root_membership_witness: MembershipWitness, constants: ConstantRollupData, @@ -149,15 +148,9 @@ impl PublicBaseRollupInputs { let end_nullifier_tree_snapshot = self.check_nullifier_tree_non_membership_and_insert_to_tree(combined_accumulated_data); - // Inject protocol update requests for deducting tx_fee from fee_payer's balance - let all_public_data_update_requests = self.calculate_all_public_data_update_requests( - self.avm_proof_data.public_inputs.transaction_fee, - combined_accumulated_data, - ); - // Validate public data update requests and update public data tree let end_public_data_tree_snapshot = - self.validate_and_process_public_state(all_public_data_update_requests); + self.validate_and_process_public_state(combined_accumulated_data.public_data_writes); // Calculate the tx effects hash of the transaction let siloed_l2_to_l1_msgs = combined_accumulated_data.l2_to_l1_msgs.map( @@ -175,7 +168,7 @@ impl PublicBaseRollupInputs { combined_accumulated_data, revert_code, self.avm_proof_data.public_inputs.transaction_fee, - all_public_data_update_requests, + combined_accumulated_data.public_data_writes, out_hash, ); @@ -262,90 +255,6 @@ impl PublicBaseRollupInputs { } snapshot } - - // Returns an array with all public data update requests for this tx. This includes all update requests - // generated by app circuits, plus the protocol update requests injected by this circuit. The only protocol - // update request we have at the time of this writing is deducting the tx_fee from the fee_payer balance. - fn calculate_all_public_data_update_requests( - self, - tx_fee: Field, - accumulated_data: CombinedAccumulatedData, - ) -> [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] { - let mut all_update_requests: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = - [PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - all_update_requests[i] = accumulated_data.public_data_writes[i]; - } - - let (payment_update_request, payment_update_index) = - self.build_or_patch_payment_update_request(tx_fee, accumulated_data); - all_update_requests[payment_update_index] = payment_update_request; - - all_update_requests - } - - // Deducts the tx_fee from the FeeJuice balance of the fee_payer. If there is already a PublicDataWrite - // in this tx for their balance (because they issued a 'claim' to increase their balance by bridging from L1), - // update it by subtracting the tx_fee. Otherwise, build a new PublicDataWrite to subtract the tx_fee - // from the balance of the fee_payer, using the fee_payer_fee_juice_balance_read_hint to read the current balance. - // Returns the data update request that subtracts the tx_fee from the fee_payer's balance, and the index where it - // should be inserted in the public data update requests array. - fn build_or_patch_payment_update_request( - self, - tx_fee: Field, - accumulated_data: CombinedAccumulatedData, - ) -> (PublicDataWrite, u32) { - let fee_payer = self.tube_data.public_inputs.fee_payer; - - // TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx - if !fee_payer.is_zero() { - let read_hint = self.fee_payer_fee_juice_balance_read_hint; - let leaf_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let existing_update_index = unsafe { - find_index_hint( - accumulated_data.public_data_writes, - |w: PublicDataWrite| w.leaf_slot == leaf_slot, - ) - }; - - if existing_update_index != MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - // Is there a balance update already in this tx? If so, update it and return its index. - let existing_update = accumulated_data.public_data_writes[existing_update_index]; - assert( - existing_update.leaf_slot == leaf_slot, - "Wrong leaf slot for Fee Juice balance update request", - ); - assert( - !existing_update.value.lt(tx_fee), - "Not enough balance for fee payer after claim to pay for transaction", - ); - - let value = compute_public_data_tree_value(existing_update.value - tx_fee); - let protocol_update_request = PublicDataWrite { leaf_slot, value }; - (protocol_update_request, existing_update_index as u32) - } else { - // Otherwise, build a new one to be inserted into the protocol update requests at the end of the array. - read_hint.validate(self.start.public_data_tree.root); - - let balance = read_hint.value; - assert( - read_hint.leaf_slot == leaf_slot, - "Wrong leaf slot for Fee Juice balance read hint", - ); - assert( - !balance.lt(tx_fee), - "Not enough balance for fee payer to pay for transaction", - ); - - let value = compute_public_data_tree_value(balance - tx_fee); - let protocol_update_request = PublicDataWrite { leaf_slot, value }; - (protocol_update_request, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) - } - } else { - // Nothing to do, just place an empty update request at the end of the array - (PublicDataWrite::empty(), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) - } - } } mod tests { @@ -355,7 +264,6 @@ mod tests { constant_rollup_data::ConstantRollupData, }, base::{ - components::fees::compute_fee_payer_fee_juice_balance_leaf_slot, public_base_rollup::PublicBaseRollupInputs, state_diff_hints::PublicBaseStateDiffHints, }, components::TX_EFFECTS_HASH_INPUT_FIELDS, @@ -365,26 +273,20 @@ mod tests { append_only_tree_snapshot::AppendOnlyTreeSnapshot, nullifier_leaf_preimage::NullifierLeafPreimage, public_data_write::PublicDataWrite, }, - address::{AztecAddress, EthAddress}, + address::EthAddress, constants::{ ARCHIVE_HEIGHT, AVM_VK_INDEX, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, - MAX_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_HEIGHT, - NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, NULLIFIER_SUBTREE_HEIGHT, - NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, - PRIVATE_KERNEL_EMPTY_INDEX, PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - PUBLIC_DATA_TREE_HEIGHT, TUBE_VK_INDEX, + MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + NOTE_HASH_SUBTREE_HEIGHT, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, + NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, + PRIVATE_KERNEL_EMPTY_INDEX, PUBLIC_DATA_TREE_HEIGHT, TUBE_VK_INDEX, }, - data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, + data::{PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, hash::silo_l2_to_l1_message, merkle_tree::MembershipWitness, messaging::l2_to_l1_message::ScopedL2ToL1Message, partial_state_reference::PartialStateReference, - tests::{ - fixture_builder::FixtureBuilder, - fixtures::{self, merkle_tree::generate_full_sha_tree}, - merkle_tree_utils::NonEmptyMerkleTree, - }, + tests::{fixture_builder::FixtureBuilder, fixtures, merkle_tree_utils::NonEmptyMerkleTree}, traits::{Empty, is_empty}, utils::{ arrays::get_sorted_tuple::get_sorted_tuple, @@ -471,17 +373,9 @@ mod tests { pre_existing_contracts: [Field; 2], pre_existing_public_data: [PublicDataTreeLeafPreimage; PRE_EXISTING_PUBLIC_DATA_LEAVES], pre_existing_blocks: [Field; 2], - // Public data writes generated by app code - public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - // Public data writes overwritten by the base rollup circuit - overwritten_public_data_writes: [Option; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - // New public data writes to be created by the protocol (eg a data update request for updating fee payer balance) - protocol_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, + public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, nullifiers: BoundedVec, constants: ConstantRollupData, - // Index of the item in the pre_existing_public_data array that contains the fee payer's Fee Juice balance. - // Used for building the public data hint read for the payment update request. If set to none, no hint is built. - fee_payer_fee_juice_balance_pre_existing_public_data_index: Option, } impl PublicBaseRollupInputsBuilder { @@ -505,29 +399,6 @@ mod tests { builder } - fn build_fee_payer_fee_juice_balance_read_hint( - self, - start_public_data_tree: NonEmptyMerkleTree, - ) -> PublicDataHint { - self.fee_payer_fee_juice_balance_pre_existing_public_data_index.map_or( - PublicDataHint::empty(), - |leaf_index_u32: u32| { - let leaf_index = leaf_index_u32 as Field; - let leaf_preimage = self.pre_existing_public_data[leaf_index]; - let membership_witness = MembershipWitness { - leaf_index, - sibling_path: start_public_data_tree.get_sibling_path(leaf_index_u32), - }; - PublicDataHint { - leaf_slot: leaf_preimage.slot, - value: leaf_preimage.value, - membership_witness, - leaf_preimage, - } - }, - ) - } - fn extract_subtree_sibling_path( path: [Field; FULL_HEIGHT], mut sibling_path: [Field; SIBLING_PATH_LENGTH], @@ -652,9 +523,6 @@ mod tests { next_available_leaf_index: self.pre_existing_public_data.len(), }; - let fee_payer_fee_juice_balance_read_hint = - self.build_fee_payer_fee_juice_balance_read_hint(start_public_data_tree); - let start_archive = NonEmptyMerkleTree::new( self.pre_existing_blocks, [0; ARCHIVE_HEIGHT], @@ -682,26 +550,10 @@ mod tests { [0; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], ); - let mut final_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX> = - BoundedVec::new(); - final_public_data_writes.extend_from_array(self.public_data_writes.storage()); - final_public_data_writes.extend_from_array(self.protocol_public_data_writes.storage()); - for i in 0..self.overwritten_public_data_writes.len() { - if self.overwritten_public_data_writes[i].is_some() { - final_public_data_writes.set( - i, - ( - final_public_data_writes.get(i).0, - self.overwritten_public_data_writes[i].unwrap_unchecked(), - ), - ); - } - } - let (low_public_data_writes_preimages, low_public_data_writes_witnesses, public_data_tree_sibling_paths) = update_public_data_tree( &mut start_public_data_tree, start_public_data_tree_snapshot, - final_public_data_writes.storage(), + self.public_data_writes.storage(), self.pre_existing_public_data, ); @@ -739,7 +591,6 @@ mod tests { sibling_path: start_archive.get_sibling_path(0), }, constants: self.constants, - fee_payer_fee_juice_balance_read_hint, } } @@ -772,13 +623,8 @@ mod tests { ], pre_existing_blocks: [0; 2], public_data_writes: BoundedVec::new(), - overwritten_public_data_writes: [ - Option::none(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - ], - protocol_public_data_writes: BoundedVec::new(), nullifiers: BoundedVec::new(), constants: ConstantRollupData::empty(), - fee_payer_fee_juice_balance_pre_existing_public_data_index: Option::none(), } } } @@ -990,7 +836,9 @@ mod tests { ); // Since we fill the tree completely, we know to expect a full tree as below - let expected_tree = generate_full_sha_tree(siloed_l2_to_l1_msgs.storage()); + let expected_tree = dep::types::merkle_tree::variable_merkle_tree::tests::generate_full_sha_tree( + siloed_l2_to_l1_msgs.storage(), + ); assert_eq(out_hash, expected_tree.get_root()); } @@ -1128,184 +976,6 @@ mod tests { assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); } - #[test] - unconstrained fn updates_fee_payer_balance_with_new_data_write() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let initial_balance = 300_000; - let tx_fee = 100_000; - let expected_balance = 200_000; - - let mut builder = PublicBaseRollupInputsBuilder::new(); - - // Set fee payer - builder.tube_data.fee_payer = fee_payer; - - // Set pre-existing balance - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Set expected protocol data update - builder.protocol_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, - )); - - let outputs = builder.execute(); - - // The new public data tree should have updated the balance of the fee payer - let updated_leaf = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: expected_balance, - next_slot: 0, - next_index: 0, - }; - let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [updated_leaf.hash(), 0], - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - 1], - [0; 1], - ); - - assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); - } - - #[test] - unconstrained fn updates_fee_payer_balance_in_existing_data_write() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let initial_balance = 100_000; - let after_claim_balance = 300_000; - let tx_fee = 100_000; - let expected_balance = 200_000; - - let mut builder = PublicBaseRollupInputsBuilder::new(); - - // Set fee payer - builder.tube_data.fee_payer = fee_payer; - - // Set pre-existing balance, but set no hint for it since we'll update a user update request - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Rollup will overwrite the public data write we are about to push - builder.overwritten_public_data_writes[builder.public_data_writes.len()] = Option::some( - PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, - ); - - // Create an existing data update that corresponds to a claim - builder.public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: after_claim_balance }, - )); - - let outputs = builder.execute(); - - // The new public data tree should have updated the balance of the fee payer - let updated_leaf = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: expected_balance, - next_slot: 0, - next_index: 0, - }; - let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [updated_leaf.hash(), 0], - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - 1], - [0; 1], - ); - - assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); - } - - #[test(should_fail_with = "Not enough balance for fee payer to pay for transaction")] - unconstrained fn fails_to_update_fee_payer_balance_if_not_enough_funds() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - // Set low initial balance so it fails! - let initial_balance = 10_000; - let tx_fee = 100_000; - - let mut builder = PublicBaseRollupInputsBuilder::new(); - - // Set fee payer - builder.tube_data.fee_payer = fee_payer; - - // Set pre-existing balance - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Set expected protocol data update - builder.protocol_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: -90_000 }, - )); - - builder.fails(); - } - - #[test(should_fail_with = "Wrong leaf slot for Fee Juice balance read hint")] - unconstrained fn fails_to_update_fee_payer_balance_if_wrong_read_hint() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let initial_balance = 300_000; - let expected_balance = 200_000; - let tx_fee = 100_000; - - let mut builder = PublicBaseRollupInputsBuilder::new(); - - // Set fee payer - builder.tube_data.fee_payer = fee_payer; - - // Set pre-existing balance in index 0 - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - - builder.pre_existing_public_data[1] = PublicDataTreeLeafPreimage { - slot: 1, - value: initial_balance, - next_slot: balance_slot, - next_index: 0, - }; - - // But point the read hint to the wrong one! - builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(1); - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Set expected protocol data update - builder.protocol_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, - )); - - builder.fails(); - } - #[test] fn valid_previous_kernel_empty() { let builder = unsafe { diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index 6db3d9272eb..75a56f6519c 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -231,7 +231,7 @@ fn silo_and_hash_unencrypted_logs( // __ // 1 unencrypted logs hash --> 1 sha256 hash -> 31 bytes -> 1 fields | Beware when populating bytes that we fill (prepend) to 32! |-> 2 types of flexible-length logs - 2 fields for their hashes // 1 contract class logs hash --> 1 sha256 hash -> 31 bytes -> 1 fields | Beware when populating bytes that we fill (prepend) to 32! __| -pub global TX_EFFECTS_HASH_INPUT_FIELDS: u32 = 1 +pub(crate) global TX_EFFECTS_HASH_INPUT_FIELDS: u32 = 1 + 1 + MAX_NOTE_HASHES_PER_TX + MAX_NULLIFIERS_PER_TX diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/avm_accumulated_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/avm_accumulated_data.nr index 2716b752fe7..6108c987133 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/avm_accumulated_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/avm_accumulated_data.nr @@ -2,7 +2,8 @@ use crate::{ abis::{log_hash::ScopedLogHash, public_data_write::PublicDataWrite}, constants::{ AVM_ACCUMULATED_DATA_LENGTH, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, - MAX_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, + MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_UNENCRYPTED_LOGS_PER_TX, }, messaging::l2_to_l1_message::ScopedL2ToL1Message, traits::{Deserialize, Empty, Serialize}, @@ -20,7 +21,7 @@ pub struct AvmAccumulatedData { // The unencrypted logs emitted from private combining with those made in the AVM execution. pub unencrypted_logs_hashes: [ScopedLogHash; MAX_UNENCRYPTED_LOGS_PER_TX], // The public data writes made in the AVM execution. - pub public_data_writes: [PublicDataWrite; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + pub public_data_writes: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], } impl Empty for AvmAccumulatedData { @@ -30,7 +31,9 @@ impl Empty for AvmAccumulatedData { nullifiers: [0; MAX_NULLIFIERS_PER_TX], l2_to_l1_msgs: [ScopedL2ToL1Message::empty(); MAX_L2_TO_L1_MSGS_PER_TX], unencrypted_logs_hashes: [ScopedLogHash::empty(); MAX_UNENCRYPTED_LOGS_PER_TX], - public_data_writes: [PublicDataWrite::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + public_data_writes: [ + PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + ], } } } @@ -82,7 +85,7 @@ impl Deserialize for AvmAccumulatedData { ), public_data_writes: reader.read_struct_array( PublicDataWrite::deserialize, - [PublicDataWrite::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + [PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], ), }; reader.finish(); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr index 35d706bbbe3..83921f18c3b 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/accumulated_data/combined_accumulated_data.nr @@ -3,7 +3,7 @@ use crate::{ constants::{ COMBINED_ACCUMULATED_DATA_LENGTH, MAX_CONTRACT_CLASS_LOGS_PER_TX, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, }, messaging::l2_to_l1_message::ScopedL2ToL1Message, traits::{Deserialize, Empty, Serialize}, @@ -24,7 +24,7 @@ pub struct CombinedAccumulatedData { pub unencrypted_log_preimages_length: Field, pub contract_class_log_preimages_length: Field, - pub public_data_writes: [PublicDataWrite; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + pub public_data_writes: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], } impl Empty for CombinedAccumulatedData { @@ -38,7 +38,9 @@ impl Empty for CombinedAccumulatedData { contract_class_logs_hashes: [ScopedLogHash::empty(); MAX_CONTRACT_CLASS_LOGS_PER_TX], unencrypted_log_preimages_length: 0, contract_class_log_preimages_length: 0, - public_data_writes: [PublicDataWrite::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + public_data_writes: [ + PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + ], } } } @@ -64,7 +66,7 @@ impl Serialize for CombinedAccumulatedData { fields.push(self.unencrypted_log_preimages_length); fields.push(self.contract_class_log_preimages_length); - for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { + for i in 0..MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { fields.extend_from_array(self.public_data_writes[i].serialize()); } @@ -101,7 +103,7 @@ impl Deserialize for CombinedAccumulatedData { contract_class_log_preimages_length: reader.read(), public_data_writes: reader.read_struct_array( PublicDataWrite::deserialize, - [PublicDataWrite::empty(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + [PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], ), }; reader.finish(); diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr index 43dc792b522..3af68aeebec 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr @@ -12,6 +12,7 @@ use crate::{ public_call_request::PublicCallRequest, tree_snapshots::TreeSnapshots, }, + address::AztecAddress, constants::{ AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH, AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS, MAX_ENQUEUED_CALLS_PER_TX, @@ -30,6 +31,7 @@ pub struct AvmCircuitPublicInputs { pub start_tree_snapshots: TreeSnapshots, pub start_gas_used: Gas, pub gas_settings: GasSettings, + pub fee_payer: AztecAddress, pub public_setup_call_requests: [PublicCallRequest; MAX_ENQUEUED_CALLS_PER_TX], pub public_app_logic_call_requests: [PublicCallRequest; MAX_ENQUEUED_CALLS_PER_TX], pub public_teardown_call_request: PublicCallRequest, @@ -54,6 +56,7 @@ impl Empty for AvmCircuitPublicInputs { start_tree_snapshots: TreeSnapshots::empty(), start_gas_used: Gas::empty(), gas_settings: GasSettings::empty(), + fee_payer: AztecAddress::zero(), public_setup_call_requests: [PublicCallRequest::empty(); MAX_ENQUEUED_CALLS_PER_TX], public_app_logic_call_requests: [PublicCallRequest::empty(); MAX_ENQUEUED_CALLS_PER_TX], public_teardown_call_request: PublicCallRequest::empty(), @@ -76,6 +79,7 @@ impl Eq for AvmCircuitPublicInputs { & (self.start_tree_snapshots == other.start_tree_snapshots) & (self.start_gas_used == other.start_gas_used) & (self.gas_settings == other.gas_settings) + & (self.fee_payer == other.fee_payer) & (self.public_setup_call_requests == other.public_setup_call_requests) & (self.public_app_logic_call_requests == other.public_app_logic_call_requests) & (self.public_teardown_call_request == other.public_teardown_call_request) @@ -111,6 +115,7 @@ impl Serialize for AvmCircuitPublicInputs { fields.extend_from_array(self.start_tree_snapshots.serialize()); fields.extend_from_array(self.start_gas_used.serialize()); fields.extend_from_array(self.gas_settings.serialize()); + fields.push(self.fee_payer.to_field()); for i in 0..self.public_setup_call_requests.len() { fields.extend_from_array(self.public_setup_call_requests[i].serialize()); } @@ -144,6 +149,7 @@ impl Deserialize for AvmCircuitPublicInputs { start_tree_snapshots: reader.read_struct(TreeSnapshots::deserialize), start_gas_used: reader.read_struct(Gas::deserialize), gas_settings: reader.read_struct(GasSettings::deserialize), + fee_payer: AztecAddress::from_field(reader.read()), public_setup_call_requests: reader.read_struct_array( PublicCallRequest::deserialize, [PublicCallRequest::empty(); MAX_ENQUEUED_CALLS_PER_TX], @@ -205,7 +211,7 @@ impl AvmProofData { ); let mut result: [Field; 4] = [input_hash, 0, 0, 0]; - for _i in 0..DUMMY_AVM_VERIFIER_NUM_ITERATIONS { + for i in 0..DUMMY_AVM_VERIFIER_NUM_ITERATIONS { result = poseidon2_permutation(result, 4); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr index 3638c6bd368..f2ceedee9bc 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr @@ -26,8 +26,8 @@ impl Verifiable for PublicTubeData { pub struct PrivateTubeData { pub public_inputs: KernelCircuitPublicInputs, - proof: TubeProof, - vk_data: VkData, + pub proof: TubeProof, + pub vk_data: VkData, } impl Verifiable for PrivateTubeData { 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 f68acffd074..4a624673052 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -227,6 +227,9 @@ pub global MULTI_CALL_ENTRYPOINT_ADDRESS: AztecAddress = AztecAddress::from_fiel pub global FEE_JUICE_ADDRESS: AztecAddress = AztecAddress::from_field(5); pub global ROUTER_ADDRESS: AztecAddress = AztecAddress::from_field(6); +// Slot of the balances map to be hashed with an AztecAddress (map key) to get an actual storage slot. +pub global FEE_JUICE_BALANCES_SLOT: u32 = 1; + // CANONICAL DEFAULT KEYS // This below are: // "az_null_npk" @@ -372,7 +375,7 @@ pub global COMBINED_ACCUMULATED_DATA_LENGTH: u32 = MAX_NOTE_HASHES_PER_TX + 1 /* unencrypted_log_preimages_length */ + (SCOPED_LOG_HASH_LENGTH * MAX_CONTRACT_CLASS_LOGS_PER_TX) + 1 /* contract_class_log_preimages_length */ - + (MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * PUBLIC_DATA_WRITE_LENGTH); + + (MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * PUBLIC_DATA_WRITE_LENGTH); pub global TX_CONSTANT_DATA_LENGTH: u32 = BLOCK_HEADER_LENGTH + TX_CONTEXT_LENGTH + 1 /* vk_tree_root */ @@ -409,7 +412,7 @@ pub global AVM_ACCUMULATED_DATA_LENGTH: u32 = MAX_NOTE_HASHES_PER_TX + MAX_NULLIFIERS_PER_TX + (MAX_L2_TO_L1_MSGS_PER_TX * SCOPED_L2_TO_L1_MESSAGE_LENGTH) + (MAX_UNENCRYPTED_LOGS_PER_TX * SCOPED_LOG_HASH_LENGTH) - + (MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * PUBLIC_DATA_WRITE_LENGTH); + + (MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * PUBLIC_DATA_WRITE_LENGTH); pub global PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH: u32 = TX_CONSTANT_DATA_LENGTH + ROLLUP_VALIDATION_REQUESTS_LENGTH @@ -431,6 +434,7 @@ pub global AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH: u32 = GLOBAL_VARIABLES_LENGTH + TREE_SNAPSHOTS_LENGTH /* start_tree_snapshots */ + GAS_LENGTH /* start_gas_used */ + GAS_SETTINGS_LENGTH + + 1 /* fee_payer */ + (MAX_ENQUEUED_CALLS_PER_TX * PUBLIC_CALL_REQUEST_LENGTH) /* public_setup_call_requests */ + (MAX_ENQUEUED_CALLS_PER_TX * PUBLIC_CALL_REQUEST_LENGTH) /* public_app_logic_call_requests */ + PUBLIC_CALL_REQUEST_LENGTH /* public_teardown_call_request */ diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/variable_merkle_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/variable_merkle_tree.nr index f5921b96582..701646552f2 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/variable_merkle_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/variable_merkle_tree.nr @@ -89,7 +89,7 @@ impl VariableMerkleTree { } } -mod tests { +pub mod tests { use crate::{ hash::accumulate_sha256, merkle_tree::variable_merkle_tree::VariableMerkleTree, tests::fixtures::merkle_tree::generate_full_sha_tree, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/proof/vk_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/proof/vk_data.nr index f2e1b72332c..cede48d01a5 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/proof/vk_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/proof/vk_data.nr @@ -6,8 +6,8 @@ use super::verification_key::VerificationKey; pub struct VkData { pub vk: VerificationKey, - vk_index: u32, - vk_path: [Field; VK_TREE_HEIGHT], + pub vk_index: u32, + pub vk_path: [Field; VK_TREE_HEIGHT], } impl VkData { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr index 460b1693128..5921dd50603 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr @@ -45,7 +45,7 @@ use crate::{ MAX_NOTE_HASH_READ_REQUESTS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PRIVATE_LOGS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, PRIVATE_CALL_REQUEST_LENGTH, PRIVATE_LOG_SIZE_IN_FIELDS, PROTOCOL_CONTRACT_TREE_HEIGHT, PUBLIC_CALL_REQUEST_LENGTH, VK_TREE_HEIGHT, }, @@ -114,7 +114,7 @@ pub struct FixtureBuilder { pub contract_class_logs_hashes: BoundedVec, pub unencrypted_log_preimages_length: Field, pub contract_class_log_preimages_length: Field, - pub public_data_writes: BoundedVec, + pub public_data_writes: BoundedVec, pub private_call_requests: BoundedVec, pub public_call_requests: BoundedVec, MAX_ENQUEUED_CALLS_PER_TX>, pub gas_used: Gas, @@ -269,10 +269,6 @@ impl FixtureBuilder { *self } - pub fn vk_tree_root() -> Field { - fixtures::vk_tree::get_vk_merkle_tree().get_root() - } - pub fn to_tx_constant_data(self) -> TxConstantData { TxConstantData { historical_header: self.historical_header, @@ -527,38 +523,6 @@ impl FixtureBuilder { } } - pub fn to_private_tube_data(self) -> PrivateTubeData { - let mut result: PrivateTubeData = std::mem::zeroed(); - result.public_inputs = self.to_kernel_circuit_public_inputs(); - result - } - - pub fn to_public_tube_data(self) -> PublicTubeData { - let mut result: PublicTubeData = std::mem::zeroed(); - result.public_inputs = self.to_private_to_public_kernel_circuit_public_inputs(true); - result - } - - pub fn to_avm_accumulated_data(self) -> AvmAccumulatedData { - AvmAccumulatedData { - note_hashes: self.note_hashes.storage().map(|n: ScopedNoteHash| n.note_hash.value), - nullifiers: self.nullifiers.storage().map(|n: ScopedNullifier| n.nullifier.value), - l2_to_l1_msgs: self.l2_to_l1_msgs.storage(), - unencrypted_logs_hashes: self.unencrypted_logs_hashes.storage(), - public_data_writes: self.public_data_writes.storage(), - } - } - - pub fn to_avm_proof_data(self, reverted: bool) -> AvmProofData { - let mut result: AvmProofData = std::mem::zeroed(); - - result.public_inputs.reverted = reverted; - result.public_inputs.global_variables = self.global_variables; - result.public_inputs.accumulated_data = self.to_avm_accumulated_data(); - - result - } - pub fn add_new_note_hash(&mut self, value: Field) { self.note_hashes.push(NoteHash { value, counter: self.next_counter() }.scope( self.contract_address, @@ -1048,6 +1012,42 @@ impl FixtureBuilder { self.counter += 1; counter } + + fn vk_tree_root() -> Field { + fixtures::vk_tree::get_vk_merkle_tree().get_root() + } + + pub fn to_private_tube_data(self) -> PrivateTubeData { + let mut result: PrivateTubeData = std::mem::zeroed(); + result.public_inputs = self.to_kernel_circuit_public_inputs(); + result + } + + pub fn to_public_tube_data(self) -> PublicTubeData { + let mut result: PublicTubeData = std::mem::zeroed(); + result.public_inputs = self.to_private_to_public_kernel_circuit_public_inputs(true); + result + } + + pub fn to_avm_accumulated_data(self) -> AvmAccumulatedData { + AvmAccumulatedData { + note_hashes: self.note_hashes.storage().map(|n: ScopedNoteHash| n.note_hash.value), + nullifiers: self.nullifiers.storage().map(|n: ScopedNullifier| n.nullifier.value), + l2_to_l1_msgs: self.l2_to_l1_msgs.storage(), + unencrypted_logs_hashes: self.unencrypted_logs_hashes.storage(), + public_data_writes: self.public_data_writes.storage(), + } + } + + pub fn to_avm_proof_data(self, reverted: bool) -> AvmProofData { + let mut result: AvmProofData = std::mem::zeroed(); + + result.public_inputs.reverted = reverted; + result.public_inputs.global_variables = self.global_variables; + result.public_inputs.accumulated_data = self.to_avm_accumulated_data(); + + result + } } impl Empty for FixtureBuilder { diff --git a/yarn-project/circuit-types/src/test/factories.ts b/yarn-project/circuit-types/src/test/factories.ts index 6360135750a..9eb5b7ab46d 100644 --- a/yarn-project/circuit-types/src/test/factories.ts +++ b/yarn-project/circuit-types/src/test/factories.ts @@ -11,7 +11,7 @@ import { GasSettings, GlobalVariables, MAX_NULLIFIERS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicCircuitPublicInputs, PublicDataWrite, RevertCode, @@ -109,7 +109,7 @@ export function makeBloatedProcessedTx({ ); avmOutput.accumulatedData.l2ToL1Msgs = revertibleData.l2ToL1Msgs; avmOutput.accumulatedData.publicDataWrites = makeTuple( - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => new PublicDataWrite(new Fr(i), new Fr(i + 10)), seed + 0x2000, ); @@ -133,7 +133,6 @@ export function makeBloatedProcessedTx({ type: ProvingRequestType.PUBLIC_VM, inputs: avmCircuitInputs, }, - undefined /* feePaymentPublicDataWrite */, gasUsed, RevertCode.OK, undefined /* revertReason */, diff --git a/yarn-project/circuit-types/src/tx/processed_tx.ts b/yarn-project/circuit-types/src/tx/processed_tx.ts index 6ae2779be14..15dc5e802ec 100644 --- a/yarn-project/circuit-types/src/tx/processed_tx.ts +++ b/yarn-project/circuit-types/src/tx/processed_tx.ts @@ -166,7 +166,6 @@ export function makeProcessedTxFromPrivateOnlyTx( export function makeProcessedTxFromTxWithPublicCalls( tx: Tx, avmProvingRequest: AvmProvingRequest, - feePaymentPublicDataWrite: PublicDataWrite | undefined, gasUsed: GasUsed, revertCode: RevertCode, revertReason: SimulationError | undefined, @@ -176,14 +175,6 @@ export function makeProcessedTxFromTxWithPublicCalls( const constants = CombinedConstantData.combine(tx.data.constants, avmOutput.globalVariables); const publicDataWrites = avmOutput.accumulatedData.publicDataWrites.filter(w => !w.isEmpty()); - if (feePaymentPublicDataWrite) { - const existingIndex = publicDataWrites.findIndex(w => w.leafSlot.equals(feePaymentPublicDataWrite.leafSlot)); - if (existingIndex >= 0) { - publicDataWrites[existingIndex] = feePaymentPublicDataWrite; - } else { - publicDataWrites.push(feePaymentPublicDataWrite); - } - } const privateLogs = [ ...tx.data.forPublic!.nonRevertibleAccumulatedData.privateLogs, diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 63d2e2779d2..bc71f92c847 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -124,6 +124,7 @@ export const REGISTERER_CONTRACT_ADDRESS = 3; export const MULTI_CALL_ENTRYPOINT_ADDRESS = 4; export const FEE_JUICE_ADDRESS = 5; export const ROUTER_ADDRESS = 6; +export const FEE_JUICE_BALANCES_SLOT = 1; export const DEFAULT_NPK_M_X = 582240093077765400562621227108555700500271598878376310175765873770292988861n; export const DEFAULT_NPK_M_Y = 10422444662424639723529825114205836958711284159673861467999592572974769103684n; export const DEFAULT_IVPK_M_X = 339708709767762472786445938838804872781183545349360029270386718856175781484n; @@ -186,7 +187,7 @@ export const AGGREGATION_OBJECT_LENGTH = 16; export const SCOPED_READ_REQUEST_LEN = 3; export const PUBLIC_DATA_READ_LENGTH = 3; export const PRIVATE_VALIDATION_REQUESTS_LENGTH = 772; -export const COMBINED_ACCUMULATED_DATA_LENGTH = 900; +export const COMBINED_ACCUMULATED_DATA_LENGTH = 902; export const TX_CONSTANT_DATA_LENGTH = 35; export const COMBINED_CONSTANT_DATA_LENGTH = 44; export const PRIVATE_ACCUMULATED_DATA_LENGTH = 1412; @@ -194,10 +195,10 @@ export const PRIVATE_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 2226; export const PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH = 900; export const PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH = 160; export const NUM_PRIVATE_TO_AVM_ACCUMULATED_DATA_ARRAYS = 3; -export const AVM_ACCUMULATED_DATA_LENGTH = 318; +export const AVM_ACCUMULATED_DATA_LENGTH = 320; export const PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1845; -export const KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 956; -export const AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1006; +export const KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 958; +export const AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH = 1008; export const CONSTANT_ROLLUP_DATA_LENGTH = 13; export const BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 31; export const BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 90; diff --git a/yarn-project/circuits.js/src/scripts/constants.in.ts b/yarn-project/circuits.js/src/scripts/constants.in.ts index 6ac65ef90ea..44fbfc546db 100644 --- a/yarn-project/circuits.js/src/scripts/constants.in.ts +++ b/yarn-project/circuits.js/src/scripts/constants.in.ts @@ -90,6 +90,7 @@ const CPP_CONSTANTS = [ 'MULTI_CALL_ENTRYPOINT_ADDRESS', 'FEE_JUICE_ADDRESS', 'ROUTER_ADDRESS', + 'FEE_JUICE_BALANCES_SLOT', ]; const CPP_GENERATORS: string[] = [ diff --git a/yarn-project/circuits.js/src/structs/avm/avm_accumulated_data.ts b/yarn-project/circuits.js/src/structs/avm/avm_accumulated_data.ts index c68d93b13e4..6b64b79fb06 100644 --- a/yarn-project/circuits.js/src/structs/avm/avm_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/avm/avm_accumulated_data.ts @@ -10,7 +10,7 @@ import { MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, } from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; @@ -38,7 +38,7 @@ export class AvmAccumulatedData { /** * The public data writes made in the AVM execution. */ - public publicDataWrites: Tuple, + public publicDataWrites: Tuple, ) {} getSize() { @@ -58,7 +58,7 @@ export class AvmAccumulatedData { reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), reader.readArray(MAX_UNENCRYPTED_LOGS_PER_TX, ScopedLogHash), - reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), + reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), ); } @@ -79,7 +79,7 @@ export class AvmAccumulatedData { reader.readFieldArray(MAX_NULLIFIERS_PER_TX), reader.readArray(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message), reader.readArray(MAX_UNENCRYPTED_LOGS_PER_TX, ScopedLogHash), - reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), + reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), ); } @@ -97,7 +97,7 @@ export class AvmAccumulatedData { makeTuple(MAX_NULLIFIERS_PER_TX, Fr.zero), makeTuple(MAX_L2_TO_L1_MSGS_PER_TX, ScopedL2ToL1Message.empty), makeTuple(MAX_UNENCRYPTED_LOGS_PER_TX, ScopedLogHash.empty), - makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite.empty), + makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite.empty), ); } diff --git a/yarn-project/circuits.js/src/structs/avm/avm_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/avm/avm_circuit_public_inputs.ts index 8877b4b8003..1a9c1abeb1a 100644 --- a/yarn-project/circuits.js/src/structs/avm/avm_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/avm/avm_circuit_public_inputs.ts @@ -1,4 +1,5 @@ import { makeTuple } from '@aztec/foundation/array'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, FieldReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; @@ -23,6 +24,7 @@ export class AvmCircuitPublicInputs { public startTreeSnapshots: TreeSnapshots, public startGasUsed: Gas, public gasSettings: GasSettings, + public feePayer: AztecAddress, public publicSetupCallRequests: Tuple, public publicAppLogicCallRequests: Tuple, public publicTeardownCallRequest: PublicCallRequest, @@ -44,6 +46,7 @@ export class AvmCircuitPublicInputs { reader.readObject(TreeSnapshots), reader.readObject(Gas), reader.readObject(GasSettings), + reader.readObject(AztecAddress), reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), reader.readObject(PublicCallRequest), @@ -65,6 +68,7 @@ export class AvmCircuitPublicInputs { this.startTreeSnapshots, this.startGasUsed, this.gasSettings, + this.feePayer, this.publicSetupCallRequests, this.publicAppLogicCallRequests, this.publicTeardownCallRequest, @@ -95,6 +99,7 @@ export class AvmCircuitPublicInputs { TreeSnapshots.fromFields(reader), Gas.fromFields(reader), GasSettings.fromFields(reader), + AztecAddress.fromFields(reader), reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), reader.readArray(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest), PublicCallRequest.fromFields(reader), @@ -116,6 +121,7 @@ export class AvmCircuitPublicInputs { TreeSnapshots.empty(), Gas.empty(), GasSettings.empty(), + AztecAddress.zero(), makeTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest.empty), makeTuple(MAX_ENQUEUED_CALLS_PER_TX, PublicCallRequest.empty), PublicCallRequest.empty(), @@ -137,6 +143,7 @@ export class AvmCircuitPublicInputs { startTreeSnapshots: ${inspect(this.startTreeSnapshots)}, startGasUsed: ${inspect(this.startGasUsed)}, gasSettings: ${inspect(this.gasSettings)}, + feePayer: ${inspect(this.feePayer)}, publicSetupCallRequests: [${this.publicSetupCallRequests .filter(x => !x.isEmpty()) .map(h => inspect(h)) diff --git a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts index b7d683de60e..62e1abefdd2 100644 --- a/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/combined_accumulated_data.ts @@ -13,7 +13,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, } from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; @@ -63,7 +63,7 @@ export class CombinedAccumulatedData { /** * All the public data update requests made in this transaction. */ - public publicDataWrites: Tuple, + public publicDataWrites: Tuple, ) {} getSize() { @@ -130,7 +130,7 @@ export class CombinedAccumulatedData { reader.readArray(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash), Fr.fromBuffer(reader), Fr.fromBuffer(reader), - reader.readArray(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), + reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite), ); } @@ -153,7 +153,7 @@ export class CombinedAccumulatedData { makeTuple(MAX_CONTRACT_CLASS_LOGS_PER_TX, ScopedLogHash.empty), Fr.zero(), Fr.zero(), - makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite.empty), + makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataWrite.empty), ); } diff --git a/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts b/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts index 80f627b4fab..fbec824f915 100644 --- a/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts +++ b/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts @@ -92,8 +92,6 @@ export class PublicBaseRollupHints { public start: PartialStateReference, /** Hints used while proving state diff validity. */ public stateDiffHints: PublicBaseStateDiffHints, - /** Public data read hint for accessing the balance of the fee payer. */ - public feePayerFeeJuiceBalanceReadHint: PublicDataHint, /** * Membership witnesses of blocks referred by each of the 2 kernels. */ @@ -109,13 +107,7 @@ export class PublicBaseRollupHints { } static getFields(fields: FieldsOf) { - return [ - fields.start, - fields.stateDiffHints, - fields.feePayerFeeJuiceBalanceReadHint, - fields.archiveRootMembershipWitness, - fields.constants, - ] as const; + return [fields.start, fields.stateDiffHints, fields.archiveRootMembershipWitness, fields.constants] as const; } /** @@ -139,7 +131,6 @@ export class PublicBaseRollupHints { return new PublicBaseRollupHints( reader.readObject(PartialStateReference), reader.readObject(PublicBaseStateDiffHints), - reader.readObject(PublicDataHint), MembershipWitness.fromBuffer(reader, ARCHIVE_HEIGHT), reader.readObject(ConstantRollupData), ); @@ -153,7 +144,6 @@ export class PublicBaseRollupHints { return new PublicBaseRollupHints( PartialStateReference.empty(), PublicBaseStateDiffHints.empty(), - PublicDataHint.empty(), MembershipWitness.empty(ARCHIVE_HEIGHT), ConstantRollupData.empty(), ); diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 59fce246f20..aa47b2becc3 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -57,7 +57,6 @@ import { MAX_PRIVATE_CALL_STACK_LENGTH_PER_CALL, MAX_PRIVATE_LOGS_PER_CALL, MAX_PRIVATE_LOGS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, MaxBlockNumber, @@ -315,7 +314,12 @@ export function makeCombinedAccumulatedData(seed = 1, full = false): CombinedAcc tupleGenerator(MAX_CONTRACT_CLASS_LOGS_PER_TX, makeScopedLogHash, seed + 0xa00, ScopedLogHash.empty), // contract class logs fr(seed + 0xd00), // unencrypted_log_preimages_length fr(seed + 0xe00), // contract_class_log_preimages_length - tupleGenerator(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, makePublicDataWrite, seed + 0xd00, PublicDataWrite.empty), + tupleGenerator( + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + makePublicDataWrite, + seed + 0xd00, + PublicDataWrite.empty, + ), ); } @@ -348,7 +352,7 @@ function makeAvmAccumulatedData(seed = 1) { makeTuple(MAX_NULLIFIERS_PER_TX, fr, seed + 0x100), makeTuple(MAX_L2_TO_L1_MSGS_PER_TX, makeScopedL2ToL1Message, seed + 0x200), makeTuple(MAX_UNENCRYPTED_LOGS_PER_TX, makeScopedLogHash, seed + 0x300), - makeTuple(MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, makePublicDataWrite, seed + 0x400), + makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, makePublicDataWrite, seed + 0x400), ); } @@ -435,6 +439,7 @@ function makeAvmCircuitPublicInputs(seed = 1) { makeTreeSnapshots(seed + 0x10), makeGas(seed + 0x20), makeGasSettings(), + makeAztecAddress(seed + 0x40), makeTuple(MAX_ENQUEUED_CALLS_PER_TX, makePublicCallRequest, seed + 0x100), makeTuple(MAX_ENQUEUED_CALLS_PER_TX, makePublicCallRequest, seed + 0x200), makePublicCallRequest(seed + 0x300), @@ -1111,14 +1116,11 @@ function makePublicBaseRollupHints(seed = 1) { const constants = makeConstantBaseRollupData(0x100); - const feePayerFeeJuiceBalanceReadHint = PublicDataHint.empty(); - return PublicBaseRollupHints.from({ start, stateDiffHints, archiveRootMembershipWitness, constants, - feePayerFeeJuiceBalanceReadHint, }); } diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index 91727ff4918..a5912d95589 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -49,7 +49,7 @@ import { MAX_NULLIFIER_READ_REQUESTS_PER_TX, MAX_PRIVATE_CALL_STACK_LENGTH_PER_TX, MAX_PRIVATE_LOGS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, MaxBlockNumber, type MembershipWitness, @@ -1233,7 +1233,7 @@ export function mapCombinedAccumulatedDataFromNoir(combinedAccumulatedData: Comb mapFieldFromNoir(combinedAccumulatedData.contract_class_log_preimages_length), mapTupleFromNoir( combinedAccumulatedData.public_data_writes, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, mapPublicDataWriteFromNoir, ), ); @@ -1606,6 +1606,7 @@ function mapAvmCircuitPublicInputsToNoir(inputs: AvmCircuitPublicInputs): AvmCir start_tree_snapshots: mapTreeSnapshotsToNoir(inputs.startTreeSnapshots), start_gas_used: mapGasToNoir(inputs.startGasUsed), gas_settings: mapGasSettingsToNoir(inputs.gasSettings), + fee_payer: mapAztecAddressToNoir(inputs.feePayer), public_setup_call_requests: mapTuple(inputs.publicSetupCallRequests, mapPublicCallRequestToNoir), public_app_logic_call_requests: mapTuple(inputs.publicAppLogicCallRequests, mapPublicCallRequestToNoir), public_teardown_call_request: mapPublicCallRequestToNoir(inputs.publicTeardownCallRequest), @@ -2178,7 +2179,6 @@ export function mapPublicBaseRollupInputsToNoir(inputs: PublicBaseRollupInputs): archive_root_membership_witness: mapMembershipWitnessToNoir(inputs.hints.archiveRootMembershipWitness), constants: mapConstantRollupDataToNoir(inputs.hints.constants), - fee_payer_fee_juice_balance_read_hint: mapPublicDataHintToNoir(inputs.hints.feePayerFeeJuiceBalanceReadHint), }; } diff --git a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts index a85c18145f4..d6c913125ca 100644 --- a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts +++ b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts @@ -95,13 +95,6 @@ export async function buildBaseRollupHints( i < noteHashSubtreeSiblingPathArray.length ? noteHashSubtreeSiblingPathArray[i] : Fr.ZERO, ); - // Create data hint for reading fee payer initial balance in Fee Juice - // If no fee payer is set, read hint should be empty - const leafSlot = computeFeePayerBalanceLeafSlot(tx.data.feePayer); - const feePayerFeeJuiceBalanceReadHint = tx.data.feePayer.isZero() - ? PublicDataHint.empty() - : await getPublicDataHint(db, leafSlot.toBigInt()); - // Update the note hash trees with the new items being inserted to get the new roots // that will be used by the next iteration of the base rollup circuit, skipping the empty ones const noteHashes = padArrayEnd(tx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX); @@ -184,7 +177,6 @@ export async function buildBaseRollupHints( return PublicBaseRollupHints.from({ start, stateDiffHints, - feePayerFeeJuiceBalanceReadHint: feePayerFeeJuiceBalanceReadHint, archiveRootMembershipWitness, constants, }); @@ -197,6 +189,13 @@ export async function buildBaseRollupHints( throw new Error(`More than one public data write in a private only tx`); } + // Create data hint for reading fee payer initial balance in Fee Juice + // If no fee payer is set, read hint should be empty + const leafSlot = computeFeePayerBalanceLeafSlot(tx.data.feePayer); + const feePayerFeeJuiceBalanceReadHint = tx.data.feePayer.isZero() + ? PublicDataHint.empty() + : await getPublicDataHint(db, leafSlot.toBigInt()); + const feeWriteLowLeafPreimage = txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages[0] || PublicDataTreeLeafPreimage.empty(); const feeWriteLowLeafMembershipWitness = diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 6c621470172..e35b4d2519f 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -711,7 +711,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(await context.persistableState.peekStorage(address, slot)).toEqual(value0); expect(trace.tracePublicStorageWrite).toHaveBeenCalledTimes(1); - expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, slot, value0); + expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, slot, value0, false); }); it('Should read value in storage (single)', async () => { @@ -739,7 +739,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.output).toEqual([value0]); expect(trace.tracePublicStorageWrite).toHaveBeenCalledTimes(1); - expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, slot, value0); + expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, slot, value0, false); expect(trace.tracePublicStorageRead).toHaveBeenCalledTimes(1); expect(trace.tracePublicStorageRead).toHaveBeenCalledWith(address, slot, value0); }); @@ -757,8 +757,8 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(await context.persistableState.peekStorage(address, listSlot1)).toEqual(calldata[1]); expect(trace.tracePublicStorageWrite).toHaveBeenCalledTimes(2); - expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, listSlot0, value0); - expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, listSlot1, value1); + expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, listSlot0, value0, false); + expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, listSlot1, value1, false); }); it('Should read a value in storage (list)', async () => { @@ -796,7 +796,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(await context.persistableState.peekStorage(address, mapSlot)).toEqual(value0); expect(trace.tracePublicStorageWrite).toHaveBeenCalledTimes(1); - expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, mapSlot, value0); + expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, mapSlot, value0, false); }); it('Should read-add-set a value in storage (map)', async () => { @@ -817,7 +817,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(trace.tracePublicStorageRead).toHaveBeenCalledTimes(1); expect(trace.tracePublicStorageRead).toHaveBeenCalledWith(address, mapSlot, Fr.zero()); expect(trace.tracePublicStorageWrite).toHaveBeenCalledTimes(1); - expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, mapSlot, value0); + expect(trace.tracePublicStorageWrite).toHaveBeenCalledWith(address, mapSlot, value0, false); }); it('Should read value in storage (map)', async () => { @@ -1159,6 +1159,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { address, slot0, value0, + false, lowLeafPreimage, new Fr(lowLeafIndex), lowLeafPath, @@ -1284,6 +1285,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { address, slot0, value0, + false, lowLeafPreimage, new Fr(lowLeafIndex), lowLeafPath, diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 5a2a6737d64..8cdc8dba107 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -152,7 +152,7 @@ export class AvmPersistableStateManager { * @param slot - the slot in the contract's storage being written to * @param value - the value being written to the slot */ - public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr): Promise { + public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr, protocolWrite = false): Promise { this.log.debug(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`); const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot); this.log.debug(`leafSlot=${leafSlot}`); @@ -183,6 +183,7 @@ export class AvmPersistableStateManager { contractAddress, slot, value, + protocolWrite, lowLeafPreimage, new Fr(lowLeafIndex), lowLeafPath, @@ -190,7 +191,7 @@ export class AvmPersistableStateManager { insertionPath, ); } else { - this.trace.tracePublicStorageWrite(contractAddress, slot, value); + this.trace.tracePublicStorageWrite(contractAddress, slot, value, protocolWrite); } } diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts index 6db2f0ed32f..7ede684befe 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.test.ts @@ -18,6 +18,7 @@ import { NoteHash, Nullifier, NullifierLeafPreimage, + PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeafPreimage, PublicDataUpdateRequest, SerializableContractInstance, @@ -72,6 +73,7 @@ describe('Enqueued-call Side Effect Trace', () => { address, slot, value, + false, lowLeafPreimage, lowLeafIndex, lowLeafSiblingPath, @@ -209,11 +211,11 @@ describe('Enqueued-call Side Effect Trace', () => { }); describe('Maximum accesses', () => { - it('Should enforce maximum number of public storage writes', () => { + it('Should enforce maximum number of user public storage writes', () => { for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; i++) { const lowLeafPreimage = new PublicDataTreeLeafPreimage(new Fr(i), new Fr(i), Fr.ZERO, 0n); const newLeafPreimage = new PublicDataTreeLeafPreimage(new Fr(i + 1), new Fr(i + 1), Fr.ZERO, 0n); - trace.tracePublicStorageWrite(address, slot, value, lowLeafPreimage, Fr.ZERO, [], newLeafPreimage, []); + trace.tracePublicStorageWrite(address, slot, value, false, lowLeafPreimage, Fr.ZERO, [], newLeafPreimage, []); } const leafPreimage = new PublicDataTreeLeafPreimage(new Fr(42), new Fr(42), Fr.ZERO, 0n); expect(() => @@ -221,6 +223,7 @@ describe('Enqueued-call Side Effect Trace', () => { AztecAddress.fromNumber(42), new Fr(42), value, + false, leafPreimage, Fr.ZERO, [], @@ -228,6 +231,56 @@ describe('Enqueued-call Side Effect Trace', () => { [], ), ).toThrow(SideEffectLimitReachedError); + // Still allows protocol writes + expect(() => + trace.tracePublicStorageWrite( + AztecAddress.fromNumber(42), + new Fr(42), + value, + true, + leafPreimage, + Fr.ZERO, + [], + leafPreimage, + [], + ), + ).not.toThrow(); + }); + + it('Should enforce maximum number of protocol public storage writes', () => { + for (let i = 0; i < PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; i++) { + const lowLeafPreimage = new PublicDataTreeLeafPreimage(new Fr(i), new Fr(i), Fr.ZERO, 0n); + const newLeafPreimage = new PublicDataTreeLeafPreimage(new Fr(i + 1), new Fr(i + 1), Fr.ZERO, 0n); + trace.tracePublicStorageWrite(address, slot, value, true, lowLeafPreimage, Fr.ZERO, [], newLeafPreimage, []); + } + const leafPreimage = new PublicDataTreeLeafPreimage(new Fr(42), new Fr(42), Fr.ZERO, 0n); + expect(() => + trace.tracePublicStorageWrite( + AztecAddress.fromNumber(42), + new Fr(42), + value, + true, + leafPreimage, + Fr.ZERO, + [], + leafPreimage, + [], + ), + ).toThrow(SideEffectLimitReachedError); + // Still allows user writes + expect(() => + trace.tracePublicStorageWrite( + AztecAddress.fromNumber(42), + new Fr(42), + value, + false, + leafPreimage, + Fr.ZERO, + [], + leafPreimage, + [], + ), + ).not.toThrow(); }); it('Should enforce maximum number of new note hashes', () => { @@ -273,13 +326,17 @@ describe('Enqueued-call Side Effect Trace', () => { 0, new SideEffectArrayLengths( MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_L2_TO_L1_MSGS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, ), ); - expect(() => trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), new Fr(42))).toThrow( + expect(() => trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), new Fr(42), false)).toThrow( + SideEffectLimitReachedError, + ); + expect(() => trace.tracePublicStorageWrite(AztecAddress.fromNumber(42), new Fr(42), new Fr(42), true)).toThrow( SideEffectLimitReachedError, ); expect(() => trace.traceNewNoteHash(AztecAddress.fromNumber(42), new Fr(42), new Fr(42))).toThrow( @@ -305,7 +362,7 @@ describe('Enqueued-call Side Effect Trace', () => { const lowLeafPreimage = new NullifierLeafPreimage(utxo, Fr.ZERO, 0n); nestedTrace.tracePublicStorageRead(address, slot, value, leafPreimage, Fr.ZERO, []); testCounter++; - nestedTrace.tracePublicStorageWrite(address, slot, value, leafPreimage, Fr.ZERO, [], leafPreimage, []); + nestedTrace.tracePublicStorageWrite(address, slot, value, false, leafPreimage, Fr.ZERO, [], leafPreimage, []); testCounter++; nestedTrace.traceNoteHashCheck(address, utxo, leafIndex, existsDefault, []); // counter does not increment for note hash checks diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts index 4963632c5e7..5ff5e7427c1 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts @@ -25,12 +25,14 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_UNENCRYPTED_LOGS_PER_TX, NOTE_HASH_TREE_HEIGHT, NULLIFIER_TREE_HEIGHT, NoteHash, Nullifier, NullifierLeafPreimage, + PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PUBLIC_DATA_TREE_HEIGHT, PrivateToAvmAccumulatedData, PrivateToAvmAccumulatedDataArrayLengths, @@ -83,6 +85,7 @@ export type SideEffects = { export class SideEffectArrayLengths { constructor( public readonly publicDataWrites: number, + public readonly protocolPublicDataWrites: number, public readonly noteHashes: number, public readonly nullifiers: number, public readonly l2ToL1Msgs: number, @@ -90,7 +93,7 @@ export class SideEffectArrayLengths { ) {} static empty() { - return new this(0, 0, 0, 0, 0); + return new this(0, 0, 0, 0, 0, 0); } } @@ -106,6 +109,8 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI private enqueuedCalls: PublicCallRequest[] = []; private publicDataWrites: PublicDataUpdateRequest[] = []; + private protocolPublicDataWritesLength: number = 0; + private userPublicDataWritesLength: number = 0; private noteHashes: ScopedNoteHash[] = []; private nullifiers: Nullifier[] = []; private l2ToL1Messages: ScopedL2ToL1Message[] = []; @@ -134,7 +139,8 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI return new PublicEnqueuedCallSideEffectTrace( this.sideEffectCounter, new SideEffectArrayLengths( - this.previousSideEffectArrayLengths.publicDataWrites + this.publicDataWrites.length, + this.previousSideEffectArrayLengths.publicDataWrites + this.userPublicDataWritesLength, + this.previousSideEffectArrayLengths.protocolPublicDataWrites + this.protocolPublicDataWritesLength, this.previousSideEffectArrayLengths.noteHashes + this.noteHashes.length, this.previousSideEffectArrayLengths.nullifiers + this.nullifiers.length, this.previousSideEffectArrayLengths.l2ToL1Msgs + this.l2ToL1Messages.length, @@ -207,20 +213,35 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI contractAddress: AztecAddress, slot: Fr, value: Fr, + protocolWrite: boolean, lowLeafPreimage: PublicDataTreeLeafPreimage = PublicDataTreeLeafPreimage.empty(), lowLeafIndex: Fr = Fr.zero(), lowLeafPath: Fr[] = emptyPublicDataPath(), newLeafPreimage: PublicDataTreeLeafPreimage = PublicDataTreeLeafPreimage.empty(), insertionPath: Fr[] = emptyPublicDataPath(), ) { - if ( - this.publicDataWrites.length + this.previousSideEffectArrayLengths.publicDataWrites >= - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - ) { - throw new SideEffectLimitReachedError( - 'public data (contract storage) write', - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ); + if (protocolWrite) { + if ( + this.protocolPublicDataWritesLength + this.previousSideEffectArrayLengths.protocolPublicDataWrites >= + PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + ) { + throw new SideEffectLimitReachedError( + 'protocol public data (contract storage) write', + PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); + } + this.protocolPublicDataWritesLength++; + } else { + if ( + this.userPublicDataWritesLength + this.previousSideEffectArrayLengths.publicDataWrites >= + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + ) { + throw new SideEffectLimitReachedError( + 'public data (contract storage) write', + MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ); + } + this.userPublicDataWritesLength++; } const leafSlot = computePublicDataTreeLeafSlot(contractAddress, slot); @@ -233,7 +254,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI ); this.log.debug( - `Traced public data write (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter})`, + `Traced public data write (address=${contractAddress}, slot=${slot}): value=${value} (counter=${this.sideEffectCounter}, isProtocol:${protocolWrite})`, ); this.incrementSideEffectCounter(); } @@ -471,6 +492,8 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI startGasUsed: Gas, /** How much gas was available for this public execution. */ gasLimits: GasSettings, + /** Address of the fee payer. */ + feePayer: AztecAddress, /** Call requests for setup phase. */ publicSetupCallRequests: PublicCallRequest[], /** Call requests for app logic phase. */ @@ -494,6 +517,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI startTreeSnapshots, startGasUsed, gasLimits, + feePayer, padArrayEnd(publicSetupCallRequests, PublicCallRequest.empty(), MAX_ENQUEUED_CALLS_PER_TX), padArrayEnd(publicAppLogicCallRequests, PublicCallRequest.empty(), MAX_ENQUEUED_CALLS_PER_TX), publicTeardownCallRequest, @@ -549,7 +573,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI padArrayEnd( this.publicDataWrites.map(w => new PublicDataWrite(w.leafSlot, w.newValue)), PublicDataWrite.empty(), - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, ), ); } diff --git a/yarn-project/simulator/src/public/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor.test.ts index 2b2a0a27fbf..7fbbfe59928 100644 --- a/yarn-project/simulator/src/public/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor.test.ts @@ -10,7 +10,6 @@ import { } from '@aztec/circuit-types'; import { AvmCircuitInputs, - type AvmCircuitPublicInputs, AztecAddress, BlockHeader, Fr, @@ -19,7 +18,6 @@ import { GlobalVariables, PublicDataWrite, RevertCode, - countAccumulatedItems, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -39,7 +37,6 @@ describe('public_processor', () => { let root: Buffer; let mockedEnqueuedCallsResult: PublicTxResult; - let mockedAvmOutput: AvmCircuitPublicInputs; let processor: PublicProcessor; @@ -61,7 +58,6 @@ describe('public_processor', () => { root = Buffer.alloc(32, 5); const avmCircuitInputs = AvmCircuitInputs.empty(); - mockedAvmOutput = avmCircuitInputs.output; mockedEnqueuedCallsResult = { avmProvingRequest: { type: ProvingRequestType.PUBLIC_VM, @@ -220,75 +216,17 @@ describe('public_processor', () => { expect(handler.addNewTx).toHaveBeenCalledWith(processed[0]); }); - it('injects balance update with public enqueued call', async function () { - const txFee = new Fr(567); - mockedAvmOutput.transactionFee = txFee; - - const tx = mockTxWithPublicCalls({ - feePayer, - }); - - const [processed, failed] = await processor.process([tx], 1, handler); - - expect(processed).toHaveLength(1); - expect(processed[0].hash).toEqual(tx.getTxHash()); - expect(processed[0].data.feePayer).toEqual(feePayer); - expect(processed[0].txEffect.transactionFee).toEqual(txFee); - expect(processed[0].txEffect.publicDataWrites[0]).toEqual( - new PublicDataWrite(computeFeePayerBalanceLeafSlot(feePayer), initialBalance.sub(txFee)), - ); - expect(failed).toEqual([]); - - expect(worldStateDB.commit).toHaveBeenCalledTimes(1); - expect(worldStateDB.storageWrite).toHaveBeenCalledTimes(1); - - expect(handler.addNewTx).toHaveBeenCalledWith(processed[0]); - }); - - it('tweaks existing balance update from claim', async function () { - const txFee = new Fr(567); - const pendingBalance = new Fr(2000); - const pendingWrites = [ - new PublicDataWrite(new Fr(888n), new Fr(999)), - new PublicDataWrite(computeFeePayerBalanceLeafSlot(feePayer), pendingBalance), - new PublicDataWrite(new Fr(666n), new Fr(777)), - ]; - mockedAvmOutput.transactionFee = txFee; - mockedAvmOutput.accumulatedData.publicDataWrites[0] = pendingWrites[0]; - mockedAvmOutput.accumulatedData.publicDataWrites[1] = pendingWrites[1]; - mockedAvmOutput.accumulatedData.publicDataWrites[2] = pendingWrites[2]; - - const tx = mockTxWithPublicCalls({ - feePayer, - }); - - const [processed, failed] = await processor.process([tx], 1, handler); - - expect(processed).toHaveLength(1); - expect(processed[0].hash).toEqual(tx.getTxHash()); - expect(processed[0].data.feePayer).toEqual(feePayer); - expect(countAccumulatedItems(processed[0].txEffect.publicDataWrites)).toBe(3); - expect(processed[0].txEffect.publicDataWrites.slice(0, 3)).toEqual([ - pendingWrites[0], - new PublicDataWrite(computeFeePayerBalanceLeafSlot(feePayer), pendingBalance.sub(txFee)), - pendingWrites[2], - ]); - expect(failed).toEqual([]); - - expect(worldStateDB.commit).toHaveBeenCalledTimes(1); - expect(worldStateDB.storageWrite).toHaveBeenCalledTimes(1); - - expect(handler.addNewTx).toHaveBeenCalledWith(processed[0]); - }); - it('rejects tx if fee payer has not enough balance', async function () { - const txFee = initialBalance.add(new Fr(1)); - mockedAvmOutput.transactionFee = txFee; - - const tx = mockTxWithPublicCalls({ + const tx = mockPrivateOnlyTx({ feePayer, }); + const privateGasUsed = new Gas(initialBalance.toNumber(), initialBalance.toNumber()); + if (privateGasUsed.computeFee(gasFees) < initialBalance) { + throw new Error('Test setup error: gas fees are too low'); + } + tx.data.gasUsed = privateGasUsed; + const [processed, failed] = await processor.process([tx], 1, handler); expect(processed).toEqual([]); diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 6045e071ed5..35c9d0552b6 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -194,16 +194,11 @@ export class PublicProcessor { } /** - * Creates the final set of data update requests for the transaction. This includes the - * set of public data update requests as returned by the public kernel, plus a data update - * request for updating fee balance. It also updates the local public state db. - * See build_or_patch_payment_update_request in base_rollup_inputs.nr for more details. + * Creates the public data write for paying the tx fee. + * This is used in private only txs, since for txs with public calls + * the avm handles the fee payment itself. */ - private async getFeePaymentPublicDataWrite( - publicDataWrites: PublicDataWrite[], - txFee: Fr, - feePayer: AztecAddress, - ): Promise { + private async getFeePaymentPublicDataWrite(txFee: Fr, feePayer: AztecAddress): Promise { if (feePayer.isZero()) { this.log.debug(`No one is paying the fee of ${txFee.toBigInt()}`); return; @@ -215,11 +210,7 @@ export class PublicProcessor { this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${feePayer}`); - const existingBalanceWrite = publicDataWrites.find(write => write.leafSlot.equals(leafSlot)); - - const balance = existingBalanceWrite - ? existingBalanceWrite.value - : await this.worldStateDB.storageRead(feeJuiceAddress, balanceSlot); + const balance = await this.worldStateDB.storageRead(feeJuiceAddress, balanceSlot); if (balance.lt(txFee)) { throw new Error( @@ -240,12 +231,7 @@ export class PublicProcessor { const gasFees = this.globalVariables.gasFees; const transactionFee = tx.data.gasUsed.computeFee(gasFees); - const accumulatedData = tx.data.forRollup!.end; - const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite( - accumulatedData.publicDataWrites, - transactionFee, - tx.data.feePayer, - ); + const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite(transactionFee, tx.data.feePayer); const processedTx = makeProcessedTxFromPrivateOnlyTx( tx, @@ -289,21 +275,7 @@ export class PublicProcessor { const durationMs = timer.ms(); this.metrics.recordTx(phaseCount, durationMs); - const data = avmProvingRequest.inputs.output; - const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite( - data.accumulatedData.publicDataWrites, - data.transactionFee, - tx.data.feePayer, - ); - - const processedTx = makeProcessedTxFromTxWithPublicCalls( - tx, - avmProvingRequest, - feePaymentPublicDataWrite, - gasUsed, - revertCode, - revertReason, - ); + const processedTx = makeProcessedTxFromTxWithPublicCalls(tx, avmProvingRequest, gasUsed, revertCode, revertReason); const returnValues = processedPhases.find(({ phase }) => phase === TxExecutionPhase.APP_LOGIC)?.returnValues ?? []; diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 23d0a14bd53..f26b259a912 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -13,6 +13,7 @@ import { AppendOnlyTreeSnapshot, AvmCircuitInputs, type AvmCircuitPublicInputs, + type AztecAddress, Fr, Gas, type GasSettings, @@ -70,6 +71,7 @@ export class PublicTxContext { private readonly teardownExecutionRequests: PublicExecutionRequest[], public readonly nonRevertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, public readonly revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, + public readonly feePayer: AztecAddress, public trace: PublicEnqueuedCallSideEffectTrace, // FIXME(dbanks12): should be private ) { this.log = createLogger(`simulator:public_tx_context`); @@ -87,6 +89,7 @@ export class PublicTxContext { const previousAccumulatedDataArrayLengths = new SideEffectArrayLengths( /*publicDataWrites*/ 0, + /*protocolPublicDataWrites*/ 0, countAccumulatedItems(nonRevertibleAccumulatedDataFromPrivate.noteHashes), /*nullifiers=*/ 0, countAccumulatedItems(nonRevertibleAccumulatedDataFromPrivate.l2ToL1Msgs), @@ -114,6 +117,7 @@ export class PublicTxContext { getExecutionRequestsByPhase(tx, TxExecutionPhase.TEARDOWN), tx.data.forPublic!.nonRevertibleAccumulatedData, tx.data.forPublic!.revertibleAccumulatedData, + tx.data.feePayer, enqueuedCallTrace, ); } @@ -322,6 +326,7 @@ export class PublicTxContext { this.startStateReference, this.startGasUsed, this.gasSettings, + this.feePayer, this.setupCallRequests, this.appLogicCallRequests, this.teardownCallRequests, diff --git a/yarn-project/simulator/src/public/public_tx_simulator.test.ts b/yarn-project/simulator/src/public/public_tx_simulator.test.ts index 87136847146..4eca8f04d7e 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.test.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.test.ts @@ -438,15 +438,9 @@ describe('public_tx_simulator', () => { const numPublicDataWrites = 3; expect(countAccumulatedItems(output.accumulatedData.publicDataWrites)).toBe(numPublicDataWrites); expect(output.accumulatedData.publicDataWrites.slice(0, numPublicDataWrites)).toEqual([ - // squashed - // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotA), fr(0x101)), + new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotA), fr(0x103)), // 0x101 replaced with 0x103 new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotB), fr(0x151)), - - new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotA), fr(0x103)), - // squashed - // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x201)), - // new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x102)), - new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x152)), + new PublicDataWrite(computePublicDataTreeLeafSlot(contractAddress, contractSlotC), fr(0x152)), // 0x201 replaced with 0x102 and then 0x152 ]); }); diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index b77ff48ae7d..4e14d76987a 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -20,6 +20,7 @@ import { } from '@aztec/circuits.js'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; +import { ProtocolContractAddress } from '@aztec/protocol-contracts'; import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client'; import { strict as assert } from 'assert'; @@ -29,6 +30,7 @@ import { type AvmPersistableStateManager, AvmSimulator } from '../avm/index.js'; import { NullifierCollisionError } from '../avm/journal/nullifiers.js'; import { getPublicFunctionDebugName } from '../common/debug_fn_name.js'; import { ExecutorMetrics } from './executor_metrics.js'; +import { computeFeePayerBalanceStorageSlot } from './fee_payment.js'; import { type WorldStateDB } from './public_db_sources.js'; import { PublicTxContext } from './public_tx_context.js'; @@ -106,11 +108,14 @@ export class PublicTxSimulator { const appLogicResult: ProcessedPhase = await this.simulateAppLogicPhase(context); processedPhases.push(appLogicResult); } + if (context.hasPhase(TxExecutionPhase.TEARDOWN)) { const teardownResult: ProcessedPhase = await this.simulateTeardownPhase(context); processedPhases.push(teardownResult); } + context.halt(); + await this.payFee(context); const endStateReference = await this.db.getStateReference(); @@ -400,4 +405,30 @@ export class PublicTxSimulator { } } } + + private async payFee(context: PublicTxContext) { + const txFee = context.getTransactionFee(TxExecutionPhase.TEARDOWN); + + if (context.feePayer.isZero()) { + this.log.debug(`No one is paying the fee of ${txFee.toBigInt()}`); + return; + } + + const feeJuiceAddress = ProtocolContractAddress.FeeJuice; + const balanceSlot = computeFeePayerBalanceStorageSlot(context.feePayer); + + this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${context.feePayer}`); + const stateManager = context.state.getActiveStateManager(); + + const currentBalance = await stateManager.readStorage(feeJuiceAddress, balanceSlot); + + if (currentBalance.lt(txFee)) { + throw new Error( + `Not enough balance for fee payer to pay for transaction (got ${currentBalance.toBigInt()} needs ${txFee.toBigInt()})`, + ); + } + + const updatedBalance = currentBalance.sub(txFee); + await stateManager.writeStorage(feeJuiceAddress, balanceSlot, updatedBalance, true); + } } diff --git a/yarn-project/simulator/src/public/side_effect_trace_interface.ts b/yarn-project/simulator/src/public/side_effect_trace_interface.ts index de0c9d9aa01..d422f5fdb82 100644 --- a/yarn-project/simulator/src/public/side_effect_trace_interface.ts +++ b/yarn-project/simulator/src/public/side_effect_trace_interface.ts @@ -31,6 +31,7 @@ export interface PublicSideEffectTraceInterface { contractAddress: AztecAddress, slot: Fr, // This is the storage slot not the computed leaf slot value: Fr, + protocolWrite: boolean, lowLeafPreimage?: PublicDataTreeLeafPreimage, lowLeafIndex?: Fr, lowLeafPath?: Fr[], diff --git a/yarn-project/simulator/src/public/transitional_adapters.ts b/yarn-project/simulator/src/public/transitional_adapters.ts index 09ec0094110..cdf0c0ab48c 100644 --- a/yarn-project/simulator/src/public/transitional_adapters.ts +++ b/yarn-project/simulator/src/public/transitional_adapters.ts @@ -1,12 +1,13 @@ import { type AvmCircuitPublicInputs, - type Fr, + type AztecAddress, + Fr, type Gas, type GasSettings, type GlobalVariables, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PrivateToAvmAccumulatedData, PrivateToAvmAccumulatedDataArrayLengths, type PrivateToPublicAccumulatedData, @@ -30,6 +31,7 @@ export function generateAvmCircuitPublicInputs( startStateReference: StateReference, startGasUsed: Gas, gasSettings: GasSettings, + feePayer: AztecAddress, setupCallRequests: PublicCallRequest[], appLogicCallRequests: PublicCallRequest[], teardownCallRequests: PublicCallRequest[], @@ -52,6 +54,7 @@ export function generateAvmCircuitPublicInputs( startTreeSnapshots, startGasUsed, gasSettings, + feePayer, setupCallRequests, appLogicCallRequests, teardownCallRequests.length ? teardownCallRequests[0] : PublicCallRequest.empty(), @@ -121,28 +124,18 @@ export function generateAvmCircuitPublicInputs( MAX_L2_TO_L1_MSGS_PER_TX, ); - const dedupedPublicDataWrites: Array = []; - const leafSlotOccurences: Map = new Map(); + // Maps slot to value. Maps in TS are iterable in insertion order, which is exactly what we want for + // squashing "to the left", where the first occurrence of a slot uses the value of the last write to it, + // and the rest occurrences are omitted + const squashedPublicDataWrites: Map = new Map(); for (const publicDataWrite of avmCircuitPublicInputs.accumulatedData.publicDataWrites) { - const slot = publicDataWrite.leafSlot.toBigInt(); - const prevOccurrences = leafSlotOccurences.get(slot) || 0; - leafSlotOccurences.set(slot, prevOccurrences + 1); - } - - for (const publicDataWrite of avmCircuitPublicInputs.accumulatedData.publicDataWrites) { - const slot = publicDataWrite.leafSlot.toBigInt(); - const prevOccurrences = leafSlotOccurences.get(slot) || 0; - if (prevOccurrences === 1) { - dedupedPublicDataWrites.push(publicDataWrite); - } else { - leafSlotOccurences.set(slot, prevOccurrences - 1); - } + squashedPublicDataWrites.set(publicDataWrite.leafSlot.toBigInt(), publicDataWrite.value); } avmCircuitPublicInputs.accumulatedData.publicDataWrites = padArrayEnd( - dedupedPublicDataWrites, + Array.from(squashedPublicDataWrites.entries()).map(([slot, value]) => new PublicDataWrite(new Fr(slot), value)), PublicDataWrite.empty(), - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, ); //console.log(`AvmCircuitPublicInputs:\n${inspect(avmCircuitPublicInputs)}`); return avmCircuitPublicInputs;