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
1491 MemoryAddress p_x_addr,
1492 MemoryAddress p_y_addr,
1493 MemoryAddress p_inf_addr,
1494 MemoryAddress q_x_addr,
1495 MemoryAddress q_y_addr,
1496 MemoryAddress q_inf_addr,
1498{
1499 BB_BENCH_NAME("Execution::ecc_add");
1500 constexpr auto opcode = ExecutionOpCode::ECADD;
1501 auto& memory = context.get_memory();
1502
1503 // Read the points from memory.
1504 const auto& p_x = memory.get(p_x_addr);
1505 const auto& p_y = memory.get(p_y_addr);
1506 const auto& p_inf = memory.get(p_inf_addr);
1507
1508 const auto& q_x = memory.get(q_x_addr);
1509 const auto& q_y = memory.get(q_y_addr);
1510 const auto& q_inf = memory.get(q_inf_addr);
1511
1512 set_and_validate_inputs(opcode, { p_x, p_y, p_inf, q_x, q_y, q_inf });
1514
1515 // Once inputs are tag checked the conversion to EmbeddedCurvePoint is safe, on curve checks are done in the add
1516 // method.
1517 EmbeddedCurvePoint p = EmbeddedCurvePoint(p_x.as_ff(), p_y.as_ff(), p_inf == MemoryValue::from<uint1_t>(1));
1518 EmbeddedCurvePoint q = EmbeddedCurvePoint(q_x.as_ff(), q_y.as_ff(), q_inf == MemoryValue::from<uint1_t>(1));
1519
1520 try {
1522 } catch (const EccException& e) {
1523 throw OpcodeExecutionException("Embedded curve add failed: " + std::string(e.what()));
1524 }
1525}
1526
1552 MemoryAddress value_addr,
1553 MemoryAddress radix_addr,
1554 MemoryAddress num_limbs_addr,
1555 MemoryAddress is_output_bits_addr, // Decides if output is U1 or U8
1557{
1558 BB_BENCH_NAME("Execution::to_radix_be");
1559 constexpr auto opcode = ExecutionOpCode::TORADIXBE;
1560 auto& memory = context.get_memory();
1561
1562 const auto& value = memory.get(value_addr); // Field
1563 const auto& radix = memory.get(radix_addr); // U32
1564 const auto& num_limbs = memory.get(num_limbs_addr); // U32
1565 const auto& is_output_bits = memory.get(is_output_bits_addr); // U1
1566
1567 // Tag check the inputs
1568 {
1569 BB_BENCH_NAME("Execution::to_radix_be::set_and_validate_inputs");
1570 set_and_validate_inputs(opcode, { value, radix, num_limbs, is_output_bits });
1571 }
1572
1573 // The range check for a valid radix (2 <= radix <= 256) is done in the gadget.
1574 // However, in order to compute the dynamic gas value we need to constrain the radix
1575 // to be <= 256 since the `get_p_limbs_per_radix` lookup table is only defined for the range [0, 256].
1576 // This does mean that the <= 256 check is duplicated - this can be optimized later.
1577
1578 // The dynamic gas factor is the maximum of the num_limbs requested by the opcode and the number of limbs
1579 // the gadget that the field modulus, p, decomposes into given a radix (num_p_limbs).
1580 // See to_radix.pil for how these values impact the row count.
1581
1582 // The lookup table of radix decomposed limbs of the modulus p is defined for radix values [0, 256],
1583 // so for any radix value greater than 256 we set num_p_limbs to 32 - with
1584 // the understanding the opcode will fail in the gadget (since the radix is invalid).
1585 uint32_t radix_value = radix.as<uint32_t>();
1586 uint32_t num_p_limbs = greater_than.gt(radix.as<uint32_t>(), 256)
1587 ? 32
1588 : static_cast<uint32_t>(get_p_limbs_per_radix_size(radix_value));
1589
1590 // Compute the dynamic gas factor - done this way to trigger relevant circuit interactions
1591 if (greater_than.gt(num_limbs.as<uint32_t>(), num_p_limbs)) {
1592 get_gas_tracker().consume_gas({ .l2_gas = num_limbs.as<uint32_t>(), .da_gas = 0 });
1593 } else {
1594 get_gas_tracker().consume_gas({ .l2_gas = num_p_limbs, .da_gas = 0 });
1595 }
1596
1597 try {
1598 // Call the gadget to perform the conversion.
1599 to_radix.to_be_radix(memory,
1600 value.as_ff(),
1601 radix.as<uint32_t>(),
1602 num_limbs.as<uint32_t>(),
1603 is_output_bits.as<uint1_t>().value() == 1,
1604 dst_addr);
1605 } catch (const ToRadixException& e) {
1606 throw OpcodeExecutionException("ToRadixBe gadget failed: " + std::string(e.what()));
1607 }
1608}
1609
1627{
1628 BB_BENCH_NAME("Execution::emit_public_log");
1629 constexpr auto opcode = ExecutionOpCode::EMITPUBLICLOG;
1630 auto& memory = context.get_memory();
1631
1632 const auto& log_size = memory.get(log_size_offset);
1633 set_and_validate_inputs(opcode, { log_size });
1634 uint32_t log_size_int = log_size.as<uint32_t>();
1635
1636 get_gas_tracker().consume_gas({ .l2_gas = log_size_int, .da_gas = log_size_int });
1637
1638 // Call the dedicated opcode component to emit the log
1639 try {
1640 emit_public_log_component.emit_public_log(memory, context, context.get_address(), log_offset, log_size_int);
1641 } catch (const EmitPublicLogException& e) {
1642 throw OpcodeExecutionException("EmitPublicLog Exception: " + std::string(e.what()));
1643 }
1644}
1645
1662{
1663 BB_BENCH_NAME("Execution::send_l2_to_l1_msg");
1664 constexpr auto opcode = ExecutionOpCode::SENDL2TOL1MSG;
1665 auto& memory = context.get_memory();
1666
1667 const auto& recipient = memory.get(recipient_addr);
1668 const auto& content = memory.get(content_addr);
1669 set_and_validate_inputs(opcode, { recipient, content });
1670
1672
1673 // We need to check this first, since the circuit will always lookup ff_gt it even if another opcode error happens.
1675 throw OpcodeExecutionException("SENDL2TOL1MSG: Recipient address is too large");
1676 }
1677
1678 if (context.get_is_static()) {
1680 "SENDL2TOL1MSG: Static call cannot update the state. Cannot send L2 to L1 message in static context");
1681 }
1682
1683 auto& side_effect_tracker = context.get_side_effect_tracker();
1684 const auto& side_effects = side_effect_tracker.get_side_effects();
1685
1686 if (side_effects.l2_to_l1_messages.size() == MAX_L2_TO_L1_MSGS_PER_TX) {
1687 throw OpcodeExecutionException("SENDL2TOL1MSG: Maximum number of L2 to L1 messages reached");
1688 }
1689
1690 side_effect_tracker.add_l2_to_l1_message(context.get_address(), EthAddress(recipient.as_ff()), content.as_ff());
1691}
1692
1708 MemoryAddress output_addr,
1709 MemoryAddress state_addr,
1710 MemoryAddress input_addr)
1711{
1712 BB_BENCH_NAME("Execution::sha256_compression");
1714
1715 try {
1716 sha256.compression(context.get_memory(), state_addr, input_addr, output_addr);
1717 } catch (const Sha256CompressionException& e) {
1718 throw OpcodeExecutionException("Sha256 Compression failed: " + std::string(e.what()));
1719 }
1720}
1721
1733{
1734 BB_BENCH_NAME("Execution::execute");
1735 call_stack_metadata_collector.notify_enter_call(enqueued_call_context->get_address(),
1736 0,
1737 make_calldata_provider(*enqueued_call_context),
1738 enqueued_call_context->get_is_static(),
1739 enqueued_call_context->get_gas_limit());
1740 external_call_stack.push(std::move(enqueued_call_context));
1741
1742 while (!external_call_stack.empty()) {
1743 // Throws CancelledException if cancelled. No-op when cancellation_token_ is nullptr (non-NAPI paths).
1744 if (cancellation_token_) {
1745 cancellation_token_->check_and_throw();
1746 }
1747
1748 // We fix the context at this point. Even if the opcode changes the stack
1749 // we'll always use this in the loop.
1750 auto& context = *external_call_stack.top();
1751
1752 // Default inputs and output initialization. This properly resets the values between two
1753 // opcode executions as well.
1754 inputs = {};
1755 output = MemoryValue::from_tag(static_cast<MemoryTag>(0), 0);
1756
1757 // Members of the execution event which are set in the try block.
1759 AddressingEvent addressing_event;
1762
1763 // State before doing anything.
1764 const auto before_context_event = context.serialize_context_event();
1765 const auto next_context_id = context_provider.get_next_context_id();
1766 const auto pc = context.get_pc();
1767
1768 try {
1769 // Temporality group 1: Bytecode retrieval. //
1770
1771 // We try to get the bytecode id. This can throw if the contract is not deployed or if we have retrieved too
1772 // many unique class ids. Note: bytecode_id is tracked in context events, not in the top-level execution
1773 // event. It is already included in the before_context_event (defaulting to 0 on error/not-found).
1774 context.get_bytecode_manager().get_bytecode_id();
1775
1776 // Temporality group 2: Instruction fetching and addressing. //
1777
1778 // We try to fetch an instruction.
1779 instruction = context.get_bytecode_manager().read_instruction(pc);
1780
1781 debug("@", pc, " ", instruction.to_string());
1782 context.set_next_pc(pc + static_cast<PC>(instruction.size_in_bytes()));
1783 // next_pc is overwritten in dispatch_opcode() for JUMP, JUMPI, INTERNALCALL, and INTERNALRETURN.
1784
1785 // Resolve the operands.
1786 auto addressing = execution_components.make_addressing(addressing_event);
1787 std::vector<Operand> resolved_operands = addressing->resolve(instruction, context.get_memory());
1788
1790 // Temporality group 3: Registers read. (triggered in each opcode (dispatch_opcode()) with
1791 // set_and_validate_inputs(opcode, { ... });)
1792 // Temporality group 4: Gas. (triggered in each opcode (dispatch_opcode()) with
1793 // get_gas_tracker().consume_gas();)
1794 // Temporality group 5: Opcode execution. (in dispatch_opcode())
1795 // Temporality group 6: Register write. (in dispatch_opcode())
1796
1798 dispatch_opcode(instruction.get_exec_opcode(), context, resolved_operands);
1799 } catch (const BytecodeRetrievalError& e) {
1800 vinfo("Bytecode retrieval error:: ", e.what());
1803 } catch (const InstructionFetchingError& e) {
1804 vinfo("Instruction fetching error: ", e.what());
1807 } catch (const AddressingException& e) {
1808 vinfo("Addressing exception: ", e.what());
1811 } catch (const RegisterValidationException& e) {
1812 vinfo("Register validation exception: ", e.what());
1815 } catch (const OutOfGasException& e) {
1816 vinfo("Out of gas exception: ", e.what());
1817 error = ExecutionError::GAS;
1819 } catch (const OpcodeExecutionException& e) {
1820 vinfo("Opcode execution exception: ", e.what());
1823 } catch (const std::exception& e) {
1824 // This is a coding error, we should not get here.
1825 // All exceptions should fall in the above catch blocks.
1826 important("An unhandled exception occurred: ", e.what());
1827 throw;
1828 }
1829
1830 // We always do what follows. "Finally".
1831 // Move on to the next pc.
1832 context.set_pc(context.get_next_pc());
1834
1835 events.emit({
1836 .error = error,
1837 .wire_instruction = instruction,
1838 .inputs = get_inputs(),
1839 .output = get_output(),
1840 .next_context_id = next_context_id,
1841 .addressing_event = addressing_event,
1842 .before_context_event = before_context_event,
1843 .after_context_event = context.serialize_context_event(),
1844 .gas_event = gas_event,
1845 });
1846
1847 // If the context has halted, we need to exit the external call.
1848 // The external call stack is expected to be popped.
1849 if (context.halted()) {
1851 }
1852 }
1853
1854 const ExecutionResult& result = get_execution_result();
1855 return EnqueuedCallResult{
1856 .success = result.success,
1857 .gas_used = result.gas_used,
1858 .halting_mode = result.halting_mode,
1859 .halting_message = result.halting_message,
1860 };
1861}
1862
1871{
1872 const auto& side_effects = parent_context.get_side_effect_tracker().get_side_effects();
1873
1874 // Optionally collect call stack metadata.
1875 call_stack_metadata_collector.notify_enter_call(child_context->get_address(),
1876 parent_context.get_pc(),
1877 make_calldata_provider(*child_context),
1878 child_context->get_is_static(),
1879 child_context->get_gas_limit());
1880
1881 const auto parent_bytecode_id = parent_context.get_bytecode_manager().get_retrieved_bytecode_id();
1882 BB_ASSERT(parent_bytecode_id.has_value(),
1883 "Bytecode should have been retrieved in the parent context if it issued a call");
1884
1885 ctx_stack_events.emit({
1886 .id = parent_context.get_context_id(),
1887 .parent_id = parent_context.get_parent_id(),
1888 .entered_context_id = child_context->get_context_id(), // gets the context id of the child!
1889 .next_pc = parent_context.get_next_pc(),
1890 .msg_sender = parent_context.get_msg_sender(),
1891 .contract_addr = parent_context.get_address(),
1892 .bytecode_id = parent_bytecode_id.value(),
1893 .is_static = parent_context.get_is_static(),
1894 .parent_cd_addr = parent_context.get_parent_cd_addr(),
1895 .parent_cd_size = parent_context.get_parent_cd_size(),
1896 .parent_gas_used = parent_context.get_parent_gas_used(),
1897 .parent_gas_limit = parent_context.get_parent_gas_limit(),
1898 .internal_call_id = parent_context.get_internal_call_stack_manager().get_call_id(),
1899 .internal_call_return_id = parent_context.get_internal_call_stack_manager().get_return_call_id(),
1900 .next_internal_call_id = parent_context.get_internal_call_stack_manager().get_next_call_id(),
1901 .tree_states = merkle_db.get_tree_state(),
1902 .written_public_data_slots_tree_snapshot = parent_context.get_written_public_data_slots_tree_snapshot(),
1903 // Non-tree-tracked side effects
1904 .numPublicLogFields = side_effects.get_num_public_log_fields(),
1905 .numL2ToL1Messages = static_cast<uint32_t>(side_effects.l2_to_l1_messages.size()),
1906 });
1907
1908 external_call_stack.push(std::move(child_context));
1909}
1910
1915{
1916 BB_BENCH_NAME("Execution::handle_exit_call");
1917
1918 // NOTE: the current (child) context should not be modified here, since it was already emitted.
1920
1921 const ExecutionResult& result = get_execution_result();
1922
1923 // Optionally collect call stack metadata.
1925 result.success,
1926 result.halting_pc,
1927 result.halting_message,
1928 make_return_data_provider(*child_context, result.rd_offset, result.rd_size),
1929 make_internal_call_stack_provider(child_context->get_internal_call_stack_manager()));
1930
1931 external_call_stack.pop();
1932
1933 // We only handle reverting/committing of nested calls. Enqueued calls are handled by TX execution.
1934 if (!external_call_stack.empty()) {
1935 // Note: committing or reverting the db here also commits or reverts the
1936 // tracked nullifiers, public writes dictionary, etc. These structures
1937 // "listen" to the db changes.
1938 if (result.success) {
1940 } else {
1942 }
1943
1944 auto& parent_context = *external_call_stack.top();
1945 // was not top level, communicate with parent
1946 parent_context.set_last_rd_addr(result.rd_offset);
1947 parent_context.set_last_rd_size(result.rd_size);
1948 parent_context.set_last_success(result.success);
1949 // Safe since the nested context gas limit should be clamped to the available gas.
1950 parent_context.set_gas_used(result.gas_used + parent_context.get_gas_used());
1951 parent_context.set_child_context(std::move(child_context));
1952
1953 BB_ASSERT_EQ(parent_context.get_checkpoint_id_at_creation(),
1955 "Checkpoint id mismatch: gone back to the wrong db/context");
1956 }
1957 // Else: was top level. ExecutionResult is already set and that will be returned.
1958}
1959
1967void Execution::handle_exceptional_halt(ContextInterface& context, const std::string& halting_message)
1968{
1969 context.set_gas_used(context.get_gas_limit()); // Consume all gas.
1970 context.halt();
1972 .rd_offset = 0,
1973 .rd_size = 0,
1974 .gas_used = context.get_gas_used(),
1975 .success = false,
1976 .halting_pc = context.get_pc(),
1977 .halting_mode = HaltingMode::EXCEPTIONAL_HALT,
1978 .halting_message = halting_message,
1979 });
1980}
1981
1992 const std::vector<Operand>& resolved_operands)
1993{
1994 BB_BENCH_NAME("Execution::dispatch_opcode");
1995
1996 debug("Dispatching opcode: ", opcode, " (", static_cast<uint32_t>(opcode), ")");
1997 switch (opcode) {
1999 call_with_operands(&Execution::add, context, resolved_operands);
2000 break;
2002 call_with_operands(&Execution::sub, context, resolved_operands);
2003 break;
2005 call_with_operands(&Execution::mul, context, resolved_operands);
2006 break;
2008 call_with_operands(&Execution::div, context, resolved_operands);
2009 break;
2011 call_with_operands(&Execution::fdiv, context, resolved_operands);
2012 break;
2014 call_with_operands(&Execution::eq, context, resolved_operands);
2015 break;
2017 call_with_operands(&Execution::lt, context, resolved_operands);
2018 break;
2020 call_with_operands(&Execution::lte, context, resolved_operands);
2021 break;
2023 call_with_operands(&Execution::op_not, context, resolved_operands);
2024 break;
2026 call_with_operands(&Execution::shl, context, resolved_operands);
2027 break;
2029 call_with_operands(&Execution::shr, context, resolved_operands);
2030 break;
2032 call_with_operands(&Execution::cast, context, resolved_operands);
2033 break;
2036 break;
2038 call_with_operands(&Execution::set, context, resolved_operands);
2039 break;
2041 call_with_operands(&Execution::mov, context, resolved_operands);
2042 break;
2044 call_with_operands(&Execution::call, context, resolved_operands);
2045 break;
2048 break;
2050 call_with_operands(&Execution::ret, context, resolved_operands);
2051 break;
2053 call_with_operands(&Execution::revert, context, resolved_operands);
2054 break;
2056 call_with_operands(&Execution::jump, context, resolved_operands);
2057 break;
2059 call_with_operands(&Execution::jumpi, context, resolved_operands);
2060 break;
2062 call_with_operands(&Execution::cd_copy, context, resolved_operands);
2063 break;
2065 call_with_operands(&Execution::rd_copy, context, resolved_operands);
2066 break;
2069 break;
2072 break;
2075 break;
2078 break;
2080 call_with_operands(&Execution::rd_size, context, resolved_operands);
2081 break;
2083 call_with_operands(&Execution::debug_log, context, resolved_operands);
2084 break;
2086 call_with_operands(&Execution::and_op, context, resolved_operands);
2087 break;
2089 call_with_operands(&Execution::or_op, context, resolved_operands);
2090 break;
2092 call_with_operands(&Execution::xor_op, context, resolved_operands);
2093 break;
2095 call_with_operands(&Execution::sload, context, resolved_operands);
2096 break;
2098 call_with_operands(&Execution::sstore, context, resolved_operands);
2099 break;
2102 break;
2105 break;
2108 break;
2111 break;
2114 break;
2117 break;
2120 break;
2122 call_with_operands(&Execution::ecc_add, context, resolved_operands);
2123 break;
2126 break;
2129 break;
2132 break;
2135 break;
2136 default:
2137 // NOTE: Keep this a `std::runtime_error` so that the main loop panics.
2138 throw std::runtime_error("Tried to dispatch unknown execution opcode: " +
2139 std::to_string(static_cast<uint32_t>(opcode)));
2140 }
2141}
2142
2152template <typename... Ts>
2155 const std::vector<Operand>& resolved_operands)
2156{
2157 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2158 BB_ASSERT_DEBUG(resolved_operands.size() == sizeof...(Ts), "Resolved operands size mismatch");
2159 auto operand_indices = std::make_index_sequence<sizeof...(Ts)>{};
2160 [f, this, &context, &resolved_operands]<std::size_t... Is>(std::index_sequence<Is...>) {
2161 // This helper handles operand conversion. In particular it converts enums to their underlying type first.
2162 [[maybe_unused]] auto convert_operand = []<typename T>(const Operand& op) -> T {
2163 if constexpr (std::is_enum_v<T>) {
2164 return static_cast<T>(op.to<std::underlying_type_t<T>>());
2165 } else {
2166 return op.to<T>();
2167 }
2168 };
2169 (this->*f)(context, convert_operand.template operator()<std::decay_t<Ts>>(resolved_operands.at(Is))...);
2170 }(operand_indices);
2171}
2172
2181{
2182 const auto& register_info = instruction_info_db.get(opcode).register_info;
2183 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2184 BB_ASSERT_DEBUG(inputs.size() == register_info.num_inputs(), "Inputs size mismatch");
2185 this->inputs = inputs;
2186 for (size_t i = 0; i < register_info.num_inputs(); i++) {
2187 if (register_info.expected_tag(i) && register_info.expected_tag(i) != this->inputs.at(i).get_tag()) {
2188 throw RegisterValidationException(format("Input ",
2189 i,
2190 " tag ",
2191 std::to_string(this->inputs.at(i).get_tag()),
2192 " does not match expected tag ",
2193 std::to_string(*register_info.expected_tag(i))));
2194 }
2195 }
2196}
2197
2205{
2206 const auto& register_info = instruction_info_db.get(opcode).register_info;
2207 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2208 BB_ASSERT_DEBUG(register_info.num_outputs() == 1, "Outputs size mismatch");
2209 this->output = output;
2210}
2211
2212} // 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 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 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 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