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