Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
execution.cpp
Go to the documentation of this file.
2
3#include <stdexcept>
4#include <string>
5#include <type_traits>
6
42
43namespace bb::avm2::simulation {
44
75{
76 BB_BENCH_NAME("Execution::add");
77 constexpr auto opcode = ExecutionOpCode::ADD;
78 auto& memory = context.get_memory();
79 const MemoryValue a = memory.get(a_addr);
80 const MemoryValue b = memory.get(b_addr);
81 set_and_validate_inputs(opcode, { a, b });
82
84
85 try {
86 MemoryValue c = alu.add(a, b);
87 memory.set(dst_addr, c);
88 set_output(opcode, c);
89 } catch (AluException& e) {
90 throw OpcodeExecutionException("Alu add operation failed: " + std::string(e.what()));
91 }
92}
93
106{
107 BB_BENCH_NAME("Execution::sub");
108 constexpr auto opcode = ExecutionOpCode::SUB;
109 auto& memory = context.get_memory();
110 const MemoryValue a = memory.get(a_addr);
111 const MemoryValue b = memory.get(b_addr);
112 set_and_validate_inputs(opcode, { a, b });
113
115
116 try {
117 MemoryValue c = alu.sub(a, b);
118 memory.set(dst_addr, c);
119 set_output(opcode, c);
120 } catch (AluException& e) {
121 throw OpcodeExecutionException("Alu sub operation failed");
122 }
123}
124
137{
138 BB_BENCH_NAME("Execution::mul");
139 constexpr auto opcode = ExecutionOpCode::MUL;
140 auto& memory = context.get_memory();
141 const MemoryValue a = memory.get(a_addr);
142 const MemoryValue b = memory.get(b_addr);
143 set_and_validate_inputs(opcode, { a, b });
144
146
147 try {
148 MemoryValue c = alu.mul(a, b);
149 memory.set(dst_addr, c);
150 set_output(opcode, c);
151 } catch (AluException& e) {
152 throw OpcodeExecutionException("Alu mul operation failed: " + std::string(e.what()));
153 }
154}
155
171{
172 BB_BENCH_NAME("Execution::div");
173 constexpr auto opcode = ExecutionOpCode::DIV;
174 auto& memory = context.get_memory();
175 const MemoryValue a = memory.get(a_addr);
176 const MemoryValue b = memory.get(b_addr);
177 set_and_validate_inputs(opcode, { a, b });
178
180
181 try {
182 MemoryValue c = alu.div(a, b);
183 memory.set(dst_addr, c);
184 set_output(opcode, c);
185 } catch (AluException& e) {
186 throw OpcodeExecutionException("Alu div operation failed: " + std::string(e.what()));
187 }
188}
189
205{
206 BB_BENCH_NAME("Execution::fdiv");
207 constexpr auto opcode = ExecutionOpCode::FDIV;
208 auto& memory = context.get_memory();
209 const MemoryValue a = memory.get(a_addr);
210 const MemoryValue b = memory.get(b_addr);
211 set_and_validate_inputs(opcode, { a, b });
212
214
215 try {
216 MemoryValue c = alu.fdiv(a, b);
217 memory.set(dst_addr, c);
218 set_output(opcode, c);
219 } catch (AluException& e) {
220 throw OpcodeExecutionException("Alu fdiv operation failed: " + std::string(e.what()));
221 }
222}
223
236{
237 BB_BENCH_NAME("Execution::eq");
238 constexpr auto opcode = ExecutionOpCode::EQ;
239 auto& memory = context.get_memory();
240 const MemoryValue a = memory.get(a_addr);
241 const MemoryValue b = memory.get(b_addr);
242 set_and_validate_inputs(opcode, { a, b });
243
245
246 try {
247 MemoryValue c = alu.eq(a, b);
248 memory.set(dst_addr, c);
249 set_output(opcode, c);
250 } catch (AluException& e) {
251 throw OpcodeExecutionException("Alu eq operation failed: " + std::string(e.what()));
252 }
253}
254
267{
268 BB_BENCH_NAME("Execution::lt");
269 constexpr auto opcode = ExecutionOpCode::LT;
270 auto& memory = context.get_memory();
271 const MemoryValue a = memory.get(a_addr);
272 const MemoryValue b = memory.get(b_addr);
273 set_and_validate_inputs(opcode, { a, b });
274
276
277 try {
278 MemoryValue c = alu.lt(a, b);
279 memory.set(dst_addr, c);
280 set_output(opcode, c);
281 } catch (AluException& e) {
282 throw OpcodeExecutionException("Alu lt operation failed: " + std::string(e.what()));
283 }
284}
285
298{
299 BB_BENCH_NAME("Execution::lte");
300 constexpr auto opcode = ExecutionOpCode::LTE;
301 auto& memory = context.get_memory();
302 const MemoryValue a = memory.get(a_addr);
303 const MemoryValue b = memory.get(b_addr);
304 set_and_validate_inputs(opcode, { a, b });
305
307
308 try {
309 MemoryValue c = alu.lte(a, b);
310 memory.set(dst_addr, c);
311 set_output(opcode, c);
312 } catch (AluException& e) {
313 throw OpcodeExecutionException("Alu lte operation failed: " + std::string(e.what()));
314 }
315}
316
328{
329 BB_BENCH_NAME("Execution::op_not");
330 constexpr auto opcode = ExecutionOpCode::NOT;
331 auto& memory = context.get_memory();
332 const MemoryValue a = memory.get(src_addr);
333 set_and_validate_inputs(opcode, { a });
334
336
337 try {
338 MemoryValue b = alu.op_not(a);
339 memory.set(dst_addr, b);
340 set_output(opcode, b);
341 } catch (AluException& e) {
342 throw OpcodeExecutionException("Alu not operation failed: " + std::string(e.what()));
343 }
344}
345
360{
361 BB_BENCH_NAME("Execution::shl");
362 constexpr auto opcode = ExecutionOpCode::SHL;
363 auto& memory = context.get_memory();
364 const MemoryValue a = memory.get(a_addr);
365 const MemoryValue b = memory.get(b_addr);
366 set_and_validate_inputs(opcode, { a, b });
367
369
370 try {
371 MemoryValue c = alu.shl(a, b);
372 memory.set(dst_addr, c);
373 set_output(opcode, c);
374 } catch (const AluException& e) {
375 throw OpcodeExecutionException("SHL Exception: " + std::string(e.what()));
376 }
377}
378
393{
394 BB_BENCH_NAME("Execution::shr");
395 constexpr auto opcode = ExecutionOpCode::SHR;
396 auto& memory = context.get_memory();
397 const MemoryValue a = memory.get(a_addr);
398 const MemoryValue b = memory.get(b_addr);
399 set_and_validate_inputs(opcode, { a, b });
400
402
403 try {
404 MemoryValue c = alu.shr(a, b);
405 memory.set(dst_addr, c);
406 set_output(opcode, c);
407 } catch (const AluException& e) {
408 throw OpcodeExecutionException("SHR Exception: " + std::string(e.what()));
409 }
410}
411
423{
424 BB_BENCH_NAME("Execution::cast");
425 constexpr auto opcode = ExecutionOpCode::CAST;
426 auto& memory = context.get_memory();
427 const auto& val = memory.get(src_addr);
428 set_and_validate_inputs(opcode, { val });
429
431 MemoryValue truncated = alu.truncate(val.as_ff(), dst_tag);
432 memory.set(dst_addr, truncated);
433 set_output(opcode, truncated);
434}
435
448{
449 BB_BENCH_NAME("Execution::get_env_var");
450 constexpr auto opcode = ExecutionOpCode::GETENVVAR;
451 auto& memory = context.get_memory();
452
454
455 // If env_var_value is not a valid EnvironmentVariable enum value, throw an OpcodeExecutionException.
456 if (env_var_value > static_cast<uint8_t>(EnvironmentVariable::MAX)) {
457 throw OpcodeExecutionException("Invalid environment variable enum value");
458 }
459
460 MemoryValue result;
461
462 switch (static_cast<EnvironmentVariable>(env_var_value)) {
464 result = MemoryValue::from<FF>(context.get_address());
465 break;
467 result = MemoryValue::from<FF>(context.get_msg_sender());
468 break;
470 result = MemoryValue::from<FF>(context.get_transaction_fee());
471 break;
473 result = MemoryValue::from<FF>(context.get_globals().chain_id);
474 break;
476 result = MemoryValue::from<FF>(context.get_globals().version);
477 break;
479 result = MemoryValue::from<uint32_t>(context.get_globals().block_number);
480 break;
482 result = MemoryValue::from<uint64_t>(context.get_globals().timestamp);
483 break;
485 result = MemoryValue::from<uint128_t>(context.get_globals().gas_fees.fee_per_l2_gas);
486 break;
488 result = MemoryValue::from<uint128_t>(context.get_globals().gas_fees.fee_per_da_gas);
489 break;
491 result = MemoryValue::from<uint1_t>(context.get_is_static() ? 1 : 0);
492 break;
494 result = MemoryValue::from<uint32_t>(context.gas_left().l2_gas);
495 break;
497 result = MemoryValue::from<uint32_t>(context.gas_left().da_gas);
498 break;
499 default:
500 // We leave this here defensively.
501 throw OpcodeExecutionException("Invalid environment variable enum value");
502 }
503
504 memory.set(dst_addr, result);
505 set_output(opcode, result);
506}
507
520{
521 BB_BENCH_NAME("Execution::set");
523
524 constexpr auto opcode = ExecutionOpCode::SET;
525 MemoryValue truncated = alu.truncate(value, dst_tag);
526 context.get_memory().set(dst_addr, truncated);
527 set_output(opcode, truncated);
528}
529
540{
541 BB_BENCH_NAME("Execution::mov");
542 constexpr auto opcode = ExecutionOpCode::MOV;
543 auto& memory = context.get_memory();
544 const MemoryValue v = memory.get(src_addr);
545 set_and_validate_inputs(opcode, { v });
546
548
549 memory.set(dst_addr, v);
550 set_output(opcode, v);
551}
552
575 MemoryAddress l2_gas_offset,
576 MemoryAddress da_gas_offset,
577 MemoryAddress addr,
578 MemoryAddress cd_size_offset,
580{
581 BB_BENCH_NAME("Execution::call");
582 constexpr auto opcode = ExecutionOpCode::CALL;
583 auto& memory = context.get_memory();
584
585 // NOTE: these reads cannot fail due to addressing guarantees.
586 const auto& allocated_l2_gas_read = memory.get(l2_gas_offset);
587 const auto& allocated_da_gas_read = memory.get(da_gas_offset);
588 const auto& contract_address = memory.get(addr);
589 // Cd offset loads are deferred to calldatacopy
590 const auto& cd_size = memory.get(cd_size_offset);
591
592 set_and_validate_inputs(opcode, { allocated_l2_gas_read, allocated_da_gas_read, contract_address, cd_size });
593
594 get_gas_tracker().consume_gas(); // Base gas.
596 Gas{ .l2_gas = allocated_l2_gas_read.as<uint32_t>(), .da_gas = allocated_da_gas_read.as<uint32_t>() });
597
598 // Tag check contract address + cd_size
600 /*msg_sender=*/context.get_address(),
601 /*transaction_fee=*/context.get_transaction_fee(),
602 /*parent_context=*/context,
603 /*cd_offset_address=*/cd_offset,
604 /*cd_size=*/cd_size.as<uint32_t>(),
605 /*is_static=*/context.get_is_static(),
606 /*gas_limit=*/gas_limit,
607 /*phase=*/context.get_phase());
608
609 // We do not recurse. This context will be use on the next cycle of execution.
610 handle_enter_call(context, std::move(nested_context));
611}
612
635 MemoryAddress l2_gas_offset,
636 MemoryAddress da_gas_offset,
637 MemoryAddress addr,
638 MemoryAddress cd_size_offset,
640{
641 BB_BENCH_NAME("Execution::static_call");
642 constexpr auto opcode = ExecutionOpCode::STATICCALL;
643 auto& memory = context.get_memory();
644
645 // NOTE: these reads cannot fail due to addressing guarantees.
646 const auto& allocated_l2_gas_read = memory.get(l2_gas_offset);
647 const auto& allocated_da_gas_read = memory.get(da_gas_offset);
648 const auto& contract_address = memory.get(addr);
649 // Cd offset loads are deferred to calldatacopy
650 const auto& cd_size = memory.get(cd_size_offset);
651
652 set_and_validate_inputs(opcode, { allocated_l2_gas_read, allocated_da_gas_read, contract_address, cd_size });
653
654 get_gas_tracker().consume_gas(); // Base gas.
656 Gas{ .l2_gas = allocated_l2_gas_read.as<uint32_t>(), .da_gas = allocated_da_gas_read.as<uint32_t>() });
657
658 // Tag check contract address + cd_size
660 /*msg_sender=*/context.get_address(),
661 /*transaction_fee=*/context.get_transaction_fee(),
662 /*parent_context=*/context,
663 /*cd_offset_address=*/cd_offset,
664 /*cd_size=*/cd_size.as<uint32_t>(),
665 /*is_static=*/true,
666 /*gas_limit=*/gas_limit,
667 /*phase=*/context.get_phase());
668
669 // We do not recurse. This context will be use on the next cycle of execution.
670 handle_enter_call(context, std::move(nested_context));
671}
672
690 MemoryAddress cd_size_offset,
693{
694 BB_BENCH_NAME("Execution::cd_copy");
695 constexpr auto opcode = ExecutionOpCode::CALLDATACOPY;
696 auto& memory = context.get_memory();
697 const auto& cd_copy_size = memory.get(cd_size_offset); // Tag check u32
698 const auto& cd_offset_read = memory.get(cd_offset); // Tag check u32
699 set_and_validate_inputs(opcode, { cd_copy_size, cd_offset_read });
700
701 get_gas_tracker().consume_gas({ .l2_gas = cd_copy_size.as<uint32_t>(), .da_gas = 0 });
702
703 try {
704 data_copy.cd_copy(context, cd_copy_size.as<uint32_t>(), cd_offset_read.as<uint32_t>(), dst_addr);
705 } catch (const DataCopyException& e) {
706 throw OpcodeExecutionException("cd copy failed: " + std::string(e.what()));
707 }
708}
709
726 MemoryAddress rd_size_offset,
727 MemoryAddress rd_offset,
729{
730 BB_BENCH_NAME("Execution::rd_copy");
731 constexpr auto opcode = ExecutionOpCode::RETURNDATACOPY;
732 auto& memory = context.get_memory();
733 const auto& rd_copy_size = memory.get(rd_size_offset); // Tag check u32
734 const auto& rd_offset_read = memory.get(rd_offset); // Tag check u32
735 set_and_validate_inputs(opcode, { rd_copy_size, rd_offset_read });
736
737 get_gas_tracker().consume_gas({ .l2_gas = rd_copy_size.as<uint32_t>(), .da_gas = 0 });
738
739 try {
740 data_copy.rd_copy(context, rd_copy_size.as<uint32_t>(), rd_offset_read.as<uint32_t>(), dst_addr);
741 } catch (const DataCopyException& e) {
742 throw OpcodeExecutionException("rd copy failed: " + std::string(e.what()));
743 }
744}
745
755{
756 BB_BENCH_NAME("Execution::rd_size");
757 constexpr auto opcode = ExecutionOpCode::RETURNDATASIZE;
758 auto& memory = context.get_memory();
759
761
762 // This is safe because the last_rd_size is tag checked on ret/revert to be U32
763 MemoryValue rd_size = MemoryValue::from<uint32_t>(context.get_last_rd_size());
764 memory.set(dst_addr, rd_size);
765 set_output(opcode, rd_size);
766}
767
782{
783 BB_BENCH_NAME("Execution::ret");
784 constexpr auto opcode = ExecutionOpCode::RETURN;
785 auto& memory = context.get_memory();
786 const auto& rd_size = memory.get(ret_size_offset);
787 set_and_validate_inputs(opcode, { rd_size });
788
790
791 set_execution_result({ .rd_offset = ret_offset,
792 .rd_size = rd_size.as<uint32_t>(),
793 .gas_used = context.get_gas_used(),
794 .success = true,
795 .halting_pc = context.get_pc(),
796 .halting_message = std::nullopt });
797
798 context.halt();
799}
800
815{
816 BB_BENCH_NAME("Execution::revert");
817 constexpr auto opcode = ExecutionOpCode::REVERT;
818 auto& memory = context.get_memory();
819 const auto& rev_size = memory.get(rev_size_offset);
820 set_and_validate_inputs(opcode, { rev_size });
821
823
824 set_execution_result({ .rd_offset = rev_offset,
825 .rd_size = rev_size.as<uint32_t>(),
826 .gas_used = context.get_gas_used(),
827 .success = false,
828 .halting_pc = context.get_pc(),
829 .halting_message = "Assertion failed: " });
830
831 context.halt();
832}
833
844{
845 BB_BENCH_NAME("Execution::jump");
847
848 context.set_next_pc(loc);
849}
850
864{
865 BB_BENCH_NAME("Execution::jumpi");
866 constexpr auto opcode = ExecutionOpCode::JUMPI;
867 auto& memory = context.get_memory();
868
869 const auto& resolved_cond = memory.get(cond_addr);
870 set_and_validate_inputs(opcode, { resolved_cond });
871
873
874 if (resolved_cond.as<uint1_t>().value() == 1) {
875 context.set_next_pc(loc);
876 }
877}
878
890{
891 BB_BENCH_NAME("Execution::internal_call");
893
894 auto& internal_call_stack_manager = context.get_internal_call_stack_manager();
895 // The next pc is pushed onto the internal call stack. This will become return_pc later.
896 internal_call_stack_manager.push(context.get_pc(), context.get_next_pc());
897 context.set_next_pc(loc);
898}
899
910{
911 BB_BENCH_NAME("Execution::internal_return");
913
914 auto& internal_call_stack_manager = context.get_internal_call_stack_manager();
915 try {
916 auto next_pc = internal_call_stack_manager.pop();
917 context.set_next_pc(next_pc);
918 } catch (const InternalCallStackException& e) {
919 // Re-throw
920 throw OpcodeExecutionException("Internal return failed: " + std::string(e.what()));
921 }
922}
923
937{
938 BB_BENCH_NAME("Execution::keccak_permutation");
940
941 try {
942 keccakf1600.permutation(context.get_memory(), dst_addr, src_addr);
943 } catch (const KeccakF1600Exception& e) {
944 throw OpcodeExecutionException("Keccak permutation failed: " + std::string(e.what()));
945 }
946}
947
980
990{
991 BB_BENCH_NAME("Execution::success_copy");
992 constexpr auto opcode = ExecutionOpCode::SUCCESSCOPY;
993 auto& memory = context.get_memory();
994
996
997 MemoryValue success = MemoryValue::from<uint1_t>(context.get_last_success());
998 memory.set(dst_addr, success);
999 set_output(opcode, success);
1000}
1001
1016{
1017 BB_BENCH_NAME("Execution::and_op");
1018 constexpr auto opcode = ExecutionOpCode::AND;
1019 auto& memory = context.get_memory();
1020 const MemoryValue a = memory.get(a_addr);
1021 const MemoryValue b = memory.get(b_addr);
1022 set_and_validate_inputs(opcode, { a, b });
1023
1024 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1025 // will result in an exception in the bitwise subtrace.
1026 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1027
1028 try {
1029 MemoryValue c = bitwise.and_op(a, b);
1030 memory.set(dst_addr, c);
1031 set_output(opcode, c);
1032 } catch (const BitwiseException& e) {
1033 throw OpcodeExecutionException("Bitwise AND Exeception: " + std::string(e.what()));
1034 }
1035}
1036
1053{
1054 BB_BENCH_NAME("Execution::or_op");
1055 constexpr auto opcode = ExecutionOpCode::OR;
1056 auto& memory = context.get_memory();
1057 const MemoryValue a = memory.get(a_addr);
1058 const MemoryValue b = memory.get(b_addr);
1059 set_and_validate_inputs(opcode, { a, b });
1060
1061 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1062 // will result in an exception in the bitwise subtrace.
1063 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1064
1065 try {
1066 MemoryValue c = bitwise.or_op(a, b);
1067 memory.set(dst_addr, c);
1068 set_output(opcode, c);
1069 } catch (const BitwiseException& e) {
1070 throw OpcodeExecutionException("Bitwise OR Exception: " + std::string(e.what()));
1071 }
1072}
1073
1088{
1089 BB_BENCH_NAME("Execution::xor_op");
1090 constexpr auto opcode = ExecutionOpCode::XOR;
1091 auto& memory = context.get_memory();
1092 const MemoryValue a = memory.get(a_addr);
1093 const MemoryValue b = memory.get(b_addr);
1094 set_and_validate_inputs(opcode, { a, b });
1095
1096 // Dynamic gas consumption for bitwise is dependent on the tag, FF tags are valid here but
1097 // will result in an exception in the bitwise subtrace.
1098 get_gas_tracker().consume_gas({ .l2_gas = get_tag_bytes(a.get_tag()), .da_gas = 0 });
1099
1100 try {
1101 MemoryValue c = bitwise.xor_op(a, b);
1102 memory.set(dst_addr, c);
1103 set_output(opcode, c);
1104 } catch (const BitwiseException& e) {
1105 throw OpcodeExecutionException("Bitwise XOR Exception: " + std::string(e.what()));
1106 }
1107}
1108
1123{
1124 BB_BENCH_NAME("Execution::sload");
1125 constexpr auto opcode = ExecutionOpCode::SLOAD;
1126
1127 auto& memory = context.get_memory();
1128
1129 const auto& slot = memory.get(slot_addr);
1130 set_and_validate_inputs(opcode, { slot });
1131
1133
1134 auto value = MemoryValue::from<FF>(merkle_db.storage_read(context.get_address(), slot.as<FF>()));
1135
1136 memory.set(dst_addr, value);
1137 set_output(opcode, value);
1138}
1139
1156{
1157 BB_BENCH_NAME("Execution::sstore");
1158 constexpr auto opcode = ExecutionOpCode::SSTORE;
1159
1160 auto& memory = context.get_memory();
1161
1162 const auto& slot = memory.get(slot_addr);
1163 const auto& value = memory.get(src_addr);
1164 set_and_validate_inputs(opcode, { value, slot });
1165
1166 bool was_slot_written_before = merkle_db.was_storage_written(context.get_address(), slot.as_ff());
1167 uint32_t da_gas_factor = static_cast<uint32_t>(!was_slot_written_before);
1168 get_gas_tracker().consume_gas({ .l2_gas = 0, .da_gas = da_gas_factor });
1169
1170 if (context.get_is_static()) {
1172 "SSTORE: Static call cannot update the state. Cannot write to storage in static context");
1173 }
1174
1175 if (!was_slot_written_before &&
1177 throw OpcodeExecutionException("SSTORE: Maximum number of data writes reached");
1178 }
1179
1180 merkle_db.storage_write(context.get_address(), slot.as_ff(), value.as_ff(), false);
1181}
1182
1199 MemoryAddress unique_note_hash_addr,
1200 MemoryAddress leaf_index_addr,
1202{
1203 BB_BENCH_NAME("Execution::note_hash_exists");
1204 constexpr auto opcode = ExecutionOpCode::NOTEHASHEXISTS;
1205
1206 auto& memory = context.get_memory();
1207 const auto& unique_note_hash = memory.get(unique_note_hash_addr);
1208 const auto& leaf_index = memory.get(leaf_index_addr);
1209 set_and_validate_inputs(opcode, { unique_note_hash, leaf_index });
1210
1212
1213 uint64_t leaf_index_value = leaf_index.as<uint64_t>();
1214
1215 bool index_in_range = greater_than.gt(NOTE_HASH_TREE_LEAF_COUNT, leaf_index_value);
1216
1218
1219 if (index_in_range) {
1220 value = MemoryValue::from<uint1_t>(merkle_db.note_hash_exists(leaf_index_value, unique_note_hash.as<FF>()));
1221 } else {
1222 value = MemoryValue::from<uint1_t>(0);
1223 }
1224
1225 memory.set(dst_addr, value);
1226 set_output(opcode, value);
1227}
1228
1243 MemoryAddress nullifier_offset,
1244 MemoryAddress address_offset,
1245 MemoryAddress exists_offset)
1246{
1247 BB_BENCH_NAME("Execution::nullifier_exists");
1248 constexpr auto opcode = ExecutionOpCode::NULLIFIEREXISTS;
1249 auto& memory = context.get_memory();
1250
1251 const auto& nullifier = memory.get(nullifier_offset);
1252 const auto& address = memory.get(address_offset);
1254
1256
1257 // Check nullifier existence via MerkleDB
1258 // (this also tag checks address and nullifier as FFs)
1259 auto exists = merkle_db.nullifier_exists(address.as_ff(), nullifier.as_ff());
1260
1261 // Write result to memory
1262 // (assigns tag u1 to result)
1263 MemoryValue result = MemoryValue::from<uint1_t>(exists ? 1 : 0);
1264 memory.set(exists_offset, result);
1265 set_output(opcode, result);
1266}
1267
1283{
1284 BB_BENCH_NAME("Execution::emit_nullifier");
1285 constexpr auto opcode = ExecutionOpCode::EMITNULLIFIER;
1286
1287 auto& memory = context.get_memory();
1288 const auto& nullifier = memory.get(nullifier_addr);
1290
1292
1293 if (context.get_is_static()) {
1295 "EMITNULLIFIER: Static call cannot update the state. Cannot emit nullifier in static context");
1296 }
1297
1299 throw OpcodeExecutionException("EMITNULLIFIER: Maximum number of nullifiers reached");
1300 }
1301
1302 // Emit nullifier via MerkleDB.
1303 try {
1304 merkle_db.nullifier_write(context.get_address(), nullifier.as<FF>());
1305 } catch (const NullifierCollisionException& e) {
1306 throw OpcodeExecutionException(format("EMITNULLIFIER: ", e.what()));
1307 }
1308}
1309
1328 MemoryAddress address_offset,
1329 MemoryAddress dst_offset,
1330 uint8_t member_enum)
1331{
1332 BB_BENCH_NAME("Execution::get_contract_instance");
1333 constexpr auto opcode = ExecutionOpCode::GETCONTRACTINSTANCE;
1334 auto& memory = context.get_memory();
1335
1336 // Execution can still handle address memory read and tag checking
1337 const auto& address_value = memory.get(address_offset);
1338 set_and_validate_inputs(opcode, { address_value });
1339
1340 AztecAddress contract_address = address_value.as<AztecAddress>();
1341
1343
1344 // Call the dedicated opcode component to get the contract instance, validate the enum,
1345 // handle other errors, and perform the memory writes.
1346 try {
1348 } catch (const GetContractInstanceException& e) {
1349 throw OpcodeExecutionException("GetContractInstance Exception: " + std::string(e.what()));
1350 }
1351
1352 // No `set_output` here since the dedicated component handles memory writes.
1353}
1354
1369{
1370 BB_BENCH_NAME("Execution::emit_note_hash");
1371 constexpr auto opcode = ExecutionOpCode::EMITNOTEHASH;
1372
1373 auto& memory = context.get_memory();
1374 const auto& note_hash = memory.get(note_hash_addr);
1376
1378
1379 if (context.get_is_static()) {
1381 "EMITNOTEHASH: Static call cannot update the state. Cannot emit note hash in static context");
1382 }
1383
1385 throw OpcodeExecutionException("EMITNOTEHASH: Maximum number of note hashes reached");
1386 }
1387
1388 merkle_db.note_hash_write(context.get_address(), note_hash.as<FF>());
1389}
1390
1407 MemoryAddress msg_hash_addr,
1408 MemoryAddress leaf_index_addr,
1410{
1411 BB_BENCH_NAME("Execution::l1_to_l2_message_exists");
1412 constexpr auto opcode = ExecutionOpCode::L1TOL2MSGEXISTS;
1413
1414 auto& memory = context.get_memory();
1415 const auto& msg_hash = memory.get(msg_hash_addr);
1416 const auto& leaf_index = memory.get(leaf_index_addr);
1417 set_and_validate_inputs(opcode, { msg_hash, leaf_index });
1418
1420
1421 uint64_t leaf_index_value = leaf_index.as<uint64_t>();
1422
1423 bool index_in_range = greater_than.gt(L1_TO_L2_MSG_TREE_LEAF_COUNT, leaf_index_value);
1424
1426
1427 if (index_in_range) {
1428 value = MemoryValue::from<uint1_t>(merkle_db.l1_to_l2_msg_exists(leaf_index_value, msg_hash.as<FF>()));
1429 } else {
1430 value = MemoryValue::from<uint1_t>(0);
1431 }
1432
1433 memory.set(dst_addr, value);
1434 set_output(opcode, value);
1435}
1436
1451{
1452 BB_BENCH_NAME("Execution::poseidon2_permutation");
1454 try {
1455 poseidon2.permutation(context.get_memory(), src_addr, dst_addr);
1456 } catch (const Poseidon2Exception& e) {
1457 throw OpcodeExecutionException("Poseidon2 permutation failed: " + std::string(e.what()));
1458 }
1459}
1460
1487 MemoryAddress p_x_addr,
1488 MemoryAddress p_y_addr,
1489 MemoryAddress p_inf_addr,
1490 MemoryAddress q_x_addr,
1491 MemoryAddress q_y_addr,
1492 MemoryAddress q_inf_addr,
1494{
1495 BB_BENCH_NAME("Execution::ecc_add");
1496 constexpr auto opcode = ExecutionOpCode::ECADD;
1497 auto& memory = context.get_memory();
1498
1499 // Read the points from memory.
1500 const auto& p_x = memory.get(p_x_addr);
1501 const auto& p_y = memory.get(p_y_addr);
1502 const auto& p_inf = memory.get(p_inf_addr);
1503
1504 const auto& q_x = memory.get(q_x_addr);
1505 const auto& q_y = memory.get(q_y_addr);
1506 const auto& q_inf = memory.get(q_inf_addr);
1507
1508 set_and_validate_inputs(opcode, { p_x, p_y, p_inf, q_x, q_y, q_inf });
1510
1511 // Once inputs are tag checked the conversion to EmbeddedCurvePoint is safe, on curve checks are done in the add
1512 // method.
1513 EmbeddedCurvePoint p = EmbeddedCurvePoint(p_x.as_ff(), p_y.as_ff(), p_inf == MemoryValue::from<uint1_t>(1));
1514 EmbeddedCurvePoint q = EmbeddedCurvePoint(q_x.as_ff(), q_y.as_ff(), q_inf == MemoryValue::from<uint1_t>(1));
1515
1516 try {
1518 } catch (const EccException& e) {
1519 throw OpcodeExecutionException("Embedded curve add failed: " + std::string(e.what()));
1520 }
1521}
1522
1548 MemoryAddress value_addr,
1549 MemoryAddress radix_addr,
1550 MemoryAddress num_limbs_addr,
1551 MemoryAddress is_output_bits_addr, // Decides if output is U1 or U8
1553{
1554 BB_BENCH_NAME("Execution::to_radix_be");
1555 constexpr auto opcode = ExecutionOpCode::TORADIXBE;
1556 auto& memory = context.get_memory();
1557
1558 const auto& value = memory.get(value_addr); // Field
1559 const auto& radix = memory.get(radix_addr); // U32
1560 const auto& num_limbs = memory.get(num_limbs_addr); // U32
1561 const auto& is_output_bits = memory.get(is_output_bits_addr); // U1
1562
1563 // Tag check the inputs
1564 {
1565 BB_BENCH_NAME("Execution::to_radix_be::set_and_validate_inputs");
1566 set_and_validate_inputs(opcode, { value, radix, num_limbs, is_output_bits });
1567 }
1568
1569 // The range check for a valid radix (2 <= radix <= 256) is done in the gadget.
1570 // However, in order to compute the dynamic gas value we need to constrain the radix
1571 // to be <= 256 since the `get_p_limbs_per_radix` lookup table is only defined for the range [0, 256].
1572 // This does mean that the <= 256 check is duplicated - this can be optimized later.
1573
1574 // The dynamic gas factor is the maximum of the num_limbs requested by the opcode and the number of limbs
1575 // the gadget that the field modulus, p, decomposes into given a radix (num_p_limbs).
1576 // See to_radix.pil for how these values impact the row count.
1577
1578 // The lookup table of radix decomposed limbs of the modulus p is defined for radix values [0, 256],
1579 // so for any radix value greater than 256 we set num_p_limbs to 32 - with
1580 // the understanding the opcode will fail in the gadget (since the radix is invalid).
1581 uint32_t radix_value = radix.as<uint32_t>();
1582 uint32_t num_p_limbs = greater_than.gt(radix.as<uint32_t>(), 256)
1583 ? 32
1584 : static_cast<uint32_t>(get_p_limbs_per_radix_size(radix_value));
1585
1586 // Compute the dynamic gas factor - done this way to trigger relevant circuit interactions
1587 if (greater_than.gt(num_limbs.as<uint32_t>(), num_p_limbs)) {
1588 get_gas_tracker().consume_gas({ .l2_gas = num_limbs.as<uint32_t>(), .da_gas = 0 });
1589 } else {
1590 get_gas_tracker().consume_gas({ .l2_gas = num_p_limbs, .da_gas = 0 });
1591 }
1592
1593 try {
1594 // Call the gadget to perform the conversion.
1595 to_radix.to_be_radix(memory,
1596 value.as_ff(),
1597 radix.as<uint32_t>(),
1598 num_limbs.as<uint32_t>(),
1599 is_output_bits.as<uint1_t>().value() == 1,
1600 dst_addr);
1601 } catch (const ToRadixException& e) {
1602 throw OpcodeExecutionException("ToRadixBe gadget failed: " + std::string(e.what()));
1603 }
1604}
1605
1623{
1624 BB_BENCH_NAME("Execution::emit_unencrypted_log");
1625 constexpr auto opcode = ExecutionOpCode::EMITUNENCRYPTEDLOG;
1626 auto& memory = context.get_memory();
1627
1628 const auto& log_size = memory.get(log_size_offset);
1629 set_and_validate_inputs(opcode, { log_size });
1630 uint32_t log_size_int = log_size.as<uint32_t>();
1631
1632 get_gas_tracker().consume_gas({ .l2_gas = log_size_int, .da_gas = log_size_int });
1633
1634 // Call the dedicated opcode component to emit the log
1635 try {
1637 memory, context, context.get_address(), log_offset, log_size_int);
1638 } catch (const EmitUnencryptedLogException& e) {
1639 throw OpcodeExecutionException("EmitUnencryptedLog Exception: " + std::string(e.what()));
1640 }
1641}
1642
1659{
1660 BB_BENCH_NAME("Execution::send_l2_to_l1_msg");
1661 constexpr auto opcode = ExecutionOpCode::SENDL2TOL1MSG;
1662 auto& memory = context.get_memory();
1663
1664 const auto& recipient = memory.get(recipient_addr);
1665 const auto& content = memory.get(content_addr);
1667
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 {
1848 .success = result.success,
1849 .gas_used = result.gas_used,
1850 };
1851}
1852
1861{
1862 const auto& side_effects = parent_context.get_side_effect_tracker().get_side_effects();
1863
1864 // Optionally collect call stack metadata.
1865 call_stack_metadata_collector.notify_enter_call(child_context->get_address(),
1866 parent_context.get_pc(),
1867 make_calldata_provider(*child_context),
1868 child_context->get_is_static(),
1869 child_context->get_gas_limit());
1870
1871 const auto parent_bytecode_id = parent_context.get_bytecode_manager().get_retrieved_bytecode_id();
1872 BB_ASSERT(parent_bytecode_id.has_value(),
1873 "Bytecode should have been retrieved in the parent context if it issued a call");
1874
1875 ctx_stack_events.emit({
1876 .id = parent_context.get_context_id(),
1877 .parent_id = parent_context.get_parent_id(),
1878 .entered_context_id = child_context->get_context_id(), // gets the context id of the child!
1879 .next_pc = parent_context.get_next_pc(),
1880 .msg_sender = parent_context.get_msg_sender(),
1881 .contract_addr = parent_context.get_address(),
1882 .bytecode_id = parent_bytecode_id.value(),
1883 .is_static = parent_context.get_is_static(),
1884 .parent_cd_addr = parent_context.get_parent_cd_addr(),
1885 .parent_cd_size = parent_context.get_parent_cd_size(),
1886 .parent_gas_used = parent_context.get_parent_gas_used(),
1887 .parent_gas_limit = parent_context.get_parent_gas_limit(),
1888 .internal_call_id = parent_context.get_internal_call_stack_manager().get_call_id(),
1889 .internal_call_return_id = parent_context.get_internal_call_stack_manager().get_return_call_id(),
1890 .next_internal_call_id = parent_context.get_internal_call_stack_manager().get_next_call_id(),
1891 .tree_states = merkle_db.get_tree_state(),
1892 .written_public_data_slots_tree_snapshot = parent_context.get_written_public_data_slots_tree_snapshot(),
1893 // Non-tree-tracked side effects
1894 .numUnencryptedLogFields = side_effects.get_num_unencrypted_log_fields(),
1895 .numL2ToL1Messages = static_cast<uint32_t>(side_effects.l2_to_l1_messages.size()),
1896 });
1897
1898 external_call_stack.push(std::move(child_context));
1899}
1900
1905{
1906 BB_BENCH_NAME("Execution::handle_exit_call");
1907
1908 // NOTE: the current (child) context should not be modified here, since it was already emitted.
1910
1911 const ExecutionResult& result = get_execution_result();
1912
1913 // Optionally collect call stack metadata.
1915 result.success,
1916 result.halting_pc,
1917 result.halting_message,
1918 make_return_data_provider(*child_context, result.rd_offset, result.rd_size),
1919 make_internal_call_stack_provider(child_context->get_internal_call_stack_manager()));
1920
1921 external_call_stack.pop();
1922
1923 // We only handle reverting/committing of nested calls. Enqueued calls are handled by TX execution.
1924 if (!external_call_stack.empty()) {
1925 // Note: committing or reverting the db here also commits or reverts the
1926 // tracked nullifiers, public writes dictionary, etc. These structures
1927 // "listen" to the db changes.
1928 if (result.success) {
1930 } else {
1932 }
1933
1934 auto& parent_context = *external_call_stack.top();
1935 // was not top level, communicate with parent
1936 parent_context.set_last_rd_addr(result.rd_offset);
1937 parent_context.set_last_rd_size(result.rd_size);
1938 parent_context.set_last_success(result.success);
1939 // Safe since the nested context gas limit should be clamped to the available gas.
1940 parent_context.set_gas_used(result.gas_used + parent_context.get_gas_used());
1941 parent_context.set_child_context(std::move(child_context));
1942
1943 BB_ASSERT_EQ(parent_context.get_checkpoint_id_at_creation(),
1945 "Checkpoint id mismatch: gone back to the wrong db/context");
1946 }
1947 // Else: was top level. ExecutionResult is already set and that will be returned.
1948}
1949
1957void Execution::handle_exceptional_halt(ContextInterface& context, const std::string& halting_message)
1958{
1959 context.set_gas_used(context.get_gas_limit()); // Consume all gas.
1960 context.halt();
1962 .rd_offset = 0,
1963 .rd_size = 0,
1964 .gas_used = context.get_gas_used(),
1965 .success = false,
1966 .halting_pc = context.get_pc(),
1967 .halting_message = halting_message,
1968 });
1969}
1970
1981 const std::vector<Operand>& resolved_operands)
1982{
1983 BB_BENCH_NAME("Execution::dispatch_opcode");
1984
1985 debug("Dispatching opcode: ", opcode, " (", static_cast<uint32_t>(opcode), ")");
1986 switch (opcode) {
1988 call_with_operands(&Execution::add, context, resolved_operands);
1989 break;
1991 call_with_operands(&Execution::sub, context, resolved_operands);
1992 break;
1994 call_with_operands(&Execution::mul, context, resolved_operands);
1995 break;
1997 call_with_operands(&Execution::div, context, resolved_operands);
1998 break;
2000 call_with_operands(&Execution::fdiv, context, resolved_operands);
2001 break;
2003 call_with_operands(&Execution::eq, context, resolved_operands);
2004 break;
2006 call_with_operands(&Execution::lt, context, resolved_operands);
2007 break;
2009 call_with_operands(&Execution::lte, context, resolved_operands);
2010 break;
2012 call_with_operands(&Execution::op_not, context, resolved_operands);
2013 break;
2015 call_with_operands(&Execution::shl, context, resolved_operands);
2016 break;
2018 call_with_operands(&Execution::shr, context, resolved_operands);
2019 break;
2021 call_with_operands(&Execution::cast, context, resolved_operands);
2022 break;
2025 break;
2027 call_with_operands(&Execution::set, context, resolved_operands);
2028 break;
2030 call_with_operands(&Execution::mov, context, resolved_operands);
2031 break;
2033 call_with_operands(&Execution::call, context, resolved_operands);
2034 break;
2037 break;
2039 call_with_operands(&Execution::ret, context, resolved_operands);
2040 break;
2042 call_with_operands(&Execution::revert, context, resolved_operands);
2043 break;
2045 call_with_operands(&Execution::jump, context, resolved_operands);
2046 break;
2048 call_with_operands(&Execution::jumpi, context, resolved_operands);
2049 break;
2051 call_with_operands(&Execution::cd_copy, context, resolved_operands);
2052 break;
2054 call_with_operands(&Execution::rd_copy, context, resolved_operands);
2055 break;
2058 break;
2061 break;
2064 break;
2067 break;
2069 call_with_operands(&Execution::rd_size, context, resolved_operands);
2070 break;
2072 call_with_operands(&Execution::debug_log, context, resolved_operands);
2073 break;
2075 call_with_operands(&Execution::and_op, context, resolved_operands);
2076 break;
2078 call_with_operands(&Execution::or_op, context, resolved_operands);
2079 break;
2081 call_with_operands(&Execution::xor_op, context, resolved_operands);
2082 break;
2084 call_with_operands(&Execution::sload, context, resolved_operands);
2085 break;
2087 call_with_operands(&Execution::sstore, context, resolved_operands);
2088 break;
2091 break;
2094 break;
2097 break;
2100 break;
2103 break;
2106 break;
2109 break;
2111 call_with_operands(&Execution::ecc_add, context, resolved_operands);
2112 break;
2115 break;
2118 break;
2121 break;
2124 break;
2125 default:
2126 // NOTE: Keep this a `std::runtime_error` so that the main loop panics.
2127 throw std::runtime_error("Tried to dispatch unknown execution opcode: " +
2128 std::to_string(static_cast<uint32_t>(opcode)));
2129 }
2130}
2131
2141template <typename... Ts>
2144 const std::vector<Operand>& resolved_operands)
2145{
2146 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2147 BB_ASSERT_DEBUG(resolved_operands.size() == sizeof...(Ts), "Resolved operands size mismatch");
2148 auto operand_indices = std::make_index_sequence<sizeof...(Ts)>{};
2149 [f, this, &context, &resolved_operands]<std::size_t... Is>(std::index_sequence<Is...>) {
2150 // This helper handles operand conversion. In particular it converts enums to their underlying type first.
2151 auto convert_operand = []<typename T>(const Operand& op) -> T {
2152 if constexpr (std::is_enum_v<T>) {
2153 return static_cast<T>(op.to<std::underlying_type_t<T>>());
2154 } else {
2155 return op.to<T>();
2156 }
2157 };
2158 (this->*f)(context, convert_operand.template operator()<std::decay_t<Ts>>(resolved_operands.at(Is))...);
2159 }(operand_indices);
2160}
2161
2170{
2171 const auto& register_info = instruction_info_db.get(opcode).register_info;
2172 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2173 BB_ASSERT_DEBUG(inputs.size() == register_info.num_inputs(), "Inputs size mismatch");
2174 this->inputs = inputs;
2175 for (size_t i = 0; i < register_info.num_inputs(); i++) {
2176 if (register_info.expected_tag(i) && register_info.expected_tag(i) != this->inputs.at(i).get_tag()) {
2177 throw RegisterValidationException(format("Input ",
2178 i,
2179 " tag ",
2180 std::to_string(this->inputs.at(i).get_tag()),
2181 " does not match expected tag ",
2182 std::to_string(*register_info.expected_tag(i))));
2183 }
2184 }
2185}
2186
2194{
2195 const auto& register_info = instruction_info_db.get(opcode).register_info;
2196 // NOTE: Only asserting in debug builds because these convertions are in the hot path.
2197 BB_ASSERT_DEBUG(register_info.num_outputs() == 1, "Outputs size mismatch");
2198 this->output = output;
2199}
2200
2201} // 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 L1_TO_L2_MSG_TREE_LEAF_COUNT
#define MAX_L2_TO_L1_MSGS_PER_TX
#define MAX_NOTE_HASHES_PER_TX
#define MAX_NULLIFIERS_PER_TX
#define MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:219
static TaggedValue from_tag(ValueTag tag, FF value)
virtual std::optional< BytecodeId > get_retrieved_bytecode_id()=0
virtual void notify_exit_call(bool success, 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_unencrypted_log(MemoryInterface &memory, ContextInterface &context, const AztecAddress &contract_address, MemoryAddress log_offset, uint32_t log_size)=0
virtual std::unique_ptr< GasTrackerInterface > make_gas_tracker(GasEvent &gas_event, const Instruction &instruction, ContextInterface &context)=0
virtual std::unique_ptr< AddressingInterface > make_addressing(AddressingEvent &event)=0
void lt(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
LT execution opcode handler: Check if the first value is less than the second.
void emit_note_hash(ContextInterface &context, MemoryAddress note_hash_addr)
EMITNOTEHASH execution opcode handler: Emit a note hash to the note hash tree.
std::vector< MemoryValue > inputs
void mov(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr)
MOV execution opcode handler: Move a memory value from one memory location to another.
void static_call(ContextInterface &context, MemoryAddress l2_gas_offset, MemoryAddress da_gas_offset, MemoryAddress addr, MemoryAddress cd_size_offset, MemoryAddress cd_offset)
STATICCALL execution opcode handler: Call a contract in a static context. Creates a new (nested) exec...
void debug_log(ContextInterface &context, MemoryAddress level_offset, MemoryAddress message_offset, MemoryAddress fields_offset, MemoryAddress fields_size_offset, uint16_t message_size)
DEBUGLOG execution opcode handler: Log a debug message. Logs a debug message to the debug logger if t...
EventEmitterInterface< ExecutionEvent > & events
void cd_copy(ContextInterface &context, MemoryAddress cd_size_offset, MemoryAddress cd_offset, MemoryAddress dst_addr)
CALLDATACOPY execution opcode handler: Copy calldata from the parent context to the current context....
std::unique_ptr< GasTrackerInterface > gas_tracker
void send_l2_to_l1_msg(ContextInterface &context, MemoryAddress recipient_addr, MemoryAddress content_addr)
SENDL2TOL1MSG execution opcode handler: Send a L2 to L1 message.
void dispatch_opcode(ExecutionOpCode opcode, ContextInterface &context, const std::vector< Operand > &resolved_operands)
Dispatch an opcode. This is the main function that dispatches the opcode to the appropriate handler.
void set_execution_result(const ExecutionResult &exec_result)
ExecutionComponentsProviderInterface & execution_components
void 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.
const std::vector< MemoryValue > & get_inputs() const
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.
virtual GasTrackerInterface & get_gas_tracker()
void poseidon2_permutation(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr)
POSEIDON2PERMUTATION execution opcode handler: Perform a Poseidon2 permutation on the input value....
void success_copy(ContextInterface &context, MemoryAddress dst_addr)
SUCCESSCOPY execution opcode handler: Copy the success flag to the destination memory location.
void fdiv(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
FDIV execution opcode handler: Divide two field values.
void jumpi(ContextInterface &context, MemoryAddress cond_addr, uint32_t loc)
JUMPI execution opcode handler: Jump to a new program counter conditionally. Next instruction will be...
void sub(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
SUB execution opcode handler: Subtract two values.
CallStackMetadataCollectorInterface & call_stack_metadata_collector
void rd_copy(ContextInterface &context, MemoryAddress rd_size_offset, MemoryAddress rd_offset, MemoryAddress dst_addr)
RETURNDATACOPY execution opcode handler: Copy return data from the current context to the parent cont...
void l1_to_l2_message_exists(ContextInterface &context, MemoryAddress msg_hash_addr, MemoryAddress leaf_index_addr, MemoryAddress dst_addr)
L1TOL2MSGEXISTS execution opcode handler: Check if a L2 to L1 message exists in the L1 to L2 message ...
void emit_unencrypted_log(ContextInterface &context, MemoryAddress log_size_offset, MemoryAddress log_offset)
EMITUNENCRYPTEDLOG execution opcode handler: Emit an unencrypted log.
void div(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
DIV execution opcode handler: Divide two values.
void emit_nullifier(ContextInterface &context, MemoryAddress nullifier_addr)
EMITNULLIFIER execution opcode handler: Emit a nullifier to the nullifier tree.
EventEmitterInterface< ContextStackEvent > & ctx_stack_events
const MemoryValue & get_output() const
void ecc_add(ContextInterface &context, MemoryAddress p_x_addr, MemoryAddress p_y_addr, MemoryAddress p_inf_addr, MemoryAddress q_x_addr, MemoryAddress q_y_addr, MemoryAddress q_inf_addr, MemoryAddress dst_addr)
ECADD execution opcode handler: Perform an elliptic curve addition and write the result to the destin...
void keccak_permutation(ContextInterface &context, MemoryAddress dst_addr, MemoryAddress src_addr)
KECCAKF1600 execution opcode handler: Perform a Keccak permutation on the data.
void jump(ContextInterface &context, uint32_t loc)
JUMP execution opcode handler: Jump to a new program counter. Next instruction will be executed at th...
const ExecutionResult & get_execution_result() const
void sha256_compression(ContextInterface &context, MemoryAddress output_addr, MemoryAddress state_addr, MemoryAddress input_addr)
SHA256COMPRESSION execution opcode handler: Perform a SHA256 compression on the input and state value...
void sload(ContextInterface &context, MemoryAddress slot_addr, MemoryAddress dst_addr)
SLOAD execution opcode handler: Load a value from the public data tree. Loads a value from the public...
void ret(ContextInterface &context, MemoryAddress ret_size_offset, MemoryAddress ret_offset)
RETURN execution opcode handler: Return from a contract. Sets the execution result to the return data...
void internal_call(ContextInterface &context, uint32_t loc)
INTERNALCALL execution opcode handler: Call a function in the current context. Pushes the current pro...
CancellationTokenPtr cancellation_token_
void op_not(ContextInterface &context, MemoryAddress src_addr, MemoryAddress dst_addr)
NOT execution opcode handler: Perform bitwise NOT operation on a value.
void shl(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
SHL execution opcode handler: Perform left shift operation on a value.
void handle_enter_call(ContextInterface &parent_context, std::unique_ptr< ContextInterface > child_context)
Handle the entering of a call. This is called when a call is made from a context. This is a helper fu...
void nullifier_exists(ContextInterface &context, MemoryAddress nullifier_offset, MemoryAddress address_offset, MemoryAddress exists_offset)
NULLIFIEREXISTS execution opcode handler: Check if a nullifier exists in the nullifier tree.
void handle_exceptional_halt(ContextInterface &context, const std::string &halting_message)
Handle the exceptional halt of a context. This is called when an exception is thrown during the execu...
void note_hash_exists(ContextInterface &context, MemoryAddress unique_note_hash_addr, MemoryAddress leaf_index_addr, MemoryAddress dst_addr)
NOTEHASHEXISTS execution opcode handler: Check if a note hash exists in the note hash tree at the spe...
ContextProviderInterface & context_provider
void to_radix_be(ContextInterface &context, MemoryAddress value_addr, MemoryAddress radix_addr, MemoryAddress num_limbs_addr, MemoryAddress is_output_bits_addr, MemoryAddress dst_addr)
TORADIXBE execution opcode handler: Convert a value to a radix-based representation....
EmitUnencryptedLogInterface & emit_unencrypted_log_component
void eq(ContextInterface &context, MemoryAddress a_addr, MemoryAddress b_addr, MemoryAddress dst_addr)
EQ execution opcode handler: Check if two values are equal.
void call(ContextInterface &context, MemoryAddress l2_gas_offset, MemoryAddress da_gas_offset, MemoryAddress addr, MemoryAddress cd_size_offset, MemoryAddress cd_offset)
CALL execution opcode handler: Call a contract. Creates a new (nested) execution context and triggers...
std::stack< std::unique_ptr< ContextInterface > > external_call_stack
void handle_exit_call()
Handle the exiting of a call. This is called when a call returns, 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:74
void call_with_operands(void(Execution::*f)(ContextInterface &, Ts...), ContextInterface &context, const std::vector< Operand > &resolved_operands)
Call with operands. This is a template magic function to dispatch the opcode by deducing the number o...
virtual void consume_gas(const Gas &dynamic_gas_factor={ 0, 0 })=0
virtual Gas compute_gas_limit_for_call(const Gas &allocated_gas)=0
virtual void get_contract_instance(MemoryInterface &memory, const AztecAddress &contract_address, MemoryAddress dst_offset, uint8_t member_enum)=0
virtual bool gt(const FF &a, const FF &b)=0
virtual bool note_hash_exists(uint64_t leaf_index, const FF &unique_note_hash) const =0
virtual FF storage_read(const AztecAddress &contract_address, const FF &slot) const =0
virtual uint32_t get_checkpoint_id() const =0
virtual bool was_storage_written(const AztecAddress &contract_address, const FF &slot) const =0
virtual void note_hash_write(const AztecAddress &contract_address, const FF &note_hash)=0
virtual bool nullifier_exists(const AztecAddress &contract_address, const FF &nullifier) const =0
virtual void storage_write(const AztecAddress &contract_address, const FF &slot, const FF &value, bool is_protocol_write)=0
virtual bool l1_to_l2_msg_exists(uint64_t leaf_index, const FF &msg_hash) const =0
virtual void nullifier_write(const AztecAddress &contract_address, const FF &nullifier)=0
virtual TreeStates get_tree_state() const =0
virtual const ExecInstructionSpec & get(ExecutionOpCode opcode) const =0
virtual 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 dst_addr
FF a
FF b
uint64_t da_gas
GasEvent gas_event
Instruction instruction
AvmProvingInputs inputs
InternalCallStackProvider make_internal_call_stack_provider(const InternalCallStackManagerInterface &internal_call_stack_manager)
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)
Definition to_radix.cpp:54
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
uint32_t MemoryAddress
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint8_t get_tag_bytes(ValueTag tag)
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
uint32_t cd_offset
std::optional< std::string > halting_message
SideEffectTracker side_effect_tracker