Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
execution_trace.test.cpp
Go to the documentation of this file.
2
3#include <cstdint>
4#include <gmock/gmock.h>
5#include <gtest/gtest.h>
6
17
18namespace bb::avm2::tracegen {
19namespace {
20
21using simulation::ExecutionEvent;
22
23using ::bb::avm2::testing::InstructionBuilder;
24using enum ::bb::avm2::WireOpCode;
25
26using ::testing::_;
27using ::testing::AllOf;
28using ::testing::ElementsAre;
29
30// Helper functions for creating common execution events
31
32// Base helper to set up common event fields
33ExecutionEvent create_base_event(const simulation::Instruction& instruction,
34 uint32_t context_id,
35 uint32_t parent_id,
36 TransactionPhase phase)
37{
38 ExecutionEvent ex_event;
39 ex_event.wire_instruction = instruction;
40 ex_event.after_context_event.id = context_id;
41 ex_event.after_context_event.parent_id = parent_id;
42 ex_event.after_context_event.phase = phase;
43 ex_event.before_context_event = ex_event.after_context_event;
44 return ex_event;
45}
46
47ExecutionEvent create_add_event(uint32_t context_id, uint32_t parent_id, TransactionPhase phase)
48{
49 const auto add_instr =
50 InstructionBuilder(WireOpCode::ADD_8).operand<uint8_t>(0).operand<uint8_t>(0).operand<uint8_t>(0).build();
51 auto ex_event = create_base_event(add_instr, context_id, parent_id, phase);
53 ex_event.output = { MemoryValue::from_tag(ValueTag::U16, 8) };
54 return ex_event;
55}
56
57ExecutionEvent create_call_event(uint32_t context_id,
58 uint32_t parent_id,
59 TransactionPhase phase,
60 uint32_t next_context_id)
61{
62 const auto call_instr = InstructionBuilder(WireOpCode::CALL)
63 .operand<uint8_t>(2)
64 .operand<uint8_t>(4)
65 .operand<uint8_t>(6)
66 .operand<uint8_t>(10)
67 .operand<uint8_t>(20)
68 .build();
69 auto ex_event = create_base_event(call_instr, context_id, parent_id, phase);
70 ex_event.next_context_id = next_context_id;
71 ex_event.inputs = { /*allocated_l2_gas_read=*/MemoryValue::from<uint32_t>(10),
72 /*allocated_da_gas_read=*/MemoryValue ::from<uint32_t>(11),
73 /*contract_address=*/
74 MemoryValue::from<uint32_t>(0xdeadbeef),
75 /*cd_size=*/MemoryValue::from<uint32_t>(0) };
76 return ex_event;
77}
78
79ExecutionEvent create_return_event(uint32_t context_id, uint32_t parent_id, TransactionPhase phase)
80{
81 const auto return_instr = InstructionBuilder(WireOpCode::RETURN).operand<uint8_t>(0).operand<uint8_t>(0).build();
82 auto ex_event = create_base_event(return_instr, context_id, parent_id, phase);
83 ex_event.inputs = { /*rd_size=*/MemoryValue::from<uint32_t>(2) };
84 return ex_event;
85}
86
87ExecutionEvent create_error_event(uint32_t context_id,
88 uint32_t parent_id,
89 TransactionPhase phase,
90 uint32_t next_context_id)
91{
92 // Actually an ADD instruction with exception=true
93 const auto add_instr =
94 InstructionBuilder(WireOpCode::ADD_8).operand<uint8_t>(0).operand<uint8_t>(0).operand<uint8_t>(0).build();
95 auto ex_event = create_base_event(add_instr, context_id, parent_id, phase);
96 ex_event.error =
97 simulation::ExecutionError::INSTRUCTION_FETCHING; // This should trigger error behavior (like discard)
98 ex_event.next_context_id = next_context_id; // Return to parent
99 // inputs and output are not used for error events
101 ex_event.output = { MemoryValue::from_tag(ValueTag::U16, 8) };
102 return ex_event;
103}
104
105TEST(ExecutionTraceGenTest, RegisterAllocation)
106{
107 TestTraceContainer trace;
108 ExecutionTraceBuilder builder;
109
110 // Some inputs
111 // Use the instruction builder - we can make the operands more complex
112 const auto instr = InstructionBuilder(WireOpCode::ADD_8)
113 // All operands are direct - for simplicity
114 .operand<uint8_t>(0)
115 .operand<uint8_t>(0)
116 .operand<uint8_t>(0)
117 .build();
118
119 ExecutionEvent ex_event = {
120 .wire_instruction = instr,
122 .output = { MemoryValue::from_tag(ValueTag::U16, 8) },
123 .addressing_event = {},
124 };
125
126 builder.process({ ex_event }, trace);
127
128 // todo: Test doesnt check the other register fields are zeroed out.
129 EXPECT_THAT(trace.as_rows(),
130 ElementsAre(
131 // First row is empty
132 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
133 // First real row
134 AllOf(ROW_FIELD_EQ(execution_sel, 1),
135 ROW_FIELD_EQ(execution_sel_exec_dispatch_alu, 1),
136 ROW_FIELD_EQ(execution_register_0_, 5),
137 ROW_FIELD_EQ(execution_register_1_, 3),
138 ROW_FIELD_EQ(execution_register_2_, 8),
139 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(ValueTag::U16)),
140 ROW_FIELD_EQ(execution_mem_tag_reg_1_, static_cast<uint8_t>(ValueTag::U16)),
141 ROW_FIELD_EQ(execution_mem_tag_reg_2_, static_cast<uint8_t>(ValueTag::U16)),
142 ROW_FIELD_EQ(execution_sel_mem_op_reg_0_, 1),
143 ROW_FIELD_EQ(execution_sel_mem_op_reg_1_, 1),
144 ROW_FIELD_EQ(execution_sel_mem_op_reg_2_, 1),
145 ROW_FIELD_EQ(execution_rw_reg_0_, 0),
146 ROW_FIELD_EQ(execution_rw_reg_1_, 0),
147 ROW_FIELD_EQ(execution_rw_reg_2_, 1))));
148}
149
150TEST(ExecutionTraceGenTest, Call)
151{
152 TestTraceContainer trace;
153 ExecutionTraceBuilder builder;
154
155 // Inputs
156 const auto call_instr = InstructionBuilder(WireOpCode::CALL)
157 .operand<uint8_t>(2)
158 .operand<uint8_t>(4)
159 .operand<uint8_t>(6)
160 .operand<uint8_t>(10)
161 .operand<uint8_t>(20)
162 .build();
163
164 Gas allocated_gas = { .l2_gas = 100, .da_gas = 200 };
165 Gas gas_limit = { .l2_gas = 1000, .da_gas = 2000 };
166 Gas gas_used = { .l2_gas = 500, .da_gas = 1900 };
167 Gas gas_left = gas_limit - gas_used;
168
169 ExecutionEvent ex_event = {
170 .wire_instruction = call_instr,
171 .inputs = { /*allocated_l2_gas_read=*/MemoryValue::from<uint32_t>(allocated_gas.l2_gas),
172 /*allocated_da_gas_read=*/MemoryValue ::from<uint32_t>(allocated_gas.da_gas),
173 /*contract_address=*/MemoryValue::from<FF>(0xdeadbeef),
174 /*cd_size=*/MemoryValue::from<uint32_t>(0) },
175 .next_context_id = 2,
176 .addressing_event = {
177 .resolution_info = {
178 { .after_relative = MemoryValue::from<uint32_t>(0),
179 .resolved_operand = MemoryValue::from<uint32_t>(0),
180 },
181 { .after_relative = MemoryValue::from<uint32_t>(0),
182 .resolved_operand = MemoryValue::from<uint32_t>(0),
183 },
184 { .after_relative = MemoryValue::from<uint32_t>(0),
185 .resolved_operand = MemoryValue::from<uint32_t>(0) },
186 { .after_relative = MemoryValue::from<uint32_t>(0),
187 .resolved_operand = MemoryValue::from<uint32_t>(10) },
188 { .after_relative = MemoryValue::from<uint32_t>(0),
189 .resolved_operand = MemoryValue::from<uint32_t>(20) },
190 } },
191 .after_context_event = {
192 .id = 1,
193 .contract_addr = 0xdeadbeef,
194 .gas_used = gas_used,
195 .gas_limit = gas_limit,
196 },
197 };
198
199 builder.process({ ex_event }, trace);
200 EXPECT_THAT(trace.as_rows(),
201 ElementsAre(
202 // First row is empty
203 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
204 // First real row
205 AllOf(ROW_FIELD_EQ(execution_sel, 1),
206 ROW_FIELD_EQ(execution_sel_execute_call, 1),
207 ROW_FIELD_EQ(execution_sel_enter_call, 1),
208 ROW_FIELD_EQ(execution_rop_3_, 10),
209 ROW_FIELD_EQ(execution_rop_4_, 20),
210 ROW_FIELD_EQ(execution_register_0_, allocated_gas.l2_gas),
211 ROW_FIELD_EQ(execution_register_1_, allocated_gas.da_gas),
212 ROW_FIELD_EQ(execution_register_2_, 0xdeadbeef),
213 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(ValueTag::U32)),
214 ROW_FIELD_EQ(execution_mem_tag_reg_1_, static_cast<uint8_t>(ValueTag::U32)),
215 ROW_FIELD_EQ(execution_mem_tag_reg_2_, static_cast<uint8_t>(ValueTag::FF)),
216 ROW_FIELD_EQ(execution_sel_mem_op_reg_0_, 1),
217 ROW_FIELD_EQ(execution_sel_mem_op_reg_1_, 1),
218 ROW_FIELD_EQ(execution_sel_mem_op_reg_2_, 1),
219 ROW_FIELD_EQ(execution_rw_reg_0_, 0),
220 ROW_FIELD_EQ(execution_rw_reg_1_, 0),
221 ROW_FIELD_EQ(execution_rw_reg_2_, 0),
222 ROW_FIELD_EQ(execution_is_static, 0),
223 ROW_FIELD_EQ(execution_context_id, 1),
224 ROW_FIELD_EQ(execution_next_context_id, 2),
225 ROW_FIELD_EQ(execution_l2_gas_left, gas_left.l2_gas),
226 ROW_FIELD_EQ(execution_da_gas_left, gas_left.da_gas),
227 ROW_FIELD_EQ(execution_is_l2_gas_left_gt_allocated, true),
228 ROW_FIELD_EQ(execution_is_da_gas_left_gt_allocated, false))));
229}
230
231TEST(ExecutionTraceGenTest, Return)
232{
233 TestTraceContainer trace;
234 ExecutionTraceBuilder builder;
235
236 // Inputs
237 const auto return_instr = InstructionBuilder(WireOpCode::RETURN).operand<uint8_t>(4).operand<uint8_t>(20).build();
238
239 ExecutionEvent ex_event = {
240 .wire_instruction = return_instr,
241 .inputs = { /*rd_size=*/MemoryValue::from<uint32_t>(2) },
242 .next_context_id = 2,
243 .addressing_event = {
244 .resolution_info = {
245 /*rd_size_offset=*/{ .after_relative = MemoryValue::from<uint32_t>(0),
246 .resolved_operand = MemoryValue::from<uint32_t>(4),
247 },
248 /*rd_offset=*/{ .after_relative = MemoryValue::from<uint32_t>(0),
249 .resolved_operand = MemoryValue::from<uint32_t>(5),
250 },
251 } },
252 .after_context_event = {
253 .id = 1,
254 .contract_addr = 0xdeadbeef,
255 },
256 };
257
258 builder.process({ ex_event }, trace);
259 EXPECT_THAT(trace.as_rows(),
260 ElementsAre(
261 // First row is empty
262 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
263 // First real row
264 AllOf(ROW_FIELD_EQ(execution_sel, 1),
265 ROW_FIELD_EQ(execution_sel_execute_return, 1),
266 ROW_FIELD_EQ(execution_sel_exit_call, 1),
267 ROW_FIELD_EQ(execution_rop_0_, 4),
268 ROW_FIELD_EQ(execution_rop_1_, 5),
269 ROW_FIELD_EQ(execution_register_0_, /*rd_size*/ 2),
270 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(ValueTag::U32)),
271 ROW_FIELD_EQ(execution_sel_mem_op_reg_0_, 1),
272 ROW_FIELD_EQ(execution_rw_reg_0_, 0),
273 ROW_FIELD_EQ(execution_is_static, 0),
274 ROW_FIELD_EQ(execution_context_id, 1),
275 ROW_FIELD_EQ(execution_next_context_id, 2))));
276}
277
278TEST(ExecutionTraceGenTest, Gas)
279{
280 TestTraceContainer trace;
281 ExecutionTraceBuilder builder;
282
283 // Use the instruction builder - we can make the operands more complex
284 const auto instr = InstructionBuilder(WireOpCode::AND_8)
285 // All operands are direct - for simplicity
286 .operand<uint8_t>(0)
287 .operand<uint8_t>(0)
288 .operand<uint8_t>(0)
289 .build();
290
291 ExecutionEvent ex_event = {
292 .wire_instruction = instr,
294 .output = { MemoryValue::from_tag(ValueTag::U16, 8) },
295 .addressing_event = {},
296 };
297
298 const auto& exec_instruction_spec = get_exec_instruction_spec().at(instr.get_exec_opcode());
299
300 const uint32_t addressing_gas = 50;
301 const uint32_t opcode_gas = exec_instruction_spec.gas_cost.opcode_gas;
302 const uint32_t dynamic_l2_gas = exec_instruction_spec.gas_cost.dyn_l2;
303 const uint32_t dynamic_da_gas = exec_instruction_spec.gas_cost.dyn_da;
304 const uint32_t base_da_gas = exec_instruction_spec.gas_cost.base_da;
305
306 Gas gas_limit = { .l2_gas = 110149, .da_gas = 100000 };
307 Gas prev_gas_used = { .l2_gas = 100000, .da_gas = 70000 };
308
309 ex_event.after_context_event.gas_limit = gas_limit; // Will OOG on l2 after dynamic gas
310 ex_event.before_context_event.gas_used = prev_gas_used;
311 ex_event.gas_event.addressing_gas = addressing_gas;
312 ex_event.gas_event.dynamic_gas_factor = { .l2_gas = 2, .da_gas = 1 };
313 ex_event.gas_event.oog_l2 = true;
314 ex_event.gas_event.oog_da = false;
315
316 uint64_t total_gas_used_l2 = prev_gas_used.l2_gas + opcode_gas + addressing_gas + (dynamic_l2_gas * 2);
317 uint64_t total_gas_used_da = prev_gas_used.da_gas + base_da_gas + (dynamic_da_gas * 1);
318
319 ex_event.gas_event.total_gas_used_l2 = total_gas_used_l2;
320 ex_event.gas_event.total_gas_used_da = total_gas_used_da;
321
322 builder.process({ ex_event }, trace);
323
324 EXPECT_THAT(trace.as_rows(),
325 ElementsAre(
326 // First row is empty
327 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
328 // First real row
329 AllOf(ROW_FIELD_EQ(execution_sel, 1),
330 ROW_FIELD_EQ(execution_opcode_gas, opcode_gas),
331 ROW_FIELD_EQ(execution_addressing_gas, addressing_gas),
332 ROW_FIELD_EQ(execution_base_da_gas, base_da_gas),
333 ROW_FIELD_EQ(execution_out_of_gas_l2, true),
334 ROW_FIELD_EQ(execution_out_of_gas_da, false),
335 ROW_FIELD_EQ(execution_sel_out_of_gas, true),
336 ROW_FIELD_EQ(execution_prev_l2_gas_used, 100000),
337 ROW_FIELD_EQ(execution_prev_da_gas_used, 70000),
338 ROW_FIELD_EQ(execution_dynamic_l2_gas_factor, 2),
339 ROW_FIELD_EQ(execution_dynamic_da_gas_factor, 1),
340 ROW_FIELD_EQ(execution_dynamic_l2_gas, dynamic_l2_gas),
341 ROW_FIELD_EQ(execution_dynamic_da_gas, dynamic_da_gas),
342 ROW_FIELD_EQ(execution_total_gas_l2, total_gas_used_l2),
343 ROW_FIELD_EQ(execution_total_gas_da, total_gas_used_da))));
344}
345
346TEST(ExecutionTraceGenTest, DiscardNestedFailContext)
347{
348 TestTraceContainer trace;
349 ExecutionTraceBuilder builder;
350
351 // Create a sequence: parent context calls child context, child does some work then fails
353 // Event 1: Parent context does ADD
354 create_add_event(1, 0, TransactionPhase::APP_LOGIC),
355
356 // Event 2: Parent calls child (context 1 -> 2)
357 create_call_event(1, 0, TransactionPhase::APP_LOGIC, 2),
358
359 // Event 3: Child context does ADD - this should have discard=1 since child will fail
360 create_add_event(2, 1, TransactionPhase::APP_LOGIC),
361
362 // Event 4: Child context fails
363 create_error_event(2, 1, TransactionPhase::APP_LOGIC, 1),
364
365 // Event 5: Parent continues after child fails
366 create_add_event(1, 0, TransactionPhase::APP_LOGIC),
367
368 // Event 6: Parent returns successfully (top-level exit)
369 create_return_event(1, 0, TransactionPhase::APP_LOGIC),
370 };
371
372 builder.process(events, trace);
373
374 const auto rows = trace.as_rows();
375
376 EXPECT_THAT(rows,
377 ElementsAre(
378 // Row 0: Initialization row
379 _,
380 // Row 1: Parent ADD before call - no discard
381 AllOf(ROW_FIELD_EQ(execution_discard, 0),
382 ROW_FIELD_EQ(execution_dying_context_id, 0),
383 ROW_FIELD_EQ(execution_is_dying_context, 0)),
384 // Row 2: Parent CALL - no discard yet (discard is set for the NEXT event)
385 AllOf(ROW_FIELD_EQ(execution_discard, 0),
386 ROW_FIELD_EQ(execution_dying_context_id, 0),
387 ROW_FIELD_EQ(execution_is_dying_context, 0)),
388 // Row 3: Child ADD - should have discard=1, dying_context_id=2
389 AllOf(ROW_FIELD_EQ(execution_discard, 1),
390 ROW_FIELD_EQ(execution_dying_context_id, 2),
391 ROW_FIELD_EQ(execution_is_dying_context, 1)),
392 // Row 4: Child fail - should still have discard=1, dying_context_id=2
393 AllOf(ROW_FIELD_EQ(execution_discard, 1),
394 ROW_FIELD_EQ(execution_dying_context_id, 2),
395 ROW_FIELD_EQ(execution_is_dying_context, 1),
396 ROW_FIELD_EQ(execution_sel_error, 1), // failure
397 ROW_FIELD_EQ(execution_nested_failure, 1)), // Has parent, so rollback
398 // Row 5: Parent continues - discard should be reset to 0
399 AllOf(ROW_FIELD_EQ(execution_discard, 0),
400 ROW_FIELD_EQ(execution_dying_context_id, 0),
401 ROW_FIELD_EQ(execution_is_dying_context, 0)),
402 // Row 6: Parent returns - no discard
403 AllOf(ROW_FIELD_EQ(execution_discard, 0),
404 ROW_FIELD_EQ(execution_dying_context_id, 0),
405 ROW_FIELD_EQ(execution_is_dying_context, 0))));
406}
407
408TEST(ExecutionTraceGenTest, DiscardAppLogicDueToTeardownError)
409{
410 TestTraceContainer trace;
411 ExecutionTraceBuilder builder;
412
413 // Create a sequence that has app logic success but teardown failure, which should discard app logic too
415 // Event 1: App logic phase - successful ADD
416 create_add_event(1, 0, TransactionPhase::APP_LOGIC),
417
418 // Event 2: App logic phase - successful RETURN (exits app logic phase)
419 create_return_event(1, 0, TransactionPhase::APP_LOGIC),
420
421 // Event 3: Teardown phase - some operation
422 create_add_event(2, 0, TransactionPhase::TEARDOWN),
423
424 // Event 4: Teardown phase - failure (that exits teardown)
425 create_error_event(2, 0, TransactionPhase::TEARDOWN, 0),
426 };
427
428 builder.process(events, trace);
429
430 const auto rows = trace.as_rows();
431
432 EXPECT_THAT(rows,
433 ElementsAre(_,
434 // Row 1: App logic ADD - should have discard=1 because teardown will error
435 AllOf(ROW_FIELD_EQ(execution_discard, 1),
436 ROW_FIELD_EQ(execution_dying_context_id, 2), // Teardown context id
437 ROW_FIELD_EQ(execution_is_dying_context, 0)), // Not the dying context itself
438 // Row 2: App logic RETURN - should have discard=1 because teardown will error
439 AllOf(ROW_FIELD_EQ(execution_discard, 1),
440 ROW_FIELD_EQ(execution_dying_context_id, 2),
441 ROW_FIELD_EQ(execution_is_dying_context, 0)),
442 // Row 3: Teardown ADD - should have discard=1
443 AllOf(ROW_FIELD_EQ(execution_discard, 1),
444 ROW_FIELD_EQ(execution_dying_context_id, 2),
445 ROW_FIELD_EQ(execution_is_dying_context, 1)), // This IS the dying context
446 // Row 4: Teardown failure - should have discard=1
447 AllOf(ROW_FIELD_EQ(execution_discard, 1),
448 ROW_FIELD_EQ(execution_dying_context_id, 2),
449 ROW_FIELD_EQ(execution_is_dying_context, 1),
450 ROW_FIELD_EQ(execution_sel_error, 1),
451 ROW_FIELD_EQ(execution_nested_failure, 0)))); // No parent, so no rollback
452}
453
454TEST(ExecutionTraceGenTest, DiscardAppLogicDueToSecondEnqueuedCallError)
455{
456 TestTraceContainer trace;
457 ExecutionTraceBuilder builder;
458
459 // Create a sequence with two enqueued calls where the second one errors
460 // This should cause the app logic from the first call to be discarded
462 // First enqueued call
463 // Event 1: First call's app logic - successful ADD
464 create_add_event(1, 0, TransactionPhase::APP_LOGIC),
465 // Event 2: First call's app logic - successful RETURN (exits first call)
466 create_return_event(1, 0, TransactionPhase::APP_LOGIC),
467
468 // Second enqueued call
469 // Event 3: Second call's app logic - ADD operation
470 create_add_event(2, 0, TransactionPhase::APP_LOGIC),
471 // Event 4: Second call's app logic - ERROR (causes second enqueued call to fail)
472 create_error_event(2, 0, TransactionPhase::APP_LOGIC, 0),
473 };
474
475 builder.process(events, trace);
476
477 const auto rows = trace.as_rows();
478
479 EXPECT_THAT(rows,
480 ElementsAre(_,
481 // Row 1: First call's ADD - should have discard=1 because second call will error
482 AllOf(ROW_FIELD_EQ(execution_discard, 1),
483 ROW_FIELD_EQ(execution_dying_context_id, 2), // Second call's context id
484 ROW_FIELD_EQ(execution_is_dying_context, 0)), // Not the dying context itself
485 // Row 2: First call's RETURN - should have discard=1 because second call will error
486 AllOf(ROW_FIELD_EQ(execution_discard, 1),
487 ROW_FIELD_EQ(execution_dying_context_id, 2),
488 ROW_FIELD_EQ(execution_is_dying_context, 0)),
489 // Row 3: Second call's ADD - should have discard=1
490 AllOf(ROW_FIELD_EQ(execution_discard, 1),
491 ROW_FIELD_EQ(execution_dying_context_id, 2),
492 ROW_FIELD_EQ(execution_is_dying_context, 1)), // This IS the dying context
493 // Row 4: Second call's ERROR - should have discard=1
494 AllOf(ROW_FIELD_EQ(execution_discard, 1),
495 ROW_FIELD_EQ(execution_dying_context_id, 2),
496 ROW_FIELD_EQ(execution_is_dying_context, 1),
497 ROW_FIELD_EQ(execution_sel_error, 1),
498 ROW_FIELD_EQ(execution_nested_failure, 0)))); // No parent, so no rollback
499}
500
501TEST(ExecutionTraceGenTest, InternalCall)
502{
503 TestTraceContainer trace;
504 ExecutionTraceBuilder builder;
505 // Use the instruction builder - we can make the operands more complex
506 const auto instr = InstructionBuilder(WireOpCode::INTERNALCALL)
507 // All operands are direct - for simplicity
508 .operand<uint32_t>(10)
509 .build();
510
511 ExecutionEvent ex_event = {
512 .wire_instruction = instr,
513 .addressing_event = {
514 .resolution_info = {
515 {
516 .resolved_operand = MemoryValue::from<uint32_t>(10) },
517 },
518 },
519 .before_context_event {
520 .internal_call_id = 1,
521 .internal_call_return_id = 0,
522 .next_internal_call_id = 2,
523 }
524 };
525
526 builder.process({ ex_event }, trace);
527
528 EXPECT_THAT(trace.as_rows(),
529 ElementsAre(
530 // First row is empty
531 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
532 // Second row is the internal call
533 AllOf(ROW_FIELD_EQ(execution_sel, 1),
534 ROW_FIELD_EQ(execution_sel_execute_internal_call, 1),
535 ROW_FIELD_EQ(execution_next_internal_call_id, 2),
536 ROW_FIELD_EQ(execution_internal_call_id, 1),
537 ROW_FIELD_EQ(execution_internal_call_return_id, 0),
538 ROW_FIELD_EQ(execution_rop_0_, 10))));
539}
540
541TEST(ExecutionTraceGenTest, InternalRetError)
542{
543 TestTraceContainer trace;
544 ExecutionTraceBuilder builder;
545 // Use the instruction builder - we can make the operands more complex
546 const auto instr = InstructionBuilder(WireOpCode::INTERNALRETURN).build();
547
548 simulation::ExecutionEvent ex_event = { .error = simulation::ExecutionError::OPCODE_EXECUTION,
549 .wire_instruction = instr,
550 .addressing_event = {},
551 .before_context_event{
552 .internal_call_id = 1,
553 .internal_call_return_id = 0,
554 .next_internal_call_id = 2,
555 } };
556
557 builder.process({ ex_event }, trace);
558
559 EXPECT_THAT(trace.as_rows(),
560 ElementsAre(
561 // First row is empty
562 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
563 // Second row is the internal call
564 AllOf(ROW_FIELD_EQ(execution_sel, 1),
565 ROW_FIELD_EQ(execution_sel_execute_internal_return, 1),
566 ROW_FIELD_EQ(execution_sel_read_unwind_call_stack, 0),
567 ROW_FIELD_EQ(execution_next_internal_call_id, 2),
568 ROW_FIELD_EQ(execution_internal_call_id, 1),
569 ROW_FIELD_EQ(execution_internal_call_return_id, 0),
570 ROW_FIELD_EQ(execution_sel_opcode_error, 1),
571 ROW_FIELD_EQ(execution_internal_call_return_id_inv, 0))));
572}
573
574TEST(ExecutionTraceGenTest, Jump)
575{
576 TestTraceContainer trace;
577 ExecutionTraceBuilder builder;
578
579 const auto instr = InstructionBuilder(WireOpCode::JUMP_32)
580 .operand<uint32_t>(120) // Immediate operand
581 .build();
582
583 ExecutionEvent ex_event_jump = {
584 .wire_instruction = instr,
585 .addressing_event = { .resolution_info = { {
586 .resolved_operand = MemoryValue::from<uint32_t>(120),
587 } } },
588 };
589
590 builder.process({ ex_event_jump }, trace);
591
592 EXPECT_THAT(trace.as_rows(),
593 ElementsAre(
594 // First row is empty
595 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
596 // Second row is the jump
597 AllOf(ROW_FIELD_EQ(execution_sel, 1),
598 ROW_FIELD_EQ(execution_sel_execute_jump, 1),
599 ROW_FIELD_EQ(execution_rop_0_, 120),
600 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_JUMP))));
601}
602
603TEST(ExecutionTraceGenTest, JumpI)
604{
605 TestTraceContainer trace;
606 ExecutionTraceBuilder builder;
607
608 const auto instr = InstructionBuilder(WireOpCode::JUMPI_32)
609 .operand<uint16_t>(654) // Condition Offset
610 .operand<uint32_t>(9876) // Immediate operand
611 .build();
612
613 ExecutionEvent ex_event_jumpi = {
614 .wire_instruction = instr,
615 .inputs = { MemoryValue::from<uint1_t>(1) }, // Conditional value
616 .addressing_event = { .resolution_info = { {
617 .resolved_operand = MemoryValue::from<uint32_t>(654),
618 },
619 {
620 .resolved_operand = MemoryValue::from<uint32_t>(9876),
621 } } },
622 };
623
624 builder.process({ ex_event_jumpi }, trace);
625
626 EXPECT_THAT(trace.as_rows(),
627 ElementsAre(
628 // First row is empty
629 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
630 // Second row is the jumpi
631 AllOf(ROW_FIELD_EQ(execution_sel, 1),
632 ROW_FIELD_EQ(execution_sel_execute_jumpi, 1),
633 ROW_FIELD_EQ(execution_rop_0_, 654),
634 ROW_FIELD_EQ(execution_rop_1_, 9876),
635 ROW_FIELD_EQ(execution_register_0_, 1),
636 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(ValueTag::U1)),
637 ROW_FIELD_EQ(execution_expected_tag_reg_0_, static_cast<uint8_t>(ValueTag::U1)),
638 ROW_FIELD_EQ(execution_sel_tag_check_reg_0_, 1),
639 ROW_FIELD_EQ(execution_sel_should_read_registers, 1),
640 ROW_FIELD_EQ(execution_sel_register_read_error, 0),
641 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_JUMPI))));
642}
643
644TEST(ExecutionTraceGenTest, JumpiWrongTag)
645{
646 TestTraceContainer trace;
647 ExecutionTraceBuilder builder;
648
649 const auto instr = InstructionBuilder(WireOpCode::JUMPI_32)
650 .operand<uint16_t>(654) // Condition Offset
651 .operand<uint32_t>(9876) // Immediate operand
652 .build();
653
654 ExecutionEvent ex_event_jumpi = {
656 .wire_instruction = instr,
657 .inputs = { MemoryValue::from<uint8_t>(1) }, // Conditional value with tag != U1
658 .addressing_event = { .resolution_info = { {
659 .resolved_operand = MemoryValue::from<uint32_t>(654),
660 },
661 {
662 .resolved_operand = MemoryValue::from<uint32_t>(9876),
663 } } },
664 };
665
666 builder.process({ ex_event_jumpi }, trace);
667
668 EXPECT_THAT(trace.as_rows(),
669 ElementsAre(
670 // First row is empty
671 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
672 // Second row is the jumpi
673 AllOf(ROW_FIELD_EQ(execution_sel, 1),
674 ROW_FIELD_EQ(execution_sel_execute_jumpi, 0), // Inactive because of register read error
675 ROW_FIELD_EQ(execution_rop_0_, 654),
676 ROW_FIELD_EQ(execution_rop_1_, 9876),
677 ROW_FIELD_EQ(execution_register_0_, 1),
678 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::U8)),
679 ROW_FIELD_EQ(execution_expected_tag_reg_0_, static_cast<uint8_t>(MemoryTag::U1)),
680 ROW_FIELD_EQ(execution_sel_tag_check_reg_0_, 1),
681 ROW_FIELD_EQ(execution_sel_should_read_registers, 1),
682 ROW_FIELD_EQ(execution_batched_tags_diff_inv_reg,
683 1), // (2**0 * (mem_tag_reg[0] - expected_tag_reg[0]))^-1 = 1
684 ROW_FIELD_EQ(execution_sel_register_read_error, 1),
685 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_JUMPI))));
686}
687
688TEST(ExecutionTraceGenTest, Mov16)
689{
690 TestTraceContainer trace;
691 ExecutionTraceBuilder builder;
692
693 const auto instr = InstructionBuilder(WireOpCode::MOV_16)
694 .operand<uint32_t>(1000) // srcOffset
695 .operand<uint32_t>(1001) // dstOffset
696 .build();
697
698 ExecutionEvent ex_event_mov = {
699 .wire_instruction = instr,
700 .inputs = { MemoryValue::from<uint128_t>(100) }, // src value
701 .output = MemoryValue::from<uint128_t>(100), // dst value
702 .addressing_event = { .resolution_info = { {
703 .resolved_operand = MemoryValue::from<uint32_t>(1000),
704 },
705 {
706 .resolved_operand = MemoryValue::from<uint32_t>(1001),
707 } } },
708 };
709
710 builder.process({ ex_event_mov }, trace);
711
712 EXPECT_THAT(trace.as_rows(),
713 ElementsAre(
714 // First row is empty
715 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
716 // Second row is the mov
717 AllOf(ROW_FIELD_EQ(execution_sel, 1),
718 ROW_FIELD_EQ(execution_sel_execute_mov, 1),
719 ROW_FIELD_EQ(execution_rop_0_, 1000),
720 ROW_FIELD_EQ(execution_rop_1_, 1001),
721 ROW_FIELD_EQ(execution_register_0_, 100),
722 ROW_FIELD_EQ(execution_register_1_, 100),
723 ROW_FIELD_EQ(execution_sel_mem_op_reg_0_, 1),
724 ROW_FIELD_EQ(execution_sel_mem_op_reg_1_, 1),
725 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::U128)),
726 ROW_FIELD_EQ(execution_mem_tag_reg_1_, static_cast<uint8_t>(MemoryTag::U128)),
727 ROW_FIELD_EQ(execution_rw_reg_0_, 0),
728 ROW_FIELD_EQ(execution_rw_reg_1_, 1),
729 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_MOV))));
730}
731
732TEST(ExecutionTraceGenTest, Mov8)
733{
734 TestTraceContainer trace;
735 ExecutionTraceBuilder builder;
736
737 const auto instr = InstructionBuilder(WireOpCode::MOV_8)
738 .operand<uint32_t>(10) // srcOffset
739 .operand<uint32_t>(11) // dstOffset
740 .build();
741
742 ExecutionEvent ex_event_mov = {
743 .wire_instruction = instr,
744 .inputs = { MemoryValue::from<uint64_t>(100) }, // src value
745 .output = MemoryValue::from<uint64_t>(100), // dst value
746 .addressing_event = { .resolution_info = { {
747 .resolved_operand = MemoryValue::from<uint32_t>(10),
748 },
749 {
750 .resolved_operand = MemoryValue::from<uint32_t>(11),
751 } } },
752 };
753
754 builder.process({ ex_event_mov }, trace);
755
756 EXPECT_THAT(trace.as_rows(),
757 ElementsAre(
758 // First row is empty
759 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
760 // Second row is the mov
761 AllOf(ROW_FIELD_EQ(execution_sel, 1),
762 ROW_FIELD_EQ(execution_sel_execute_mov, 1),
763 ROW_FIELD_EQ(execution_rop_0_, 10),
764 ROW_FIELD_EQ(execution_rop_1_, 11),
765 ROW_FIELD_EQ(execution_register_0_, 100),
766 ROW_FIELD_EQ(execution_register_1_, 100),
767 ROW_FIELD_EQ(execution_sel_mem_op_reg_0_, 1),
768 ROW_FIELD_EQ(execution_sel_mem_op_reg_1_, 1),
769 ROW_FIELD_EQ(execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::U64)),
770 ROW_FIELD_EQ(execution_mem_tag_reg_1_, static_cast<uint8_t>(MemoryTag::U64)),
771 ROW_FIELD_EQ(execution_rw_reg_0_, 0),
772 ROW_FIELD_EQ(execution_rw_reg_1_, 1),
773 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_MOV))));
774}
775
776TEST(ExecutionTraceGenTest, SuccessCopy)
777{
778 TestTraceContainer trace;
779 ExecutionTraceBuilder builder;
780 const auto instr = InstructionBuilder(WireOpCode::SUCCESSCOPY)
781 .operand<uint8_t>(45) // Dst Offset
782 .build();
783 // clang-format off
784 ExecutionEvent ex_event = {
785 .wire_instruction = instr,
786 .output = { MemoryValue::from_tag(ValueTag::U1, 1) }, // Success copy outputs true
787 .addressing_event = {
788 .resolution_info = { { .resolved_operand = MemoryValue::from<uint8_t>(45) } }
789 },
790 .after_context_event = { .last_child_success = true }
791 };
792 // clang-format on
793
794 builder.process({ ex_event }, trace);
795 EXPECT_THAT(trace.as_rows(),
796 ElementsAre(
797 // First row is empty
798 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
799 // Second row is the success copy
800 AllOf(ROW_FIELD_EQ(execution_sel, 1),
801 ROW_FIELD_EQ(execution_sel_execute_success_copy, 1),
802 ROW_FIELD_EQ(execution_rop_0_, 45), // Dst Offset
803 ROW_FIELD_EQ(execution_register_0_, 1),
804 ROW_FIELD_EQ(execution_mem_tag_reg_0_, /*U1=*/1), // Memory tag for dst
805 ROW_FIELD_EQ(execution_last_child_success, 1), // last_child_success = true
806 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SUCCESSCOPY))));
807}
808
809TEST(ExecutionTraceGenTest, RdSize)
810{
811 TestTraceContainer trace;
812 ExecutionTraceBuilder builder;
813 const auto instr = InstructionBuilder(WireOpCode::RETURNDATASIZE)
814 .operand<uint16_t>(1234) // Dst Offset
815 .build();
816 // clang-format off
817 ExecutionEvent ex_event = {
818 .wire_instruction = instr,
819 .output = { MemoryValue::from_tag(ValueTag::U32, 100) }, // RdSize output
820 .addressing_event = {
821 .resolution_info = { { .resolved_operand = MemoryValue::from<uint16_t>(1234) } }
822 },
823
824 .after_context_event = { .last_child_rd_size = 100 }
825 };
826 // clang-format on
827
828 builder.process({ ex_event }, trace);
829 EXPECT_THAT(trace.as_rows(),
830 ElementsAre(
831 // First row is empty
832 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
833 // Second row is the rd_size
834 AllOf(ROW_FIELD_EQ(execution_sel, 1),
835 ROW_FIELD_EQ(execution_sel_execute_returndata_size, 1),
836 ROW_FIELD_EQ(execution_rop_0_, 1234), // Dst Offset
837 ROW_FIELD_EQ(execution_register_0_, 100), // RdSize output
838 ROW_FIELD_EQ(execution_mem_tag_reg_0_, /*U32=*/4), // Memory tag for dst
839 ROW_FIELD_EQ(execution_last_child_returndata_size, 100), // last_child_returndata_size = 100
840 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_RETURNDATASIZE))));
841}
842
843TEST(ExecutionTraceGenTest, SLoad)
844{
845 TestTraceContainer trace;
846 ExecutionTraceBuilder builder;
847
848 uint16_t slot_offset = 1234;
849 uint16_t contract_address_offset = 2345;
850 uint16_t dst_offset = 4567;
851
852 FF slot = 42;
853 FF contract_address = 0xdeadbeef;
854 FF dst_value = 27;
855
856 const auto instr = InstructionBuilder(WireOpCode::SLOAD)
857 .operand<uint16_t>(slot_offset)
858 .operand<uint16_t>(contract_address_offset)
859 .operand<uint16_t>(dst_offset)
860 .build();
861
862 ExecutionEvent ex_event = {
863 .wire_instruction = instr,
864 .inputs = { MemoryValue::from<FF>(slot), MemoryValue::from<FF>(contract_address) },
865 .output = MemoryValue::from<FF>(dst_value),
866 .addressing_event = { .resolution_info = { { .resolved_operand = MemoryValue::from<uint16_t>(slot_offset) },
867 { .resolved_operand =
868 MemoryValue::from<uint16_t>(contract_address_offset) },
869 { .resolved_operand = MemoryValue::from<uint16_t>(dst_offset) } } },
870 };
871
872 builder.process({ ex_event }, trace);
873 EXPECT_THAT(trace.as_rows(),
874 ElementsAre(
875 // First row is empty
876 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
877 // Second row is the sload
878 AllOf(ROW_FIELD_EQ(execution_sel, 1),
879 ROW_FIELD_EQ(execution_sel_execute_sload, 1),
880 ROW_FIELD_EQ(execution_rop_0_, slot_offset),
881 ROW_FIELD_EQ(execution_rop_1_, contract_address_offset),
882 ROW_FIELD_EQ(execution_rop_2_, dst_offset),
883 ROW_FIELD_EQ(execution_register_0_, slot),
884 ROW_FIELD_EQ(execution_register_1_, contract_address),
885 ROW_FIELD_EQ(execution_register_2_, dst_value),
886 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for slot
887 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF), // Memory tag for contract_address
888 ROW_FIELD_EQ(execution_mem_tag_reg_2_, MEM_TAG_FF), // Memory tag for dst
889 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SLOAD))));
890}
891
892TEST(ExecutionTraceGenTest, SStore)
893{
894 TestTraceContainer trace;
895 ExecutionTraceBuilder builder;
896
897 uint16_t slot_offset = 1234;
898 uint16_t value_offset = 4567;
899
900 FF slot = 42;
901 FF value = 27;
902
903 const auto instr =
904 InstructionBuilder(WireOpCode::SSTORE).operand<uint16_t>(value_offset).operand<uint16_t>(slot_offset).build();
905
906 ExecutionEvent ex_event = {
907 .wire_instruction = instr,
908 .inputs = { MemoryValue::from<FF>(value), MemoryValue::from<FF>(slot) },
909 .addressing_event = {
910 .resolution_info = {
911 { .resolved_operand = MemoryValue::from<uint16_t>(value_offset) },
912 { .resolved_operand = MemoryValue::from<uint16_t>(slot_offset) },
913 } },
914 .before_context_event = {
915 .tree_states = {
916 .public_data_tree = {
917 .counter = 5,
918 },
919 }
920 },
921 .gas_event = {
922 .dynamic_gas_factor = { .da_gas = 1 },
923 },
924 };
925
926 builder.process({ ex_event }, trace);
927 EXPECT_THAT(trace.as_rows(),
928 ElementsAre(
929 // First row is empty
930 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
931 // Second row is the sstore
932 AllOf(ROW_FIELD_EQ(execution_sel, 1),
933 ROW_FIELD_EQ(execution_sel_execute_sstore, 1),
934 ROW_FIELD_EQ(execution_sel_gas_sstore, 1),
935 ROW_FIELD_EQ(execution_rop_0_, value_offset),
936 ROW_FIELD_EQ(execution_rop_1_, slot_offset),
937 ROW_FIELD_EQ(execution_register_0_, value),
938 ROW_FIELD_EQ(execution_register_1_, slot),
939 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for value
940 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF), // Memory tag for slot
941 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SSTORE),
942 ROW_FIELD_EQ(execution_max_data_writes_reached, 0),
943 ROW_FIELD_EQ(execution_remaining_data_writes_inv,
945 ROW_FIELD_EQ(execution_sel_write_public_data, 1))));
946}
947
948TEST(ExecutionTraceGenTest, NoteHashExists)
949{
950 TestTraceContainer trace;
951 ExecutionTraceBuilder builder;
952
953 uint16_t unique_note_hash_offset = 1234;
954 uint16_t leaf_index_offset = 4567;
955 uint16_t dst_offset = 8901;
956
957 FF unique_note_hash = 42;
958 uint64_t leaf_index = 27;
959 uint1_t dst_value = 1;
960
961 const auto instr = InstructionBuilder(WireOpCode::NOTEHASHEXISTS)
962 .operand<uint16_t>(unique_note_hash_offset)
963 .operand<uint16_t>(leaf_index_offset)
964 .operand<uint16_t>(dst_offset)
965 .build();
966
967 ExecutionEvent ex_event = {
968 .wire_instruction = instr,
969 .inputs = { MemoryValue::from<FF>(unique_note_hash), MemoryValue::from<uint64_t>(leaf_index) },
970 .output = MemoryValue::from<uint1_t>(dst_value),
971 .addressing_event = { .resolution_info = { { .resolved_operand =
972 MemoryValue::from<uint16_t>(unique_note_hash_offset) },
973 { .resolved_operand =
974 MemoryValue::from<uint16_t>(leaf_index_offset) },
975 { .resolved_operand = MemoryValue::from<uint16_t>(dst_offset) } } },
976 };
977
978 builder.process({ ex_event }, trace);
979 EXPECT_THAT(
980 trace.as_rows(),
981 ElementsAre(
982 // First row is empty
983 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
984 // Second row is the note_hash_exists
985 AllOf(ROW_FIELD_EQ(execution_sel, 1),
986 ROW_FIELD_EQ(execution_sel_execute_notehash_exists, 1),
987 ROW_FIELD_EQ(execution_rop_0_, unique_note_hash_offset),
988 ROW_FIELD_EQ(execution_rop_1_, leaf_index_offset),
989 ROW_FIELD_EQ(execution_rop_2_, dst_offset),
990 ROW_FIELD_EQ(execution_register_0_, unique_note_hash),
991 ROW_FIELD_EQ(execution_register_1_, leaf_index),
992 ROW_FIELD_EQ(execution_register_2_, FF(dst_value)),
993 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for unique_note_hash
994 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_U64), // Memory tag for leaf_index
995 ROW_FIELD_EQ(execution_mem_tag_reg_2_, MEM_TAG_U1), // Memory tag for dst
996 ROW_FIELD_EQ(execution_note_hash_leaf_in_range, 1),
997 ROW_FIELD_EQ(execution_note_hash_tree_leaf_count, static_cast<uint64_t>(NOTE_HASH_TREE_LEAF_COUNT)),
998 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_NOTEHASH_EXISTS))));
999}
1000
1001TEST(ExecutionTraceGenTest, EmitNoteHash)
1002{
1003 TestTraceContainer trace;
1004 ExecutionTraceBuilder builder;
1005
1006 uint16_t note_hash_offset = 1234;
1007
1008 FF note_hash = 42;
1009 uint32_t prev_num_note_hashes_emitted = MAX_NOTE_HASHES_PER_TX - 1;
1010
1011 const auto instr = InstructionBuilder(WireOpCode::EMITNOTEHASH).operand<uint16_t>(note_hash_offset).build();
1012
1013 ExecutionEvent ex_event = {
1014 .wire_instruction = instr,
1015 .inputs = { MemoryValue::from<FF>(note_hash) },
1016 .addressing_event = {
1017 .resolution_info = { { .resolved_operand =
1018 MemoryValue::from<uint16_t>(note_hash_offset) } } },
1019 .before_context_event = {
1020 .tree_states = {
1021 .note_hash_tree = {
1022 .counter = prev_num_note_hashes_emitted,
1023 },
1024 }
1025 }
1026 };
1027
1028 builder.process({ ex_event }, trace);
1029 EXPECT_THAT(trace.as_rows(),
1030 ElementsAre(
1031 // First row is empty
1032 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1033 // Second row is the emit_note_hash
1034 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1035 ROW_FIELD_EQ(execution_sel_execute_emit_notehash, 1),
1036 ROW_FIELD_EQ(execution_rop_0_, note_hash_offset),
1037 ROW_FIELD_EQ(execution_register_0_, note_hash),
1038 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for note_hash
1039 ROW_FIELD_EQ(execution_remaining_note_hashes_inv,
1040 FF(MAX_NOTE_HASHES_PER_TX - prev_num_note_hashes_emitted).invert()),
1041 ROW_FIELD_EQ(execution_sel_write_note_hash, 1),
1042 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_EMIT_NOTEHASH))));
1043}
1044
1045TEST(ExecutionTraceGenTest, L1ToL2MessageExists)
1046{
1047 TestTraceContainer trace;
1048 ExecutionTraceBuilder builder;
1049
1050 uint16_t msg_hash_offset = 1234;
1051 uint16_t leaf_index_offset = 4567;
1052 uint16_t dst_offset = 8901;
1053
1054 FF msg_hash = 42;
1055 uint64_t leaf_index = 27;
1056 uint1_t dst_value = 1;
1057
1058 const auto instr = InstructionBuilder(WireOpCode::L1TOL2MSGEXISTS)
1059 .operand<uint16_t>(msg_hash_offset)
1060 .operand<uint16_t>(leaf_index_offset)
1061 .operand<uint16_t>(dst_offset)
1062 .build();
1063
1064 ExecutionEvent ex_event = {
1065 .wire_instruction = instr,
1066 .inputs = { MemoryValue::from<FF>(msg_hash), MemoryValue::from<uint64_t>(leaf_index) },
1067 .output = MemoryValue::from<uint1_t>(dst_value),
1068 .addressing_event = { .resolution_info = { { .resolved_operand = MemoryValue::from<uint16_t>(msg_hash_offset) },
1069 { .resolved_operand =
1070 MemoryValue::from<uint16_t>(leaf_index_offset) },
1071 { .resolved_operand = MemoryValue::from<uint16_t>(dst_offset) } } },
1072 };
1073
1074 builder.process({ ex_event }, trace);
1075 EXPECT_THAT(trace.as_rows(),
1076 ElementsAre(
1077 // First row is empty
1078 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1079 // Second row is the l1_to_l2_msg_exists
1080 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1081 ROW_FIELD_EQ(execution_sel_execute_l1_to_l2_message_exists, 1),
1082 ROW_FIELD_EQ(execution_rop_0_, msg_hash_offset),
1083 ROW_FIELD_EQ(execution_rop_1_, leaf_index_offset),
1084 ROW_FIELD_EQ(execution_rop_2_, dst_offset),
1085 ROW_FIELD_EQ(execution_register_0_, msg_hash),
1086 ROW_FIELD_EQ(execution_register_1_, leaf_index),
1087 ROW_FIELD_EQ(execution_register_2_, FF(dst_value)),
1088 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for msg_hash
1089 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_U64), // Memory tag for leaf_index
1090 ROW_FIELD_EQ(execution_mem_tag_reg_2_, MEM_TAG_U1), // Memory tag for dst
1091 ROW_FIELD_EQ(execution_l1_to_l2_msg_leaf_in_range, 1),
1092 ROW_FIELD_EQ(execution_l1_to_l2_msg_tree_leaf_count,
1093 static_cast<uint64_t>(L1_TO_L2_MSG_TREE_LEAF_COUNT)),
1094 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_L1_TO_L2_MESSAGE_EXISTS))));
1095}
1096
1097TEST(ExecutionTraceGenTest, NullifierExists)
1098{
1099 TestTraceContainer trace;
1100 ExecutionTraceBuilder builder;
1101 // constants
1102 uint16_t nullifier_offset = 100;
1103 uint16_t exists_offset = 300;
1104 FF siloed_nullifier = 0x123456;
1105 bool exists = true;
1106
1107 const auto instr = InstructionBuilder(WireOpCode::NULLIFIEREXISTS)
1108 .operand<uint16_t>(nullifier_offset)
1109 .operand<uint16_t>(exists_offset)
1110 .build();
1111 ExecutionEvent ex_event = { .wire_instruction = instr,
1112 .inputs = { MemoryValue::from_tag(ValueTag::FF, siloed_nullifier) },
1113 .output = { MemoryValue::from_tag(ValueTag::U1, exists ? 1 : 0) }, // exists = true
1114 .addressing_event = {
1115 .resolution_info = {
1116 { .resolved_operand = MemoryValue::from<FF>(siloed_nullifier) },
1117 { .resolved_operand = MemoryValue::from<uint16_t>(exists_offset) } } } };
1118
1119 builder.process({ ex_event }, trace);
1120 EXPECT_THAT(trace.as_rows(),
1121 ElementsAre(
1122 // First row is empty
1123 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1124 // Second row is the nullifier_exists
1125 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1126 ROW_FIELD_EQ(execution_sel_execute_nullifier_exists, 1),
1127 ROW_FIELD_EQ(execution_rop_0_, siloed_nullifier),
1128 ROW_FIELD_EQ(execution_rop_1_, exists_offset),
1129 ROW_FIELD_EQ(execution_register_0_, siloed_nullifier),
1130 ROW_FIELD_EQ(execution_register_1_, exists ? 1 : 0),
1131 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF),
1132 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_U1),
1133 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_NULLIFIER_EXISTS))));
1134}
1135
1136TEST(ExecutionTraceGenTest, EmitNullifier)
1137{
1138 TestTraceContainer trace;
1139 ExecutionTraceBuilder builder;
1140
1141 uint16_t nullifier_offset = 100;
1142 FF nullifier = 0x123456;
1143 uint32_t prev_num_nullifiers_emitted = MAX_NULLIFIERS_PER_TX - 1;
1144
1145 const auto instr = InstructionBuilder(WireOpCode::EMITNULLIFIER).operand<uint16_t>(nullifier_offset).build();
1146
1147 ExecutionEvent ex_event = {
1148 .wire_instruction = instr,
1149 .inputs = { MemoryValue::from_tag(ValueTag::FF, nullifier) },
1150 .addressing_event = {
1151 .resolution_info = { { .resolved_operand = MemoryValue::from<FF>(nullifier) } } },
1152 .before_context_event = {
1153 .tree_states = {
1154 .nullifier_tree = {
1155 .counter = prev_num_nullifiers_emitted,
1156 },
1157 }
1158 }
1159 };
1160
1161 builder.process({ ex_event }, trace);
1162 EXPECT_THAT(trace.as_rows(),
1163 ElementsAre(
1164 // First row is empty
1165 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1166 // Second row is the emit_nullifier
1167 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1168 ROW_FIELD_EQ(execution_sel_execute_emit_nullifier, 1),
1169 ROW_FIELD_EQ(execution_rop_0_, nullifier),
1170 ROW_FIELD_EQ(execution_register_0_, nullifier),
1171 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF),
1172 ROW_FIELD_EQ(execution_remaining_nullifiers_inv,
1173 FF(MAX_NULLIFIERS_PER_TX - prev_num_nullifiers_emitted).invert()),
1174 ROW_FIELD_EQ(execution_sel_write_nullifier, 1),
1175 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_EMIT_NULLIFIER))));
1176}
1177
1178TEST(ExecutionTraceGenTest, SendL2ToL1Msg)
1179{
1180 TestTraceContainer trace;
1181 ExecutionTraceBuilder builder;
1182
1183 uint16_t recipient_offset = 100;
1184 uint16_t content_offset = 101;
1185 FF recipient = 0x123456;
1186 FF content = 0xdeadbeef;
1187 uint32_t prev_num_l2_to_l1_msgs = MAX_L2_TO_L1_MSGS_PER_TX - 1;
1188
1189 const auto instr = InstructionBuilder(WireOpCode::SENDL2TOL1MSG)
1190 .operand<uint16_t>(recipient_offset)
1191 .operand<uint16_t>(content_offset)
1192 .build();
1193
1194 ExecutionEvent ex_event = { .wire_instruction = instr,
1195 .inputs = { MemoryValue::from_tag(ValueTag::FF, recipient),
1196 MemoryValue::from_tag(ValueTag::FF, content) },
1197 .addressing_event = { .resolution_info = { { .resolved_operand =
1198 MemoryValue::from<FF>(recipient) },
1199 { .resolved_operand =
1200 MemoryValue::from<FF>(content) } } },
1201 .before_context_event = {
1202 .numL2ToL1Messages = prev_num_l2_to_l1_msgs,
1203 } };
1204
1205 builder.process({ ex_event }, trace);
1206 EXPECT_THAT(
1207 trace.as_rows(),
1208 ElementsAre(
1209 // First row is empty
1210 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1211 // Second row is the send_l2_to_l1_msg
1212 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1213 ROW_FIELD_EQ(execution_sel_execute_send_l2_to_l1_msg, 1),
1214 ROW_FIELD_EQ(execution_register_0_, recipient),
1215 ROW_FIELD_EQ(execution_register_1_, content),
1216 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF),
1217 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF),
1218 ROW_FIELD_EQ(execution_remaining_l2_to_l1_msgs_inv,
1219 FF(MAX_L2_TO_L1_MSGS_PER_TX - prev_num_l2_to_l1_msgs).invert()),
1220 ROW_FIELD_EQ(execution_sel_write_l2_to_l1_msg, 1),
1221 ROW_FIELD_EQ(execution_public_inputs_index,
1223 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SENDL2TOL1MSG))));
1224}
1225
1226} // namespace
1227} // namespace bb::avm2::tracegen
TEST(acir_formal_proofs, uint_terms_add)
Tests 128-bit unsigned addition Verifies that the ACIR implementation of addition is correct Executio...
bb::field< bb::Bn254FrParams > FF
Definition field.cpp:24
#define MEM_TAG_U1
#define AVM_EXEC_OP_ID_SUCCESSCOPY
#define AVM_EXEC_OP_ID_NULLIFIER_EXISTS
#define AVM_EXEC_OP_ID_SSTORE
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_L2_TO_L1_MSGS_ROW_IDX
#define AVM_EXEC_OP_ID_EMIT_NULLIFIER
#define AVM_EXEC_OP_ID_NOTEHASH_EXISTS
#define AVM_EXEC_OP_ID_SLOAD
#define NOTE_HASH_TREE_LEAF_COUNT
#define AVM_EXEC_OP_ID_JUMP
#define L1_TO_L2_MSG_TREE_LEAF_COUNT
#define AVM_EXEC_OP_ID_EMIT_NOTEHASH
#define MAX_L2_TO_L1_MSGS_PER_TX
#define MAX_NOTE_HASHES_PER_TX
#define AVM_EXEC_OP_ID_MOV
#define MAX_NULLIFIERS_PER_TX
#define AVM_EXEC_OP_ID_SENDL2TOL1MSG
#define AVM_EXEC_OP_ID_RETURNDATASIZE
#define AVM_EXEC_OP_ID_JUMPI
#define AVM_EXEC_OP_ID_L1_TO_L2_MESSAGE_EXISTS
#define MEM_TAG_FF
#define MEM_TAG_U64
#define MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
static TaggedValue from_tag(ValueTag tag, FF value)
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
Process the ALU events and populate the ALU relevant columns in the trace.
std::vector< AvmFullRowConstRef > as_rows() const
AluTraceBuilder builder
Definition alu.test.cpp:124
TestTraceContainer trace
Instruction instruction
const auto call_instr
#define ROW_FIELD_EQ(field_name, expression)
Definition macros.hpp:7
AvmFlavorSettings::FF FF
Definition field.hpp:10
const std::unordered_map< ExecutionOpCode, ExecInstructionSpec > & get_exec_instruction_spec()
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t context_id