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