Skip to content

Commit e80488d

Browse files
authored
feat(avm): protocol contractg mutations (#19586)
Protocol contract mutations turned out to be much more complex. Might've been easier to implement a hardcoded set. We need to ensure we re-validate the enqueued calls whenever we mutate the protocol contracts since we could have invalidated some addresses. Note: The TS simulation required a change to match the cpp simulator
1 parent e800870 commit e80488d

File tree

13 files changed

+254
-67
lines changed

13 files changed

+254
-67
lines changed

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

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

4545
try {
4646
ws_mgr->checkpoint();
47-
cpp_result =
48-
cpp_simulator.simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*note_hashes=*/{});
47+
cpp_result = cpp_simulator.simulate(*ws_mgr,
48+
contract_db,
49+
tx,
50+
globals,
51+
/*public_data_writes=*/{},
52+
/*note_hashes=*/{},
53+
/*protocol_contracts=*/{});
4954
ws_mgr->revert();
5055
} catch (const std::exception& e) {
5156
throw std::runtime_error(std::string("CppSimulator threw an exception: ") + e.what());
5257
}
5358

5459
ws_mgr->checkpoint();
55-
auto js_result =
56-
js_simulator->simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, /*note_hashes=*/{});
60+
auto js_result = js_simulator->simulate(*ws_mgr,
61+
contract_db,
62+
tx,
63+
globals,
64+
/*public_data_writes=*/{},
65+
/*note_hashes=*/{},
66+
/*protocol_contracts=*/{});
5767

5868
context.reset();
5969

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ class FuzzTest : public ::testing::Test {
6565
auto cpp_simulator = CppSimulator();
6666
auto globals = create_default_globals();
6767

68-
auto result = cpp_simulator.simulate(*ws_mgr, contract_db, tx, globals, /*public_data_writes=*/{}, {});
68+
auto result = cpp_simulator.simulate(*ws_mgr,
69+
contract_db,
70+
tx,
71+
globals,
72+
/*public_data_writes=*/{},
73+
/*note_hashes=*/{},
74+
/*protocol_contracts=*/{});
6975

7076
ws_mgr->revert();
7177

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ std::string serialize_simulation_request(
3939
const GlobalVariables& globals,
4040
const FuzzerContractDB& contract_db,
4141
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
42-
const std::vector<FF>& note_hashes)
42+
const std::vector<FF>& note_hashes,
43+
const ProtocolContracts& protocol_contracts)
4344
{
4445
// Build vectors from contract_db
4546
std::vector<ContractClass> classes_vec = contract_db.get_contract_classes();
@@ -54,6 +55,7 @@ std::string serialize_simulation_request(
5455
.contract_instances = std::move(instances_vec),
5556
.public_data_writes = public_data_writes,
5657
.note_hashes = note_hashes,
58+
.protocol_contracts = protocol_contracts,
5759
};
5860

5961
auto [buffer, size] = msgpack_encode_buffer(request);
@@ -83,7 +85,8 @@ SimulatorResult CppSimulator::simulate(
8385
const Tx& tx,
8486
const GlobalVariables& globals,
8587
[[maybe_unused]] const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
86-
[[maybe_unused]] const std::vector<FF>& note_hashes)
88+
[[maybe_unused]] const std::vector<FF>& note_hashes,
89+
const ProtocolContracts& protocol_contracts)
8790
{
8891
// Note: public_data_writes and note_hashes are already applied to C++ world state in setup_fuzzer_state
8992

@@ -96,8 +99,6 @@ SimulatorResult CppSimulator::simulate(
9699
},
97100
};
98101

99-
ProtocolContracts protocol_contracts{};
100-
101102
WorldState& ws = ws_mgr.get_world_state();
102103
WorldStateRevision ws_rev = ws_mgr.get_current_revision();
103104

@@ -157,9 +158,11 @@ SimulatorResult JsSimulator::simulate(
157158
const Tx& tx,
158159
const GlobalVariables& globals,
159160
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
160-
const std::vector<FF>& note_hashes)
161+
const std::vector<FF>& note_hashes,
162+
const ProtocolContracts& protocol_contracts)
161163
{
162-
std::string serialized = serialize_simulation_request(tx, globals, contract_db, public_data_writes, note_hashes);
164+
std::string serialized =
165+
serialize_simulation_request(tx, globals, contract_db, public_data_writes, note_hashes, protocol_contracts);
163166

164167
// Send the request
165168
process.write_line(serialized);

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ struct FuzzerSimulationRequest {
2929
std::vector<bb::crypto::merkle_tree::PublicDataLeafValue> public_data_writes;
3030
// Note hashes to be applied before simulation
3131
std::vector<FF> note_hashes;
32+
// Protocol contracts mapping (canonical address index -> derived address)
33+
ProtocolContracts protocol_contracts;
3234

3335
MSGPACK_CAMEL_CASE_FIELDS(ws_data_dir,
3436
ws_map_size_kb,
@@ -37,7 +39,8 @@ struct FuzzerSimulationRequest {
3739
contract_classes,
3840
contract_instances,
3941
public_data_writes,
40-
note_hashes);
42+
note_hashes,
43+
protocol_contracts);
4144
};
4245

4346
struct SimulatorResult {
@@ -63,7 +66,8 @@ class Simulator {
6366
const Tx& tx,
6467
const GlobalVariables& globals,
6568
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
66-
const std::vector<FF>& note_hashes) = 0;
69+
const std::vector<FF>& note_hashes,
70+
const ProtocolContracts& protocol_contracts) = 0;
6771
};
6872

