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