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 c96a60510f49..1bfcc330495d 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp @@ -44,14 +44,16 @@ SimulatorResult fuzz_against_ts_simulator(FuzzerData& fuzzer_data, FuzzerContext try { ws_mgr->checkpoint(); - cpp_result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}); + cpp_result = cpp_simulator.simulate( + *ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*protocol_contracts=*/{}); 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, globals, /*public_data_writes=*/{}); + auto js_result = + js_simulator->simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*protocol_contracts=*/{}); context.reset(); 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 8dd4bd6f95e1..f886b09465e2 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 @@ -56,7 +56,8 @@ class FuzzTest : public ::testing::Test { auto cpp_simulator = CppSimulator(); auto globals = create_default_globals(); - auto result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}); + auto result = cpp_simulator.simulate( + *ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*protocol_contracts=*/{}); ws_mgr->revert(); 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 d05ebb19b5e4..09bd1cc9b5ab 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp @@ -38,7 +38,8 @@ std::string serialize_simulation_request( const Tx& tx, const GlobalVariables& globals, const FuzzerContractDB& contract_db, - const std::vector& public_data_writes) + const std::vector& public_data_writes, + const ProtocolContracts& protocol_contracts) { // Build vectors from contract_db std::vector classes_vec = contract_db.get_contract_classes(); @@ -52,6 +53,7 @@ std::string serialize_simulation_request( .contract_classes = std::move(classes_vec), .contract_instances = std::move(instances_vec), .public_data_writes = public_data_writes, + .protocol_contracts = protocol_contracts, }; auto [buffer, size] = msgpack_encode_buffer(request); @@ -80,7 +82,8 @@ SimulatorResult CppSimulator::simulate( fuzzer::FuzzerContractDB& contract_db, const Tx& tx, const GlobalVariables& globals, - [[maybe_unused]] const std::vector& public_data_writes) + [[maybe_unused]] const std::vector& public_data_writes, + const ProtocolContracts& protocol_contracts) { // Note: public_data_writes are already applied to C++ world state in setup_fuzzer_state @@ -93,8 +96,6 @@ SimulatorResult CppSimulator::simulate( }, }; - ProtocolContracts protocol_contracts{}; - WorldState& ws = ws_mgr.get_world_state(); WorldStateRevision ws_rev = ws_mgr.get_current_revision(); @@ -153,9 +154,11 @@ SimulatorResult JsSimulator::simulate( fuzzer::FuzzerContractDB& contract_db, const Tx& tx, const GlobalVariables& globals, - const std::vector& public_data_writes) + const std::vector& public_data_writes, + const ProtocolContracts& protocol_contracts) { - std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes); + std::string serialized = + serialize_simulation_request(tx, globals, contract_db, public_data_writes, protocol_contracts); // Send the request process.write_line(serialized); 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 f8e32bf7ba9a..6b45b6026ad4 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp @@ -27,9 +27,17 @@ struct FuzzerSimulationRequest { 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, public_data_writes); + // Protocol contracts mapping (canonical address index -> derived address) + ProtocolContracts protocol_contracts; + + MSGPACK_CAMEL_CASE_FIELDS(ws_data_dir, + ws_map_size_kb, + tx, + globals, + contract_classes, + contract_instances, + public_data_writes, + protocol_contracts); }; struct SimulatorResult { @@ -54,18 +62,19 @@ class Simulator { fuzzer::FuzzerContractDB& contract_db, const Tx& tx, const GlobalVariables& globals, - const std::vector& public_data_writes) = 0; + const std::vector& public_data_writes, + const ProtocolContracts& protocol_contracts) = 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, - const GlobalVariables& globals, - const std::vector& public_data_writes) override; + SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + const GlobalVariables& globals, + const std::vector& public_data_writes, + const ProtocolContracts& protocol_contracts) override; }; /// @brief uses the yarn-project/simulator to simulate the bytecode @@ -87,12 +96,12 @@ 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, - const GlobalVariables& globals, - const std::vector& public_data_writes) override; + SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr, + fuzzer::FuzzerContractDB& contract_db, + const Tx& tx, + const GlobalVariables& globals, + const std::vector& public_data_writes, + const ProtocolContracts& protocol_contracts) 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 8165a6588c07..ad40f96de7c3 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp @@ -14,6 +14,7 @@ #include "barretenberg/avm_fuzzer/mutations/basic_types/uint64_t.hpp" #include "barretenberg/avm_fuzzer/mutations/configuration.hpp" #include "barretenberg/avm_fuzzer/mutations/fuzzer_data.hpp" +#include "barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp" #include "barretenberg/avm_fuzzer/mutations/tx_data.hpp" #include "barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp" #include "barretenberg/common/log.hpp" @@ -47,6 +48,22 @@ void setup_fuzzer_state(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr contract_db.add_contract_instance(contract_address, contract_instance); } + // For protocol contracts, also add instances keyed by canonical address (1-11). + // This is needed because protocol contracts are looked up by canonical address, + // but the derived address in protocol_contracts.derived_addresses maps to the actual instance. + for (size_t i = 0; i < tx_data.protocol_contracts.derived_addresses.size(); ++i) { + const auto& derived_address = tx_data.protocol_contracts.derived_addresses[i]; + if (!derived_address.is_zero()) { + // Canonical address is index + 1 (addresses 1-11 map to indices 0-10) + AztecAddress canonical_address(static_cast(i + 1)); + // Find the instance for this derived address and also add it by canonical address + auto maybe_instance = contract_db.get_contract_instance(derived_address); + if (maybe_instance.has_value()) { + contract_db.add_contract_instance(canonical_address, maybe_instance.value()); + } + } + } + // Register contract addresses in the world state for (const auto& addr : tx_data.contract_addresses) { ws_mgr.register_contract_address(addr); @@ -81,8 +98,12 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr try { ws_mgr.checkpoint(); - cpp_result = cpp_simulator.simulate( - ws_mgr, contract_db, tx_data.tx, tx_data.global_variables, tx_data.public_data_writes); + cpp_result = cpp_simulator.simulate(ws_mgr, + contract_db, + tx_data.tx, + tx_data.global_variables, + tx_data.public_data_writes, + tx_data.protocol_contracts); fuzz_info("CppSimulator completed without exception"); fuzz_info("CppSimulator result: ", cpp_result); ws_mgr.revert(); @@ -98,8 +119,12 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr } ws_mgr.checkpoint(); - auto js_result = - js_simulator->simulate(ws_mgr, contract_db, tx_data.tx, tx_data.global_variables, tx_data.public_data_writes); + auto js_result = js_simulator->simulate(ws_mgr, + contract_db, + tx_data.tx, + tx_data.global_variables, + tx_data.public_data_writes, + tx_data.protocol_contracts); // If the results do not match if (!compare_simulator_results(cpp_result, js_result)) { @@ -122,7 +147,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr /// @throws An exception if simulation results differ or check_circuit fails int fuzz_prover(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contract_db, FuzzerTxData& tx_data) { - ProtocolContracts protocol_contracts{}; + ProtocolContracts& protocol_contracts = tx_data.protocol_contracts; WorldState& ws = ws_mgr.get_world_state(); WorldStateRevision ws_rev = ws_mgr.get_current_revision(); AvmSimulationHelper helper; @@ -311,6 +336,15 @@ size_t mutate_tx_data(FuzzerContext& context, tx_data.contract_addresses = contract_addresses; + // Clear any protocol contract derived addresses that reference addresses no longer in the contract set. + // This can happen when bytecode mutation causes contract addresses to change between mutation rounds. + // note: we shouldnt have to aggressively do this if we modify only 1 fuzzer data at a time. + for (auto& derived_address : tx_data.protocol_contracts.derived_addresses) { + if (!derived_address.is_zero() && !seen_addresses.contains(derived_address)) { + derived_address = AztecAddress(0); + } + } + // Ensure all enqueued calls have valid contract addresses (not placeholders) // We may add more advanced mutation to change contract addresses later, right now we just ensure they are valid auto idx_dist = std::uniform_int_distribution(0, contract_addresses.size() - 1); @@ -352,14 +386,10 @@ size_t mutate_tx_data(FuzzerContext& context, // This is just mutating the gas values and timestamp mutate_uint64_t(tx_data.global_variables.timestamp, rng, BASIC_UINT64_T_MUTATION_CONFIGURATION); mutate_gas_fees(tx_data.global_variables.gas_fees, rng); - // This must be less than or equal to the tx max fees per gas - tx_data.global_variables.gas_fees.fee_per_da_gas = std::min( - tx_data.global_variables.gas_fees.fee_per_da_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_da_gas); - tx_data.global_variables.gas_fees.fee_per_l2_gas = std::min( - tx_data.global_variables.gas_fees.fee_per_l2_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_l2_gas); break; - // case TxDataMutationType::ProtocolContractsMutation: - // break; + case FuzzerTxDataMutationType::ProtocolContractsMutation: + mutate_protocol_contracts(tx_data.protocol_contracts, tx_data.tx, tx_data.contract_addresses, rng); + break; } // todo: do we need to ensure this or are should we able to process 0 enqueued calls? @@ -376,6 +406,13 @@ size_t mutate_tx_data(FuzzerContext& context, .calldata = calldata }); } + // Ensure global gas_fees <= max_fees_per_gas (required for compute_effective_gas_fees) + // This must run after ANY mutation since TxMutation can reduce max_fees_per_gas + tx_data.global_variables.gas_fees.fee_per_da_gas = std::min( + tx_data.global_variables.gas_fees.fee_per_da_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_da_gas); + tx_data.global_variables.gas_fees.fee_per_l2_gas = std::min( + tx_data.global_variables.gas_fees.fee_per_l2_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_l2_gas); + // 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 = diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp index f27d3394e8b7..fd4e82663452 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp @@ -64,10 +64,10 @@ enum class FuzzerTxDataMutationType : uint8_t { ContractClassMutation, ContractInstanceMutation, GlobalVariablesMutation, - // ProtocolContractsMutation + ProtocolContractsMutation }; -using FuzzerTxDataMutationConfig = WeightedSelectionConfig; +using FuzzerTxDataMutationConfig = WeightedSelectionConfig; constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({ { FuzzerTxDataMutationType::TxMutation, 10 }, @@ -75,6 +75,7 @@ constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = Fuz { FuzzerTxDataMutationType::ContractClassMutation, 1 }, { FuzzerTxDataMutationType::ContractInstanceMutation, 1 }, { FuzzerTxDataMutationType::GlobalVariablesMutation, 4 }, + { FuzzerTxDataMutationType::ProtocolContractsMutation, 4 }, }); // Build bytecode and contract artifacts from fuzzer data diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/protocol_contracts.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/protocol_contracts.cpp new file mode 100644 index 000000000000..26f25f4a02f5 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/protocol_contracts.cpp @@ -0,0 +1,79 @@ +#include "barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp" + +#include "barretenberg/vm2/common/aztec_constants.hpp" +#include "barretenberg/vm2/common/aztec_types.hpp" + +namespace bb::avm2::fuzzer { + +namespace { + +void update_enqueued_calls_for_protocol_contract(Tx& tx, + const AztecAddress& prev_address, + const AztecAddress& new_address) +{ + // Update setup_enqueued_calls + for (auto& call : tx.setup_enqueued_calls) { + if (call.request.contract_address == prev_address) { + call.request.contract_address = new_address; + } + } + // Update app_logic_enqueued_calls + for (auto& call : tx.app_logic_enqueued_calls) { + if (call.request.contract_address == prev_address) { + call.request.contract_address = new_address; + } + } + // Update teardown_enqueued_call + if (tx.teardown_enqueued_call.has_value() && tx.teardown_enqueued_call->request.contract_address == prev_address) { + tx.teardown_enqueued_call->request.contract_address = new_address; + } +} + +} // namespace + +void mutate_protocol_contracts(ProtocolContracts& protocol_contracts, + Tx& tx, + const std::vector& contract_addresses, + std::mt19937_64& rng) +{ + if (contract_addresses.empty()) { + return; + } + + auto choice = PROTOCOL_CONTRACTS_MUTATION_CONFIGURATION.select(rng); + switch (choice) { + case ProtocolContractsMutationOptions::Mutate: { + // Pick a random index and add a protocol contract + auto protocol_contract_index = std::uniform_int_distribution(0, MAX_PROTOCOL_CONTRACTS - 1)(rng); + auto contract_address_index = std::uniform_int_distribution(0, contract_addresses.size() - 1)(rng); + + AztecAddress derived_address = contract_addresses[contract_address_index]; + protocol_contracts.derived_addresses[protocol_contract_index] = derived_address; + + // The index of the protocol contracts array maps to canonical address. + // Add 1 because canonical addresses are 1-indexed + AztecAddress canonical_address(static_cast(protocol_contract_index + 1)); + + // todo(ilyas): there should be a more efficient way to do this + // Update any enqueued calls that reference the derived address to now use the canonical address + update_enqueued_calls_for_protocol_contract( + tx, /*prev_address=*/derived_address, /*new_address=*/canonical_address); + break; + } + case ProtocolContractsMutationOptions::Remove: { + // Pick an index, and zero it out, removing the protocol contract. It may already be zero but the fuzzer should + // figure out better mutations + size_t idx = std::uniform_int_distribution(0, MAX_PROTOCOL_CONTRACTS - 1)(rng); + AztecAddress canonical_address(static_cast(idx + 1)); + AztecAddress derived_address = protocol_contracts.derived_addresses[idx]; + // Update any enqueued calls that reference the canonical address to use the derived address + update_enqueued_calls_for_protocol_contract( + tx, /*prev_address=*/canonical_address, /*new_address=*/derived_address); + // Set the derived address to zero + protocol_contracts.derived_addresses[idx] = AztecAddress(0); + break; + } + } +} + +} // namespace bb::avm2::fuzzer diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp new file mode 100644 index 000000000000..c95cfa2806bf --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "barretenberg/avm_fuzzer/common/weighted_selection.hpp" +#include "barretenberg/vm2/common/avm_io.hpp" +#include "barretenberg/vm2/common/aztec_types.hpp" + +#include + +namespace bb::avm2::fuzzer { + +enum class ProtocolContractsMutationOptions : uint8_t { + Mutate, + Remove, +}; + +using ProtocolContractsMutationConfig = WeightedSelectionConfig; + +constexpr ProtocolContractsMutationConfig PROTOCOL_CONTRACTS_MUTATION_CONFIGURATION = ProtocolContractsMutationConfig({ + { ProtocolContractsMutationOptions::Mutate, 3 }, + { ProtocolContractsMutationOptions::Remove, 1 }, +}); + +void mutate_protocol_contracts(bb::avm2::ProtocolContracts& protocol_contracts, + bb::avm2::Tx& tx, + const std::vector& contract_addresses, + std::mt19937_64& rng); + +} // 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 index f9d3ee0b57fc..a369048f2073 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp @@ -9,9 +9,8 @@ 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. +// MIN_FEE must be >= 1 to prevent underflow in compute_effective_gas_fees. +// The invariant max_fees_per_gas >= gas_fees is enforced in fuzzer_lib.cpp after mutations. constexpr uint128_t MIN_FEE = 1; constexpr uint128_t MAX_FEE = 1000; // 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 40ca179cb68d..9573e5ccfbbd 100644 --- a/yarn-project/simulator/src/public/fuzzing/avm_fuzzer_simulator.ts +++ b/yarn-project/simulator/src/public/fuzzing/avm_fuzzer_simulator.ts @@ -4,6 +4,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, + MAX_PROTOCOL_CONTRACTS, } from '@aztec/constants'; import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; @@ -20,7 +21,16 @@ import { PrivateLog } from '@aztec/stdlib/logs'; import { ScopedL2ToL1Message } from '@aztec/stdlib/messaging'; import { ChonkProof } from '@aztec/stdlib/proofs'; import { MerkleTreeId, type MerkleTreeWriteOperations, PublicDataTreeLeaf } from '@aztec/stdlib/trees'; -import { BlockHeader, GlobalVariables, HashedValues, Tx, TxConstantData, TxContext, TxHash } from '@aztec/stdlib/tx'; +import { + BlockHeader, + GlobalVariables, + HashedValues, + ProtocolContracts, + Tx, + TxConstantData, + TxContext, + TxHash, +} from '@aztec/stdlib/tx'; import type { NativeWorldStateService } from '@aztec/world-state'; import { BaseAvmSimulationTester } from '../avm/fixtures/base_avm_simulation_tester.js'; @@ -41,6 +51,7 @@ export class FuzzerSimulationRequest { 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 + public readonly protocolContracts: ProtocolContracts, // Protocol contracts mapping from C++ ) {} static fromPlainObject(obj: any): FuzzerSimulationRequest { @@ -55,6 +66,7 @@ export class FuzzerSimulationRequest { obj.contractClasses, obj.contractInstances, obj.publicDataWrites ?? [], + ProtocolContracts.fromPlainObject(obj.protocolContracts), ); } } @@ -183,16 +195,23 @@ export class AvmFuzzerSimulator extends BaseAvmSimulationTester { merkleTrees: MerkleTreeWriteOperations, contractDataSource: SimpleContractDataSource, globals: GlobalVariables, + protocolContracts: ProtocolContracts, ) { super(contractDataSource, merkleTrees); const contractsDb = new PublicContractsDB(contractDataSource); - this.simulator = new PublicTxSimulator(merkleTrees, contractsDb, globals, { - skipFeeEnforcement: false, - collectDebugLogs: false, - collectHints: false, - collectStatistics: false, - collectCallMetadata: false, - }); + this.simulator = new PublicTxSimulator( + merkleTrees, + contractsDb, + globals, + { + skipFeeEnforcement: false, + collectDebugLogs: false, + collectHints: false, + collectStatistics: false, + collectCallMetadata: false, + }, + protocolContracts, + ); } /** @@ -201,10 +220,11 @@ export class AvmFuzzerSimulator extends BaseAvmSimulationTester { public static async create( worldStateService: NativeWorldStateService, globals: GlobalVariables, + protocolContracts: ProtocolContracts, ): Promise { const contractDataSource = new SimpleContractDataSource(); const merkleTrees = await worldStateService.fork(); - return new AvmFuzzerSimulator(merkleTrees, contractDataSource, globals); + return new AvmFuzzerSimulator(merkleTrees, contractDataSource, globals, protocolContracts); } /** @@ -232,12 +252,15 @@ export class AvmFuzzerSimulator extends BaseAvmSimulationTester { /** * Add a contract instance from C++ raw msgpack data. - * This also inserts the contract address nullifier into the nullifier tree. + * This also inserts the contract address nullifier into the nullifier tree, + * unless the address is a protocol canonical address (1-11). */ public async addContractInstanceFromCpp(rawAddress: any, rawInstance: any): Promise { const address = AztecAddress.fromPlainObject(rawAddress); const instance = contractInstanceWithAddressFromPlainObject(address, rawInstance); - await this.addContractInstance(instance); + // Protocol canonical addresses (1-11) should not have nullifiers inserted + const isProtocolCanonicalAddress = address.toBigInt() <= MAX_PROTOCOL_CONTRACTS && address.toBigInt() >= 1n; + await this.addContractInstance(instance, /* skipNullifierInsertion */ isProtocolCanonicalAddress); } /** 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 95cfa07c4e03..99b98bfeb36e 100644 --- a/yarn-project/simulator/src/public/fuzzing/avm_simulator_bin.ts +++ b/yarn-project/simulator/src/public/fuzzing/avm_simulator_bin.ts @@ -6,7 +6,7 @@ import { deserializeFromMessagePack, serializeWithMessagePack, } from '@aztec/stdlib/avm'; -import { GlobalVariables, TreeSnapshots } from '@aztec/stdlib/tx'; +import { GlobalVariables, ProtocolContracts, TreeSnapshots } from '@aztec/stdlib/tx'; import { NativeWorldStateService } from '@aztec/world-state'; import { createInterface } from 'readline'; @@ -56,15 +56,13 @@ async function simulateWithFuzzer( 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 + protocolContracts: ProtocolContracts, // Protocol contracts mapping from C++ ): Promise<{ reverted: boolean; output: Fr[]; revertReason?: string; publicInputs: AvmCircuitPublicInputs }> { const worldStateService = await openExistingWorldState(dataDir, mapSizeKb); - const simulator = await AvmFuzzerSimulator.create(worldStateService, globals); + const simulator = await AvmFuzzerSimulator.create(worldStateService, globals, protocolContracts); - // Apply public data writes before simulation (e.g., for bytecode upgrades) - await simulator.applyPublicDataWrites(rawPublicDataWrites); - - // Register contract classes from C++ + // Register contract classes from C++ (must happen before public data writes to match C++ order) for (const rawClass of rawContractClasses) { await simulator.addContractClassFromCpp(rawClass); } @@ -74,6 +72,10 @@ async function simulateWithFuzzer( await simulator.addContractInstanceFromCpp(rawAddress, rawInstance); } + // Apply public data writes after contract registration (e.g., for bytecode upgrades) + // This must happen last to match C++ setup_fuzzer_state ordering + await simulator.applyPublicDataWrites(rawPublicDataWrites); + const result = await simulator.simulate(txHint); const output = result @@ -104,6 +106,7 @@ async function execute(base64Line: string): Promise { request.contractClasses, request.contractInstances, request.publicDataWrites, + request.protocolContracts, ); // Serialize the result to msgpack and encode it in base64 for output diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts index d34d52229b93..0dd48ccaf342 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts @@ -10,6 +10,7 @@ import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; import { type GlobalVariables, NestedProcessReturnValues, + ProtocolContracts, PublicCallRequestWithCalldata, Tx, TxExecutionPhase, @@ -85,6 +86,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { protected contractsDB: PublicContractsDB, protected globalVariables: GlobalVariables, config?: Partial, + protected protocolContracts: ProtocolContracts = ProtocolContractsList, ) { this.config = PublicSimulatorConfig.from(config ?? {}); this.log = createLogger(`simulator:public_tx_simulator`); @@ -103,7 +105,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { const hints = new AvmExecutionHints( this.globalVariables, AvmTxHint.fromTx(tx, this.globalVariables.gasFees), - ProtocolContractsList, // imported from file + this.protocolContracts, ); const hintingMerkleTree = await HintingMerkleWriteOperations.create(this.merkleTree, hints); const hintingTreesDB = new PublicTreesDB(hintingMerkleTree); @@ -114,7 +116,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { hintingContractsDB, tx, this.globalVariables, - ProtocolContractsList, // imported from file + this.protocolContracts, this.config.proverId, ); diff --git a/yarn-project/simulator/src/public/state_manager/state_manager.ts b/yarn-project/simulator/src/public/state_manager/state_manager.ts index 7fa3cc6d855f..198aa9de36b3 100644 --- a/yarn-project/simulator/src/public/state_manager/state_manager.ts +++ b/yarn-project/simulator/src/public/state_manager/state_manager.ts @@ -3,6 +3,7 @@ import { CONTRACT_CLASS_REGISTRY_CONTRACT_ADDRESS, CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, FEE_JUICE_ADDRESS, + MAX_PROTOCOL_CONTRACTS, MULTI_CALL_ENTRYPOINT_ADDRESS, ROUTER_ADDRESS, } from '@aztec/constants'; @@ -549,12 +550,5 @@ export class PublicPersistableStateManager { } function contractAddressIsCanonical(contractAddress: AztecAddress): boolean { - return ( - contractAddress.equals(AztecAddress.fromNumber(CANONICAL_AUTH_REGISTRY_ADDRESS)) || - contractAddress.equals(AztecAddress.fromNumber(CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS)) || - contractAddress.equals(AztecAddress.fromNumber(CONTRACT_CLASS_REGISTRY_CONTRACT_ADDRESS)) || - contractAddress.equals(AztecAddress.fromNumber(MULTI_CALL_ENTRYPOINT_ADDRESS)) || - contractAddress.equals(AztecAddress.fromNumber(FEE_JUICE_ADDRESS)) || - contractAddress.equals(AztecAddress.fromNumber(ROUTER_ADDRESS)) - ); + return contractAddress.toBigInt() >= 1 && contractAddress.toBigInt() <= MAX_PROTOCOL_CONTRACTS; }