6973
/// @brief uses barretenberg/vm2 to simulate the bytecode
@@ -74,7 +78,8 @@ class CppSimulator : public Simulator {
7478
const Tx& tx,
7579
const GlobalVariables& globals,
7680
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
77-
const std::vector<FF>& note_hashes) override;
81+
const std::vector<FF>& note_hashes,
82+
const ProtocolContracts& protocol_contracts) override;
7883
};
7984

8085
/// @brief uses the yarn-project/simulator to simulate the bytecode
@@ -101,7 +106,8 @@ class JsSimulator : public Simulator {
101106
const Tx& tx,
102107
const GlobalVariables& globals,
103108
const std::vector<bb::crypto::merkle_tree::PublicDataLeafValue>& public_data_writes,
104-
const std::vector<FF>& note_hashes) override;
109+
const std::vector<FF>& note_hashes,
110+
const ProtocolContracts& protocol_contracts) override;
105111
};
106112

107113
GlobalVariables create_default_globals();

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

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "barretenberg/avm_fuzzer/mutations/basic_types/uint64_t.hpp"
1616
#include "barretenberg/avm_fuzzer/mutations/configuration.hpp"
1717
#include "barretenberg/avm_fuzzer/mutations/fuzzer_data.hpp"
18+
#include "barretenberg/avm_fuzzer/mutations/protocol_contracts.hpp"
1819
#include "barretenberg/avm_fuzzer/mutations/tx_data.hpp"
1920
#include "barretenberg/avm_fuzzer/mutations/tx_types/gas.hpp"
2021
#include "barretenberg/common/log.hpp"
@@ -49,6 +50,22 @@ void setup_fuzzer_state(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
4950
contract_db.add_contract_instance(contract_address, contract_instance);
5051
}
5152

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

86103
try {
87104
ws_mgr.checkpoint();
88-
cpp_result = cpp_simulator.simulate(
89-
ws_mgr, contract_db, tx_data.tx, tx_data.global_variables, tx_data.public_data_writes, tx_data.note_hashes);
105+
cpp_result = cpp_simulator.simulate(ws_mgr,
106+
contract_db,
107+
tx_data.tx,
108+
tx_data.global_variables,
109+
tx_data.public_data_writes,
110+
tx_data.note_hashes,
111+
tx_data.protocol_contracts);
90112
fuzz_info("CppSimulator completed without exception");
91113
fuzz_info("CppSimulator result: ", cpp_result);
92114
ws_mgr.revert();
@@ -102,8 +124,13 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
102124
}
103125

104126
ws_mgr.checkpoint();
105-
auto js_result = js_simulator->simulate(
106-
ws_mgr, contract_db, tx_data.tx, tx_data.global_variables, tx_data.public_data_writes, tx_data.note_hashes);
127+
auto js_result = js_simulator->simulate(ws_mgr,
128+
contract_db,
129+
tx_data.tx,
130+
tx_data.global_variables,
131+
tx_data.public_data_writes,
132+
tx_data.note_hashes,
133+
tx_data.protocol_contracts);
107134

