diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp index ca121ebefa29..7f0e5a035d37 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/errors.hpp @@ -20,6 +20,7 @@ enum class AvmError : uint32_t { RADIX_OUT_OF_BOUNDS, DUPLICATE_NULLIFIER, SIDE_EFFECT_LIMIT_REACHED, + STATIC_CALL_ALTERATION }; } // namespace bb::avm_trace diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.cpp index f3332c79a69c..366d403e54e1 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.cpp @@ -146,6 +146,8 @@ FF AvmMerkleTreeTraceBuilder::perform_storage_write([[maybe_unused]] uint32_t cl // Update the low leaf tree_snapshots.public_data_tree.root = unconstrained_update_leaf_index(low_preimage_hash, static_cast(low_index), low_path); + // Update the set of writes to unique slots + public_data_unique_writes.insert(slot); return tree_snapshots.public_data_tree.root; } // Check the low leaf conditions (i.e. the slot is sandwiched by the low nullifier, or the new slot is a max @@ -172,6 +174,8 @@ FF AvmMerkleTreeTraceBuilder::perform_storage_write([[maybe_unused]] uint32_t cl // Insert the new leaf into the tree tree_snapshots.public_data_tree.root = unconstrained_update_leaf_index(leaf_preimage_hash, index, insertion_path); tree_snapshots.public_data_tree.size++; + // Update the set of writes to unique slots + public_data_unique_writes.insert(slot); return tree_snapshots.public_data_tree.root; } diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.hpp index c9bb18fc1246..6b80357b532a 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.hpp @@ -6,6 +6,7 @@ #include "barretenberg/vm/avm/trace/gadgets/poseidon2.hpp" #include "barretenberg/vm/avm/trace/public_inputs.hpp" +#include #include namespace bb::avm_trace { @@ -42,7 +43,12 @@ class AvmMerkleTreeTraceBuilder { FF compute_public_tree_leaf_slot(uint32_t clk, FF contract_address, FF leaf_index); TreeSnapshots& get_tree_snapshots() { return tree_snapshots; } - void set_tree_snapshots(TreeSnapshots& tree_snapshots) { this->tree_snapshots = tree_snapshots; } + void restore_tree_state(TreeSnapshots& tree_snapshots, std::unordered_set& public_data_unique_writes) + { + this->tree_snapshots = tree_snapshots; + this->public_data_unique_writes = public_data_unique_writes; + } + std::unordered_set get_public_data_unique_writes() { return public_data_unique_writes; } // Public Data Tree bool perform_storage_read(uint32_t clk, @@ -119,6 +125,7 @@ class AvmMerkleTreeTraceBuilder { std::vector merkle_check_trace; TreeSnapshots non_revertible_tree_snapshots; TreeSnapshots tree_snapshots; + std::unordered_set public_data_unique_writes; MerkleEntry compute_root_from_path(uint32_t clk, const FF& leaf_value, const uint64_t leaf_index, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index 3f94d7762778..1dfad0dad0b4 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -100,7 +100,9 @@ uint32_t finalize_rng_chks_for_testing(std::vector& main_trace, auto old_size = main_trace.size(); for (auto const& clk : custom_clk) { if (clk >= old_size) { - main_trace.push_back(Row{ .main_clk = FF(clk) }); + Row row{}; + row.main_clk = clk; + main_trace.push_back(row); } } @@ -219,6 +221,19 @@ uint32_t AvmTraceBuilder::get_inserted_note_hashes_count() public_inputs.start_tree_snapshots.note_hash_tree.size; } +uint32_t AvmTraceBuilder::get_inserted_nullifiers_count() +{ + return merkle_tree_trace_builder.get_tree_snapshots().nullifier_tree.size - + public_inputs.start_tree_snapshots.nullifier_tree.size; +} + +// Keeping track of the public data writes isn't as simple as comparing sizes +// because of updates to leaves that were already in the tree and state squashing +uint32_t AvmTraceBuilder::get_public_data_writes_count() +{ + return static_cast(merkle_tree_trace_builder.get_public_data_unique_writes().size()); +} + void AvmTraceBuilder::insert_private_state(const std::vector& siloed_nullifiers, const std::vector& unique_note_hashes) { @@ -297,6 +312,7 @@ void AvmTraceBuilder::pay_fee() throw std::runtime_error("Not enough balance for fee payer to pay for transaction"); } + // TS equivalent: // 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); @@ -2623,11 +2639,14 @@ AvmError AvmTraceBuilder::op_sload(uint8_t indirect, uint32_t slot_offset, uint3 AvmError AvmTraceBuilder::op_sstore(uint8_t indirect, uint32_t src_offset, uint32_t slot_offset) { - // We keep the first encountered error - AvmError error = AvmError::NO_ERROR; auto clk = static_cast(main_trace.size()) + 1; + uint32_t unique_public_data_slot_writes = get_public_data_writes_count(); + AvmError error = current_ext_call_ctx.is_static_call ? AvmError::STATIC_CALL_ALTERATION + : unique_public_data_slot_writes >= MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + ? AvmError::SIDE_EFFECT_LIMIT_REACHED + : AvmError::NO_ERROR; - if (storage_write_counter >= MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) { + if (!is_ok(error)) { // 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. @@ -2805,8 +2824,12 @@ AvmError AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash { auto const clk = static_cast(main_trace.size()) + 1; uint32_t inserted_note_hashes_count = get_inserted_note_hashes_count(); - if (inserted_note_hashes_count >= MAX_NOTE_HASHES_PER_TX) { - AvmError error = AvmError::SIDE_EFFECT_LIMIT_REACHED; + auto [row, error] = create_kernel_output_opcode(indirect, clk, note_hash_offset); + error = current_ext_call_ctx.is_static_call ? AvmError::STATIC_CALL_ALTERATION + : inserted_note_hashes_count >= MAX_NOTE_HASHES_PER_TX ? AvmError::SIDE_EFFECT_LIMIT_REACHED + : AvmError::NO_ERROR; + + if (!is_ok(error)) { auto row = Row{ .main_clk = clk, .main_internal_return_ptr = internal_return_ptr, @@ -2820,7 +2843,6 @@ AvmError AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash return error; } - auto [row, error] = create_kernel_output_opcode(indirect, clk, note_hash_offset); row.main_sel_op_emit_note_hash = FF(1); row.main_op_err = FF(static_cast(!is_ok(error))); @@ -2952,12 +2974,14 @@ AvmError AvmTraceBuilder::op_nullifier_exists(uint8_t indirect, AvmError AvmTraceBuilder::op_emit_nullifier(uint8_t indirect, uint32_t nullifier_offset) { - // We keep the first encountered error - AvmError error = AvmError::NO_ERROR; auto const clk = static_cast(main_trace.size()) + 1; - if (nullifier_write_counter >= MAX_NULLIFIERS_PER_TX) { - error = AvmError::SIDE_EFFECT_LIMIT_REACHED; + uint32_t inserted_nullifier_count = get_inserted_nullifiers_count(); + AvmError error = current_ext_call_ctx.is_static_call ? AvmError::STATIC_CALL_ALTERATION + : inserted_nullifier_count >= MAX_NULLIFIERS_PER_TX ? AvmError::SIDE_EFFECT_LIMIT_REACHED + : AvmError::NO_ERROR; + + if (!is_ok(error)) { auto row = Row{ .main_clk = clk, .main_internal_return_ptr = internal_return_ptr, @@ -3531,6 +3555,7 @@ AvmError AvmTraceBuilder::constrain_external_call(OpCode opcode, current_ext_call_ctx.last_pc = pc; current_ext_call_ctx.success_offset = resolved_success_offset; current_ext_call_ctx.tree_snapshot = merkle_tree_trace_builder.get_tree_snapshots(); + current_ext_call_ctx.public_data_unique_writes = merkle_tree_trace_builder.get_public_data_unique_writes(); external_call_ctx_stack.emplace(current_ext_call_ctx); // Ext Ctx setup @@ -3541,6 +3566,7 @@ AvmError AvmTraceBuilder::constrain_external_call(OpCode opcode, current_ext_call_ctx = ExtCallCtx{ .context_id = static_cast(clk), .parent_id = current_ext_call_ctx.context_id, + .is_static_call = opcode == OpCode::STATICCALL, .contract_address = read_addr.val, .calldata = calldata, .nested_returndata = {}, @@ -3549,7 +3575,8 @@ AvmError AvmTraceBuilder::constrain_external_call(OpCode opcode, .l2_gas = static_cast(read_gas_l2.val), .da_gas = static_cast(read_gas_da.val), .internal_return_ptr_stack = {}, - .tree_snapshot = {} }; + .tree_snapshot = {}, + .public_data_unique_writes = {} }; set_pc(0); return error; @@ -3662,8 +3689,6 @@ ReturnDataError AvmTraceBuilder::op_return(uint8_t indirect, uint32_t ret_offset // Update the call_ptr before we write the success flag set_call_ptr(static_cast(current_ext_call_ctx.context_id)); write_to_memory(current_ext_call_ctx.success_offset, FF::one(), AvmMemoryTag::U1); - // If we successfully returned, we update the current parent's tree state - current_ext_call_ctx.tree_snapshot = merkle_tree_trace_builder.get_tree_snapshots(); } } @@ -3760,7 +3785,8 @@ ReturnDataError AvmTraceBuilder::op_revert(uint8_t indirect, uint32_t ret_offset set_call_ptr(static_cast(current_ext_call_ctx.context_id)); write_to_memory(current_ext_call_ctx.success_offset, FF::zero(), AvmMemoryTag::U1); // Reset the tree state to parent's snapshot - merkle_tree_trace_builder.set_tree_snapshots(current_ext_call_ctx.tree_snapshot); + merkle_tree_trace_builder.restore_tree_state(current_ext_call_ctx.tree_snapshot, + current_ext_call_ctx.public_data_unique_writes); } } diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp index 0687b848baf0..56b5f050cd48 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp @@ -263,6 +263,7 @@ class AvmTraceBuilder { struct ExtCallCtx { uint32_t context_id; // This is the unique id of the ctx, we'll use the clk uint32_t parent_id; + bool is_static_call = false; FF contract_address{}; std::vector calldata; std::vector nested_returndata; @@ -272,6 +273,7 @@ class AvmTraceBuilder { uint32_t da_gas; std::stack internal_return_ptr_stack; TreeSnapshots tree_snapshot; // This is the tree state at the time of the call + std::unordered_set public_data_unique_writes; }; ExtCallCtx current_ext_call_ctx{}; @@ -363,6 +365,8 @@ class AvmTraceBuilder { IntermRegister reg, AvmMemTraceBuilder::MemOpOwner mem_op_owner = AvmMemTraceBuilder::MAIN); uint32_t get_inserted_note_hashes_count(); + uint32_t get_inserted_nullifiers_count(); + uint32_t get_public_data_writes_count(); FF get_tx_hash() const { return public_inputs.previous_non_revertible_accumulated_data.nullifiers[0]; } // TODO: remove these once everything is constrained.