Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ std::string serialize_simulation_request(
const Tx& tx,
const GlobalVariables& globals,
const FuzzerContractDB& contract_db,
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
const ProtocolContracts& protocol_contracts)
{
// Build vectors from contract_db
std::vector<ContractClass> classes_vec = contract_db.get_contract_classes();
Expand All @@ -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);
Expand Down Expand Up @@ -80,7 +82,8 @@ SimulatorResult CppSimulator::simulate(
fuzzer::FuzzerContractDB& contract_db,
const Tx& tx,
const GlobalVariables& globals,
[[maybe_unused]] const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
[[maybe_unused]] const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
const ProtocolContracts& protocol_contracts)
{
// Note: public_data_writes are already applied to C++ world state in setup_fuzzer_state

Expand All @@ -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();

Expand Down Expand Up @@ -153,9 +154,11 @@ SimulatorResult JsSimulator::simulate(
fuzzer::FuzzerContractDB& contract_db,
const Tx& tx,
const GlobalVariables& globals,
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& 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);
Expand Down
41 changes: 25 additions & 16 deletions barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ struct FuzzerSimulationRequest {
std::vector<std::pair<AztecAddress, ContractInstance>> contract_instances;
// Public data tree writes to apply before simulation (e.g., for bytecode upgrades)
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue> 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 {
Expand All @@ -54,18 +62,19 @@ class Simulator {
fuzzer::FuzzerContractDB& contract_db,
const Tx& tx,
const GlobalVariables& globals,
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) = 0;
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& 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<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) override;
SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
fuzzer::FuzzerContractDB& contract_db,
const Tx& tx,
const GlobalVariables& globals,
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
const ProtocolContracts& protocol_contracts) override;
};

/// @brief uses the yarn-project/simulator to simulate the bytecode
Expand All @@ -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<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) override;
SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
fuzzer::FuzzerContractDB& contract_db,
const Tx& tx,
const GlobalVariables& globals,
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
const ProtocolContracts& protocol_contracts) override;
};

GlobalVariables create_default_globals();
Expand Down
61 changes: 49 additions & 12 deletions barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<uint256_t>(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);
Expand Down Expand Up @@ -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();
Expand All @@ -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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -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<size_t>(0, contract_addresses.size() - 1);
Expand Down Expand Up @@ -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?
Expand All @@ -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 =
Expand Down
5 changes: 3 additions & 2 deletions barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,18 @@ enum class FuzzerTxDataMutationType : uint8_t {
ContractClassMutation,
ContractInstanceMutation,
GlobalVariablesMutation,
// ProtocolContractsMutation
ProtocolContractsMutation
};

using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 5>;
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 6>;

constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({
{ FuzzerTxDataMutationType::TxMutation, 10 },
{ FuzzerTxDataMutationType::BytecodeMutation, 1 },
{ FuzzerTxDataMutationType::ContractClassMutation, 1 },
{ FuzzerTxDataMutationType::ContractInstanceMutation, 1 },
{ FuzzerTxDataMutationType::GlobalVariablesMutation, 4 },
{ FuzzerTxDataMutationType::ProtocolContractsMutation, 4 },
});

// Build bytecode and contract artifacts from fuzzer data
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AztecAddress>& 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<size_t>(0, MAX_PROTOCOL_CONTRACTS - 1)(rng);
auto contract_address_index = std::uniform_int_distribution<size_t>(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<uint256_t>(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<size_t>(0, MAX_PROTOCOL_CONTRACTS - 1)(rng);
AztecAddress canonical_address(static_cast<uint256_t>(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
Original file line number Diff line number Diff line change
@@ -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 <random>

namespace bb::avm2::fuzzer {

enum class ProtocolContractsMutationOptions : uint8_t {
Mutate,
Remove,
};

using ProtocolContractsMutationConfig = WeightedSelectionConfig<ProtocolContractsMutationOptions, 2>;

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<AztecAddress>& contract_addresses,
std::mt19937_64& rng);

} // namespace bb::avm2::fuzzer
Loading
Loading