@aztec/simulator 0.0.1-commit.fce3e4f → 0.0.1-commit.ff7989d6c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/README.md +6 -4
  2. package/dest/common/errors.d.ts +8 -2
  3. package/dest/common/errors.d.ts.map +1 -1
  4. package/dest/private/acvm/acvm.d.ts +4 -2
  5. package/dest/private/acvm/acvm.d.ts.map +1 -1
  6. package/dest/private/acvm/acvm.js +4 -3
  7. package/dest/private/acvm/deserialize.d.ts +2 -2
  8. package/dest/private/acvm/deserialize.d.ts.map +1 -1
  9. package/dest/private/acvm/deserialize.js +1 -1
  10. package/dest/private/acvm/serialize.d.ts +2 -2
  11. package/dest/private/acvm/serialize.d.ts.map +1 -1
  12. package/dest/private/acvm/serialize.js +1 -1
  13. package/dest/private/acvm_native.d.ts +5 -3
  14. package/dest/private/acvm_native.d.ts.map +1 -1
  15. package/dest/private/acvm_native.js +8 -6
  16. package/dest/private/acvm_wasm.d.ts +4 -3
  17. package/dest/private/acvm_wasm.d.ts.map +1 -1
  18. package/dest/private/acvm_wasm.js +4 -4
  19. package/dest/private/circuit_recording/circuit_recorder.d.ts +4 -3
  20. package/dest/private/circuit_recording/circuit_recorder.d.ts.map +1 -1
  21. package/dest/private/circuit_recording/circuit_recorder.js +21 -18
  22. package/dest/private/circuit_recording/file_circuit_recorder.d.ts +3 -2
  23. package/dest/private/circuit_recording/file_circuit_recorder.d.ts.map +1 -1
  24. package/dest/private/circuit_recording/file_circuit_recorder.js +2 -2
  25. package/dest/private/circuit_recording/memory_circuit_recorder.d.ts +7 -2
  26. package/dest/private/circuit_recording/memory_circuit_recorder.d.ts.map +1 -1
  27. package/dest/private/circuit_recording/memory_circuit_recorder.js +4 -4
  28. package/dest/private/factory.d.ts +3 -3
  29. package/dest/private/factory.d.ts.map +1 -1
  30. package/dest/private/factory.js +7 -4
  31. package/dest/public/avm/avm_context.d.ts +3 -3
  32. package/dest/public/avm/avm_context.d.ts.map +1 -1
  33. package/dest/public/avm/avm_contract_call_result.d.ts +6 -6
  34. package/dest/public/avm/avm_contract_call_result.d.ts.map +1 -1
  35. package/dest/public/avm/avm_contract_call_result.js +3 -3
  36. package/dest/public/avm/avm_execution_environment.d.ts +7 -6
  37. package/dest/public/avm/avm_execution_environment.d.ts.map +1 -1
  38. package/dest/public/avm/avm_execution_environment.js +1 -1
  39. package/dest/public/avm/avm_gas.d.ts +1 -1
  40. package/dest/public/avm/avm_gas.d.ts.map +1 -1
  41. package/dest/public/avm/avm_gas.js +3 -3
  42. package/dest/public/avm/avm_machine_state.d.ts +7 -6
  43. package/dest/public/avm/avm_machine_state.d.ts.map +1 -1
  44. package/dest/public/avm/avm_machine_state.js +3 -2
  45. package/dest/public/avm/avm_memory_types.d.ts +2 -2
  46. package/dest/public/avm/avm_memory_types.d.ts.map +1 -1
  47. package/dest/public/avm/avm_memory_types.js +4 -1
  48. package/dest/public/avm/avm_simulator.d.ts +4 -3
  49. package/dest/public/avm/avm_simulator.d.ts.map +1 -1
  50. package/dest/public/avm/avm_simulator.js +11 -9
  51. package/dest/public/avm/calldata.d.ts +51 -0
  52. package/dest/public/avm/calldata.d.ts.map +1 -0
  53. package/dest/public/avm/calldata.js +63 -0
  54. package/dest/public/avm/errors.d.ts +8 -2
  55. package/dest/public/avm/errors.d.ts.map +1 -1
  56. package/dest/public/avm/errors.js +14 -2
  57. package/dest/public/avm/fixtures/account_proof_fetcher.d.ts +2 -0
  58. package/dest/public/avm/fixtures/account_proof_fetcher.d.ts.map +1 -0
  59. package/dest/public/avm/fixtures/account_proof_fetcher.js +152 -0
  60. package/dest/public/avm/fixtures/avm_simulation_tester.d.ts +1 -1
  61. package/dest/public/avm/fixtures/avm_simulation_tester.d.ts.map +1 -1
  62. package/dest/public/avm/fixtures/avm_simulation_tester.js +4 -3
  63. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts +2 -2
  64. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -1
  65. package/dest/public/avm/fixtures/base_avm_simulation_tester.js +1 -1
  66. package/dest/public/avm/fixtures/initializers.d.ts +2 -2
  67. package/dest/public/avm/fixtures/initializers.d.ts.map +1 -1
  68. package/dest/public/avm/fixtures/initializers.js +5 -4
  69. package/dest/public/avm/fixtures/utils.d.ts +4 -3
  70. package/dest/public/avm/fixtures/utils.d.ts.map +1 -1
  71. package/dest/public/avm/fixtures/utils.js +3 -2
  72. package/dest/public/avm/opcodes/accrued_substate.d.ts +17 -18
  73. package/dest/public/avm/opcodes/accrued_substate.d.ts.map +1 -1
  74. package/dest/public/avm/opcodes/accrued_substate.js +42 -43
  75. package/dest/public/avm/opcodes/addressing_mode.js +2 -2
  76. package/dest/public/avm/opcodes/arithmetic.d.ts +10 -8
  77. package/dest/public/avm/opcodes/arithmetic.d.ts.map +1 -1
  78. package/dest/public/avm/opcodes/arithmetic.js +12 -2
  79. package/dest/public/avm/opcodes/bitwise.d.ts +7 -7
  80. package/dest/public/avm/opcodes/bitwise.d.ts.map +1 -1
  81. package/dest/public/avm/opcodes/bitwise.js +5 -5
  82. package/dest/public/avm/opcodes/comparators.d.ts +4 -4
  83. package/dest/public/avm/opcodes/comparators.d.ts.map +1 -1
  84. package/dest/public/avm/opcodes/comparators.js +1 -1
  85. package/dest/public/avm/opcodes/contract.d.ts +3 -3
  86. package/dest/public/avm/opcodes/contract.d.ts.map +1 -1
  87. package/dest/public/avm/opcodes/contract.js +8 -8
  88. package/dest/public/avm/opcodes/control_flow.d.ts +3 -3
  89. package/dest/public/avm/opcodes/control_flow.d.ts.map +1 -1
  90. package/dest/public/avm/opcodes/control_flow.js +4 -4
  91. package/dest/public/avm/opcodes/conversion.d.ts +3 -3
  92. package/dest/public/avm/opcodes/conversion.d.ts.map +1 -1
  93. package/dest/public/avm/opcodes/conversion.js +4 -4
  94. package/dest/public/avm/opcodes/ec_add.d.ts +4 -4
  95. package/dest/public/avm/opcodes/ec_add.d.ts.map +1 -1
  96. package/dest/public/avm/opcodes/ec_add.js +17 -8
  97. package/dest/public/avm/opcodes/environment_getters.d.ts +5 -5
  98. package/dest/public/avm/opcodes/environment_getters.d.ts.map +1 -1
  99. package/dest/public/avm/opcodes/environment_getters.js +6 -6
  100. package/dest/public/avm/opcodes/external_calls.d.ts +9 -9
  101. package/dest/public/avm/opcodes/external_calls.d.ts.map +1 -1
  102. package/dest/public/avm/opcodes/external_calls.js +23 -22
  103. package/dest/public/avm/opcodes/hashing.d.ts +8 -8
  104. package/dest/public/avm/opcodes/hashing.d.ts.map +1 -1
  105. package/dest/public/avm/opcodes/hashing.js +21 -16
  106. package/dest/public/avm/opcodes/instruction_impl.d.ts +4 -4
  107. package/dest/public/avm/opcodes/instruction_impl.d.ts.map +1 -1
  108. package/dest/public/avm/opcodes/instruction_impl.js +4 -4
  109. package/dest/public/avm/opcodes/memory.d.ts +14 -14
  110. package/dest/public/avm/opcodes/memory.d.ts.map +1 -1
  111. package/dest/public/avm/opcodes/memory.js +26 -26
  112. package/dest/public/avm/opcodes/misc.d.ts +3 -3
  113. package/dest/public/avm/opcodes/misc.d.ts.map +1 -1
  114. package/dest/public/avm/opcodes/misc.js +6 -6
  115. package/dest/public/avm/opcodes/storage.d.ts +16 -15
  116. package/dest/public/avm/opcodes/storage.d.ts.map +1 -1
  117. package/dest/public/avm/opcodes/storage.js +34 -24
  118. package/dest/public/avm/revert_reason.d.ts +2 -2
  119. package/dest/public/avm/revert_reason.d.ts.map +1 -1
  120. package/dest/public/avm/revert_reason.js +3 -2
  121. package/dest/public/avm/serialization/bytecode_serialization.d.ts +1 -1
  122. package/dest/public/avm/serialization/bytecode_serialization.d.ts.map +1 -1
  123. package/dest/public/avm/serialization/bytecode_serialization.js +12 -9
  124. package/dest/public/avm/serialization/instruction_serialization.d.ts +2 -2
  125. package/dest/public/avm/serialization/instruction_serialization.d.ts.map +1 -1
  126. package/dest/public/avm/serialization/instruction_serialization.js +2 -2
  127. package/dest/public/avm/test_utils.d.ts +2 -2
  128. package/dest/public/avm/test_utils.d.ts.map +1 -1
  129. package/dest/public/avm/test_utils.js +1 -1
  130. package/dest/public/contracts_db_checkpoint.d.ts +2 -2
  131. package/dest/public/contracts_db_checkpoint.d.ts.map +1 -1
  132. package/dest/public/db_interfaces.d.ts +2 -2
  133. package/dest/public/db_interfaces.d.ts.map +1 -1
  134. package/dest/public/debug_fn_name.d.ts +16 -3
  135. package/dest/public/debug_fn_name.d.ts.map +1 -1
  136. package/dest/public/debug_fn_name.js +31 -3
  137. package/dest/public/executor_metrics.d.ts +1 -1
  138. package/dest/public/executor_metrics.d.ts.map +1 -1
  139. package/dest/public/executor_metrics.js +12 -33
  140. package/dest/public/fixtures/amm_test.js +4 -4
  141. package/dest/public/fixtures/bulk_test.d.ts +3 -3
  142. package/dest/public/fixtures/bulk_test.d.ts.map +1 -1
  143. package/dest/public/fixtures/bulk_test.js +5 -69
  144. package/dest/public/fixtures/custom_bytecode_tester.d.ts +28 -6
  145. package/dest/public/fixtures/custom_bytecode_tester.d.ts.map +1 -1
  146. package/dest/public/fixtures/custom_bytecode_tester.js +36 -12
  147. package/dest/public/fixtures/custom_bytecode_tests.d.ts +11 -8
  148. package/dest/public/fixtures/custom_bytecode_tests.d.ts.map +1 -1
  149. package/dest/public/fixtures/custom_bytecode_tests.js +83 -18
  150. package/dest/public/fixtures/index.d.ts +4 -2
  151. package/dest/public/fixtures/index.d.ts.map +1 -1
  152. package/dest/public/fixtures/index.js +3 -1
  153. package/dest/public/fixtures/minimal_public_tx.d.ts +2 -7
  154. package/dest/public/fixtures/minimal_public_tx.d.ts.map +1 -1
  155. package/dest/public/fixtures/minimal_public_tx.js +4 -14
  156. package/dest/public/fixtures/opcode_spammer.d.ts +122 -0
  157. package/dest/public/fixtures/opcode_spammer.d.ts.map +1 -0
  158. package/dest/public/fixtures/opcode_spammer.js +1653 -0
  159. package/dest/public/fixtures/public_tx_simulation_tester.d.ts +22 -3
  160. package/dest/public/fixtures/public_tx_simulation_tester.d.ts.map +1 -1
  161. package/dest/public/fixtures/public_tx_simulation_tester.js +50 -13
  162. package/dest/public/fixtures/simple_contract_data_source.d.ts +5 -4
  163. package/dest/public/fixtures/simple_contract_data_source.d.ts.map +1 -1
  164. package/dest/public/fixtures/simple_contract_data_source.js +4 -4
  165. package/dest/public/fixtures/token_test.d.ts +6 -2
  166. package/dest/public/fixtures/token_test.d.ts.map +1 -1
  167. package/dest/public/fixtures/token_test.js +13 -11
  168. package/dest/public/fixtures/utils.d.ts +2 -2
  169. package/dest/public/fixtures/utils.d.ts.map +1 -1
  170. package/dest/public/fixtures/utils.js +8 -7
  171. package/dest/public/fuzzing/avm_fuzzer_simulator.d.ts +60 -0
  172. package/dest/public/fuzzing/avm_fuzzer_simulator.d.ts.map +1 -0
  173. package/dest/public/fuzzing/avm_fuzzer_simulator.js +171 -0
  174. package/dest/public/fuzzing/avm_simulator_bin.d.ts +2 -0
  175. package/dest/public/fuzzing/avm_simulator_bin.d.ts.map +1 -0
  176. package/dest/public/fuzzing/avm_simulator_bin.js +118 -0
  177. package/dest/public/hinting_db_sources.d.ts +5 -3
  178. package/dest/public/hinting_db_sources.d.ts.map +1 -1
  179. package/dest/public/hinting_db_sources.js +7 -2
  180. package/dest/public/index.d.ts +2 -2
  181. package/dest/public/index.d.ts.map +1 -1
  182. package/dest/public/index.js +1 -1
  183. package/dest/public/public_db_sources.d.ts +5 -4
  184. package/dest/public/public_db_sources.d.ts.map +1 -1
  185. package/dest/public/public_db_sources.js +5 -5
  186. package/dest/public/public_processor/guarded_merkle_tree.d.ts +4 -2
  187. package/dest/public/public_processor/guarded_merkle_tree.d.ts.map +1 -1
  188. package/dest/public/public_processor/guarded_merkle_tree.js +5 -0
  189. package/dest/public/public_processor/public_processor.d.ts +10 -7
  190. package/dest/public/public_processor/public_processor.d.ts.map +1 -1
  191. package/dest/public/public_processor/public_processor.js +454 -51
  192. package/dest/public/public_processor/public_processor_metrics.d.ts +2 -2
  193. package/dest/public/public_processor/public_processor_metrics.d.ts.map +1 -1
  194. package/dest/public/public_processor/public_processor_metrics.js +28 -45
  195. package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts +3 -14
  196. package/dest/public/public_tx_simulator/contract_provider_for_cpp.d.ts.map +1 -1
  197. package/dest/public/public_tx_simulator/contract_provider_for_cpp.js +20 -55
  198. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.d.ts +24 -37
  199. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.d.ts.map +1 -1
  200. package/dest/public/public_tx_simulator/cpp_public_tx_simulator.js +67 -145
  201. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.d.ts +42 -0
  202. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.d.ts.map +1 -0
  203. package/dest/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.js +86 -0
  204. package/dest/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.d.ts +30 -0
  205. package/dest/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.d.ts.map +1 -0
  206. package/dest/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.js +171 -0
  207. package/dest/public/public_tx_simulator/dumping_cpp_public_tx_simulator.d.ts +23 -0
  208. package/dest/public/public_tx_simulator/dumping_cpp_public_tx_simulator.d.ts.map +1 -0
  209. package/dest/public/public_tx_simulator/dumping_cpp_public_tx_simulator.js +52 -0
  210. package/dest/public/public_tx_simulator/factories.d.ts +14 -0
  211. package/dest/public/public_tx_simulator/factories.d.ts.map +1 -0
  212. package/dest/public/public_tx_simulator/factories.js +28 -0
  213. package/dest/public/public_tx_simulator/index.d.ts +4 -1
  214. package/dest/public/public_tx_simulator/index.d.ts.map +1 -1
  215. package/dest/public/public_tx_simulator/index.js +3 -0
  216. package/dest/public/public_tx_simulator/measured_public_tx_simulator.d.ts +3 -3
  217. package/dest/public/public_tx_simulator/measured_public_tx_simulator.d.ts.map +1 -1
  218. package/dest/public/public_tx_simulator/public_tx_context.d.ts +5 -4
  219. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  220. package/dest/public/public_tx_simulator/public_tx_context.js +9 -9
  221. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts +8 -6
  222. package/dest/public/public_tx_simulator/public_tx_simulator.d.ts.map +1 -1
  223. package/dest/public/public_tx_simulator/public_tx_simulator.js +23 -12
  224. package/dest/public/public_tx_simulator/public_tx_simulator_interface.d.ts +24 -1
  225. package/dest/public/public_tx_simulator/public_tx_simulator_interface.d.ts.map +1 -1
  226. package/dest/public/public_tx_simulator/telemetry_public_tx_simulator.d.ts +2 -2
  227. package/dest/public/public_tx_simulator/telemetry_public_tx_simulator.d.ts.map +1 -1
  228. package/dest/public/public_tx_simulator/telemetry_public_tx_simulator.js +395 -19
  229. package/dest/public/side_effect_trace.d.ts +6 -5
  230. package/dest/public/side_effect_trace.d.ts.map +1 -1
  231. package/dest/public/side_effect_trace.js +4 -4
  232. package/dest/public/side_effect_trace_interface.d.ts +2 -2
  233. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  234. package/dest/public/state_manager/nullifiers.d.ts +2 -2
  235. package/dest/public/state_manager/nullifiers.d.ts.map +1 -1
  236. package/dest/public/state_manager/public_storage.d.ts +2 -2
  237. package/dest/public/state_manager/public_storage.d.ts.map +1 -1
  238. package/dest/public/state_manager/public_storage.js +1 -1
  239. package/dest/public/state_manager/state_manager.d.ts +16 -5
  240. package/dest/public/state_manager/state_manager.d.ts.map +1 -1
  241. package/dest/public/state_manager/state_manager.js +19 -9
  242. package/dest/public/test_executor_metrics.d.ts +3 -2
  243. package/dest/public/test_executor_metrics.d.ts.map +1 -1
  244. package/dest/public/test_executor_metrics.js +2 -2
  245. package/package.json +20 -20
  246. package/src/common/errors.ts +1 -1
  247. package/src/private/acvm/acvm.ts +4 -3
  248. package/src/private/acvm/deserialize.ts +1 -1
  249. package/src/private/acvm/serialize.ts +1 -1
  250. package/src/private/acvm_native.ts +11 -5
  251. package/src/private/acvm_wasm.ts +7 -3
  252. package/src/private/circuit_recording/circuit_recorder.ts +22 -19
  253. package/src/private/circuit_recording/file_circuit_recorder.ts +7 -2
  254. package/src/private/circuit_recording/memory_circuit_recorder.ts +6 -4
  255. package/src/private/factory.ts +7 -4
  256. package/src/public/avm/avm_context.ts +2 -2
  257. package/src/public/avm/avm_contract_call_result.ts +8 -6
  258. package/src/public/avm/avm_execution_environment.ts +10 -5
  259. package/src/public/avm/avm_gas.ts +5 -5
  260. package/src/public/avm/avm_machine_state.ts +7 -6
  261. package/src/public/avm/avm_memory_types.ts +5 -1
  262. package/src/public/avm/avm_simulator.ts +16 -10
  263. package/src/public/avm/calldata.ts +100 -0
  264. package/src/public/avm/errors.ts +17 -3
  265. package/src/public/avm/fixtures/account_proof.json +553 -0
  266. package/src/public/avm/fixtures/account_proof_fetcher.ts +166 -0
  267. package/src/public/avm/fixtures/avm_simulation_tester.ts +9 -3
  268. package/src/public/avm/fixtures/base_avm_simulation_tester.ts +1 -1
  269. package/src/public/avm/fixtures/initializers.ts +5 -4
  270. package/src/public/avm/fixtures/utils.ts +3 -2
  271. package/src/public/avm/opcodes/accrued_substate.ts +31 -34
  272. package/src/public/avm/opcodes/addressing_mode.ts +2 -2
  273. package/src/public/avm/opcodes/arithmetic.ts +14 -2
  274. package/src/public/avm/opcodes/bitwise.ts +3 -3
  275. package/src/public/avm/opcodes/comparators.ts +1 -1
  276. package/src/public/avm/opcodes/contract.ts +4 -7
  277. package/src/public/avm/opcodes/control_flow.ts +2 -2
  278. package/src/public/avm/opcodes/conversion.ts +3 -3
  279. package/src/public/avm/opcodes/ec_add.ts +15 -6
  280. package/src/public/avm/opcodes/environment_getters.ts +7 -7
  281. package/src/public/avm/opcodes/external_calls.ts +17 -15
  282. package/src/public/avm/opcodes/hashing.ts +16 -10
  283. package/src/public/avm/opcodes/instruction_impl.ts +2 -2
  284. package/src/public/avm/opcodes/memory.ts +20 -20
  285. package/src/public/avm/opcodes/misc.ts +4 -4
  286. package/src/public/avm/opcodes/storage.ts +30 -22
  287. package/src/public/avm/revert_reason.ts +6 -3
  288. package/src/public/avm/serialization/bytecode_serialization.ts +19 -8
  289. package/src/public/avm/serialization/instruction_serialization.ts +2 -2
  290. package/src/public/avm/test_utils.ts +1 -1
  291. package/src/public/contracts_db_checkpoint.ts +1 -1
  292. package/src/public/db_interfaces.ts +1 -1
  293. package/src/public/debug_fn_name.ts +39 -5
  294. package/src/public/executor_metrics.ts +9 -33
  295. package/src/public/fixtures/amm_test.ts +4 -4
  296. package/src/public/fixtures/bulk_test.ts +9 -9
  297. package/src/public/fixtures/custom_bytecode_tester.ts +53 -19
  298. package/src/public/fixtures/custom_bytecode_tests.ts +111 -18
  299. package/src/public/fixtures/index.ts +7 -1
  300. package/src/public/fixtures/minimal_public_tx.ts +6 -15
  301. package/src/public/fixtures/opcode_spammer.ts +1717 -0
  302. package/src/public/fixtures/public_tx_simulation_tester.ts +69 -13
  303. package/src/public/fixtures/simple_contract_data_source.ts +8 -9
  304. package/src/public/fixtures/token_test.ts +18 -9
  305. package/src/public/fixtures/utils.ts +6 -7
  306. package/src/public/fuzzing/avm_fuzzer_simulator.ts +288 -0
  307. package/src/public/fuzzing/avm_simulator_bin.ts +175 -0
  308. package/src/public/hinting_db_sources.ts +8 -3
  309. package/src/public/index.ts +7 -1
  310. package/src/public/public_db_sources.ts +16 -6
  311. package/src/public/public_processor/guarded_merkle_tree.ts +6 -1
  312. package/src/public/public_processor/public_processor.ts +81 -45
  313. package/src/public/public_processor/public_processor_metrics.ts +16 -44
  314. package/src/public/public_tx_simulator/contract_provider_for_cpp.ts +25 -63
  315. package/src/public/public_tx_simulator/cpp_public_tx_simulator.ts +83 -178
  316. package/src/public/public_tx_simulator/cpp_public_tx_simulator_with_hinted_dbs.ts +134 -0
  317. package/src/public/public_tx_simulator/cpp_vs_ts_public_tx_simulator.ts +239 -0
  318. package/src/public/public_tx_simulator/dumping_cpp_public_tx_simulator.ts +83 -0
  319. package/src/public/public_tx_simulator/factories.ts +43 -0
  320. package/src/public/public_tx_simulator/index.ts +3 -0
  321. package/src/public/public_tx_simulator/measured_public_tx_simulator.ts +1 -1
  322. package/src/public/public_tx_simulator/public_tx_context.ts +14 -7
  323. package/src/public/public_tx_simulator/public_tx_simulator.ts +42 -16
  324. package/src/public/public_tx_simulator/public_tx_simulator_interface.ts +23 -0
  325. package/src/public/public_tx_simulator/telemetry_public_tx_simulator.ts +1 -1
  326. package/src/public/side_effect_trace.ts +6 -3
  327. package/src/public/side_effect_trace_interface.ts +1 -1
  328. package/src/public/state_manager/nullifiers.ts +1 -1
  329. package/src/public/state_manager/public_storage.ts +1 -1
  330. package/src/public/state_manager/state_manager.ts +39 -23
  331. package/src/public/test_executor_metrics.ts +3 -3
