diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/avm_fuzzer/CMakeLists.txt index c3701c0bb0f8..0f0c5502ea32 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/CMakeLists.txt @@ -34,4 +34,8 @@ if(FUZZING_AVM) # Add the harness subdirectory which will create the alu_fuzzer target add_subdirectory(harness) + + # Corpus analyzer tool (not a fuzzer, a standalone binary) + add_executable(avm_tx_corpus_analyzer avm_tx_corpus_analyzer.cpp) + target_link_libraries(avm_tx_corpus_analyzer avm_fuzzer) endif() diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/avm_tx_corpus_analyzer.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/avm_tx_corpus_analyzer.cpp new file mode 100644 index 000000000000..01fc6fb97b98 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/avm_tx_corpus_analyzer.cpp @@ -0,0 +1,349 @@ +/** + * @file avm_tx_corpus_analyzer.cpp + * @brief Analyzes the AVM fuzzer corpus to produce statistics on opcodes and enqueued calls. + * + * Usage: ./avm_tx_corpus_analyzer [corpus_path] + * corpus_path: Path to the corpus directory (default: corpus/tx relative to current dir) + */ + +#include "barretenberg/avm_fuzzer/fuzz_lib/control_flow.hpp" +#include "barretenberg/avm_fuzzer/fuzzer_lib.hpp" +#include "barretenberg/serialize/msgpack_impl.hpp" +#include "barretenberg/vm2/common/opcodes.hpp" +#include "barretenberg/vm2/simulation/lib/serialization.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +using namespace bb::avm2; +using namespace bb::avm2::fuzzer; + +// Statistics structure for a distribution +struct Stats { + double mean = 0.0; + double median = 0.0; + size_t mode = 0; + std::map histogram; // value -> count +}; + +// Compute mean, median, mode and histogram from a vector of values +Stats compute_stats(const std::vector& values) +{ + Stats stats; + + if (values.empty()) { + return stats; + } + + // Histogram + for (size_t v : values) { + stats.histogram[v]++; + } + + // Mean + double sum = std::accumulate(values.begin(), values.end(), 0.0); + stats.mean = sum / static_cast(values.size()); + + // Median + std::vector sorted = values; + std::sort(sorted.begin(), sorted.end()); + size_t n = sorted.size(); + if (n % 2 == 0) { + stats.median = (static_cast(sorted[n / 2 - 1]) + static_cast(sorted[n / 2])) / 2.0; + } else { + stats.median = static_cast(sorted[n / 2]); + } + + // Mode (value with highest count) + size_t max_count = 0; + for (const auto& [value, count] : stats.histogram) { + if (count > max_count) { + max_count = count; + stats.mode = value; + } + } + + return stats; +} + +// Count opcodes in bytecode +void count_opcodes(const std::vector& bytecode, std::map& opcode_counts) +{ + size_t pos = 0; + while (pos < bytecode.size()) { + try { + auto instruction = simulation::deserialize_instruction(bytecode, pos); + opcode_counts[instruction.opcode]++; + pos += instruction.size_in_bytes(); + } catch (const std::exception&) { + // Invalid bytecode, stop parsing + break; + } + } +} + +// Get opcode name as string +std::string opcode_name(WireOpCode opcode) +{ + std::ostringstream oss; + oss << opcode; + return oss.str(); +} + +// Print a visual histogram bar +std::string histogram_bar(size_t count, size_t max_count, size_t max_width = 40) +{ + if (max_count == 0) { + return ""; + } + size_t bar_len = static_cast( + std::round(static_cast(count) / static_cast(max_count) * static_cast(max_width))); + return std::string(bar_len, '#'); +} + +// Print opcode histogram +void print_opcode_histogram(const std::map& opcode_counts) +{ + std::cout << "\n=== Opcode Histogram ===\n"; + + if (opcode_counts.empty()) { + std::cout << "No opcodes found.\n"; + return; + } + + // Find max count for scaling bars + size_t max_count = 0; + size_t total_instructions = 0; + for (const auto& [opcode, count] : opcode_counts) { + max_count = std::max(max_count, count); + total_instructions += count; + } + + // Find max opcode name length for alignment + size_t max_name_len = 0; + for (const auto& [opcode, count] : opcode_counts) { + max_name_len = std::max(max_name_len, opcode_name(opcode).length()); + } + + // Sort by count (descending) + std::vector> sorted_counts(opcode_counts.begin(), opcode_counts.end()); + std::sort( + sorted_counts.begin(), sorted_counts.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); + + for (const auto& [opcode, count] : sorted_counts) { + std::cout << std::setw(static_cast(max_name_len)) << std::left << opcode_name(opcode) << ": " + << std::setw(8) << std::right << count << " " << histogram_bar(count, max_count) << "\n"; + } + + // Summary stats + std::cout << "\n=== Opcode Statistics ===\n"; + std::cout << "Total instructions: " << total_instructions << "\n"; + + size_t total_opcodes = static_cast(WireOpCode::LAST_OPCODE_SENTINEL); + std::cout << "Unique opcodes used: " << opcode_counts.size() << "/" << total_opcodes << "\n"; + + // Find and display missing opcodes + std::vector missing_opcodes; + for (size_t i = 0; i < total_opcodes; i++) { + auto opcode = static_cast(i); + if (opcode_counts.find(opcode) == opcode_counts.end()) { + missing_opcodes.push_back(opcode); + } + } + + if (!missing_opcodes.empty()) { + std::cout << "Missing opcodes (" << missing_opcodes.size() << "): "; + for (size_t i = 0; i < missing_opcodes.size(); i++) { + if (i > 0) { + std::cout << ", "; + } + std::cout << opcode_name(missing_opcodes[i]); + } + std::cout << "\n"; + } + + if (!sorted_counts.empty()) { + std::cout << "Most common: " << opcode_name(sorted_counts.front().first) << " (" << sorted_counts.front().second + << ")\n"; + std::cout << "Least common: " << opcode_name(sorted_counts.back().first) << " (" << sorted_counts.back().second + << ")\n"; + } +} + +// Structure to track multi-phase transaction statistics +struct MultiPhaseStats { + size_t txs_with_setup_and_app_logic = 0; + size_t txs_with_setup_and_teardown = 0; + size_t txs_with_app_logic_and_teardown = 0; + size_t txs_with_all_three_phases = 0; + size_t txs_with_multiple_phases = 0; // Any combination of 2+ phases +}; + +// Print enqueued calls statistics +void print_enqueued_calls_stats(const Stats& setup, + const Stats& app_logic, + const Stats& teardown, + const MultiPhaseStats& multi_phase) +{ + std::cout << "\n=== Enqueued Calls Statistics ===\n"; + + auto print_stats = [](const std::string& name, const Stats& s) { + std::cout << "\n" << name << ":\n"; + std::cout << " Mean: " << std::fixed << std::setprecision(2) << s.mean << ", Median: " << s.median + << ", Mode: " << s.mode << "\n"; + std::cout << " Histogram: "; + for (const auto& [value, count] : s.histogram) { + std::cout << value << "(" << count << ") "; + } + std::cout << "\n"; + }; + + print_stats("Setup Calls", setup); + print_stats("App Logic Calls", app_logic); + print_stats("Teardown Calls", teardown); + + std::cout << "\nMulti-Phase Transactions:\n"; + std::cout << " Txs with calls in multiple phases: " << multi_phase.txs_with_multiple_phases << "\n"; + std::cout << " Txs with setup + app_logic only: " << multi_phase.txs_with_setup_and_app_logic << "\n"; + std::cout << " Txs with setup + teardown only: " << multi_phase.txs_with_setup_and_teardown << "\n"; + std::cout << " Txs with app_logic + teardown only: " << multi_phase.txs_with_app_logic_and_teardown << "\n"; + std::cout << " Txs with all three phases: " << multi_phase.txs_with_all_three_phases << "\n"; +} + +int main(int argc, char** argv) +{ + // Default corpus path (relative to where we run from) + std::string corpus_dir = "corpus/tx"; + if (argc > 1) { + corpus_dir = argv[1]; + } + + // Check if corpus directory exists + if (!fs::exists(corpus_dir)) { + std::cerr << "Error: Corpus directory does not exist: " << corpus_dir << "\n"; + return 1; + } + + if (!fs::is_directory(corpus_dir)) { + std::cerr << "Error: Not a directory: " << corpus_dir << "\n"; + return 1; + } + + std::cout << "=== AVM Fuzzer Corpus Analysis ===\n"; + std::cout << "Corpus directory: " << corpus_dir << "\n"; + + // Statistics accumulators + std::map total_opcode_counts; + std::vector setup_call_counts; + std::vector app_logic_call_counts; + std::vector teardown_call_counts; + MultiPhaseStats multi_phase_stats; + size_t files_processed = 0; + size_t files_failed = 0; + size_t total_input_programs = 0; + + // Iterate over all files in the corpus directory + for (const auto& entry : fs::directory_iterator(corpus_dir)) { + if (!entry.is_regular_file()) { + continue; + } + + const auto& path = entry.path(); + + // Read file contents + std::ifstream file(path, std::ios::binary); + if (!file) { + files_failed++; + continue; + } + + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + // Deserialize FuzzerTxData + FuzzerTxData tx_data; + try { + msgpack::unpack(reinterpret_cast(buffer.data()), buffer.size()).get().convert(tx_data); + } catch (const std::exception& e) { + files_failed++; + continue; + } + + files_processed++; + + // Count enqueued calls + size_t setup_count = tx_data.tx.setup_enqueued_calls.size(); + size_t app_logic_count = tx_data.tx.app_logic_enqueued_calls.size(); + size_t teardown_count = tx_data.tx.teardown_enqueued_call.has_value() ? 1 : 0; + + setup_call_counts.push_back(setup_count); + app_logic_call_counts.push_back(app_logic_count); + teardown_call_counts.push_back(teardown_count); + + // Track multi-phase statistics + bool has_setup = setup_count > 0; + bool has_app_logic = app_logic_count > 0; + bool has_teardown = teardown_count > 0; + int phases_with_calls = (has_setup ? 1 : 0) + (has_app_logic ? 1 : 0) + (has_teardown ? 1 : 0); + + if (phases_with_calls >= 2) { + multi_phase_stats.txs_with_multiple_phases++; + } + if (has_setup && has_app_logic && !has_teardown) { + multi_phase_stats.txs_with_setup_and_app_logic++; + } + if (has_setup && has_teardown && !has_app_logic) { + multi_phase_stats.txs_with_setup_and_teardown++; + } + if (has_app_logic && has_teardown && !has_setup) { + multi_phase_stats.txs_with_app_logic_and_teardown++; + } + if (has_setup && has_app_logic && has_teardown) { + multi_phase_stats.txs_with_all_three_phases++; + } + + // Process each input program and build bytecode + for (auto& fuzzer_data : tx_data.input_programs) { + total_input_programs++; + + try { + // Build bytecode using ControlFlow + ControlFlow control_flow(fuzzer_data.instruction_blocks); + for (const auto& cfg_instruction : fuzzer_data.cfg_instructions) { + control_flow.process_cfg_instruction(cfg_instruction); + } + auto bytecode = control_flow.build_bytecode(fuzzer_data.return_options); + + // Count opcodes in the bytecode + count_opcodes(bytecode, total_opcode_counts); + } catch (const std::exception&) { + // Skip invalid bytecode generation + continue; + } + } + } + + // Print summary + std::cout << "\nFiles processed: " << files_processed << "\n"; + std::cout << "Files failed: " << files_failed << "\n"; + std::cout << "Total input programs: " << total_input_programs << "\n"; + + // Print opcode histogram + print_opcode_histogram(total_opcode_counts); + + // Print enqueued calls statistics + Stats setup_stats = compute_stats(setup_call_counts); + Stats app_logic_stats = compute_stats(app_logic_call_counts); + Stats teardown_stats = compute_stats(teardown_call_counts); + print_enqueued_calls_stats(setup_stats, app_logic_stats, teardown_stats, multi_phase_stats); + + return 0; +} 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 38b098d71fe8..a5da2e0c4d2d 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp @@ -44,16 +44,26 @@ 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=*/{}, /*note_hashes=*/{}); + cpp_result = cpp_simulator.simulate(*ws_mgr, + contract_db, + tx, + globals, + /*public_data_writes=*/{}, + /*note_hashes=*/{}, + /*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=*/{}, /*note_hashes=*/{}); + auto js_result = js_simulator->simulate(*ws_mgr, + contract_db, + tx, + globals, + /*public_data_writes=*/{}, + /*note_hashes=*/{}, + /*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 cab37e206251..cde4f5bcdc8d 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 @@ -65,7 +65,13 @@ 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=*/{}, + /*note_hashes=*/{}, + /*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 010aa2cc35d6..90af762161f7 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp @@ -39,7 +39,8 @@ std::string serialize_simulation_request( const GlobalVariables& globals, const FuzzerContractDB& contract_db, const std::vector& public_data_writes, - const std::vector& note_hashes) + const std::vector& note_hashes, + const ProtocolContracts& protocol_contracts) { // Build vectors from contract_db std::vector classes_vec = contract_db.get_contract_classes(); @@ -54,6 +55,7 @@ std::string serialize_simulation_request( .contract_instances = std::move(instances_vec), .public_data_writes = public_data_writes, .note_hashes = note_hashes, + .protocol_contracts = protocol_contracts, }; auto [buffer, size] = msgpack_encode_buffer(request); @@ -83,7 +85,8 @@ SimulatorResult CppSimulator::simulate( const Tx& tx, const GlobalVariables& globals, [[maybe_unused]] const std::vector& public_data_writes, - [[maybe_unused]] const std::vector& note_hashes) + [[maybe_unused]] const std::vector& note_hashes, + const ProtocolContracts& protocol_contracts) { // Note: public_data_writes and note_hashes are already applied to C++ world state in setup_fuzzer_state @@ -96,8 +99,6 @@ SimulatorResult CppSimulator::simulate( }, }; - ProtocolContracts protocol_contracts{}; - WorldState& ws = ws_mgr.get_world_state(); WorldStateRevision ws_rev = ws_mgr.get_current_revision(); @@ -157,9 +158,11 @@ SimulatorResult JsSimulator::simulate( const Tx& tx, const GlobalVariables& globals, const std::vector& public_data_writes, - const std::vector& note_hashes) + const std::vector& note_hashes, + const ProtocolContracts& protocol_contracts) { - std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes, note_hashes); + std::string serialized = + serialize_simulation_request(tx, globals, contract_db, public_data_writes, note_hashes, 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 d9cd3c3bfd2e..78557b9582fc 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp @@ -29,6 +29,8 @@ struct FuzzerSimulationRequest { std::vector public_data_writes; // Note hashes to be applied before simulation std::vector note_hashes; + // Protocol contracts mapping (canonical address index -> derived address) + ProtocolContracts protocol_contracts; MSGPACK_CAMEL_CASE_FIELDS(ws_data_dir, ws_map_size_kb, @@ -37,7 +39,8 @@ struct FuzzerSimulationRequest { contract_classes, contract_instances, public_data_writes, - note_hashes); + note_hashes, + protocol_contracts); }; struct SimulatorResult { @@ -63,7 +66,8 @@ class Simulator { const Tx& tx, const GlobalVariables& globals, const std::vector& public_data_writes, - const std::vector& note_hashes) = 0; + const std::vector& note_hashes, + const ProtocolContracts& protocol_contracts) = 0; }; /// @brief uses barretenberg/vm2 to simulate the bytecode @@ -74,7 +78,8 @@ class CppSimulator : public Simulator { const Tx& tx, const GlobalVariables& globals, const std::vector& public_data_writes, - const std::vector& note_hashes) override; + const std::vector& note_hashes, + const ProtocolContracts& protocol_contracts) override; }; /// @brief uses the yarn-project/simulator to simulate the bytecode @@ -101,7 +106,8 @@ class JsSimulator : public Simulator { const Tx& tx, const GlobalVariables& globals, const std::vector& public_data_writes, - const std::vector& note_hashes) override; + const std::vector& note_hashes, + 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 12d2d292d25e..0629a997fd7c 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp @@ -15,6 +15,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" @@ -49,6 +50,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); @@ -85,8 +102,13 @@ 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, tx_data.note_hashes); + cpp_result = cpp_simulator.simulate(ws_mgr, + contract_db, + tx_data.tx, + tx_data.global_variables, + tx_data.public_data_writes, + tx_data.note_hashes, + tx_data.protocol_contracts); fuzz_info("CppSimulator completed without exception"); fuzz_info("CppSimulator result: ", cpp_result); ws_mgr.revert(); @@ -102,8 +124,13 @@ 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, tx_data.note_hashes); + auto js_result = js_simulator->simulate(ws_mgr, + contract_db, + tx_data.tx, + tx_data.global_variables, + tx_data.public_data_writes, + tx_data.note_hashes, + tx_data.protocol_contracts); // If the results do not match if (!compare_simulator_results(cpp_result, js_result)) { @@ -126,7 +153,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; @@ -369,14 +396,21 @@ 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; + } + + // Clear any protocol contract derived addresses that reference addresses no longer in the contract set. + // This can happen when mutations (e.g., ContractClassMutation, ContractInstanceMutation) change contract addresses. + // Must run AFTER all mutations since some mutations modify contract_addresses. + std::unordered_set valid_addresses(tx_data.contract_addresses.begin(), + tx_data.contract_addresses.end()); + for (auto& derived_address : tx_data.protocol_contracts.derived_addresses) { + if (!derived_address.is_zero() && !valid_addresses.contains(derived_address)) { + derived_address = AztecAddress(0); + } } // todo: do we need to ensure this or are should we able to process 0 enqueued calls? @@ -393,6 +427,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 f78e40e05bb7..93d33213fb2d 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp @@ -66,10 +66,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 }, @@ -77,6 +77,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/barretenberg/cpp/src/barretenberg/avm_fuzzer/prover.fuzzer.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/prover.fuzzer.cpp index 2dbab4d1ecfa..1ad7262a0909 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/prover.fuzzer.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/prover.fuzzer.cpp @@ -11,6 +11,12 @@ using namespace bb::avm2::fuzzer; +// Extra counters to guide libfuzzer towards inputs with more enqueued calls. +// Index 0 = 1 call, index 1 = 2 calls, etc. When an input has N enqueued calls, +// we increment counter[N-1], signaling new coverage to libfuzzer. +constexpr size_t MAX_ENQUEUED_CALLS_COUNTER = 32; +__attribute__((section("__libfuzzer_extra_counters"))) uint8_t enqueued_calls_counter[MAX_ENQUEUED_CALLS_COUNTER]; + extern "C" int LLVMFuzzerInitialize(int*, char***) { FuzzerWorldStateManager::initialize(); @@ -45,6 +51,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) tx_data = create_default_tx_data(context); } + // Signal coverage for number of enqueued calls to guide fuzzer towards more calls + size_t num_calls = tx_data.tx.setup_enqueued_calls.size() + tx_data.tx.app_logic_enqueued_calls.size(); + if (num_calls > 0 && num_calls <= MAX_ENQUEUED_CALLS_COUNTER) { + enqueued_calls_counter[num_calls - 1]++; + } + // Setup contracts and fund fee payer // Fuzzer state is dependent on the tx data setup_fuzzer_state(*ws_mgr, contract_db, tx_data); diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/run_fuzzer.sh b/barretenberg/cpp/src/barretenberg/avm_fuzzer/run_fuzzer.sh index 4513c2dc7030..3576f6bebf0b 100755 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/run_fuzzer.sh +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/run_fuzzer.sh @@ -11,6 +11,7 @@ show_usage() { echo " build - Build the fuzzer binary" echo " fuzz [--log] [-- args...] - Run the fuzzer (--log to tail fuzz-0.log)" echo " coverage [type] - Generate coverage report (type: html or report, default: html)" + echo " analyze - Analyze corpus and show opcode/call statistics" echo " list-targets - List all available fuzzing targets" echo "" echo "Additional fuzzer arguments can be passed after '--'. For example:" @@ -44,6 +45,33 @@ if [ "$COMMAND" = "list-targets" ]; then exit 0 fi +# Handle analyze command +if [ "$COMMAND" = "analyze" ]; then + # Get the script directory and project root + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + BARRETENBERG_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)" + CPP_DIR="$BARRETENBERG_ROOT/cpp" + BUILD_DIR="$CPP_DIR/build-fuzzing-avm" + BUILD_PRESET="fuzzing-avm" + + cd "$CPP_DIR" + + # Configure if needed + if [ ! -f "$BUILD_DIR/CMakeCache.txt" ]; then + echo "Configuring cmake..." + cmake --preset "$BUILD_PRESET" + fi + + # Build the analyzer + echo "Building avm_tx_corpus_analyzer..." + cmake --build "$BUILD_DIR" --target avm_tx_corpus_analyzer + + echo "" + # Run analyzer on tx corpus + "$BUILD_DIR/bin/avm_tx_corpus_analyzer" "$SCRIPT_DIR/corpus/tx" + exit 0 +fi + FUZZER_ALIAS=$1 shift diff --git a/barretenberg/cpp/src/barretenberg/avm_fuzzer/tx.fuzzer.cpp b/barretenberg/cpp/src/barretenberg/avm_fuzzer/tx.fuzzer.cpp index 5343a0b5aea5..3b2f06e664be 100644 --- a/barretenberg/cpp/src/barretenberg/avm_fuzzer/tx.fuzzer.cpp +++ b/barretenberg/cpp/src/barretenberg/avm_fuzzer/tx.fuzzer.cpp @@ -10,6 +10,12 @@ using namespace bb::avm2::fuzzer; using namespace bb::avm2::simulation; +// Extra counters to guide libfuzzer towards inputs with more enqueued calls. +// Index 0 = 1 call, index 1 = 2 calls, etc. When an input has N enqueued calls, +// we increment counter[N-1], signaling new coverage to libfuzzer. +constexpr size_t MAX_ENQUEUED_CALLS_COUNTER = 32; +__attribute__((section("__libfuzzer_extra_counters"))) uint8_t enqueued_calls_counter[MAX_ENQUEUED_CALLS_COUNTER]; + extern "C" int LLVMFuzzerInitialize(int*, char***) { const char* simulator_path = std::getenv("AVM_SIMULATOR_BIN"); @@ -49,6 +55,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) tx_data = create_default_tx_data(context); } + // Signal coverage for number of enqueued calls to guide fuzzer towards more calls + size_t num_calls = tx_data.tx.setup_enqueued_calls.size() + tx_data.tx.app_logic_enqueued_calls.size(); + if (num_calls > 0 && num_calls <= MAX_ENQUEUED_CALLS_COUNTER) { + enqueued_calls_counter[num_calls - 1]++; + } + // Setup contracts and fund fee payer setup_fuzzer_state(*ws_mgr, contract_db, tx_data); fund_fee_payer(*ws_mgr, tx_data.tx); 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 bac801cd87ad..a0eb39022a95 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'; @@ -42,6 +52,7 @@ export class FuzzerSimulationRequest { public readonly contractInstances: [any, any][], // Raw pairs [address, instance] public readonly publicDataWrites: any[], // Raw public data tree writes to apply before simulation public readonly noteHashes: any[], // Raw note hashes to apply before simulation + public readonly protocolContracts: ProtocolContracts, // Protocol contracts mapping from C++ ) {} static fromPlainObject(obj: any): FuzzerSimulationRequest { @@ -57,6 +68,7 @@ export class FuzzerSimulationRequest { obj.contractInstances, obj.publicDataWrites ?? [], obj.noteHashes ?? [], + ProtocolContracts.fromPlainObject(obj.protocolContracts), ); } } @@ -185,16 +197,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, + ); } /** @@ -203,10 +222,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); } /** @@ -234,12 +254,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 423aeb9e95e0..8575ff0e261f 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'; @@ -57,17 +57,15 @@ async function simulateWithFuzzer( rawContractInstances: [any, any][], // Replace these when we are moving contract instances to TS rawPublicDataWrites: any[], // Public data tree writes to apply before simulation rawNoteHashes: any[], // Note hashes 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); - - // Apply public data writes before simulation (e.g., for bytecode upgrades) - await simulator.applyPublicDataWrites(rawPublicDataWrites); + const simulator = await AvmFuzzerSimulator.create(worldStateService, globals, protocolContracts); await simulator.applyNoteHashes(rawNoteHashes); - // 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); } @@ -77,6 +75,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 @@ -108,6 +110,7 @@ async function execute(base64Line: string): Promise { request.contractInstances, request.publicDataWrites, request.noteHashes, + 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..b798c8b675a2 100644 --- a/yarn-project/simulator/src/public/state_manager/state_manager.ts +++ b/yarn-project/simulator/src/public/state_manager/state_manager.ts @@ -1,11 +1,4 @@ -import { - CANONICAL_AUTH_REGISTRY_ADDRESS, - CONTRACT_CLASS_REGISTRY_CONTRACT_ADDRESS, - CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, - FEE_JUICE_ADDRESS, - MULTI_CALL_ENTRYPOINT_ADDRESS, - ROUTER_ADDRESS, -} from '@aztec/constants'; +import { CONTRACT_INSTANCE_REGISTRY_CONTRACT_ADDRESS, MAX_PROTOCOL_CONTRACTS } from '@aztec/constants'; import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; import { Fr } from '@aztec/foundation/curves/bn254'; import { jsonStringify } from '@aztec/foundation/json-rpc'; @@ -549,12 +542,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; }