108135
// If the results do not match
109136
if (!compare_simulator_results(cpp_result, js_result)) {
@@ -126,7 +153,7 @@ SimulatorResult fuzz_tx(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contr
126153
/// @throws An exception if simulation results differ or check_circuit fails
127154
int fuzz_prover(FuzzerWorldStateManager& ws_mgr, FuzzerContractDB& contract_db, FuzzerTxData& tx_data)
128155
{
129-
ProtocolContracts protocol_contracts{};
156+
ProtocolContracts& protocol_contracts = tx_data.protocol_contracts;
130157
WorldState& ws = ws_mgr.get_world_state();
131158
WorldStateRevision ws_rev = ws_mgr.get_current_revision();
132159
AvmSimulationHelper helper;
@@ -369,14 +396,21 @@ size_t mutate_tx_data(FuzzerContext& context,
369396
// This is just mutating the gas values and timestamp
370397
mutate_uint64_t(tx_data.global_variables.timestamp, rng, BASIC_UINT64_T_MUTATION_CONFIGURATION);
371398
mutate_gas_fees(tx_data.global_variables.gas_fees, rng);
372-
// This must be less than or equal to the tx max fees per gas
373-
tx_data.global_variables.gas_fees.fee_per_da_gas = std::min(
374-
tx_data.global_variables.gas_fees.fee_per_da_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_da_gas);
375-
tx_data.global_variables.gas_fees.fee_per_l2_gas = std::min(
376-
tx_data.global_variables.gas_fees.fee_per_l2_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_l2_gas);
377399
break;
378-
// case TxDataMutationType::ProtocolContractsMutation:
379-
// break;
400+
case FuzzerTxDataMutationType::ProtocolContractsMutation:
401+
mutate_protocol_contracts(tx_data.protocol_contracts, tx_data.tx, tx_data.contract_addresses, rng);
402+
break;
403+
}
404+
405+
// Clear any protocol contract derived addresses that reference addresses no longer in the contract set.
406+
// This can happen when mutations (e.g., ContractClassMutation, ContractInstanceMutation) change contract addresses.
407+
// Must run AFTER all mutations since some mutations modify contract_addresses.
408+
std::unordered_set<AztecAddress> valid_addresses(tx_data.contract_addresses.begin(),
409+
tx_data.contract_addresses.end());
410+
for (auto& derived_address : tx_data.protocol_contracts.derived_addresses) {
411+
if (!derived_address.is_zero() && !valid_addresses.contains(derived_address)) {
412+
derived_address = AztecAddress(0);
413+
}
380414
}
381415

382416
// 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,
393427
.calldata = calldata });
394428
}
395429

430+
// Ensure global gas_fees <= max_fees_per_gas (required for compute_effective_gas_fees)
431+
// This must run after ANY mutation since TxMutation can reduce max_fees_per_gas
432+
tx_data.global_variables.gas_fees.fee_per_da_gas = std::min(
433+
tx_data.global_variables.gas_fees.fee_per_da_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_da_gas);
434+
tx_data.global_variables.gas_fees.fee_per_l2_gas = std::min(
435+
tx_data.global_variables.gas_fees.fee_per_l2_gas, tx_data.tx.gas_settings.max_fees_per_gas.fee_per_l2_gas);
436+
396437
// Compute effective gas fees matching TS computeEffectiveGasFees
397438
// This must be done after any mutation that could affect gas settings or global variables
398439
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
@@ -66,17 +66,18 @@ enum class FuzzerTxDataMutationType : uint8_t {
6666
ContractClassMutation,
6767
ContractInstanceMutation,
6868
GlobalVariablesMutation,
69-
// ProtocolContractsMutation
69+
ProtocolContractsMutation
7070
};
7171

72-
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 5>;
72+
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 6>;
7373

7474
constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({
7575
{ FuzzerTxDataMutationType::TxMutation, 10 },
7676
{ FuzzerTxDataMutationType::BytecodeMutation, 1 },
7777
{ FuzzerTxDataMutationType::ContractClassMutation, 1 },
7878
{ FuzzerTxDataMutationType::ContractInstanceMutation, 1 },
7979
{ FuzzerTxDataMutationType::GlobalVariablesMutation, 4 },
80+
{ FuzzerTxDataMutationType::ProtocolContractsMutation, 4 },
8081
});
8182

8283
// Build bytecode and contract artifacts from fuzzer data
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
const 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 canonical address to use the derived 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+
break;
75+
}
76+
}
77+
}
78+
79+
} // namespace bb::avm2::fuzzer
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include "barretenberg/avm_fuzzer/common/weighted_selection.hpp"
4+
#include "barretenberg/vm2/common/avm_io.hpp"
5+
#include "barretenberg/vm2/common/aztec_types.hpp"
6+
7+
#include <random>
8+
9+
namespace bb::avm2::fuzzer {
10+
11+
enum class ProtocolContractsMutationOptions : uint8_t {
12+
Mutate,
13+
Remove,
14+
};
15+
16+
using ProtocolContractsMutationConfig = WeightedSelectionConfig<ProtocolContractsMutationOptions, 2>;
17+
18+
constexpr ProtocolContractsMutationConfig PROTOCOL_CONTRACTS_MUTATION_CONFIGURATION = ProtocolContractsMutationConfig({
19+
{ ProtocolContractsMutationOptions::Mutate, 3 },
20+
{ ProtocolContractsMutationOptions::Remove, 1 },
21+
});
22+
23+
void mutate_protocol_contracts(bb::avm2::ProtocolContracts& protocol_contracts,
24+
bb::avm2::Tx& tx,
25+
const std::vector<AztecAddress>& contract_addresses,
26+
std::mt19937_64& rng);
27+
28+
} // namespace bb::avm2::fuzzer

0 commit comments

Comments
 (0)