@@ -0,0 +1,1717 @@
1
+ /**
2
+ * Opcode Spammer - A minimal, data-driven opcode spammer for AVM gas benchmarking.
3
+ *
4
+ * Design principles:
5
+ * 1. Data over code: Opcode behavior is configuration, not control flow
6
+ * 2. Derive, don't declare: Categories and strategies follow from the data
7
+ * 3. Maximize coverage: Fill bytecode to the limit for accurate gas measurement
8
+ * 4. Smallest wire format: Use _8 variants over _16 to fit more instructions per loop
9
+ * 5. Single file: Everything in one module
10
+ *
11
+ * ## Architecture
12
+ *
13
+ * ```
14
+ * ┌─────────────────────────────────────────────────────────────────┐
15
+ * │ SPAM_CONFIGS │
16
+ * │ Record<Opcode, SpamConfig[]> │
17
+ * │ │
18
+ * │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
19
+ * │ │ ADD_8 │ │ POSEIDON2 │ │EMITNULLIFIER│ ... │
20
+ * │ │ [7 configs] │ │ [1 config] │ │ [1 config] │ │
21
+ * │ │ (per type) │ │ │ │ (limit=63) │ │
22
+ * │ └─────────────┘ └─────────────┘ └─────────────┘ │
23
+ * └─────────────────────────────────────────────────────────────────┘
24
+ * │
25
+ * ▼
26
+ * ┌─────────────────────────────────────────────────────────────────┐
27
+ * │ getSpamConfigsPerOpcode() │
28
+ * │ Returns { opcodes, config[] } for test iteration │
29
+ * └─────────────────────────────────────────────────────────────────┘
30
+ * │
31
+ * ▼
32
+ * ┌─────────────────────────────────────────────────────────────────┐
33
+ * │ testOpcodeSpamCase() │
34
+ * │ Routes to appropriate bytecode generator & executes test │
35
+ * │ │
36
+ * │ config.limit === undefined? │
37
+ * │ YES → testStandardOpcodeSpam() │
38
+ * │ NO → testSideEffectOpcodeSpam() │
39
+ * └─────────────────────────────────────────────────────────────────┘
40
+ * ```
41
+ *
42
+ * ## Two Execution Strategies
43
+ *
44
+ * ### Strategy 1: Standard Opcodes (Gas-Limited)
45
+ *
46
+ * For opcodes without per-TX limits (arithmetic, comparisons, memory ops, etc.), we create a single contract with an infinite loop:
47
+ *
48
+ * ```
49
+ * ┌────────────────────────────────────────────────────────────────┐
50
+ * │ SINGLE CONTRACT │
51
+ * │ │
52
+ * │ ┌──────────────────────────────────────────────────────────┐ │
53
+ * │ │ SETUP PHASE │ │
54
+ * │ │ SET mem[0] = initial_value │ │
55
+ * │ │ SET mem[1] = operand │ │
56
+ * │ │ ... │ │
57
+ * │ └──────────────────────────────────────────────────────────┘ │
58
+ * │ │ │
59
+ * │ ▼ │
60
+ * │ ┌──────────────────────────────────────────────────────────┐ │
61
+ * │ │ LOOP (fills remaining bytecode space) ◄─────┐ │ │
62
+ * │ │ TARGET_OPCODE ─┐ │ │ │
63
+ * │ │ TARGET_OPCODE │ unrolled N times │ │ │
64
+ * │ │ TARGET_OPCODE │ (N = available_bytes / instr_size)│ │ │
65
+ * │ │ ... ─┘ │ │ │
66
+ * │ │ JUMP back ──────────────────────────────────────────┘ │ │
67
+ * │ └──────────────────────────────────────────────────────────┘ │
68
+ * │ │
69
+ * │ Executes until: OUT OF GAS │
70
+ * └────────────────────────────────────────────────────────────────┘
71
+ * ```
72
+ *
73
+ * **Bytecode Layout:**
74
+ * ```
75
+ * ┌─────────────────────────────────────────────────────────────────┐
76
+ * │ 0x00: SET instructions (setup) │
77
+ * │ ... │
78
+ * │ 0xNN: ┌─── LOOP START ◄──────────────────────────────────────┐ │
79
+ * │ │ TARGET_OPCODE │ │
80
+ * │ │ TARGET_OPCODE (unrolled to fill max bytecode size) │ │
81
+ * │ │ TARGET_OPCODE │ │
82
+ * │ │ ... │ │
83
+ * │ └─► JUMP 0xNN ─────────────────────────────────────────┘ │
84
+ * │ MAX_BYTECODE_BYTES │
85
+ * └─────────────────────────────────────────────────────────────────┘
86
+ * ```
87
+ *
88
+ * ### Strategy 2: Side-Effect Limited Opcodes (Nested Call Pattern)
89
+ *
90
+ * For opcodes with per-TX limits (EMITNOTEHASH, EMITNULLIFIER, SENDL2TOL1MSG, etc.), we use a two-contract pattern where the inner contract executes side effects up to the limit, then REVERTs to discard them:
91
+ *
92
+ * ```
93
+ * ┌─────────────────────────────────────────────────────────────────┐
94
+ * │ OUTER CONTRACT │
95
+ * │ │
96
+ * │ ┌───────────────────────────────────────────────────────────┐ │
97
+ * │ │ SETUP │ │
98
+ * │ │ CALLDATACOPY inner_address from calldata[0] │ │
99
+ * │ │ SET l2Gas = MAX_UINT32 │ │
100
+ * │ │ SET daGas = MAX_UINT32 │ │
101
+ * │ └───────────────────────────────────────────────────────────┘ │
102
+ * │ │ │
103
+ * │ ▼ │
104
+ * │ ┌───────────────────────────────────────────────────────────┐ │
105
+ * │ │ LOOP ◄────┐ │ │
106
+ * │ │ CALL inner_contract ──────────────────────┐ │ │ │
107
+ * │ │ JUMP back ─────────────────────────────────────────────┘ │ │
108
+ * │ └───────────────────────────────────────────────────────────┘ │
109
+ * │ │ │
110
+ * │ Executes until: OUT OF GAS │ │
111
+ * └───────────────────────────────────────────────│─────────────────┘
112
+ * │
113
+ * ▼
114
+ * ┌─────────────────────────────────────────────────────────────────┐
115
+ * │ INNER CONTRACT │
116
+ * │ │
117
+ * │ ┌───────────────────────────────────────────────────────────┐ │
118
+ * │ │ SETUP │ │
119
+ * │ │ SET initial values for side-effect opcode │ │
120
+ * │ └───────────────────────────────────────────────────────────┘ │
121
+ * │ │ │
122
+ * │ ▼ │
123
+ * │ ┌───────────────────────────────────────────────────────────┐ │
124
+ * │ │ BODY (unrolled, NOT a loop) │ │
125
+ * │ │ SIDE_EFFECT_OPCODE ─┐ │ │
126
+ * │ │ SIDE_EFFECT_OPCODE │ repeated `limit` times │ │
127
+ * │ │ SIDE_EFFECT_OPCODE │ (e.g., 64 for EMITNOTEHASH) │ │
128
+ * │ │ ... ─┘ │ │
129
+ * │ └───────────────────────────────────────────────────────────┘ │
130
+ * │ │ │
131
+ * │ ▼ │
132
+ * │ ┌───────────────────────────────────────────────────────────┐ │
133
+ * │ │ CLEANUP │ │
134
+ * │ │ REVERT (discards all side effects from this call) │ │
135
+ * │ └───────────────────────────────────────────────────────────┘ │
136
+ * │ │
137
+ * └─────────────────────────────────────────────────────────────────┘
138
+ * ```
139
+ *
140
+ * **Why this pattern?**
141
+ *
142
+ * Side-effect opcodes have per-TX limits:
143
+ * - `EMITNOTEHASH`: max 64 per TX
144
+ * - `EMITNULLIFIER`: max 63 per TX (one reserved for TX nullifier)
145
+ * - `SENDL2TOL1MSG`: max 8 per TX
146
+ * - `EMITPUBLICLOG`: limited by total log payload size
147
+ *
148
+ * By having the inner contract REVERT after emitting side effects, those effects are discarded, allowing the outer contract to call it again. This enables thousands of opcode executions per TX instead of just the limit.
149
+ *
150
+ */
151
+ import {
152
+ FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH,
153
+ MAX_L2_TO_L1_MSGS_PER_TX,
154
+ MAX_NOTE_HASHES_PER_TX,
155
+ MAX_NULLIFIERS_PER_TX,
156
+ MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
157
+ MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
158
+ MAX_PUBLIC_LOG_SIZE_IN_FIELDS,
159
+ PUBLIC_LOG_HEADER_LENGTH,
160
+ } from '@aztec/constants';
161
+ import { Grumpkin } from '@aztec/foundation/crypto/grumpkin';
162
+ import { randomBigInt } from '@aztec/foundation/crypto/random';
163
+ import { Fr } from '@aztec/foundation/curves/bn254';
164
+ import type { Bufferable } from '@aztec/foundation/serialize';
165
+ import { type CallStackMetadata, PublicDataWrite, type PublicTxResult } from '@aztec/stdlib/avm';
166
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
167
+ import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
168
+ import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
169
+ import { MerkleTreeId } from '@aztec/stdlib/trees';
170
+
171
+ import assert from 'assert';
172
+
173
+ import { Field, type MemoryValue, TaggedMemory, TypeTag, Uint1, Uint32, Uint64 } from '../avm/avm_memory_types.js';
174
+ import {
175
+ Add,
176
+ And,
177
+ Call,
178
+ CalldataCopy,
179
+ Cast,
180
+ DebugLog,
181
+ Div,
182
+ EcAdd,
183
+ EmitNoteHash,
184
+ EmitNullifier,
185
+ EmitPublicLog,
186
+ Eq,
187
+ FieldDiv,
188
+ GetContractInstance,
189
+ GetEnvVar,
190
+ InternalCall,
191
+ InternalReturn,
192
+ Jump,
193
+ JumpI,
194
+ KeccakF1600,
195
+ L1ToL2MessageExists,
196
+ Lt,
197
+ Lte,
198
+ Mov,
199
+ Mul,
200
+ Not,
201
+ NoteHashExists,
202
+ NullifierExists,
203
+ Or,
204
+ Poseidon2,
205
+ Return,
206
+ ReturndataCopy,
207
+ ReturndataSize,
208
+ Revert,
209
+ SLoad,
210
+ SStore,
211
+ SendL2ToL1Message,
212
+ Set,
213
+ Sha256Compression,
214
+ Shl,
215
+ Shr,
216
+ StaticCall,
217
+ Sub,
218
+ SuccessCopy,
219
+ ToRadixBE,
220
+ Xor,
221
+ } from '../avm/opcodes/index.js';
222
+ import { encodeToBytecode } from '../avm/serialization/bytecode_serialization.js';
223
+ import { Opcode } from '../avm/serialization/instruction_serialization.js';
224
+ import { deployCustomBytecode, executeCustomBytecode } from './custom_bytecode_tester.js';
225
+ import type { PublicTxSimulationTester } from './public_tx_simulation_tester.js';
226
+
227
+ // ============================================================================
228
+ // Types
229
+ // ============================================================================
230
+
231
+ /**
232
+ * Memory cell to initialize before spamming.
233
+ */
234
+ interface MemSetup {
235
+ offset: number;
236
+ value: MemoryValue;
237
+ }
238
+
239
+ /**
240
+ * Some setup action to take before spamming.
241
+ * Either a memory cell to initialize, or some instruction generator.
242
+ */
243
+ type SetupItem = MemSetup | (() => Bufferable[]);
244
+
245
+ /**
246
+ * Everything needed to spam an opcode.
247
+ */
248
+ export interface SpamConfig {
249
+ /** Memory cells to initialize */
250
+ setup: SetupItem[];
251
+
252
+ /** Factory to create target instruction(s) to spam */
253
+ targetInstructions: () => Bufferable[];
254
+
255
+ /** Instructions to run after target spam (e.g., REVERT) */
256
+ cleanupInstructions?: () => Bufferable[];
257
+
258
+ /**
259
+ * Per-TX limit for the target opcode (for side-effect-limited opcodes)
260
+ * If set:
261
+ * 1. makes nested CALL
262
+ * 2. executes target opcode #limit times in a nested call
263
+ * 3. REVERT
264
+ * 4. CALL again to repeat
265
+ */
266
+ limit?: number;
267
+
268
+ /** Optional label for this config variant (e.g., UINT8 or MAXSIZE) */
269
+ label?: string;
270
+
271
+ /** Whether to pass the contract address as calldata[0] */
272
+ addressAsCalldata?: boolean;
273
+ }
274
+
275
+ /**
276
+ * An object containing opcode name and its SpamConfigs
277
+ * Useful when ready to iterate over all opcodes and test them.
278
+ */
279
+ export interface SpamConfigsForOpcode {
280
+ /** Opcode name (e.g., "ADD_8") */
281
+ opcode: string;
282
+
283
+ /** All spam configs for this opcode (one or more) */
284
+ configs: SpamConfig[];
285
+ }
286
+
287
+ // ============================================================================
288
+ // Constants
289
+ // ============================================================================
290
+
291
+ /**
292
+ * Constants for "warm" tree reads - these values are inserted into the trees
293
+ * before running the spammer so that existence checks can find them.
294
+ */
295
+ export const WARM_NOTE_HASH = new Fr(0xdeadbeefn);
296
+ export const WARM_L1_TO_L2_MSG = new Fr(0xcafebabedeadbeefn);
297
+
298
+ /** Warm nullifier constant - a pre-siloed nullifier value inserted directly into the tree */
299
+ export const WARM_SILOED_NULLIFIER = new Fr(0xdeadbeef0001n);
300
+
301
+ /** Warm storage constants - storage is inserted for the deployed contract's address */
302
+ export const WARM_STORAGE_SLOT = new Fr(0xdeadbeef0002n);
303
+ export const WARM_STORAGE_VALUE = new Fr(0xcafebabe0003n);
304
+
305
+ /**
306
+ * Leaf indices inserted to by insertWarmTreeEntries().
307
+ * Ideally we'd getTreeInfo and set dynamically, but that doesn't
308
+ * work easily with static spam configs, so we assume intial index 0.
309
+ */
310
+ export const WARM_NOTE_HASH_LEAF_INDEX = 0n;
311
+ export const WARM_L1_TO_L2_MSG_LEAF_INDEX = 0n;
312
+
313
+ /**
314
+ * Insert entries into the trees so that "warm" configs can find them with existence checks.
315
+ * Call this before running the opcode spammer to enable warm tree reads.
316
+ *
317
+ * Inserts:
318
+ * - Note hash into NOTE_HASH_TREE
319
+ * - L1 to L2 message into L1_TO_L2_MESSAGE_TREE
320
+ * - Siloed nullifier into NULLIFIER_TREE (for NULLIFIEREXISTS warm check)
321
+ * - Storage value into PUBLIC_DATA_TREE (for SLOAD warm check)
322
+ */
323
+ export async function insertWarmTreeEntries(
324
+ merkleTrees: MerkleTreeWriteOperations,
325
+ contractAddress: AztecAddress,
326
+ ): Promise<void> {
327
+ // Insert into note hash tree
328
+ await merkleTrees.appendLeaves(MerkleTreeId.NOTE_HASH_TREE, [WARM_NOTE_HASH]);
329
+
330
+ // Insert into L1 to L2 message tree
331
+ await merkleTrees.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [WARM_L1_TO_L2_MSG]);
332
+
333
+ // Insert siloed nullifier into nullifier tree (already siloed - used directly by NULLIFIEREXISTS)
334
+ await merkleTrees.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [WARM_SILOED_NULLIFIER.toBuffer()]);
335
+
336
+ // Insert storage value into public data tree
337
+ const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, WARM_STORAGE_SLOT);
338
+ const publicDataWrite = new PublicDataWrite(leafSlot, WARM_STORAGE_VALUE);
339
+ await merkleTrees.sequentialInsert(MerkleTreeId.PUBLIC_DATA_TREE, [publicDataWrite.toBuffer()]);
340
+ }
341
+
342
+ /**
343
+ * Maximum bytecode size in bytes.
344
+ *
345
+ * Bytecode is encoded as fields using bufferAsFields():
346
+ * - 1 field for the byte length
347
+ * - ceil(byteLength / 31) fields for the data (31 bytes per field)
348
+ *
349
+ * So: 1 + ceil(byteLength / 31) <= MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS
350
+ * ceil(byteLength / 31) <= MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1
351
+ * byteLength <= (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * 31
352
+ */
353
+ const BYTES_PER_FIELD = Fr.SIZE_IN_BYTES - 1; // 31 bytes of data per field
354
+ const MAX_BYTECODE_BYTES = (MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS - 1) * BYTES_PER_FIELD;
355
+
356
+ const JUMP_SIZE = encodeToBytecode([new Jump(0)]).length; // JUMP_32
357
+ const INTERNALCALL_SIZE = encodeToBytecode([new InternalCall(0)]).length;
358
+
359
+ // ============================================================================
360
+ // Type Variant Helpers (for generating multiple configs per opcode)
361
+ // ============================================================================
362
+
363
+ // Not using these sets directly because we want to control order
364
+ //const ALL_TAGS = Array.from(VALID_TAGS);
365
+ //const INT_TAGS = Array.from(INTEGRAL_TAGS);
366
+ // Ordered so that limiting #configs per opcode still tests max size (Field)
367
+ const ALL_TAGS = [
368
+ TypeTag.FIELD,
369
+ TypeTag.UINT1,
370
+ TypeTag.UINT8,
371
+ TypeTag.UINT16,
372
+ TypeTag.UINT32,
373
+ TypeTag.UINT64,
374
+ TypeTag.UINT128,
375
+ ];
376
+
377
+ // ordered so that limiting #configs per opcode still tests max size
378
+ const INT_TAGS = [TypeTag.UINT128, TypeTag.UINT1, TypeTag.UINT8, TypeTag.UINT16, TypeTag.UINT32, TypeTag.UINT64];
379
+
380
+ /** Build from tag truncating - shorter name */
381
+ function withTag(v: bigint, tag: TypeTag): MemoryValue {
382
+ return TaggedMemory.buildFromTagTruncating(v, tag);
383
+ }
384
+
385
+ // ============================================================================
386
+ // Random Value Helpers (seeded via SEED env var for reproducibility)
387
+ // ============================================================================
388
+
389
+ /** Modulus (really just max+1) for each integer type tag */
390
+ const TAG_MODULI: Partial<Record<TypeTag, bigint>> = {
391
+ [TypeTag.UINT1]: 2n,
392
+ [TypeTag.UINT8]: 256n,
393
+ [TypeTag.UINT16]: 65536n,
394
+ [TypeTag.UINT32]: 0x1_0000_0000n,
395
+ [TypeTag.UINT64]: 0x1_0000_0000_0000_0000n,
396
+ [TypeTag.UINT128]: 0x1_0000_0000_0000_0000_0000_0000_0000_0000n,
397
+ [TypeTag.FIELD]: Fr.MODULUS,
398
+ };
399
+
400
+ /** Generate a random value with the given type tag. Uses SEED env var if set. */
401
+ function randomWithTag(tag: TypeTag): MemoryValue {
402
+ const modulus = TAG_MODULI[tag];
403
+ if (modulus === undefined) {
404
+ throw new Error(`Unsupported tag for random generation: ${TypeTag[tag]}`);
405
+ }
406
+ const value = randomBigInt(modulus);
407
+ return TaggedMemory.buildFromTagTruncating(value, tag);
408
+ }
409
+
410
+ /** Generate a random non-zero value with the given type tag (for division). */
411
+ function randomNonZeroWithTag(tag: TypeTag): MemoryValue {
412
+ const modulus = TAG_MODULI[tag];
413
+ if (modulus === undefined) {
414
+ throw new Error(`Unsupported tag for random generation: ${TypeTag[tag]}`);
415
+ }
416
+ // Generate random in range [1, max) by generating [0, max-1) and adding 1
417
+ const value = randomBigInt(modulus - 1n) + 1n;
418
+ return TaggedMemory.buildFromTagTruncating(value, tag);
419
+ }
420
+
421
+ /** Generate a random non-zero Field value (for field division). */
422
+ function randomNonZeroField(): Field {
423
+ return new Field(randomBigInt(Fr.MODULUS - 1n) + 1n);
424
+ }
425
+
426
+ /** Reserved memory offsets for external call loop (used by CALL spam and side-effect opcodes) */
427
+ const CONST_0_OFFSET = 0; // Uint32(0)
428
+ const CONST_1_OFFSET = 1; // Uint32(1)
429
+ const CONST_MAX_U32_OFFSET = 2; // Uint32(MAX_U32)
430
+ const CALL_ADDR_OFFSET = 3; // copy addr from calldata to here, and then use this addr for CALL
431
+ const CALL_ARGS_OFFSET = CALL_ADDR_OFFSET; // address is the arg to send to CALL
432
+ const CALL_COPY_SIZE_OFFSET = CONST_1_OFFSET; // copy size = 1 (forward calldata[0])
433
+ const CALL_CALLDATA_INDEX_OFFSET = CONST_0_OFFSET; // calldata[0]
434
+ const CALL_L2_GAS_OFFSET = CONST_MAX_U32_OFFSET; // MAX_U32 gets capped to remaining gas by AVM
435
+ const CALL_DA_GAS_OFFSET = CONST_MAX_U32_OFFSET; // MAX_U32 gets capped to remaining gas by AVM
436
+ const CALL_ARGS_SIZE_OFFSET = CONST_1_OFFSET; // argsSize = 1 (forward calldata[0] - might contain contract address)
437
+ const MAX_U32 = 0xffffffffn;
438
+
439
+ /**
440
+ * A SpamConfig for to make external CALLs to an address specified in calldata[0].
441
+ */
442
+ export const EXTERNAL_CALL_CONFIG: SpamConfig = {
443
+ setup: [
444
+ // calldata will contain 1 item: the external call address
445
+ { offset: CONST_0_OFFSET, value: new Uint32(0) }, // used for cdStartOffset
446
+ { offset: CONST_1_OFFSET, value: new Uint32(1) }, // used for copySize and argsSize
447
+ { offset: CONST_MAX_U32_OFFSET, value: new Uint32(MAX_U32) }, // l2Gas/daGas - MAX_U32 gets capped to remaining gas
448
+ () => [
449
+ new CalldataCopy(
450
+ /*addressing_mode=*/ 0,
451
+ /*copySizeOffset=*/ CALL_COPY_SIZE_OFFSET,
452
+ /*cdStartOffset=*/ CALL_CALLDATA_INDEX_OFFSET,
453
+ /*dstOffset=*/ CALL_ADDR_OFFSET,
454
+ ),
455
+ ], // address = calldata[0] of parent call
456
+ ],
457
+ targetInstructions: () => [
458
+ new Call(
459
+ /*addressing_mode=*/ 0,
460
+ /*l2GasOffset=*/ CALL_L2_GAS_OFFSET,
461
+ /*daGasOffset=*/ CALL_DA_GAS_OFFSET,
462
+ /*addrOffset=*/ CALL_ADDR_OFFSET,
463
+ /*argsSizeOffset=*/ CALL_ARGS_SIZE_OFFSET,
464
+ /*argsOffset=*/ CALL_ARGS_OFFSET,
465
+ ),
466
+ ],
467
+ addressAsCalldata: true, // indicates that the contract address should be passed as calldata[0]
468
+ };
469
+
470
+ const STATIC_CALL_CONFIG: SpamConfig = {
471
+ setup: [
472
+ // calldata will contain 1 item: the external call address
473
+ { offset: CONST_0_OFFSET, value: new Uint32(0) }, // used for cdStartOffset
474
+ { offset: CONST_1_OFFSET, value: new Uint32(1) }, // used for copySize and argsSize
475
+ { offset: CONST_MAX_U32_OFFSET, value: new Uint32(MAX_U32) }, // l2Gas/daGas - MAX_U32 gets capped to remaining gas
476
+ () => [
477
+ new CalldataCopy(
478
+ /*addressing_mode=*/ 0,
479
+ /*copySizeOffset=*/ CALL_COPY_SIZE_OFFSET,
480
+ /*cdStartOffset=*/ CALL_CALLDATA_INDEX_OFFSET,
481
+ /*dstOffset=*/ CALL_ADDR_OFFSET,
482
+ ),
483
+ ], // address = calldata[0] of parent call
484
+ ],
485
+ targetInstructions: () => [
486
+ new StaticCall(
487
+ /*addressing_mode=*/ 0,
488
+ /*l2GasOffset=*/ CALL_L2_GAS_OFFSET,
489
+ /*daGasOffset=*/ CALL_DA_GAS_OFFSET,
490
+ /*addrOffset=*/ CALL_ADDR_OFFSET,
491
+ /*argsSizeOffset=*/ CALL_ARGS_SIZE_OFFSET,
492
+ /*argsOffset=*/ CALL_ARGS_OFFSET,
493
+ ),
494
+ ],
495
+ addressAsCalldata: true, // indicates that the contract address should be passed as calldata[0]
496
+ };
497
+
498
+ // ============================================================================
499
+ // Configuration Map
500
+ // ============================================================================
501
+
502
+ /**
503
+ * Opcode spammer configs for ~all opcodes.
504
+ * Each opcode maps to an array of configs (usually one, but can be multiple for type variants, etc.)
505
+ * Uses smallest wire format (_8) for maximum instruction density.
506
+ */
507
+ export const SPAM_CONFIGS: Partial<Record<Opcode, SpamConfig[]>> = {
508
+ // ═══════════════════════════════════════════════════════════════════════════
509
+ // ARITHMETIC - Test with all type variants (random values, seeded via SEED env var)
510
+ // ═══════════════════════════════════════════════════════════════════════════
511
+ [Opcode.ADD_8]: ALL_TAGS.map(tag => ({
512
+ label: TypeTag[tag],
513
+ setup: [
514
+ { offset: 0, value: randomWithTag(tag) }, // random accumulator
515
+ { offset: 1, value: randomWithTag(tag) }, // random addend
516
+ ],
517
+ targetInstructions: () => [
518
+ new Add(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
519
+ Opcode.ADD_8,
520
+ Add.wireFormat8,
521
+ ),
522
+ ],
523
+ })),
524
+
525
+ [Opcode.SUB_8]: ALL_TAGS.map(tag => ({
526
+ label: TypeTag[tag],
527
+ setup: [
528
+ { offset: 0, value: randomWithTag(tag) }, // random minuend
529
+ { offset: 1, value: randomWithTag(tag) }, // random subtrahend
530
+ ],
531
+ targetInstructions: () => [
532
+ new Sub(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
533
+ Opcode.SUB_8,
534
+ Sub.wireFormat8,
535
+ ),
536
+ ],
537
+ })),
538
+
539
+ [Opcode.MUL_8]: ALL_TAGS.map(tag => ({
540
+ label: TypeTag[tag],
541
+ setup: [
542
+ { offset: 0, value: randomWithTag(tag) }, // random multiplicand
543
+ { offset: 1, value: randomWithTag(tag) }, // random multiplier
544
+ ],
545
+ targetInstructions: () => [
546
+ new Mul(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
547
+ Opcode.MUL_8,
548
+ Mul.wireFormat8,
549
+ ),
550
+ ],
551
+ })),
552
+
553
+ // DIV doesn't support FIELD type
554
+ [Opcode.DIV_8]: INT_TAGS.map(tag => ({
555
+ label: TypeTag[tag],
556
+ setup: [
557
+ { offset: 0, value: randomWithTag(tag) }, // random dividend
558
+ { offset: 1, value: randomNonZeroWithTag(tag) }, // random non-zero divisor
559
+ ],
560
+ targetInstructions: () => [
561
+ new Div(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
562
+ Opcode.DIV_8,
563
+ Div.wireFormat8,
564
+ ),
565
+ ],
566
+ })),
567
+
568
+ // Field-only
569
+ [Opcode.FDIV_8]: [
570
+ {
571
+ setup: [
572
+ { offset: 0, value: new Field(Fr.random()) }, // random dividend
573
+ { offset: 1, value: randomNonZeroField() }, // random non-zero divisor
574
+ ],
575
+ targetInstructions: () => [
576
+ new FieldDiv(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
577
+ Opcode.FDIV_8,
578
+ FieldDiv.wireFormat8,
579
+ ),
580
+ ],
581
+ },
582
+ ],
583
+
584
+ // ═══════════════════════════════════════════════════════════════════════════
585
+ // COMPARATORS - Test with all type variants (random values)
586
+ // ═══════════════════════════════════════════════════════════════════════════
587
+ [Opcode.EQ_8]: ALL_TAGS.map(tag => ({
588
+ label: TypeTag[tag],
589
+ setup: [
590
+ { offset: 0, value: randomWithTag(tag) }, // random value a
591
+ { offset: 1, value: randomWithTag(tag) }, // random value b
592
+ ],
593
+ targetInstructions: () => [
594
+ new Eq(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.EQ_8, Eq.wireFormat8),
595
+ ],
596
+ })),
597
+
598
+ [Opcode.LT_8]: ALL_TAGS.map(tag => ({
599
+ label: TypeTag[tag],
600
+ setup: [
601
+ { offset: 0, value: randomWithTag(tag) }, // random value a
602
+ { offset: 1, value: randomWithTag(tag) }, // random value b
603
+ ],
604
+ targetInstructions: () => [
605
+ new Lt(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(Opcode.LT_8, Lt.wireFormat8),
606
+ ],
607
+ })),
608
+
609
+ [Opcode.LTE_8]: ALL_TAGS.map(tag => ({
610
+ label: TypeTag[tag],
611
+ setup: [
612
+ { offset: 0, value: randomWithTag(tag) }, // random value a
613
+ { offset: 1, value: randomWithTag(tag) }, // random value b
614
+ ],
615
+ targetInstructions: () => [
616
+ new Lte(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2).as(
617
+ Opcode.LTE_8,
618
+ Lte.wireFormat8,
619
+ ),
620
+ ],
621
+ })),
622
+
623
+ // ═══════════════════════════════════════════════════════════════════════════
624
+ // BITWISE - Integer types only (no FIELD) (random values)
625
+ // ═══════════════════════════════════════════════════════════════════════════
626
+ [Opcode.AND_8]: INT_TAGS.map(tag => ({
627
+ label: TypeTag[tag],
628
+ setup: [
629
+ { offset: 0, value: randomWithTag(tag) }, // random value a
630
+ { offset: 1, value: randomWithTag(tag) }, // random value b
631
+ ],
632
+ targetInstructions: () => [
633
+ new And(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
634
+ Opcode.AND_8,
635
+ And.wireFormat8,
636
+ ),
637
+ ],
638
+ })),
639
+
640
+ [Opcode.OR_8]: INT_TAGS.map(tag => ({
641
+ label: TypeTag[tag],
642
+ setup: [
643
+ { offset: 0, value: randomWithTag(tag) }, // random value a
644
+ { offset: 1, value: randomWithTag(tag) }, // random value b
645
+ ],
646
+ targetInstructions: () => [
647
+ new Or(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(Opcode.OR_8, Or.wireFormat8),
648
+ ],
649
+ })),
650
+
651
+ [Opcode.XOR_8]: INT_TAGS.map(tag => ({
652
+ label: TypeTag[tag],
653
+ setup: [
654
+ { offset: 0, value: randomWithTag(tag) }, // random value a
655
+ { offset: 1, value: randomWithTag(tag) }, // random value b
656
+ ],
657
+ targetInstructions: () => [
658
+ new Xor(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
659
+ Opcode.XOR_8,
660
+ Xor.wireFormat8,
661
+ ),
662
+ ],
663
+ })),
664
+
665
+ [Opcode.NOT_8]: INT_TAGS.map(tag => ({
666
+ label: TypeTag[tag],
667
+ setup: [{ offset: 0, value: randomWithTag(tag) }], // random value
668
+ targetInstructions: () => [
669
+ new Not(/*addressing_mode=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 0).as(Opcode.NOT_8, Not.wireFormat8),
670
+ ],
671
+ })),
672
+
673
+ [Opcode.SHL_8]: INT_TAGS.map(tag => ({
674
+ label: TypeTag[tag],
675
+ setup: [
676
+ { offset: 0, value: randomWithTag(tag) }, // random value to shift
677
+ { offset: 1, value: withTag(1n, tag) }, // shift by 1 (small fixed amount to avoid overflow)
678
+ ],
679
+ targetInstructions: () => [
680
+ new Shl(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
681
+ Opcode.SHL_8,
682
+ Shl.wireFormat8,
683
+ ),
684
+ ],
685
+ })),
686
+
687
+ [Opcode.SHR_8]: INT_TAGS.map(tag => ({
688
+ label: TypeTag[tag],
689
+ setup: [
690
+ { offset: 0, value: randomWithTag(tag) }, // random value to shift
691
+ { offset: 1, value: withTag(1n, tag) }, // shift by 1 (small fixed amount)
692
+ ],
693
+ targetInstructions: () => [
694
+ new Shr(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
695
+ Opcode.SHR_8,
696
+ Shr.wireFormat8,
697
+ ),
698
+ ],
699
+ })),
700
+
701
+ // ═══════════════════════════════════════════════════════════════════════════
702
+ // CAST / MOV - Test with all type variants (random values)
703
+ // ═══════════════════════════════════════════════════════════════════════════
704
+ [Opcode.CAST_8]: ALL_TAGS.map(tag => ({
705
+ label: TypeTag[tag],
706
+ setup: [{ offset: 0, value: randomWithTag(tag) }], // random value to cast
707
+ targetInstructions: () => [
708
+ new Cast(/*addressing_mode=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1, /*dstTag=*/ TypeTag.UINT32).as(
709
+ Opcode.CAST_8,
710
+ Cast.wireFormat8,
711
+ ),
712
+ ],
713
+ })),
714
+
715
+ [Opcode.MOV_8]: ALL_TAGS.map(tag => ({
716
+ label: TypeTag[tag],
717
+ setup: [{ offset: 0, value: randomWithTag(tag) }], // random value to move
718
+ targetInstructions: () => [
719
+ new Mov(/*addressing_mode=*/ 0, /*srcOffset=*/ 0, /*dstOffset=*/ 1).as(Opcode.MOV_8, Mov.wireFormat8),
720
+ ],
721
+ })),
722
+
723
+ // ═══════════════════════════════════════════════════════════════════════════
724
+ // MEMORY - SET
725
+ // ═══════════════════════════════════════════════════════════════════════════
726
+ // Not testing all wire formats as they should be roughly the same in terms of simulation
727
+ // and proving time
728
+ //[Opcode.SET_8]: [
729
+ // {
730
+ // setup: [],
731
+ // targetInstructions: () => [new Set(0, 0, TypeTag.UINT8, 42).as(Opcode.SET_8, Set.wireFormat8)],
732
+ // },
733
+ //],
734
+
735
+ //[Opcode.SET_16]: [
736
+ // {
737
+ // setup: [],
738
+ // targetInstructions: () => [new Set(0, 0, TypeTag.UINT16, 4242).as(Opcode.SET_16, Set.wireFormat16)],
739
+ // },
740
+ //],
741
+
742
+ //[Opcode.SET_32]: [
743
+ // {
744
+ // setup: [],
745
+ // targetInstructions: () => [new Set(0, 0, TypeTag.UINT32, 424242).as(Opcode.SET_32, Set.wireFormat32)],
746
+ // },
747
+ //],
748
+
749
+ //[Opcode.SET_64]: [
750
+ // {
751
+ // setup: [],
752
+ // targetInstructions: () => [new Set(0, 0, TypeTag.UINT64, 42424242n).as(Opcode.SET_64, Set.wireFormat64)],
753
+ // },
754
+ //],
755
+
756
+ [Opcode.SET_128]: [
757
+ {
758
+ setup: [],
759
+ targetInstructions: () => [
760
+ new Set(/*addressing_mode=*/ 0, /*dstOffset=*/ 0, /*inTag=*/ TypeTag.UINT128, /*value=*/ 4242424242424242n).as(
761
+ Opcode.SET_128,
762
+ Set.wireFormat128,
763
+ ),
764
+ ],
765
+ },
766
+ ],
767
+
768
+ //[Opcode.SET_FF]: [
769
+ // {
770
+ // setup: [],
771
+ // targetInstructions: () => [new Set(0, 0, TypeTag.FIELD, 42n).as(Opcode.SET_FF, Set.wireFormatFF)],
772
+ // },
773
+ //],
774
+
775
+ // ═══════════════════════════════════════════════════════════════════════════
776
+ // CONTROL FLOW
777
+ // ═══════════════════════════════════════════════════════════════════════════
778
+ [Opcode.JUMP_32]: [
779
+ {
780
+ setup: [],
781
+ // Target will be overwritten by loop builder
782
+ targetInstructions: () => [new Jump(/*jumpOffset=*/ 0)],
783
+ },
784
+ ],
785
+
786
+ [Opcode.JUMPI_32]: [
787
+ {
788
+ setup: [{ offset: 0, value: new Uint1(0n) }], // Always false
789
+ targetInstructions: () => [new JumpI(/*addressing_mode=*/ 0, /*condOffset=*/ 0, /*loc=*/ 0)],
790
+ },
791
+ ],
792
+
793
+ // INTERNALCALL: calls itself infinitely by jumping to its own PC (PC 0, since no setup)
794
+ // Creates infinite recursion until OOG (internal call stack grows forever)
795
+ [Opcode.INTERNALCALL]: [
796
+ {
797
+ setup: [],
798
+ targetInstructions: () => [new InternalCall(/*loc=*/ 0)],
799
+ },
800
+ ],
801
+
802
+ // INTERNALRETURN: needs INTERNALCALL to return without error
803
+ // Layout: INTERNALCALL(10) -> JUMP(0) -> INTERNALRETURN
804
+ // INTERNALCALL jumps to INTERNALRETURN, which returns to JUMP, which loops back
805
+ [Opcode.INTERNALRETURN]: [
806
+ {
807
+ setup: [],
808
+ targetInstructions: () => [
809
+ new InternalCall(/*loc=*/ INTERNALCALL_SIZE + JUMP_SIZE), // jump to INTERNALRETURN
810
+ new Jump(/*jumpOffset=*/ 0), // loop back to start
811
+ new InternalReturn(), // return back to jump
812
+ ],
813
+ },
814
+ ],
815
+
816
+ // CALL (EXTERNALCALL): calls the current contract address (self) in a loop
817
+ // Contract address is passed via calldata[0] and propagated to nested calls
818
+ [Opcode.CALL]: [EXTERNAL_CALL_CONFIG],
819
+ [Opcode.STATICCALL]: [STATIC_CALL_CONFIG],
820
+
821
+ // RETURN: terminates execution, so we need to use the two-contract pattern
822
+ // Outer contract CALLs inner contract in a loop, inner contract does RETURN
823
+ [Opcode.RETURN]: [
824
+ {
825
+ setup: [
826
+ { offset: 0, value: new Uint32(0) }, // returnSize = 0
827
+ ],
828
+ targetInstructions: () => [
829
+ new Return(/*addressing_mode=*/ 0, /*returnSizeOffset=*/ 0, /*returnOffset=*/ 0), // return nothing (size=0)
830
+ ],
831
+ // Use the side-effect-limit pattern (even though it's not a side-effect) as it fits
832
+ // this case (we want to CALL, RETURN, then CALL again back in parent). We omit "cleanup"
833
+ // because we don't need to REVERT as we do for real side-effects.
834
+ limit: 1, // RETURN can only execute once per call
835
+ },
836
+ ],
837
+
838
+ // REVERT: terminates execution, so we need to use the two-contract pattern
839
+ // Outer contract CALLs inner contract in a loop, inner contract does REVERT
840
+ [Opcode.REVERT_8]: [
841
+ {
842
+ setup: [
843
+ { offset: 0, value: new Uint32(0) }, // retSize = 0
844
+ ],
845
+ targetInstructions: () => [
846
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 0, /*returnOffset=*/ 1).as(
847
+ Opcode.REVERT_8,
848
+ Revert.wireFormat8,
849
+ ),
850
+ ],
851
+ limit: 1, // REVERT can only execute once per call
852
+ },
853
+ ],
854
+
855
+ // ═══════════════════════════════════════════════════════════════════════════
856
+ // ENVIRONMENT
857
+ // ═══════════════════════════════════════════════════════════════════════════
858
+ [Opcode.GETENVVAR_16]: [
859
+ {
860
+ setup: [],
861
+ targetInstructions: () => [
862
+ new GetEnvVar(/*addressing_mode=*/ 0, /*dstOffset=*/ 0, /*varEnum=*/ 0).as(
863
+ Opcode.GETENVVAR_16,
864
+ GetEnvVar.wireFormat16,
865
+ ),
866
+ ],
867
+ },
868
+ ],
869
+
870
+ // CALLDATACOPY has dynamic gas scaling with copySize
871
+ [Opcode.CALLDATACOPY]: [
872
+ {
873
+ label: 'Min copy size',
874
+ // CalldataCopy with copySize=0 is a no-op but still executes the opcode
875
+ setup: [
876
+ { offset: 0, value: new Uint32(0n) }, // copySize = 0 (minimum)
877
+ { offset: 1, value: new Uint32(0n) }, // cdStart = 0
878
+ ],
879
+ targetInstructions: () => [
880
+ new CalldataCopy(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2),
881
+ ],
882
+ },
883
+ {
884
+ label: 'Large copy size',
885
+ // Large copySize with large dynamic gas - will OOG quickly
886
+ // NOTE: we don't want it so large that it exceeds memory bounds (MAX_MEMORY_SIZE = 2^32)
887
+ // and we really want it small enough that we run at least 1 successful target opcode.
888
+ setup: [
889
+ { offset: 0, value: new Uint32(1000n) }, // copySize = 1000 (large enough to show scaling)
890
+ { offset: 1, value: new Uint32(0n) }, // cdStart = 0
891
+ ],
892
+ targetInstructions: () => [
893
+ new CalldataCopy(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2),
894
+ ],
895
+ },
896
+ {
897
+ label: 'Near min copy size of 1',
898
+ // Near-min but actually copies data (more meaningful than size=0 no-op)
899
+ setup: [
900
+ { offset: 0, value: new Uint32(1n) }, // copySize = 1
901
+ { offset: 1, value: new Uint32(0n) }, // cdStart = 0
902
+ ],
903
+ targetInstructions: () => [
904
+ new CalldataCopy(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*cdStartOffset=*/ 1, /*dstOffset=*/ 2),
905
+ ],
906
+ },
907
+ ],
908
+
909
+ [Opcode.SUCCESSCOPY]: [
910
+ {
911
+ setup: [],
912
+ targetInstructions: () => [new SuccessCopy(/*addressing_mode=*/ 0, /*dstOffset=*/ 0)],
913
+ },
914
+ ],
915
+
916
+ [Opcode.RETURNDATASIZE]: [
917
+ {
918
+ setup: [],
919
+ targetInstructions: () => [new ReturndataSize(/*addressing_mode=*/ 0, /*dstOffset=*/ 0)],
920
+ },
921
+ ],
922
+
923
+ // RETURNDATACOPY has dynamic gas scaling with copySize
924
+ [Opcode.RETURNDATACOPY]: [
925
+ {
926
+ label: 'Min copy size',
927
+ setup: [
928
+ { offset: 0, value: new Uint32(0n) }, // copySize = 0 (minimum)
929
+ { offset: 1, value: new Uint32(0n) }, // rdOffset
930
+ ],
931
+ targetInstructions: () => [
932
+ new ReturndataCopy(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*rdStartOffset=*/ 1, /*dstOffset=*/ 2),
933
+ ],
934
+ },
935
+ {
936
+ label: 'Large copy size',
937
+ // Large copySize to maximize dynamic gas - will OOG quickly
938
+ // NOTE: we don't want it so large that it exceeds memory bounds (MAX_MEMORY_SIZE = 2^32)
939
+ // and we really want it small enough that we run at least 1 successful target opcode.
940
+ setup: [
941
+ { offset: 0, value: new Uint32(1000n) }, // copySize = 1000 (large enough to show scaling)
942
+ { offset: 1, value: new Uint32(0n) }, // rdOffset
943
+ ],
944
+ targetInstructions: () => [
945
+ new ReturndataCopy(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*rdStartOffset=*/ 1, /*dstOffset=*/ 2),
946
+ ],
947
+ },
948
+ {
949
+ label: 'Near min copy size of 1',
950
+ // Near-min but actually copies data (more meaningful than size=0 no-op)
951
+ setup: [
952
+ { offset: 0, value: new Uint32(1n) }, // copySize = 1
953
+ { offset: 1, value: new Uint32(0n) }, // rdOffset
954
+ ],
955
+ targetInstructions: () => [
956
+ new ReturndataCopy(/*addressing_mode=*/ 0, /*copySizeOffset=*/ 0, /*rdStartOffset=*/ 1, /*dstOffset=*/ 2),
957
+ ],
958
+ },
959
+ ],
960
+
961
+ // ═══════════════════════════════════════════════════════════════════════════
962
+ // WORLD STATE READS
963
+ // ═══════════════════════════════════════════════════════════════════════════
964
+ [Opcode.SLOAD]: [
965
+ {
966
+ label: 'Cold read (slot not written)',
967
+ setup: [
968
+ { offset: 0, value: new Field(Fr.random()) }, // random slot
969
+ () => [
970
+ // Get current contract address into offset 1
971
+ new GetEnvVar(/*addressing_mode=*/ 0, /*dstOffset=*/ 1, /*varEnum=*/ 0).as(
972
+ Opcode.GETENVVAR_16,
973
+ GetEnvVar.wireFormat16,
974
+ ),
975
+ ],
976
+ ],
977
+ targetInstructions: () => [
978
+ new SLoad(/*addressing_mode=*/ 0, /*slotOffset=*/ 0, /*contractAddressOffset=*/ 1, /*dstOffset=*/ 2),
979
+ ],
980
+ },
981
+ {
982
+ label: 'Warm read (from tree)',
983
+ // Uses pre-inserted storage from insertWarmTreeEntries() which is called after contract deployment
984
+ setup: [
985
+ { offset: 0, value: new Field(WARM_STORAGE_SLOT) }, // pre-inserted slot
986
+ () => [
987
+ // Get current contract address into offset 1
988
+ new GetEnvVar(/*addressing_mode=*/ 0, /*dstOffset=*/ 1, /*varEnum=*/ 0).as(
989
+ Opcode.GETENVVAR_16,
990
+ GetEnvVar.wireFormat16,
991
+ ),
992
+ ],
993
+ ],
994
+ targetInstructions: () => [
995
+ new SLoad(/*addressing_mode=*/ 0, /*slotOffset=*/ 0, /*contractAddressOffset=*/ 1, /*dstOffset=*/ 2),
996
+ ],
997
+ },
998
+ {
999
+ label: 'Warm read (SSTORE first, unique slot per SLOAD)',
1000
+ // Memory layout: slot (incremented), value, constant 1, contract address (from GETENVVAR), revertSize, loaded value
1001
+ setup: [
1002
+ { offset: 0, value: new Field(Fr.random()) }, // slot (will be incremented)
1003
+ { offset: 1, value: new Field(Fr.random()) }, // value to store
1004
+ { offset: 2, value: new Field(1n) }, // constant 1 for ADD
1005
+ () => [
1006
+ // Get current contract address into offset 3
1007
+ new GetEnvVar(/*addressing_mode=*/ 0, /*dstOffset=*/ 3, /*varEnum=*/ 0).as(
1008
+ Opcode.GETENVVAR_16,
1009
+ GetEnvVar.wireFormat16,
1010
+ ),
1011
+ ],
1012
+ { offset: 4, value: new Uint32(0n) }, // revertSize
1013
+ ],
1014
+ targetInstructions: () => [
1015
+ new SStore(/*addressing_mode=*/ 0, /*srcOffset=*/ 1, /*slotOffset=*/ 0),
1016
+ new SLoad(/*addressing_mode=*/ 0, /*slotOffset=*/ 0, /*contractAddressOffset=*/ 3, /*dstOffset=*/ 5),
1017
+ new Add(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 2, /*dstOffset=*/ 0).as(
1018
+ Opcode.ADD_8,
1019
+ Add.wireFormat8,
1020
+ ), // slot++
1021
+ ],
1022
+ cleanupInstructions: () => [
1023
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 4, /*returnOffset=*/ 0).as(
1024
+ Opcode.REVERT_8,
1025
+ Revert.wireFormat8,
1026
+ ),
1027
+ ],
1028
+ limit: MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
1029
+ },
1030
+ ],
1031
+
1032
+ [Opcode.NOTEHASHEXISTS]: [
1033
+ {
1034
+ label: 'Cold (non-existent)',
1035
+ // Note: Can't easily do "write first" version - would need to know the leaf index
1036
+ // that EMITNOTEHASH will produce, which depends on tree state
1037
+ setup: [
1038
+ { offset: 0, value: new Field(Fr.random()) }, // random noteHash
1039
+ { offset: 1, value: randomWithTag(TypeTag.UINT64) }, // random leafIndex
1040
+ ],
1041
+ targetInstructions: () => [
1042
+ new NoteHashExists(/*addressing_mode=*/ 0, /*noteHashOffset=*/ 0, /*leafIndexOffset=*/ 1, /*existsOffset=*/ 2),
1043
+ ],
1044
+ },
1045
+ {
1046
+ label: 'Warm (exists in tree)',
1047
+ // Uses pre-inserted note hash from insertWarmTreeEntries()
1048
+ setup: [
1049
+ { offset: 0, value: new Field(WARM_NOTE_HASH) }, // pre-inserted noteHash
1050
+ { offset: 1, value: new Uint64(WARM_NOTE_HASH_LEAF_INDEX) }, // known leafIndex
1051
+ ],
1052
+ targetInstructions: () => [
1053
+ new NoteHashExists(/*addressing_mode=*/ 0, /*noteHashOffset=*/ 0, /*leafIndexOffset=*/ 1, /*existsOffset=*/ 2),
1054
+ ],
1055
+ },
1056
+ ],
1057
+
1058
+ [Opcode.NULLIFIEREXISTS]: [
1059
+ {
1060
+ label: 'Non-existent nullifier',
1061
+ // NULLIFIEREXISTS now takes a siloed nullifier directly (no address parameter)
1062
+ setup: [
1063
+ { offset: 0, value: new Field(Fr.random()) }, // random siloed nullifier (won't exist)
1064
+ ],
1065
+ targetInstructions: () => [
1066
+ new NullifierExists(/*addressing_mode=*/ 0, /*siloedNullifierOffset=*/ 0, /*existsOffset=*/ 1),
1067
+ ],
1068
+ },
1069
+ {
1070
+ label: 'Existing nullifier (warm - from tree)',
1071
+ // Uses pre-inserted siloed nullifier from insertWarmTreeEntries()
1072
+ // NULLIFIEREXISTS now takes a siloed nullifier directly
1073
+ setup: [
1074
+ { offset: 0, value: new Field(WARM_SILOED_NULLIFIER) }, // pre-inserted siloed nullifier
1075
+ ],
1076
+ targetInstructions: () => [
1077
+ new NullifierExists(/*addressing_mode=*/ 0, /*siloedNullifierOffset=*/ 0, /*existsOffset=*/ 1),
1078
+ ],
1079
+ },
1080
+ ],
1081
+
1082
+ [Opcode.L1TOL2MSGEXISTS]: [
1083
+ {
1084
+ label: 'Cold (non-existent)',
1085
+ setup: [
1086
+ { offset: 0, value: new Field(Fr.random()) }, // random msgHash
1087
+ { offset: 1, value: randomWithTag(TypeTag.UINT64) }, // random msgLeafIndex
1088
+ ],
1089
+ targetInstructions: () => [
1090
+ new L1ToL2MessageExists(
1091
+ /*addressing_mode=*/ 0,
1092
+ /*msgHashOffset=*/ 0,
1093
+ /*msgLeafIndexOffset=*/ 1,
1094
+ /*existsOffset=*/ 2,
1095
+ ),
1096
+ ],
1097
+ },
1098
+ {
1099
+ label: 'Warm (exists in tree)',
1100
+ // Uses pre-inserted L1 to L2 message from insertWarmTreeEntries()
1101
+ setup: [
1102
+ { offset: 0, value: new Field(WARM_L1_TO_L2_MSG) }, // pre-inserted msgHash
1103
+ { offset: 1, value: new Uint64(WARM_L1_TO_L2_MSG_LEAF_INDEX) }, // known msgLeafIndex
1104
+ ],
1105
+ targetInstructions: () => [
1106
+ new L1ToL2MessageExists(
1107
+ /*addressing_mode=*/ 0,
1108
+ /*msgHashOffset=*/ 0,
1109
+ /*msgLeafIndexOffset=*/ 1,
1110
+ /*existsOffset=*/ 2,
1111
+ ),
1112
+ ],
1113
+ },
1114
+ ],
1115
+
1116
+ [Opcode.GETCONTRACTINSTANCE]: [
1117
+ {
1118
+ // Use GETENVVAR to get current contract address (varEnum 0 = ADDRESS)
1119
+ // This ensures we're querying a valid deployed contract
1120
+ setup: [
1121
+ () => [
1122
+ new GetEnvVar(/*addressing_mode=*/ 0, /*dstOffset=*/ 0, /*varEnum=*/ 0).as(
1123
+ Opcode.GETENVVAR_16,
1124
+ GetEnvVar.wireFormat16,
1125
+ ),
1126
+ ],
1127
+ ],
1128
+ // memberEnum 0 = DEPLOYER
1129
+ targetInstructions: () => [
1130
+ new GetContractInstance(/*addressing_mode=*/ 0, /*addressOffset=*/ 0, /*dstOffset=*/ 1, /*memberEnum=*/ 0),
1131
+ ],
1132
+ },
1133
+ ],
1134
+
1135
+ // ═══════════════════════════════════════════════════════════════════════════
1136
+ // SIDE-EFFECT LIMITED (have per-TX limit, use nested call pattern)
1137
+ // ═══════════════════════════════════════════════════════════════════════════
1138
+ [Opcode.EMITNOTEHASH]: [
1139
+ {
1140
+ setup: [
1141
+ { offset: 0, value: new Field(Fr.random()) }, // random noteHash
1142
+ { offset: 1, value: new Uint32(0n) }, // revertSize
1143
+ ],
1144
+ targetInstructions: () => [new EmitNoteHash(/*addressing_mode=*/ 0, /*noteHashOffset=*/ 0)],
1145
+ cleanupInstructions: () => [
1146
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 1, /*returnOffset=*/ 0).as(
1147
+ Opcode.REVERT_8,
1148
+ Revert.wireFormat8,
1149
+ ),
1150
+ ], // revert with empty
1151
+ limit: MAX_NOTE_HASHES_PER_TX,
1152
+ },
1153
+ ],
1154
+
1155
+ [Opcode.EMITNULLIFIER]: [
1156
+ {
1157
+ // Nullifiers must be unique - increment value after each emit
1158
+ // Memory layout: offset 0 = nullifier value, offset 1 = constant 1 for incrementing
1159
+ setup: [
1160
+ { offset: 0, value: new Field(Fr.random()) }, // random nullifier (will be incremented)
1161
+ { offset: 1, value: new Field(1n) }, // constant 1 for ADD
1162
+ { offset: 2, value: new Uint32(0n) }, // revertSize
1163
+ ],
1164
+ targetInstructions: () => [
1165
+ new EmitNullifier(/*addressing_mode=*/ 0, /*nullifierOffset=*/ 0),
1166
+ new Add(/*addressing_mode=*/ 0, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 0).as(
1167
+ Opcode.ADD_8,
1168
+ Add.wireFormat8,
1169
+ ), // nullifier++
1170
+ ],
1171
+ cleanupInstructions: () => [
1172
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 2, /*returnOffset=*/ 0).as(
1173
+ Opcode.REVERT_8,
1174
+ Revert.wireFormat8,
1175
+ ),
1176
+ ], // revert with empty
1177
+ limit: MAX_NULLIFIERS_PER_TX - 1, // minus 1 because a TX will always have 1 "TX nullifier" from private
1178
+ },
1179
+ ],
1180
+
1181
+ [Opcode.SENDL2TOL1MSG]: [
1182
+ {
1183
+ setup: [
1184
+ { offset: 0, value: new Field(Fr.random()) }, // random recipient
1185
+ { offset: 1, value: new Field(Fr.random()) }, // random content
1186
+ { offset: 2, value: new Uint32(0n) }, // revertSize
1187
+ ],
1188
+ targetInstructions: () => [
1189
+ new SendL2ToL1Message(/*addressing_mode=*/ 0, /*recipientOffset=*/ 0, /*contentOffset=*/ 1),
1190
+ ],
1191
+ cleanupInstructions: () => [
1192
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 2, /*returnOffset=*/ 0).as(
1193
+ Opcode.REVERT_8,
1194
+ Revert.wireFormat8,
1195
+ ),
1196
+ ], // revert with empty
1197
+ limit: MAX_L2_TO_L1_MSGS_PER_TX,
1198
+ },
1199
+ ],
1200
+
1201
+ // SSTORE has two modes:
1202
+ // 1. Same slot: Writing to the same slot repeatedly has no per-TX limit - it just overwrites.
1203
+ // 2. Unique slots: Writing to unique slots is limited by MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX.
1204
+ [Opcode.SSTORE]: [
1205
+ {
1206
+ label: 'Same slot (no limit)',
1207
+ setup: [
1208
+ { offset: 0, value: new Field(Fr.random()) }, // random value
1209
+ { offset: 1, value: new Field(Fr.random()) }, // random slot (same slot each iteration)
1210
+ { offset: 2, value: new Uint32(0n) }, // revertSize
1211
+ ],
1212
+ targetInstructions: () => [new SStore(/*addressing_mode=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1)],
1213
+ cleanupInstructions: () => [
1214
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 2, /*returnOffset=*/ 0).as(
1215
+ Opcode.REVERT_8,
1216
+ Revert.wireFormat8,
1217
+ ),
1218
+ ], // revert with empty
1219
+ },
1220
+ {
1221
+ label: 'Unique slots (side-effect limited)',
1222
+ setup: [
1223
+ { offset: 0, value: new Field(Fr.random()) }, // random value (constant)
1224
+ { offset: 1, value: new Field(Fr.random()) }, // random slot (will be incremented)
1225
+ { offset: 2, value: new Field(1n) }, // constant 1 for ADD
1226
+ { offset: 3, value: new Uint32(0n) }, // revertSize
1227
+ ],
1228
+ targetInstructions: () => [
1229
+ new SStore(/*addressing_mode=*/ 0, /*srcOffset=*/ 0, /*slotOffset=*/ 1),
1230
+ new Add(/*addressing_mode=*/ 0, /*aOffset=*/ 1, /*bOffset=*/ 2, /*dstOffset=*/ 1).as(
1231
+ Opcode.ADD_8,
1232
+ Add.wireFormat8,
1233
+ ), // slot++
1234
+ ],
1235
+ cleanupInstructions: () => [
1236
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 3, /*returnOffset=*/ 0).as(
1237
+ Opcode.REVERT_8,
1238
+ Revert.wireFormat8,
1239
+ ),
1240
+ ], // revert with empty
1241
+ limit: MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
1242
+ },
1243
+ ],
1244
+
1245
+ // EMITPUBLICLOG - two configs: minimal (many small logs) and max-size (one large log)
1246
+ [Opcode.EMITPUBLICLOG]: [
1247
+ {
1248
+ label: 'Many empty logs, revert, repeat',
1249
+ setup: [
1250
+ { offset: 0, value: new Uint32(0n) }, // logSize = 0 fields (minimal)
1251
+ { offset: 1, value: new Uint32(0n) }, // revertSize
1252
+ ],
1253
+ targetInstructions: () => [new EmitPublicLog(/*addressing_mode=*/ 0, /*logSizeOffset=*/ 0, /*logOffset=*/ 1)], // logOffset doesn't matter when size is 0
1254
+ cleanupInstructions: () => [
1255
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 1, /*returnOffset=*/ 0).as(
1256
+ Opcode.REVERT_8,
1257
+ Revert.wireFormat8,
1258
+ ),
1259
+ ], // revert with empty
1260
+ // Max logs with 0-field content: floor(4096 / 2) = 2048
1261
+ limit: Math.floor(FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH / PUBLIC_LOG_HEADER_LENGTH),
1262
+ },
1263
+ {
1264
+ label: 'One max size log, revert, repeat',
1265
+ setup: [
1266
+ // logSize = MAX_PUBLIC_LOG_SIZE_IN_FIELDS
1267
+ { offset: 0, value: new Uint32(BigInt(MAX_PUBLIC_LOG_SIZE_IN_FIELDS)) },
1268
+ { offset: 1, value: new Uint32(0n) }, // revertSize
1269
+ // NOTE: We don't initialize the log contents and just let it use default values (Field(0n))
1270
+ // so that we save more gas and bytecode space for the Emit.
1271
+ //// Initialize all log content fields to zero (FIELD type)
1272
+ //...Array.from({ length: MAX_PUBLIC_LOG_SIZE_IN_FIELDS }, (_, i) => ({
1273
+ // offset: 2 + i,
1274
+ // value: new Field(0n),
1275
+ //})),
1276
+ ],
1277
+ targetInstructions: () => [new EmitPublicLog(/*addressing_mode=*/ 0, /*logSizeOffset=*/ 0, /*logOffset=*/ 2)], // uses logOffset 2 (uninitialized Field(0))
1278
+ cleanupInstructions: () => [
1279
+ new Revert(/*addressing_mode=*/ 0, /*retSizeOffset=*/ 1, /*returnOffset=*/ 0).as(
1280
+ Opcode.REVERT_8,
1281
+ Revert.wireFormat8,
1282
+ ),
1283
+ ], // revert with empty
1284
+ limit: 1, // Only 1 max-size log fits
1285
+ },
1286
+ ],
1287
+
1288
+ // ═══════════════════════════════════════════════════════════════════════════
1289
+ // GADGETS - Random inputs (seeded via SEED env var)
1290
+ // ═══════════════════════════════════════════════════════════════════════════
1291
+ [Opcode.POSEIDON2]: [
1292
+ {
1293
+ // Poseidon2 takes 4 field elements as input
1294
+ setup: Array.from({ length: 4 }, (_, i) => ({
1295
+ offset: i,
1296
+ value: new Field(Fr.random()), // random field element
1297
+ })),
1298
+ // Poseidon hash data at M[0..3], write result to M[0:3] (reuse results as next inputs)
1299
+ targetInstructions: () => [
1300
+ new Poseidon2(/*addressing_mode=*/ 0, /*inputStateOffset=*/ 0, /*outputStateOffset=*/ 0),
1301
+ ],
1302
+ },
1303
+ ],
1304
+
1305
+ [Opcode.SHA256COMPRESSION]: [
1306
+ {
1307
+ setup: [
1308
+ // State: 8 x UINT32 at offsets 0-7 (random initial state)
1309
+ ...Array.from({ length: 8 }, (_, i) => ({
1310
+ offset: i,
1311
+ value: randomWithTag(TypeTag.UINT32),
1312
+ })),
1313
+ // Inputs: 16 x UINT32 at offsets 8-23 (random message block)
1314
+ ...Array.from({ length: 16 }, (_, i) => ({
1315
+ offset: 8 + i,
1316
+ value: randomWithTag(TypeTag.UINT32),
1317
+ })),
1318
+ ],
1319
+ targetInstructions: () => [
1320
+ new Sha256Compression(/*addressing_mode=*/ 0, /*outputOffset=*/ 0, /*stateOffset=*/ 0, /*inputsOffset=*/ 8),
1321
+ ],
1322
+ },
1323
+ ],
1324
+
1325
+ [Opcode.KECCAKF1600]: [
1326
+ {
1327
+ // Keccak state: 25 x UINT64 (5x5 lane array) with random values
1328
+ setup: Array.from({ length: 25 }, (_, i) => ({
1329
+ offset: i,
1330
+ value: randomWithTag(TypeTag.UINT64),
1331
+ })),
1332
+ targetInstructions: () => [new KeccakF1600(/*addressing_mode=*/ 0, /*dstOffset=*/ 0, /*inputOffset=*/ 0)],
1333
+ },
1334
+ ],
1335
+
1336
+ [Opcode.ECADD]: [
1337
+ {
1338
+ // Use the Grumpkin generator point G for both points (valid curve point)
1339
+ setup: [
1340
+ { offset: 0, value: new Field(Grumpkin.generator.x) }, // p1X = G.x
1341
+ { offset: 1, value: new Field(Grumpkin.generator.y) }, // p1Y = G.y
1342
+ { offset: 2, value: new Uint1(0n) }, // p1IsInfinite = false
1343
+ { offset: 3, value: new Field(Grumpkin.generator.x) }, // p2X = G.x
1344
+ { offset: 4, value: new Field(Grumpkin.generator.y) }, // p2Y = G.y
1345
+ { offset: 5, value: new Uint1(0n) }, // p2IsInfinite = false
1346
+ ],
1347
+ targetInstructions: () => [
1348
+ new EcAdd(
1349
+ /*addressing_mode=*/ 0,
1350
+ /*p1XOffset=*/ 0,
1351
+ /*p1YOffset=*/ 1,
1352
+ /*p1IsInfiniteOffset=*/ 2,
1353
+ /*p2XOffset=*/ 3,
1354
+ /*p2YOffset=*/ 4,
1355
+ /*p2IsInfiniteOffset=*/ 5,
1356
+ /*dstOffset=*/ 0,
1357
+ ),
1358
+ ],
1359
+ },
1360
+ ],
1361
+
1362
+ // TORADIXBE has dynamic gas scaling with numLimbs
1363
+ [Opcode.TORADIXBE]: [
1364
+ {
1365
+ label: 'Min limbs',
1366
+ setup: [
1367
+ { offset: 0, value: new Field(1n) }, // small value that fits in 1 limb (can't randomize - would truncate)
1368
+ { offset: 1, value: new Uint32(2n) }, // radix = 2 (binary)
1369
+ { offset: 2, value: new Uint32(1n) }, // numLimbs = 1 (minimum)
1370
+ { offset: 3, value: new Uint1(0n) }, // outputBits = false
1371
+ ],
1372
+ targetInstructions: () => [
1373
+ new ToRadixBE(
1374
+ /*addressing_mode=*/ 0,
1375
+ /*srcOffset=*/ 0,
1376
+ /*radixOffset=*/ 1,
1377
+ /*numLimbsOffset=*/ 2,
1378
+ /*outputBitsOffset=*/ 3,
1379
+ /*dstOffset=*/ 4,
1380
+ ),
1381
+ ],
1382
+ },
1383
+ {
1384
+ label: 'Max limbs',
1385
+ setup: [
1386
+ { offset: 0, value: new Field(Fr.random()) }, // random field value (fits in 256 bits)
1387
+ { offset: 1, value: new Uint32(2n) }, // radix = 2 (binary)
1388
+ { offset: 2, value: new Uint32(256n) }, // numLimbs = 256 (max bits in field)
1389
+ { offset: 3, value: new Uint1(0n) }, // outputBits = false
1390
+ ],
1391
+ targetInstructions: () => [
1392
+ new ToRadixBE(
1393
+ /*addressing_mode=*/ 0,
1394
+ /*srcOffset=*/ 0,
1395
+ /*radixOffset=*/ 1,
1396
+ /*numLimbsOffset=*/ 2,
1397
+ /*outputBitsOffset=*/ 3,
1398
+ /*dstOffset=*/ 4,
1399
+ ),
1400
+ ],
1401
+ },
1402
+ {
1403
+ label: 'Radix 3 (slow divmod path)',
1404
+ // Radix 3 bypasses the fast path for power-of-2 radixes (4, 8, 16, 32, 64, 128, 256)
1405
+ // and uses the slow divmod implementation instead
1406
+ setup: [
1407
+ { offset: 0, value: new Field(Fr.random()) }, // random field value
1408
+ { offset: 1, value: new Uint32(3n) }, // radix = 3 (non-power-of-2)
1409
+ { offset: 2, value: new Uint32(161n) }, // numLimbs = 161 (ceil(256 / log2(3)) ≈ 161 limbs to represent 256 bits)
1410
+ { offset: 3, value: new Uint1(0n) }, // outputBits = false
1411
+ ],
1412
+ targetInstructions: () => [
1413
+ new ToRadixBE(
1414
+ /*addressing_mode=*/ 0,
1415
+ /*srcOffset=*/ 0,
1416
+ /*radixOffset=*/ 1,
1417
+ /*numLimbsOffset=*/ 2,
1418
+ /*outputBitsOffset=*/ 3,
1419
+ /*dstOffset=*/ 4,
1420
+ ),
1421
+ ],
1422
+ },
1423
+ ],
1424
+
1425
+ // ═══════════════════════════════════════════════════════════════════════════
1426
+ // MISC
1427
+ // ═══════════════════════════════════════════════════════════════════════════
1428
+ // DEBUGLOG only has base gas (no dynamic gas scaling) - memory reads only happen
1429
+ // when collectDebugLogs config is enabled
1430
+ [Opcode.DEBUGLOG]: [
1431
+ {
1432
+ setup: [
1433
+ { offset: 0, value: new Field(0n) }, // level (0 = trace)
1434
+ { offset: 1, value: new Field(0n) }, // message
1435
+ { offset: 2, value: new Field(0n) }, // fields
1436
+ { offset: 3, value: new Uint32(0n) }, // fieldsSize = 0
1437
+ ],
1438
+ // messageSize = 0
1439
+ targetInstructions: () => [
1440
+ new DebugLog(
1441
+ /*addressing_mode=*/ 0,
1442
+ /*levelOffset=*/ 0,
1443
+ /*messageOffset=*/ 1,
1444
+ /*fieldsOffset=*/ 2,
1445
+ /*fieldsSizeOffset=*/ 3,
1446
+ /*messageSize=*/ 0,
1447
+ ),
1448
+ ],
1449
+ },
1450
+ // No real reason to test this by default since debug logs only meaningfully do scaling work
1451
+ // when collectDebugLogs is enabled.
1452
+ // {
1453
+ // label: 'Max sizes',
1454
+ // setup: [
1455
+ // { offset: 0, value: new Field(0n) }, // level (0 = trace)
1456
+ // { offset: 1, value: new Field(0n) }, // message start
1457
+ // { offset: 2, value: new Field(0n) }, // fields start
1458
+ // { offset: 3, value: new Uint32(1000n) }, // fieldsSize = 1000 (large enough to show scaling)
1459
+ // ],
1460
+ // // messageSize = 1000 (large enough to show scaling)
1461
+ // targetInstructions: () => [
1462
+ // new DebugLog(
1463
+ // /*addressing_mode=*/ 0,
1464
+ // /*levelOffset=*/ 0,
1465
+ // /*messageOffset=*/ 1,
1466
+ // /*fieldsOffset=*/ 2,
1467
+ // /*fieldsSizeOffset=*/ 3,
1468
+ // /*messageSize=*/ 1000,
1469
+ // ),
1470
+ // ],
1471
+ // },
1472
+ ],
1473
+ };
1474
+
1475
+ /**
1476
+ * Get all spam test cases grouped by opcode.
1477
+ * This is the main entry point for tests - it handles all the complexity of
1478
+ * type variants, multiple configs, etc.
1479
+ *
1480
+ * Returns hierarchical structure for nested describe blocks in tests.
1481
+ *
1482
+ * @param maxConfigsPerOpcode - Maximum number of configs to include per opcode.
1483
+ * Defaults to Infinity (no limit). Useful for quick
1484
+ * smoke tests where testing all type variants is too slow,
1485
+ * or for proving tests that are inherently slower.
1486
+ */
1487
+ export function getSpamConfigsPerOpcode(maxConfigsPerOpcode: number = Infinity): SpamConfigsForOpcode[] {
1488
+ const groups: SpamConfigsForOpcode[] = [];
1489
+
1490
+ for (const [opcodeKey, configs] of Object.entries(SPAM_CONFIGS)) {
1491
+ const opcode = Opcode[Number(opcodeKey) as Opcode];
1492
+ if (!configs) {
1493
+ throw new Error(`Opcode ${opcode} listed in spam configs, but empty`);
1494
+ }
1495
+
1496
+ // Apply the limit to the number of configs per opcode
1497
+ const limitedConfigs = configs.slice(0, maxConfigsPerOpcode);
1498
+
1499
+ const cases: SpamConfig[] = limitedConfigs.map(config => ({
1500
+ ...config,
1501
+ // unlabeled configs just get opcode name
1502
+ label: config.label ? `${opcode}/${config.label}` : opcode,
1503
+ }));
1504
+
1505
+ groups.push({ opcode: opcode, configs: cases });
1506
+ }
1507
+
1508
+ return groups;
1509
+ }
1510
+
1511
+ // ============================================================================
1512
+ // Helper Functions
1513
+ // ============================================================================
1514
+
1515
+ /**
1516
+ * Create a SET instruction from a MemoryValue.
1517
+ * Chooses smallest SET variant based on offset and value magnitude for optimal bytecode density.
1518
+ */
1519
+ function createSetInstruction(offset: number, memValue: MemoryValue): Bufferable {
1520
+ const tag = memValue.getTag();
1521
+ const value = memValue.toBigInt();
1522
+
1523
+ // SET_8 only supports offset <= 255 and value <= 255
1524
+ if (offset <= 0xff && value <= 0xffn) {
1525
+ return new Set(0, offset, tag, Number(value)).as(Opcode.SET_8, Set.wireFormat8);
1526
+ }
1527
+ // SET_16+ support offset <= 65535
1528
+ if (value <= 0xffffn) {
1529
+ return new Set(0, offset, tag, Number(value)).as(Opcode.SET_16, Set.wireFormat16);
1530
+ }
1531
+ if (value <= 0xffffffffn) {
1532
+ return new Set(0, offset, tag, Number(value)).as(Opcode.SET_32, Set.wireFormat32);
1533
+ }
1534
+ if (value <= 0xffffffffffffffffn) {
1535
+ return new Set(0, offset, tag, value).as(Opcode.SET_64, Set.wireFormat64);
1536
+ }
1537
+ if (value <= 0xffffffffffffffffffffffffffffffffn) {
1538
+ return new Set(0, offset, tag, value).as(Opcode.SET_128, Set.wireFormat128);
1539
+ }
1540
+ return new Set(0, offset, tag, value).as(Opcode.SET_FF, Set.wireFormatFF);
1541
+ }
1542
+
1543
+ /**
1544
+ * Append (to the instructions array) the SET instructions for the setup.
1545
+ *
1546
+ * @param instructions - the instructions array to append the setup to
1547
+ * @param setup - the setup configuration specifying what SETs to do
1548
+ */
1549
+ function appendSetupInstructions(instructions: Bufferable[], setup: SetupItem[]): void {
1550
+ for (const item of setup) {
1551
+ if (typeof item === 'function') {
1552
+ // item is a function that creates setup instructions (like)
1553
+ instructions.push(...item());
1554
+ } else {
1555
+ // MemSetup
1556
+ instructions.push(createSetInstruction(item.offset, item.value));
1557
+ }
1558
+ }
1559
+ }
1560
+
1561
+ /**
1562
+ * Append (to the instructions array) the target instructions nTimes times.
1563
+ *
1564
+ * @param instructions - the instructions array to append the loop to
1565
+ * @param config - the spam config to use
1566
+ * @param nTimes - the number of times to append the target instructions
1567
+ * @returns the number of target instructions appended
1568
+ */
1569
+ function appendTargetNTimes(instructions: Bufferable[], config: SpamConfig, nTimes: number) {
1570
+ for (let i = 0; i < nTimes; i++) {
1571
+ instructions.push(...config.targetInstructions());
1572
+ }
1573
+ }
1574
+
1575
+ /**
1576
+ * Append (to the instructions array) an infinite loop that maximizes target instruction density.
1577
+ * Fills remaining bytecode space with unrolled target instructions.
1578
+ *
1579
+ * @param instructions - the instructions array to append the loop to
1580
+ * @param config - the spam config to use
1581
+ * @returns the number of target instructions in the loop body
1582
+ */
1583
+ function appendInfiniteLoop(instructions: Bufferable[], config: SpamConfig): number {
1584
+ const setupBytecode = encodeToBytecode(instructions);
1585
+ const setupSize = setupBytecode.length;
1586
+
1587
+ // Compute the size of the target instruction(s)
1588
+ const targetSize = encodeToBytecode(config.targetInstructions()).length;
1589
+
1590
+ // Fill remaining space (loop body) with target instructions
1591
+ const availableForLoopBody = MAX_BYTECODE_BYTES - setupSize - JUMP_SIZE;
1592
+ const numTargetsInLoopBody = Math.floor(availableForLoopBody / targetSize);
1593
+
1594
+ const loopStartPc = setupSize;
1595
+ appendTargetNTimes(instructions, config, numTargetsInLoopBody);
1596
+ instructions.push(new Jump(loopStartPc)); // JUMP_SIZE (JUMP_32)
1597
+
1598
+ return numTargetsInLoopBody;
1599
+ }
1600
+
1601
+ /**
1602
+ * Generate basic opcode spam bytecode from a SpamConfig.
1603
+ * Spams the target instruction(s) in an infinite loop until out-of-gas.
1604
+ */
1605
+ export function createOpcodeSpamBytecode(config: SpamConfig): Buffer {
1606
+ assert(
1607
+ config.limit === undefined,
1608
+ 'If config has `limit`, use createSideEffectLimitedSpamInRevertingNestedCall instead',
1609
+ );
1610
+
1611
+ const instructions: Bufferable[] = [];
1612
+
1613
+ // 1. Setup memory
1614
+ appendSetupInstructions(instructions, config.setup);
1615
+
1616
+ // 2. Infinite loop - maximize calls to target until out-of-gas
1617
+ appendInfiniteLoop(instructions, config);
1618
+
1619
+ return encodeToBytecode(instructions);
1620
+ }
1621
+
1622
+ /**
1623
+ * Generate a bytecode that spams a side-effect limited opcode #limit times
1624
+ * NOT in a loop, but inline/unrolled. Then revert.
1625
+ *
1626
+ * @param config - the side-effect limited spam config to use
1627
+ * @returns the bytecode for the side-effect limited spam
1628
+ */
1629
+ export function createSideEffectSpamBytecode(config: SpamConfig): Buffer {
1630
+ assert(
1631
+ config.limit !== undefined,
1632
+ 'If config has `limit`, use createSideEffectLimitedSpamInRevertingNestedCall instead',
1633
+ );
1634
+ const instructions: Bufferable[] = [];
1635
+
1636
+ // 1. Setup
1637
+ appendSetupInstructions(instructions, config.setup);
1638
+
1639
+ // 2. Body - run target instruction(s) #limit times
1640
+ appendTargetNTimes(instructions, config, config.limit);
1641
+
1642
+ // 3. Cleanup (revert)
1643
+ if (config.cleanupInstructions) {
1644
+ instructions.push(...config.cleanupInstructions());
1645
+ }
1646
+
1647
+ return encodeToBytecode(instructions);
1648
+ }
1649
+
1650
+ async function testStandardOpcodeSpam(
1651
+ tester: PublicTxSimulationTester,
1652
+ config: SpamConfig,
1653
+ expectToBeTrue: (x: boolean) => void,
1654
+ ): Promise<PublicTxResult> {
1655
+ const bytecode = createOpcodeSpamBytecode(config);
1656
+ const contract = await deployCustomBytecode(bytecode, tester, config.label);
1657
+
1658
+ await insertWarmTreeEntries(tester.merkleTrees, contract.address);
1659
+
1660
+ // Should we pass the contract address as calldata?
1661
+ const calldata = config.addressAsCalldata ? [contract.address.toField()] : [];
1662
+ const result = await executeCustomBytecode(contract, tester, config.label, calldata);
1663
+
1664
+ // should have halted with out of gas
1665
+ expectToBeTrue(!result.revertCode.isOK());
1666
+ const revertReason = result.findRevertReason()?.message.toLowerCase();
1667
+ const allowedReasons = ['out of gas', 'not enough l2gas'];
1668
+ // expect the reason to match ONE of the allowed reasons
1669
+ expectToBeTrue(allowedReasons.some(allowedReason => revertReason?.includes(allowedReason)));
1670
+ return result;
1671
+ }
1672
+
1673
+ async function testSideEffectOpcodeSpam(
1674
+ tester: PublicTxSimulationTester,
1675
+ config: SpamConfig,
1676
+ expectToBeTrue: (x: boolean) => void,
1677
+ ): Promise<PublicTxResult> {
1678
+ // Inner contract will spam the side-effect limited opcode up to its limit, then REVERT
1679
+ const innerBytecode = createSideEffectSpamBytecode(config);
1680
+ // Outer contract will CALL to inner contract in a loop
1681
+ const outerBytecode = createOpcodeSpamBytecode(EXTERNAL_CALL_CONFIG);
1682
+ const innerContract = await deployCustomBytecode(innerBytecode, tester, `${config.label}_Inner`);
1683
+ const outerContract = await deployCustomBytecode(outerBytecode, tester, `${config.label}_Outer`);
1684
+ // Outer contract reads calldata[0] as inner contract address to CALL to
1685
+ const result = await executeCustomBytecode(outerContract, tester, config.label, [innerContract.address.toField()]);
1686
+
1687
+ // should have halted with out of gas or explicit REVERT (assertion failed)
1688
+ expectToBeTrue(!result.revertCode.isOK());
1689
+ const revertReason = result.findRevertReason()?.message.toLowerCase();
1690
+ const allowedReasons = ['assertion failed', 'out of gas', 'not enough l2gas'];
1691
+ // expect the reason to match ONE of the allowed reasons
1692
+ expectToBeTrue(allowedReasons.some(allowedReason => revertReason?.includes(allowedReason)));
1693
+
1694
+ // Top-level should _always_ run out of gas for these tests
1695
+ // Check top-level halting message
1696
+ // WARNING: only the C++ simulator (or TsVsCpp) will have haltingMessage
1697
+ const allowedOuterReasons = ['out of gas', 'not enough l2gas'];
1698
+ if (result.callStackMetadata && result.callStackMetadata.length > 0) {
1699
+ const outerCallMetadata = result.callStackMetadata[0] as CallStackMetadata;
1700
+ const outerReason = outerCallMetadata.haltingMessage?.toLowerCase();
1701
+ // expect the reason to match ONE of the allowed reasons
1702
+ expectToBeTrue(allowedOuterReasons.some(allowedReason => outerReason?.includes(allowedReason)));
1703
+ }
1704
+
1705
+ return result;
1706
+ }
1707
+
1708
+ export async function testOpcodeSpamCase(
1709
+ tester: PublicTxSimulationTester,
1710
+ config: SpamConfig,
1711
+ expectToBeTrue: (x: boolean) => void = () => {}, // default no-op
1712
+ ): Promise<PublicTxResult> {
1713
+ if (config.limit) {
1714
+ return await testSideEffectOpcodeSpam(tester, config, expectToBeTrue);
1715
+ }
1716
+ return await testStandardOpcodeSpam(tester, config, expectToBeTrue);
1717
+ }