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 <cstddef>
4#include <stdexcept>
5#include <type_traits>
6
21// Interface headers not included in execution.hpp because the types are forward-declared there.
43
44namespace bb::avm2::simulation {
45
76{
77 BB_BENCH_NAME("Execution::add");
78 constexpr auto opcode = ExecutionOpCode::ADD;
79 auto& memory = context.get_memory();
80 const MemoryValue a = memory.get(a_addr);
81 const MemoryValue b = memory.get(b_addr);
82 set_and_validate_inputs(opcode, { a, b });
83
85
86 try {
87 MemoryValue c = alu.add(a, b);
88 memory.set(dst_addr, c);
89 set_output(opcode, c);
90 } catch (AluException& e) {
91 throw OpcodeExecutionException("Alu add operation failed: " + std::string(e.what()));
92 }
93}
94
107{
108 BB_BENCH_NAME("Execution::sub");
109 constexpr auto opcode = ExecutionOpCode::SUB;
110 auto& memory = context.get_memory();
111 const MemoryValue a = memory.get(a_addr);
112 const MemoryValue b = memory.get(b_addr);
113 set_and_validate_inputs(opcode, { a, b });
114
116
117 try {
118 MemoryValue c = alu.sub(a, b);
119 memory.set(dst_addr, c);
120 set_output(opcode, c);
121 } catch (AluException& e) {
122 throw OpcodeExecutionException("Alu sub operation failed");
123 }
124}
125
138{
139 BB_BENCH_NAME("Execution::mul");
140 constexpr auto opcode = ExecutionOpCode::MUL;
141 auto& memory = context.get_memory();
142 const MemoryValue a = memory.get(a_addr);
143 const MemoryValue b = memory.get(b_addr);
144 set_and_validate_inputs(opcode, { a, b });
145
147
148 try {
149 MemoryValue c = alu.mul(a, b);
150 memory.set(dst_addr, c);
151 set_output(opcode, c);
152 } catch (AluException& e) {
153 throw OpcodeExecutionException("Alu mul operation failed: " + std::string(e.what()));
154 }
155}
156
172{
173 BB_BENCH_NAME("Execution::div");
174 constexpr auto opcode = ExecutionOpCode::DIV;
175 auto& memory = context.get_memory();
176 const MemoryValue a = memory.get(a_addr);
177 const MemoryValue b = memory.get(b_addr);
178 set_and_validate_inputs(opcode, { a, b });
179
181
182 try {
183 MemoryValue c = alu.div(a, b);
184 memory.set(dst_addr, c);
185 set_output(opcode, c);
186 } catch (AluException& e) {
187 throw OpcodeExecutionException("Alu div operation failed: " + std::string(e.what()));
188 }
189}
190
206{
207 BB_BENCH_NAME("Execution::fdiv");
208 constexpr auto opcode = ExecutionOpCode::FDIV;
209 auto& memory = context.get_memory();
210 const MemoryValue a = memory.get(a_addr);
211 const MemoryValue b = memory.get(b_addr);
212 set_and_validate_inputs(opcode, { a, b });
213
215
216 try {
217 MemoryValue c = alu.fdiv(a, b);
218 memory.set(dst_addr, c);
219 set_output(opcode, c);
220 } catch (AluException& e) {
221 throw OpcodeExecutionException("Alu fdiv operation failed: " + std::string(e.what()));
222 }
223}
224
237{
238 BB_BENCH_NAME("Execution::eq");
239 constexpr auto opcode = ExecutionOpCode::EQ;
240 auto& memory = context.get_memory();
241 const MemoryValue a = memory.get(a_addr);
242 const MemoryValue b = memory.get(b_addr);
243 set_and_validate_inputs(opcode, { a, b });
244
246
247 try {
248 MemoryValue c = alu.eq(a, b);
249 memory.set(dst_addr, c);
250 set_output(opcode, c);
251 } catch (AluException& e) {
252 throw OpcodeExecutionException("Alu eq operation failed: " + std::string(e.what()));
253 }
254}
255
268{
269 BB_BENCH_NAME("Execution::lt");
270 constexpr auto opcode = ExecutionOpCode::LT;
271 auto& memory = context.get_memory();
272 const MemoryValue a = memory.get(a_addr);
273 const MemoryValue b = memory.get(b_addr);
274 set_and_validate_inputs(opcode, { a, b });
275
277
278 try {
279 MemoryValue c = alu.lt(a, b);
280 memory.set(dst_addr, c);
281 set_output(opcode, c);
282 } catch (AluException& e) {
283 throw OpcodeExecutionException("Alu lt operation failed: " + std::string(e.what()));
284 }
285}
286
299{
300 BB_BENCH_NAME("Execution::lte");
301 constexpr auto opcode = ExecutionOpCode::LTE;
302 auto& memory = context.get_memory();
303 const MemoryValue a = memory.get(a_addr);
304 const MemoryValue b = memory.get(b_addr);
305 set_and_validate_inputs(opcode, { a, b });
306
308
309 try {
310 MemoryValue c = alu.lte(a, b);
311 memory.set(dst_addr, c);
312 set_output(opcode, c);
313 } catch (AluException& e) {
314 throw OpcodeExecutionException("Alu lte operation failed: " + std::string(e.what()));
315 }
316}
317
329{
330 BB_BENCH_NAME("Execution::op_not");
331 constexpr auto opcode = ExecutionOpCode::NOT;
332 auto& memory = context.get_memory();
333 const MemoryValue a = memory.get(src_addr);
334 set_and_validate_inputs(opcode, { a });
335
337
338 try {
339 MemoryValue b = alu.op_not(a);
340 memory.set(dst_addr, b);
341 set_output(opcode, b);
342 } catch (AluException& e) {
343 throw OpcodeExecutionException("Alu not operation failed: " + std::string(e.what()));
344 }
345}
346
361{
362 BB_BENCH_NAME("Execution::shl");
363 constexpr auto opcode = ExecutionOpCode::SHL;
364 auto& memory = context.get_memory();
365 const MemoryValue a = memory.get(a_addr);
366 const MemoryValue b = memory.get(b_addr);
367 set_and_validate_inputs(opcode, { a, b });
368
370
371 try {
372 MemoryValue c = alu.shl(a, b);
373 memory.set(dst_addr, c);
374 set_output(opcode, c);
375 } catch (const AluException& e) {
376 throw OpcodeExecutionException("SHL Exception: " + std::string(e.what()));
377 }
378}
379
394{
395 BB_BENCH_NAME("Execution::shr");
396 constexpr auto opcode = ExecutionOpCode::SHR;
397 auto& memory = context.get_memory();
398 const MemoryValue a = memory.get(a_addr);
399 const MemoryValue b = memory.get(b_addr);
400 set_and_validate_inputs(opcode, { a, b });
401
403
404 try {
405 MemoryValue c = alu.shr(a, b);
406 memory.set(dst_addr, c);
407 set_output(opcode, c);
408 } catch (const AluException& e) {
409 throw OpcodeExecutionException("SHR Exception: " + std::string(e.what()));
410 }
411}
412
424{
425 BB_BENCH_NAME("Execution::cast");
426 constexpr auto opcode = ExecutionOpCode::CAST;
427 auto& memory = context.get_memory();
428 const auto& val = memory.get(src_addr);
429 set_and_validate_inputs(opcode, { val });
430
432 MemoryValue truncated = alu.truncate(val.as_ff(), dst_tag);
433 memory.set(dst_addr, truncated);
434 set_output(opcode, truncated);
435}
436
449{
450 BB_BENCH_NAME("Execution::get_env_var");
451 constexpr auto opcode = ExecutionOpCode::GETENVVAR;
452 auto& memory = context.get_memory();
453
455
456 // If env_var_value is not a valid EnvironmentVariable enum value, throw an OpcodeExecutionException.
457 if (env_var_value > static_cast<uint8_t>(EnvironmentVariable::MAX)) {
458 throw OpcodeExecutionException("Invalid environment variable enum value");
459 }
460
461 MemoryValue result;
462
463 switch (static_cast<EnvironmentVariable>(env_var_value)) {
465 result = MemoryValue::from<FF>(context.get_address());
466 break;
468 result = MemoryValue::from<FF>(context.get_msg_sender());
469 break;
471 result = MemoryValue::from<FF>(context.get_transaction_fee());
472 break;
474 result = MemoryValue::from<FF>(context.get_globals().chain_id);
475 break;
477 result = MemoryValue::from<FF>(context.get_globals().version);
478 break;
480 result = MemoryValue::from<uint32_t>(context.get_globals().block_number);
481 break;
483 result = MemoryValue::from<uint64_t>(context.get_globals().timestamp);
484 break;
486 result = MemoryValue::from<uint128_t>(context.get_globals().gas_fees.fee_per_l2_gas);
487 break;
489 result = MemoryValue::from<uint128_t>(context.get_globals().gas_fees.fee_per_da_gas);
490 break;
492 result = MemoryValue::from<uint1_t>(context.get_is_static() ? 1 : 0);
493 break;
495 result = MemoryValue::from<uint32_t>(context.gas_left().l2_gas);
496 break;
498 result = MemoryValue::from<uint32_t>(context.gas_left().da_gas);
499 break;
500 default:
501 // We leave this here defensively.
502 throw OpcodeExecutionException("Invalid environment variable enum value");
503 }
504
505 memory.set(dst_addr, result);
506 set_output(opcode, result);
507}
508
521{
522 BB_BENCH_NAME("Execution::set");
524
525 constexpr auto opcode = ExecutionOpCode::SET;
526 MemoryValue truncated = alu.truncate(value, dst_tag);
527 context.get_memory().set(dst_addr, truncated);
528 set_output(opcode, truncated);
529}
530
541{
542 BB_BENCH_NAME("Execution::mov");
543 constexpr auto opcode = ExecutionOpCode::MOV;
544 auto& memory = context.get_memory();
545 const MemoryValue v = memory.get(src_addr);
546 set_and_validate_inputs(opcode, { v });
547
549
550 memory.set(dst_addr, v);
551 set_output(opcode, v);
552}
553
576 MemoryAddress l2_gas_offset,
577 MemoryAddress da_gas_offset,
578 MemoryAddress addr,
579 MemoryAddress args_size_offset,
580 MemoryAddress args_offset)
581{
582 BB_BENCH_NAME("Execution::call");
583 constexpr auto opcode = ExecutionOpCode::CALL;
584 auto& memory = context.get_memory();
585
586 // NOTE: these reads cannot fail due to addressing guarantees.
587 const auto& allocated_l2_gas_read = memory.get(l2_gas_offset);
588 const auto& allocated_da_gas_read = memory.get(da_gas_offset);
589 const auto& contract_address = memory.get(addr);
590 // Args offset load is deferred to calldatacopy
591 const auto& args_size = memory.get(args_size_offset);
592
593 set_and_validate_inputs(opcode, { allocated_l2_gas_read, allocated_da_gas_read, contract_address, args_size });
594
595 get_gas_tracker().consume_gas(); // Base gas.
597 Gas{ .l2_gas = allocated_l2_gas_read.as<uint32_t>(), .da_gas = allocated_da_gas_read.as<uint32_t>() });
598
599 // Tag check contract address + args_size
600 auto nested_context = context_provider.make_nested_context(contract_address,
601 /*msg_sender=*/context.get_address(),
602 /*transaction_fee=*/context.get_transaction_fee(),
603 /*parent_context=*/context,
604 /*cd_offset_address=*/args_offset,
605 /*cd_size=*/args_size.as<uint32_t>(),
606 /*is_static=*/context.get_is_static(),
607 /*gas_limit=*/gas_limit,
608 /*phase=*/context.get_phase());
609
610 // We do not recurse. This context will be use on the next cycle of execution.
611 handle_enter_call(context, std::move(nested_context));
612}
613
636 MemoryAddress l2_gas_offset,
637 MemoryAddress da_gas_offset,
638 MemoryAddress addr,
639 MemoryAddress args_size_offset,
640 MemoryAddress args_offset)
641{
642 BB_BENCH_NAME("Execution::static_call");
643 constexpr auto opcode = ExecutionOpCode::STATICCALL;
644 auto& memory = context.get_memory();
645
646 // NOTE: these reads cannot fail due to addressing guarantees.
647 const auto& allocated_l2_gas_read = memory.get(l2_gas_offset);
648 const auto& allocated_da_gas_read = memory.get(da_gas_offset);
649 const auto& contract_address = memory.get(addr);
650 // Args offset load is deferred to calldatacopy
651 const auto& args_size = memory.get(args_size_offset);
652
653 set_and_validate_inputs(opcode, { allocated_l2_gas_read, allocated_da_gas_read, contract_address, args_size });
654
655 get_gas_tracker().consume_gas(); // Base gas.
657 Gas{ .l2_gas = allocated_l2_gas_read.as<uint32_t>(), .da_gas = allocated_da_gas_read.as<uint32_t>() });
658
659 // Tag check contract address + args_size
660 auto nested_context = context_provider.make_nested_context(contract_address,
661 /*msg_sender=*/context.get_address(),
662 /*transaction_fee=*/context.get_transaction_fee(),
663 /*parent_context=*/context,
664 /*cd_offset_address=*/args_offset,
665 /*cd_size=*/args_size.as<uint32_t>(),
666 /*is_static=*/true,
667 /*gas_limit=*/gas_limit,
668 /*phase=*/context.get_phase());
669
670 // We do not recurse. This context will be use on the next cycle of execution.
671 handle_enter_call(context, std::move(nested_context));
672}
673
691 MemoryAddress cd_size_offset,
694{
695 BB_BENCH_NAME("Execution::cd_copy");
696 constexpr auto opcode = ExecutionOpCode::CALLDATACOPY;
697 auto& memory = context.get_memory();
698 const auto& cd_copy_size = memory.get(cd_size_offset); // Tag check u32
699 const auto& cd_offset_read = memory.get(cd_offset); // Tag check u32
700 set_and_validate_inputs(opcode, { cd_copy_size, cd_offset_read });
701
702 get_gas_tracker().consume_gas({ .l2_gas = cd_copy_size.as<uint32_t>(), .da_gas = 0 });
703
704 try {
705 data_copy.cd_copy(context, cd_copy_size.as<uint32_t>(), cd_offset_read.as<uint32_t>(), dst_addr);
706 } catch (const DataCopyException& e) {
707 throw OpcodeExecutionException("cd copy failed: " + std::string(e.what()));
708 }
709}
710
727 MemoryAddress rd_size_offset,
728 MemoryAddress rd_offset,
730{
731 BB_BENCH_NAME("Execution::rd_copy");
732 constexpr auto opcode = ExecutionOpCode::RETURNDATACOPY;
733 auto& memory = context.get_memory();
734 const auto& rd_copy_size = memory.get(rd_size_offset); // Tag check u32
735 const auto& rd_offset_read = memory.get(rd_offset); // Tag check u32
736 set_and_validate_inputs(opcode, { rd_copy_size, rd_offset_read });
737
738 get_gas_tracker().consume_gas({ .l2_gas = rd_copy_size.as<uint32_t>(), .da_gas = 0 });
739
740 try {
741 data_copy.rd_copy(context, rd_copy_size.as<uint32_t>(), rd_offset_read.as<uint32_t>(), dst_addr);
742 } catch (const DataCopyException& e) {
743 throw OpcodeExecutionException("rd copy failed: " + std::string(e.what()));
744 }
745}
746
756{
757 BB_BENCH_NAME("Execution::rd_size");
758 constexpr auto opcode = ExecutionOpCode::RETURNDATASIZE;
759 auto& memory = context.get_memory();
760
762
763 // This is safe because the last_rd_size is tag checked on ret/revert to be U32
764 MemoryValue rd_size = MemoryValue::from<uint32_t>(context.get_last_rd_size());
765 memory.set(dst_addr, rd_size);
766 set_output(opcode, rd_size);
767}
768
783{
784 BB_BENCH_NAME("Execution::ret");
785 constexpr auto opcode = ExecutionOpCode::RETURN;
786 auto& memory = context.get_memory();
787 const auto& rd_size = memory.get(ret_size_offset);
788 set_and_validate_inputs(opcode, { rd_size });
789
791
792 set_execution_result({ .rd_offset = ret_offset,
793 .rd_size = rd_size.as<uint32_t>(),
794 .gas_used = context.get_gas_used(),
795 .success = true,
796 .halting_pc = context.get_pc(),
797 .halting_mode = HaltingMode::RETURN,
798 .halting_message = std::nullopt });
799
800 context.halt();
801}
802
817{
818 BB_BENCH_NAME("Execution::revert");
819 constexpr auto opcode = ExecutionOpCode::REVERT;
820 auto& memory = context.get_memory();
821 const auto& rev_size = memory.get(rev_size_offset);
822 set_and_validate_inputs(opcode, { rev_size });
823
825
826 set_execution_result({ .rd_offset = rev_offset,
827 .rd_size = rev_size.as<uint32_t>(),
828 .gas_used = context.get_gas_used(),
829 .success = false,
830 .halting_pc = context.get_pc(),
831 .halting_mode = HaltingMode::REVERT,
832 .halting_message = "Assertion failed: " });
833
834 context.halt();
835}
836
847{
848 BB_BENCH_NAME("Execution::jump");
850
851 context.set_next_pc(loc);
852}
853
867{
868 BB_BENCH_NAME("Execution::jumpi");
869 constexpr auto opcode = ExecutionOpCode::JUMPI;
870 auto& memory = context.get_memory();
871
872 const auto& resolved_cond = memory.get(cond_addr);
873 set_and_validate_inputs(opcode, { resolved_cond });
874
876
877 if (resolved_cond.as<uint1_t>().value() == 1) {
878 context.set_next_pc(loc);
879 }
880}
881
893{
894 BB_BENCH_NAME("Execution::internal_call");
896
897 auto& internal_call_stack_manager = context.get_internal_call_stack_manager();
898 // The next pc is pushed onto the internal call stack. This will become return_pc later.
899 internal_call_stack_manager.push(context.get_pc(), context.get_next_pc());
900 context.set_next_pc(loc);
901}
902
913{
914 BB_BENCH_NAME("Execution::internal_return");
916
917 auto& internal_call_stack_manager = context.get_internal_call_stack_manager();
918 try {
919 auto next_pc = internal_call_stack_manager.pop();
920 context.set_next_pc(next_pc);
921 } catch (const InternalCallStackException& e) {
922 // Re-throw
923 throw OpcodeExecutionException("Internal return failed: " + std::string(e.what()));
924 }
925}
926
940{
941 BB_BENCH_NAME("Execution::keccak_permutation");
943
944 try {
945 keccakf1600.permutation(context.get_memory(), dst_addr, src_addr);
946 } catch (const KeccakF1600Exception& e) {
947 throw OpcodeExecutionException("Keccak permutation failed: " + std::string(e.what()));
948 }
949}
950
983
993{
994 BB_BENCH_NAME("Execution::success_copy");
995 constexpr auto opcode = ExecutionOpCode::SUCCESSCOPY;
996 auto& memory = context.get_memory();
997
999
1000 MemoryValue success = MemoryValue::from<uint1_t>(context.get_last_success());
1001 memory.set(dst_addr, success);
1002 set_output(opcode, success);
1003}
1004
1019{
1020 BB_BENCH_NAME("Execution::and_op");
1021 constexpr auto opcode = ExecutionOpCode::AND;
1022 auto& memory = context.get_memory();
1023 const MemoryValue a = memory.get(a_addr);
1024 const MemoryValue b = memory.get(b_addr);
1025 set_and_validate_inputs(opcode, { a, b });
1026
1027 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1028 // will result in an exception in the bitwise subtrace.
1029 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1030
1031 try {
1032 MemoryValue c = bitwise.and_op(a, b);
1033 memory.set(dst_addr, c);
1034 set_output(opcode, c);
1035 } catch (const BitwiseException& e) {
1036 throw OpcodeExecutionException("Bitwise AND Exeception: " + std::string(e.what()));
1037 }
1038}
1039
1056{
1057 BB_BENCH_NAME("Execution::or_op");
1058 constexpr auto opcode = ExecutionOpCode::OR;
1059 auto& memory = context.get_memory();
1060 const MemoryValue a = memory.get(a_addr);
1061 const MemoryValue b = memory.get(b_addr);
1062 set_and_validate_inputs(opcode, { a, b });
1063
1064 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1065 // will result in an exception in the bitwise subtrace.
1066 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1067
1068 try {
1069 MemoryValue c = bitwise.or_op(a, b);
1070 memory.set(dst_addr, c);
1071 set_output(opcode, c);
1072 } catch (const BitwiseException& e) {
1073 throw OpcodeExecutionException("Bitwise OR Exception: " + std::string(e.what()));
1074 }
1075}
1076
1091{
1092 BB_BENCH_NAME("Execution::xor_op");
1093 constexpr auto opcode = ExecutionOpCode::XOR;
1094 auto& memory = context.get_memory();
1095 const MemoryValue a = memory.get(a_addr);
1096 const MemoryValue b = memory.get(b_addr);
1097 set_and_validate_inputs(opcode, { a, b });
1098
1099 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1100 // will result in an exception in the bitwise subtrace.
1101 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1102
1103 try {
1104 MemoryValue c = bitwise.xor_op(a, b);
1105 memory.set(dst_addr, c);
1106 set_output(opcode, c);
1107 } catch (const BitwiseException& e) {
1108 throw OpcodeExecutionException("Bitwise XOR Exception: " + std::string(e.what()));
1109 }
1110}
1111
1128 MemoryAddress slot_addr,
1129 MemoryAddress contract_address_addr,
1131{
1132 BB_BENCH_NAME("Execution::sload");
1133 constexpr auto opcode = ExecutionOpCode::SLOAD;
1134
1135 auto& memory = context.get_memory();
1136
1137 const auto& slot = memory.get(slot_addr);
1138 const auto& contract_address = memory.get(contract_address_addr);
1139 set_and_validate_inputs(opcode, { slot, contract_address });
1140
1142
1143 auto value = MemoryValue::from<FF>(merkle_db.storage_read(contract_address.as<AztecAddress>(), slot.as<FF>()));
1144
1145 memory.set(dst_addr, value);
1146 set_output(opcode, value);
1147}
1148
1165{
1166 BB_BENCH_NAME("Execution::sstore");
1167 constexpr auto opcode = ExecutionOpCode::SSTORE;
1168
1169 auto& memory = context.get_memory();
1170
1171 const auto& slot = memory.get(slot_addr);
1172 const auto& value = memory.get(src_addr);
1173 set_and_validate_inputs(opcode, { value, slot });
1174
1175 bool was_slot_written_before = merkle_db.was_storage_written(context.get_address(), slot.as_ff());
1176 uint32_t da_gas_factor = static_cast<uint32_t>(!was_slot_written_before);
1177 get_gas_tracker().consume_gas({ .l2_gas = 0, .da_gas = da_gas_factor });
1178
1179 if (context.get_is_static()) {
1181 "SSTORE: Static call cannot update the state. Cannot write to storage in static context");
1182 }
1183
1184 if (!was_slot_written_before &&
1186 throw OpcodeExecutionException("SSTORE: Maximum number of data writes reached");
1187 }
1188
1189 merkle_db.storage_write(context.get_address(), slot.as_ff(), value.as_ff(), false);
1190}
1191
1208 MemoryAddress unique_note_hash_addr,
1209 MemoryAddress leaf_index_addr,
1211{
1212 BB_BENCH_NAME("Execution::note_hash_exists");
1213 constexpr auto opcode = ExecutionOpCode::NOTEHASHEXISTS;
1214
1215 auto& memory = context.get_memory();
1216 const auto& unique_note_hash = memory.get(unique_note_hash_addr);
1217 const auto& leaf_index = memory.get(leaf_index_addr);
1218 set_and_validate_inputs(opcode, { unique_note_hash, leaf_index });
1219
1221
1222 uint64_t leaf_index_value = leaf_index.as<uint64_t>();
1223
1224 bool index_in_range = greater_than.gt(NOTE_HASH_TREE_LEAF_COUNT, leaf_index_value);
1225
1227
1228 if (index_in_range) {
1229 value = MemoryValue::from<uint1_t>(merkle_db.note_hash_exists(leaf_index_value, unique_note_hash.as<FF>()));
1230 } else {
1231 value = MemoryValue::from<uint1_t>(0);
1232 }
1233
1234 memory.set(dst_addr, value);
1235 set_output(opcode, value);
1236}
1237
1250 MemoryAddress siloed_nullifier_offset,
1251 MemoryAddress exists_offset)
1252{
1253 BB_BENCH_NAME("Execution::nullifier_exists");
1254 constexpr auto opcode = ExecutionOpCode::NULLIFIEREXISTS;
1255 auto& memory = context.get_memory();
1256
1257 const auto& siloed_nullifier = memory.get(siloed_nullifier_offset);
1258 set_and_validate_inputs(opcode, { siloed_nullifier });
1259
1261
1262 // Check siloed nullifier existence via MerkleDB
1263 auto exists = merkle_db.siloed_nullifier_exists(siloed_nullifier.as_ff());
1264
1265 // Write result to memory
1266 // (assigns tag u1 to result)
1267 MemoryValue result = MemoryValue::from<uint1_t>(exists ? 1 : 0);
1268 memory.set(exists_offset, result);
1269 set_output(opcode, result);
1270}
1271
1287{
1288 BB_BENCH_NAME("Execution::emit_nullifier");
1289 constexpr auto opcode = ExecutionOpCode::EMITNULLIFIER;
1290
1291 auto& memory = context.get_memory();
1292 const auto& nullifier = memory.get(nullifier_addr);
1293 set_and_validate_inputs(opcode, { nullifier });
1294
1296
1297 if (context.get_is_static()) {
1299 "EMITNULLIFIER: Static call cannot update the state. Cannot emit nullifier in static context");
1300 }
1301
1303 throw OpcodeExecutionException("EMITNULLIFIER: Maximum number of nullifiers reached");
1304 }
1305
1306 // Emit nullifier via MerkleDB.
1307 try {
1308 merkle_db.nullifier_write(context.get_address(), nullifier.as<FF>());
1309 } catch (const NullifierCollisionException& e) {
1310 throw OpcodeExecutionException(format("EMITNULLIFIER: ", e.what()));
1311 }
1312}
1313
1332 MemoryAddress address_offset,
1333 MemoryAddress dst_offset,
1334 uint8_t member_enum)
1335{
1336 BB_BENCH_NAME("Execution::get_contract_instance");
1337 constexpr auto opcode = ExecutionOpCode::GETCONTRACTINSTANCE;
1338 auto& memory = context.get_memory();
1339
1340 // Execution can still handle address memory read and tag checking
1341 const auto& address_value = memory.get(address_offset);
1342 set_and_validate_inputs(opcode, { address_value });
1343
1344 AztecAddress contract_address = address_value.as<AztecAddress>();
1345
1347
1348 // Call the dedicated opcode component to get the contract instance, validate the enum,
1349 // handle other errors, and perform the memory writes.
1350 try {
1352 } catch (const GetContractInstanceException& e) {
1353 throw OpcodeExecutionException("GetContractInstance Exception: " + std::string(e.what()));
1354 }
1355
1356 // No `set_output` here since the dedicated component handles memory writes.
1357}
1358
1373{
1374 BB_BENCH_NAME("Execution::emit_note_hash");
1375 constexpr auto opcode = ExecutionOpCode::EMITNOTEHASH;
1376
1377 auto& memory = context.get_memory();
1378 const auto& note_hash = memory.get(note_hash_addr);
1380
1382
1383 if (context.get_is_static()) {
1385 "EMITNOTEHASH: Static call cannot update the state. Cannot emit note hash in static context");
1386 }
1387
1389 throw OpcodeExecutionException("EMITNOTEHASH: Maximum number of note hashes reached");
1390 }
1391
1392 merkle_db.note_hash_write(context.get_address(), note_hash.as<FF>());
1393}
1394
1411 MemoryAddress msg_hash_addr,
1412 MemoryAddress leaf_index_addr,
1414{
1415 BB_BENCH_NAME("Execution::l1_to_l2_message_exists");
1416 constexpr auto opcode = ExecutionOpCode::L1TOL2MSGEXISTS;
1417
1418 auto& memory = context.get_memory();
1419 const auto& msg_hash = memory.get(msg_hash_addr);
1420 const auto& leaf_index = memory.get(leaf_index_addr);
1421 set_and_validate_inputs(opcode, { msg_hash, leaf_index });
1422
1424
1425 uint64_t leaf_index_value = leaf_index.as<uint64_t>();
1426
1427 bool index_in_range = greater_than.gt(L1_TO_L2_MSG_TREE_LEAF_COUNT, leaf_index_value);
1428
1430
1431 if (index_in_range) {
1432 value = MemoryValue::from<uint1_t>(merkle_db.l1_to_l2_msg_exists(leaf_index_value, msg_hash.as<FF>()));
1433 } else {
1434 value = MemoryValue::from<uint1_t>(0);
1435 }
1436
1437 memory.set(dst_addr, value);
1438 set_output(opcode, value);
1439}
1440
1455{
1456 BB_BENCH_NAME("Execution::poseidon2_permutation");
1458 try {
1459 poseidon2.permutation(context.get_memory(), src_addr, dst_addr);
1460 } catch (const Poseidon2Exception& e) {
1461 throw OpcodeExecutionException("Poseidon2 permutation failed: " + std::string(e.what()));
1462 }
1463}
1464
1487 MemoryAddress p_x_addr,
1488 MemoryAddress p_y_addr,
1489 MemoryAddress q_x_addr,
1490 MemoryAddress q_y_addr,
1492{
1493 BB_BENCH_NAME("Execution::ecc_add");
1494 constexpr auto opcode = ExecutionOpCode::ECADD;
1495 auto& memory = context.get_memory();
1496
1497 // Read the points from memory.
1498 const auto& p_x = memory.get(p_x_addr);
1499 const auto& p_y = memory.get(p_y_addr);
1500
1501 const auto& q_x = memory.get(q_x_addr);
1502 const auto& q_y = memory.get(q_y_addr);
1503
1504 set_and_validate_inputs(opcode, { p_x, p_y, q_x, q_y });
1506
1507 // Once inputs are tag checked the conversion to EmbeddedCurvePoint is safe, on curve checks are done in the add
1508 // method.
1509 EmbeddedCurvePoint p = EmbeddedCurvePoint(p_x.as_ff(), p_y.as_ff());
1510 EmbeddedCurvePoint q = EmbeddedCurvePoint(q_x.as_ff(), q_y.as_ff());
1511
1512 try {
1514 } catch (const EccException& e) {
1515 throw OpcodeExecutionException("Embedded curve add failed: " + std::string(e.what()));
1516 }
1517}
1518
1544 MemoryAddress value_addr,
1545 MemoryAddress radix_addr,
1546 MemoryAddress num_limbs_addr,
1547 MemoryAddress is_output_bits_addr, // Decides if output is U1 or U8
1549{
1550 BB_BENCH_NAME("Execution::to_radix_be");
1551 constexpr auto opcode = ExecutionOpCode::TORADIXBE;
1552 auto& memory = context.get_memory();
1553
1554 const auto& value = memory.get(value_addr); // Field
1555 const auto& radix = memory.get(radix_addr); // U32
1556 const auto& num_limbs = memory.get(num_limbs_addr); // U32
1557 const auto& is_output_bits = memory.get(is_output_bits_addr); // U1
1558
1559 // Tag check the inputs
1560 {
1561 BB_BENCH_NAME("Execution::to_radix_be::set_and_validate_inputs");
1562 set_and_validate_inputs(opcode, { value, radix, num_limbs, is_output_bits });
1563 }
1564
1565 // The range check for a valid radix (2 <= radix <= 256) is done in the gadget.
1566 // However, in order to compute the dynamic gas value we need to constrain the radix
1567 // to be <= 256 since the `get_p_limbs_per_radix` lookup table is only defined for the range [0, 256].
1568 // This does mean that the <= 256 check is duplicated - this can be optimized later.
1569
1570 // The dynamic gas factor is the maximum of the num_limbs requested by the opcode and the number of limbs
1571 // the gadget that the field modulus, p, decomposes into given a radix (num_p_limbs).
1572 // See to_radix.pil for how these values impact the row count.
1573
1574 // The lookup table of radix decomposed limbs of the modulus p is defined for radix values [0, 256],
1575 // so for any radix value greater than 256 we set num_p_limbs to 32 - with
1576 // the understanding the opcode will fail in the gadget (since the radix is invalid).
1577 uint32_t radix_value = radix.as<uint32_t>();
1578 uint32_t num_p_limbs = greater_than.gt(radix.as<uint32_t>(), 256)
1579 ? 32
1580 : static_cast<uint32_t>(get_p_limbs_per_radix_size(radix_value));
1581
1582 // Compute the dynamic gas factor - done this way to trigger relevant circuit interactions
1583 if (greater_than.gt(num_limbs.as<uint32_t>(), num_p_limbs)) {
1584 get_gas_tracker().consume_gas({ .l2_gas = num_limbs.as<uint32_t>(), .da_gas = 0 });
1585 } else {
1586 get_gas_tracker().consume_gas({ .l2_gas = num_p_limbs, .da_gas = 0 });
1587 }
1588
1589 try {
1590 // Call the gadget to perform the conversion.
1591 to_radix.to_be_radix(memory,
1592 value.as_ff(),
1593 radix.as<uint32_t>(),
1594 num_limbs.as<uint32_t>(),
1595 is_output_bits.as<uint1_t>().value() == 1,
1596 dst_addr);
1597 } catch (const ToRadixException& e) {
1598 throw OpcodeExecutionException("ToRadixBe gadget failed: " + std::string(e.what()));
1599 }
1600}
1601
1619{
1620 BB_BENCH_NAME("Execution::emit_public_log");
1621 constexpr auto opcode = ExecutionOpCode::EMITPUBLICLOG;
1622 auto& memory = context.get_memory();
1623
1624 const auto& log_size = memory.get(log_size_offset);
1625 set_and_validate_inputs(opcode, { log_size });
1626 uint32_t log_size_int = log_size.as<uint32_t>();
1627
1628 get_gas_tracker().consume_gas({ .l2_gas = log_size_int, .da_gas = log_size_int });
1629
1630 // Call the dedicated opcode component to emit the log
1631 try {
1632 emit_public_log_component.emit_public_log(memory, context, context.get_address(), log_offset, log_size_int);
1633 } catch (const EmitPublicLogException& e) {
1634 throw OpcodeExecutionException("EmitPublicLog Exception: " + std::string(e.what()));
1635 }
1636}
1637
1654{
1655 BB_BENCH_NAME("Execution::send_l2_to_l1_msg");
1656 constexpr auto opcode = ExecutionOpCode::SENDL2TOL1MSG;
1657 auto& memory = context.get_memory();
1658
1659 const auto& recipient = memory.get(recipient_addr);
1660 const auto& content = memory.get(content_addr);
1661 set_and_validate_inputs(opcode, { recipient, content });
1662
1664
1665 // We need to check this first, since the circuit will always lookup ff_gt it even if another opcode error happens.
1667 throw OpcodeExecutionException("SENDL2TOL1MSG: Recipient address is too large");
1668 }
1669
1670 if (context.get_is_static()) {
1672 "SENDL2TOL1MSG: Static call cannot update the state. Cannot send L2 to L1 message in static context");
1673 }
1674
1675 auto& side_effect_tracker = context.get_side_effect_tracker();
1676 const auto& side_effects = side_effect_tracker.get_side_effects();
1677
1678 if (side_effects.l2_to_l1_messages.size() == MAX_L2_TO_L1_MSGS_PER_TX) {
1679 throw OpcodeExecutionException("SENDL2TOL1MSG: Maximum number of L2 to L1 messages reached");
1680 }
1681
1682 side_effect_tracker.add_l2_to_l1_message(context.get_address(), EthAddress(recipient.as_ff()), content.as_ff());
1683}
1684
1700 MemoryAddress output_addr,
1701 MemoryAddress state_addr,
1702 MemoryAddress input_addr)
1703{
1704 BB_BENCH_NAME("Execution::sha256_compression");
1706
1707 try {
1708 sha256.compression(context.get_memory(), state_addr, input_addr, output_addr);
1709 } catch (const Sha256CompressionException& e) {
1710 throw OpcodeExecutionException("Sha256 Compression failed: " + std::string(e.what()));
1711 }
1712}
1713
1725{
1726 BB_BENCH_NAME("Execution::execute");
1727 call_stack_metadata_collector.notify_enter_call(enqueued_call_context->get_address(),
1728 0,
1729 make_calldata_provider(*enqueued_call_context),
1730 enqueued_call_context->get_is_static(),
1731 enqueued_call_context->get_gas_limit());
1732 external_call_stack.push(std::move(enqueued_call_context));
1733
1734 while (!external_call_stack.empty()) {
1735 // Throws CancelledException if cancelled. No-op when cancellation_token_ is nullptr (non-NAPI paths).
1736 if (cancellation_token_) {
1737 cancellation_token_->check_and_throw();
1738 }
1739
1740 // We fix the context at this point. Even if the opcode changes the stack
1741 // we'll always use this in the loop.
1742 auto& context = *external_call_stack.top();
1743
1744 // Default inputs and output initialization. This properly resets the values between two
1745 // opcode executions as well.
1746 inputs = {};
1747 output = MemoryValue::from_tag(static_cast<MemoryTag>(0), 0);
1748
1749 // Members of the execution event which are set in the try block.
1751 AddressingEvent addressing_event;
1754
1755 // State before doing anything.
1756 const auto before_context_event = context.serialize_context_event();
1757 const auto next_context_id = context_provider.get_next_context_id();
1758 const auto pc = context.get_pc();
1759
1760 try {
1761 // Temporality group 1: Bytecode retrieval. //
1762
1763 // We try to get the bytecode id. This can throw if the contract is not deployed or if we have retrieved too
1764 // many unique class ids. Note: bytecode_id is tracked in context events, not in the top-level execution
1765 // event. It is already included in the before_context_event (defaulting to 0 on error/not-found).
1766 context.get_bytecode_manager().get_bytecode_id();
1767
1768 // Temporality group 2: Instruction fetching and addressing. //
1769
1770 // We try to fetch an instruction.
1771 instruction = context.get_bytecode_manager().read_instruction(pc);
1772
1773 debug("@", pc, " ", instruction.to_string());
1774 context.set_next_pc(pc + static_cast<PC>(instruction.size_in_bytes()));
1775 // next_pc is overwritten in dispatch_opcode() for JUMP, JUMPI, INTERNALCALL, and INTERNALRETURN.
1776
1777 // Resolve the operands.
1778 auto addressing = execution_components.make_addressing(addressing_event);
1779 std::vector<Operand> resolved_operands = addressing->resolve(instruction, context.get_memory());
1780
1782 // Temporality group 3: Registers read. (triggered in each opcode (dispatch_opcode()) with
1783 // set_and_validate_inputs(opcode, { ... });)
1784 // Temporality group 4: Gas. (triggered in each opcode (dispatch_opcode()) with
1785 // get_gas_tracker().consume_gas();)
1786 // Temporality group 5: Opcode execution. (in dispatch_opcode())
1787 // Temporality group 6: Register write. (in dispatch_opcode())
1788
1790 dispatch_opcode(instruction.get_exec_opcode(), context, resolved_operands);
1791 } catch (const BytecodeRetrievalError& e) {
1792 vinfo("Bytecode retrieval error:: ", e.what());
1795 } catch (const InstructionFetchingError& e) {
1796 vinfo("Instruction fetching error: ", e.what());
1799 } catch (const AddressingException& e) {
1800 vinfo("Addressing exception: ", e.what());
1803 } catch (const RegisterValidationException& e) {
1804 vinfo("Register validation exception: ", e.what());
1807 } catch (const OutOfGasException& e) {
1808 vinfo("Out of gas exception: ", e.what());
1809 error = ExecutionError::GAS;
1811 } catch (const OpcodeExecutionException& e) {
1812 vinfo("Opcode execution exception: ", e.what());
1815 } catch (const std::exception& e) {
1816 // This is a coding error, we should not get here.
1817 // All exceptions should fall in the above catch blocks.
1818 important("An unhandled exception occurred: ", e.what());
1819 throw;
1820 }
1821
1822 // We always do what follows. "Finally".
1823 // Move on to the next pc.
1824 context.set_pc(context.get_next_pc());
1826
1827 events.emit({
1828 .error = error,
1829 .wire_instruction = instruction,
1830 .inputs = get_inputs(),
1831 .output = get_output(),
1832 .next_context_id = next_context_id,
1833 .addressing_event = addressing_event,
1834 .before_context_event = before_context_event,
1835 .after_context_event = context.serialize_context_event(),
1836 .gas_event = gas_event,
1837 });
1838
1839 // If the context has halted, we need to exit the external call.
1840 // The external call stack is expected to be popped.
1841 if (context.halted()) {
1843 }
1844 }
1845
1846 const ExecutionResult& result = get_execution_result();
1847 return EnqueuedCallResult{
1848 .success = result.success,
1849 .gas_used = result.gas_used,
1850 .halting_mode = result.halting_mode,
1851 .halting_message = result.halting_message,
1852 };
1853}
1854
1863{
1864 const auto& side_effects = parent_context.get_side_effect_tracker().get_side_effects();
1865
1866 // Optionally collect call stack metadata.
1867 call_stack_metadata_collector.notify_enter_call(child_context->get_address(),
1868 parent_context.get_pc(),
1869 make_calldata_provider(*child_context),
1870 child_context->get_is_static(),
1871 child_context->get_gas_limit());
1872
1873 const auto parent_bytecode_id = parent_context.get_bytecode_manager().get_retrieved_bytecode_id();
1874 BB_ASSERT(parent_bytecode_id.has_value(),
1875 "Bytecode should have been retrieved in the parent context if it issued a call");
1876
1877 ctx_stack_events.emit({
1878 .id = parent_context.get_context_id(),
1879 .parent_id = parent_context.get_parent_id(),
1880 .entered_context_id = child_context->get_context_id(), // gets the context id of the child!
1881 .next_pc = parent_context.get_next_pc(),
1882 .msg_sender = parent_context.get_msg_sender(),
1883 .contract_addr = parent_context.get_address(),
1884 .bytecode_id = parent_bytecode_id.value(),
1885 .is_static = parent_context.get_is_static(),
1886 .parent_cd_addr = parent_context.get_parent_cd_addr(),
1887 .parent_cd_size = parent_context.get_parent_cd_size(),
1888 .parent_gas_used = parent_context.get_parent_gas_used(),
1889 .parent_gas_limit = parent_context.get_parent_gas_limit(),
1890 .internal_call_id = parent_context.get_internal_call_stack_manager().get_call_id(),
1891 .internal_call_return_id = parent_context.get_internal_call_stack_manager().get_return_call_id(),
1892 .next_internal_call_id = parent_context.get_internal_call_stack_manager().get_next_call_id(),
1893 .tree_states = merkle_db.get_tree_state(),
1894 .written_public_data_slots_tree_snapshot = parent_context.get_written_public_data_slots_tree_snapshot(),
1895 // Non-tree-tracked side effects
1896 .numPublicLogFields = side_effects.get_num_public_log_fields(),
1897 .numL2ToL1Messages = static_cast<uint32_t>(side_effects.l2_to_l1_messages.size()),
1898 });
1899
1900 external_call_stack.push(std::move(child_context));
1901}
1902
1907{
1908 BB_BENCH_NAME("Execution::handle_exit_call");
1909
1910 // NOTE: the current (child) context should not be modified here, since it was already emitted.
1912
1913 const ExecutionResult& result = get_execution_result();
1914
1915 // Optionally collect call stack metadata.
1917 result.success,
1918 result.halting_pc,
1919 result.halting_message,
1920 make_return_data_provider(*child_context, result.rd_offset, result.rd_size),
1921 make_internal_call_stack_provider(child_context->get_internal_call_stack_manager()));
1922
1923 external_call_stack.pop();
1924
1925 // We only handle reverting/committing of nested calls. Enqueued calls are handled by TX execution.
1926 if (!external_call_stack.empty()) {
1927 // Note: committing or reverting the db here also commits or reverts the
1928 // tracked nullifiers, public writes dictionary, etc. These structures
1929 // "listen" to the db changes.
1930 if (result.success) {
1932 } else {
1934 }
1935
1936 auto& parent_context = *external_call_stack.top();
1937 // was not top level, communicate with parent
1938 parent_context.set_last_rd_addr(result.rd_offset);
1939 parent_context.set_last_rd_size(result.rd_size);
1940 parent_context.set_last_success(result.success);
1941 // Safe since the nested context gas limit should be clamped to the available gas.
1942 parent_context.set_gas_used(result.gas_used + parent_context.get_gas_used());
1943 parent_context.set_child_context(std::move(child_context));
1944
1945 BB_ASSERT_EQ(parent_context.get_checkpoint_id_at_creation(),
1947 "Checkpoint id mismatch: gone back to the wrong db/context");
1948 }
1949 // Else: was top level. ExecutionResult is already set and that will be returned.
1950}
1951
1959void Execution::handle_exceptional_halt(ContextInterface& context, const std::string& halting_message)
1960{
1961 context.set_gas_used(context.get_gas_limit()); // Consume all gas.
1962 context.halt();
1964 .rd_offset = 0,
1965 .rd_size = 0,
1966 .gas_used = context.get_gas_used(),
1967 .success = false,
1968 .halting_pc = context.get_pc(),
1969 .halting_mode = HaltingMode::EXCEPTIONAL_HALT,
1970 .halting_message = halting_message,
1971 });
1972}
1973
1984 const std::vector<Operand>& resolved_operands)
1985{
1986 BB_BENCH_NAME("Execution::dispatch_opcode");
1987
1988 debug("Dispatching opcode: ", opcode, " (", static_cast<uint32_t>(opcode), ")");
1989 switch (opcode) {
1991 call_with_operands(&Execution::add, context, resolved_operands);
1992 break;
1994 call_with_operands(&Execution::sub, context, resolved_operands);
1995 break;
1997 call_with_operands(&Execution::mul, context, resolved_operands);
1998 break;
2000 call_with_operands(&Execution::div, context, resolved_operands);
2001 break;
2003 call_with_operands(&Execution::fdiv, context, resolved_operands);
2004 break;
2006 call_with_operands(&Execution::eq, context, resolved_operands);
2007 break;
2009 call_with_operands(&Execution::lt, context, resolved_operands);
2010 break;
2012 call_with_operands(&Execution::lte, context, resolved_operands);
2013 break;
2015 call_with_operands(&Execution::op_not, context, resolved_operands);
2016 break;
2018 call_with_operands(&Execution::shl, context, resolved_operands);
2019 break;
2021 call_with_operands(&Execution::shr, context, resolved_operands);
2022 break;
2024 call_with_operands(&Execution::cast, context, resolved_operands);
2025 break;
2028 break;
2030 call_with_operands(&Execution::set, context, resolved_operands);
2031 break;
2033 call_with_operands(&Execution::mov, context, resolved_operands);
2034 break;
2036 call_with_operands(&Execution::call, context, resolved_operands);
2037 break;
2040 break;
2042 call_with_operands(&Execution::ret, context, resolved_operands);
2043 break;
2045 call_with_operands(&Execution::revert, context, resolved_operands);
2046 break;
2048 call_with_operands(&Execution::jump, context, resolved_operands);
2049 break;
2051 call_with_operands(&Execution::jumpi, context, resolved_operands);
2052 break;
2054 call_with_operands(&Execution::cd_copy, context, resolved_operands);
2055 break;
2057 call_with_operands(&Execution::rd_copy, context, resolved_operands);
2058 break;
2061 break;
2064 break;
2067 break;
2070 break;
2072 call_with_operands(&Execution::rd_size, context, resolved_operands);
2073 break;
2075 call_with_operands(&Execution::debug_log, context, resolved_operands);
2076 break;
2078 call_with_operands(&Execution::and_op, context, resolved_operands);
2079 break;
2081 call_with_operands(&Execution::or_op, context, resolved_operands);
2082 break;
2084 call_with_operands(&Execution::xor_op, context, resolved_operands);
2085 break;
2087 call_with_operands(&Execution::sload, context, resolved_operands);
2088 break;
2090 call_with_operands(&Execution::sstore, context, resolved_operands);
2091 break;
2094 break;
2097 break;
2100 break;
2103 break;
2106 break;
2109 break;
2112 break;
2114 call_with_operands(&Execution::ecc_add, context, resolved_operands);
2115 break;
2118 break;
2121 break;
2124 break;
2127 break;
2128 default:
2129 // NOTE: Keep this a `std::runtime_error` so that the main loop panics.
2130 throw std::runtime_error("Tried to dispatch unknown execution opcode: " +
2131 std::to_string(static_cast<uint32_t>(opcode)));
2132 }
2133}
2134
2144template <typename... Ts>
2147 const std::vector<Operand>& resolved_operands)
2148{
2149 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2150 BB_ASSERT_DEBUG(resolved_operands.size() == sizeof...(Ts), "Resolved operands size mismatch");
2151 auto operand_indices = std::make_index_sequence<sizeof...(Ts)>{};
2152 [f, this, &context, &resolved_operands]<std::size_t... Is>(std::index_sequence<Is...>) {
2153 // This helper handles operand conversion. In particular it converts enums to their underlying type first.
2154 [[maybe_unused]] auto convert_operand = []<typename T>(const Operand& op) -> T {
2155 if constexpr (std::is_enum_v<T>) {
2156 return static_cast<T>(op.to<std::underlying_type_t<T>>());
2157 } else {
2158 return op.to<T>();
2159 }
2160 };
2161 (this->*f)(context, convert_operand.template operator()<std::decay_t<Ts>>(resolved_operands.at(Is))...);
2162 }(operand_indices);
2163}
2164
2173{
2174 const auto& register_info = instruction_info_db.get(opcode).register_info;
2175 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2176 BB_ASSERT_DEBUG(inputs.size() == register_info.num_inputs(), "Inputs size mismatch");
2177 this->inputs = inputs;
2178 for (size_t i = 0; i < register_info.num_inputs(); i++) {
2179 if (register_info.expected_tag(i) && register_info.expected_tag(i) != this->inputs.at(i).get_tag()) {
2180 throw RegisterValidationException(format("Input ",
2181 i,
2182 " tag ",
2183 std::to_string(this->inputs.at(i).get_tag()),
2184 " does not match expected tag ",
2185 std::to_string(*register_info.expected_tag(i))));
2186 }
2187 }
2188}
2189
2197{
2198 const auto& register_info = instruction_info_db.get(opcode).register_info;
2199 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2200 BB_ASSERT_DEBUG(register_info.num_outputs() == 1, "Outputs size mismatch");
2201 this->output = output;
2202}
2203
2204} // namespace bb::avm2::simulation
MemoryTag dst_tag
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_DEBUG(expression,...)
Definition assert.hpp:55
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define NOTE_HASH_TREE_LEAF_COUNT
#define MAX_ETH_ADDRESS_VALUE
#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:264
static TaggedValue from(T value)
static TaggedValue from_tag(ValueTag tag, FF value)
virtual std::optional< BytecodeId > get_retrieved_bytecode_id()=0
virtual void notify_exit_call(bool success, PC 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, PC caller_pc, const CalldataProvider &calldata_provider, bool is_static_call, const Gas &gas_limit)=0
virtual const AztecAddress & get_msg_sender() const =0
virtual Gas get_parent_gas_limit() const =0
virtual InternalCallStackManagerInterface & get_internal_call_stack_manager()=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_public_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 args_size_offset, MemoryAddress args_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 cast(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr, MemoryTag dst_tag)
CAST execution opcode handler: Cast a value to a different tag.
void sstore(ContextInterface &context, MemoryAddress src_addr, MemoryAddress slot_addr)
SSTORE execution opcode handler: Store a value in the public data tree.
void emit_public_log(ContextInterface &context, MemoryAddress log_size_offset, MemoryAddress log_offset)
EMITPUBLICLOG execution opcode handler: Emit a public log.
const std::vector< MemoryValue > & get_inputs() const
void call(ContextInterface &context, MemoryAddress l2_gas_offset, MemoryAddress da_gas_offset, MemoryAddress addr, MemoryAddress args_size_offset, MemoryAddress args_offset)
CALL execution opcode handler: Call a contract. Creates a new (nested) execution context and triggers...
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.
void get_env_var(ContextInterface &context, MemoryAddress dst_addr, uint8_t env_var_value)
GETENVVAR execution opcode handler: Get the value of an environment variable.
void sload(ContextInterface &context, MemoryAddress slot_addr, MemoryAddress contract_address_addr, MemoryAddress dst_addr)
SLOAD execution opcode handler: Load a value from the public data tree. Loads a value from the public...
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 ...
EmitPublicLogInterface & emit_public_log_component
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 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 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 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 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....
void nullifier_exists(ContextInterface &context, MemoryAddress siloed_nullifier_offset, MemoryAddress exists_offset)
NULLIFIEREXISTS execution opcode handler: Check if a siloed nullifier exists in the nullifier tree.
void eq(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
EQ execution opcode handler: Check if two values are equal.
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, reverts or errors.
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 ecc_add(ContextInterface &context, MemoryAddress p_x_addr, MemoryAddress p_y_addr, MemoryAddress q_x_addr, MemoryAddress q_y_addr, MemoryAddress dst_addr)
ECADD execution opcode handler: Perform an elliptic curve addition and write the result to the destin...
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 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 set(ContextInterface &context, MemoryAddress dst_addr, MemoryTag 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 add(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
ADD execution opcode handler: Add two values.
Definition execution.cpp:75
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 void storage_write(const AztecAddress &contract_address, const FF &slot, const FF &value, bool is_protocol_write)=0
virtual bool siloed_nullifier_exists(const FF &nullifier) const =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 InternalCallId get_return_call_id() const =0
virtual InternalCallId get_next_call_id() 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:23
#define vinfo(...)
Definition log.hpp:94
#define important(...)
Definition log.hpp:92
#define debug(...)
Definition log.hpp:99
uint32_t src_addr
uint32_t dst_addr
FF a
FF b
uint64_t da_gas
GasEvent gas_event
Instruction instruction
AvmProvingInputs inputs
AVM range check gadget for witness generation.
InternalCallStackProvider make_internal_call_stack_provider(const InternalCallStackManagerInterface &internal_call_stack_manager)
CalldataProvider make_calldata_provider(const ContextInterface &context)
ReturnDataProvider make_return_data_provider(const ContextInterface &context, uint32_t rd_mem_offset_in_child, uint32_t rd_size)
uint32_t PC
size_t get_p_limbs_per_radix_size(size_t radix)
Gets the number of limbs that the modulus, p, decomposes into for a given radix.
Definition to_radix.cpp:75
AvmFlavorSettings::FF FF
Definition field.hpp:10
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
uint32_t MemoryAddress
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
Exception thrown on errors during the Keccak-f[1600] permutation.
SideEffectTracker side_effect_tracker