Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
instr_fetching.test.cpp
Go to the documentation of this file.
1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
3
4#include <cstdint>
5#include <memory>
6#include <vector>
7
21
22namespace bb::avm2::constraining {
23namespace {
24
25using tracegen::BytecodeTraceBuilder;
26using tracegen::PrecomputedTraceBuilder;
27using tracegen::RangeCheckTraceBuilder;
28using tracegen::TestTraceContainer;
29
31using C = Column;
32
33using instr_fetching = instr_fetching<FF>;
34
35using simulation::BytecodeDecompositionEvent;
37using simulation::Instruction;
38using simulation::InstructionFetchingEvent;
40using simulation::RangeCheckEvent;
41
42TEST(InstrFetchingConstrainingTest, EmptyRow)
43{
44 check_relation<instr_fetching>(testing::empty_trace());
45}
46
47// Basic positive test with a hardcoded bytecode for ADD_8
48TEST(InstrFetchingConstrainingTest, Add8WithTraceGen)
49{
50 TestTraceContainer trace;
51 BytecodeTraceBuilder builder;
52 PrecomputedTraceBuilder precomputed_builder;
53
54 Instruction add_8_instruction = {
55 .opcode = WireOpCode::ADD_8,
56 .addressing_mode = 3,
57 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
58 };
59
60 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
61
62 builder.process_instruction_fetching({ { .bytecode_id = 1,
63 .pc = 0,
64 .instruction = add_8_instruction,
66 trace);
67 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
68
69 EXPECT_EQ(trace.get_num_rows(), 2);
70 check_relation<instr_fetching>(trace);
71}
72
73// Basic positive test with a hardcoded bytecode for ECADD
74// Cover the longest amount of operands.
75TEST(InstrFetchingConstrainingTest, EcaddWithTraceGen)
76{
77 TestTraceContainer trace;
78 BytecodeTraceBuilder builder;
79 PrecomputedTraceBuilder precomputed_builder;
80
81 Instruction ecadd_instruction = {
82 .opcode = WireOpCode::ECADD,
83 .addressing_mode = 0x1f1f,
84 .operands = { Operand::from<uint16_t>(0x1279),
85 Operand::from<uint16_t>(0x127a),
86 Operand::from<uint16_t>(0x127b),
87 Operand::from<uint16_t>(0x127c),
88 Operand::from<uint16_t>(0x127d),
89 Operand::from<uint16_t>(0x127e),
90 Operand::from<uint16_t>(0x127f) },
91 };
92
93 std::vector<uint8_t> bytecode = ecadd_instruction.serialize();
94 builder.process_instruction_fetching({ { .bytecode_id = 1,
95 .pc = 0,
96 .instruction = ecadd_instruction,
98 trace);
99 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
100
101 EXPECT_EQ(trace.get_num_rows(), 2);
102 check_relation<instr_fetching>(trace);
103}
104
105// Helper routine generating a vector of instruction fetching events for each
106// opcode.
107std::vector<InstructionFetchingEvent> gen_instr_events_each_opcode()
108{
109 std::vector<uint8_t> bytecode;
110 std::vector<Instruction> instructions;
111 constexpr auto num_opcodes = static_cast<size_t>(WireOpCode::LAST_OPCODE_SENTINEL);
112 instructions.reserve(num_opcodes);
114
115 for (size_t i = 0; i < num_opcodes; i++) {
116 pc_positions.at(i) = static_cast<uint32_t>(bytecode.size());
117 const auto instr = testing::random_instruction(static_cast<WireOpCode>(i));
118 instructions.emplace_back(instr);
119 const auto instruction_bytes = instr.serialize();
120 bytecode.insert(bytecode.end(),
121 std::make_move_iterator(instruction_bytes.begin()),
122 std::make_move_iterator(instruction_bytes.end()));
123 }
124
125 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
126 // Always use *bytecode_ptr from now on instead of bytecode as this one was moved.
127
129 instr_events.reserve(num_opcodes);
130 for (size_t i = 0; i < num_opcodes; i++) {
131 instr_events.emplace_back(InstructionFetchingEvent{
132 .bytecode_id = 1, .pc = pc_positions.at(i), .instruction = instructions.at(i), .bytecode = bytecode_ptr });
133 }
134 return instr_events;
135}
136
137// Positive test for each opcode. We assume that decode instruction is working correctly.
138// It works as long as the relations are not constraining the correct range for TAG nor indirect.
139TEST(InstrFetchingConstrainingTest, EachOpcodeWithTraceGen)
140{
141 TestTraceContainer trace;
142 BytecodeTraceBuilder builder;
143 PrecomputedTraceBuilder precomputed_builder;
144
145 builder.process_instruction_fetching(gen_instr_events_each_opcode(), trace);
146 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
147
148 constexpr auto num_opcodes = static_cast<size_t>(WireOpCode::LAST_OPCODE_SENTINEL);
149 EXPECT_EQ(trace.get_num_rows(), num_opcodes + 1);
150 check_relation<instr_fetching>(trace);
151}
152
153// Negative test about decomposition of operands. We mutate correct operand values in the trace.
154// This also covers wrong operands which are not "involved" by the instruction.
155// We perform this for a random instruction for opcodes: REVERT_16, CAST_8, TORADIXBE
156TEST(InstrFetchingConstrainingTest, NegativeWrongOperand)
157{
158 BytecodeTraceBuilder builder;
159 PrecomputedTraceBuilder precomputed_builder;
160
162 std::vector<size_t> sub_relations = {
171 };
172
173 constexpr std::array<C, 8> operand_cols = {
174 C::instr_fetching_addressing_mode,
175 C::instr_fetching_op1,
176 C::instr_fetching_op2,
177 C::instr_fetching_op3,
178 C::instr_fetching_op4,
179 C::instr_fetching_op5,
180 C::instr_fetching_op6,
181 C::instr_fetching_op7,
182 };
183
184 for (const auto& opcode : opcodes) {
185 TestTraceContainer trace;
186 const auto instr = testing::random_instruction(opcode);
187 builder.process_instruction_fetching(
188 { { .bytecode_id = 1,
189 .pc = 0,
190 .instruction = instr,
191 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
192 trace);
193 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
194
195 check_relation<instr_fetching>(trace);
196
197 EXPECT_EQ(trace.get_num_rows(), 2);
198
199 for (size_t i = 0; i < operand_cols.size(); i++) {
200 auto mutated_trace = trace;
201 const FF mutated_operand = trace.get(operand_cols.at(i), 0) + 1; // Mutate to value + 1
202 mutated_trace.set(operand_cols.at(i), 0, mutated_operand);
203 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(mutated_trace, sub_relations.at(i)),
204 instr_fetching::get_subrelation_label(sub_relations.at(i)));
205 }
206 }
207}
208
209// Positive test for interaction with instruction spec table using same events as for the test
210// EachOpcodeWithTraceGen, i.e., one event/row is generated per wire opcode.
211// It works as long as the relations are not constraining the correct range for TAG nor indirect.
212TEST(InstrFetchingConstrainingTest, WireInstructionSpecInteractions)
213{
214 TestTraceContainer trace;
215 BytecodeTraceBuilder bytecode_builder;
216 PrecomputedTraceBuilder precomputed_builder;
217
220 bytecode_builder.process_instruction_fetching(gen_instr_events_each_opcode(), trace);
221 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
222
223 EXPECT_EQ(trace.get_num_rows(), 1 << 8); // 2^8 for selector against wire_instruction_spec
224
225 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(trace);
226 check_relation<instr_fetching>(trace);
227}
228
229std::vector<RangeCheckEvent> gen_range_check_events(const std::vector<InstructionFetchingEvent>& instr_events)
230{
231 std::vector<RangeCheckEvent> range_check_events;
232 range_check_events.reserve(instr_events.size());
233
234 for (const auto& instr_event : instr_events) {
235 range_check_events.emplace_back(RangeCheckEvent{
236 .value =
237 (instr_event.error.has_value() && instr_event.error == InstrDeserializationEventError::PC_OUT_OF_RANGE)
238 ? instr_event.pc - instr_event.bytecode->size()
239 : instr_event.bytecode->size() - instr_event.pc - 1,
240 .num_bits = AVM_PC_SIZE_IN_BITS,
241 });
242 }
243 return range_check_events;
244}
245
246// Positive test for the interaction with bytecode decomposition table.
247// One event/row is generated per wire opcode (same as for test WireInstructionSpecInteractions).
248TEST(InstrFetchingConstrainingTest, BcDecompositionInteractions)
249{
250 TestTraceContainer trace;
251 BytecodeTraceBuilder bytecode_builder;
252 PrecomputedTraceBuilder precomputed_builder;
253
254 const auto instr_fetch_events = gen_instr_events_each_opcode();
255 bytecode_builder.process_instruction_fetching(instr_fetch_events, trace);
256 bytecode_builder.process_decomposition({ {
257 .bytecode_id = instr_fetch_events.at(0).bytecode_id,
258 .bytecode = instr_fetch_events.at(0).bytecode,
259 } },
260 trace);
261 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
262
263 check_interaction<BytecodeTraceBuilder,
266
267 // BC Decomposition trace is the longest here.
268 EXPECT_EQ(trace.get_num_rows(), instr_fetch_events.at(0).bytecode->size() + 1);
269
270 check_relation<instr_fetching>(trace);
271}
272
273void check_all(const std::vector<InstructionFetchingEvent>& instr_events,
274 const std::vector<RangeCheckEvent>& range_check_events,
276{
277 TestTraceContainer trace;
278 BytecodeTraceBuilder bytecode_builder;
279 PrecomputedTraceBuilder precomputed_builder;
280 RangeCheckTraceBuilder range_check_builder;
281
286 bytecode_builder.process_instruction_fetching(instr_events, trace);
287 bytecode_builder.process_decomposition(decomposition_events, trace);
288 range_check_builder.process(range_check_events, trace);
289 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
290
291 check_interaction<BytecodeTraceBuilder,
298
299 EXPECT_EQ(trace.get_num_rows(), 1 << 16); // 2^16 for range checks
300
301 check_relation<instr_fetching>(trace);
302}
303
304void check_without_range_check(const std::vector<InstructionFetchingEvent>& instr_events,
306{
307 TestTraceContainer trace;
308 BytecodeTraceBuilder bytecode_builder;
309 PrecomputedTraceBuilder precomputed_builder;
310
314 bytecode_builder.process_instruction_fetching(instr_events, trace);
315 bytecode_builder.process_decomposition(decomposition_events, trace);
316 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
317
318 check_interaction<BytecodeTraceBuilder,
324
325 EXPECT_EQ(trace.get_num_rows(), 1 << 8); // 2^8 for range checks
326
327 check_relation<instr_fetching>(trace);
328}
329
330// Positive test with 5 five bytecodes and bytecode_id = 0,1,2,3,4
331// Bytecode i is generated by truncating instr_fetch_events to i * 6 instructions.
332// Check relations and all interactions.
333TEST(InstrFetchingConstrainingTest, MultipleBytecodes)
334{
335 const auto instr_fetch_events = gen_instr_events_each_opcode();
336 constexpr size_t num_of_bytecodes = 5;
339
340 for (size_t i = 0; i < num_of_bytecodes; i++) {
341 std::vector<uint8_t> bytecode;
342 const auto num_of_instr = i * 6;
343
344 for (size_t j = 0; j < num_of_instr; j++) {
345 const auto& instr = instr_fetch_events.at(j).instruction;
346 const auto instruction_bytes = instr.serialize();
347 bytecode.insert(bytecode.end(),
348 std::make_move_iterator(instruction_bytes.begin()),
349 std::make_move_iterator(instruction_bytes.end()));
350 }
351
352 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
353
354 for (size_t j = 0; j < num_of_instr; j++) {
355 auto instr_event = instr_fetch_events.at(j);
356 instr_event.bytecode_id = static_cast<BytecodeId>(i);
357 instr_event.bytecode = bytecode_ptr;
358 instr_events.emplace_back(instr_event);
359 }
360
361 decomposition_events.emplace_back(BytecodeDecompositionEvent{
362 .bytecode_id = static_cast<BytecodeId>(i),
363 .bytecode = bytecode_ptr,
364 });
365 }
366
367 check_all(instr_events, gen_range_check_events(instr_events), decomposition_events);
368}
369
370// Positive test with one single instruction with error INSTRUCTION_OUT_OF_RANGE.
371// The bytecode consists into a serialized single instruction with pc = 0 and
372// the bytecode had the last byte removed. This byte corresponds to a full operand.
373TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRange)
374{
375 Instruction add_8_instruction = {
376 .opcode = WireOpCode::ADD_8,
377 .addressing_mode = 3,
378 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
379 };
380
381 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
382 bytecode.pop_back(); // Remove last byte
383 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
384
385 const std::vector<InstructionFetchingEvent> instr_events = {
386 {
387 .bytecode_id = 1,
388 .pc = 0,
389 .bytecode = bytecode_ptr,
390 .error = InstrDeserializationEventError::INSTRUCTION_OUT_OF_RANGE,
391 },
392 };
393
395 {
396 .bytecode_id = 1,
397 .bytecode = bytecode_ptr,
398 },
399 };
400
401 check_without_range_check(instr_events, decomposition_events);
402}
403
404// Positive test with one single instruction (SET_FF) with error INSTRUCTION_OUT_OF_RANGE.
405// The bytecode consists into a serialized single instruction with pc = 0 and
406// the bytecode had the two last bytes removed. The truncated instruction is cut
407// in the middle of an operand.
408TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRangeSplitOperand)
409{
410 Instruction set_ff_instruction = {
411 .opcode = WireOpCode::SET_FF,
412 .addressing_mode = 0x01,
413 .operands = { Operand::from<uint16_t>(0x1279),
414 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::FF)),
415 Operand::from<FF>(FF::modulus_minus_two) },
416 };
417
418 std::vector<uint8_t> bytecode = set_ff_instruction.serialize();
419 bytecode.resize(bytecode.size() - 2); // Remove last two bytes)
420 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
421
422 const std::vector<InstructionFetchingEvent> instr_events = {
423 {
424 .bytecode_id = 1,
425 .pc = 0,
426 .bytecode = bytecode_ptr,
427 .error = InstrDeserializationEventError::INSTRUCTION_OUT_OF_RANGE,
428 },
429 };
430
432 {
433 .bytecode_id = 1,
434 .bytecode = bytecode_ptr,
435 },
436 };
437
438 check_without_range_check(instr_events, decomposition_events);
439}
440
441// Positive test with error case PC_OUT_OF_RANGE. We pass a pc which is out of range.
442TEST(InstrFetchingConstrainingTest, SingleInstructionPcOutOfRange)
443{
444 Instruction add_8_instruction = {
445 .opcode = WireOpCode::SUB_8,
446 .addressing_mode = 3,
447 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
448 };
449
450 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
451 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
452
453 const std::vector<InstructionFetchingEvent> instr_events = {
454 // We first need a first instruction at pc == 0 as the trace assumes this.
455 {
456 .bytecode_id = 1,
457 .pc = 0,
458 .instruction = add_8_instruction,
459 .bytecode = bytecode_ptr,
460 },
461 {
462 .bytecode_id = 1,
463 .pc = static_cast<uint32_t>(bytecode_ptr->size() + 1),
464 .bytecode = bytecode_ptr,
465 .error = InstrDeserializationEventError::PC_OUT_OF_RANGE,
466 },
467 };
468
470 {
471 .bytecode_id = 1,
472 .bytecode = bytecode_ptr,
473 },
474 };
475
476 check_all(instr_events, gen_range_check_events(instr_events), decomposition_events);
477}
478
479// Positive test with error case OPCODE_OUT_OF_RANGE. We generate bytecode of a SET_128 instruction and
480// move the PC to a position corresponding to the beginning of the 128-bit immediate value of SET_128.
481// The immediate value in SET_128 starts with byte 0xFF (which we know is not a valid opcode).
482TEST(InstrFetchingConstrainingTest, SingleInstructionOpcodeOutOfRange)
483{
484 Instruction set_128_instruction = {
485 .opcode = WireOpCode::SET_128,
486 .addressing_mode = 0,
487 .operands = { Operand::from<uint16_t>(0x1234),
488 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::U128)),
489 Operand::from<uint128_t>(static_cast<uint128_t>(0xFF) << 120) },
490 };
491
492 std::vector<uint8_t> bytecode = set_128_instruction.serialize();
493 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
494
495 const std::vector<InstructionFetchingEvent> instr_events = {
496 {
497 .bytecode_id = 1,
498 .pc = 0,
499 .instruction = set_128_instruction,
500 .bytecode = bytecode_ptr,
501 },
502 {
503 .bytecode_id = 1,
504 .pc = 5, // We move pc to the beginning of the 128-bit immediate value.
505 .bytecode = bytecode_ptr,
506 .error = InstrDeserializationEventError::OPCODE_OUT_OF_RANGE,
507 },
508 };
509
511 {
512 .bytecode_id = 1,
513 .bytecode = bytecode_ptr,
514 },
515 };
516
517 check_without_range_check(instr_events, decomposition_events);
518}
519
520// Positive test with one single instruction (SET_16) with error TAG_OUT_OF_RANGE.
521// The bytecode consists into a serialized single instruction with pc = 0.
522// The operand at index 1 is wrongly set to value 12
523TEST(InstrFetchingConstrainingTest, SingleInstructionTagOutOfRange)
524{
525 Instruction set_16_instruction = {
526 .opcode = WireOpCode::SET_16,
527 .addressing_mode = 0,
528 .operands = { Operand::from<uint16_t>(0x1234), Operand::from<uint8_t>(12), Operand::from<uint16_t>(0x5678) },
529 };
530
531 std::vector<uint8_t> bytecode = set_16_instruction.serialize();
532 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
533
534 const std::vector<InstructionFetchingEvent> instr_events = {
535 {
536 .bytecode_id = 1,
537 .pc = 0,
538 .instruction = set_16_instruction,
539 .bytecode = bytecode_ptr,
540 .error = InstrDeserializationEventError::TAG_OUT_OF_RANGE,
541 },
542 };
543
545 {
546 .bytecode_id = 1,
547 .bytecode = bytecode_ptr,
548 },
549 };
550
551 check_without_range_check(instr_events, decomposition_events);
552}
553
554// Negative interaction test with some values not matching the instruction spec table.
555TEST(InstrFetchingConstrainingTest, NegativeWrongWireInstructionSpecInteractions)
556{
557 BytecodeTraceBuilder bytecode_builder;
558 PrecomputedTraceBuilder precomputed_builder;
559
560 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
561 // Test works if the following vector is extended to other opcodes though.
563
564 for (const auto& opcode : opcodes) {
565 TestTraceContainer trace;
566 const auto instr = testing::random_instruction(opcode);
567 bytecode_builder.process_instruction_fetching(
568 { { .bytecode_id = 1,
569 .pc = 0,
570 .instruction = instr,
571 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
572 trace);
575 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
576
577 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(trace);
578
579 ASSERT_EQ(trace.get(C::lookup_instr_fetching_wire_instruction_info_counts, static_cast<uint32_t>(opcode)), 1);
580
581 constexpr std::array<C, 21> mutated_cols = {
582 C::instr_fetching_exec_opcode, C::instr_fetching_instr_size, C::instr_fetching_sel_has_tag,
583 C::instr_fetching_sel_tag_is_op2, C::instr_fetching_sel_op_dc_0, C::instr_fetching_sel_op_dc_1,
584 C::instr_fetching_sel_op_dc_2, C::instr_fetching_sel_op_dc_3, C::instr_fetching_sel_op_dc_4,
585 C::instr_fetching_sel_op_dc_5, C::instr_fetching_sel_op_dc_6, C::instr_fetching_sel_op_dc_7,
586 C::instr_fetching_sel_op_dc_8, C::instr_fetching_sel_op_dc_9, C::instr_fetching_sel_op_dc_10,
587 C::instr_fetching_sel_op_dc_11, C::instr_fetching_sel_op_dc_12, C::instr_fetching_sel_op_dc_13,
588 C::instr_fetching_sel_op_dc_14, C::instr_fetching_sel_op_dc_15, C::instr_fetching_sel_op_dc_16,
589 };
590
591 // Mutate execution opcode
592 for (const auto& col : mutated_cols) {
593 auto mutated_trace = trace;
594 const FF mutated_value = trace.get(col, 1) + 1; // Mutate to value + 1
595 mutated_trace.set(col, 1, mutated_value);
596
598 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(
599 mutated_trace)),
600 "Failed.*LOOKUP_INSTR_FETCHING_WIRE_INSTRUCTION_INFO.*Could not find tuple in destination.");
601 }
602 }
603}
604
605// Negative interaction test with some values not matching the bytecode decomposition table.
606TEST(InstrFetchingConstrainingTest, NegativeWrongBcDecompositionInteractions)
607{
608 TestTraceContainer trace;
609 BytecodeTraceBuilder bytecode_builder;
610
611 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
612 // Test works if the following vector is extended to other opcodes though.
614
615 for (const auto& opcode : opcodes) {
616 TestTraceContainer trace;
617 const auto instr = testing::random_instruction(opcode);
618 auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(instr.serialize());
619 bytecode_builder.process_instruction_fetching({ {
620 .bytecode_id = 1,
621 .pc = 0,
622 .instruction = instr,
623 .bytecode = bytecode_ptr,
624 } },
625 trace);
626 bytecode_builder.process_decomposition({ {
627 .bytecode_id = 1,
628 .bytecode = bytecode_ptr,
629 } },
630 trace);
631
632 auto valid_trace = trace; // Keep original trace before lookup processing
633 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytes_from_bc_dec_settings>(valid_trace);
634
635 constexpr std::array<C, 39> mutated_cols = {
636 C::instr_fetching_pc, C::instr_fetching_bytecode_id, C::instr_fetching_bd0, C::instr_fetching_bd1,
637 C::instr_fetching_bd2, C::instr_fetching_bd3, C::instr_fetching_bd4, C::instr_fetching_bd5,
638 C::instr_fetching_bd6, C::instr_fetching_bd7, C::instr_fetching_bd8, C::instr_fetching_bd9,
639 C::instr_fetching_bd10, C::instr_fetching_bd11, C::instr_fetching_bd12, C::instr_fetching_bd13,
640 C::instr_fetching_bd14, C::instr_fetching_bd15, C::instr_fetching_bd16, C::instr_fetching_bd17,
641 C::instr_fetching_bd18, C::instr_fetching_bd19, C::instr_fetching_bd20, C::instr_fetching_bd21,
642 C::instr_fetching_bd22, C::instr_fetching_bd23, C::instr_fetching_bd24, C::instr_fetching_bd25,
643 C::instr_fetching_bd26, C::instr_fetching_bd27, C::instr_fetching_bd28, C::instr_fetching_bd29,
644 C::instr_fetching_bd30, C::instr_fetching_bd31, C::instr_fetching_bd32, C::instr_fetching_bd33,
645 C::instr_fetching_bd34, C::instr_fetching_bd35, C::instr_fetching_bd36,
646 };
647
648 // Mutate execution opcode
649 for (const auto& col : mutated_cols) {
650 auto mutated_trace = trace;
651 const FF mutated_value = trace.get(col, 1) + 1; // Mutate to value + 1
652 mutated_trace.set(col, 1, mutated_value);
653
655 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytes_from_bc_dec_settings>(
656 mutated_trace)),
657 "Failed.*BYTES_FROM_BC_DEC. Could not find tuple in destination.");
658 }
659 }
660}
661
662// Negative interaction test for #[BYTECODE_SIZE_FROM_BC_DEC] where bytecode_size has the wrong value.
663// We set pc different from zero.
664TEST(InstrFetchingConstrainingTest, NegativeWrongBytecodeSizeBcDecompositionInteractions)
665{
666 TestTraceContainer trace;
667 BytecodeTraceBuilder bytecode_builder;
668 PrecomputedTraceBuilder precomputed_builder;
669
670 const uint32_t pc = 15;
671 std::vector<uint8_t> bytecode(pc, 0x23);
672
673 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
674 // Test works if the following vector is extended to other opcodes though.
676
677 for (const auto& opcode : opcodes) {
678 TestTraceContainer trace;
679
680 const auto instr = testing::random_instruction(opcode);
681 const auto instr_bytecode = instr.serialize();
682 bytecode.insert(bytecode.end(),
683 std::make_move_iterator(instr_bytecode.begin()),
684 std::make_move_iterator(instr_bytecode.end()));
686
687 bytecode_builder.process_instruction_fetching({ {
688 .bytecode_id = 1,
689 .pc = pc,
690 .instruction = instr,
691 .bytecode = bytecode_ptr,
692 } },
693 trace);
694 bytecode_builder.process_decomposition({ {
695 .bytecode_id = 1,
696 .bytecode = bytecode_ptr,
697 } },
698 trace);
699 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
700
701 auto valid_trace = trace; // Keep original trace before lookup processing
702 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytecode_size_from_bc_dec_settings>(valid_trace);
703
704 auto mutated_trace = trace;
705 const FF mutated_value = trace.get(C::instr_fetching_bytecode_size, 1) + 1; // Mutate to value + 1
706 mutated_trace.set(C::instr_fetching_bytecode_size, 1, mutated_value);
707
709 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytecode_size_from_bc_dec_settings>(
710 mutated_trace)),
711 "Failed.*BYTECODE_SIZE_FROM_BC_DEC. Could not find tuple in destination.");
712 }
713}
714
715TEST(InstrFetchingConstrainingTest, NegativeWrongTagValidationInteractions)
716{
717 TestTraceContainer trace;
718 BytecodeTraceBuilder bytecode_builder;
719 PrecomputedTraceBuilder precomputed_builder;
720
721 // Some chosen opcode with a tag. We limit to one as this unit test is costly.
722 // Test works if the following vector is extended to other opcodes though.
724
725 for (const auto& opcode : opcodes) {
726 TestTraceContainer trace;
727 const auto instr = testing::random_instruction(opcode);
728 bytecode_builder.process_instruction_fetching(
729 { { .bytecode_id = 1,
730 .pc = 0,
731 .instruction = instr,
732 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
733 trace);
736 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
737
738 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_tag_value_validation_settings>(trace);
739
740 auto valid_trace = trace; // Keep original trace before lookup processing
741
742 // Mutate tag out-of-range error
743 auto mutated_trace = trace;
744 ASSERT_EQ(trace.get(C::instr_fetching_tag_out_of_range, 1), 0);
745 mutated_trace.set(C::instr_fetching_tag_out_of_range, 1, 1); // Mutate by toggling the error.
746
748 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_tag_value_validation_settings>(
749 mutated_trace)),
750 "Failed.*LOOKUP_INSTR_FETCHING_TAG_VALUE_VALIDATION.*Could not find tuple in destination.");
751 }
752}
753
754// Negative test on not toggling instr_out_of_range when instr_size > bytes_to_read
755TEST(InstrFetchingConstrainingTest, NegativeNotTogglingInstrOutOfRange)
756{
757 TestTraceContainer trace({
758 { { C::precomputed_first_row, 1 } },
759 {
760 { C::instr_fetching_bytes_to_read, 11 },
761 { C::instr_fetching_instr_abs_diff, 0 },
762 { C::instr_fetching_instr_out_of_range, 1 }, // Will be mutated to zero
763 { C::instr_fetching_instr_size, 12 },
764 { C::instr_fetching_sel, 1 },
765 },
766 });
767
768 check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE);
769
770 trace.set(C::instr_fetching_instr_out_of_range, 1, 0); // Mutate to wrong value
771
772 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE),
773 "INSTR_OUT_OF_RANGE_TOGGLE");
774}
775
776// Negative test on wrongly toggling instr_out_of_range when instr_size <= bytes_to_read
777TEST(InstrFetchingConstrainingTest, NegativeTogglingInstrInRange)
778{
779 TestTraceContainer trace({
780 { { C::precomputed_first_row, 1 } },
781 {
782 { C::instr_fetching_bytes_to_read, 12 },
783 { C::instr_fetching_instr_abs_diff, 0 },
784 { C::instr_fetching_instr_out_of_range, 0 }, // Will be mutated to 1
785 { C::instr_fetching_instr_size, 12 },
786 { C::instr_fetching_sel, 1 },
787 },
788 });
789
790 check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE);
791
792 trace.set(C::instr_fetching_instr_out_of_range, 1, 1); // Mutate to wrong value
793
794 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE),
795 "INSTR_OUT_OF_RANGE_TOGGLE");
796}
797
798// Negative test on not toggling pc_out_of_range when pc >= bytecode_size
799TEST(InstrFetchingConstrainingTest, NegativeNotTogglingPcOutOfRange)
800{
801 TestTraceContainer trace({
802 { { C::precomputed_first_row, 1 } },
803 {
804 { C::instr_fetching_bytecode_size, 12 },
805 { C::instr_fetching_pc, 12 },
806 { C::instr_fetching_pc_abs_diff, 0 },
807 { C::instr_fetching_pc_out_of_range, 1 }, // Will be mutated to 0
808 { C::instr_fetching_sel, 1 },
809 },
810 });
811
812 check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE);
813
814 trace.set(C::instr_fetching_pc_out_of_range, 1, 0); // Mutate to wrong value
815
816 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE),
817 "PC_OUT_OF_RANGE_TOGGLE");
818}
819
820// Negative test on wrongly toggling pc_out_of_range when pc < bytecode_size
821TEST(InstrFetchingConstrainingTest, NegativeTogglingPcInRange)
822{
823 TestTraceContainer trace({
824 { { C::precomputed_first_row, 1 } },
825 {
826 { C::instr_fetching_bytecode_size, 12 },
827 { C::instr_fetching_pc, 11 },
828 { C::instr_fetching_pc_abs_diff, 0 },
829 { C::instr_fetching_pc_out_of_range, 0 }, // Will be mutated to 1
830 { C::instr_fetching_sel, 1 },
831 },
832 });
833
834 check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE);
835
836 trace.set(C::instr_fetching_pc_out_of_range, 1, 1); // Mutate to wrong value
837
838 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE),
839 "PC_OUT_OF_RANGE_TOGGLE");
840}
841
842TEST(InstrFetchingConstrainingTest, ErrorFlagSetButSelParsingErrIsZero)
843{
844 // Create a minimal trace that satisfies all constraints EXCEPT the (commented out) one
845 // that should enforce sel_parsing_err = pc_out_of_range + opcode_out_of_range + instr_out_of_range +
846 // tag_out_of_range
847 TestTraceContainer trace({
848 { { C::precomputed_first_row, 1 } },
849 {
850 { C::instr_fetching_sel, 1 },
851 // Error flags - pc_out_of_range is SET to 1
852 { C::instr_fetching_pc_out_of_range, 1 },
853 { C::instr_fetching_opcode_out_of_range, 0 },
854 { C::instr_fetching_instr_out_of_range, 0 },
855 { C::instr_fetching_tag_out_of_range, 0 },
856 // sel_parsing_err should be 1 (since pc_out_of_range = 1) but we set it to 0
857 { C::instr_fetching_sel_parsing_err, 0 },
858 // Values to satisfy PC_OUT_OF_RANGE_TOGGLE constraint (subrelation 4):
859 // pc_abs_diff = sel * ((2 * pc_out_of_range - 1) * (pc - bytecode_size) - 1 + pc_out_of_range)
860 // With pc_out_of_range = 1: pc_abs_diff = (2*1-1) * (pc - bytecode_size) - 1 + 1 = pc - bytecode_size
861 { C::instr_fetching_bytecode_size, 10 },
862 { C::instr_fetching_pc, 15 }, // pc > bytecode_size
863 { C::instr_fetching_pc_abs_diff, 5 }, // pc - bytecode_size = 15 - 10 = 5
864 { C::instr_fetching_pc_size_in_bits, 32 }, // AVM_PC_SIZE_IN_BITS constant
865 // Values to satisfy INSTR_OUT_OF_RANGE_TOGGLE constraint (subrelation 6):
866 // instr_abs_diff = (2 * instr_out_of_range - 1) * (instr_size - bytes_to_read) - instr_out_of_range
867 // With instr_out_of_range = 0: instr_abs_diff = (-1) * (instr_size - bytes_to_read) = bytes_to_read -
868 // instr_size
869 { C::instr_fetching_bytes_to_read, 10 },
870 { C::instr_fetching_instr_size, 5 },
871 { C::instr_fetching_instr_abs_diff, 5 }, // bytes_to_read - instr_size = 10 - 5 = 5
872 },
873 });
874
875 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace),
876 "Relation instr_fetching, subrelation 5 failed at row 1");
877}
878
883TEST(InstrFetchingConstrainingTest, CorrectBehavior_SelParsingErrMatchesErrors)
884{
885 TestTraceContainer trace({
886 { { C::precomputed_first_row, 1 } },
887 {
888 { C::instr_fetching_sel, 1 },
889 { C::instr_fetching_pc_out_of_range, 1 },
890 { C::instr_fetching_opcode_out_of_range, 0 },
891 { C::instr_fetching_instr_out_of_range, 0 },
892 { C::instr_fetching_tag_out_of_range, 0 },
893 { C::instr_fetching_sel_parsing_err, 1 }, // Correctly set to 1
894 // Supporting values
895 { C::instr_fetching_bytecode_size, 10 },
896 { C::instr_fetching_pc, 15 },
897 { C::instr_fetching_pc_abs_diff, 5 },
898 { C::instr_fetching_pc_size_in_bits, 32 },
899 { C::instr_fetching_bytes_to_read, 10 },
900 { C::instr_fetching_instr_size, 5 },
901 { C::instr_fetching_instr_abs_diff, 5 }, // bytes_to_read - instr_size = 10 - 5 = 5
902 },
903 });
904
905 // This should pass both before and after the fix.
906 check_relation<instr_fetching>(trace);
907}
908
912TEST(InstrFetchingConstrainingTest, CorrectBehavior_NoErrorsMeansSelParsingErrIsZero)
913{
914 TestTraceContainer trace({
915 { { C::precomputed_first_row, 1 } },
916 {
917 { C::instr_fetching_sel, 1 },
918 { C::instr_fetching_pc_out_of_range, 0 },
919 { C::instr_fetching_opcode_out_of_range, 0 },
920 { C::instr_fetching_instr_out_of_range, 0 },
921 { C::instr_fetching_tag_out_of_range, 0 },
922 { C::instr_fetching_sel_parsing_err, 0 }, // Correctly set to 0
923 { C::instr_fetching_sel_pc_in_range, 1 }, // sel * (1 - pc_out_of_range) = 1 * 1 = 1
924 // pc_abs_diff = sel * ((2 * pc_out_of_range - 1) * (pc - bytecode_size) - 1 + pc_out_of_range)
925 // With pc_out_of_range = 0: pc_abs_diff = (2*0-1) * (pc - bytecode_size) - 1 + 0
926 // = -(pc - bytecode_size) - 1 = bytecode_size - pc - 1
927 { C::instr_fetching_bytecode_size, 20 },
928 { C::instr_fetching_pc, 5 },
929 { C::instr_fetching_pc_abs_diff, 14 }, // bytecode_size - pc - 1 = 20 - 5 - 1 = 14
930 { C::instr_fetching_pc_size_in_bits, 32 },
931 // instr_abs_diff = bytes_to_read - instr_size (when instr_out_of_range = 0)
932 { C::instr_fetching_bytes_to_read, 15 },
933 { C::instr_fetching_instr_size, 10 },
934 { C::instr_fetching_instr_abs_diff, 5 }, // bytes_to_read - instr_size = 15 - 10 = 5
935 },
936 });
937
938 // This should pass both before and after the fix.
939 check_relation<instr_fetching>(trace);
940}
941
942} // namespace
943} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
#define AVM_PC_SIZE_IN_BITS
EventEmitter< BytecodeDecompositionEvent > decomposition_events
static constexpr size_t SR_OP1_BYTES_DECOMPOSITION
static constexpr size_t SR_OP3_BYTES_DECOMPOSITION
static constexpr size_t SR_OP6_BYTES_DECOMPOSITION
static constexpr size_t SR_OP4_BYTES_DECOMPOSITION
static constexpr size_t SR_ADDRESSING_MODE_BYTES_DECOMPOSITION
static constexpr size_t SR_INSTR_OUT_OF_RANGE_TOGGLE
static std::string get_subrelation_label(size_t index)
static constexpr size_t SR_OP7_BYTES_DECOMPOSITION
static constexpr size_t SR_OP5_BYTES_DECOMPOSITION
static constexpr size_t SR_PC_OUT_OF_RANGE_TOGGLE
static constexpr size_t SR_OP2_BYTES_DECOMPOSITION
void process_wire_instruction_spec(TraceContainer &trace)
void process_memory_tag_range(TraceContainer &trace)
void process_misc(TraceContainer &trace, const uint32_t num_rows=MAX_AVM_TRACE_SIZE)
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value)
RangeCheckTraceBuilder range_check_builder
Definition alu.test.cpp:121
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
AluTraceBuilder builder
Definition alu.test.cpp:124
TestTraceContainer trace
void check_interaction(tracegen::TestTraceContainer &trace)
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
Instruction random_instruction(WireOpCode w_opcode)
Definition fixtures.cpp:125
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
lookup_settings< lookup_instr_fetching_wire_instruction_info_settings_ > lookup_instr_fetching_wire_instruction_info_settings
lookup_settings< lookup_instr_fetching_bytecode_size_from_bc_dec_settings_ > lookup_instr_fetching_bytecode_size_from_bc_dec_settings
lookup_settings< lookup_instr_fetching_bytes_from_bc_dec_settings_ > lookup_instr_fetching_bytes_from_bc_dec_settings
lookup_settings< lookup_instr_fetching_instr_abs_diff_positive_settings_ > lookup_instr_fetching_instr_abs_diff_positive_settings
lookup_settings< lookup_instr_fetching_pc_abs_diff_positive_settings_ > lookup_instr_fetching_pc_abs_diff_positive_settings
lookup_settings< lookup_instr_fetching_tag_value_validation_settings_ > lookup_instr_fetching_tag_value_validation_settings
Instruction
Enumeration of VM instructions that can be executed.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
unsigned __int128 uint128_t
Definition serialize.hpp:44
static constexpr uint256_t modulus_minus_two