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 dst_offset = 4567;
850
851 FF slot = 42;
852 FF dst_value = 27;
853
854 const auto instr =
855 InstructionBuilder(WireOpCode::SLOAD).operand<uint16_t>(slot_offset).operand<uint16_t>(dst_offset).build();
856
857 ExecutionEvent ex_event = {
858 .wire_instruction = instr,
859 .inputs = { MemoryValue::from<FF>(slot) },
860 .output = MemoryValue::from<FF>(dst_value),
861 .addressing_event = { .resolution_info = { { .resolved_operand = MemoryValue::from<uint16_t>(slot_offset) },
862 { .resolved_operand = MemoryValue::from<uint16_t>(dst_offset) } } },
863 };
864
865 builder.process({ ex_event }, trace);
866 EXPECT_THAT(trace.as_rows(),
867 ElementsAre(
868 // First row is empty
869 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
870 // Second row is the sload
871 AllOf(ROW_FIELD_EQ(execution_sel, 1),
872 ROW_FIELD_EQ(execution_sel_execute_sload, 1),
873 ROW_FIELD_EQ(execution_rop_0_, slot_offset),
874 ROW_FIELD_EQ(execution_rop_1_, dst_offset),
875 ROW_FIELD_EQ(execution_register_0_, slot),
876 ROW_FIELD_EQ(execution_register_1_, dst_value),
877 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for slot
878 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF), // Memory tag for dst
879 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SLOAD))));
880}
881
882TEST(ExecutionTraceGenTest, SStore)
883{
884 TestTraceContainer trace;
885 ExecutionTraceBuilder builder;
886
887 uint16_t slot_offset = 1234;
888 uint16_t value_offset = 4567;
889
890 FF slot = 42;
891 FF value = 27;
892
893 const auto instr =
894 InstructionBuilder(WireOpCode::SSTORE).operand<uint16_t>(value_offset).operand<uint16_t>(slot_offset).build();
895
896 ExecutionEvent ex_event = {
897 .wire_instruction = instr,
898 .inputs = { MemoryValue::from<FF>(value), MemoryValue::from<FF>(slot) },
899 .addressing_event = {
900 .resolution_info = {
901 { .resolved_operand = MemoryValue::from<uint16_t>(value_offset) },
902 { .resolved_operand = MemoryValue::from<uint16_t>(slot_offset) },
903 } },
904 .before_context_event = {
905 .tree_states = {
906 .public_data_tree = {
907 .counter = 5,
908 },
909 }
910 },
911 .gas_event = {
912 .dynamic_gas_factor = { .da_gas = 1 },
913 },
914 };
915
916 builder.process({ ex_event }, trace);
917 EXPECT_THAT(trace.as_rows(),
918 ElementsAre(
919 // First row is empty
920 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
921 // Second row is the sstore
922 AllOf(ROW_FIELD_EQ(execution_sel, 1),
923 ROW_FIELD_EQ(execution_sel_execute_sstore, 1),
924 ROW_FIELD_EQ(execution_sel_gas_sstore, 1),
925 ROW_FIELD_EQ(execution_rop_0_, value_offset),
926 ROW_FIELD_EQ(execution_rop_1_, slot_offset),
927 ROW_FIELD_EQ(execution_register_0_, value),
928 ROW_FIELD_EQ(execution_register_1_, slot),
929 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for value
930 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF), // Memory tag for slot
931 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SSTORE),
932 ROW_FIELD_EQ(execution_max_data_writes_reached, 0),
933 ROW_FIELD_EQ(execution_remaining_data_writes_inv,
935 ROW_FIELD_EQ(execution_sel_write_public_data, 1))));
936}
937
938TEST(ExecutionTraceGenTest, NoteHashExists)
939{
940 TestTraceContainer trace;
941 ExecutionTraceBuilder builder;
942
943 uint16_t unique_note_hash_offset = 1234;
944 uint16_t leaf_index_offset = 4567;
945 uint16_t dst_offset = 8901;
946
947 FF unique_note_hash = 42;
948 uint64_t leaf_index = 27;
949 uint1_t dst_value = 1;
950
951 const auto instr = InstructionBuilder(WireOpCode::NOTEHASHEXISTS)
952 .operand<uint16_t>(unique_note_hash_offset)
953 .operand<uint16_t>(leaf_index_offset)
954 .operand<uint16_t>(dst_offset)
955 .build();
956
957 ExecutionEvent ex_event = {
958 .wire_instruction = instr,
959 .inputs = { MemoryValue::from<FF>(unique_note_hash), MemoryValue::from<uint64_t>(leaf_index) },
960 .output = MemoryValue::from<uint1_t>(dst_value),
961 .addressing_event = { .resolution_info = { { .resolved_operand =
962 MemoryValue::from<uint16_t>(unique_note_hash_offset) },
963 { .resolved_operand =
964 MemoryValue::from<uint16_t>(leaf_index_offset) },
965 { .resolved_operand = MemoryValue::from<uint16_t>(dst_offset) } } },
966 };
967
968 builder.process({ ex_event }, trace);
969 EXPECT_THAT(
970 trace.as_rows(),
971 ElementsAre(
972 // First row is empty
973 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
974 // Second row is the note_hash_exists
975 AllOf(ROW_FIELD_EQ(execution_sel, 1),
976 ROW_FIELD_EQ(execution_sel_execute_notehash_exists, 1),
977 ROW_FIELD_EQ(execution_rop_0_, unique_note_hash_offset),
978 ROW_FIELD_EQ(execution_rop_1_, leaf_index_offset),
979 ROW_FIELD_EQ(execution_rop_2_, dst_offset),
980 ROW_FIELD_EQ(execution_register_0_, unique_note_hash),
981 ROW_FIELD_EQ(execution_register_1_, leaf_index),
982 ROW_FIELD_EQ(execution_register_2_, FF(dst_value)),
983 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for unique_note_hash
984 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_U64), // Memory tag for leaf_index
985 ROW_FIELD_EQ(execution_mem_tag_reg_2_, MEM_TAG_U1), // Memory tag for dst
986 ROW_FIELD_EQ(execution_note_hash_leaf_in_range, 1),
987 ROW_FIELD_EQ(execution_note_hash_tree_leaf_count, static_cast<uint64_t>(NOTE_HASH_TREE_LEAF_COUNT)),
988 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_NOTEHASH_EXISTS))));
989}
990
991TEST(ExecutionTraceGenTest, EmitNoteHash)
992{
993 TestTraceContainer trace;
994 ExecutionTraceBuilder builder;
995
996 uint16_t note_hash_offset = 1234;
997
998 FF note_hash = 42;
999 uint32_t prev_num_note_hashes_emitted = MAX_NOTE_HASHES_PER_TX - 1;
1000
1001 const auto instr = InstructionBuilder(WireOpCode::EMITNOTEHASH).operand<uint16_t>(note_hash_offset).build();
1002
1003 ExecutionEvent ex_event = {
1004 .wire_instruction = instr,
1005 .inputs = { MemoryValue::from<FF>(note_hash) },
1006 .addressing_event = {
1007 .resolution_info = { { .resolved_operand =
1008 MemoryValue::from<uint16_t>(note_hash_offset) } } },
1009 .before_context_event = {
1010 .tree_states = {
1011 .note_hash_tree = {
1012 .counter = prev_num_note_hashes_emitted,
1013 },
1014 }
1015 }
1016 };
1017
1018 builder.process({ ex_event }, trace);
1019 EXPECT_THAT(trace.as_rows(),
1020 ElementsAre(
1021 // First row is empty
1022 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1023 // Second row is the emit_note_hash
1024 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1025 ROW_FIELD_EQ(execution_sel_execute_emit_notehash, 1),
1026 ROW_FIELD_EQ(execution_rop_0_, note_hash_offset),
1027 ROW_FIELD_EQ(execution_register_0_, note_hash),
1028 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for note_hash
1029 ROW_FIELD_EQ(execution_remaining_note_hashes_inv,
1030 FF(MAX_NOTE_HASHES_PER_TX - prev_num_note_hashes_emitted).invert()),
1031 ROW_FIELD_EQ(execution_sel_write_note_hash, 1),
1032 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_EMIT_NOTEHASH))));
1033}
1034
1035TEST(ExecutionTraceGenTest, L1ToL2MessageExists)
1036{
1037 TestTraceContainer trace;
1038 ExecutionTraceBuilder builder;
1039
1040 uint16_t msg_hash_offset = 1234;
1041 uint16_t leaf_index_offset = 4567;
1042 uint16_t dst_offset = 8901;
1043
1044 FF msg_hash = 42;
1045 uint64_t leaf_index = 27;
1046 uint1_t dst_value = 1;
1047
1048 const auto instr = InstructionBuilder(WireOpCode::L1TOL2MSGEXISTS)
1049 .operand<uint16_t>(msg_hash_offset)
1050 .operand<uint16_t>(leaf_index_offset)
1051 .operand<uint16_t>(dst_offset)
1052 .build();
1053
1054 ExecutionEvent ex_event = {
1055 .wire_instruction = instr,
1056 .inputs = { MemoryValue::from<FF>(msg_hash), MemoryValue::from<uint64_t>(leaf_index) },
1057 .output = MemoryValue::from<uint1_t>(dst_value),
1058 .addressing_event = { .resolution_info = { { .resolved_operand = MemoryValue::from<uint16_t>(msg_hash_offset) },
1059 { .resolved_operand =
1060 MemoryValue::from<uint16_t>(leaf_index_offset) },
1061 { .resolved_operand = MemoryValue::from<uint16_t>(dst_offset) } } },
1062 };
1063
1064 builder.process({ ex_event }, trace);
1065 EXPECT_THAT(trace.as_rows(),
1066 ElementsAre(
1067 // First row is empty
1068 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1069 // Second row is the l1_to_l2_msg_exists
1070 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1071 ROW_FIELD_EQ(execution_sel_execute_l1_to_l2_message_exists, 1),
1072 ROW_FIELD_EQ(execution_rop_0_, msg_hash_offset),
1073 ROW_FIELD_EQ(execution_rop_1_, leaf_index_offset),
1074 ROW_FIELD_EQ(execution_rop_2_, dst_offset),
1075 ROW_FIELD_EQ(execution_register_0_, msg_hash),
1076 ROW_FIELD_EQ(execution_register_1_, leaf_index),
1077 ROW_FIELD_EQ(execution_register_2_, FF(dst_value)),
1078 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF), // Memory tag for msg_hash
1079 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_U64), // Memory tag for leaf_index
1080 ROW_FIELD_EQ(execution_mem_tag_reg_2_, MEM_TAG_U1), // Memory tag for dst
1081 ROW_FIELD_EQ(execution_l1_to_l2_msg_leaf_in_range, 1),
1082 ROW_FIELD_EQ(execution_l1_to_l2_msg_tree_leaf_count,
1083 static_cast<uint64_t>(L1_TO_L2_MSG_TREE_LEAF_COUNT)),
1084 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_L1_TO_L2_MESSAGE_EXISTS))));
1085}
1086
1087TEST(ExecutionTraceGenTest, NullifierExists)
1088{
1089 TestTraceContainer trace;
1090 ExecutionTraceBuilder builder;
1091 // constants
1092 uint16_t nullifier_offset = 100;
1093 uint16_t address_offset = 200;
1094 uint16_t exists_offset = 300;
1095 FF nullifier = 0x123456;
1096 FF address = 0xdeadbeef;
1097 bool exists = true;
1098
1099 const auto instr = InstructionBuilder(WireOpCode::NULLIFIEREXISTS)
1100 .operand<uint16_t>(nullifier_offset)
1101 .operand<uint16_t>(address_offset)
1102 .operand<uint16_t>(exists_offset)
1103 .build();
1104 ExecutionEvent ex_event = {
1105 .wire_instruction = instr,
1107 .output = { MemoryValue::from_tag(ValueTag::U1, exists ? 1 : 0) }, // exists = true
1108 .addressing_event = { .resolution_info = { { .resolved_operand = MemoryValue::from<FF>(nullifier) },
1109 { .resolved_operand = MemoryValue::from<FF>(address) },
1110 { .resolved_operand =
1111 MemoryValue::from<uint16_t>(exists_offset) } } }
1112 };
1113
1114 builder.process({ ex_event }, trace);
1115 EXPECT_THAT(trace.as_rows(),
1116 ElementsAre(
1117 // First row is empty
1118 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1119 // Second row is the nullifier_exists
1120 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1121 ROW_FIELD_EQ(execution_sel_execute_nullifier_exists, 1),
1122 ROW_FIELD_EQ(execution_rop_0_, nullifier),
1123 ROW_FIELD_EQ(execution_rop_1_, address),
1124 ROW_FIELD_EQ(execution_rop_2_, exists_offset),
1125 ROW_FIELD_EQ(execution_register_0_, nullifier),
1126 ROW_FIELD_EQ(execution_register_1_, address),
1127 ROW_FIELD_EQ(execution_register_2_, exists ? 1 : 0),
1128 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF),
1129 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF),
1130 ROW_FIELD_EQ(execution_mem_tag_reg_2_, MEM_TAG_U1),
1131 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_NULLIFIER_EXISTS))));
1132}
1133
1134TEST(ExecutionTraceGenTest, EmitNullifier)
1135{
1136 TestTraceContainer trace;
1137 ExecutionTraceBuilder builder;
1138
1139 uint16_t nullifier_offset = 100;
1140 FF nullifier = 0x123456;
1141 uint32_t prev_num_nullifiers_emitted = MAX_NULLIFIERS_PER_TX - 1;
1142
1143 const auto instr = InstructionBuilder(WireOpCode::EMITNULLIFIER).operand<uint16_t>(nullifier_offset).build();
1144
1145 ExecutionEvent ex_event = {
1146 .wire_instruction = instr,
1147 .inputs = { MemoryValue::from_tag(ValueTag::FF, nullifier) },
1148 .addressing_event = {
1149 .resolution_info = { { .resolved_operand = MemoryValue::from<FF>(nullifier) } } },
1150 .before_context_event = {
1151 .tree_states = {
1152 .nullifier_tree = {
1153 .counter = prev_num_nullifiers_emitted,
1154 },
1155 }
1156 }
1157 };
1158
1159 builder.process({ ex_event }, trace);
1160 EXPECT_THAT(trace.as_rows(),
1161 ElementsAre(
1162 // First row is empty
1163 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1164 // Second row is the emit_nullifier
1165 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1166 ROW_FIELD_EQ(execution_sel_execute_emit_nullifier, 1),
1167 ROW_FIELD_EQ(execution_rop_0_, nullifier),
1168 ROW_FIELD_EQ(execution_register_0_, nullifier),
1169 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF),
1170 ROW_FIELD_EQ(execution_remaining_nullifiers_inv,
1171 FF(MAX_NULLIFIERS_PER_TX - prev_num_nullifiers_emitted).invert()),
1172 ROW_FIELD_EQ(execution_sel_write_nullifier, 1),
1173 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_EMIT_NULLIFIER))));
1174}
1175
1176TEST(ExecutionTraceGenTest, SendL2ToL1Msg)
1177{
1178 TestTraceContainer trace;
1179 ExecutionTraceBuilder builder;
1180
1181 uint16_t recipient_offset = 100;
1182 uint16_t content_offset = 101;
1183 FF recipient = 0x123456;
1184 FF content = 0xdeadbeef;
1185 uint32_t prev_num_l2_to_l1_msgs = MAX_L2_TO_L1_MSGS_PER_TX - 1;
1186
1187 const auto instr = InstructionBuilder(WireOpCode::SENDL2TOL1MSG)
1188 .operand<uint16_t>(recipient_offset)
1189 .operand<uint16_t>(content_offset)
1190 .build();
1191
1192 ExecutionEvent ex_event = { .wire_instruction = instr,
1193 .inputs = { MemoryValue::from_tag(ValueTag::FF, recipient),
1194 MemoryValue::from_tag(ValueTag::FF, content) },
1195 .addressing_event = { .resolution_info = { { .resolved_operand =
1196 MemoryValue::from<FF>(recipient) },
1197 { .resolved_operand =
1198 MemoryValue::from<FF>(content) } } },
1199 .before_context_event = {
1200 .numL2ToL1Messages = prev_num_l2_to_l1_msgs,
1201 } };
1202
1203 builder.process({ ex_event }, trace);
1204 EXPECT_THAT(
1205 trace.as_rows(),
1206 ElementsAre(
1207 // First row is empty
1208 AllOf(ROW_FIELD_EQ(execution_sel, 0)),
1209 // Second row is the send_l2_to_l1_msg
1210 AllOf(ROW_FIELD_EQ(execution_sel, 1),
1211 ROW_FIELD_EQ(execution_sel_execute_send_l2_to_l1_msg, 1),
1212 ROW_FIELD_EQ(execution_register_0_, recipient),
1213 ROW_FIELD_EQ(execution_register_1_, content),
1214 ROW_FIELD_EQ(execution_mem_tag_reg_0_, MEM_TAG_FF),
1215 ROW_FIELD_EQ(execution_mem_tag_reg_1_, MEM_TAG_FF),
1216 ROW_FIELD_EQ(execution_remaining_l2_to_l1_msgs_inv,
1217 FF(MAX_L2_TO_L1_MSGS_PER_TX - prev_num_l2_to_l1_msgs).invert()),
1218 ROW_FIELD_EQ(execution_sel_write_l2_to_l1_msg, 1),
1219 ROW_FIELD_EQ(execution_public_inputs_index,
1221 ROW_FIELD_EQ(execution_subtrace_operation_id, AVM_EXEC_OP_ID_SENDL2TOL1MSG))));
1222}
1223
1224} // namespace
1225} // 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:22
#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
const std::unordered_map< ExecutionOpCode, ExecInstructionSpec > & get_exec_instruction_spec()
AvmFlavorSettings::FF FF
Definition field.hpp:10
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t context_id