Skip to content

Commit a1ee749

Browse files
committed
feat(avm): protocol contractg mutations
1 parent ed25916 commit a1ee749

File tree

13 files changed

+251
-69
lines changed

13 files changed

+251
-69
lines changed

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,16 @@ SimulatorResult fuzz_against_ts_simulator(FuzzerData& fuzzer_data, FuzzerContext
4444

4545
try {
4646
ws_mgr->checkpoint();
47-
cpp_result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{});
47+
cpp_result = cpp_simulator.simulate(
48+
*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*protocol_contracts=*/{});
4849
ws_mgr->revert();
4950
} catch (const std::exception& e) {
5051
throw std::runtime_error(std::string("CppSimulator threw an exception: ") + e.what());
5152
}
5253

5354
ws_mgr->checkpoint();
54-
auto js_result = js_simulator->simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{});
55+
auto js_result =
56+
js_simulator->simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*protocol_contracts=*/{});
5557

5658
context.reset();
5759

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/fuzz.test.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ class FuzzTest : public ::testing::Test {
5050
FuzzerContractDB contract_db = context.get_contract_db();
5151

5252
auto tx = create_default_tx(contract_address, MSG_SENDER, calldata, TRANSACTION_FEE, IS_STATIC_CALL, GAS_LIMIT);
53+
auto globals = create_default_globals();
5354
FF fee_required_da = FF(tx.effective_gas_fees.fee_per_da_gas) * FF(tx.gas_settings.gas_limits.da_gas);
5455
FF fee_required_l2 = FF(tx.effective_gas_fees.fee_per_l2_gas) * FF(tx.gas_settings.gas_limits.l2_gas);
5556
ws_mgr->write_fee_payer_balance(tx.fee_payer, fee_required_da + fee_required_l2);
5657
auto cpp_simulator = CppSimulator();
5758
auto globals = create_default_globals();
5859

59-
auto result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{});
60+
auto result = cpp_simulator.simulate(
61+
*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*protocol_contracts=*/{});
6062

6163
ws_mgr->revert();
6264

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ std::string serialize_simulation_request(
3838
const Tx& tx,
3939
const GlobalVariables& globals,
4040
const FuzzerContractDB& contract_db,
41-
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
41+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
42+
const ProtocolContracts& protocol_contracts)
4243
{
4344
// Build vectors from contract_db
4445
std::vector<ContractClass> classes_vec = contract_db.get_contract_classes();
@@ -52,6 +53,7 @@ std::string serialize_simulation_request(
5253
.contract_classes = std::move(classes_vec),
5354
.contract_instances = std::move(instances_vec),
5455
.public_data_writes = public_data_writes,
56+
.protocol_contracts = protocol_contracts,
5557
};
5658

5759
auto [buffer, size] = msgpack_encode_buffer(request);
@@ -80,7 +82,8 @@ SimulatorResult CppSimulator::simulate(
8082
fuzzer::FuzzerContractDB& contract_db,
8183
const Tx& tx,
8284
const GlobalVariables& globals,
83-
[[maybe_unused]] const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
85+
[[maybe_unused]] const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
86+
const ProtocolContracts& protocol_contracts)
8487
{
8588
// Note: public_data_writes are already applied to C++ world state in setup_fuzzer_state
8689

@@ -93,8 +96,6 @@ SimulatorResult CppSimulator::simulate(
9396
},
9497
};
9598

96-
ProtocolContracts protocol_contracts{};
97-
9899
WorldState& ws = ws_mgr.get_world_state();
99100
WorldStateRevision ws_rev = ws_mgr.get_current_revision();
100101

@@ -153,9 +154,11 @@ SimulatorResult JsSimulator::simulate(
153154
fuzzer::FuzzerContractDB& contract_db,
154155
const Tx& tx,
155156
const GlobalVariables& globals,
156-
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes)
157+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
158+
const ProtocolContracts& protocol_contracts)
157159
{
158-
std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes);
160+
std::string serialized =
161+
serialize_simulation_request(tx, globals, contract_db, public_data_writes, protocol_contracts);
159162

