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