Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
execution.cpp
Go to the documentation of this file.
2
3#include <stdexcept>
4#include <string>
5
40
41namespace bb::avm2::simulation {
42
73{
74 BB_BENCH_NAME("Execution::add");
75 constexpr auto opcode = ExecutionOpCode::ADD;
76 auto& memory = context.get_memory();
77 const MemoryValue& a = memory.get(a_addr);
78 const auto& b = memory.get(b_addr);
79 set_and_validate_inputs(opcode, { a, b });
80
82
83 try {
84 MemoryValue c = alu.add(a, b);
85 memory.set(dst_addr, c);
86 set_output(opcode, c);
87 } catch (AluException& e) {
88 throw OpcodeExecutionException("Alu add operation failed: " + std::string(e.what()));
89 }
90}
91
104{
105 BB_BENCH_NAME("Execution::sub");
106 constexpr auto opcode = ExecutionOpCode::SUB;
107 auto& memory = context.get_memory();
108 const auto& a = memory.get(a_addr);
109 const auto& b = memory.get(b_addr);
110 set_and_validate_inputs(opcode, { a, b });
111
113
114 try {
115 MemoryValue c = alu.sub(a, b);
116 memory.set(dst_addr, c);
117 set_output(opcode, c);
118 } catch (AluException& e) {
119 throw OpcodeExecutionException("Alu sub operation failed");
120 }
121}
122
135{
136 BB_BENCH_NAME("Execution::mul");
137 constexpr auto opcode = ExecutionOpCode::MUL;
138 auto& memory = context.get_memory();
139 const auto& a = memory.get(a_addr);
140 const auto& b = memory.get(b_addr);
141 set_and_validate_inputs(opcode, { a, b });
142
144
145 try {
146 MemoryValue c = alu.mul(a, b);
147 memory.set(dst_addr, c);
148 set_output(opcode, c);
149 } catch (AluException& e) {
150 throw OpcodeExecutionException("Alu mul operation failed: " + std::string(e.what()));
151 }
152}
153
169{
170 BB_BENCH_NAME("Execution::div");
171 constexpr auto opcode = ExecutionOpCode::DIV;
172 auto& memory = context.get_memory();
173 const auto& a = memory.get(a_addr);
174 MemoryValue b = memory.get(b_addr);
175 set_and_validate_inputs(opcode, { a, b });
176
178
179 try {
180 MemoryValue c = alu.div(a, b);
181 memory.set(dst_addr, c);
182 set_output(opcode, c);
183 } catch (AluException& e) {
184 throw OpcodeExecutionException("Alu div operation failed: " + std::string(e.what()));
185 }
186}
187
203{
204 BB_BENCH_NAME("Execution::fdiv");
205 constexpr auto opcode = ExecutionOpCode::FDIV;
206 auto& memory = context.get_memory();
207 const auto& a = memory.get(a_addr);
208 const auto& b = memory.get(b_addr);
209 set_and_validate_inputs(opcode, { a, b });
210
212
213 try {
214 MemoryValue c = alu.fdiv(a, b);
215 memory.set(dst_addr, c);
216 set_output(opcode, c);
217 } catch (AluException& e) {
218 throw OpcodeExecutionException("Alu fdiv operation failed: " + std::string(e.what()));
219 }
220}
221
234{
235 BB_BENCH_NAME("Execution::eq");
236 constexpr auto opcode = ExecutionOpCode::EQ;
237 auto& memory = context.get_memory();
238 const auto& a = memory.get(a_addr);
239 const auto& b = memory.get(b_addr);
240 set_and_validate_inputs(opcode, { a, b });
241
243
244 try {
245 MemoryValue c = alu.eq(a, b);
246 memory.set(dst_addr, c);
247 set_output(opcode, c);
248 } catch (AluException& e) {
249 throw OpcodeExecutionException("Alu eq operation failed: " + std::string(e.what()));
250 }
251}
252
265{
266 BB_BENCH_NAME("Execution::lt");
267 constexpr auto opcode = ExecutionOpCode::LT;
268 auto& memory = context.get_memory();
269 const auto& a = memory.get(a_addr);
270 const auto& b = memory.get(b_addr);
271 set_and_validate_inputs(opcode, { a, b });
272
274
275 try {
276 MemoryValue c = alu.lt(a, b);
277 memory.set(dst_addr, c);
278 set_output(opcode, c);
279 } catch (AluException& e) {
280 throw OpcodeExecutionException("Alu lt operation failed: " + std::string(e.what()));
281 }
282}
283
296{
297 BB_BENCH_NAME("Execution::lte");
298 constexpr auto opcode = ExecutionOpCode::LT;
299 auto& memory = context.get_memory();
300 const auto& a = memory.get(a_addr);
301 const auto& b = memory.get(b_addr);
302 set_and_validate_inputs(opcode, { a, b });
303
305
306 try {
307 MemoryValue c = alu.lte(a, b);
308 memory.set(dst_addr, c);
309 set_output(opcode, c);
310 } catch (AluException& e) {
311 throw OpcodeExecutionException("Alu lte operation failed: " + std::string(e.what()));
312 }
313}
314
326{
327 BB_BENCH_NAME("Execution::op_not");
328 constexpr auto opcode = ExecutionOpCode::NOT;
329 auto& memory = context.get_memory();
330 const auto& a = memory.get(src_addr);
331 set_and_validate_inputs(opcode, { a });
332
334
335 try {
336 MemoryValue b = alu.op_not(a);
337 memory.set(dst_addr, b);
338 set_output(opcode, b);
339 } catch (AluException& e) {
340 throw OpcodeExecutionException("Alu not operation failed: " + std::string(e.what()));
341 }
342}
343
358{
359 BB_BENCH_NAME("Execution::shl");
360 constexpr auto opcode = ExecutionOpCode::SHL;
361 auto& memory = context.get_memory();
362 const auto& a = memory.get(a_addr);
363 const auto& b = memory.get(b_addr);
364 set_and_validate_inputs(opcode, { a, b });
365
367
368 try {
369 MemoryValue c = alu.shl(a, b);
370 memory.set(dst_addr, c);
371 set_output(opcode, c);
372 } catch (const AluException& e) {
373 throw OpcodeExecutionException("SHL Exception: " + std::string(e.what()));
374 }
375}
376
391{
392 BB_BENCH_NAME("Execution::shr");
393 constexpr auto opcode = ExecutionOpCode::SHR;
394 auto& memory = context.get_memory();
395 const auto& a = memory.get(a_addr);
396 const auto& b = memory.get(b_addr);
397 set_and_validate_inputs(opcode, { a, b });
398
400
401 try {
402 MemoryValue c = alu.shr(a, b);
403 memory.set(dst_addr, c);
404 set_output(opcode, c);
405 } catch (const AluException& e) {
406 throw OpcodeExecutionException("SHR Exception: " + std::string(e.what()));
407 }
408}
409
421{
422 BB_BENCH_NAME("Execution::cast");
423 constexpr auto opcode = ExecutionOpCode::CAST;
424 auto& memory = context.get_memory();
425 const auto& val = memory.get(src_addr);
426 set_and_validate_inputs(opcode, { val });
427
429 MemoryValue truncated = alu.truncate(val.as_ff(), static_cast<MemoryTag>(dst_tag));
430 memory.set(dst_addr, truncated);
431 set_output(opcode, truncated);
432}
433
445{
446 BB_BENCH_NAME("Execution::get_env_var");
447 constexpr auto opcode = ExecutionOpCode::GETENVVAR;
448 auto& memory = context.get_memory();
449
451
452 MemoryValue result;
453
454 EnvironmentVariable env_var = static_cast<EnvironmentVariable>(var_enum);
455 switch (env_var) {
457 result = MemoryValue::from<FF>(context.get_address());
458 break;
460 result = MemoryValue::from<FF>(context.get_msg_sender());
461 break;
463 result = MemoryValue::from<FF>(context.get_transaction_fee());
464 break;
466 result = MemoryValue::from<FF>(context.get_globals().chain_id);
467 break;
469 result = MemoryValue::from<FF>(context.get_globals().version);
470 break;
472 result = MemoryValue::from<uint32_t>(context.get_globals().block_number);
473 break;
475 result = MemoryValue::from<uint64_t>(context.get_globals().timestamp);
476 break;
478 result = MemoryValue::from<uint128_t>(context.get_globals().gas_fees.fee_per_l2_gas);
479 break;
481 result = MemoryValue::from<uint128_t>(context.get_globals().gas_fees.fee_per_da_gas);
482 break;
484 result = MemoryValue::from<uint1_t>(context.get_is_static() ? 1 : 0);
485 break;
487 result = MemoryValue::from<uint32_t>(context.gas_left().l2_gas);
488 break;
490 result = MemoryValue::from<uint32_t>(context.gas_left().da_gas);
491 break;
492 default:
493 throw OpcodeExecutionException("Invalid environment variable enum value");
494 }
495
496 memory.set(dst_addr, result);
497 set_output(opcode, result);
498}
499
511// TODO: My dispatch system makes me have a uint8_t tag. Rethink.
513{
514 BB_BENCH_NAME("Execution::set");
516
517 constexpr auto opcode = ExecutionOpCode::SET;
518 MemoryValue truncated = alu.truncate(value, static_cast<MemoryTag>(dst_tag));
519 context.get_memory().set(dst_addr, truncated);
520 set_output(opcode, truncated);
521}
522
533{
534 BB_BENCH_NAME("Execution::mov");
535 constexpr auto opcode = ExecutionOpCode::MOV;
536 auto& memory = context.get_memory();
537 const auto& v = memory.get(src_addr);
538 set_and_validate_inputs(opcode, { v });
539
541
542 memory.set(dst_addr, v);
543 set_output(opcode, v);
544}
545
568 MemoryAddress l2_gas_offset,
569 MemoryAddress da_gas_offset,
570 MemoryAddress addr,
571 MemoryAddress cd_size_offset,
573{
574 BB_BENCH_NAME("Execution::call");
575 constexpr auto opcode = ExecutionOpCode::CALL;
576 auto& memory = context.get_memory();
577
578 // NOTE: these reads cannot fail due to addressing guarantees.
579 const auto& allocated_l2_gas_read = memory.get(l2_gas_offset);
580 const auto& allocated_da_gas_read = memory.get(da_gas_offset);
581 const auto& contract_address = memory.get(addr);
582 // Cd offset loads are deferred to calldatacopy
583 const auto& cd_size = memory.get(cd_size_offset);
584
585 set_and_validate_inputs(opcode, { allocated_l2_gas_read, allocated_da_gas_read, contract_address, cd_size });
586
587 get_gas_tracker().consume_gas(); // Base gas.
589 Gas{ .l2_gas = allocated_l2_gas_read.as<uint32_t>(), .da_gas = allocated_da_gas_read.as<uint32_t>() });
590
591 // Tag check contract address + cd_size
593 /*msg_sender=*/context.get_address(),
594 /*transaction_fee=*/context.get_transaction_fee(),
595 /*parent_context=*/context,
596 /*cd_offset_address=*/cd_offset,
597 /*cd_size=*/cd_size.as<uint32_t>(),
598 /*is_static=*/context.get_is_static(),
599 /*gas_limit=*/gas_limit,
600 /*phase=*/context.get_phase());
601
602 // We do not recurse. This context will be use on the next cycle of execution.
603 handle_enter_call(context, std::move(nested_context));
604}
605
628 MemoryAddress l2_gas_offset,
629 MemoryAddress da_gas_offset,
630 MemoryAddress addr,
631 MemoryAddress cd_size_offset,
633{
634 BB_BENCH_NAME("Execution::static_call");
635 constexpr auto opcode = ExecutionOpCode::STATICCALL;
636 auto& memory = context.get_memory();
637
638 // NOTE: these reads cannot fail due to addressing guarantees.
639 const auto& allocated_l2_gas_read = memory.get(l2_gas_offset);
640 const auto& allocated_da_gas_read = memory.get(da_gas_offset);
641 const auto& contract_address = memory.get(addr);
642 // Cd offset loads are deferred to calldatacopy
643 const auto& cd_size = memory.get(cd_size_offset);
644
645 set_and_validate_inputs(opcode, { allocated_l2_gas_read, allocated_da_gas_read, contract_address, cd_size });
646
647 get_gas_tracker().consume_gas(); // Base gas.
649 Gas{ .l2_gas = allocated_l2_gas_read.as<uint32_t>(), .da_gas = allocated_da_gas_read.as<uint32_t>() });
650
651 // Tag check contract address + cd_size
653 /*msg_sender=*/context.get_address(),
654 /*transaction_fee=*/context.get_transaction_fee(),
655 /*parent_context=*/context,
656 /*cd_offset_address=*/cd_offset,
657 /*cd_size=*/cd_size.as<uint32_t>(),
658 /*is_static=*/true,
659 /*gas_limit=*/gas_limit,
660 /*phase=*/context.get_phase());
661
662 // We do not recurse. This context will be use on the next cycle of execution.
663 handle_enter_call(context, std::move(nested_context));
664}
665
683 MemoryAddress cd_size_offset,
686{
687 BB_BENCH_NAME("Execution::cd_copy");
688 constexpr auto opcode = ExecutionOpCode::CALLDATACOPY;
689 auto& memory = context.get_memory();
690 const auto& cd_copy_size = memory.get(cd_size_offset); // Tag check u32
691 const auto& cd_offset_read = memory.get(cd_offset); // Tag check u32
692 set_and_validate_inputs(opcode, { cd_copy_size, cd_offset_read });
693
694 get_gas_tracker().consume_gas({ .l2_gas = cd_copy_size.as<uint32_t>(), .da_gas = 0 });
695
696 try {
697 data_copy.cd_copy(context, cd_copy_size.as<uint32_t>(), cd_offset_read.as<uint32_t>(), dst_addr);
698 } catch (const DataCopyException& e) {
699 throw OpcodeExecutionException("cd copy failed: " + std::string(e.what()));
700 }
701}
702
719 MemoryAddress rd_size_offset,
720 MemoryAddress rd_offset,
722{
723 BB_BENCH_NAME("Execution::rd_copy");
724 constexpr auto opcode = ExecutionOpCode::RETURNDATACOPY;
725 auto& memory = context.get_memory();
726 const auto& rd_copy_size = memory.get(rd_size_offset); // Tag check u32
727 const auto& rd_offset_read = memory.get(rd_offset); // Tag check u32
728 set_and_validate_inputs(opcode, { rd_copy_size, rd_offset_read });
729
730 get_gas_tracker().consume_gas({ .l2_gas = rd_copy_size.as<uint32_t>(), .da_gas = 0 });
731
732 try {
733 data_copy.rd_copy(context, rd_copy_size.as<uint32_t>(), rd_offset_read.as<uint32_t>(), dst_addr);
734 } catch (const DataCopyException& e) {
735 throw OpcodeExecutionException("rd copy failed: " + std::string(e.what()));
736 }
737}
738
748{
749 BB_BENCH_NAME("Execution::rd_size");
750 constexpr auto opcode = ExecutionOpCode::RETURNDATASIZE;
751 auto& memory = context.get_memory();
752
754
755 // This is safe because the last_rd_size is tag checked on ret/revert to be U32
756 MemoryValue rd_size = MemoryValue::from<uint32_t>(context.get_last_rd_size());
757 memory.set(dst_addr, rd_size);
758 set_output(opcode, rd_size);
759}
760
775{
776 BB_BENCH_NAME("Execution::ret");
777 constexpr auto opcode = ExecutionOpCode::RETURN;
778 auto& memory = context.get_memory();
779 const auto& rd_size = memory.get(ret_size_offset);
780 set_and_validate_inputs(opcode, { rd_size });
781
783
784 set_execution_result({ .rd_offset = ret_offset,
785 .rd_size = rd_size.as<uint32_t>(),
786 .gas_used = context.get_gas_used(),
787 .success = true,
788 .halting_pc = context.get_pc(),
789 .halting_message = std::nullopt });
790
791 context.halt();
792}
793
808{
809 BB_BENCH_NAME("Execution::revert");
810 constexpr auto opcode = ExecutionOpCode::REVERT;
811 auto& memory = context.get_memory();
812 const auto& rev_size = memory.get(rev_size_offset);
813 set_and_validate_inputs(opcode, { rev_size });
814
816
817 set_execution_result({ .rd_offset = rev_offset,
818 .rd_size = rev_size.as<uint32_t>(),
819 .gas_used = context.get_gas_used(),
820 .success = false,
821 .halting_pc = context.get_pc(),
822 .halting_message = "Assertion failed: " });
823
824 context.halt();
825}
826
837{
838 BB_BENCH_NAME("Execution::jump");
840
841 context.set_next_pc(loc);
842}
843
857{
858 BB_BENCH_NAME("Execution::jumpi");
859 constexpr auto opcode = ExecutionOpCode::JUMPI;
860 auto& memory = context.get_memory();
861
862 const auto& resolved_cond = memory.get(cond_addr);
863 set_and_validate_inputs(opcode, { resolved_cond });
864
866
867 if (resolved_cond.as<uint1_t>().value() == 1) {
868 context.set_next_pc(loc);
869 }
870}
871
883{
884 BB_BENCH_NAME("Execution::internal_call");
886
887 auto& internal_call_stack_manager = context.get_internal_call_stack_manager();
888 // The next pc is pushed onto the internal call stack. This will become return_pc later.
889 internal_call_stack_manager.push(context.get_pc(), context.get_next_pc());
890 context.set_next_pc(loc);
891}
892
903{
904 BB_BENCH_NAME("Execution::internal_return");
906
907 auto& internal_call_stack_manager = context.get_internal_call_stack_manager();
908 try {
909 auto next_pc = internal_call_stack_manager.pop();
910 context.set_next_pc(next_pc);
911 } catch (const InternalCallStackException& e) {
912 // Re-throw
913 throw OpcodeExecutionException("Internal return failed: " + std::string(e.what()));
914 }
915}
916
930{
931 BB_BENCH_NAME("Execution::keccak_permutation");
933
934 try {
935 keccakf1600.permutation(context.get_memory(), dst_addr, src_addr);
936 } catch (const KeccakF1600Exception& e) {
937 throw OpcodeExecutionException("Keccak permutation failed: " + std::string(e.what()));
938 }
939}
940
956 MemoryAddress level_offset,
957 MemoryAddress message_offset,
958 MemoryAddress fields_offset,
959 MemoryAddress fields_size_offset,
960 uint16_t message_size)
961{
962 BB_BENCH_NAME("Execution::debug_log");
964
966 context.get_address(),
967 level_offset,
968 message_offset,
969 message_size,
970 fields_offset,
971 fields_size_offset);
972}
973
983{
984 BB_BENCH_NAME("Execution::success_copy");
985 constexpr auto opcode = ExecutionOpCode::SUCCESSCOPY;
986 auto& memory = context.get_memory();
987
989
990 MemoryValue success = MemoryValue::from<uint1_t>(context.get_last_success());
991 memory.set(dst_addr, success);
992 set_output(opcode, success);
993}
994
1009{
1010 BB_BENCH_NAME("Execution::and_op");
1011 constexpr auto opcode = ExecutionOpCode::AND;
1012 auto& memory = context.get_memory();
1013 const auto& a = memory.get(a_addr);
1014 const auto& b = memory.get(b_addr);
1015 set_and_validate_inputs(opcode, { a, b });
1016
1017 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1018 // will result in an exception in the bitwise subtrace.
1019 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1020
1021 try {
1022 MemoryValue c = bitwise.and_op(a, b);
1023 memory.set(dst_addr, c);
1024 set_output(opcode, c);
1025 } catch (const BitwiseException& e) {
1026 throw OpcodeExecutionException("Bitwise AND Exeception: " + std::string(e.what()));
1027 }
1028}
1029
1046{
1047 BB_BENCH_NAME("Execution::or_op");
1048 constexpr auto opcode = ExecutionOpCode::OR;
1049 auto& memory = context.get_memory();
1050 const auto& a = memory.get(a_addr);
1051 const auto& b = memory.get(b_addr);
1052 set_and_validate_inputs(opcode, { a, b });
1053
1054 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1055 // will result in an exception in the bitwise subtrace.
1056 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1057
1058 try {
1059 MemoryValue c = bitwise.or_op(a, b);
1060 memory.set(dst_addr, c);
1061 set_output(opcode, c);
1062 } catch (const BitwiseException& e) {
1063 throw OpcodeExecutionException("Bitwise OR Exception: " + std::string(e.what()));
1064 }
1065}
1066
1081{
1082 BB_BENCH_NAME("Execution::xor_op");
1083 constexpr auto opcode = ExecutionOpCode::XOR;
1084 auto& memory = context.get_memory();
1085 const auto& a = memory.get(a_addr);
1086 const auto& b = memory.get(b_addr);
1087 set_and_validate_inputs(opcode, { a, b });
1088
1089 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1090 // will result in an exception in the bitwise subtrace.
1091 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1092
1093 try {
1094 MemoryValue c = bitwise.xor_op(a, b);
1095 memory.set(dst_addr, c);
1096 set_output(opcode, c);
1097 } catch (const BitwiseException& e) {
1098 throw OpcodeExecutionException("Bitwise XOR Exception: " + std::string(e.what()));
1099 }
1100}
1101
1116{
1117 BB_BENCH_NAME("Execution::sload");
1118 constexpr auto opcode = ExecutionOpCode::SLOAD;
1119
1120 auto& memory = context.get_memory();
1121
1122 const auto& slot = memory.get(slot_addr);
1123 set_and_validate_inputs(opcode, { slot });
1124
1126
1127 auto value = MemoryValue::from<FF>(merkle_db.storage_read(context.get_address(), slot.as<FF>()));
1128
1129 memory.set(dst_addr, value);
1130 set_output(opcode, value);
1131}
1132
1149{
1150 BB_BENCH_NAME("Execution::sstore");
1151 constexpr auto opcode = ExecutionOpCode::SSTORE;
1152
1153 auto& memory = context.get_memory();
1154
1155 const auto& slot = memory.get(slot_addr);
1156 const auto& value = memory.get(src_addr);
1157 set_and_validate_inputs(opcode, { value, slot });
1158
1159 bool was_slot_written_before = merkle_db.was_storage_written(context.get_address(), slot.as_ff());
1160 uint32_t da_gas_factor = static_cast<uint32_t>(!was_slot_written_before);
1161 get_gas_tracker().consume_gas({ .l2_gas = 0, .da_gas = da_gas_factor });
1162
1163 if (context.get_is_static()) {
1165 "SSTORE: Static call cannot update the state. Cannot write to storage in static context");
1166 }
1167
1168 if (!was_slot_written_before &&
1170 throw OpcodeExecutionException("SSTORE: Maximum number of data writes reached");
1171 }
1172
1173 merkle_db.storage_write(context.get_address(), slot.as_ff(), value.as_ff(), false);
1174}
1175
1192 MemoryAddress unique_note_hash_addr,
1193 MemoryAddress leaf_index_addr,
1195{
1196 BB_BENCH_NAME("Execution::note_hash_exists");
1197 constexpr auto opcode = ExecutionOpCode::NOTEHASHEXISTS;
1198
1199 auto& memory = context.get_memory();
1200 const auto& unique_note_hash = memory.get(unique_note_hash_addr);
1201 const auto& leaf_index = memory.get(leaf_index_addr);
1202 set_and_validate_inputs(opcode, { unique_note_hash, leaf_index });
1203
1205
1206 uint64_t leaf_index_value = leaf_index.as<uint64_t>();
1207
1208 bool index_in_range = greater_than.gt(NOTE_HASH_TREE_LEAF_COUNT, leaf_index_value);
1209
1211
1212 if (index_in_range) {
1213 value = MemoryValue::from<uint1_t>(merkle_db.note_hash_exists(leaf_index_value, unique_note_hash.as<FF>()));
1214 } else {
1215 value = MemoryValue::from<uint1_t>(0);
1216 }
1217
1218 memory.set(dst_addr, value);
1219 set_output(opcode, value);
1220}
1221
1236 MemoryAddress nullifier_offset,
1237 MemoryAddress address_offset,
1238 MemoryAddress exists_offset)
1239{
1240 BB_BENCH_NAME("Execution::nullifier_exists");
1241 constexpr auto opcode = ExecutionOpCode::NULLIFIEREXISTS;
1242 auto& memory = context.get_memory();
1243
1244 const auto& nullifier = memory.get(nullifier_offset);
1245 const auto& address = memory.get(address_offset);
1247
1249
1250 // Check nullifier existence via MerkleDB
1251 // (this also tag checks address and nullifier as FFs)
1252 auto exists = merkle_db.nullifier_exists(address.as_ff(), nullifier.as_ff());
1253
1254 // Write result to memory
1255 // (assigns tag u1 to result)
1256 MemoryValue result = MemoryValue::from<uint1_t>(exists ? 1 : 0);
1257 memory.set(exists_offset, result);
1258 set_output(opcode, result);
1259}
1260
1276{
1277 BB_BENCH_NAME("Execution::emit_nullifier");
1278 constexpr auto opcode = ExecutionOpCode::EMITNULLIFIER;
1279
1280 auto& memory = context.get_memory();
1281 const auto& nullifier = memory.get(nullifier_addr);
1283
1285
1286 if (context.get_is_static()) {
1288 "EMITNULLIFIER: Static call cannot update the state. Cannot emit nullifier in static context");
1289 }
1290
1292 throw OpcodeExecutionException("EMITNULLIFIER: Maximum number of nullifiers reached");
1293 }
1294
1295 // Emit nullifier via MerkleDB.
1296 try {
1297 merkle_db.nullifier_write(context.get_address(), nullifier.as<FF>());
1298 } catch (const NullifierCollisionException& e) {
1299 throw OpcodeExecutionException(format("EMITNULLIFIER: ", e.what()));
1300 }
1301}
1302
1321 MemoryAddress address_offset,
1322 MemoryAddress dst_offset,
1323 uint8_t member_enum)
1324{
1325 BB_BENCH_NAME("Execution::get_contract_instance");
1326 constexpr auto opcode = ExecutionOpCode::GETCONTRACTINSTANCE;
1327 auto& memory = context.get_memory();
1328
1329 // Execution can still handle address memory read and tag checking
1330 const auto& address_value = memory.get(address_offset);
1331 set_and_validate_inputs(opcode, { address_value });
1332
1333 AztecAddress contract_address = address_value.as<AztecAddress>();
1334
1336
1337 // Call the dedicated opcode component to get the contract instance, validate the enum,
1338 // handle other errors, and perform the memory writes.
1339 try {
1341 } catch (const GetContractInstanceException& e) {
1342 throw OpcodeExecutionException("GetContractInstance Exception: " + std::string(e.what()));
1343 }
1344
1345 // No `set_output` here since the dedicated component handles memory writes.
1346}
1347
1362{
1363 BB_BENCH_NAME("Execution::emit_note_hash");
1364 constexpr auto opcode = ExecutionOpCode::EMITNOTEHASH;
1365
1366 auto& memory = context.get_memory();
1367 const auto& note_hash = memory.get(note_hash_addr);
1369
1371
1372 if (context.get_is_static()) {
1374 "EMITNOTEHASH: Static call cannot update the state. Cannot emit note hash in static context");
1375 }
1376
1378 throw OpcodeExecutionException("EMITNOTEHASH: Maximum number of note hashes reached");
1379 }
1380
1381 merkle_db.note_hash_write(context.get_address(), note_hash.as<FF>());
1382}
1383
1400 MemoryAddress msg_hash_addr,
1401 MemoryAddress leaf_index_addr,
1403{
1404 BB_BENCH_NAME("Execution::l1_to_l2_message_exists");
1405 constexpr auto opcode = ExecutionOpCode::L1TOL2MSGEXISTS;
1406
1407 auto& memory = context.get_memory();
1408 const auto& msg_hash = memory.get(msg_hash_addr);
1409 const auto& leaf_index = memory.get(leaf_index_addr);
1410 set_and_validate_inputs(opcode, { msg_hash, leaf_index });
1411
1413
1414 uint64_t leaf_index_value = leaf_index.as<uint64_t>();
1415
1416 bool index_in_range = greater_than.gt(L1_TO_L2_MSG_TREE_LEAF_COUNT, leaf_index_value);
1417
1419
1420 if (index_in_range) {
1421 value = MemoryValue::from<uint1_t>(merkle_db.l1_to_l2_msg_exists(leaf_index_value, msg_hash.as<FF>()));
1422 } else {
1423 value = MemoryValue::from<uint1_t>(0);
1424 }
1425
1426 memory.set(dst_addr, value);
1427 set_output(opcode, value);
1428}
1429
1444{
1445 BB_BENCH_NAME("Execution::poseidon2_permutation");
1447 try {
1448 poseidon2.permutation(context.get_memory(), src_addr, dst_addr);
1449 } catch (const Poseidon2Exception& e) {
1450 throw OpcodeExecutionException("Poseidon2 permutation failed: " + std::string(e.what()));
1451 }
1452}
1453
1480 MemoryAddress p_x_addr,
1481 MemoryAddress p_y_addr,
1482 MemoryAddress p_inf_addr,
1483 MemoryAddress q_x_addr,
1484 MemoryAddress q_y_addr,
1485 MemoryAddress q_inf_addr,
1487{
1488 BB_BENCH_NAME("Execution::ecc_add");
1489 constexpr auto opcode = ExecutionOpCode::ECADD;
1490 auto& memory = context.get_memory();
1491
1492 // Read the points from memory.
1493 const auto& p_x = memory.get(p_x_addr);
1494 const auto& p_y = memory.get(p_y_addr);
1495 const auto& p_inf = memory.get(p_inf_addr);
1496
1497 const auto& q_x = memory.get(q_x_addr);
1498 const auto& q_y = memory.get(q_y_addr);
1499 const auto& q_inf = memory.get(q_inf_addr);
1500
1501 set_and_validate_inputs(opcode, { p_x, p_y, p_inf, q_x, q_y, q_inf });
1503
1504 // Once inputs are tag checked the conversion to EmbeddedCurvePoint is safe, on curve checks are done in the add
1505 // method.
1506 EmbeddedCurvePoint p = EmbeddedCurvePoint(p_x.as_ff(), p_y.as_ff(), p_inf == MemoryValue::from<uint1_t>(1));
1507 EmbeddedCurvePoint q = EmbeddedCurvePoint(q_x.as_ff(), q_y.as_ff(), q_inf == MemoryValue::from<uint1_t>(1));
1508
1509 try {
1511 } catch (const EccException& e) {
1512 throw OpcodeExecutionException("Embedded curve add failed: " + std::string(e.what()));
1513 }
1514}
1515
1541 MemoryAddress value_addr,
1542 MemoryAddress radix_addr,
1543 MemoryAddress num_limbs_addr,
1544 MemoryAddress is_output_bits_addr, // Decides if output is U1 or U8
1546{
1547 BB_BENCH_NAME("Execution::to_radix_be");
1548 constexpr auto opcode = ExecutionOpCode::TORADIXBE;
1549 auto& memory = context.get_memory();
1550
1551 const auto& value = memory.get(value_addr); // Field
1552 const auto& radix = memory.get(radix_addr); // U32
1553 const auto& num_limbs = memory.get(num_limbs_addr); // U32
1554 const auto& is_output_bits = memory.get(is_output_bits_addr); // U1
1555
1556 // Tag check the inputs
1557 {
1558 BB_BENCH_NAME("Execution::to_radix_be::set_and_validate_inputs");
1559 set_and_validate_inputs(opcode, { value, radix, num_limbs, is_output_bits });
1560 }
1561
1562 // The range check for a valid radix (2 <= radix <= 256) is done in the gadget.
1563 // However, in order to compute the dynamic gas value we need to constrain the radix
1564 // to be <= 256 since the `get_p_limbs_per_radix` lookup table is only defined for the range [0, 256].
1565 // This does mean that the <= 256 check is duplicated - this can be optimized later.
1566
1567 // The dynamic gas factor is the maximum of the num_limbs requested by the opcode and the number of limbs
1568 // the gadget that the field modulus, p, decomposes into given a radix (num_p_limbs).
1569 // See to_radix.pil for how these values impact the row count.
1570
1571 // The lookup table of radix decomposed limbs of the modulus p is defined for radix values [0, 256],
1572 // so for any radix value greater than 256 we set num_p_limbs to 32 - with
1573 // the understanding the opcode will fail in the gadget (since the radix is invalid).
1574 uint32_t radix_value = radix.as<uint32_t>();
1575 uint32_t num_p_limbs = greater_than.gt(radix.as<uint32_t>(), 256)
1576 ? 32
1577 : static_cast<uint32_t>(get_p_limbs_per_radix_size(radix_value));
1578
1579 // Compute the dynamic gas factor - done this way to trigger relevant circuit interactions
1580 if (greater_than.gt(num_limbs.as<uint32_t>(), num_p_limbs)) {
1581 get_gas_tracker().consume_gas({ .l2_gas = num_limbs.as<uint32_t>(), .da_gas = 0 });
1582 } else {
1583 get_gas_tracker().consume_gas({ .l2_gas = num_p_limbs, .da_gas = 0 });
1584 }
1585
1586 try {
1587 // Call the gadget to perform the conversion.
1588 to_radix.to_be_radix(memory,
1589 value.as_ff(),
1590 radix.as<uint32_t>(),
1591 num_limbs.as<uint32_t>(),
1592 is_output_bits.as<uint1_t>().value() == 1,
1593 dst_addr);
1594 } catch (const ToRadixException& e) {
1595 throw OpcodeExecutionException("ToRadixBe gadget failed: " + std::string(e.what()));
1596 }
1597}
1598
1616{
1617 BB_BENCH_NAME("Execution::emit_unencrypted_log");
1618 constexpr auto opcode = ExecutionOpCode::EMITUNENCRYPTEDLOG;
1619 auto& memory = context.get_memory();
1620
1621 const auto& log_size = memory.get(log_size_offset);
1622 set_and_validate_inputs(opcode, { log_size });
1623 uint32_t log_size_int = log_size.as<uint32_t>();
1624
1625 get_gas_tracker().consume_gas({ .l2_gas = log_size_int, .da_gas = log_size_int });
1626
1627 // Call the dedicated opcode component to emit the log
1628 try {
1630 memory, context, context.get_address(), log_offset, log_size_int);
1631 } catch (const EmitUnencryptedLogException& e) {
1632 throw OpcodeExecutionException("EmitUnencryptedLog Exception: " + std::string(e.what()));
1633 }
1634}
1635
1652{
1653 BB_BENCH_NAME("Execution::send_l2_to_l1_msg");
1654 constexpr auto opcode = ExecutionOpCode::SENDL2TOL1MSG;
1655 auto& memory = context.get_memory();
1656
1657 const auto& recipient = memory.get(recipient_addr);
1658 const auto& content = memory.get(content_addr);
1660
1662
1663 if (context.get_is_static()) {
1665 "SENDL2TOL1MSG: Static call cannot update the state. Cannot send L2 to L1 message in static context");
1666 }
1667
1668 auto& side_effect_tracker = context.get_side_effect_tracker();
1669 const auto& side_effects = side_effect_tracker.get_side_effects();
1670
1671 if (side_effects.l2_to_l1_messages.size() == MAX_L2_TO_L1_MSGS_PER_TX) {
1672 throw OpcodeExecutionException("SENDL2TOL1MSG: Maximum number of L2 to L1 messages reached");
1673 }
1674
1675 side_effect_tracker.add_l2_to_l1_message(context.get_address(), EthAddress(recipient.as_ff()), content.as_ff());
1676}
1677
1693 MemoryAddress output_addr,
1694 MemoryAddress state_addr,
1695 MemoryAddress input_addr)
1696{
1697 BB_BENCH_NAME("Execution::sha256_compression");
1699
1700 try {
1701 sha256.compression(context.get_memory(), state_addr, input_addr, output_addr);
1702 } catch (const Sha256CompressionException& e) {
1703 throw OpcodeExecutionException("Sha256 Compression failed: " + std::string(e.what()));
1704 }
1705}
1706
1718{
1719 BB_BENCH_NAME("Execution::execute");
1720 call_stack_metadata_collector.notify_enter_call(enqueued_call_context->get_address(),
1721 0,
1722 make_calldata_provider(*enqueued_call_context),
1723 enqueued_call_context->get_is_static(),
1724 enqueued_call_context->get_gas_limit());
1725 external_call_stack.push(std::move(enqueued_call_context));
1726
1727 while (!external_call_stack.empty()) {
1728 // Throws CancelledException if cancelled. No-op when cancellation_token_ is nullptr (non-NAPI paths).
1729 if (cancellation_token_) {
1730 cancellation_token_->check_and_throw();
1731 }
1732
1733 // We fix the context at this point. Even if the opcode changes the stack
1734 // we'll always use this in the loop.
1735 auto& context = *external_call_stack.top();
1736
1737 // Default inputs and output initialization. This properly resets the values between two
1738 // opcode executions as well.
1739 inputs = {};
1740 output = MemoryValue::from_tag(static_cast<MemoryTag>(0), 0);
1741
1742 // Members of the execution event which are set in the try block.
1744 AddressingEvent addressing_event;
1747
1748 // State before doing anything.
1749 const auto before_context_event = context.serialize_context_event();
1750 const auto next_context_id = context_provider.get_next_context_id();
1751 const auto pc = context.get_pc();
1752
1753 try {
1754 // Temporality group 1: Bytecode retrieval. //
1755
1756 // We try to get the bytecode id. This can throw if the contract is not deployed or if we have retrieved too
1757 // many unique class ids. Note: bytecode_id is tracked in context events, not in the top-level execution
1758 // event. It is already included in the before_context_event (defaulting to 0 on error/not-found).
1759 context.get_bytecode_manager().get_bytecode_id();
1760
1761 // Temporality group 2: Instruction fetching and addressing. //
1762
1763 // We try to fetch an instruction.
1764 instruction = context.get_bytecode_manager().read_instruction(pc);
1765
1766 debug("@", pc, " ", instruction.to_string());
1767 context.set_next_pc(pc + static_cast<uint32_t>(instruction.size_in_bytes()));
1768 // next_pc is overwritten in dispatch_opcode() for JUMP, JUMPI, INTERNALCALL, and INTERNALRETURN.
1769
1770 // Resolve the operands.
1771 auto addressing = execution_components.make_addressing(addressing_event);
1772 std::vector<Operand> resolved_operands = addressing->resolve(instruction, context.get_memory());
1773
1775 // Temporality group 3: Registers read. (triggered in each opcode (dispatch_opcode()) with
1776 // set_and_validate_inputs(opcode, { ... });)
1777 // Temporality group 4: Gas. (triggered in each opcode (dispatch_opcode()) with
1778 // get_gas_tracker().consume_gas();)
1779 // Temporality group 5: Opcode execution. (in dispatch_opcode())
1780 // Temporality group 6: Register write. (in dispatch_opcode())
1781
1783 dispatch_opcode(instruction.get_exec_opcode(), context, resolved_operands);
1784 }
1785 // TODO(fcarreiro): handle this in a better way.
1786 catch (const BytecodeRetrievalError& e) {
1787 vinfo("Bytecode retrieval error:: ", e.what());
1790 } catch (const InstructionFetchingError& e) {
1791 vinfo("Instruction fetching error: ", e.what());
1794 } catch (const AddressingException& e) {
1795 vinfo("Addressing exception: ", e.what());
1798 } catch (const RegisterValidationException& e) {
1799 vinfo("Register validation exception: ", e.what());
1802 } catch (const OutOfGasException& e) {
1803 vinfo("Out of gas exception: ", e.what());
1804 error = ExecutionError::GAS;
1806 } catch (const OpcodeExecutionException& e) {
1807 vinfo("Opcode execution exception: ", e.what());
1810 } catch (const std::exception& e) {
1811 // This is a coding error, we should not get here.
1812 // All exceptions should fall in the above catch blocks.
1813 important("An unhandled exception occurred: ", e.what());
1814 throw;
1815 }
1816
1817 // We always do what follows. "Finally".
1818 // Move on to the next pc.
1819 context.set_pc(context.get_next_pc());
1821
1822 // TODO: We set the inputs and outputs here and into the execution event,
1823 // but maybe there's a better way to do this.
1824 events.emit({
1825 .error = error,
1826 .wire_instruction = instruction,
1827 .inputs = get_inputs(),
1828 .output = get_output(),
1829 .next_context_id = next_context_id,
1830 .addressing_event = addressing_event,
1831 .before_context_event = before_context_event,
1832 .after_context_event = context.serialize_context_event(),
1833 .gas_event = gas_event,
1834 });
1835
1836 // If the context has halted, we need to exit the external call.
1837 // The external call stack is expected to be popped.
1838 if (context.halted()) {
1840 }
1841 }
1842
1843 const ExecutionResult& result = get_execution_result();
1844 return {
1845 .success = result.success,
1846 .gas_used = result.gas_used,
1847 };
1848}
1849
1858{
1859 const auto& side_effects = parent_context.get_side_effect_tracker().get_side_effects();
1860
1861 // Optionally collect call stack metadata.
1862 call_stack_metadata_collector.notify_enter_call(child_context->get_address(),
1863 parent_context.get_pc(),
1864 make_calldata_provider(*child_context),
1865 child_context->get_is_static(),
1866 child_context->get_gas_limit());
1867
1868 ctx_stack_events.emit({
1869 .id = parent_context.get_context_id(),
1870 .parent_id = parent_context.get_parent_id(),
1871 .entered_context_id = child_context->get_context_id(), // gets the context id of the child!
1872 .next_pc = parent_context.get_next_pc(),
1873 .msg_sender = parent_context.get_msg_sender(),
1874 .contract_addr = parent_context.get_address(),
1875 .bytecode_id = parent_context.get_bytecode_manager()
1877 .value(), // Bytecode should have been retrieved in the parent context if it issued a call.
1878 .is_static = parent_context.get_is_static(),
1879 .parent_cd_addr = parent_context.get_parent_cd_addr(),
1880 .parent_cd_size = parent_context.get_parent_cd_size(),
1881 .parent_gas_used = parent_context.get_parent_gas_used(),
1882 .parent_gas_limit = parent_context.get_parent_gas_limit(),
1883 .tree_states = merkle_db.get_tree_state(),
1884 .written_public_data_slots_tree_snapshot = parent_context.get_written_public_data_slots_tree_snapshot(),
1885 // Non-tree-tracked side effects
1886 .numUnencryptedLogFields = side_effects.get_num_unencrypted_log_fields(),
1887 .numL2ToL1Messages = static_cast<uint32_t>(side_effects.l2_to_l1_messages.size()),
1888 });
1889
1890 external_call_stack.push(std::move(child_context));
1891}
1892
1897{
1898 BB_BENCH_NAME("Execution::handle_exit_call");
1899
1900 // NOTE: the current (child) context should not be modified here, since it was already emitted.
1902
1903 const ExecutionResult& result = get_execution_result();
1904
1905 // Optionally collect call stack metadata.
1907 result.success,
1908 result.halting_pc,
1909 result.halting_message,
1910 make_return_data_provider(*child_context, result.rd_offset, result.rd_size),
1911 make_internal_call_stack_provider(child_context->get_internal_call_stack_manager()));
1912
1913 external_call_stack.pop();
1914
1915 // We only handle reverting/committing of nested calls. Enqueued calls are handled by TX execution.
1916 if (!external_call_stack.empty()) {
1917 // Note: committing or reverting the db here also commits or reverts the
1918 // tracked nullifiers, public writes dictionary, etc. These structures
1919 // "listen" to the db changes.
1920 if (result.success) {
1922 } else {
1924 }
1925
1926 auto& parent_context = *external_call_stack.top();
1927 // was not top level, communicate with parent
1928 parent_context.set_last_rd_addr(result.rd_offset);
1929 parent_context.set_last_rd_size(result.rd_size);
1930 parent_context.set_last_success(result.success);
1931 // Safe since the nested context gas limit should be clamped to the available gas.
1932 parent_context.set_gas_used(result.gas_used + parent_context.get_gas_used());
1933 parent_context.set_child_context(std::move(child_context));
1934
1935 // TODO(fcarreiro): move somewhere else.
1936 if (parent_context.get_checkpoint_id_at_creation() != merkle_db.get_checkpoint_id()) {
1937 throw std::runtime_error(format("Checkpoint id mismatch: ",
1938 parent_context.get_checkpoint_id_at_creation(),
1939 " != ",
1941 " (gone back to the wrong db/context)"));
1942 }
1943 }
1944 // Else: was top level. ExecutionResult is already set and that will be returned.
1945}
1946
1954void Execution::handle_exceptional_halt(ContextInterface& context, const std::string& halting_message)
1955{
1956 context.set_gas_used(context.get_gas_limit()); // Consume all gas.
1957 context.halt();
1959 .rd_offset = 0,
1960 .rd_size = 0,
1961 .gas_used = context.get_gas_used(),
1962 .success = false,
1963 .halting_pc = context.get_pc(),
1964 .halting_message = halting_message,
1965 });
1966}
1967
1978 const std::vector<Operand>& resolved_operands)
1979{
1980 BB_BENCH_NAME("Execution::dispatch_opcode");
1981
1982 debug("Dispatching opcode: ", opcode, " (", static_cast<uint32_t>(opcode), ")");
1983 switch (opcode) {
1985 call_with_operands(&Execution::add, context, resolved_operands);
1986 break;
1988 call_with_operands(&Execution::sub, context, resolved_operands);
1989 break;
1991 call_with_operands(&Execution::mul, context, resolved_operands);
1992 break;
1994 call_with_operands(&Execution::div, context, resolved_operands);
1995 break;
1997 call_with_operands(&Execution::fdiv, context, resolved_operands);
1998 break;
2000 call_with_operands(&Execution::eq, context, resolved_operands);
2001 break;
2003 call_with_operands(&Execution::lt, context, resolved_operands);
2004 break;
2006 call_with_operands(&Execution::lte, context, resolved_operands);
2007 break;
2009 call_with_operands(&Execution::op_not, context, resolved_operands);
2010 break;
2012 call_with_operands(&Execution::shl, context, resolved_operands);
2013 break;
2015 call_with_operands(&Execution::shr, context, resolved_operands);
2016 break;
2018 call_with_operands(&Execution::cast, context, resolved_operands);
2019 break;
2022 break;
2024 call_with_operands(&Execution::set, context, resolved_operands);
2025 break;
2027 call_with_operands(&Execution::mov, context, resolved_operands);
2028 break;
2030 call_with_operands(&Execution::call, context, resolved_operands);
2031 break;
2034 break;
2036 call_with_operands(&Execution::ret, context, resolved_operands);
2037 break;
2039 call_with_operands(&Execution::revert, context, resolved_operands);
2040 break;
2042 call_with_operands(&Execution::jump, context, resolved_operands);
2043 break;
2045 call_with_operands(&Execution::jumpi, context, resolved_operands);
2046 break;
2048 call_with_operands(&Execution::cd_copy, context, resolved_operands);
2049 break;
2051 call_with_operands(&Execution::rd_copy, context, resolved_operands);
2052 break;
2055 break;
2058 break;
2061 break;
2064 break;
2066 call_with_operands(&Execution::rd_size, context, resolved_operands);
2067 break;
2069 call_with_operands(&Execution::debug_log, context, resolved_operands);
2070 break;
2072 call_with_operands(&Execution::and_op, context, resolved_operands);
2073 break;
2075 call_with_operands(&Execution::or_op, context, resolved_operands);
2076 break;
2078 call_with_operands(&Execution::xor_op, context, resolved_operands);
2079 break;
2081 call_with_operands(&Execution::sload, context, resolved_operands);
2082 break;
2084 call_with_operands(&Execution::sstore, context, resolved_operands);
2085 break;
2088 break;
2091 break;
2094 break;
2097 break;
2100 break;
2103 break;
2106 break;
2108 call_with_operands(&Execution::ecc_add, context, resolved_operands);
2109 break;
2112 break;
2115 break;
2118 break;
2121 break;
2122 default:
2123 // NOTE: Keep this a `std::runtime_error` so that the main loop panics.
2124 throw std::runtime_error("Tried to dispatch unknown execution opcode: " +
2125 std::to_string(static_cast<uint32_t>(opcode)));
2126 }
2127}
2128
2138template <typename... Ts>
2141 const std::vector<Operand>& resolved_operands)
2142{
2143 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2144 BB_ASSERT_DEBUG(resolved_operands.size() == sizeof...(Ts), "Resolved operands size mismatch");
2145 auto operand_indices = std::make_index_sequence<sizeof...(Ts)>{};
2146 [f, this, &context, &resolved_operands]<std::size_t... Is>(std::index_sequence<Is...>) {
2147 (this->*f)(context, resolved_operands.at(Is).to<std::decay_t<Ts>>()...);
2148 }(operand_indices);
2149}
2150
2159{
2160 const auto& register_info = instruction_info_db.get(opcode).register_info;
2161 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2162 BB_ASSERT_DEBUG(inputs.size() == register_info.num_inputs(), "Inputs size mismatch");
2163 this->inputs = inputs;
2164 for (size_t i = 0; i < register_info.num_inputs(); i++) {
2165 if (register_info.expected_tag(i) && register_info.expected_tag(i) != this->inputs.at(i).get_tag()) {
2166 throw RegisterValidationException(format("Input ",
2167 i,
2168 " tag ",
2169 std::to_string(this->inputs.at(i).get_tag()),
2170 " does not match expected tag ",
2171 std::to_string(*register_info.expected_tag(i))));
2172 }
2173 }
2174}
2175
2183{
2184 const auto& register_info = instruction_info_db.get(opcode).register_info;
2185 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2186 BB_ASSERT_DEBUG(register_info.num_outputs() == 1, "Outputs size mismatch");
2187 this->output = output;
2188}
2189
2190} // namespace bb::avm2::simulation
MemoryTag dst_tag
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
#define NOTE_HASH_TREE_LEAF_COUNT
#define L1_TO_L2_MSG_TREE_LEAF_COUNT
#define MAX_L2_TO_L1_MSGS_PER_TX
#define MAX_NOTE_HASHES_PER_TX
#define MAX_NULLIFIERS_PER_TX
#define MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:219
static TaggedValue from_tag(ValueTag tag, FF value)
virtual std::optional< BytecodeId > get_retrieved_bytecode_id()=0
virtual void notify_exit_call(bool success, uint32_t pc, const std::optional< std::string > &halting_message, const ReturnDataProvider &return_data_provider, const InternalCallStackProvider &internal_call_stack_provider)=0
virtual void notify_enter_call(const AztecAddress &contract_address, uint32_t caller_pc, const CalldataProvider &calldata_provider, bool is_static_call, const Gas &gas_limit)=0
virtual uint32_t get_pc() const =0
virtual const AztecAddress & get_msg_sender() const =0
virtual Gas get_parent_gas_limit() const =0
virtual uint32_t get_next_pc() const =0
virtual uint32_t get_parent_cd_size() const =0
virtual SideEffectTrackerInterface & get_side_effect_tracker()=0
virtual MemoryAddress get_parent_cd_addr() const =0
virtual AppendOnlyTreeSnapshot get_written_public_data_slots_tree_snapshot()=0
virtual uint32_t get_parent_id() const =0
virtual bool get_is_static() const =0
virtual BytecodeManagerInterface & get_bytecode_manager()=0
virtual const AztecAddress & get_address() const =0
virtual uint32_t get_context_id() const =0
virtual Gas get_parent_gas_used() const =0
virtual std::unique_ptr< ContextInterface > make_nested_context(const AztecAddress &address, const AztecAddress &msg_sender, const FF &transaction_fee, ContextInterface &parent_context, MemoryAddress cd_offset_address, uint32_t cd_size, bool is_static, const Gas &gas_limit, TransactionPhase phase)=0
virtual uint32_t get_next_context_id() const =0
virtual void debug_log(MemoryInterface &memory, AztecAddress contract_address, MemoryAddress level_offset, MemoryAddress message_offset, uint16_t message_size, MemoryAddress fields_offset, MemoryAddress fields_size_offset)=0
virtual EmbeddedCurvePoint add(const EmbeddedCurvePoint &p, const EmbeddedCurvePoint &q)=0
virtual void emit_unencrypted_log(MemoryInterface &memory, ContextInterface &context, const AztecAddress &contract_address, MemoryAddress log_offset, uint32_t log_size)=0
virtual std::unique_ptr< GasTrackerInterface > make_gas_tracker(GasEvent &gas_event, const Instruction &instruction, ContextInterface &context)=0
virtual std::unique_ptr< AddressingInterface > make_addressing(AddressingEvent &event)=0
void lt(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
LT execution opcode handler: Check if the first value is less than the second.
void emit_note_hash(ContextInterface &context, MemoryAddress note_hash_addr)
EMITNOTEHASH execution opcode handler: Emit a note hash to the note hash tree.
std::vector< MemoryValue > inputs
void mov(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr)
MOV execution opcode handler: Move a memory value from one memory location to another.
void static_call(ContextInterface &context, MemoryAddress l2_gas_offset, MemoryAddress da_gas_offset, MemoryAddress addr, MemoryAddress cd_size_offset, MemoryAddress cd_offset)
STATICCALL execution opcode handler: Call a contract in a static context. Creates a new (nested) exec...
void debug_log(ContextInterface &context, MemoryAddress level_offset, MemoryAddress message_offset, MemoryAddress fields_offset, MemoryAddress fields_size_offset, uint16_t message_size)
DEBUGLOG execution opcode handler: Log a debug message. Logs a debug message to the debug logger if t...
EventEmitterInterface< ExecutionEvent > & events
void cd_copy(ContextInterface &context, MemoryAddress cd_size_offset, MemoryAddress cd_offset, MemoryAddress dst_addr)
CALLDATACOPY execution opcode handler: Copy calldata from the parent context to the current context....
std::unique_ptr< GasTrackerInterface > gas_tracker
void send_l2_to_l1_msg(ContextInterface &context, MemoryAddress recipient_addr, MemoryAddress content_addr)
SENDL2TOL1MSG execution opcode handler: Send a L2 to L1 message.
void dispatch_opcode(ExecutionOpCode opcode, ContextInterface &context, const std::vector< Operand > &resolved_operands)
Dispatch an opcode. This is the main function that dispatches the opcode to the appropriate handler.
void set_execution_result(const ExecutionResult &exec_result)
ExecutionComponentsProviderInterface & execution_components
void sstore(ContextInterface &context, MemoryAddress src_addr, MemoryAddress slot_addr)
SSTORE execution opcode handler: Store a value in the public data tree.
const std::vector< MemoryValue > & get_inputs() const
void set(ContextInterface &context, MemoryAddress dst_addr, uint8_t tag, const FF &value)
SET execution opcode handler: Set the value at a memory location. If the value does not fit in the ta...
void internal_return(ContextInterface &context)
INTERNALRETURN execution opcode handler: Return from a function in the current context....
void set_output(ExecutionOpCode opcode, const MemoryValue &output)
Set the output register.
virtual GasTrackerInterface & get_gas_tracker()
void poseidon2_permutation(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr)
POSEIDON2PERMUTATION execution opcode handler: Perform a Poseidon2 permutation on the input value....
void success_copy(ContextInterface &context, MemoryAddress dst_addr)
SUCCESSCOPY execution opcode handler: Copy the success flag to the destination memory location.
void fdiv(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
FDIV execution opcode handler: Divide two field values.
void jumpi(ContextInterface &context, MemoryAddress cond_addr, uint32_t loc)
JUMPI execution opcode handler: Jump to a new program counter conditionally. Next instruction will be...
void sub(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
SUB execution opcode handler: Subtract two values.
CallStackMetadataCollectorInterface & call_stack_metadata_collector
void rd_copy(ContextInterface &context, MemoryAddress rd_size_offset, MemoryAddress rd_offset, MemoryAddress dst_addr)
RETURNDATACOPY execution opcode handler: Copy return data from the current context to the parent cont...
void l1_to_l2_message_exists(ContextInterface &context, MemoryAddress msg_hash_addr, MemoryAddress leaf_index_addr, MemoryAddress dst_addr)
L1TOL2MSGEXISTS execution opcode handler: Check if a L2 to L1 message exists in the L1 to L2 message ...
void emit_unencrypted_log(ContextInterface &context, MemoryAddress log_size_offset, MemoryAddress log_offset)
EMITUNENCRYPTEDLOG execution opcode handler: Emit an unencrypted log.
void div(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
DIV execution opcode handler: Divide two values.
void emit_nullifier(ContextInterface &context, MemoryAddress nullifier_addr)
EMITNULLIFIER execution opcode handler: Emit a nullifier to the nullifier tree.
EventEmitterInterface< ContextStackEvent > & ctx_stack_events
const MemoryValue & get_output() const
void ecc_add(ContextInterface &context, MemoryAddress p_x_addr, MemoryAddress p_y_addr, MemoryAddress p_inf_addr, MemoryAddress q_x_addr, MemoryAddress q_y_addr, MemoryAddress q_inf_addr, MemoryAddress dst_addr)
ECADD execution opcode handler: Perform an elliptic curve addition and write the result to the destin...
void keccak_permutation(ContextInterface &context, MemoryAddress dst_addr, MemoryAddress src_addr)
KECCAKF1600 execution opcode handler: Perform a Keccak permutation on the data.
void jump(ContextInterface &context, uint32_t loc)
JUMP execution opcode handler: Jump to a new program counter. Next instruction will be executed at th...
const ExecutionResult & get_execution_result() const
void sha256_compression(ContextInterface &context, MemoryAddress output_addr, MemoryAddress state_addr, MemoryAddress input_addr)
SHA256COMPRESSION execution opcode handler: Perform a SHA256 compression on the input and state value...
void sload(ContextInterface &context, MemoryAddress slot_addr, MemoryAddress dst_addr)
SLOAD execution opcode handler: Load a value from the public data tree. Loads a value from the public...
void ret(ContextInterface &context, MemoryAddress ret_size_offset, MemoryAddress ret_offset)
RETURN execution opcode handler: Return from a contract. Sets the execution result to the return data...
void internal_call(ContextInterface &context, uint32_t loc)
INTERNALCALL execution opcode handler: Call a function in the current context. Pushes the current pro...
CancellationTokenPtr cancellation_token_
void op_not(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr)
NOT execution opcode handler: Perform bitwise NOT operation on a value.
void shl(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
SHL execution opcode handler: Perform left shift operation on a value.
void handle_enter_call(ContextInterface &parent_context, std::unique_ptr< ContextInterface > child_context)
Handle the entering of a call. This is called when a call is made from a context. This is a helper fu...
void nullifier_exists(ContextInterface &context, MemoryAddress nullifier_offset, MemoryAddress address_offset, MemoryAddress exists_offset)
NULLIFIEREXISTS execution opcode handler: Check if a nullifier exists in the nullifier tree.
void handle_exceptional_halt(ContextInterface &context, const std::string &halting_message)
Handle the exceptional halt of a context. This is called when an exception is thrown during the execu...
void note_hash_exists(ContextInterface &context, MemoryAddress unique_note_hash_addr, MemoryAddress leaf_index_addr, MemoryAddress dst_addr)
NOTEHASHEXISTS execution opcode handler: Check if a note hash exists in the note hash tree at the spe...
ContextProviderInterface & context_provider
void cast(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr, uint8_t dst_tag)
CAST execution opcode handler: Cast a value to a different tag.
void to_radix_be(ContextInterface &context, MemoryAddress value_addr, MemoryAddress radix_addr, MemoryAddress num_limbs_addr, MemoryAddress is_output_bits_addr, MemoryAddress dst_addr)
TORADIXBE execution opcode handler: Convert a value to a radix-based representation....
EmitUnencryptedLogInterface & emit_unencrypted_log_component
void eq(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
EQ execution opcode handler: Check if two values are equal.
void call(ContextInterface &context, MemoryAddress l2_gas_offset, MemoryAddress da_gas_offset, MemoryAddress addr, MemoryAddress cd_size_offset, MemoryAddress cd_offset)
CALL execution opcode handler: Call a contract. Creates a new (nested) execution context and triggers...
std::stack< std::unique_ptr< ContextInterface > > external_call_stack
void handle_exit_call()
Handle the exiting of a call. This is called when a call returns or reverts.
GreaterThanInterface & greater_than
void revert(ContextInterface &context, MemoryAddress rev_size_offset, MemoryAddress rev_offset)
REVERT execution opcode handler: Revert from a contract. Sets the execution result to the revert data...
void rd_size(ContextInterface &context, MemoryAddress dst_addr)
RETURNDATASIZE execution opcode handler: Get the size of the return data.
void mul(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
MUL execution opcode handler: Multiply two values.
void or_op(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
OR execution opcode handler: Perform a bitwise OR operation on the two input values.
void shr(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
SHR execution opcode handler: Perform right shift operation on a value.
DebugLoggerInterface & debug_log_component
void xor_op(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
XOR execution opcode handler: Perform a bitwise XOR operation on the two input values.
void get_contract_instance(ContextInterface &context, MemoryAddress address_offset, MemoryAddress dst_offset, uint8_t member_enum)
GETCONTRACTINSTANCE execution opcode handler: Get a contract instance. Gets a contract instance from ...
GetContractInstanceInterface & get_contract_instance_component
void set_and_validate_inputs(ExecutionOpCode opcode, const std::vector< MemoryValue > &inputs)
Set the register inputs and validate the tags. The tag information is taken from the instruction info...
void get_env_var(ContextInterface &context, MemoryAddress dst_addr, uint8_t var_enum)
GETENVVAR execution opcode handler: Get the value of an environment variable.
void lte(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
LTE execution opcode handler: Check if the first value is less than or equal to the second.
const InstructionInfoDBInterface & instruction_info_db
HighLevelMerkleDBInterface & merkle_db
void and_op(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
AND execution opcode handler: Perform a bitwise AND operation on the two input values.
EnqueuedCallResult execute(std::unique_ptr< ContextInterface > enqueued_call_context) override
Execute a top-level enqueued call.
ExecutionIdManagerInterface & execution_id_manager
void add(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
ADD execution opcode handler: Add two values.
Definition execution.cpp:72
void call_with_operands(void(Execution::*f)(ContextInterface &, Ts...), ContextInterface &context, const std::vector< Operand > &resolved_operands)
Call with operands. This is a template magic function to dispatch the opcode by deducing the number o...
virtual void consume_gas(const Gas &dynamic_gas_factor={ 0, 0 })=0
virtual Gas compute_gas_limit_for_call(const Gas &allocated_gas)=0
virtual void get_contract_instance(MemoryInterface &memory, const AztecAddress &contract_address, MemoryAddress dst_offset, uint8_t member_enum)=0
virtual bool gt(const FF &a, const FF &b)=0
virtual bool note_hash_exists(uint64_t leaf_index, const FF &unique_note_hash) const =0
virtual FF storage_read(const AztecAddress &contract_address, const FF &slot) const =0
virtual uint32_t get_checkpoint_id() const =0
virtual bool was_storage_written(const AztecAddress &contract_address, const FF &slot) const =0
virtual void note_hash_write(const AztecAddress &contract_address, const FF &note_hash)=0
virtual bool nullifier_exists(const AztecAddress &contract_address, const FF &nullifier) const =0
virtual void storage_write(const AztecAddress &contract_address, const FF &slot, const FF &value, bool is_protocol_write)=0
virtual bool l1_to_l2_msg_exists(uint64_t leaf_index, const FF &msg_hash) const =0
virtual void nullifier_write(const AztecAddress &contract_address, const FF &nullifier)=0
virtual TreeStates get_tree_state() const =0
virtual const ExecInstructionSpec & get(ExecutionOpCode opcode) const =0
virtual const TrackedSideEffects & get_side_effects() const =0
A 1-bit unsigned integer type.
Definition uint1.hpp:15
constexpr uint8_t value() const noexcept
Definition uint1.hpp:62
std::string format(Args... args)
Definition log.hpp:24
#define vinfo(...)
Definition log.hpp:94
#define debug(...)
Definition log.hpp:76
void important(Args... args)
Definition log.hpp:104
uint32_t dst_addr
FF a
FF b
uint64_t da_gas
GasEvent gas_event
Instruction instruction
AvmProvingInputs inputs
InternalCallStackProvider make_internal_call_stack_provider(const InternalCallStackManagerInterface &internal_call_stack_manager)
ReturnDataProvider make_return_data_provider(const ContextInterface &context, uint32_t rd_offset, uint32_t rd_size)
CalldataProvider make_calldata_provider(const ContextInterface &context)
size_t get_p_limbs_per_radix_size(size_t radix)
Definition to_radix.cpp:54
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
uint32_t MemoryAddress
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint8_t get_tag_bytes(ValueTag tag)
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
uint32_t cd_offset
std::optional< std::string > halting_message
SideEffectTracker side_effect_tracker