160163
// Send the request
161164
process.write_line(serialized);

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzz_lib/simulator.hpp

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@ struct FuzzerSimulationRequest {
2727
std::vector<std::pair<AztecAddress, ContractInstance>> contract_instances;
2828
// Public data tree writes to apply before simulation (e.g., for bytecode upgrades)
2929
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue> public_data_writes;
30-
31-
MSGPACK_CAMEL_CASE_FIELDS(
32-
ws_data_dir, ws_map_size_kb, tx, globals, contract_classes, contract_instances, public_data_writes);
30+
// Protocol contracts mapping (canonical address index -> derived address)
31+
ProtocolContracts protocol_contracts;
32+
33+
MSGPACK_CAMEL_CASE_FIELDS(ws_data_dir,
34+
ws_map_size_kb,
35+
tx,
36+
globals,
37+
contract_classes,
38+
contract_instances,
39+
public_data_writes,
40+
protocol_contracts);
3341
};
3442

3543
struct SimulatorResult {
@@ -54,18 +62,19 @@ class Simulator {
5462
fuzzer::FuzzerContractDB& contract_db,
5563
const Tx& tx,
5664
const GlobalVariables& globals,
57-
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) = 0;
65+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
66+
const ProtocolContracts& protocol_contracts) = 0;
5867
};
5968

6069
/// @brief uses barretenberg/vm2 to simulate the bytecode
6170
class CppSimulator : public Simulator {
6271
public:
63-
SimulatorResult simulate(
64-
fuzzer::FuzzerWorldStateManager& ws_mgr,
65-
fuzzer::FuzzerContractDB& contract_db,
66-
const Tx& tx,
67-
const GlobalVariables& globals,
68-
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) override;
72+
SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
73+
fuzzer::FuzzerContractDB& contract_db,
74+
const Tx& tx,
75+
const GlobalVariables& globals,
76+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
77+
const ProtocolContracts& protocol_contracts) override;
6978
};
7079

7180
/// @brief uses the yarn-project/simulator to simulate the bytecode
@@ -87,12 +96,12 @@ class JsSimulator : public Simulator {
8796
static JsSimulator* getInstance();
8897
static void initialize(std::string& simulator_path);
8998

90-
SimulatorResult simulate(
91-
fuzzer::FuzzerWorldStateManager& ws_mgr,
92-
fuzzer::FuzzerContractDB& contract_db,
93-
const Tx& tx,
94-
const GlobalVariables& globals,
95-
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes) override;
99+
SimulatorResult simulate(fuzzer::FuzzerWorldStateManager& ws_mgr,
100+
fuzzer::FuzzerContractDB& contract_db,
101+
const Tx& tx,
102+
const GlobalVariables& globals,
103+
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
104+
const ProtocolContracts& protocol_contracts) override;
96105
};
97106

98107
GlobalVariables create_default_globals();

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "barretenberg/avm_fuzzer/mutations/basic_types/uint64_t.hpp"
1515
#include "barretenberg/avm_fuzzer/mutations/configuration.hpp"
1616
#include "barretenberg/avm_fuzzer/mutations/fuzzer_data.hpp"
17+
#include "barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp"
1718
#include "barretenberg/avm_fuzzer/mutations/tx_data.hpp"
1819
#include "barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp"
1920
#include "barretenberg/common/log.hpp"
@@ -47,6 +48,22 @@ void setup_fuzzer_state(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
4748
contract_db.add_contract_instance(contract_address, contract_instance);
4849
}
4950

