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 a7106ee766cf..1d5f8a923cb2 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp @@ -25,24 +25,33 @@ 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) + for (const auto& write : tx_data.public_data_writes) { + ws_mgr.public_data_write(write); } } @@ -69,7 +78,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()); @@ -83,7 +94,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)) { @@ -271,14 +282,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); } @@ -298,17 +318,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; 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_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/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