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 .indirect = 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,
65 .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } },
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 .indirect = 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,
97 .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } },
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 = {
167 };
168
169 constexpr std::array<C, 8> operand_cols = {
170 C::instr_fetching_indirect, C::instr_fetching_op1, C::instr_fetching_op2, C::instr_fetching_op3,
171 C::instr_fetching_op4, C::instr_fetching_op5, C::instr_fetching_op6, C::instr_fetching_op7,
172 };
173
174 for (const auto& opcode : opcodes) {
175 TestTraceContainer trace;
176 const auto instr = testing::random_instruction(opcode);
177 builder.process_instruction_fetching(
178 { { .bytecode_id = 1,
179 .pc = 0,
180 .instruction = instr,
181 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
182 trace);
183 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
184
185 check_relation<instr_fetching>(trace);
186
187 EXPECT_EQ(trace.get_num_rows(), 2);
188
189 for (size_t i = 0; i < operand_cols.size(); i++) {
190 auto mutated_trace = trace;
191 const FF mutated_operand = trace.get(operand_cols.at(i), 0) + 1; // Mutate to value + 1
192 mutated_trace.set(operand_cols.at(i), 0, mutated_operand);
193 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(mutated_trace, sub_relations.at(i)),
194 instr_fetching::get_subrelation_label(sub_relations.at(i)));
195 }
196 }
197}
198
199// Positive test for interaction with instruction spec table using same events as for the test
200// EachOpcodeWithTraceGen, i.e., one event/row is generated per wire opcode.
201// It works as long as the relations are not constraining the correct range for TAG nor indirect.
202TEST(InstrFetchingConstrainingTest, WireInstructionSpecInteractions)
203{
204 TestTraceContainer trace;
205 BytecodeTraceBuilder bytecode_builder;
206 PrecomputedTraceBuilder precomputed_builder;
207
210 bytecode_builder.process_instruction_fetching(gen_instr_events_each_opcode(), trace);
211 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
212
213 EXPECT_EQ(trace.get_num_rows(), 1 << 8); // 2^8 for selector against wire_instruction_spec
214
215 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(trace);
216 check_relation<instr_fetching>(trace);
217}
218
219std::vector<RangeCheckEvent> gen_range_check_events(const std::vector<InstructionFetchingEvent>& instr_events)
220{
221 std::vector<RangeCheckEvent> range_check_events;
222 range_check_events.reserve(instr_events.size());
223
224 for (const auto& instr_event : instr_events) {
225 range_check_events.emplace_back(RangeCheckEvent{
226 .value = instr_event.error == InstrDeserializationError::PC_OUT_OF_RANGE
227 ? instr_event.pc - instr_event.bytecode->size()
228 : instr_event.bytecode->size() - instr_event.pc - 1,
229 .num_bits = AVM_PC_SIZE_IN_BITS,
230 });
231 }
232 return range_check_events;
233}
234
235// Positive test for the interaction with bytecode decomposition table.
236// One event/row is generated per wire opcode (same as for test WireInstructionSpecInteractions).
237TEST(InstrFetchingConstrainingTest, BcDecompositionInteractions)
238{
239 TestTraceContainer trace;
240 BytecodeTraceBuilder bytecode_builder;
241 PrecomputedTraceBuilder precomputed_builder;
242
243 const auto instr_fetch_events = gen_instr_events_each_opcode();
244 bytecode_builder.process_instruction_fetching(instr_fetch_events, trace);
245 bytecode_builder.process_decomposition({ {
246 .bytecode_id = instr_fetch_events.at(0).bytecode_id,
247 .bytecode = instr_fetch_events.at(0).bytecode,
248 } },
249 trace);
250 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
251
252 check_interaction<BytecodeTraceBuilder,
255
256 // BC Decomposition trace is the longest here.
257 EXPECT_EQ(trace.get_num_rows(), instr_fetch_events.at(0).bytecode->size() + 1);
258
259 check_relation<instr_fetching>(trace);
260}
261
262void check_all(const std::vector<InstructionFetchingEvent>& instr_events,
263 const std::vector<RangeCheckEvent>& range_check_events,
265{
266 TestTraceContainer trace;
267 BytecodeTraceBuilder bytecode_builder;
268 PrecomputedTraceBuilder precomputed_builder;
269 RangeCheckTraceBuilder range_check_builder;
270
275 bytecode_builder.process_instruction_fetching(instr_events, trace);
276 bytecode_builder.process_decomposition(decomposition_events, trace);
277 range_check_builder.process(range_check_events, trace);
278 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
279
280 check_interaction<BytecodeTraceBuilder,
287
288 EXPECT_EQ(trace.get_num_rows(), 1 << 16); // 2^16 for range checks
289
290 check_relation<instr_fetching>(trace);
291}
292
293void check_without_range_check(const std::vector<InstructionFetchingEvent>& instr_events,
295{
296 TestTraceContainer trace;
297 BytecodeTraceBuilder bytecode_builder;
298 PrecomputedTraceBuilder precomputed_builder;
299
303 bytecode_builder.process_instruction_fetching(instr_events, trace);
304 bytecode_builder.process_decomposition(decomposition_events, trace);
305 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
306
307 check_interaction<BytecodeTraceBuilder,
313
314 EXPECT_EQ(trace.get_num_rows(), 1 << 8); // 2^8 for range checks
315
316 check_relation<instr_fetching>(trace);
317}
318
319// Positive test with 5 five bytecodes and bytecode_id = 0,1,2,3,4
320// Bytecode i is generated by truncating instr_fetch_events to i * 6 instructions.
321// Check relations and all interactions.
322TEST(InstrFetchingConstrainingTest, MultipleBytecodes)
323{
324 const auto instr_fetch_events = gen_instr_events_each_opcode();
325 constexpr size_t num_of_bytecodes = 5;
328
329 for (size_t i = 0; i < num_of_bytecodes; i++) {
330 std::vector<uint8_t> bytecode;
331 const auto num_of_instr = i * 6;
332
333 for (size_t j = 0; j < num_of_instr; j++) {
334 const auto& instr = instr_fetch_events.at(j).instruction;
335 const auto instruction_bytes = instr.serialize();
336 bytecode.insert(bytecode.end(),
337 std::make_move_iterator(instruction_bytes.begin()),
338 std::make_move_iterator(instruction_bytes.end()));
339 }
340
341 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
342
343 for (size_t j = 0; j < num_of_instr; j++) {
344 auto instr_event = instr_fetch_events.at(j);
345 instr_event.bytecode_id = static_cast<BytecodeId>(i);
346 instr_event.bytecode = bytecode_ptr;
347 instr_events.emplace_back(instr_event);
348 }
349
350 decomposition_events.emplace_back(BytecodeDecompositionEvent{
351 .bytecode_id = static_cast<BytecodeId>(i),
352 .bytecode = bytecode_ptr,
353 });
354 }
355
356 check_all(instr_events, gen_range_check_events(instr_events), decomposition_events);
357}
358
359// Positive test with one single instruction with error INSTRUCTION_OUT_OF_RANGE.
360// The bytecode consists into a serialized single instruction with pc = 0 and
361// the bytecode had the last byte removed. This byte corresponds to a full operand.
362TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRange)
363{
364 Instruction add_8_instruction = {
365 .opcode = WireOpCode::ADD_8,
366 .indirect = 3,
367 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
368 };
369
370 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
371 bytecode.pop_back(); // Remove last byte
372 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
373
374 const std::vector<InstructionFetchingEvent> instr_events = {
375 {
376 .bytecode_id = 1,
377 .pc = 0,
378 .bytecode = bytecode_ptr,
379 .error = InstrDeserializationError::INSTRUCTION_OUT_OF_RANGE,
380 },
381 };
382
384 {
385 .bytecode_id = 1,
386 .bytecode = bytecode_ptr,
387 },
388 };
389
390 check_without_range_check(instr_events, decomposition_events);
391}
392
393// Positive test with one single instruction (SET_FF) with error INSTRUCTION_OUT_OF_RANGE.
394// The bytecode consists into a serialized single instruction with pc = 0 and
395// the bytecode had the two last bytes removed. The truncated instruction is cut
396// in the middle of an operand.
397TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRangeSplitOperand)
398{
399 Instruction set_ff_instruction = {
400 .opcode = WireOpCode::SET_FF,
401 .indirect = 0x01,
402 .operands = { Operand::from<uint16_t>(0x1279),
403 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::FF)),
404 Operand::from<FF>(FF::modulus_minus_two) },
405 };
406
407 std::vector<uint8_t> bytecode = set_ff_instruction.serialize();
408 bytecode.resize(bytecode.size() - 2); // Remove last two bytes)
409 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
410
411 const std::vector<InstructionFetchingEvent> instr_events = {
412 {
413 .bytecode_id = 1,
414 .pc = 0,
415 .bytecode = bytecode_ptr,
416 .error = InstrDeserializationError::INSTRUCTION_OUT_OF_RANGE,
417 },
418 };
419
421 {
422 .bytecode_id = 1,
423 .bytecode = bytecode_ptr,
424 },
425 };
426
427 check_without_range_check(instr_events, decomposition_events);
428}
429
430// Positive test with error case PC_OUT_OF_RANGE. We pass a pc which is out of range.
431TEST(InstrFetchingConstrainingTest, SingleInstructionPcOutOfRange)
432{
433 Instruction add_8_instruction = {
434 .opcode = WireOpCode::SUB_8,
435 .indirect = 3,
436 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
437 };
438
439 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
440 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
441
442 const std::vector<InstructionFetchingEvent> instr_events = {
443 // We first need a first instruction at pc == 0 as the trace assumes this.
444 {
445 .bytecode_id = 1,
446 .pc = 0,
447 .instruction = add_8_instruction,
448 .bytecode = bytecode_ptr,
449 },
450 {
451 .bytecode_id = 1,
452 .pc = static_cast<uint32_t>(bytecode_ptr->size() + 1),
453 .bytecode = bytecode_ptr,
454 .error = InstrDeserializationError::PC_OUT_OF_RANGE,
455 },
456 };
457
459 {
460 .bytecode_id = 1,
461 .bytecode = bytecode_ptr,
462 },
463 };
464
465 check_all(instr_events, gen_range_check_events(instr_events), decomposition_events);
466}
467
468// Positive test with error case OPCODE_OUT_OF_RANGE. We generate bytecode of a SET_128 instruction and
469// move the PC to a position corresponding to the beginning of the 128-bit immediate value of SET_128.
470// The immediate value in SET_128 starts with byte 0xFF (which we know is not a valid opcode).
471TEST(InstrFetchingConstrainingTest, SingleInstructionOpcodeOutOfRange)
472{
473 Instruction set_128_instruction = {
474 .opcode = WireOpCode::SET_128,
475 .indirect = 0,
476 .operands = { Operand::from<uint16_t>(0x1234),
477 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::U128)),
478 Operand::from<uint128_t>(static_cast<uint128_t>(0xFF) << 120) },
479 };
480
481 std::vector<uint8_t> bytecode = set_128_instruction.serialize();
482 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
483
484 const std::vector<InstructionFetchingEvent> instr_events = {
485 {
486 .bytecode_id = 1,
487 .pc = 0,
488 .instruction = set_128_instruction,
489 .bytecode = bytecode_ptr,
490 },
491 {
492 .bytecode_id = 1,
493 .pc = 5, // We move pc to the beginning of the 128-bit immediate value.
494 .bytecode = bytecode_ptr,
495 .error = InstrDeserializationError::OPCODE_OUT_OF_RANGE,
496 },
497 };
498
500 {
501 .bytecode_id = 1,
502 .bytecode = bytecode_ptr,
503 },
504 };
505
506 check_without_range_check(instr_events, decomposition_events);
507}
508
509// Positive test with one single instruction (SET_16) with error TAG_OUT_OF_RANGE.
510// The bytecode consists into a serialized single instruction with pc = 0.
511// The operand at index 1 is wrongly set to value 12
512TEST(InstrFetchingConstrainingTest, SingleInstructionTagOutOfRange)
513{
514 Instruction set_16_instruction = {
515 .opcode = WireOpCode::SET_16,
516 .indirect = 0,
517 .operands = { Operand::from<uint16_t>(0x1234), Operand::from<uint8_t>(12), Operand::from<uint16_t>(0x5678) },
518 };
519
520 std::vector<uint8_t> bytecode = set_16_instruction.serialize();
521 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
522
523 const std::vector<InstructionFetchingEvent> instr_events = {
524 {
525 .bytecode_id = 1,
526 .pc = 0,
527 .instruction = set_16_instruction,
528 .bytecode = bytecode_ptr,
529 .error = InstrDeserializationError::TAG_OUT_OF_RANGE,
530 },
531 };
532
534 {
535 .bytecode_id = 1,
536 .bytecode = bytecode_ptr,
537 },
538 };
539
540 check_without_range_check(instr_events, decomposition_events);
541}
542
543// Negative interaction test with some values not matching the instruction spec table.
544TEST(InstrFetchingConstrainingTest, NegativeWrongWireInstructionSpecInteractions)
545{
546 BytecodeTraceBuilder bytecode_builder;
547 PrecomputedTraceBuilder precomputed_builder;
548
549 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
550 // Test works if the following vector is extended to other opcodes though.
552
553 for (const auto& opcode : opcodes) {
554 TestTraceContainer trace;
555 const auto instr = testing::random_instruction(opcode);
556 bytecode_builder.process_instruction_fetching(
557 { { .bytecode_id = 1,
558 .pc = 0,
559 .instruction = instr,
560 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
561 trace);
564 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
565
566 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(trace);
567
568 ASSERT_EQ(trace.get(C::lookup_instr_fetching_wire_instruction_info_counts, static_cast<uint32_t>(opcode)), 1);
569
570 constexpr std::array<C, 21> mutated_cols = {
571 C::instr_fetching_exec_opcode, C::instr_fetching_instr_size, C::instr_fetching_sel_has_tag,
572 C::instr_fetching_sel_tag_is_op2, C::instr_fetching_sel_op_dc_0, C::instr_fetching_sel_op_dc_1,
573 C::instr_fetching_sel_op_dc_2, C::instr_fetching_sel_op_dc_3, C::instr_fetching_sel_op_dc_4,
574 C::instr_fetching_sel_op_dc_5, C::instr_fetching_sel_op_dc_6, C::instr_fetching_sel_op_dc_7,
575 C::instr_fetching_sel_op_dc_8, C::instr_fetching_sel_op_dc_9, C::instr_fetching_sel_op_dc_10,
576 C::instr_fetching_sel_op_dc_11, C::instr_fetching_sel_op_dc_12, C::instr_fetching_sel_op_dc_13,
577 C::instr_fetching_sel_op_dc_14, C::instr_fetching_sel_op_dc_15, C::instr_fetching_sel_op_dc_16,
578 };
579
580 // Mutate execution opcode
581 for (const auto& col : mutated_cols) {
582 auto mutated_trace = trace;
583 const FF mutated_value = trace.get(col, 1) + 1; // Mutate to value + 1
584 mutated_trace.set(col, 1, mutated_value);
585
587 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(
588 mutated_trace)),
589 "Failed.*LOOKUP_INSTR_FETCHING_WIRE_INSTRUCTION_INFO.*Could not find tuple in destination.");
590 }
591 }
592}
593
594// Negative interaction test with some values not matching the bytecode decomposition table.
595TEST(InstrFetchingConstrainingTest, NegativeWrongBcDecompositionInteractions)
596{
597 TestTraceContainer trace;
598 BytecodeTraceBuilder bytecode_builder;
599
600 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
601 // Test works if the following vector is extended to other opcodes though.
603
604 for (const auto& opcode : opcodes) {
605 TestTraceContainer trace;
606 const auto instr = testing::random_instruction(opcode);
607 auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(instr.serialize());
608 bytecode_builder.process_instruction_fetching({ {
609 .bytecode_id = 1,
610 .pc = 0,
611 .instruction = instr,
612 .bytecode = bytecode_ptr,
613 } },
614 trace);
615 bytecode_builder.process_decomposition({ {
616 .bytecode_id = 1,
617 .bytecode = bytecode_ptr,
618 } },
619 trace);
620
621 auto valid_trace = trace; // Keep original trace before lookup processing
622 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytes_from_bc_dec_settings>(valid_trace);
623
624 constexpr std::array<C, 39> mutated_cols = {
625 C::instr_fetching_pc, C::instr_fetching_bytecode_id, C::instr_fetching_bd0, C::instr_fetching_bd1,
626 C::instr_fetching_bd2, C::instr_fetching_bd3, C::instr_fetching_bd4, C::instr_fetching_bd5,
627 C::instr_fetching_bd6, C::instr_fetching_bd7, C::instr_fetching_bd8, C::instr_fetching_bd9,
628 C::instr_fetching_bd10, C::instr_fetching_bd11, C::instr_fetching_bd12, C::instr_fetching_bd13,
629 C::instr_fetching_bd14, C::instr_fetching_bd15, C::instr_fetching_bd16, C::instr_fetching_bd17,
630 C::instr_fetching_bd18, C::instr_fetching_bd19, C::instr_fetching_bd20, C::instr_fetching_bd21,
631 C::instr_fetching_bd22, C::instr_fetching_bd23, C::instr_fetching_bd24, C::instr_fetching_bd25,
632 C::instr_fetching_bd26, C::instr_fetching_bd27, C::instr_fetching_bd28, C::instr_fetching_bd29,
633 C::instr_fetching_bd30, C::instr_fetching_bd31, C::instr_fetching_bd32, C::instr_fetching_bd33,
634 C::instr_fetching_bd34, C::instr_fetching_bd35, C::instr_fetching_bd36,
635 };
636
637 // Mutate execution opcode
638 for (const auto& col : mutated_cols) {
639 auto mutated_trace = trace;
640 const FF mutated_value = trace.get(col, 1) + 1; // Mutate to value + 1
641 mutated_trace.set(col, 1, mutated_value);
642
644 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytes_from_bc_dec_settings>(
645 mutated_trace)),
646 "Failed.*BYTES_FROM_BC_DEC. Could not find tuple in destination.");
647 }
648 }
649}
650
651// Negative interaction test for #[BYTECODE_SIZE_FROM_BC_DEC] where bytecode_size has the wrong value.
652// We set pc different from zero.
653TEST(InstrFetchingConstrainingTest, NegativeWrongBytecodeSizeBcDecompositionInteractions)
654{
655 TestTraceContainer trace;
656 BytecodeTraceBuilder bytecode_builder;
657 PrecomputedTraceBuilder precomputed_builder;
658
659 const uint32_t pc = 15;
660 std::vector<uint8_t> bytecode(pc, 0x23);
661
662 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
663 // Test works if the following vector is extended to other opcodes though.
665
666 for (const auto& opcode : opcodes) {
667 TestTraceContainer trace;
668
669 const auto instr = testing::random_instruction(opcode);
670 const auto instr_bytecode = instr.serialize();
671 bytecode.insert(bytecode.end(),
672 std::make_move_iterator(instr_bytecode.begin()),
673 std::make_move_iterator(instr_bytecode.end()));
674 auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(bytecode);
675
676 bytecode_builder.process_instruction_fetching({ {
677 .bytecode_id = 1,
678 .pc = pc,
679 .instruction = instr,
680 .bytecode = bytecode_ptr,
681 } },
682 trace);
683 bytecode_builder.process_decomposition({ {
684 .bytecode_id = 1,
685 .bytecode = bytecode_ptr,
686 } },
687 trace);
688 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
689
690 auto valid_trace = trace; // Keep original trace before lookup processing
691 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytecode_size_from_bc_dec_settings>(valid_trace);
692
693 auto mutated_trace = trace;
694 const FF mutated_value = trace.get(C::instr_fetching_bytecode_size, 1) + 1; // Mutate to value + 1
695 mutated_trace.set(C::instr_fetching_bytecode_size, 1, mutated_value);
696
698 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytecode_size_from_bc_dec_settings>(
699 mutated_trace)),
700 "Failed.*BYTECODE_SIZE_FROM_BC_DEC. Could not find tuple in destination.");
701 }
702}
703
704TEST(InstrFetchingConstrainingTest, NegativeWrongTagValidationInteractions)
705{
706 TestTraceContainer trace;
707 BytecodeTraceBuilder bytecode_builder;
708 PrecomputedTraceBuilder precomputed_builder;
709
710 // Some chosen opcode with a tag. We limit to one as this unit test is costly.
711 // Test works if the following vector is extended to other opcodes though.
713
714 for (const auto& opcode : opcodes) {
715 TestTraceContainer trace;
716 const auto instr = testing::random_instruction(opcode);
717 bytecode_builder.process_instruction_fetching(
718 { { .bytecode_id = 1,
719 .pc = 0,
720 .instruction = instr,
721 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
722 trace);
725 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
726
727 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_tag_value_validation_settings>(trace);
728
729 auto valid_trace = trace; // Keep original trace before lookup processing
730
731 // Mutate tag out-of-range error
732 auto mutated_trace = trace;
733 ASSERT_EQ(trace.get(C::instr_fetching_tag_out_of_range, 1), 0);
734 mutated_trace.set(C::instr_fetching_tag_out_of_range, 1, 1); // Mutate by toggling the error.
735
737 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_tag_value_validation_settings>(
738 mutated_trace)),
739 "Failed.*LOOKUP_INSTR_FETCHING_TAG_VALUE_VALIDATION.*Could not find tuple in destination.");
740 }
741}
742
743// Negative test on not toggling instr_out_of_range when instr_size > bytes_to_read
744TEST(InstrFetchingConstrainingTest, NegativeNotTogglingInstrOutOfRange)
745{
746 TestTraceContainer trace({
747 { { C::precomputed_first_row, 1 } },
748 {
749 { C::instr_fetching_bytes_to_read, 11 },
750 { C::instr_fetching_instr_abs_diff, 0 },
751 { C::instr_fetching_instr_out_of_range, 1 }, // Will be mutated to zero
752 { C::instr_fetching_instr_size, 12 },
753 { C::instr_fetching_sel, 1 },
754 },
755 });
756
757 check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE);
758
759 trace.set(C::instr_fetching_instr_out_of_range, 1, 0); // Mutate to wrong value
760
762 "INSTR_OUT_OF_RANGE_TOGGLE");
763}
764
765// Negative test on wrongly toggling instr_out_of_range when instr_size <= bytes_to_read
766TEST(InstrFetchingConstrainingTest, NegativeTogglingInstrInRange)
767{
768 TestTraceContainer trace({
769 { { C::precomputed_first_row, 1 } },
770 {
771 { C::instr_fetching_bytes_to_read, 12 },
772 { C::instr_fetching_instr_abs_diff, 0 },
773 { C::instr_fetching_instr_out_of_range, 0 }, // Will be mutated to 1
774 { C::instr_fetching_instr_size, 12 },
775 { C::instr_fetching_sel, 1 },
776 },
777 });
778
779 check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE);
780
781 trace.set(C::instr_fetching_instr_out_of_range, 1, 1); // Mutate to wrong value
782
784 "INSTR_OUT_OF_RANGE_TOGGLE");
785}
786
787// Negative test on not toggling pc_out_of_range when pc >= bytecode_size
788TEST(InstrFetchingConstrainingTest, NegativeNotTogglingPcOutOfRange)
789{
790 TestTraceContainer trace({
791 { { C::precomputed_first_row, 1 } },
792 {
793 { C::instr_fetching_bytecode_size, 12 },
794 { C::instr_fetching_pc, 12 },
795 { C::instr_fetching_pc_abs_diff, 0 },
796 { C::instr_fetching_pc_out_of_range, 1 }, // Will be mutated to 0
797 { C::instr_fetching_sel, 1 },
798 },
799 });
800
801 check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE);
802
803 trace.set(C::instr_fetching_pc_out_of_range, 1, 0); // Mutate to wrong value
804
806 "PC_OUT_OF_RANGE_TOGGLE");
807}
808
809// Negative test on wrongly toggling pc_out_of_range when pc < bytecode_size
810TEST(InstrFetchingConstrainingTest, NegativeTogglingPcInRange)
811{
812 TestTraceContainer trace({
813 { { C::precomputed_first_row, 1 } },
814 {
815 { C::instr_fetching_bytecode_size, 12 },
816 { C::instr_fetching_pc, 11 },
817 { C::instr_fetching_pc_abs_diff, 0 },
818 { C::instr_fetching_pc_out_of_range, 0 }, // Will be mutated to 1
819 { C::instr_fetching_sel, 1 },
820 },
821 });
822
823 check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE);
824
825 trace.set(C::instr_fetching_pc_out_of_range, 1, 1); // Mutate to wrong value
826
828 "PC_OUT_OF_RANGE_TOGGLE");
829}
830
831} // namespace
832} // namespace bb::avm2::constraining
#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_INDIRECT_BYTES_DECOMPOSITION
static constexpr size_t SR_OP6_BYTES_DECOMPOSITION
static constexpr size_t SR_OP4_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:120
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:119
AluTraceBuilder builder
Definition alu.test.cpp:123
TestTraceContainer trace
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessage)
Definition macros.hpp:7
void check_interaction(tracegen::TestTraceContainer &trace)
TEST(TxExecutionConstrainingTest, WriteTreeValue)
Definition tx.test.cpp:402
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
typename Flavor::FF FF
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