51+
// For protocol contracts, also add instances keyed by canonical address (1-11).
52+
// This is needed because protocol contracts are looked up by canonical address,
53+
// but the derived address in protocol_contracts.derived_addresses maps to the actual instance.
54+
for (size_t i = 0; i < tx_data.protocol_contracts.derived_addresses.size(); ++i) {
55+
const auto& derived_address = tx_data.protocol_contracts.derived_addresses[i];
56+
if (!derived_address.is_zero()) {
57+
// Canonical address is index + 1 (addresses 1-11 map to indices 0-10)
58+
AztecAddress canonical_address(static_cast<uint256_t>(i + 1));
59+
// Find the instance for this derived address and also add it by canonical address
60+
auto maybe_instance = contract_db.get_contract_instance(derived_address);
61+
if (maybe_instance.has_value()) {
62+
contract_db.add_contract_instance(canonical_address, maybe_instance.value());
63+
}
64+
}
65+
}
66+
5067
// Register contract addresses in the world state
5168
for (const auto& addr : tx_data.contract_addresses) {
5269
ws_mgr.register_contract_address(addr);
@@ -81,8 +98,12 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
8198

8299
try {
83100
ws_mgr.checkpoint();
84-
cpp_result = cpp_simulator.simulate(
85-
ws_mgr, contract_db, tx_data.tx, tx_data.global_variables, tx_data.public_data_writes);
101+
cpp_result = cpp_simulator.simulate(ws_mgr,
102+
contract_db,
103+
tx_data.tx,
104+
tx_data.global_variables,
105+
tx_data.public_data_writes,
106+
tx_data.protocol_contracts);
86107
fuzz_info("CppSimulator completed without exception");
87108
fuzz_info("CppSimulator result: ", cpp_result);
88109
ws_mgr.revert();
@@ -98,8 +119,12 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
98119
}
99120

100121
ws_mgr.checkpoint();
101-
auto js_result =
102-
js_simulator->simulate(ws_mgr, contract_db, tx_data.tx, tx_data.global_variables, tx_data.public_data_writes);
122+
auto js_result = js_simulator->simulate(ws_mgr,
123+
contract_db,
124+
tx_data.tx,
125+
tx_data.global_variables,
126+
tx_data.public_data_writes,
127+
tx_data.protocol_contracts);
103128

104129
// If the results do not match
105130
if (!compare_simulator_results(cpp_result, js_result)) {
@@ -122,7 +147,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
122147
/// @throws An exception if simulation results differ or check_circuit fails
123148
int fuzz_prover(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contract_db, FuzzerTxData& tx_data)
124149
{
125-
ProtocolContracts protocol_contracts{};
150+
ProtocolContracts& protocol_contracts = tx_data.protocol_contracts;
126151
WorldState& ws = ws_mgr.get_world_state();
127152
WorldStateRevision ws_rev = ws_mgr.get_current_revision();
128153
AvmSimulationHelper helper;
@@ -311,6 +336,15 @@ size_t mutate_tx_data(FuzzerContext& context,
311336

312337
tx_data.contract_addresses = contract_addresses;
313338

339+
// Clear any protocol contract derived addresses that reference addresses no longer in the contract set.
340+
// This can happen when bytecode mutation causes contract addresses to change between mutation rounds.
341+
// note: we shouldnt have to aggressively do this if we modify only 1 fuzzer data at a time.
342+
for (auto& derived_address : tx_data.protocol_contracts.derived_addresses) {
343+
if (!derived_address.is_zero() && !seen_addresses.contains(derived_address)) {
344+
derived_address = AztecAddress(0);
345+
}
346+
}
347+
314348
// Ensure all enqueued calls have valid contract addresses (not placeholders)
315349
// We may add more advanced mutation to change contract addresses later, right now we just ensure they are valid
316350
auto idx_dist = std::uniform_int_distribution<size_t>(0, contract_addresses.size() - 1);
@@ -352,14 +386,10 @@ size_t mutate_tx_data(FuzzerContext& context,
352386
// This is just mutating the gas values and timestamp
353387
mutate_uint64_t(tx_data.global_variables.timestamp, rng, BASIC_UINT64_T_MUTATION_CONFIGURATION);
354388
mutate_gas_fees(tx_data.global_variables.gas_fees, rng);
355-
// This must be less than or equal to the tx max fees per gas
356-
tx_data.global_variables.gas_fees.fee_per_da_gas = std::min(
357-
tx_data.global_variables.gas_fees.fee_per_da_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_da_gas);
358-
tx_data.global_variables.gas_fees.fee_per_l2_gas = std::min(
359-
tx_data.global_variables.gas_fees.fee_per_l2_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_l2_gas);
360389
break;
361-
// case TxDataMutationType::ProtocolContractsMutation:
362-
// break;
390+
case FuzzerTxDataMutationType::ProtocolContractsMutation:
391+
mutate_protocol_contracts(tx_data.protocol_contracts, tx_data.tx, tx_data.contract_addresses, rng);
392+
break;
363393
}
364394

365395
// 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,
376406
.calldata = calldata });
377407
}
378408

409+
// Ensure global gas_fees <= max_fees_per_gas (required for compute_effective_gas_fees)
410+
// This must run after ANY mutation since TxMutation can reduce max_fees_per_gas
411+
tx_data.global_variables.gas_fees.fee_per_da_gas = std::min(
412+
tx_data.global_variables.gas_fees.fee_per_da_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_da_gas);
413+
tx_data.global_variables.gas_fees.fee_per_l2_gas = std::min(
414+
tx_data.global_variables.gas_fees.fee_per_l2_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_l2_gas);
415+
379416
// Compute effective gas fees matching TS computeEffectiveGasFees
380417
// This must be done after any mutation that could affect gas settings or global variables
381418
tx_data.tx.effective_gas_fees =

barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,18 @@ enum class FuzzerTxDataMutationType : uint8_t {
6464
ContractClassMutation,
6565
ContractInstanceMutation,
6666
GlobalVariablesMutation,
67-
// ProtocolContractsMutation
67+
ProtocolContractsMutation
6868
};
6969

70-
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 5>;
70+
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 6>;
7171

7272
constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({
7373
{ FuzzerTxDataMutationType::TxMutation, 10 },
7474
{ FuzzerTxDataMutationType::BytecodeMutation, 1 },
7575
{ FuzzerTxDataMutationType::ContractClassMutation, 1 },
7676
{ FuzzerTxDataMutationType::ContractInstanceMutation, 1 },
7777
{ FuzzerTxDataMutationType::GlobalVariablesMutation, 4 },
78+
{ FuzzerTxDataMutationType::ProtocolContractsMutation, 4 },
7879
});
7980

8081
// Build bytecode and contract artifacts from fuzzer data
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp"
2+
3+
#include "barretenberg/vm2/common/aztec_constants.hpp"
4+
#include "barretenberg/vm2/common/aztec_types.hpp"
5+
6+
namespace bb::avm2::fuzzer {
7+
8+
namespace {
9+
10+
void update_enqueued_calls_for_protocol_contract(Tx& tx,
11+
const AztecAddress& prev_address,
12+
const AztecAddress& new_address)
13+
{
14+
// Update setup_enqueued_calls
15+
for (auto& call : tx.setup_enqueued_calls) {
16+
if (call.request.contract_address == prev_address) {
17+
call.request.contract_address = new_address;
18+
}
19+
}
20+
// Update app_logic_enqueued_calls
21+
for (auto& call : tx.app_logic_enqueued_calls) {
22+
if (call.request.contract_address == prev_address) {
23+
call.request.contract_address = new_address;
24+
}
25+
}
26+
// Update teardown_enqueued_call
27+
if (tx.teardown_enqueued_call.has_value() && tx.teardown_enqueued_call->request.contract_address == prev_address) {
28+
tx.teardown_enqueued_call->request.contract_address = new_address;
29+
}
30+
}
31+
32+
} // namespace
33+
34+
void mutate_protocol_contracts(ProtocolContracts& protocol_contracts,
35+
Tx& tx,
36+
std::vector<AztecAddress> contract_addresses,
37+
std::mt19937_64& rng)
38+
{
39+
if (contract_addresses.empty()) {
40+
return;
41+
}
42+
43+
auto choice = PROTOCOL_CONTRACTS_MUTATION_CONFIGURATION.select(rng);
44+
switch (choice) {
45+
case ProtocolContractsMutationOptions::Mutate: {
46+
// Pick a random index and add a protocol contract
47+
auto protocol_contract_index = std::uniform_int_distribution<size_t>(0, MAX_PROTOCOL_CONTRACTS - 1)(rng);
48+
auto contract_address_index = std::uniform_int_distribution<size_t>(0, contract_addresses.size() - 1)(rng);
49+
50+
AztecAddress derived_address = contract_addresses[contract_address_index];
51+
protocol_contracts.derived_addresses[protocol_contract_index] = derived_address;
52+
53+
// The index of the protocol contracts array maps to canonical address.
54+
// Add 1 because canonical addresses are 1-indexed
55+
AztecAddress canonical_address(static_cast<uint256_t>(protocol_contract_index + 1));
56+
57+
// todo(ilyas): there should be a more efficient way to do this
58+
// Update any enqueued calls that reference the derived address to now use the canonical address
59+
update_enqueued_calls_for_protocol_contract(
60+
tx, /*prev_address=*/derived_address, /*new_address=*/canonical_address);
61+
break;
62+
}
63+
case ProtocolContractsMutationOptions::Remove: {
64+
// Pick an index, and zero it out, removing the protocol contract. It may already be zero but the fuzzer should
65+
// figure out better mutations
66+
size_t idx = std::uniform_int_distribution<size_t>(0, MAX_PROTOCOL_CONTRACTS - 1)(rng);
67+
AztecAddress canonical_address(static_cast<uint256_t>(idx + 1));
68+
AztecAddress derived_address = protocol_contracts.derived_addresses[idx];
69+
// Update any enqueued calls that reference the derived address to use the canonical address
70+
update_enqueued_calls_for_protocol_contract(
71+
tx, /*prev_address=*/canonical_address, /*new_address=*/derived_address);
72+
// Set the derived address to zero
73+
protocol_contracts.derived_addresses[idx] = AztecAddress(0);
74+
}
75+
}
76+
}
77+
78+
} // namespace bb::avm2::fuzzer

0 commit comments

Comments
 (0)