Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
external_call.fuzzer.cpp
Go to the documentation of this file.
5#include <array>
6#include <cstddef>
7#include <cstdint>
8#include <memory>
9#include <vector>
10
28
29using namespace bb::avm2::simulation;
30using namespace bb::avm2::tracegen;
31using namespace bb::avm2::constraining;
32using namespace bb::avm2::fuzzing;
33
34using bb::avm2::FF;
37
39
40const uint8_t max_flat_calls = 3;
41const uint8_t max_nested_calls = 2;
43// To avoid OOG error:
45
46// Constant instructions:
48 .operand<uint8_t>(0)
49 .operand<uint8_t>(0)
50 .operand<uint8_t>(0)
51 .build();
52
55 MemoryValue l2_gas = MemoryValue::from<uint32_t>(min_l2_gas);
56 MemoryValue da_gas = MemoryValue::from<uint32_t>(0);
58
60
61 void print() const
62 {
63 info("contract_address: ", contract_address);
64 info("l2_gas: ", l2_gas.to_string());
65 info("da_gas: ", da_gas.to_string());
66 info("is_static: ", is_static);
67 }
68
69 void to_buffer(uint8_t* buffer) const
70 {
71 size_t offset = 0;
73 offset += sizeof(contract_address);
75 offset += sizeof(l2_gas);
77 offset += sizeof(da_gas);
79 offset += sizeof(is_static);
80 }
81
83 {
85 size_t offset = 0;
87 offset += sizeof(input.contract_address);
88 std::memcpy(&input.l2_gas, buffer + offset, sizeof(input.l2_gas));
89 offset += sizeof(input.l2_gas);
90 std::memcpy(&input.da_gas, buffer + offset, sizeof(input.da_gas));
91 offset += sizeof(input.da_gas);
92 std::memcpy(&input.is_static, buffer + offset, sizeof(input.is_static));
93 offset += sizeof(input.is_static);
94
95 return input;
96 }
97};
98
100 uint32_t start_pc = 0;
101 // Number i where we have (CALL/STATICCALL -> (CALL/STATICCALL -> RETURN) xj -> RETURN) xi
102 uint8_t num_flat_calls = 1;
103 // Number j where we have (CALL/STATICCALL -> (CALL/STATICCALL -> RETURN) xj -> RETURN) xi
104 uint8_t num_nested_calls = 0;
105
106 std::array<ExternalCallFuzzerInstance, max_total_calls> call_instances{};
107
109
110 void print() const
111 {
112 info("start_pc: ", start_pc);
113 info("num_flat_calls: ", int(num_flat_calls));
114 info("num_nested_calls: ", int(num_nested_calls));
115 for (size_t i = 0; i < call_instances.size(); i++) {
116 info("call_instance ", i, ": ");
117 call_instances[i].print();
118 }
119 }
120
121 void to_buffer(uint8_t* buffer) const
122 {
123 size_t offset = 0;
125 offset += sizeof(start_pc);
127 offset += sizeof(num_flat_calls);
129 offset += sizeof(num_nested_calls);
130 for (const auto& call_instance : call_instances) {
131 call_instance.to_buffer(buffer + offset);
133 }
134 }
135
137 {
139 size_t offset = 0;
140 std::memcpy(&input.start_pc, buffer + offset, sizeof(input.start_pc));
141 offset += sizeof(input.start_pc);
142 std::memcpy(&input.num_flat_calls, buffer + offset, sizeof(input.num_flat_calls));
143 offset += sizeof(input.num_flat_calls);
145 offset += sizeof(input.num_nested_calls);
146 for (auto& call_instance : input.call_instances) {
149 }
150
151 return input;
152 }
153};
154
155// Mutate a single random call instance
157{
158 // Modify a random call instance (using num_events to ensure it's used in a run)
159 size_t num_events =
160 static_cast<size_t>(input.num_flat_calls) * (input.num_nested_calls == 0 ? 1 : input.num_nested_calls);
161 std::uniform_int_distribution<size_t> index_dist(0, num_events == 0 ? 0 : num_events - 1);
162 size_t value_idx = index_dist(rng);
163 std::uniform_int_distribution<int> inner_mutation_dist(0, 3);
164 int inner_mutation_choice = inner_mutation_dist(rng);
165 switch (inner_mutation_choice) {
166 case 0: {
167 // Modify l2 gas (a minimum of min_l2_gas to avoid OOG error)
168 std::uniform_int_distribution<uint32_t> gas_dist(min_l2_gas, std::numeric_limits<uint32_t>::max());
169 input.call_instances[value_idx].l2_gas = MemoryValue::from<uint32_t>(gas_dist(rng));
170 break;
171 }
172 case 1: {
173 // Modify da gas
174 std::uniform_int_distribution<uint32_t> gas_dist(0, std::numeric_limits<uint32_t>::max());
175 input.call_instances[value_idx].da_gas = MemoryValue::from<uint32_t>(gas_dist(rng));
176 break;
177 }
178 case 2: {
179 // Modify contract address
180 std::uniform_int_distribution<uint64_t> addr_dist(0, std::numeric_limits<uint64_t>::max());
181 input.call_instances[value_idx].contract_address =
182 FF(addr_dist(rng), addr_dist(rng), addr_dist(rng), addr_dist(rng));
183 break;
184 }
185 case 3: {
186 // Toggle is_static
187 input.call_instances[value_idx].is_static = !input.call_instances[value_idx].is_static;
188 break;
189 }
190 default:
191 break;
192 }
193}
194
195extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
196{
197 if (size < sizeof(ExternalCallFuzzerInput)) {
198 // Initialize with default input
200 input.to_buffer(data);
201 return sizeof(ExternalCallFuzzerInput);
202 }
203 std::mt19937 rng(seed);
204
205 // Deserialize current input
207
208 // Choose random mutation
209 std::uniform_int_distribution<int> mutation_dist(0, 3);
210 int mutation_choice = mutation_dist(rng);
211
212 switch (mutation_choice) {
213 case 0: {
214 // Modify number of flat internal calls
216 input.num_flat_calls = num_flat_calls_dist(rng);
217 break;
218 }
219 case 1: {
220 // Modify number of nested internal calls
222 input.num_nested_calls = num_nested_calls_dist(rng);
223 break;
224 }
225 case 2: {
226 // Modify initial context pc
227 size_t num_events =
228 static_cast<size_t>(input.num_flat_calls) * (input.num_nested_calls == 0 ? 1 : input.num_nested_calls);
229 // Creating a large offset to avoid overflow of pc
230 const auto& spec = get_wire_instruction_spec();
231 size_t instr_sizes_offset =
232 num_events * (spec.at(WireOpCode::CALL).size_in_bytes + spec.at(WireOpCode::RETURN).size_in_bytes +
233 dummy_instr.size_in_bytes());
235 0, std::numeric_limits<uint32_t>::max() - uint32_t(instr_sizes_offset));
236 input.start_pc = start_pc_dist(rng);
237 break;
238 }
239 case 3: {
240 // Modify a random calldata instance
241 mutate_call_instance(input, rng);
242 break;
243 }
244 default:
245 break;
246 }
247 // Serialize mutated input back to buffer
248 input.to_buffer(data);
249
250 if (max_size > sizeof(ExternalCallFuzzerInput)) {
251 return sizeof(ExternalCallFuzzerInput);
252 }
253
254 return sizeof(ExternalCallFuzzerInput);
255}
256
257// NOTE: context->serialize_context_event() causes stack overflow :(
259{
260 return {
261 .id = context->get_context_id(),
262 .parent_id = context->get_parent_id(),
263 .pc = context->get_pc(),
264 .gas_used = context->get_gas_used(),
265 .gas_limit = context->get_gas_limit(),
266 };
267}
268
271 std::unique_ptr<ContextInterface>& parent_context,
272 ExecutionComponentsProvider& execution_components,
274{
275 auto allocated_l2_gas_read = input.l2_gas;
276 auto allocated_da_gas_read = input.da_gas;
277 auto instr = bb::avm2::testing::InstructionBuilder(input.is_static ? WireOpCode::STATICCALL : WireOpCode::CALL)
278 .operand<uint8_t>(2)
279 .operand<uint8_t>(4)
280 .operand<uint8_t>(6)
281 .operand<uint8_t>(10)
282 .operand<uint8_t>(20)
283 .build();
284
285 ExecutionEvent ex_event = { .wire_instruction = instr, .before_context_event = fill_context_event(parent_context) };
286 AddressingEvent addressing_event;
288
289 // Execution.execute pre - dispatch
290 parent_context->set_next_pc(parent_context->get_pc() + static_cast<uint32_t>(instr.size_in_bytes()));
291 auto addressing = execution_components.make_addressing(addressing_event);
292 addressing->resolve(ex_event.wire_instruction, parent_context->get_memory());
293 auto gas_tracker = execution_components.make_gas_tracker(gas_event, ex_event.wire_instruction, *parent_context);
294
295 // Execution.call / static_call
296 gas_tracker->consume_gas();
297 auto new_gas_limit = gas_tracker->compute_gas_limit_for_call(
298 Gas{ allocated_l2_gas_read.as<uint32_t>(), allocated_da_gas_read.as<uint32_t>() });
299
300 auto child_context = helper.make_nested_fuzzing_context(
301 input.contract_address, input.contract_address, *parent_context, input.is_static, new_gas_limit);
302
303 // Execution.execute post-dispatch:
304 parent_context->set_pc(parent_context->get_next_pc());
305 ex_event.inputs = { allocated_l2_gas_read,
306 allocated_da_gas_read,
307 MemoryValue::from<FF>(input.contract_address),
308 /* cd_size = */ MemoryValue::from<uint32_t>(0) };
309 ex_event.addressing_event = addressing_event;
310 ex_event.gas_event = gas_event;
311 ex_event.after_context_event = fill_context_event(parent_context);
312
313 ex_events.push_back(ex_event);
314
315 // Push event from the call itself (via nested child context)
316 // Note: we only need the gas_limit in after_context_event, hence filling that rather than .before_context_event
317 ExecutionEvent nested_event = {
319 .inputs = { MemoryValue::from(FF(0)), MemoryValue::from(FF(0)), MemoryValue::from(FF(0)) },
320 .after_context_event = fill_context_event(child_context)
321 };
322
323 ex_events.push_back(nested_event);
324
325 return child_context;
326}
327
330 ExecutionComponentsProvider& execution_components)
331{
332 auto instr = bb::avm2::testing::InstructionBuilder(WireOpCode::RETURN)
333 .operand<uint8_t>(30) // ret_size_offset
334 .operand<uint8_t>(40) // ret_offset
335 .build();
336
337 ExecutionEvent ex_event = { .wire_instruction = instr, .before_context_event = fill_context_event(context) };
339
340 // Execution.execute pre - dispatch
341 context->set_next_pc(context->get_pc() + static_cast<uint32_t>(instr.size_in_bytes()));
342 auto gas_tracker = execution_components.make_gas_tracker(gas_event, ex_event.wire_instruction, *context);
343
344 // Execution.ret
345 gas_tracker->consume_gas();
346
347 // TODO(MW): Mimic set execution result to set gas_used and check w asserts for nested returns:
348
349 // set_execution_result({ .rd_offset = ret_offset,
350 // .rd_size = rd_size.as<uint32_t>(),
351 // .gas_used = context.get_gas_used(),
352 // .success = true,
353 // .halting_pc = context.get_pc(),
354 // .halting_message = std::nullopt });
355
356 context->halt();
357
358 // Execution.execute post-dispatch:
359 context->set_pc(context->get_next_pc());
360 ex_event.inputs = { MemoryValue::from<uint32_t>(10) /* =rd_size, TODO(MW): fuzz? */ };
362
363 ex_events.push_back(ex_event);
364}
365
366extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
367{
369
370 if (size < sizeof(ExternalCallFuzzerInput)) {
371 return 0;
372 }
373
375
376 // Set up gadgets and event emitters
377 GadgetFuzzerContextHelper context_helper;
378 auto context = context_helper.make_enqueued_fuzzing_context();
379 context->set_pc(input.start_pc);
381 ExecutionComponentsProvider execution_components(context_helper.greater_than, instruction_info_db);
383
384 try {
385 size_t current_call_idx = 0;
386 for (auto i = 0; i < input.num_flat_calls; i++) {
387 auto child_context = fuzz_call(
388 ex_events, context_helper, context, execution_components, input.call_instances[current_call_idx++]);
389 if (input.num_nested_calls > 0) {
390 fuzz_call(ex_events,
391 context_helper,
392 child_context,
393 execution_components,
394 input.call_instances[current_call_idx++]);
395 fuzz_return(ex_events, child_context, execution_components);
396 // This fuzzer doesn't test beyond the external_call.pil relations/lookups, so we don't need a
397 // handle_exit_call()
398 }
399 fuzz_return(ex_events, context, execution_components);
400 }
401
402 } catch (const std::exception& e) {
403 // No opcode errors to test here
404 return 0;
405 }
406
409 ExecutionTraceBuilder ex_builder;
410
411 ex_builder.process(ex_events, trace);
412 gt_builder.process(context_helper.greater_than_emitter.dump_events(), trace);
413
414 if (getenv("AVM_DEBUG") != nullptr) {
415 info("Debugging trace:");
417 debugger.run();
418 }
419
420 check_relation<external_call_rel>(trace);
424
425 return 0;
426}
#define AVM_RETURN_BASE_L2_GAS
#define AVM_CALL_BASE_L2_GAS
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
static TaggedValue from(T value)
std::string to_string() const
Sets up gadgets and instance managers to provide a context for fuzzing. NOTE: rudimentary set up for ...
DeduplicatingEventEmitter< GreaterThanEvent > greater_than_emitter
std::unique_ptr< simulation::ContextInterface > make_nested_fuzzing_context(AztecAddress address, AztecAddress msg_sender, ContextInterface &parent_context, bool is_static=false, Gas gas_limit=GAS_LIMIT)
std::unique_ptr< simulation::ContextInterface > make_enqueued_fuzzing_context(AztecAddress address=AztecAddress(0), AztecAddress msg_sender=AztecAddress(0), bool is_static=false, FF transaction_fee=FF(0), std::span< const FF > calldata={}, Gas gas_limit=GAS_LIMIT, Gas gas_used=GAS_USED_BY_PRIVATE, TransactionPhase phase=TransactionPhase::APP_LOGIC)
std::unique_ptr< GasTrackerInterface > make_gas_tracker(GasEvent &gas_event, const Instruction &instruction, ContextInterface &context) override
Create a gas tracker bound to the given event, instruction, and context.
std::unique_ptr< AddressingInterface > make_addressing(AddressingEvent &event) override
Create an addressing resolver that writes its resolution results into the given event.
simulation::Instruction build() const
InstructionBuilder & operand(OperandBuilder operand)
void process(const simulation::EventEmitterInterface< simulation::ExecutionEvent >::Container &ex_events, TraceContainer &trace)
Process the execution events and populate the relevant columns in the trace. ExecutionError enum is u...
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Process the greater-than events and populate the relevant columns in the trace.
Definition gt_trace.cpp:20
#define info(...)
Definition log.hpp:93
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:123
const std::vector< MemoryValue > data
TestTraceContainer trace
ssize_t offset
Definition engine.cpp:62
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
const uint32_t min_l2_gas
const uint8_t max_total_calls
ContextEvent fill_context_event(std::unique_ptr< ContextInterface > &context)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t max_size, unsigned int seed)
const auto dummy_instr
const uint8_t max_flat_calls
void fuzz_return(std::vector< ExecutionEvent > &ex_events, std::unique_ptr< ContextInterface > &context, ExecutionComponentsProvider &execution_components)
const uint8_t max_nested_calls
void mutate_call_instance(ExternalCallFuzzerInput &input, std::mt19937 rng)
std::unique_ptr< ContextInterface > fuzz_call(std::vector< ExecutionEvent > &ex_events, GadgetFuzzerContextHelper &helper, std::unique_ptr< ContextInterface > &parent_context, ExecutionComponentsProvider &execution_components, ExternalCallFuzzerInstance input)
InstructionInfoDB instruction_info_db
GasEvent gas_event
void check_interaction(tracegen::TestTraceContainer &trace)
AVM range check gadget for witness generation.
lookup_settings< lookup_external_call_is_l2_gas_left_gt_allocated_settings_ > lookup_external_call_is_l2_gas_left_gt_allocated_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
const std::unordered_map< WireOpCode, WireInstructionSpec > & get_wire_instruction_spec()
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
ExternalCallFuzzerInput()=default
std::array< ExternalCallFuzzerInstance, max_total_calls > call_instances
void to_buffer(uint8_t *buffer) const
static ExternalCallFuzzerInput from_buffer(const uint8_t *buffer)
static ExternalCallFuzzerInstance from_buffer(const uint8_t *buffer)
ExternalCallFuzzerInstance()=default
void to_buffer(uint8_t *buffer) const
Settings to be passed ot GenericLookupRelationImpl.
std::vector< MemoryValue > inputs