From b80df134e20134279ac593ce5985a6c7c2837c3b Mon Sep 17 00:00:00 2001 From: IlyasRidhuan Date: Tue, 23 Dec 2025 15:25:28 +0000 Subject: [PATCH 1/3] feat(avm): revert in fuzzer --- .../avm_fuzzer/fuzz_lib/control_flow.cpp | 30 +++++++++++++++---- .../avm_fuzzer/fuzz_lib/control_flow.hpp | 15 ++++++++++ .../avm_fuzzer/fuzz_lib/program_block.cpp | 28 +++++++++++++++++ .../avm_fuzzer/fuzz_lib/program_block.hpp | 8 +++++ .../avm_fuzzer/mutations/configuration.hpp | 4 ++- .../control_flow/control_flow_vec.cpp | 10 +++++++ 6 files changed, 89 insertions(+), 6 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.cpp index 6ebd989dfd5a..92216247e7af 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.cpp @@ -145,6 +145,21 @@ void ControlFlow::process_finalize_with_return(FinalizeWithReturn instruction) current_block = non_terminated_blocks.at(0); } +void ControlFlow::process_finalize_with_revert(FinalizeWithRevert instruction) +{ + if (this->current_block->terminator_type != TerminatorType::NONE) { + return; + } + current_block->finalize_with_revert(instruction.revert_options.return_size, + instruction.revert_options.return_value_tag, + instruction.revert_options.return_value_offset_index); + std::vector non_terminated_blocks = get_non_terminated_blocks(); + if (non_terminated_blocks.size() == 0) { + return; + } + current_block = non_terminated_blocks.at(0); +} + void ControlFlow::process_switch_to_non_terminated_block(SwitchToNonTerminatedBlock instruction) { std::vector non_terminated_blocks = get_non_terminated_blocks(); @@ -214,6 +229,7 @@ void ControlFlow::process_cfg_instruction(CFGInstruction instruction) [&](JumpToBlock arg) { process_jump_to_block(arg); }, [&](JumpIfToBlock arg) { process_jump_if_to_block(arg); }, [&](FinalizeWithReturn arg) { process_finalize_with_return(arg); }, + [&](FinalizeWithRevert arg) { process_finalize_with_revert(arg); }, [&](SwitchToNonTerminatedBlock arg) { process_switch_to_non_terminated_block(arg); }, [&](InsertInternalCall arg) { process_insert_internal_call(arg); } }, instruction); @@ -239,7 +255,8 @@ int predict_block_size(ProgramBlock* block) auto bytecode_length = static_cast(create_bytecode(block->get_instructions()).size()); switch (block->terminator_type) { case TerminatorType::RETURN: - return bytecode_length; // finalized with return, already counted + case TerminatorType::REVERT: + return bytecode_length; // finalized with return/revert, already counted case TerminatorType::JUMP: return bytecode_length + JMP_SIZE; // finalized with jump case TerminatorType::JUMP_IF: { @@ -263,9 +280,9 @@ int predict_block_size(ProgramBlock* block) return bytecode_length + JMP_IF_SIZE + JMP_SIZE; // finalized with jumpi } default: - throw std::runtime_error("Predict block size: Every block should be terminated with return, jump, or jumpi, " - "got " + - std::to_string(static_cast(block->terminator_type))); + throw std::runtime_error( + "Predict block size: Every block should be terminated with return, revert, jump, or jumpi, got " + + std::to_string(static_cast(block->terminator_type))); } throw std::runtime_error("Unreachable"); } @@ -315,6 +332,9 @@ std::vector ControlFlow::build_bytecode(const ReturnOptions& return_opt case TerminatorType::RETURN: // finalized with return // already terminated with return break; + case TerminatorType::REVERT: // finalized with revert + // already terminated with revert + break; case TerminatorType::JUMP: { // finalized with jump ProgramBlock* target_block = block->successors.at(0); size_t target_block_idx = find_block_idx(target_block, blocks); @@ -347,7 +367,7 @@ std::vector ControlFlow::build_bytecode(const ReturnOptions& return_opt } default: throw std::runtime_error( - "Inject terminators: Every block should be terminated with return, jump, or jumpi"); + "Inject terminators: Every block should be terminated with return, revert, jump, or jumpi"); } block_bytecodes.push_back(create_bytecode(instructions)); } diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.hpp index 09707e2c5ace..d646affcf2aa 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/control_flow.hpp @@ -64,6 +64,12 @@ struct FinalizeWithReturn { MSGPACK_FIELDS(return_options); }; +/// @brief finalizes the current block with Revert and switches to the first non-terminated block +struct FinalizeWithRevert { + ReturnOptions revert_options; + MSGPACK_FIELDS(revert_options); +}; + /// @brief switches to the non-terminated block with the chosen index struct SwitchToNonTerminatedBlock { uint16_t non_terminated_block_idx; @@ -83,6 +89,7 @@ using CFGInstruction = std::variant; template struct overloaded_cfg_instruction : Ts... { @@ -111,6 +118,10 @@ inline std::ostream& operator<<(std::ostream& os, const CFGInstruction& instruct os << "FinalizeWithReturn " << arg.return_options.return_size << " " << arg.return_options.return_value_tag << " " << arg.return_options.return_value_offset_index; }, + [&](FinalizeWithRevert arg) { + os << "FinalizeWithRevert " << arg.revert_options.return_size << " " + << arg.revert_options.return_value_tag << " " << arg.revert_options.return_value_offset_index; + }, [&](SwitchToNonTerminatedBlock arg) { os << "SwitchToNonTerminatedBlock " << arg.non_terminated_block_idx; }, @@ -160,6 +171,10 @@ class ControlFlow { /// @param instruction the instruction to process void process_finalize_with_return(FinalizeWithReturn instruction); + /// @brief terminates the current block with Revert and switches to the first non-terminated block + /// @param instruction the instruction to process + void process_finalize_with_revert(FinalizeWithRevert instruction); + /// @brief switches to the non-terminated block with the chosen index /// @param instruction the instruction to process void process_switch_to_non_terminated_block(SwitchToNonTerminatedBlock instruction); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.cpp index 270882952cea..c04940d9897a 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.cpp @@ -1586,6 +1586,34 @@ void ProgramBlock::finalize_with_return(uint8_t return_size, instructions.push_back(return_instruction); } +void ProgramBlock::finalize_with_revert(uint8_t revert_size, + MemoryTagWrapper revert_value_tag, + uint16_t revert_value_offset_index) +{ + this->terminator_type = TerminatorType::REVERT; + + auto revert_addr = memory_manager.get_memory_offset_16(revert_value_tag.value, revert_value_offset_index); + if (!revert_addr.has_value()) { + revert_addr = std::optional(0); + } + + // Once we do more of the randomness in Instruction selection, revert_size_offset we shouldnt need to hardcode + uint16_t revert_size_offset = 5U; + // Ensure operands are created as U16 to match wire format (UINT16) + auto set_size_instruction = bb::avm2::testing::InstructionBuilder(bb::avm2::WireOpCode::SET_16) + .operand(revert_size_offset) + .operand(bb::avm2::MemoryTag::U32) + .operand(static_cast(revert_size)) + .build(); + instructions.push_back(set_size_instruction); + // REVERT_16 expects UINT16 operands, ensure we cast to uint16_t explicitly + auto revert_instruction = bb::avm2::testing::InstructionBuilder(bb::avm2::WireOpCode::REVERT_16) + .operand(static_cast(revert_size_offset)) + .operand(revert_addr.value()) + .build(); + instructions.push_back(revert_instruction); +} + void ProgramBlock::finalize_with_jump(ProgramBlock* target_block, bool copy_memory_manager) { this->terminator_type = TerminatorType::JUMP; diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.hpp index a442b4e75408..360b03e0bdde 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/program_block.hpp @@ -29,6 +29,7 @@ enum class TerminatorType { RETURN, + REVERT, JUMP, JUMP_IF, NONE, @@ -139,6 +140,13 @@ class ProgramBlock { MemoryTagWrapper return_value_tag, uint16_t return_value_offset_index); + /// @brief finalize the program block with a revert instruction + /// Similar to finalize_with_return but uses REVERT opcode instead. + /// Sets the terminator type to REVERT. + void finalize_with_revert(uint8_t revert_size, + MemoryTagWrapper revert_value_tag, + uint16_t revert_value_offset_index); + /// @brief finalize the block with a jump /// Sets the terminator type to JUMP, adds the target block to the successors and the current block to the /// predecessors. diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/configuration.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/configuration.hpp index 5807264211b2..13ae8b732692 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/configuration.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/configuration.hpp @@ -555,11 +555,12 @@ enum class CFGInstructionGenerationOptions { JumpToBlock, JumpIfToBlock, FinalizeWithReturn, + FinalizeWithRevert, SwitchToNonTerminatedBlock, InsertInternalCall, }; -using CFGInstructionGenerationConfig = WeightedSelectionConfig; +using CFGInstructionGenerationConfig = WeightedSelectionConfig; constexpr CFGInstructionGenerationConfig BASIC_CFG_INSTRUCTION_GENERATION_CONFIGURATION = CFGInstructionGenerationConfig({ @@ -569,6 +570,7 @@ constexpr CFGInstructionGenerationConfig BASIC_CFG_INSTRUCTION_GENERATION_CONFIG { CFGInstructionGenerationOptions::JumpToBlock, 15 }, { CFGInstructionGenerationOptions::JumpIfToBlock, 15 }, { CFGInstructionGenerationOptions::FinalizeWithReturn, 7 }, + { CFGInstructionGenerationOptions::FinalizeWithRevert, 3 }, { CFGInstructionGenerationOptions::SwitchToNonTerminatedBlock, 8 }, { CFGInstructionGenerationOptions::InsertInternalCall, 3 }, }); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/control_flow/control_flow_vec.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/control_flow/control_flow_vec.cpp index be0ed13e0ea6..2143ab12cc75 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/control_flow/control_flow_vec.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/control_flow/control_flow_vec.cpp @@ -58,6 +58,11 @@ void mutate_finalize_with_return(FinalizeWithReturn& instr, std::mt19937_64& rng mutate_return_options(instr.return_options, rng, BASIC_RETURN_OPTIONS_MUTATION_CONFIGURATION); } +void mutate_finalize_with_revert(FinalizeWithRevert& instr, std::mt19937_64& rng) +{ + mutate_return_options(instr.revert_options, rng, BASIC_RETURN_OPTIONS_MUTATION_CONFIGURATION); +} + void mutate_switch_to_non_terminated_block(SwitchToNonTerminatedBlock& instr, std::mt19937_64& rng) { mutate_uint16_t(instr.non_terminated_block_idx, rng, BASIC_UINT16_T_MUTATION_CONFIGURATION); @@ -86,6 +91,10 @@ CFGInstruction generate_cfg_instruction(std::mt19937_64& rng) return FinalizeWithReturn(ReturnOptions(generate_random_uint8(rng), generate_memory_tag(rng, BASIC_MEMORY_TAG_GENERATION_CONFIGURATION), generate_random_uint16(rng))); + case CFGInstructionGenerationOptions::FinalizeWithRevert: + return FinalizeWithRevert(ReturnOptions(generate_random_uint8(rng), + generate_memory_tag(rng, BASIC_MEMORY_TAG_GENERATION_CONFIGURATION), + generate_random_uint16(rng))); case CFGInstructionGenerationOptions::SwitchToNonTerminatedBlock: return SwitchToNonTerminatedBlock(generate_random_uint16(rng)); case CFGInstructionGenerationOptions::InsertInternalCall: @@ -102,6 +111,7 @@ void mutate_cfg_instruction(CFGInstruction& cfg_instruction, std::mt19937_64& rn [&](JumpToBlock& instr) { mutate_jump_to_block(instr, rng); }, [&](JumpIfToBlock& instr) { mutate_jump_if_to_block(instr, rng); }, [&](FinalizeWithReturn& instr) { mutate_finalize_with_return(instr, rng); }, + [&](FinalizeWithRevert& instr) { mutate_finalize_with_revert(instr, rng); }, [&](SwitchToNonTerminatedBlock& instr) { mutate_switch_to_non_terminated_block(instr, rng); }, [&](InsertInternalCall& instr) { mutate_insert_internal_call(instr, rng); } }, cfg_instruction); From 4e326343ce6d8214dd5f12fc092d6d5d9885fe10 Mon Sep 17 00:00:00 2001 From: IlyasRidhuan Date: Wed, 24 Dec 2025 10:31:04 +0000 Subject: [PATCH 2/3] fix(avm): improve backfill and sload --- .../mutations/instructions/instruction.cpp | 111 ++++++++++++++++-- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/instructions/instruction.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/instructions/instruction.cpp index 28ecd0bf7eed..8ddd604db4fd 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/instructions/instruction.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/instructions/instruction.cpp @@ -51,7 +51,8 @@ AddressRef generate_address_ref(std::mt19937_64& rng, uint32_t max_operand_value std::vector generate_ecadd_instruction(std::mt19937_64& rng) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { // Random mode: use existing memory values (may fail if not valid points on curve) @@ -100,6 +101,16 @@ std::vector generate_ecadd_instruction(std::mt19937_64& rng) AddressRef p2_y_addr = generate_address_ref(rng, MAX_16BIT_OPERAND); AddressRef p2_inf_addr = generate_address_ref(rng, MAX_8BIT_OPERAND); + // Force Direct addressing so SET instructions write to the same addresses that ECADD reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + p1_x_addr.mode = AddressingMode::Direct; + p1_y_addr.mode = AddressingMode::Direct; + p1_inf_addr.mode = AddressingMode::Direct; + p2_x_addr.mode = AddressingMode::Direct; + p2_y_addr.mode = AddressingMode::Direct; + p2_inf_addr.mode = AddressingMode::Direct; + backfill_point(p1, p1_x_addr, p1_y_addr, p1_inf_addr); backfill_point(p2, p2_x_addr, p2_y_addr, p2_inf_addr); @@ -143,7 +154,8 @@ FuzzInstruction generate_set_for_tag(bb::avm2::MemoryTag tag, AddressRef addr, s template std::vector generate_alu_with_matching_tags(std::mt19937_64& rng, uint32_t max_operand) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { return { InstructionType{ .a_address = generate_variable_ref(rng), @@ -155,6 +167,12 @@ std::vector generate_alu_with_matching_tags(std::mt19937_64& rn AddressRef a_addr = generate_address_ref(rng, max_operand); AddressRef b_addr = generate_address_ref(rng, max_operand); + // Force Direct addressing so SET instructions write to the same addresses that ALU reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + a_addr.mode = AddressingMode::Direct; + b_addr.mode = AddressingMode::Direct; + std::vector instructions; instructions.push_back(generate_set_for_tag(tag, a_addr, rng)); instructions.push_back(generate_set_for_tag(tag, b_addr, rng)); @@ -168,7 +186,8 @@ std::vector generate_alu_with_matching_tags(std::mt19937_64& rn template std::vector generate_alu_with_matching_tags_not_ff(std::mt19937_64& rng, uint32_t max_operand) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { return { InstructionType{ .a_address = generate_variable_ref(rng), @@ -186,6 +205,12 @@ std::vector generate_alu_with_matching_tags_not_ff(std::mt19937 AddressRef a_addr = generate_address_ref(rng, max_operand); AddressRef b_addr = generate_address_ref(rng, max_operand); + // Force Direct addressing so SET instructions write to the same addresses that ALU reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + a_addr.mode = AddressingMode::Direct; + b_addr.mode = AddressingMode::Direct; + std::vector instructions; instructions.push_back(generate_set_for_tag(tag, a_addr, rng)); instructions.push_back(generate_set_for_tag(tag, b_addr, rng)); @@ -196,7 +221,8 @@ std::vector generate_alu_with_matching_tags_not_ff(std::mt19937 std::vector generate_fdiv_instruction(std::mt19937_64& rng, uint32_t max_operand) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { // Random mode: use existing memory values @@ -221,6 +247,12 @@ std::vector generate_fdiv_instruction(std::mt19937_64& rng, uin AddressRef a_addr = generate_address_ref(rng, max_operand); AddressRef b_addr = generate_address_ref(rng, max_operand); + // Force Direct addressing so SET instructions write to the same addresses that FDIV reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + a_addr.mode = AddressingMode::Direct; + b_addr.mode = AddressingMode::Direct; + // SET the dividend (a) instructions.push_back(SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, .result_address = a_addr, .value = generate_nonzero_field() }); @@ -238,7 +270,8 @@ std::vector generate_fdiv_instruction(std::mt19937_64& rng, uin std::vector generate_keccakf_instruction(std::mt19937_64& rng) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { // Random mode return { KECCAKF1600_Instruction{ .src_address = generate_variable_ref(rng), @@ -249,6 +282,10 @@ std::vector generate_keccakf_instruction(std::mt19937_64& rng) // Keccak needs to backfill 25 U64 values, these need be contiguous in memory AddressRef src_address = generate_address_ref(rng, MAX_16BIT_OPERAND - 24); + // Force Direct addressing so SET instructions write to the same addresses that KECCAKF1600 reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + src_address.mode = AddressingMode::Direct; for (size_t i = 0; i < 25; i++) { instructions.push_back( SET_64_Instruction{ .value_tag = bb::avm2::MemoryTag::U64, @@ -263,7 +300,8 @@ std::vector generate_keccakf_instruction(std::mt19937_64& rng) std::vector generate_sha256compression_instruction(std::mt19937_64& rng) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { // Random mode return { SHA256COMPRESSION_Instruction{ .state_address = generate_variable_ref(rng), @@ -277,6 +315,10 @@ std::vector generate_sha256compression_instruction(std::mt19937 // Generate state address (8 contiguous U32 values) AddressRef state_address = generate_address_ref(rng, MAX_16BIT_OPERAND - 7); + // Force Direct addressing so SET instructions write to the same addresses that SHA256COMPRESSION reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + state_address.mode = AddressingMode::Direct; for (size_t i = 0; i < 8; i++) { instructions.push_back(SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32, @@ -287,6 +329,7 @@ std::vector generate_sha256compression_instruction(std::mt19937 // Generate input address (16 contiguous U32 values) AddressRef input_address = generate_address_ref(rng, MAX_16BIT_OPERAND - 15); + input_address.mode = AddressingMode::Direct; for (size_t i = 0; i < 16; i++) { instructions.push_back(SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32, @@ -304,7 +347,8 @@ std::vector generate_sha256compression_instruction(std::mt19937 std::vector generate_toradixbe_instruction(std::mt19937_64& rng) { - bool use_backfill = std::uniform_int_distribution(0, 1)(rng) == 0; + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; if (!use_backfill) { // Random mode return { TORADIXBE_Instruction{ .value_address = generate_variable_ref(rng), @@ -324,6 +368,14 @@ std::vector generate_toradixbe_instruction(std::mt19937_64& rng AddressRef num_limbs_addr = generate_address_ref(rng, MAX_16BIT_OPERAND); AddressRef output_bits_addr = generate_address_ref(rng, MAX_8BIT_OPERAND); + // Force Direct addressing so SET instructions write to the same addresses that TORADIXBE reads from. + // TODO: Implement smart backfilling for indirect addressing modes by also setting up + // the pointer addresses (e.g., M[pointer_address_seed] = target_address). + value_addr.mode = AddressingMode::Direct; + radix_addr.mode = AddressingMode::Direct; + num_limbs_addr.mode = AddressingMode::Direct; + output_bits_addr.mode = AddressingMode::Direct; + // SET the radix (U32) - pick radix between 2 and 256 uint32_t radix = std::uniform_int_distribution(2, 256)(rng); instructions.push_back( @@ -360,6 +412,47 @@ std::vector generate_toradixbe_instruction(std::mt19937_64& rng return instructions; } +// A better way in the future is to pass in a vector of possible slots that have been written to, +// this would allow us to supply external world state info. +std::vector generate_sload_instruction(std::mt19937_64& rng) +{ + // 80% chance to use backfill (4 out of 5) to increase success rate + bool use_backfill = std::uniform_int_distribution(0, 4)(rng) != 0; + + if (!use_backfill) { + // Random mode: requires at least one prior SSTORE to have been processed + return { SLOAD_Instruction{ .slot_index = generate_random_uint16(rng), + .slot_address = generate_address_ref(rng, MAX_16BIT_OPERAND), + .result_address = generate_address_ref(rng, MAX_16BIT_OPERAND) } }; + } + + // Backfill mode: generate SSTORE first to ensure storage_addresses is non-empty + // This guarantees SLOAD will find a valid slot (get_slot uses modulo on non-empty vector) + std::vector instructions; + instructions.reserve(3); + + AddressRef sstore_src = generate_address_ref(rng, MAX_16BIT_OPERAND); + // Force Direct addressing so SET writes to the same address that SSTORE reads from. + // TODO: Implement smart backfilling for indirect addressing modes. + sstore_src.mode = AddressingMode::Direct; + + // SET a value to store + instructions.push_back(SET_FF_Instruction{ + .value_tag = bb::avm2::MemoryTag::FF, .result_address = sstore_src, .value = generate_random_field(rng) }); + + // SSTORE - appends to storage_addresses in memory_manager + instructions.push_back(SSTORE_Instruction{ .src_address = sstore_src, + .result_address = generate_address_ref(rng, MAX_16BIT_OPERAND), + .slot = generate_random_field(rng) }); + + // SLOAD - now guaranteed to succeed (storage_addresses not empty, get_slot uses modulo) + instructions.push_back(SLOAD_Instruction{ .slot_index = generate_random_uint16(rng), + .slot_address = generate_address_ref(rng, MAX_16BIT_OPERAND), + .result_address = generate_address_ref(rng, MAX_16BIT_OPERAND) }); + + return instructions; +} + std::vector generate_instruction(std::mt19937_64& rng) { InstructionGenerationOptions option = BASIC_INSTRUCTION_GENERATION_CONFIGURATION.select(rng); @@ -494,9 +587,7 @@ std::vector generate_instruction(std::mt19937_64& rng) .result_address = generate_address_ref(rng, MAX_16BIT_OPERAND), .slot = generate_random_field(rng) } }; case InstructionGenerationOptions::SLOAD: - return { SLOAD_Instruction{ .slot_index = generate_random_uint16(rng), - .slot_address = generate_address_ref(rng, MAX_16BIT_OPERAND), - .result_address = generate_address_ref(rng, MAX_16BIT_OPERAND) } }; + return generate_sload_instruction(rng); case InstructionGenerationOptions::GETENVVAR: return { GETENVVAR_Instruction{ .result_address = generate_address_ref(rng, MAX_16BIT_OPERAND), .type = generate_random_uint8(rng) } }; From 945e381d5afda00f77aa317cd7977b694b2c6385 Mon Sep 17 00:00:00 2001 From: IlyasRidhuan Date: Wed, 24 Dec 2025 10:43:33 +0000 Subject: [PATCH 3/3] test(avm): adding missing tests --- .../avm_fuzzer/fuzz_lib/fuzz.test.cpp | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.test.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.test.cpp index 0a8058048d1b..f1173325ff92 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.test.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.test.cpp @@ -1646,3 +1646,231 @@ TEST(fuzz, StaticCallToNonStaticFunctionSuccessCopy) EXPECT_EQ(result.reverted, false); } } // namespace external_calls + +namespace crypto_ops { + +TEST(fuzz, Poseidon2PermSmoke) +{ + // Set up 4 FF values for Poseidon2 permutation input + std::vector instructions; + for (uint32_t i = 0; i < 4; i++) { + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = i, .mode = AddressingMode::Direct }, + .value = FF(i + 1) }); + } + // Poseidon2 permutation: reads from address 0-3, writes to address 10-13 + instructions.push_back( + POSEIDON2PERM_Instruction{ .src_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .dst_address = AddressRef{ .address = 10, .mode = AddressingMode::Direct } }); + + auto instruction_blocks = std::vector>{ instructions }; + auto control_flow = ControlFlow(instruction_blocks); + control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 }); + auto bytecode = control_flow.build_bytecode( + ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::FF, .return_value_offset_index = 2 }); + + auto result = simulate_with_default_tx(bytecode, {}); + EXPECT_FALSE(result.reverted); +} + +TEST(fuzz, Keccakf1600Smoke) +{ + // Set up 25 U64 values for Keccak-f[1600] permutation input + std::vector instructions; + for (uint32_t i = 0; i < 25; i++) { + instructions.push_back( + SET_64_Instruction{ .value_tag = bb::avm2::MemoryTag::U64, + .result_address = AddressRef{ .address = i, .mode = AddressingMode::Direct }, + .value = static_cast(i + 1) }); + } + // Keccak-f[1600]: reads from address 0-24, writes to address 100-124 + instructions.push_back( + KECCAKF1600_Instruction{ .src_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .dst_address = AddressRef{ .address = 100, .mode = AddressingMode::Direct } }); + + auto instruction_blocks = std::vector>{ instructions }; + auto control_flow = ControlFlow(instruction_blocks); + control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 }); + auto bytecode = control_flow.build_bytecode(ReturnOptions{ + .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U64, .return_value_offset_index = 25 }); + + auto result = simulate_with_default_tx(bytecode, {}); + EXPECT_FALSE(result.reverted); +} + +TEST(fuzz, Sha256CompressionSmoke) +{ + // Set up 8 U32 values for state and 16 U32 values for input + std::vector instructions; + + // State: addresses 0-7 + for (uint32_t i = 0; i < 8; i++) { + instructions.push_back( + SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32, + .result_address = AddressRef{ .address = i, .mode = AddressingMode::Direct }, + .value = i + 1 }); + } + // Input: addresses 20-35 + for (uint32_t i = 0; i < 16; i++) { + instructions.push_back( + SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32, + .result_address = AddressRef{ .address = 20 + i, .mode = AddressingMode::Direct }, + .value = i + 100 }); + } + // SHA256 compression: state at 0-7, input at 20-35, output at 50-57 + instructions.push_back( + SHA256COMPRESSION_Instruction{ .state_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .input_address = AddressRef{ .address = 20, .mode = AddressingMode::Direct }, + .dst_address = AddressRef{ .address = 50, .mode = AddressingMode::Direct } }); + + auto instruction_blocks = std::vector>{ instructions }; + auto control_flow = ControlFlow(instruction_blocks); + control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 }); + auto bytecode = control_flow.build_bytecode(ReturnOptions{ + .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U32, .return_value_offset_index = 12 }); + + auto result = simulate_with_default_tx(bytecode, {}); + EXPECT_FALSE(result.reverted); +} + +TEST(fuzz, EcaddSmoke) +{ + std::vector instructions; + + // Use the generator point G = (1, sqrt(-16)) as p1 + // For simplicity, we'll use infinity points which should work + // Set p1 as infinity: x=0, y=0, infinite=1 + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .value = FF(0) }); // p1.x + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct }, + .value = FF(0) }); // p1.y + instructions.push_back( + SET_8_Instruction{ .value_tag = bb::avm2::MemoryTag::U1, + .result_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct }, + .value = 1 }); // p1.infinite = true + + // Set p2 as infinity too + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = 3, .mode = AddressingMode::Direct }, + .value = FF(0) }); // p2.x + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = 4, .mode = AddressingMode::Direct }, + .value = FF(0) }); // p2.y + instructions.push_back( + SET_8_Instruction{ .value_tag = bb::avm2::MemoryTag::U1, + .result_address = AddressRef{ .address = 5, .mode = AddressingMode::Direct }, + .value = 1 }); // p2.infinite = true + + // ECADD instruction: infinity + infinity = infinity + instructions.push_back(ECADD_Instruction{ .p1_x = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .p1_y = AddressRef{ .address = 1, .mode = AddressingMode::Direct }, + .p1_infinite = AddressRef{ .address = 2, .mode = AddressingMode::Direct }, + .p2_x = AddressRef{ .address = 3, .mode = AddressingMode::Direct }, + .p2_y = AddressRef{ .address = 4, .mode = AddressingMode::Direct }, + .p2_infinite = AddressRef{ .address = 5, .mode = AddressingMode::Direct }, + .result = AddressRef{ .address = 10, .mode = AddressingMode::Direct } }); + + auto instruction_blocks = std::vector>{ instructions }; + auto control_flow = ControlFlow(instruction_blocks); + control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 }); + // Return the infinite flag of the result (should be 1) + auto bytecode = control_flow.build_bytecode( + ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U1, .return_value_offset_index = 3 }); + + auto result = simulate_with_default_tx(bytecode, {}); + EXPECT_FALSE(result.reverted); +} + +} // namespace crypto_ops + +namespace conversions { + +TEST(fuzz, ToRadixBESmoke) +{ + std::vector instructions; + + // Set value to convert (FF) + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .value = FF(255) }); + // Set radix (U32) = 2 (binary) + instructions.push_back( + SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32, + .result_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct }, + .value = 2 }); + // Set num_limbs (U32) = 8 + instructions.push_back( + SET_32_Instruction{ .value_tag = bb::avm2::MemoryTag::U32, + .result_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct }, + .value = 8 }); + // Set output_bits (U1) = 1 (since radix is 2) + instructions.push_back( + SET_8_Instruction{ .value_tag = bb::avm2::MemoryTag::U1, + .result_address = AddressRef{ .address = 3, .mode = AddressingMode::Direct }, + .value = 1 }); + + // TORADIXBE instruction + instructions.push_back( + TORADIXBE_Instruction{ .value_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .radix_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct }, + .num_limbs_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct }, + .output_bits_address = AddressRef{ .address = 3, .mode = AddressingMode::Direct }, + .dst_address = AddressRef{ .address = 10, .mode = AddressingMode::Direct }, + .is_output_bits = true }); + + auto instruction_blocks = std::vector>{ instructions }; + auto control_flow = ControlFlow(instruction_blocks); + control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 }); + auto bytecode = control_flow.build_bytecode( + ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U1, .return_value_offset_index = 2 }); + + auto result = simulate_with_default_tx(bytecode, {}); + EXPECT_FALSE(result.reverted); +} + +} // namespace conversions + +namespace l1_l2_messaging { + +TEST(fuzz, L1ToL2MsgExistsSmoke) +{ + std::vector instructions; + + // Set msg_hash (FF) + instructions.push_back( + SET_FF_Instruction{ .value_tag = bb::avm2::MemoryTag::FF, + .result_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .value = FF(12345) }); + // Set leaf_index (U64) + instructions.push_back( + SET_64_Instruction{ .value_tag = bb::avm2::MemoryTag::U64, + .result_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct }, + .value = 0 }); + + // L1TOL2MSGEXISTS instruction - check if message exists (it won't, but shouldn't revert) + instructions.push_back( + L1TOL2MSGEXISTS_Instruction{ .msg_hash_address = AddressRef{ .address = 0, .mode = AddressingMode::Direct }, + .leaf_index_address = AddressRef{ .address = 1, .mode = AddressingMode::Direct }, + .result_address = AddressRef{ .address = 2, .mode = AddressingMode::Direct } }); + + auto instruction_blocks = std::vector>{ instructions }; + auto control_flow = ControlFlow(instruction_blocks); + control_flow.process_cfg_instruction(InsertSimpleInstructionBlock{ .instruction_block_idx = 0 }); + auto bytecode = control_flow.build_bytecode( + ReturnOptions{ .return_size = 1, .return_value_tag = bb::avm2::MemoryTag::U1, .return_value_offset_index = 0 }); + + auto result = simulate_with_default_tx(bytecode, {}); + EXPECT_FALSE(result.reverted); + // Message doesn't exist, so result should be 0 + EXPECT_EQ(result.output.at(0), FF::zero()); +} + +} // namespace l1_l2_messaging