diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.cpp index 40a5149e1ae9..cda0715dc27a 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.cpp @@ -237,4 +237,10 @@ void FuzzerWorldStateManager::write_fee_payer_balance(const AztecAddress& fee_pa ws->update_public_data(PublicDataLeafValue(leaf_slot, balance), fork_id); } +void FuzzerWorldStateManager::public_data_write(const bb::crypto::merkle_tree::PublicDataLeafValue& public_data) +{ + auto fork_id = fork_ids.top(); + ws->update_public_data(public_data, fork_id); +} + } // namespace bb::avm2::fuzzer diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.hpp index 9a8d58347737..e229dfe7b52d 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/common/interfaces/dbs.hpp @@ -88,6 +88,7 @@ class FuzzerWorldStateManager { void reset_world_state(); void register_contract_address(const AztecAddress& contract_address); void write_fee_payer_balance(const AztecAddress& fee_payer, const FF& balance); + void public_data_write(const bb::crypto::merkle_tree::PublicDataLeafValue& public_data); world_state::WorldStateRevision get_current_revision() const; world_state::WorldStateRevision fork(); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp index e1cb9d3d1c2d..b104917ebe5f 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp @@ -42,14 +42,14 @@ SimulatorResult fuzz_against_ts_simulator(FuzzerData& fuzzer_data, FuzzerContext try { ws_mgr->checkpoint(); - cpp_result = cpp_simulator.simulate(*ws_mgr, contract_db, tx); + cpp_result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, /*public_data_writes=*/{}); ws_mgr->revert(); } catch (const std::exception& e) { throw std::runtime_error(std::string("CppSimulator threw an exception: ") + e.what()); } ws_mgr->checkpoint(); - auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx); + auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx, /*public_data_writes=*/{}); context.reset(); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp index 398ab6764cbf..87773722977a 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp @@ -31,12 +31,14 @@ using namespace bb::avm2::simulation; using namespace bb::avm2::fuzzer; using namespace bb::world_state; -const auto MAX_RETURN_DATA_SIZE_IN_FIELDS = 1024; +constexpr auto MAX_RETURN_DATA_SIZE_IN_FIELDS = 1024; // Helper function to serialize simulation request via msgpack -std::string serialize_simulation_request(const Tx& tx, - const GlobalVariables& globals, - const FuzzerContractDB& contract_db) +std::string serialize_simulation_request( + const Tx& tx, + const GlobalVariables& globals, + const FuzzerContractDB& contract_db, + const std::vector& public_data_writes) { // Build vectors from contract_db std::vector classes_vec = contract_db.get_contract_classes(); @@ -49,6 +51,7 @@ std::string serialize_simulation_request(const Tx& tx, .globals = globals, .contract_classes = std::move(classes_vec), .contract_instances = std::move(instances_vec), + .public_data_writes = public_data_writes, }; auto [buffer, size] = msgpack_encode_buffer(request); @@ -72,10 +75,13 @@ GlobalVariables create_default_globals() }; } -SimulatorResult CppSimulator::simulate(fuzzer::FuzzerWorldStateManager& ws_mgr, - fuzzer::FuzzerContractDB& contract_db, - const Tx& tx) +SimulatorResult CppSimulator::simulate( + fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + [[maybe_unused]] const std::vector& public_data_writes) { + // Note: public_data_writes are already applied to C++ world state in setup_fuzzer_state const PublicSimulatorConfig config{ .skip_fee_enforcement = false, @@ -143,13 +149,15 @@ void JsSimulator::initialize(std::string& simulator_path) instance = new JsSimulator(simulator_path); } -SimulatorResult JsSimulator::simulate([[maybe_unused]] fuzzer::FuzzerWorldStateManager& ws_mgr, - fuzzer::FuzzerContractDB& contract_db, - const Tx& tx) +SimulatorResult JsSimulator::simulate( + [[maybe_unused]] fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + const std::vector& public_data_writes) { auto globals = create_default_globals(); - std::string serialized = serialize_simulation_request(tx, globals, contract_db); + std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes); // Send the request process.write_line(serialized); @@ -172,7 +180,7 @@ SimulatorResult JsSimulator::simulate([[maybe_unused]] fuzzer::FuzzerWorldStateM bool compare_simulator_results(SimulatorResult& result1, SimulatorResult& result2) { // Since the simulator results are interchangeable between TS and C++, we limit the return data size for comparison - // todo(ilyas): we ideally specfify one param as the TS result and truncate only that one + // todo(ilyas): we ideally specify one param as the TS result and truncate only that one if (result1.output.size() > MAX_RETURN_DATA_SIZE_IN_FIELDS) { result1.output.resize(MAX_RETURN_DATA_SIZE_IN_FIELDS); } diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp index 27e53dd05607..97c1c2a02512 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp @@ -4,6 +4,7 @@ #include "barretenberg/avm_fuzzer/common/interfaces/dbs.hpp" #include "barretenberg/avm_fuzzer/common/process.hpp" +#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp" #include "barretenberg/vm2/common/avm_io.hpp" #include "barretenberg/vm2/common/aztec_types.hpp" #include "barretenberg/vm2/common/field.hpp" @@ -24,8 +25,11 @@ struct FuzzerSimulationRequest { std::vector contract_classes; // Having addresses here avoids doing re-work in TS. std::vector> contract_instances; + // Public data tree writes to apply before simulation (e.g., for bytecode upgrades) + std::vector public_data_writes; - MSGPACK_CAMEL_CASE_FIELDS(ws_data_dir, ws_map_size_kb, tx, globals, contract_classes, contract_instances); + MSGPACK_CAMEL_CASE_FIELDS( + ws_data_dir, ws_map_size_kb, tx, globals, contract_classes, contract_instances, public_data_writes); }; struct SimulatorResult { @@ -45,17 +49,21 @@ class Simulator { Simulator(Simulator&&) = delete; Simulator& operator=(Simulator&&) = delete; Simulator() = default; - virtual SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr, - fuzzer::FuzzerContractDB& contract_db, - const Tx& tx) = 0; + virtual SimulatorResult simulate( + fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + const std::vector& public_data_writes) = 0; }; /// @brief uses barretenberg/vm2 to simulate the bytecode class CppSimulator : public Simulator { public: - SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr, - fuzzer::FuzzerContractDB& contract_db, - const Tx& tx) override; + SimulatorResult simulate( + fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + const std::vector& public_data_writes) override; }; /// @brief uses the yarn-project/simulator to simulate the bytecode @@ -77,9 +85,11 @@ class JsSimulator : public Simulator { static JsSimulator* getInstance(); static void initialize(std::string& simulator_path); - SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr, - fuzzer::FuzzerContractDB& contract_db, - const Tx& tx) override; + SimulatorResult simulate( + fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + const std::vector& public_data_writes) override; }; GlobalVariables create_default_globals(); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp index bac52c7b9904..22a91a1cde56 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp @@ -13,6 +13,7 @@ #include "barretenberg/avm_fuzzer/fuzzer_comparison_helper.hpp" #include "barretenberg/avm_fuzzer/mutations/fuzzer_data.hpp" #include "barretenberg/avm_fuzzer/mutations/tx_data.hpp" +#include "barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp" #include "barretenberg/common/log.hpp" #include "barretenberg/vm2/avm_api.hpp" #include "barretenberg/vm2/common/avm_io.hpp" @@ -24,23 +25,34 @@ using namespace bb::avm2::fuzzer; using namespace bb::avm2::simulation; using namespace bb::world_state; +extern size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize); + void setup_fuzzer_state(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contract_db, const FuzzerTxData& tx_data) { // Add all contract classes and instances to the contract DB + // There may be more classes than instances because of the possibility of mutated bytecodes that are used in + // upgrades - these are not directly instantiated for (size_t i = 0; i < tx_data.contract_classes.size(); ++i) { const auto& contract_class = tx_data.contract_classes[i]; + contract_db.add_contract_class(contract_class.id, contract_class); + } + + // Add contract instances to the contract DB + for (size_t i = 0; i < tx_data.contract_instances.size(); ++i) { const auto& contract_instance = tx_data.contract_instances[i]; auto contract_address = tx_data.contract_addresses[i]; - contract_db.add_contract_class(contract_class.id, contract_class); contract_db.add_contract_instance(contract_address, contract_instance); } - // Register the de-duplicated set of contract addresses to the world state (in insertion order) - std::unordered_set seen_addresses; + // Register contract addresses in the world state for (const auto& addr : tx_data.contract_addresses) { - if (seen_addresses.insert(addr).second) { - fuzz_info("Registering contract address in world state: ", addr); - ws_mgr.register_contract_address(addr); + ws_mgr.register_contract_address(addr); + } + + // Apply public data tree writes (e.g., for contract instance upgrades) + if (!tx_data.public_data_writes.empty()) { + for (const auto& write : tx_data.public_data_writes) { + ws_mgr.public_data_write(write); } } } @@ -68,7 +80,9 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr try { ws_mgr.checkpoint(); - cpp_result = cpp_simulator.simulate(ws_mgr, contract_db, tx_data.tx); + cpp_result = cpp_simulator.simulate(ws_mgr, contract_db, tx_data.tx, tx_data.public_data_writes); + fuzz_info("CppSimulator completed without exception"); + fuzz_info("CppSimulator result: ", cpp_result); ws_mgr.revert(); } catch (const std::exception& e) { fuzz_info("CppSimulator threw an exception: ", e.what()); @@ -82,7 +96,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr } ws_mgr.checkpoint(); - auto js_result = js_simulator->simulate(ws_mgr, contract_db, tx_data.tx); + auto js_result = js_simulator->simulate(ws_mgr, contract_db, tx_data.tx, tx_data.public_data_writes); // If the results do not match if (!compare_simulator_results(cpp_result, js_result)) { @@ -270,14 +284,23 @@ size_t mutate_tx_data(FuzzerContext& context, tx_data.contract_classes.clear(); tx_data.contract_instances.clear(); tx_data.contract_addresses.clear(); + tx_data.public_data_writes.clear(); std::vector contract_addresses; + std::unordered_set seen_addresses; for (auto& fuzzer_data : tx_data.input_programs) { const auto [bytecode, contract_class, contract_instance] = build_bytecode_and_artifacts(fuzzer_data); auto contract_address = simulation::compute_contract_address(contract_instance); - contract_addresses.push_back(contract_address); + // Skip duplicate addresses - multiple input_programs can generate the same address + if (seen_addresses.contains(contract_address)) { + fuzz_info("Skipping duplicate contract address: ", contract_address); + continue; + } + seen_addresses.insert(contract_address); + + contract_addresses.push_back(contract_address); tx_data.contract_classes.push_back(contract_class); tx_data.contract_instances.push_back(contract_instance); } @@ -297,17 +320,21 @@ size_t mutate_tx_data(FuzzerContext& context, } // Select mutation type (weighted against bytecode mutations) -- todo - auto mutation_type = std::uniform_int_distribution(0, 0); - TxDataMutationType mutation_choice = static_cast(mutation_type(rng)); + FuzzerTxDataMutationType mutation_choice = FUZZER_TX_DATA_MUTATION_CONFIGURATION.select(rng); switch (mutation_choice) { - case TxDataMutationType::TxMutation: + case FuzzerTxDataMutationType::TxMutation: mutate_tx(tx_data.tx, contract_addresses, rng); break; - // case TxDataMutationType::BytecodeMutation: - // // todo: Maybe here we can do some direct mutations on the bytecode - // // Mutations here are likely to cause immediate failure - // break; + case FuzzerTxDataMutationType::BytecodeMutation: { + // Mutate bytecode and append public data writes for world state setup + mutate_bytecode(tx_data.contract_classes, + tx_data.contract_instances, + tx_data.contract_addresses, + tx_data.public_data_writes, + rng); + break; + } // case TxDataMutationType::ContractClassMutation: // // Mutations here are likely to cause immediate failure // break; @@ -333,6 +360,12 @@ size_t mutate_tx_data(FuzzerContext& context, .calldata_hash = calldata_hash }, .calldata = calldata }); } + + // Compute effective gas fees matching TS computeEffectiveGasFees + // This must be done after any mutation that could affect gas settings or global variables + tx_data.tx.effective_gas_fees = + compute_effective_gas_fees(tx_data.global_variables.gas_fees, tx_data.tx.gas_settings); + auto [mutated_serialized_fuzzer_data, mutated_serialized_fuzzer_data_size] = msgpack_encode_buffer(tx_data); if (mutated_serialized_fuzzer_data_size > max_size) { delete[] mutated_serialized_fuzzer_data; diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp index b946f8ed2a76..8592a9aa118c 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp @@ -9,6 +9,9 @@ #include "barretenberg/avm_fuzzer/fuzz_lib/fuzzer_context.hpp" #include "barretenberg/avm_fuzzer/fuzz_lib/fuzzer_data.hpp" #include "barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp" +#include "barretenberg/avm_fuzzer/mutations/bytecode.hpp" +#include "barretenberg/avm_fuzzer/mutations/tx_data.hpp" +#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp" #include "barretenberg/serialize/msgpack_impl.hpp" #include "barretenberg/vm2/common/avm_io.hpp" #include "barretenberg/vm2/common/aztec_types.hpp" @@ -28,13 +31,17 @@ struct FuzzerTxData { GlobalVariables global_variables; ProtocolContracts protocol_contracts; + // Public data tree writes to be applied during state setup (e.g., for bytecode upgrades) + std::vector public_data_writes; + MSGPACK_FIELDS(input_programs, contract_classes, contract_instances, contract_addresses, tx, global_variables, - protocol_contracts); + protocol_contracts, + public_data_writes); }; inline std::ostream& operator<<(std::ostream& os, const FuzzerTxData& data) @@ -51,16 +58,22 @@ using ContractArtifacts = std::tuple; using FuzzerContext = bb::avm2::fuzzer::FuzzerContext; // Mutation configuration -enum class TxDataMutationType : uint8_t { +enum class FuzzerTxDataMutationType : uint8_t { TxMutation, - // todo: implement other mutation types - // BytecodeMutation, + BytecodeMutation, // ContractClassMutation, // ContractInstanceMutation, // GlobalVariablesMutation, // ProtocolContractsMutation }; +using FuzzerTxDataMutationConfig = WeightedSelectionConfig; + +constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({ + { FuzzerTxDataMutationType::TxMutation, 10 }, + { FuzzerTxDataMutationType::BytecodeMutation, 1 }, +}); + // Build bytecode and contract artifacts from fuzzer data ContractArtifacts build_bytecode_and_artifacts(FuzzerData& fuzzer_data); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/bytecode.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/bytecode.cpp new file mode 100644 index 000000000000..1c3a19848bbc --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/bytecode.cpp @@ -0,0 +1,82 @@ +#include "barretenberg/avm_fuzzer/mutations/bytecode.hpp" + +#include "barretenberg/avm_fuzzer/fuzz_lib/constants.hpp" +#include "barretenberg/crypto/poseidon2/poseidon2.hpp" +#include "barretenberg/vm2/common/aztec_constants.hpp" +#include "barretenberg/vm2/simulation/lib/contract_crypto.hpp" + +extern "C" size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize); + +namespace bb::avm2::fuzzer { + +void mutate_bytecode(std::vector& contract_classes, + std::vector& contract_instances, + const std::vector& contract_addresses, + std::vector& public_data_writes, + std::mt19937_64& rng) +{ + using Poseidon2 = crypto::Poseidon2; + + // Skip if no contracts to mutate + if (contract_classes.empty()) { + return; + } + + // Select a random contract + size_t idx = std::uniform_int_distribution(0, contract_classes.size() - 1)(rng); + + ContractClass& klass = contract_classes[idx]; + ContractInstance& instance = contract_instances[idx]; + const AztecAddress& address = contract_addresses[idx]; + + // Copy bytecode and mutate it, we allow the default byte-wise fuzzing strategy to modify the + // bytecode, including expanding or shrinking it. + std::vector bytecode = klass.packed_bytecode; + size_t original_size = bytecode.size(); + size_t max_size = original_size * 2; // Allow growth up to 2x original size + // We have to resize before calling LLVMFuzzerMutate to ensure there's enough space without writing OOB + // We have to resize after so that the vector's metadata is correct + // LLVMFuzzerMutate is a C function that operates on raw pointers + bytecode.resize(max_size); + size_t new_size = LLVMFuzzerMutate(bytecode.data(), original_size, max_size); + bytecode.resize(new_size); // We need to resize here in case it shrunk + + // Compute new bytecode commitment and class ID + FF new_bytecode_commitment = simulation::compute_public_bytecode_commitment(bytecode); + FF new_class_id = simulation::compute_contract_class_id( + klass.artifact_hash, klass.private_functions_root, new_bytecode_commitment); + + // Store original class ID before modifications + FF original_class_id = instance.original_contract_class_id; + + // Copy into NEW contract class with updated bytecode and id, we don't modify the existing one in case + // other instances refer to it + ContractClass new_class = klass; + new_class.id = new_class_id; + new_class.packed_bytecode = std::move(bytecode); + + // Update instance's current class ID to point to the newly upgraded-to class + fuzz_info("Contract at address ", address, ", upgraded from ", original_class_id, " -> ", new_class_id); + instance.current_contract_class_id = new_class_id; + + // Add the new contract class to the vector (for serialization to TS) + contract_classes.push_back(new_class); + + // Compute public data tree writes for UpdateCheck to pass + FF delayed_public_mutable_slot = Poseidon2::hash({ FF(UPDATED_CLASS_IDS_SLOT), address }); + + // Build preimage + FF metadata = 0; // The lower 32 bits are the timestamp_of_change, we set to 0 so it has "taken effect" + FF hash = Poseidon2::hash({ metadata, original_class_id, new_class_id }); + + std::array values = { metadata, original_class_id, new_class_id, hash }; + + for (size_t i = 0; i < 4; i++) { + FF storage_slot = delayed_public_mutable_slot + i; + FF leaf_slot = Poseidon2::hash( + { FF(DOM_SEP__PUBLIC_LEAF_INDEX), FF(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS), storage_slot }); + public_data_writes.push_back(bb::crypto::merkle_tree::PublicDataLeafValue{ leaf_slot, values[i] }); + } +} + +} // namespace bb::avm2::fuzzer diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/bytecode.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/bytecode.hpp new file mode 100644 index 000000000000..2f82ce19f870 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/bytecode.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp" +#include "barretenberg/vm2/common/aztec_types.hpp" +#include +#include + +namespace bb::avm2::fuzzer { + +void mutate_bytecode(std::vector& contract_classes, + std::vector& contract_instances, + const std::vector& contract_addresses, + std::vector& public_data_writes, + std::mt19937_64& rng); + +} // namespace bb::avm2::fuzzer diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.cpp index 5455e0ddf2a8..b376828f655b 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.cpp @@ -6,6 +6,7 @@ #include "barretenberg/avm_fuzzer/mutations/fuzzer_data.hpp" #include "barretenberg/avm_fuzzer/mutations/instructions/instruction_block.hpp" #include "barretenberg/avm_fuzzer/mutations/tx_types/accumulated_data.hpp" +#include "barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp" #include "barretenberg/avm_fuzzer/mutations/tx_types/public_call_request.hpp" #include "barretenberg/vm2/common/avm_io.hpp" #include "barretenberg/vm2/common/aztec_constants.hpp" @@ -58,20 +59,6 @@ void mutate_teardown(std::optional& teardown_call namespace bb::avm2::fuzzer { -// Gas bounds for mutation -constexpr uint32_t MIN_GAS = 1000; -constexpr uint32_t MAX_GAS = 10000000; - -// Fee bounds for mutation -constexpr uint128_t MIN_FEE = 1; -constexpr uint128_t MAX_FEE = 1000; - -constexpr uint32_t AVM_MAX_PROCESSABLE_DA_GAS = (MAX_NOTE_HASHES_PER_TX * AVM_EMITNOTEHASH_BASE_DA_GAS) + - (MAX_NULLIFIERS_PER_TX * AVM_EMITNULLIFIER_BASE_DA_GAS) + - (MAX_L2_TO_L1_MSGS_PER_TX * AVM_SENDL2TOL1MSG_BASE_DA_GAS) + - (MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * AVM_SSTORE_DYN_DA_GAS) + - (PUBLIC_LOGS_LENGTH * AVM_EMITUNENCRYPTEDLOG_BASE_DA_GAS); - void mutate_tx(Tx& tx, std::vector& contract_addresses, std::mt19937_64& rng) { auto choice = TX_MUTATION_CONFIGURATION.select(rng); @@ -102,98 +89,35 @@ void mutate_tx(Tx& tx, std::vector& contract_addresses, std::mt199 fuzz_info("Mutating revertible accumulated data"); mutate_revertible_accumulated_data(tx.revertible_accumulated_data, rng); break; - - // case 2: - // // Mutate gas_settings - // mutate_gas_settings(tx.gas_settings, rng); - // break; - // case 3: - // // Mutate effective_gas_fees - // mutate_gas_fees(tx.effective_gas_fees, rng); - // break; - // case 4: - // // Mutate Deployment data - // break; - // case 8: - // // Mutate gas_used_by_private - // break; - // case 9: - // // Mutate fee_payer - // break; - //} - } -} - -void mutate_gas_settings(GasSettings& gas_settings, std::mt19937_64& rng) -{ - auto choice = std::uniform_int_distribution(0, 3)(rng); - - switch (choice) { - case 0: - // Pick a Gas Limit between [0, AVM_MAX_PROCESSABLE_L2_GAS] - // fixme: probably should not mutate both l2_gas and da_gas to max in one go - gas_settings.gas_limits.l2_gas = std::uniform_int_distribution(0, AVM_MAX_PROCESSABLE_L2_GAS)(rng); - gas_settings.gas_limits.da_gas = std::uniform_int_distribution(0, AVM_MAX_PROCESSABLE_DA_GAS)(rng); - break; - case 1: - // Mutate teardown_gas_limits - gas_settings.teardown_gas_limits.l2_gas = - std::uniform_int_distribution(0, AVM_MAX_PROCESSABLE_L2_GAS)(rng); - gas_settings.teardown_gas_limits.da_gas = - std::uniform_int_distribution(0, AVM_MAX_PROCESSABLE_DA_GAS)(rng); - break; - case 2: - // Mutate max_fees_per_gas - // mutate_gas_fees(gas_settings.max_fees_per_gas, rng); - break; - case 3: - // Mutate max_priority_fees_per_gas - // mutate_gas_fees(gas_settings.max_priority_fees_per_gas, rng); - break; - } -} - -void mutate_gas(Gas& gas, std::mt19937_64& rng) -{ - auto choice = std::uniform_int_distribution(0, 2)(rng); - - switch (choice) { - case 0: - // Mutate l2_gas - gas.l2_gas = std::uniform_int_distribution(MIN_GAS, MAX_GAS)(rng); - break; - case 1: - // Mutate da_gas - gas.da_gas = std::uniform_int_distribution(MIN_GAS, MAX_GAS)(rng); - break; - case 2: - // Set both to same value - gas.l2_gas = gas.da_gas = std::uniform_int_distribution(MIN_GAS, MAX_GAS)(rng); - break; - } -} - -void mutate_gas_fees(GasFees& fees, std::mt19937_64& rng) -{ - auto choice = std::uniform_int_distribution(0, 3)(rng); - - switch (choice) { - case 0: - // Mutate fee_per_da_gas - fees.fee_per_da_gas = std::uniform_int_distribution(MIN_FEE, MAX_FEE)(rng); - break; - case 1: - // Mutate fee_per_l2_gas - fees.fee_per_l2_gas = std::uniform_int_distribution(MIN_FEE, MAX_FEE)(rng); - break; - case 2: - // Set both to zero - fees.fee_per_da_gas = 0; - fees.fee_per_l2_gas = 0; - break; - case 3: - // Set both to same non-zero value - fees.fee_per_da_gas = fees.fee_per_l2_gas = std::uniform_int_distribution(1, MAX_FEE)(rng); + case TxMutationOptions::GasSettings: + // Mutate gas_settings + fuzz_info("Mutating gas settings"); + mutate_gas_settings(tx.gas_settings, rng); + // Ensure effective_gas_fees <= max_fees_per_gas after mutation + tx.effective_gas_fees.fee_per_da_gas = + std::min(tx.effective_gas_fees.fee_per_da_gas, tx.gas_settings.max_fees_per_gas.fee_per_da_gas); + tx.effective_gas_fees.fee_per_l2_gas = + std::min(tx.effective_gas_fees.fee_per_l2_gas, tx.gas_settings.max_fees_per_gas.fee_per_l2_gas); + break; + case TxMutationOptions::GasFees: + // Mutate effective_gas_fees + fuzz_info("Mutating effective gas fees"); + mutate_gas_fees(tx.effective_gas_fees, rng); + // Ensure effective_gas_fees <= max_fees_per_gas after mutation + tx.effective_gas_fees.fee_per_da_gas = + std::min(tx.effective_gas_fees.fee_per_da_gas, tx.gas_settings.max_fees_per_gas.fee_per_da_gas); + tx.effective_gas_fees.fee_per_l2_gas = + std::min(tx.effective_gas_fees.fee_per_l2_gas, tx.gas_settings.max_fees_per_gas.fee_per_l2_gas); + break; + case TxMutationOptions::GasUsedByPrivate: + // Mutate gas_used_by_private + fuzz_info("Mutating gas used by private"); + mutate_gas(tx.gas_used_by_private, rng, tx.gas_settings.gas_limits); + break; + case TxMutationOptions::FeePayer: + // Mutate fee_payer + fuzz_info("Mutating fee payer"); + mutate_field(tx.fee_payer, rng, BASIC_FIELD_MUTATION_CONFIGURATION); break; } } diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.hpp index 3bc6d10d6534..1934d4bb7d27 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_data.hpp @@ -15,9 +15,13 @@ enum class TxMutationOptions { TearDownEnqueuedCall, NonRevertibleData, RevertibleData, + GasSettings, + GasFees, + GasUsedByPrivate, + FeePayer }; -using TxMutationConfig = WeightedSelectionConfig; +using TxMutationConfig = WeightedSelectionConfig; constexpr TxMutationConfig TX_MUTATION_CONFIGURATION = TxMutationConfig({ { TxMutationOptions::SetupEnqueuedCalls, 30 }, @@ -25,21 +29,16 @@ constexpr TxMutationConfig TX_MUTATION_CONFIGURATION = TxMutationConfig({ { TxMutationOptions::TearDownEnqueuedCall, 10 }, { TxMutationOptions::NonRevertibleData, 15 }, { TxMutationOptions::RevertibleData, 15 }, + { TxMutationOptions::GasSettings, 5 }, + { TxMutationOptions::GasFees, 3 }, + { TxMutationOptions::GasUsedByPrivate, 1 }, + { TxMutationOptions::FeePayer, 1 }, }); namespace bb::avm2::fuzzer { void mutate_tx(Tx& tx, std::vector& contract_addresses, std::mt19937_64& rng); -// GasSettings mutation -void mutate_gas_settings(GasSettings& gas_settings, std::mt19937_64& rng); - -// Gas mutation -void mutate_gas(Gas& gas, std::mt19937_64& rng); - -// GasFees mutation -void mutate_gas_fees(GasFees& fees, std::mt19937_64& rng); - void mutate_fuzzer_data_vec(const FuzzerContext& context, std::vector& enqueued_calls, std::mt19937_64& rng, diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/accumulated_data.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/accumulated_data.hpp index 0cc65ab7b18e..2a1609656a11 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/accumulated_data.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/accumulated_data.hpp @@ -31,8 +31,8 @@ constexpr AccumulatedDataMutationConfig ACCUMULATED_DATA_MUTATION_CONFIGURATION template void mutate_vec_with_limit(std::vector& vec, std::mt19937_64& rng, - std::function mutate_element_function, - std::function generate_random_element_function, + const std::function& mutate_element_function, + const std::function& generate_random_element_function, const VecMutationConfig& config, size_t vec_limit) { diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.cpp new file mode 100644 index 000000000000..4d12a4f3fd29 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.cpp @@ -0,0 +1,139 @@ +#include "barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp" + +#include "barretenberg/avm_fuzzer/fuzz_lib/constants.hpp" +#include "barretenberg/avm_fuzzer/mutations/basic_types/field.hpp" +#include "barretenberg/avm_fuzzer/mutations/basic_types/vector.hpp" +#include "barretenberg/avm_fuzzer/mutations/configuration.hpp" +#include "barretenberg/common/serialize.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/vm2/common/avm_io.hpp" + +using bb::avm2::AztecAddress; +using bb::avm2::FF; + +namespace { + +constexpr uint128_t MAX_U128 = ~static_cast(0); + +uint128_t generate_u128(std::mt19937_64& rng, uint128_t min = 0, uint128_t max = MAX_U128) +{ + uint64_t high = std::uniform_int_distribution()(rng); + uint64_t low = std::uniform_int_distribution()(rng); + uint128_t value = (static_cast(high) << 64) | static_cast(low); + // Scale to desired range + return min + (value % (max - min + 1)); +} + +} // namespace + +namespace bb::avm2::fuzzer { + +Gas generate_gas(std::mt19937_64& rng) +{ + uint32_t l2_gas = std::uniform_int_distribution(MIN_GAS, AVM_MAX_PROCESSABLE_L2_GAS)(rng); + uint32_t da_gas = std::uniform_int_distribution(MIN_GAS, AVM_MAX_PROCESSABLE_DA_GAS)(rng); + + return Gas{ l2_gas, da_gas }; +} + +void mutate_gas(Gas& gas, std::mt19937_64& rng, const Gas& max) +{ + auto choice = std::uniform_int_distribution(0, 1)(rng); + + switch (choice) { + case 0: + // Mutate l2_gas + gas.l2_gas = std::uniform_int_distribution(MIN_GAS, max.l2_gas)(rng); + break; + case 1: + // Mutate da_gas + gas.da_gas = std::uniform_int_distribution(MIN_GAS, max.da_gas)(rng); + break; + } +} + +GasFees generate_gas_fees(std::mt19937_64& rng) +{ + uint128_t fee_per_da_gas = generate_u128(rng, MIN_FEE, MAX_FEE); + uint128_t fee_per_l2_gas = generate_u128(rng, MIN_FEE, MAX_FEE); + + return GasFees{ + fee_per_da_gas, + fee_per_l2_gas, + }; +} + +void mutate_gas_fees(GasFees& fees, std::mt19937_64& rng) +{ + auto choice = std::uniform_int_distribution(0, 1)(rng); + + switch (choice) { + case 0: + // Mutate fee_per_da_gas + fees.fee_per_da_gas = generate_u128(rng, MIN_FEE, MAX_FEE); + break; + case 1: + // Mutate fee_per_l2_gas + fees.fee_per_l2_gas = generate_u128(rng, MIN_FEE, MAX_FEE); + break; + } +} + +GasSettings generate_gas_settings(std::mt19937_64& rng) +{ + Gas gas_limits = generate_gas(rng); + Gas teardown_gas_limits = generate_gas(rng); + GasFees max_fees_per_gas = generate_gas_fees(rng); + GasFees max_priority_fees_per_gas = generate_gas_fees(rng); + + return GasSettings{ + gas_limits, + teardown_gas_limits, + max_fees_per_gas, + max_priority_fees_per_gas, + }; +} + +void mutate_gas_settings(GasSettings& gas_settings, std::mt19937_64& rng) +{ + auto choice = GAS_SETTINGS_MUTATION_CONFIGURATION.select(rng); + + switch (choice) { + case GasSettingsMutationOptions::GasLimits: + // Mutate gas_limits + mutate_gas(gas_settings.gas_limits, rng); + break; + case GasSettingsMutationOptions::TeardownGasLimits: + // Mutate teardown_gas_limits + mutate_gas(gas_settings.teardown_gas_limits, rng); + break; + case GasSettingsMutationOptions::MaxFeesPerGas: + // Mutate max_fees_per_gas + mutate_gas_fees(gas_settings.max_fees_per_gas, rng); + break; + case GasSettingsMutationOptions::MaxPriorityFeesPerGas: + // Mutate max_priority_fees_per_gas + mutate_gas_fees(gas_settings.max_priority_fees_per_gas, rng); + break; + } +} + +GasFees compute_effective_gas_fees(const GasFees& gas_fees, const GasSettings& gas_settings) +{ + // Match TS computeEffectiveGasFees from yarn-project/stdlib/src/fees/transaction_fee.ts + // priorityFees = min(maxPriorityFeesPerGas, maxFeesPerGas - gasFees) + // effectiveFees = gasFees + priorityFees + auto min_u128 = [](uint128_t a, uint128_t b) { return a < b ? a : b; }; + + uint128_t priority_da = min_u128(gas_settings.max_priority_fees_per_gas.fee_per_da_gas, + gas_settings.max_fees_per_gas.fee_per_da_gas - gas_fees.fee_per_da_gas); + uint128_t priority_l2 = min_u128(gas_settings.max_priority_fees_per_gas.fee_per_l2_gas, + gas_settings.max_fees_per_gas.fee_per_l2_gas - gas_fees.fee_per_l2_gas); + + return GasFees{ + .fee_per_da_gas = gas_fees.fee_per_da_gas + priority_da, + .fee_per_l2_gas = gas_fees.fee_per_l2_gas + priority_l2, + }; +} + +} // namespace bb::avm2::fuzzer diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp new file mode 100644 index 000000000000..f9d3ee0b57fc --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include "barretenberg/avm_fuzzer/common/weighted_selection.hpp" +#include "barretenberg/vm2/common/avm_io.hpp" + +namespace bb::avm2::fuzzer { + +// Fee bounds for mutation. +// MIN_FEE must be >= 1 to prevent underflow in compute_effective_gas_fees, since +// global_variables.gas_fees is hardcoded to {1, 1}. This can change once we enable +// smart mutations of global variables that maintain the invariant max_fees_per_gas >= gas_fees. +constexpr uint128_t MIN_FEE = 1; +constexpr uint128_t MAX_FEE = 1000; +// +// Gas bounds for mutation +constexpr uint32_t MIN_GAS = 0; +constexpr uint32_t AVM_MAX_PROCESSABLE_DA_GAS = (MAX_NOTE_HASHES_PER_TX * AVM_EMITNOTEHASH_BASE_DA_GAS) + + (MAX_NULLIFIERS_PER_TX * AVM_EMITNULLIFIER_BASE_DA_GAS) + + (MAX_L2_TO_L1_MSGS_PER_TX * AVM_SENDL2TOL1MSG_BASE_DA_GAS) + + (MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * AVM_SSTORE_DYN_DA_GAS) + + (PUBLIC_LOGS_LENGTH * AVM_EMITUNENCRYPTEDLOG_BASE_DA_GAS); + +enum class GasSettingsMutationOptions : uint8_t { + GasLimits, + TeardownGasLimits, + MaxFeesPerGas, + MaxPriorityFeesPerGas, +}; + +using GasSettingsMutationConfig = WeightedSelectionConfig; + +constexpr GasSettingsMutationConfig GAS_SETTINGS_MUTATION_CONFIGURATION = GasSettingsMutationConfig({ + { GasSettingsMutationOptions::GasLimits, 20 }, + { GasSettingsMutationOptions::TeardownGasLimits, 10 }, + { GasSettingsMutationOptions::MaxFeesPerGas, 20 }, + { GasSettingsMutationOptions::MaxPriorityFeesPerGas, 5 }, +}); + +Gas generate_gas(std::mt19937_64& rng); +void mutate_gas(Gas& gas, + std::mt19937_64& rng, + const Gas& max = Gas{ AVM_MAX_PROCESSABLE_L2_GAS, AVM_MAX_PROCESSABLE_DA_GAS }); + +GasSettings generate_gas_settings(std::mt19937_64& rng); +void mutate_gas_settings(GasSettings& data, std::mt19937_64& rng); + +GasFees generate_gas_fees(std::mt19937_64& rng); +void mutate_gas_fees(GasFees& gas_fees, std::mt19937_64& rng); + +// Compute effective gas fees matching TS computeEffectiveGasFees. +// Requires: maxFeesPerGas >= gasFees (otherwise underflow) +GasFees compute_effective_gas_fees(const GasFees& gas_fees, const GasSettings& gas_settings); + +} // namespace bb::avm2::fuzzer diff --git a/yarn-project/simulator/src/public/fuzzing/avm_fuzzer_simulator.ts b/yarn-project/simulator/src/public/fuzzing/avm_fuzzer_simulator.ts index a5ff9ef70ba1..40ca179cb68d 100644 --- a/yarn-project/simulator/src/public/fuzzing/avm_fuzzer_simulator.ts +++ b/yarn-project/simulator/src/public/fuzzing/avm_fuzzer_simulator.ts @@ -19,7 +19,7 @@ import { import { PrivateLog } from '@aztec/stdlib/logs'; import { ScopedL2ToL1Message } from '@aztec/stdlib/messaging'; import { ChonkProof } from '@aztec/stdlib/proofs'; -import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; +import { MerkleTreeId, type MerkleTreeWriteOperations, PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { BlockHeader, GlobalVariables, HashedValues, Tx, TxConstantData, TxContext, TxHash } from '@aztec/stdlib/tx'; import type { NativeWorldStateService } from '@aztec/world-state'; @@ -40,6 +40,7 @@ export class FuzzerSimulationRequest { public readonly globals: GlobalVariables, public readonly contractClasses: any[], // Raw, processed by addContractClassFromCpp public readonly contractInstances: [any, any][], // Raw pairs [address, instance] + public readonly publicDataWrites: any[], // Raw public data tree writes to apply before simulation ) {} static fromPlainObject(obj: any): FuzzerSimulationRequest { @@ -53,6 +54,7 @@ export class FuzzerSimulationRequest { GlobalVariables.fromPlainObject(obj.globals), obj.contractClasses, obj.contractInstances, + obj.publicDataWrites ?? [], ); } } @@ -237,4 +239,15 @@ export class AvmFuzzerSimulator extends BaseAvmSimulationTester { const instance = contractInstanceWithAddressFromPlainObject(address, rawInstance); await this.addContractInstance(instance); } + + /** + * Apply public data tree writes from C++ raw msgpack data. + * This is used to pre-populate the public data tree before simulation (e.g., for bytecode upgrades). + */ + public async applyPublicDataWrites(rawWrites: any[]): Promise { + for (const rawWrite of rawWrites) { + const leaf = PublicDataTreeLeaf.fromPlainObject(rawWrite); + await this.merkleTrees.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [leaf.toBuffer()]); + } + } } diff --git a/yarn-project/simulator/src/public/fuzzing/avm_simulator_bin.ts b/yarn-project/simulator/src/public/fuzzing/avm_simulator_bin.ts index 808f50298941..95cfa07c4e03 100644 --- a/yarn-project/simulator/src/public/fuzzing/avm_simulator_bin.ts +++ b/yarn-project/simulator/src/public/fuzzing/avm_simulator_bin.ts @@ -55,11 +55,15 @@ async function simulateWithFuzzer( globals: GlobalVariables, rawContractClasses: any[], // Replace these when we are moving contract classes to TS rawContractInstances: [any, any][], // Replace these when we are moving contract instances to TS + rawPublicDataWrites: any[], // Public data tree writes to apply before simulation ): Promise<{ reverted: boolean; output: Fr[]; revertReason?: string; publicInputs: AvmCircuitPublicInputs }> { const worldStateService = await openExistingWorldState(dataDir, mapSizeKb); const simulator = await AvmFuzzerSimulator.create(worldStateService, globals); + // Apply public data writes before simulation (e.g., for bytecode upgrades) + await simulator.applyPublicDataWrites(rawPublicDataWrites); + // Register contract classes from C++ for (const rawClass of rawContractClasses) { await simulator.addContractClassFromCpp(rawClass); @@ -99,6 +103,7 @@ async function execute(base64Line: string): Promise { request.globals, request.contractClasses, request.contractInstances, + request.publicDataWrites, ); // Serialize the result to msgpack and encode it in base64 for output