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
32
33namespace bb::avm2::constraining {
34namespace {
35
36using tracegen::BytecodeTraceBuilder;
37using tracegen::PrecomputedTraceBuilder;
38using tracegen::RangeCheckTraceBuilder;
39using tracegen::TestTraceContainer;
40
42using C = Column;
43
44using instr_fetching = instr_fetching<FF>;
45
46using simulation::BytecodeDecompositionEvent;
48using simulation::Instruction;
49using simulation::InstructionFetchingEvent;
51using simulation::RangeCheckEvent;
52
53TEST(InstrFetchingConstrainingTest, EmptyRow)
54{
55 check_relation<instr_fetching>(testing::empty_trace());
56}
57
58// Basic positive test with a hardcoded bytecode for ADD_8
59TEST(InstrFetchingConstrainingTest, Add8WithTraceGen)
60{
61 TestTraceContainer trace;
62 BytecodeTraceBuilder builder;
63 PrecomputedTraceBuilder precomputed_builder;
64
65 Instruction add_8_instruction = {
66 .opcode = WireOpCode::ADD_8,
67 .addressing_mode = 3,
68 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
69 };
70
71 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
72
73 builder.process_instruction_fetching({ { .bytecode_id = 1,
74 .pc = 0,
75 .instruction = add_8_instruction,
77 trace);
78 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
79
80 EXPECT_EQ(trace.get_num_rows(), 1);
81 check_relation<instr_fetching>(trace);
82}
83
84// Basic positive test with a hardcoded bytecode for ECADD
85// Cover the longest amount of operands.
86TEST(InstrFetchingConstrainingTest, EcaddWithTraceGen)
87{
88 TestTraceContainer trace;
89 BytecodeTraceBuilder builder;
90 PrecomputedTraceBuilder precomputed_builder;
91
92 Instruction ecadd_instruction = {
93 .opcode = WireOpCode::ECADD,
94 .addressing_mode = 0x1f1f,
95 .operands = { Operand::from<uint16_t>(0x1279),
96 Operand::from<uint16_t>(0x127a),
97 Operand::from<uint16_t>(0x127b),
98 Operand::from<uint16_t>(0x127c),
99 Operand::from<uint16_t>(0x127d),
100 Operand::from<uint16_t>(0x127e),
101 Operand::from<uint16_t>(0x127f) },
102 };
103
104 std::vector<uint8_t> bytecode = ecadd_instruction.serialize();
105 builder.process_instruction_fetching({ { .bytecode_id = 1,
106 .pc = 0,
107 .instruction = ecadd_instruction,
109 trace);
110 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
111
112 EXPECT_EQ(trace.get_num_rows(), 1);
113 check_relation<instr_fetching>(trace);
114}
115
116// Helper routine generating a vector of instruction fetching events for each
117// opcode.
118std::vector<InstructionFetchingEvent> gen_instr_events_each_opcode()
119{
120 std::vector<uint8_t> bytecode;
121 std::vector<Instruction> instructions;
122 constexpr auto num_opcodes = static_cast<size_t>(WireOpCode::LAST_OPCODE_SENTINEL);
123 instructions.reserve(num_opcodes);
125
126 for (size_t i = 0; i < num_opcodes; i++) {
127 pc_positions.at(i) = static_cast<uint32_t>(bytecode.size());
128 const auto instr = testing::random_instruction(static_cast<WireOpCode>(i));
129 instructions.emplace_back(instr);
130 const auto instruction_bytes = instr.serialize();
131 bytecode.insert(bytecode.end(),
132 std::make_move_iterator(instruction_bytes.begin()),
133 std::make_move_iterator(instruction_bytes.end()));
134 }
135
136 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
137 // Always use *bytecode_ptr from now on instead of bytecode as this one was moved.
138
140 instr_events.reserve(num_opcodes);
141 for (size_t i = 0; i < num_opcodes; i++) {
142 instr_events.emplace_back(InstructionFetchingEvent{
143 .bytecode_id = 1, .pc = pc_positions.at(i), .instruction = instructions.at(i), .bytecode = bytecode_ptr });
144 }
145 return instr_events;
146}
147
148// Positive test for each opcode. We assume that decode instruction is working correctly.
149// It works as long as the relations are not constraining the correct range for TAG nor indirect.
150TEST(InstrFetchingConstrainingTest, EachOpcodeWithTraceGen)
151{
152 TestTraceContainer trace;
153 BytecodeTraceBuilder builder;
154 PrecomputedTraceBuilder precomputed_builder;
155
156 builder.process_instruction_fetching(gen_instr_events_each_opcode(), trace);
157 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
158
159 constexpr auto num_opcodes = static_cast<size_t>(WireOpCode::LAST_OPCODE_SENTINEL);
160 EXPECT_EQ(trace.get_num_rows(), num_opcodes);
161 check_relation<instr_fetching>(trace);
162}
163
164// Negative test about decomposition of operands. We mutate correct operand values in the trace.
165// This also covers wrong operands which are not "involved" by the instruction.
166// We perform this for a random instruction for opcodes: REVERT_16, CAST_8, TORADIXBE
167TEST(InstrFetchingConstrainingTest, NegativeWrongOperand)
168{
169 BytecodeTraceBuilder builder;
170 PrecomputedTraceBuilder precomputed_builder;
171
173 std::vector<size_t> sub_relations = {
182 };
183
184 constexpr std::array<C, 8> operand_cols = {
185 C::instr_fetching_addressing_mode,
186 C::instr_fetching_op1,
187 C::instr_fetching_op2,
188 C::instr_fetching_op3,
189 C::instr_fetching_op4,
190 C::instr_fetching_op5,
191 C::instr_fetching_op6,
192 C::instr_fetching_op7,
193 };
194
195 for (const auto& opcode : opcodes) {
196 TestTraceContainer trace;
197 const auto instr = testing::random_instruction(opcode);
198 builder.process_instruction_fetching(
199 { { .bytecode_id = 1,
200 .pc = 0,
201 .instruction = instr,
202 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
203 trace);
204 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
205
206 check_relation<instr_fetching>(trace);
207
208 EXPECT_EQ(trace.get_num_rows(), 1);
209
210 for (size_t i = 0; i < operand_cols.size(); i++) {
211 auto mutated_trace = trace;
212 const FF mutated_operand = trace.get(operand_cols.at(i), 0) + 1; // Mutate to value + 1
213 mutated_trace.set(operand_cols.at(i), 0, mutated_operand);
214 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(mutated_trace, sub_relations.at(i)),
215 instr_fetching::get_subrelation_label(sub_relations.at(i)));
216 }
217 }
218}
219
220// Positive test for interaction with instruction spec table using same events as for the test
221// EachOpcodeWithTraceGen, i.e., one event/row is generated per wire opcode.
222// It works as long as the relations are not constraining the correct range for TAG nor indirect.
223TEST(InstrFetchingConstrainingTest, WireInstructionSpecInteractions)
224{
225 TestTraceContainer trace;
226 BytecodeTraceBuilder bytecode_builder;
227 PrecomputedTraceBuilder precomputed_builder;
228
231 bytecode_builder.process_instruction_fetching(gen_instr_events_each_opcode(), trace);
232 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
233
234 EXPECT_EQ(trace.get_num_rows(), 1 << 8); // 2^8 for selector against wire_instruction_spec
235
236 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(trace);
237 check_relation<instr_fetching>(trace);
238}
239
240std::vector<RangeCheckEvent> gen_range_check_events(const std::vector<InstructionFetchingEvent>& instr_events)
241{
242 std::vector<RangeCheckEvent> range_check_events;
243 range_check_events.reserve(instr_events.size());
244
245 for (const auto& instr_event : instr_events) {
246 range_check_events.emplace_back(RangeCheckEvent{
247 .value =
248 (instr_event.error.has_value() && instr_event.error == InstrDeserializationEventError::PC_OUT_OF_RANGE)
249 ? instr_event.pc - instr_event.bytecode->size()
250 : instr_event.bytecode->size() - instr_event.pc - 1,
251 .num_bits = AVM_PC_SIZE_IN_BITS,
252 });
253 }
254 return range_check_events;
255}
256
257// Positive test for the interaction with bytecode decomposition table.
258// One event/row is generated per wire opcode (same as for test WireInstructionSpecInteractions).
259TEST(InstrFetchingConstrainingTest, BcDecompositionInteractions)
260{
261 TestTraceContainer trace;
262 BytecodeTraceBuilder bytecode_builder;
263 PrecomputedTraceBuilder precomputed_builder;
264
265 const auto instr_fetch_events = gen_instr_events_each_opcode();
266 bytecode_builder.process_instruction_fetching(instr_fetch_events, trace);
267 bytecode_builder.process_decomposition({ {
268 .bytecode_id = instr_fetch_events.at(0).bytecode_id,
269 .bytecode = instr_fetch_events.at(0).bytecode,
270 } },
271 trace);
272 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
273
274 check_interaction<BytecodeTraceBuilder,
277
278 // BC Decomposition trace is the longest here and requires an extra prepended row.
279 EXPECT_EQ(trace.get_num_rows(), instr_fetch_events.at(0).bytecode->size() + 1);
280
281 check_relation<instr_fetching>(trace);
282}
283
284void check_all(const std::vector<InstructionFetchingEvent>& instr_events,
285 const std::vector<RangeCheckEvent>& range_check_events,
287{
288 TestTraceContainer trace;
289 BytecodeTraceBuilder bytecode_builder;
290 PrecomputedTraceBuilder precomputed_builder;
291 RangeCheckTraceBuilder range_check_builder;
292
297 bytecode_builder.process_instruction_fetching(instr_events, trace);
298 bytecode_builder.process_decomposition(decomposition_events, trace);
299 range_check_builder.process(range_check_events, trace);
300 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
301
302 check_interaction<BytecodeTraceBuilder,
309
310 EXPECT_EQ(trace.get_num_rows(), 1 << 16); // 2^16 for range checks
311
312 check_relation<instr_fetching>(trace);
313}
314
315void check_without_range_check(const std::vector<InstructionFetchingEvent>& instr_events,
317{
318 TestTraceContainer trace;
319 BytecodeTraceBuilder bytecode_builder;
320 PrecomputedTraceBuilder precomputed_builder;
321
325 bytecode_builder.process_instruction_fetching(instr_events, trace);
326 bytecode_builder.process_decomposition(decomposition_events, trace);
327 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
328
329 check_interaction<BytecodeTraceBuilder,
335
336 EXPECT_EQ(trace.get_num_rows(), 1 << 8); // 2^8 for range checks
337
338 check_relation<instr_fetching>(trace);
339}
340
341// Positive test with 5 five bytecodes and bytecode_id = 0,1,2,3,4
342// Bytecode i is generated by truncating instr_fetch_events to i * 6 instructions.
343// Check relations and all interactions.
344TEST(InstrFetchingConstrainingTest, MultipleBytecodes)
345{
346 const auto instr_fetch_events = gen_instr_events_each_opcode();
347 constexpr size_t num_of_bytecodes = 5;
350
351 for (size_t i = 0; i < num_of_bytecodes; i++) {
352 std::vector<uint8_t> bytecode;
353 const auto num_of_instr = i * 6;
354
355 for (size_t j = 0; j < num_of_instr; j++) {
356 const auto& instr = instr_fetch_events.at(j).instruction;
357 const auto instruction_bytes = instr.serialize();
358 bytecode.insert(bytecode.end(),
359 std::make_move_iterator(instruction_bytes.begin()),
360 std::make_move_iterator(instruction_bytes.end()));
361 }
362
363 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
364
365 for (size_t j = 0; j < num_of_instr; j++) {
366 auto instr_event = instr_fetch_events.at(j);
367 instr_event.bytecode_id = static_cast<BytecodeId>(i);
368 instr_event.bytecode = bytecode_ptr;
369 instr_events.emplace_back(instr_event);
370 }
371
372 decomposition_events.emplace_back(BytecodeDecompositionEvent{
373 .bytecode_id = static_cast<BytecodeId>(i),
374 .bytecode = bytecode_ptr,
375 });
376 }
377
378 check_all(instr_events, gen_range_check_events(instr_events), decomposition_events);
379}
380
381// Positive test with one single instruction with error INSTRUCTION_OUT_OF_RANGE.
382// The bytecode consists into a serialized single instruction with pc = 0 and
383// the bytecode had the last byte removed. This byte corresponds to a full operand.
384TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRange)
385{
386 Instruction add_8_instruction = {
387 .opcode = WireOpCode::ADD_8,
388 .addressing_mode = 3,
389 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
390 };
391
392 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
393 bytecode.pop_back(); // Remove last byte
394 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
395
396 const std::vector<InstructionFetchingEvent> instr_events = {
397 {
398 .bytecode_id = 1,
399 .pc = 0,
400 .bytecode = bytecode_ptr,
401 .error = InstrDeserializationEventError::INSTRUCTION_OUT_OF_RANGE,
402 },
403 };
404
406 {
407 .bytecode_id = 1,
408 .bytecode = bytecode_ptr,
409 },
410 };
411
412 check_without_range_check(instr_events, decomposition_events);
413}
414
415// Positive test with one single instruction (SET_FF) with error INSTRUCTION_OUT_OF_RANGE.
416// The bytecode consists into a serialized single instruction with pc = 0 and
417// the bytecode had the two last bytes removed. The truncated instruction is cut
418// in the middle of an operand.
419TEST(InstrFetchingConstrainingTest, SingleInstructionOutOfRangeSplitOperand)
420{
421 Instruction set_ff_instruction = {
422 .opcode = WireOpCode::SET_FF,
423 .addressing_mode = 0x01,
424 .operands = { Operand::from<uint16_t>(0x1279),
425 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::FF)),
426 Operand::from<FF>(FF::modulus_minus_two) },
427 };
428
429 std::vector<uint8_t> bytecode = set_ff_instruction.serialize();
430 bytecode.resize(bytecode.size() - 2); // Remove last two bytes)
431 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
432
433 const std::vector<InstructionFetchingEvent> instr_events = {
434 {
435 .bytecode_id = 1,
436 .pc = 0,
437 .bytecode = bytecode_ptr,
438 .error = InstrDeserializationEventError::INSTRUCTION_OUT_OF_RANGE,
439 },
440 };
441
443 {
444 .bytecode_id = 1,
445 .bytecode = bytecode_ptr,
446 },
447 };
448
449 check_without_range_check(instr_events, decomposition_events);
450}
451
452// Positive test with error case PC_OUT_OF_RANGE. We pass a pc which is out of range.
453TEST(InstrFetchingConstrainingTest, SingleInstructionPcOutOfRange)
454{
455 Instruction add_8_instruction = {
456 .opcode = WireOpCode::SUB_8,
457 .addressing_mode = 3,
458 .operands = { Operand::from<uint8_t>(0x34), Operand::from<uint8_t>(0x35), Operand::from<uint8_t>(0x36) },
459 };
460
461 std::vector<uint8_t> bytecode = add_8_instruction.serialize();
462 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
463
464 const std::vector<InstructionFetchingEvent> instr_events = {
465 // We first need a first instruction at pc == 0 as the trace assumes this.
466 {
467 .bytecode_id = 1,
468 .pc = 0,
469 .instruction = add_8_instruction,
470 .bytecode = bytecode_ptr,
471 },
472 {
473 .bytecode_id = 1,
474 .pc = static_cast<uint32_t>(bytecode_ptr->size() + 1),
475 .bytecode = bytecode_ptr,
476 .error = InstrDeserializationEventError::PC_OUT_OF_RANGE,
477 },
478 };
479
481 {
482 .bytecode_id = 1,
483 .bytecode = bytecode_ptr,
484 },
485 };
486
487 check_all(instr_events, gen_range_check_events(instr_events), decomposition_events);
488}
489
490// Positive test with error case OPCODE_OUT_OF_RANGE. We generate bytecode of a SET_128 instruction and
491// move the PC to a position corresponding to the beginning of the 128-bit immediate value of SET_128.
492// The immediate value in SET_128 starts with byte 0xFF (which we know is not a valid opcode).
493TEST(InstrFetchingConstrainingTest, SingleInstructionOpcodeOutOfRange)
494{
495 Instruction set_128_instruction = {
496 .opcode = WireOpCode::SET_128,
497 .addressing_mode = 0,
498 .operands = { Operand::from<uint16_t>(0x1234),
499 Operand::from<uint8_t>(static_cast<uint8_t>(MemoryTag::U128)),
500 Operand::from<uint128_t>(static_cast<uint128_t>(0xFF) << 120) },
501 };
502
503 std::vector<uint8_t> bytecode = set_128_instruction.serialize();
504 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
505
506 const std::vector<InstructionFetchingEvent> instr_events = {
507 {
508 .bytecode_id = 1,
509 .pc = 0,
510 .instruction = set_128_instruction,
511 .bytecode = bytecode_ptr,
512 },
513 {
514 .bytecode_id = 1,
515 .pc = 5, // We move pc to the beginning of the 128-bit immediate value.
516 .bytecode = bytecode_ptr,
517 .error = InstrDeserializationEventError::OPCODE_OUT_OF_RANGE,
518 },
519 };
520
522 {
523 .bytecode_id = 1,
524 .bytecode = bytecode_ptr,
525 },
526 };
527
528 check_without_range_check(instr_events, decomposition_events);
529}
530
531// Positive test with one single instruction (SET_16) with error TAG_OUT_OF_RANGE.
532// The bytecode consists into a serialized single instruction with pc = 0.
533// The operand at index 1 is wrongly set to value 12
534TEST(InstrFetchingConstrainingTest, SingleInstructionTagOutOfRange)
535{
536 Instruction set_16_instruction = {
537 .opcode = WireOpCode::SET_16,
538 .addressing_mode = 0,
539 .operands = { Operand::from<uint16_t>(0x1234), Operand::from<uint8_t>(12), Operand::from<uint16_t>(0x5678) },
540 };
541
542 std::vector<uint8_t> bytecode = set_16_instruction.serialize();
543 const auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(std::move(bytecode));
544
545 const std::vector<InstructionFetchingEvent> instr_events = {
546 {
547 .bytecode_id = 1,
548 .pc = 0,
549 .instruction = set_16_instruction,
550 .bytecode = bytecode_ptr,
551 .error = InstrDeserializationEventError::TAG_OUT_OF_RANGE,
552 },
553 };
554
556 {
557 .bytecode_id = 1,
558 .bytecode = bytecode_ptr,
559 },
560 };
561
562 check_without_range_check(instr_events, decomposition_events);
563}
564
565// Negative interaction test with some values not matching the instruction spec table.
566TEST(InstrFetchingConstrainingTest, NegativeWrongWireInstructionSpecInteractions)
567{
568 BytecodeTraceBuilder bytecode_builder;
569 PrecomputedTraceBuilder precomputed_builder;
570
571 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
572 // Test works if the following vector is extended to other opcodes though.
574
575 for (const auto& opcode : opcodes) {
576 TestTraceContainer trace;
577 const auto instr = testing::random_instruction(opcode);
578 bytecode_builder.process_instruction_fetching(
579 { { .bytecode_id = 1,
580 .pc = 0,
581 .instruction = instr,
582 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
583 trace);
586 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
587
588 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(trace);
589
590 ASSERT_EQ(trace.get(C::lookup_instr_fetching_wire_instruction_info_counts, static_cast<uint32_t>(opcode)), 1);
591
592 constexpr std::array<C, 21> mutated_cols = {
593 C::instr_fetching_exec_opcode, C::instr_fetching_instr_size, C::instr_fetching_sel_has_tag,
594 C::instr_fetching_sel_tag_is_op2, C::instr_fetching_sel_op_dc_0, C::instr_fetching_sel_op_dc_1,
595 C::instr_fetching_sel_op_dc_2, C::instr_fetching_sel_op_dc_3, C::instr_fetching_sel_op_dc_4,
596 C::instr_fetching_sel_op_dc_5, C::instr_fetching_sel_op_dc_6, C::instr_fetching_sel_op_dc_7,
597 C::instr_fetching_sel_op_dc_8, C::instr_fetching_sel_op_dc_9, C::instr_fetching_sel_op_dc_10,
598 C::instr_fetching_sel_op_dc_11, C::instr_fetching_sel_op_dc_12, C::instr_fetching_sel_op_dc_13,
599 C::instr_fetching_sel_op_dc_14, C::instr_fetching_sel_op_dc_15, C::instr_fetching_sel_op_dc_16,
600 };
601
602 // Mutate execution opcode
603 for (const auto& col : mutated_cols) {
604 auto mutated_trace = trace;
605 const FF mutated_value = trace.get(col, 0) + 1; // Mutate to value + 1
606 mutated_trace.set(col, 0, mutated_value);
607
609 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_wire_instruction_info_settings>(
610 mutated_trace)),
611 "Failed.*LOOKUP_INSTR_FETCHING_WIRE_INSTRUCTION_INFO.*Could not find tuple in destination.");
612 }
613 }
614}
615
616// Negative interaction test with some values not matching the bytecode decomposition table.
617TEST(InstrFetchingConstrainingTest, NegativeWrongBcDecompositionInteractions)
618{
619 TestTraceContainer trace;
620 BytecodeTraceBuilder bytecode_builder;
621
622 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
623 // Test works if the following vector is extended to other opcodes though.
625
626 for (const auto& opcode : opcodes) {
627 TestTraceContainer trace;
628 const auto instr = testing::random_instruction(opcode);
629 auto bytecode_ptr = std::make_shared<std::vector<uint8_t>>(instr.serialize());
630 bytecode_builder.process_instruction_fetching({ {
631 .bytecode_id = 1,
632 .pc = 0,
633 .instruction = instr,
634 .bytecode = bytecode_ptr,
635 } },
636 trace);
637 bytecode_builder.process_decomposition({ {
638 .bytecode_id = 1,
639 .bytecode = bytecode_ptr,
640 } },
641 trace);
642
643 auto valid_trace = trace; // Keep original trace before lookup processing
644 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytes_from_bc_dec_settings>(valid_trace);
645
646 constexpr std::array<C, 39> mutated_cols = {
647 C::instr_fetching_pc, C::instr_fetching_bytecode_id, C::instr_fetching_bd0, C::instr_fetching_bd1,
648 C::instr_fetching_bd2, C::instr_fetching_bd3, C::instr_fetching_bd4, C::instr_fetching_bd5,
649 C::instr_fetching_bd6, C::instr_fetching_bd7, C::instr_fetching_bd8, C::instr_fetching_bd9,
650 C::instr_fetching_bd10, C::instr_fetching_bd11, C::instr_fetching_bd12, C::instr_fetching_bd13,
651 C::instr_fetching_bd14, C::instr_fetching_bd15, C::instr_fetching_bd16, C::instr_fetching_bd17,
652 C::instr_fetching_bd18, C::instr_fetching_bd19, C::instr_fetching_bd20, C::instr_fetching_bd21,
653 C::instr_fetching_bd22, C::instr_fetching_bd23, C::instr_fetching_bd24, C::instr_fetching_bd25,
654 C::instr_fetching_bd26, C::instr_fetching_bd27, C::instr_fetching_bd28, C::instr_fetching_bd29,
655 C::instr_fetching_bd30, C::instr_fetching_bd31, C::instr_fetching_bd32, C::instr_fetching_bd33,
656 C::instr_fetching_bd34, C::instr_fetching_bd35, C::instr_fetching_bd36,
657 };
658
659 // Mutate execution opcode
660 for (const auto& col : mutated_cols) {
661 auto mutated_trace = trace;
662 const FF mutated_value = trace.get(col, 0) + 1; // Mutate to value + 1
663 mutated_trace.set(col, 0, mutated_value);
664
666 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytes_from_bc_dec_settings>(
667 mutated_trace)),
668 "Failed.*BYTES_FROM_BC_DEC. Could not find tuple in destination.");
669 }
670 }
671}
672
673// Negative interaction test for #[BYTECODE_SIZE_FROM_BC_DEC] where bytecode_size has the wrong value.
674// We set pc different from zero.
675TEST(InstrFetchingConstrainingTest, NegativeWrongBytecodeSizeBcDecompositionInteractions)
676{
677 TestTraceContainer trace;
678 BytecodeTraceBuilder bytecode_builder;
679 PrecomputedTraceBuilder precomputed_builder;
680
681 const uint32_t pc = 15;
682 std::vector<uint8_t> bytecode(pc, 0x23);
683
684 // Some arbitrary chosen opcodes. We limit to one as this unit test is costly.
685 // Test works if the following vector is extended to other opcodes though.
687
688 for (const auto& opcode : opcodes) {
689 TestTraceContainer trace;
690
691 const auto instr = testing::random_instruction(opcode);
692 const auto instr_bytecode = instr.serialize();
693 bytecode.insert(bytecode.end(),
694 std::make_move_iterator(instr_bytecode.begin()),
695 std::make_move_iterator(instr_bytecode.end()));
697
698 bytecode_builder.process_instruction_fetching({ {
699 .bytecode_id = 1,
700 .pc = pc,
701 .instruction = instr,
702 .bytecode = bytecode_ptr,
703 } },
704 trace);
705 bytecode_builder.process_decomposition({ {
706 .bytecode_id = 1,
707 .bytecode = bytecode_ptr,
708 } },
709 trace);
710 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
711
712 auto valid_trace = trace; // Keep original trace before lookup processing
713 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytecode_size_from_bc_dec_settings>(valid_trace);
714
715 auto mutated_trace = trace;
716 const FF mutated_value = trace.get(C::instr_fetching_bytecode_size, 0) + 1; // Mutate to value + 1
717 mutated_trace.set(C::instr_fetching_bytecode_size, 0, mutated_value);
718
720 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_bytecode_size_from_bc_dec_settings>(
721 mutated_trace)),
722 "Failed.*BYTECODE_SIZE_FROM_BC_DEC. Could not find tuple in destination.");
723 }
724}
725
726using ::bb::avm2::testing::InstructionBuilder;
727using simulation::EventEmitter;
728using simulation::MockExecutionIdManager;
729using simulation::MockGreaterThan;
730using simulation::Poseidon2;
731using simulation::Poseidon2HashEvent;
732using simulation::Poseidon2PermutationEvent;
733using simulation::Poseidon2PermutationMemoryEvent;
734using ::testing::StrictMock;
735using tracegen::Poseidon2TraceBuilder;
736
737TEST(InstrFetchingConstrainingTest, NegativeTruncatedBytecodeRepro)
738{
739 TestTraceContainer trace;
740 BytecodeTraceBuilder bytecode_builder;
741 PrecomputedTraceBuilder precomputed_builder;
742 RangeCheckTraceBuilder range_check_builder;
743 EventEmitter<Poseidon2HashEvent> hash_event_emitter;
744 EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
745 EventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
746 StrictMock<MockGreaterThan> mock_gt;
747 StrictMock<MockExecutionIdManager> mock_execution_id_manager;
748 // Note: this helper expects bytecode fields without the prepended separator and does not complete decomposition
751
752 Poseidon2TraceBuilder poseidon2_builder;
753
754 // Build some good bytecode:
755 const uint32_t pc = 15;
756 std::vector<uint8_t> bytecode(pc, 0x23);
757 const auto add_instr =
758 InstructionBuilder(WireOpCode::SUB_8).operand<uint8_t>(5).operand<uint8_t>(5).operand<uint8_t>(0).build();
759 const auto instr_bytecode = add_instr.serialize();
760 bytecode.insert(
761 bytecode.end(), std::make_move_iterator(instr_bytecode.begin()), std::make_move_iterator(instr_bytecode.end()));
762
763 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
764 std::vector<FF> prepended_fields = { simulation::compute_public_bytecode_first_field(bytecode.size()) };
765 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
767
768 // Remove the final byte (which has a value of zero)
769 std::vector<uint8_t> trunc_bytecode(pc, 0x23);
770 trunc_bytecode.insert(trunc_bytecode.end(),
771 std::make_move_iterator(instr_bytecode.begin()),
772 std::make_move_iterator(instr_bytecode.end()));
773 trunc_bytecode.resize(trunc_bytecode.size() - 1);
774 std::vector<FF> trunc_fields = simulation::encode_bytecode(trunc_bytecode);
775 std::vector<FF> trunc_prepended_fields = { DOM_SEP__PUBLIC_BYTECODE };
776 trunc_prepended_fields.insert(trunc_prepended_fields.end(), trunc_fields.begin(), trunc_fields.end());
777 FF trunc_hash = poseidon2.hash(trunc_prepended_fields);
778 // 'Real' bytecode: [ 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 02 00 05 05 00 ] of length 20 bytes
779 // We could previously process a truncated bytecode with the same id:
780 // 'Fake' bytecode: [ 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 02 00 05 05 ] of length 19 bytes
781 // Before introducing #[BYTECODE_LENGTH_BYTES] in bc_hashing.pil and including the size in
782 // compute_public_bytecode_first_field(), (#20254) trunc_hash == hash, meaning we could use truncated bytecode.
783 ASSERT_NE(hash, trunc_hash);
784
785 // Now, we cannot process the truncated bytecode and force a good instruction on the full bytecode to fail:
786 auto trunc_bytecode_ptr = std::make_shared<std::vector<uint8_t>>(trunc_bytecode);
788 InstructionFetchingEvent instr_event = {
789 .bytecode_id = hash,
790 .pc = pc,
791 .instruction = add_instr,
792 .bytecode = bytecode_ptr,
793 };
794 bytecode_builder.process_instruction_fetching({ instr_event }, trace);
795 bytecode_builder.process_hashing({ {
796 .bytecode_id = hash,
797 .bytecode_length_in_bytes = static_cast<uint32_t>(trunc_bytecode.size()),
798 .bytecode_fields = trunc_fields,
799 } },
800 trace);
801
802 bytecode_builder.process_decomposition({ {
803 .bytecode_id = hash,
804 .bytecode = trunc_bytecode_ptr,
805 } },
806 trace);
807
808 // Prep trace:
809 range_check_builder.process(gen_range_check_events({ instr_event }), trace);
814
815 tracegen::MultiPermutationBuilder<perm_bc_hashing_get_packed_field_0_settings,
818 perm_builder(C::bc_decomposition_sel_packed);
819 perm_builder.process(trace);
820
821 check_relation<bb::avm2::bc_hashing<FF>>(trace);
823 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
824 "Failed.*LOOKUP_BC_HASHING_POSEIDON2_HASH. Could not find tuple in destination.");
825}
826
827TEST(InstrFetchingConstrainingTest, NegativeWrongTagValidationInteractions)
828{
829 TestTraceContainer trace;
830 BytecodeTraceBuilder bytecode_builder;
831 PrecomputedTraceBuilder precomputed_builder;
832
833 // Some chosen opcode with a tag. We limit to one as this unit test is costly.
834 // Test works if the following vector is extended to other opcodes though.
836
837 for (const auto& opcode : opcodes) {
838 TestTraceContainer trace;
839 const auto instr = testing::random_instruction(opcode);
840 bytecode_builder.process_instruction_fetching(
841 { { .bytecode_id = 1,
842 .pc = 0,
843 .instruction = instr,
844 .bytecode = std::make_shared<std::vector<uint8_t>>(instr.serialize()) } },
845 trace);
848 precomputed_builder.process_misc(trace, trace.get_num_rows()); // Limit to the number of rows we need.
849
850 check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_tag_value_validation_settings>(trace);
851
852 auto valid_trace = trace; // Keep original trace before lookup processing
853
854 // Mutate tag out-of-range error
855 auto mutated_trace = trace;
856 ASSERT_EQ(trace.get(C::instr_fetching_tag_out_of_range, 0), 0);
857 mutated_trace.set(C::instr_fetching_tag_out_of_range, 0, 1); // Mutate by toggling the error.
858
860 (check_interaction<BytecodeTraceBuilder, lookup_instr_fetching_tag_value_validation_settings>(
861 mutated_trace)),
862 "Failed.*LOOKUP_INSTR_FETCHING_TAG_VALUE_VALIDATION.*Could not find tuple in destination.");
863 }
864}
865
866// Negative test on not toggling instr_out_of_range when instr_size > bytes_to_read
867TEST(InstrFetchingConstrainingTest, NegativeNotTogglingInstrOutOfRange)
868{
869 TestTraceContainer trace({
870 {
871 { C::instr_fetching_bytes_to_read, 11 },
872 { C::instr_fetching_instr_abs_diff, 0 },
873 { C::instr_fetching_instr_out_of_range, 1 }, // Will be mutated to zero
874 { C::instr_fetching_instr_size, 12 },
875 { C::instr_fetching_sel, 1 },
876 },
877 });
878
879 check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE);
880
881 trace.set(C::instr_fetching_instr_out_of_range, 0, 0); // Mutate to wrong value
882
883 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE),
884 "INSTR_OUT_OF_RANGE_TOGGLE");
885}
886
887// Negative test on wrongly toggling instr_out_of_range when instr_size <= bytes_to_read
888TEST(InstrFetchingConstrainingTest, NegativeTogglingInstrInRange)
889{
890 TestTraceContainer trace({
891 {
892 { C::instr_fetching_bytes_to_read, 12 },
893 { C::instr_fetching_instr_abs_diff, 0 },
894 { C::instr_fetching_instr_out_of_range, 0 }, // Will be mutated to 1
895 { C::instr_fetching_instr_size, 12 },
896 { C::instr_fetching_sel, 1 },
897 },
898 });
899
900 check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE);
901
902 trace.set(C::instr_fetching_instr_out_of_range, 0, 1); // Mutate to wrong value
903
904 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_INSTR_OUT_OF_RANGE_TOGGLE),
905 "INSTR_OUT_OF_RANGE_TOGGLE");
906}
907
908// Negative test on not toggling pc_out_of_range when pc >= bytecode_size
909TEST(InstrFetchingConstrainingTest, NegativeNotTogglingPcOutOfRange)
910{
911 TestTraceContainer trace({
912 {
913 { C::instr_fetching_bytecode_size, 12 },
914 { C::instr_fetching_pc, 12 },
915 { C::instr_fetching_pc_abs_diff, 0 },
916 { C::instr_fetching_pc_out_of_range, 1 }, // Will be mutated to 0
917 { C::instr_fetching_sel, 1 },
918 },
919 });
920
921 check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE);
922
923 trace.set(C::instr_fetching_pc_out_of_range, 0, 0); // Mutate to wrong value
924
925 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE),
926 "PC_OUT_OF_RANGE_TOGGLE");
927}
928
929// Negative test on wrongly toggling pc_out_of_range when pc < bytecode_size
930TEST(InstrFetchingConstrainingTest, NegativeTogglingPcInRange)
931{
932 TestTraceContainer trace({
933 {
934 { C::instr_fetching_bytecode_size, 12 },
935 { C::instr_fetching_pc, 11 },
936 { C::instr_fetching_pc_abs_diff, 0 },
937 { C::instr_fetching_pc_out_of_range, 0 }, // Will be mutated to 1
938 { C::instr_fetching_sel, 1 },
939 },
940 });
941
942 check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE);
943
944 trace.set(C::instr_fetching_pc_out_of_range, 0, 1); // Mutate to wrong value
945
946 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace, instr_fetching::SR_PC_OUT_OF_RANGE_TOGGLE),
947 "PC_OUT_OF_RANGE_TOGGLE");
948}
949
950TEST(InstrFetchingConstrainingTest, ErrorFlagSetButSelParsingErrIsZero)
951{
952 // Create a minimal trace that satisfies all constraints EXCEPT the (commented out) one
953 // that should enforce sel_parsing_err = pc_out_of_range + opcode_out_of_range + instr_out_of_range +
954 // tag_out_of_range
955 TestTraceContainer trace({
956 {
957 { C::instr_fetching_sel, 1 },
958 // Error flags - pc_out_of_range is SET to 1
959 { C::instr_fetching_pc_out_of_range, 1 },
960 { C::instr_fetching_opcode_out_of_range, 0 },
961 { C::instr_fetching_instr_out_of_range, 0 },
962 { C::instr_fetching_tag_out_of_range, 0 },
963 // sel_parsing_err should be 1 (since pc_out_of_range = 1) but we set it to 0
964 { C::instr_fetching_sel_parsing_err, 0 },
965 // Values to satisfy PC_OUT_OF_RANGE_TOGGLE constraint (subrelation 4):
966 // pc_abs_diff = sel * ((2 * pc_out_of_range - 1) * (pc - bytecode_size) - 1 + pc_out_of_range)
967 // With pc_out_of_range = 1: pc_abs_diff = (2*1-1) * (pc - bytecode_size) - 1 + 1 = pc - bytecode_size
968 { C::instr_fetching_bytecode_size, 10 },
969 { C::instr_fetching_pc, 15 }, // pc > bytecode_size
970 { C::instr_fetching_pc_abs_diff, 5 }, // pc - bytecode_size = 15 - 10 = 5
971 { C::instr_fetching_pc_size_in_bits, 32 }, // AVM_PC_SIZE_IN_BITS constant
972 // Values to satisfy INSTR_OUT_OF_RANGE_TOGGLE constraint (subrelation 6):
973 // instr_abs_diff = (2 * instr_out_of_range - 1) * (instr_size - bytes_to_read) - instr_out_of_range
974 // With instr_out_of_range = 0: instr_abs_diff = (-1) * (instr_size - bytes_to_read) = bytes_to_read -
975 // instr_size
976 { C::instr_fetching_bytes_to_read, 10 },
977 { C::instr_fetching_instr_size, 5 },
978 { C::instr_fetching_instr_abs_diff, 5 }, // bytes_to_read - instr_size = 10 - 5 = 5
979 },
980 });
981
982 EXPECT_THROW_WITH_MESSAGE(check_relation<instr_fetching>(trace),
983 "Relation instr_fetching, subrelation .* failed at row 0");
984}
985
990TEST(InstrFetchingConstrainingTest, CorrectBehaviorSelParsingErrMatchesErrors)
991{
992 TestTraceContainer trace({
993 {
994 { C::instr_fetching_sel, 1 },
995 { C::instr_fetching_pc_out_of_range, 1 },
996 { C::instr_fetching_opcode_out_of_range, 0 },
997 { C::instr_fetching_instr_out_of_range, 0 },
998 { C::instr_fetching_tag_out_of_range, 0 },
999 { C::instr_fetching_sel_parsing_err, 1 }, // Correctly set to 1
1000 // Supporting values
1001 { C::instr_fetching_bytecode_size, 10 },
1002 { C::instr_fetching_pc, 15 },
1003 { C::instr_fetching_pc_abs_diff, 5 },
1004 { C::instr_fetching_pc_size_in_bits, 32 },
1005 { C::instr_fetching_bytes_to_read, 10 },
1006 { C::instr_fetching_instr_size, 5 },
1007 { C::instr_fetching_instr_abs_diff, 5 }, // bytes_to_read - instr_size = 10 - 5 = 5
1008 },
1009 });
1010
1011 // This should pass both before and after the fix.
1012 check_relation<instr_fetching>(trace);
1013}
1014
1018TEST(InstrFetchingConstrainingTest, CorrectBehaviorNoErrorsMeansSelParsingErrIsZero)
1019{
1020 TestTraceContainer trace({
1021 {
1022 { C::instr_fetching_sel, 1 },
1023 { C::instr_fetching_pc_out_of_range, 0 },
1024 { C::instr_fetching_opcode_out_of_range, 0 },
1025 { C::instr_fetching_instr_out_of_range, 0 },
1026 { C::instr_fetching_tag_out_of_range, 0 },
1027 { C::instr_fetching_sel_parsing_err, 0 }, // Correctly set to 0
1028 { C::instr_fetching_sel_pc_in_range, 1 }, // sel * (1 - pc_out_of_range) = 1 * 1 = 1
1029 // pc_abs_diff = sel * ((2 * pc_out_of_range - 1) * (pc - bytecode_size) - 1 + pc_out_of_range)
1030 // With pc_out_of_range = 0: pc_abs_diff = (2*0-1) * (pc - bytecode_size) - 1 + 0
1031 // = -(pc - bytecode_size) - 1 = bytecode_size - pc - 1
1032 { C::instr_fetching_bytecode_size, 20 },
1033 { C::instr_fetching_pc, 5 },
1034 { C::instr_fetching_pc_abs_diff, 14 }, // bytecode_size - pc - 1 = 20 - 5 - 1 = 14
1035 { C::instr_fetching_pc_size_in_bits, 32 },
1036 // instr_abs_diff = bytes_to_read - instr_size (when instr_out_of_range = 0)
1037 { C::instr_fetching_bytes_to_read, 15 },
1038 { C::instr_fetching_instr_size, 10 },
1039 { C::instr_fetching_instr_abs_diff, 5 }, // bytes_to_read - instr_size = 15 - 10 = 5
1040 },
1041 });
1042
1043 // This should pass both before and after the fix.
1044 check_relation<instr_fetching>(trace);
1045}
1046
1047} // namespace
1048} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
std::shared_ptr< Napi::ThreadSafeFunction > bytecode
#define DOM_SEP__PUBLIC_BYTECODE
#define AVM_PC_SIZE_IN_BITS
StrictMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
Poseidon2TraceBuilder poseidon2_builder
StrictMock< MockExecutionIdManager > mock_execution_id_manager
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_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
Processes the hash events for the Poseidon2 hash function. It populates the columns for the poseidon2...
void process_misc(TraceContainer &trace, const uint32_t num_rows=PRECOMPUTED_TRACE_SIZE)
Populate miscellaneous precomputed columns: first_row selector and idx (row index).
void process_wire_instruction_spec(TraceContainer &trace)
Populate the wire-level instruction specification table.
void process_memory_tag_range(TraceContainer &trace)
Populate the memory tag out-of-range selector.
void process_sel_range_16(TraceContainer &trace)
Generate a selector column that activates the first 2^16 (65536) rows.
void process_sel_range_8(TraceContainer &trace)
Generate a selector column that activates the first 2^8 (256) rows.
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
Processes range check events and populates the trace with decomposed value columns.
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value)
Native Poseidon2 hash function implementation.
Definition poseidon2.hpp:22
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
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.
std::vector< FF > encode_bytecode(std::span< const uint8_t > bytecode)
Encodes the bytecode into a vector of field elements. Each field element represents 31 bytes of the b...
FF compute_public_bytecode_first_field(size_t bytecode_size)
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
permutation_settings< perm_bc_hashing_get_packed_field_2_settings_ > perm_bc_hashing_get_packed_field_2_settings
permutation_settings< perm_bc_hashing_get_packed_field_1_settings_ > perm_bc_hashing_get_packed_field_1_settings
permutation_settings< perm_bc_hashing_get_packed_field_0_settings_ > perm_bc_hashing_get_packed_field_0_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:45
static constexpr uint256_t modulus_minus_two