@aztec/simulator 0.87.2 → 0.87.3-nightly.20250528

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 (138) hide show
  1. package/dest/client.d.ts +2 -0
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +2 -0
  4. package/dest/private/acvm/acvm.d.ts +4 -0
  5. package/dest/private/acvm/acvm.d.ts.map +1 -1
  6. package/dest/private/acvm/oracle/oracle.d.ts +1 -1
  7. package/dest/private/acvm/oracle/oracle.d.ts.map +1 -1
  8. package/dest/private/acvm/oracle/oracle.js +2 -2
  9. package/dest/private/acvm/oracle/typed_oracle.d.ts +1 -1
  10. package/dest/private/acvm/oracle/typed_oracle.d.ts.map +1 -1
  11. package/dest/private/acvm/oracle/typed_oracle.js +2 -2
  12. package/dest/private/private_execution.d.ts.map +1 -1
  13. package/dest/private/private_execution.js +2 -1
  14. package/dest/private/private_execution_oracle.d.ts +1 -1
  15. package/dest/private/private_execution_oracle.d.ts.map +1 -1
  16. package/dest/private/private_execution_oracle.js +1 -1
  17. package/dest/private/providers/circuit_recording/circuit_recorder.d.ts +39 -19
  18. package/dest/private/providers/circuit_recording/circuit_recorder.d.ts.map +1 -1
  19. package/dest/private/providers/circuit_recording/circuit_recorder.js +90 -126
  20. package/dest/private/providers/circuit_recording/file_circuit_recorder.d.ts +31 -0
  21. package/dest/private/providers/circuit_recording/file_circuit_recorder.d.ts.map +1 -0
  22. package/dest/private/providers/circuit_recording/file_circuit_recorder.js +135 -0
  23. package/dest/private/providers/circuit_recording/memory_circuit_recorder.d.ts +5 -0
  24. package/dest/private/providers/circuit_recording/memory_circuit_recorder.d.ts.map +1 -0
  25. package/dest/private/providers/circuit_recording/memory_circuit_recorder.js +9 -0
  26. package/dest/private/providers/circuit_recording/simulation_provider_recorder_wrapper.d.ts +3 -1
  27. package/dest/private/providers/circuit_recording/simulation_provider_recorder_wrapper.d.ts.map +1 -1
  28. package/dest/private/providers/circuit_recording/simulation_provider_recorder_wrapper.js +16 -11
  29. package/dest/private/utility_execution_oracle.d.ts +1 -1
  30. package/dest/private/utility_execution_oracle.d.ts.map +1 -1
  31. package/dest/private/utility_execution_oracle.js +1 -1
  32. package/dest/public/avm/avm_gas.d.ts +4 -5
  33. package/dest/public/avm/avm_gas.d.ts.map +1 -1
  34. package/dest/public/avm/avm_gas.js +28 -18
  35. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts +1 -0
  36. package/dest/public/avm/fixtures/base_avm_simulation_tester.d.ts.map +1 -1
  37. package/dest/public/avm/fixtures/base_avm_simulation_tester.js +6 -0
  38. package/dest/public/avm/fixtures/utils.js +1 -1
  39. package/dest/public/avm/opcodes/accrued_substate.d.ts.map +1 -1
  40. package/dest/public/avm/opcodes/accrued_substate.js +8 -7
  41. package/dest/public/avm/opcodes/addressing_mode.d.ts +2 -0
  42. package/dest/public/avm/opcodes/addressing_mode.d.ts.map +1 -1
  43. package/dest/public/avm/opcodes/addressing_mode.js +6 -0
  44. package/dest/public/avm/opcodes/arithmetic.d.ts.map +1 -1
  45. package/dest/public/avm/opcodes/arithmetic.js +1 -1
  46. package/dest/public/avm/opcodes/bitwise.d.ts +5 -1
  47. package/dest/public/avm/opcodes/bitwise.d.ts.map +1 -1
  48. package/dest/public/avm/opcodes/bitwise.js +17 -2
  49. package/dest/public/avm/opcodes/comparators.d.ts.map +1 -1
  50. package/dest/public/avm/opcodes/comparators.js +1 -1
  51. package/dest/public/avm/opcodes/contract.d.ts.map +1 -1
  52. package/dest/public/avm/opcodes/contract.js +1 -1
  53. package/dest/public/avm/opcodes/control_flow.d.ts.map +1 -1
  54. package/dest/public/avm/opcodes/control_flow.js +4 -4
  55. package/dest/public/avm/opcodes/conversion.d.ts +1 -0
  56. package/dest/public/avm/opcodes/conversion.d.ts.map +1 -1
  57. package/dest/public/avm/opcodes/conversion.js +263 -2
  58. package/dest/public/avm/opcodes/ec_add.d.ts.map +1 -1
  59. package/dest/public/avm/opcodes/ec_add.js +1 -1
  60. package/dest/public/avm/opcodes/environment_getters.d.ts.map +1 -1
  61. package/dest/public/avm/opcodes/environment_getters.js +1 -1
  62. package/dest/public/avm/opcodes/external_calls.d.ts.map +1 -1
  63. package/dest/public/avm/opcodes/external_calls.js +6 -8
  64. package/dest/public/avm/opcodes/hashing.d.ts.map +1 -1
  65. package/dest/public/avm/opcodes/hashing.js +3 -3
  66. package/dest/public/avm/opcodes/instruction.d.ts +9 -3
  67. package/dest/public/avm/opcodes/instruction.d.ts.map +1 -1
  68. package/dest/public/avm/opcodes/instruction.js +12 -7
  69. package/dest/public/avm/opcodes/memory.d.ts.map +1 -1
  70. package/dest/public/avm/opcodes/memory.js +8 -6
  71. package/dest/public/avm/opcodes/misc.js +1 -3
  72. package/dest/public/avm/opcodes/storage.d.ts.map +1 -1
  73. package/dest/public/avm/opcodes/storage.js +5 -3
  74. package/dest/public/fixtures/index.d.ts +1 -0
  75. package/dest/public/fixtures/index.d.ts.map +1 -1
  76. package/dest/public/fixtures/index.js +1 -0
  77. package/dest/public/fixtures/minimal_public_tx.d.ts +9 -0
  78. package/dest/public/fixtures/minimal_public_tx.d.ts.map +1 -0
  79. package/dest/public/fixtures/minimal_public_tx.js +43 -0
  80. package/dest/public/public_tx_simulator/apps_tests/amm_test.d.ts.map +1 -1
  81. package/dest/public/public_tx_simulator/apps_tests/amm_test.js +27 -55
  82. package/dest/public/public_tx_simulator/public_tx_context.d.ts +1 -1
  83. package/dest/public/public_tx_simulator/public_tx_context.d.ts.map +1 -1
  84. package/dest/public/public_tx_simulator/public_tx_context.js +5 -4
  85. package/dest/public/side_effect_trace.d.ts +4 -1
  86. package/dest/public/side_effect_trace.d.ts.map +1 -1
  87. package/dest/public/side_effect_trace.js +13 -2
  88. package/dest/public/side_effect_trace_interface.d.ts +1 -0
  89. package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
  90. package/dest/public/state_manager/state_manager.d.ts +1 -0
  91. package/dest/public/state_manager/state_manager.d.ts.map +1 -1
  92. package/dest/public/state_manager/state_manager.js +3 -0
  93. package/dest/server.d.ts +2 -0
  94. package/dest/server.d.ts.map +1 -1
  95. package/dest/server.js +2 -0
  96. package/dest/testing.d.ts +1 -1
  97. package/dest/testing.d.ts.map +1 -1
  98. package/dest/testing.js +1 -1
  99. package/package.json +15 -15
  100. package/src/client.ts +2 -0
  101. package/src/private/acvm/acvm.ts +3 -0
  102. package/src/private/acvm/oracle/oracle.ts +2 -2
  103. package/src/private/acvm/oracle/typed_oracle.ts +2 -2
  104. package/src/private/private_execution.ts +1 -0
  105. package/src/private/private_execution_oracle.ts +1 -1
  106. package/src/private/providers/circuit_recording/circuit_recorder.ts +114 -136
  107. package/src/private/providers/circuit_recording/file_circuit_recorder.ts +158 -0
  108. package/src/private/providers/circuit_recording/memory_circuit_recorder.ts +11 -0
  109. package/src/private/providers/circuit_recording/simulation_provider_recorder_wrapper.ts +22 -14
  110. package/src/private/utility_execution_oracle.ts +1 -1
  111. package/src/public/avm/avm_gas.ts +20 -14
  112. package/src/public/avm/fixtures/base_avm_simulation_tester.ts +5 -0
  113. package/src/public/avm/fixtures/utils.ts +1 -1
  114. package/src/public/avm/opcodes/accrued_substate.ts +23 -7
  115. package/src/public/avm/opcodes/addressing_mode.ts +8 -0
  116. package/src/public/avm/opcodes/arithmetic.ts +3 -1
  117. package/src/public/avm/opcodes/bitwise.ts +26 -2
  118. package/src/public/avm/opcodes/comparators.ts +3 -1
  119. package/src/public/avm/opcodes/contract.ts +3 -1
  120. package/src/public/avm/opcodes/control_flow.ts +6 -4
  121. package/src/public/avm/opcodes/conversion.ts +21 -2
  122. package/src/public/avm/opcodes/ec_add.ts +3 -1
  123. package/src/public/avm/opcodes/environment_getters.ts +3 -1
  124. package/src/public/avm/opcodes/external_calls.ts +16 -8
  125. package/src/public/avm/opcodes/hashing.ts +11 -3
  126. package/src/public/avm/opcodes/instruction.ts +14 -7
  127. package/src/public/avm/opcodes/memory.ts +23 -6
  128. package/src/public/avm/opcodes/misc.ts +4 -4
  129. package/src/public/avm/opcodes/storage.ts +13 -3
  130. package/src/public/fixtures/index.ts +1 -0
  131. package/src/public/fixtures/minimal_public_tx.ts +57 -0
  132. package/src/public/public_tx_simulator/apps_tests/amm_test.ts +49 -44
  133. package/src/public/public_tx_simulator/public_tx_context.ts +26 -5
  134. package/src/public/side_effect_trace.ts +13 -0
  135. package/src/public/side_effect_trace_interface.ts +1 -0
  136. package/src/public/state_manager/state_manager.ts +4 -0
  137. package/src/server.ts +2 -0
  138. package/src/testing.ts +1 -1
@@ -0,0 +1,11 @@
1
+ import { CircuitRecorder } from './circuit_recorder.js';
2
+
3
+ /*
4
+ * In memory circuit recorder uses the default implementation. This is kept
5
+ * while we decide the fate of the FileCircuitRecorder
6
+ */
7
+ export class MemoryCircuitRecorder extends CircuitRecorder {
8
+ constructor() {
9
+ super();
10
+ }
11
+ }
@@ -2,18 +2,21 @@ import type { ForeignCallHandler } from '@aztec/noir-protocol-circuits-types/typ
2
2
  import type { FunctionArtifactWithContractName } from '@aztec/stdlib/abi';
3
3
  import type { NoirCompiledCircuitWithName } from '@aztec/stdlib/noir';
4
4
 
5
- import type { ACIRCallback, ACIRExecutionResult } from '../../acvm/acvm.js';
5
+ import type { ACIRCallback, ACIRCallbackStats, ACIRExecutionResult } from '../../acvm/acvm.js';
6
6
  import type { ACVMWitness } from '../../acvm/acvm_types.js';
7
7
  import type { ACVMSuccess } from '../acvm_native.js';
8
8
  import type { SimulationProvider } from '../simulation_provider.js';
9
- import { CircuitRecorder } from './circuit_recorder.js';
9
+ import type { CircuitRecorder } from './circuit_recorder.js';
10
10
 
11
11
  /**
12
12
  * Takes a simulation provider and wraps it in a circuit recorder. See CircuitRecorder for more details on how circuit
13
13
  * recording works.
14
14
  */
15
15
  export class SimulationProviderRecorderWrapper implements SimulationProvider {
16
- constructor(private simulator: SimulationProvider) {}
16
+ constructor(
17
+ private simulator: SimulationProvider,
18
+ private recorder: CircuitRecorder,
19
+ ) {}
17
20
 
18
21
  executeProtocolCircuit(
19
22
  input: ACVMWitness,
@@ -47,7 +50,7 @@ export class SimulationProviderRecorderWrapper implements SimulationProvider {
47
50
  );
48
51
  }
49
52
 
50
- async #simulate<C extends ACIRCallback | ForeignCallHandler | undefined, T>(
53
+ async #simulate<C extends ACIRCallback | ForeignCallHandler | undefined, T extends ACIRExecutionResult | ACVMSuccess>(
51
54
  simulateFn: (wrappedCallback: C) => Promise<T>,
52
55
  input: ACVMWitness,
53
56
  bytecode: Buffer,
@@ -55,28 +58,33 @@ export class SimulationProviderRecorderWrapper implements SimulationProvider {
55
58
  functionName: string,
56
59
  callback: C,
57
60
  ): Promise<T> {
58
- const recordDir = process.env.CIRCUIT_RECORD_DIR;
59
- if (!recordDir) {
60
- // Recording is not enabled so we just execute the circuit
61
- return simulateFn(callback);
62
- }
63
-
64
61
  // Start recording circuit execution
65
- const recorder = await CircuitRecorder.start(recordDir, input, bytecode, contractName, functionName);
62
+ await this.recorder.start(input, bytecode, contractName, functionName);
66
63
 
67
64
  // If callback was provided, we wrap it in a circuit recorder callback wrapper
68
- const wrappedCallback = recorder.wrapCallback(callback);
65
+ const wrappedCallback = this.recorder.wrapCallback(callback);
69
66
  let result: T;
70
67
  try {
71
68
  result = await simulateFn(wrappedCallback as C);
72
69
  } catch (error) {
73
70
  // If an error occurs, we finalize the recording file with the error
74
- await recorder.finishWithError(error);
71
+ await this.recorder.finishWithError(error);
75
72
  throw error;
76
73
  }
77
74
 
78
75
  // Witness generation is complete so we finish the circuit recorder
79
- await recorder.finish();
76
+ const recording = await this.recorder.finish();
77
+
78
+ (result as ACIRExecutionResult).oracles = recording.oracleCalls?.reduce(
79
+ (acc, { time, name }) => {
80
+ if (!acc[name]) {
81
+ acc[name] = { times: [] };
82
+ }
83
+ acc[name].times.push(time);
84
+ return acc;
85
+ },
86
+ {} as Record<string, ACIRCallbackStats>,
87
+ );
80
88
 
81
89
  return result;
82
90
  }
@@ -274,7 +274,7 @@ export class UtilityExecutionOracle extends TypedOracle {
274
274
  return await this.executionDataProvider.getIndexedTaggingSecretAsSender(this.contractAddress, sender, recipient);
275
275
  }
276
276
 
277
- public override async syncPrivateState(pendingTaggedLogArrayBaseSlot: Fr) {
277
+ public override async fetchTaggedLogs(pendingTaggedLogArrayBaseSlot: Fr) {
278
278
  await this.executionDataProvider.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes);
279
279
 
280
280
  await this.executionDataProvider.removeNullifiedNotes(this.contractAddress);
@@ -104,7 +104,7 @@ const BASE_GAS_COSTS: Record<Opcode, Gas> = {
104
104
  [Opcode.MOV_8]: makeCost(c.AVM_MOV_BASE_L2_GAS, 0),
105
105
  [Opcode.MOV_16]: makeCost(c.AVM_MOV_BASE_L2_GAS, 0),
106
106
  [Opcode.SLOAD]: makeCost(c.AVM_SLOAD_BASE_L2_GAS, 0),
107
- [Opcode.SSTORE]: makeCost(c.AVM_SSTORE_BASE_L2_GAS, c.AVM_SSTORE_BASE_DA_GAS),
107
+ [Opcode.SSTORE]: makeCost(c.AVM_SSTORE_BASE_L2_GAS, 0), // DA gas is dynamic
108
108
  [Opcode.NOTEHASHEXISTS]: makeCost(c.AVM_NOTEHASHEXISTS_BASE_L2_GAS, 0),
109
109
  [Opcode.EMITNOTEHASH]: makeCost(c.AVM_EMITNOTEHASH_BASE_L2_GAS, c.AVM_EMITNOTEHASH_BASE_DA_GAS),
110
110
  [Opcode.NULLIFIEREXISTS]: makeCost(c.AVM_NULLIFIEREXISTS_BASE_L2_GAS, 0),
@@ -129,13 +129,16 @@ const BASE_GAS_COSTS: Record<Opcode, Gas> = {
129
129
  const DYNAMIC_GAS_COSTS = new Map<Opcode, Gas>([
130
130
  [Opcode.CALLDATACOPY, makeCost(c.AVM_CALLDATACOPY_DYN_L2_GAS, 0)],
131
131
  [Opcode.RETURNDATACOPY, makeCost(c.AVM_RETURNDATACOPY_DYN_L2_GAS, 0)],
132
+ // TODO: Call and static call based on bytecode length
132
133
  [Opcode.EMITUNENCRYPTEDLOG, makeCost(c.AVM_EMITUNENCRYPTEDLOG_DYN_L2_GAS, c.AVM_EMITUNENCRYPTEDLOG_DYN_DA_GAS)],
133
- [Opcode.CALL, makeCost(c.AVM_CALL_DYN_L2_GAS, 0)],
134
- [Opcode.STATICCALL, makeCost(c.AVM_STATICCALL_DYN_L2_GAS, 0)],
135
- [Opcode.RETURN, makeCost(c.AVM_RETURN_DYN_L2_GAS, 0)],
136
- [Opcode.REVERT_8, makeCost(c.AVM_REVERT_DYN_L2_GAS, 0)],
137
- [Opcode.REVERT_16, makeCost(c.AVM_REVERT_DYN_L2_GAS, 0)],
138
134
  [Opcode.TORADIXBE, makeCost(c.AVM_TORADIXBE_DYN_L2_GAS, 0)],
135
+ [Opcode.AND_8, makeCost(c.AVM_BITWISE_DYN_L2_GAS, 0)],
136
+ [Opcode.AND_16, makeCost(c.AVM_BITWISE_DYN_L2_GAS, 0)],
137
+ [Opcode.OR_8, makeCost(c.AVM_BITWISE_DYN_L2_GAS, 0)],
138
+ [Opcode.OR_16, makeCost(c.AVM_BITWISE_DYN_L2_GAS, 0)],
139
+ [Opcode.XOR_8, makeCost(c.AVM_BITWISE_DYN_L2_GAS, 0)],
140
+ [Opcode.XOR_16, makeCost(c.AVM_BITWISE_DYN_L2_GAS, 0)],
141
+ [Opcode.SSTORE, makeCost(0, c.AVM_SSTORE_DYN_DA_GAS)],
139
142
  ]);
140
143
 
141
144
  /** Returns the fixed base gas cost for a given opcode. */
@@ -143,17 +146,20 @@ export function getBaseGasCost(opcode: Opcode): Gas {
143
146
  return BASE_GAS_COSTS[opcode];
144
147
  }
145
148
 
146
- export function getDynamicGasCost(opcode: Opcode): Gas {
147
- return DYNAMIC_GAS_COSTS.has(opcode) ? DYNAMIC_GAS_COSTS.get(opcode)! : makeCost(0, 0);
149
+ export function computeAddressingCost(indirectOperandsCount: number, relativeOperandsCount: number): Gas {
150
+ return makeCost(
151
+ indirectOperandsCount * c.AVM_ADDRESSING_INDIRECT_L2_GAS + relativeOperandsCount * c.AVM_ADDRESSING_RELATIVE_L2_GAS,
152
+ 0,
153
+ );
148
154
  }
149
155
 
150
- /** Returns gas cost for an operation on a given type tag based on the base cost per byte. */
151
- export function getGasCostForTypeTag(tag: TypeTag, baseCost: Gas) {
152
- return mulGas(baseCost, getGasCostMultiplierFromTypeTag(tag));
156
+ export function getDynamicGasCost(opcode: Opcode): Gas {
157
+ return DYNAMIC_GAS_COSTS.has(opcode) ? DYNAMIC_GAS_COSTS.get(opcode)! : makeCost(0, 0);
153
158
  }
154
159
 
155
- /** Returns a multiplier based on the size of the type represented by the tag. Throws on uninitialized or invalid. */
156
- function getGasCostMultiplierFromTypeTag(tag: TypeTag) {
160
+ /** Returns a multiplier based on the byte size of the type represented by the integer tag.
161
+ * Used to account for necessary rows in the bitwise trace. Throws on invalid. */
162
+ export function getBitwiseDynamicGasMultiplier(tag: TypeTag) {
157
163
  switch (tag) {
158
164
  case TypeTag.UINT1: // same as u8
159
165
  return 1;
@@ -168,7 +174,7 @@ function getGasCostMultiplierFromTypeTag(tag: TypeTag) {
168
174
  case TypeTag.UINT128:
169
175
  return 16;
170
176
  case TypeTag.FIELD:
171
- return 32;
177
+ return 0; // Field is not allowed for bitwise operations. However we don't fail in gas, since we'll fail in bitwise.
172
178
  case TypeTag.INVALID:
173
179
  throw new InstructionExecutionError(`Invalid tag type for gas cost multiplier: ${TypeTag[tag]}`);
174
180
  }
@@ -100,4 +100,9 @@ export abstract class BaseAvmSimulationTester {
100
100
  );
101
101
  await this.merkleTrees.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()]);
102
102
  }
103
+
104
+ async insertNullifier(contractThatEmitted: AztecAddress, nullifier: Fr) {
105
+ const siloedNullifier = await siloNullifier(contractThatEmitted, nullifier);
106
+ await this.merkleTrees.sequentialInsert(MerkleTreeId.NULLIFIER_TREE, [siloedNullifier.toBuffer()]);
107
+ }
103
108
  }
@@ -122,7 +122,7 @@ export async function createContractClassAndInstance(
122
122
  const contractClass = await makeContractClassPublic(seed, bytecode);
123
123
 
124
124
  const constructorAbi = getContractFunctionAbi('constructor', contractArtifact);
125
- const { publicKeys } = await deriveKeys(Fr.random());
125
+ const { publicKeys } = await deriveKeys(new Fr(seed));
126
126
  const initializationHash = await computeInitializationHash(constructorAbi, constructorArgs);
127
127
  const contractInstance =
128
128
  originalContractClassId === undefined
@@ -31,7 +31,9 @@ export class NoteHashExists extends Instruction {
31
31
  const memory = context.machineState.memory;
32
32
  const addressing = Addressing.fromWire(this.indirect);
33
33
 
34
- context.machineState.consumeGas(this.gasCost());
34
+ context.machineState.consumeGas(
35
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
36
+ );
35
37
  const operands = [this.noteHashOffset, this.leafIndexOffset, this.existsOffset];
36
38
  const [noteHashOffset, leafIndexOffset, existsOffset] = addressing.resolve(operands, memory);
37
39
  memory.checkTags(TypeTag.FIELD, noteHashOffset, leafIndexOffset);
@@ -62,7 +64,9 @@ export class EmitNoteHash extends Instruction {
62
64
  const memory = context.machineState.memory;
63
65
  const addressing = Addressing.fromWire(this.indirect);
64
66
 
65
- context.machineState.consumeGas(this.gasCost());
67
+ context.machineState.consumeGas(
68
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
69
+ );
66
70
 
67
71
  const operands = [this.noteHashOffset];
68
72
  const [noteHashOffset] = addressing.resolve(operands, memory);
@@ -102,7 +106,9 @@ export class NullifierExists extends Instruction {
102
106
  const memory = context.machineState.memory;
103
107
  const addressing = Addressing.fromWire(this.indirect);
104
108
 
105
- context.machineState.consumeGas(this.gasCost());
109
+ context.machineState.consumeGas(
110
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
111
+ );
106
112
 
107
113
  const operands = [this.nullifierOffset, this.addressOffset, this.existsOffset];
108
114
  const [nullifierOffset, addressOffset, existsOffset] = addressing.resolve(operands, memory);
@@ -137,7 +143,9 @@ export class EmitNullifier extends Instruction {
137
143
  const memory = context.machineState.memory;
138
144
  const addressing = Addressing.fromWire(this.indirect);
139
145
 
140
- context.machineState.consumeGas(this.gasCost());
146
+ context.machineState.consumeGas(
147
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
148
+ );
141
149
 
142
150
  const operands = [this.nullifierOffset];
143
151
  const [nullifierOffset] = addressing.resolve(operands, memory);
@@ -184,7 +192,9 @@ export class L1ToL2MessageExists extends Instruction {
184
192
  const memory = context.machineState.memory;
185
193
  const addressing = Addressing.fromWire(this.indirect);
186
194
 
187
- context.machineState.consumeGas(this.gasCost());
195
+ context.machineState.consumeGas(
196
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
197
+ );
188
198
 
189
199
  const operands = [this.msgHashOffset, this.msgLeafIndexOffset, this.existsOffset];
190
200
  const [msgHashOffset, msgLeafIndexOffset, existsOffset] = addressing.resolve(operands, memory);
@@ -220,6 +230,10 @@ export class EmitUnencryptedLog extends Instruction {
220
230
  const memory = context.machineState.memory;
221
231
  const addressing = Addressing.fromWire(this.indirect);
222
232
 
233
+ context.machineState.consumeGas(
234
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
235
+ );
236
+
223
237
  const operands = [this.logOffset, this.logSizeOffset];
224
238
  const [logOffset, logSizeOffset] = addressing.resolve(operands, memory);
225
239
  memory.checkTag(TypeTag.UINT32, logSizeOffset);
@@ -228,7 +242,7 @@ export class EmitUnencryptedLog extends Instruction {
228
242
 
229
243
  const contractAddress = context.environment.address;
230
244
 
231
- context.machineState.consumeGas(this.gasCost(logSize));
245
+ context.machineState.consumeGas(this.dynamicGasCost(logSize));
232
246
  const log = memory.getSlice(logOffset, logSize).map(f => f.toFr());
233
247
  context.persistableState.writePublicLog(contractAddress, log);
234
248
  }
@@ -256,7 +270,9 @@ export class SendL2ToL1Message extends Instruction {
256
270
  const memory = context.machineState.memory;
257
271
  const addressing = Addressing.fromWire(this.indirect);
258
272
 
259
- context.machineState.consumeGas(this.gasCost());
273
+ context.machineState.consumeGas(
274
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
275
+ );
260
276
 
261
277
  const operands = [this.recipientOffset, this.contentOffset];
262
278
  const [recipientOffset, contentOffset] = addressing.resolve(operands, memory);
@@ -54,6 +54,14 @@ export class Addressing {
54
54
  return wire;
55
55
  }
56
56
 
57
+ public indirectOperandsCount(): number {
58
+ return this.modePerOperand.filter(mode => mode & AddressingMode.INDIRECT).length;
59
+ }
60
+
61
+ public relativeOperandsCount(): number {
62
+ return this.modePerOperand.filter(mode => mode & AddressingMode.RELATIVE).length;
63
+ }
64
+
57
65
  /**
58
66
  * Resolves the offsets using the addressing mode.
59
67
  * @param offsets The offsets to resolve.
@@ -16,7 +16,9 @@ export abstract class ThreeOperandArithmeticInstruction extends ThreeOperandInst
16
16
  const memory = context.machineState.memory;
17
17
  const addressing = Addressing.fromWire(this.indirect);
18
18
 
19
- context.machineState.consumeGas(this.gasCost());
19
+ context.machineState.consumeGas(
20
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
21
+ );
20
22
 
21
23
  const operands = [this.aOffset, this.bOffset, this.dstOffset];
22
24
  const [aOffset, bOffset, dstOffset] = addressing.resolve(operands, memory);
@@ -1,4 +1,5 @@
1
1
  import type { AvmContext } from '../avm_context.js';
2
+ import { getBitwiseDynamicGasMultiplier } from '../avm_gas.js';
2
3
  import { type IntegralValue, TaggedMemory, type TaggedMemoryInterface, TypeTag } from '../avm_memory_types.js';
3
4
  import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
4
5
  import { Addressing } from './addressing_mode.js';
@@ -10,12 +11,17 @@ abstract class ThreeOperandBitwiseInstruction extends ThreeOperandInstruction {
10
11
  const memory = context.machineState.memory;
11
12
  const addressing = Addressing.fromWire(this.indirect);
12
13
 
13
- context.machineState.consumeGas(this.gasCost());
14
+ context.machineState.consumeGas(
15
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
16
+ );
14
17
 
15
18
  const operands = [this.aOffset, this.bOffset, this.dstOffset];
16
19
  const [aOffset, bOffset, dstOffset] = addressing.resolve(operands, memory);
17
20
  this.checkTags(memory, aOffset, bOffset);
18
21
 
22
+ const multiplier = this.getDynamicMultiplier(memory.getTag(aOffset));
23
+ context.machineState.consumeGas(this.dynamicGasCost(multiplier));
24
+
19
25
  const a = memory.getAs<IntegralValue>(aOffset);
20
26
  const b = memory.getAs<IntegralValue>(bOffset);
21
27
 
@@ -28,6 +34,10 @@ abstract class ThreeOperandBitwiseInstruction extends ThreeOperandInstruction {
28
34
  TaggedMemory.checkIsIntegralTag(memory.getTag(aOffset));
29
35
  memory.checkTagsAreSame(aOffset, bOffset);
30
36
  }
37
+
38
+ protected getDynamicMultiplier(_lhsTag: TypeTag): number {
39
+ return 0;
40
+ }
31
41
  }
32
42
 
33
43
  export class And extends ThreeOperandBitwiseInstruction {
@@ -37,6 +47,10 @@ export class And extends ThreeOperandBitwiseInstruction {
37
47
  protected override compute(a: IntegralValue, b: IntegralValue): IntegralValue {
38
48
  return a.and(b);
39
49
  }
50
+
51
+ protected override getDynamicMultiplier(lhsTag: TypeTag): number {
52
+ return getBitwiseDynamicGasMultiplier(lhsTag);
53
+ }
40
54
  }
41
55
 
42
56
  export class Or extends ThreeOperandBitwiseInstruction {
@@ -46,6 +60,10 @@ export class Or extends ThreeOperandBitwiseInstruction {
46
60
  protected override compute(a: IntegralValue, b: IntegralValue): IntegralValue {
47
61
  return a.or(b);
48
62
  }
63
+
64
+ protected override getDynamicMultiplier(lhsTag: TypeTag): number {
65
+ return getBitwiseDynamicGasMultiplier(lhsTag);
66
+ }
49
67
  }
50
68
 
51
69
  export class Xor extends ThreeOperandBitwiseInstruction {
@@ -55,6 +73,10 @@ export class Xor extends ThreeOperandBitwiseInstruction {
55
73
  protected override compute(a: IntegralValue, b: IntegralValue): IntegralValue {
56
74
  return a.xor(b);
57
75
  }
76
+
77
+ protected override getDynamicMultiplier(lhsTag: TypeTag): number {
78
+ return getBitwiseDynamicGasMultiplier(lhsTag);
79
+ }
58
80
  }
59
81
 
60
82
  export class Shl extends ThreeOperandBitwiseInstruction {
@@ -102,7 +124,9 @@ export class Not extends Instruction {
102
124
  const memory = context.machineState.memory;
103
125
  const addressing = Addressing.fromWire(this.indirect);
104
126
 
105
- context.machineState.consumeGas(this.gasCost());
127
+ context.machineState.consumeGas(
128
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
129
+ );
106
130
 
107
131
  const operands = [this.srcOffset, this.dstOffset];
108
132
  const [srcOffset, dstOffset] = addressing.resolve(operands, memory);
@@ -9,7 +9,9 @@ abstract class ComparatorInstruction extends ThreeOperandInstruction {
9
9
  const memory = context.machineState.memory;
10
10
  const addressing = Addressing.fromWire(this.indirect);
11
11
 
12
- context.machineState.consumeGas(this.gasCost());
12
+ context.machineState.consumeGas(
13
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
14
+ );
13
15
 
14
16
  const operands = [this.aOffset, this.bOffset, this.dstOffset];
15
17
  const [aOffset, bOffset, dstOffset] = addressing.resolve(operands, memory);
@@ -36,7 +36,9 @@ export class GetContractInstance extends Instruction {
36
36
  const memory = context.machineState.memory;
37
37
  const addressing = Addressing.fromWire(this.indirect);
38
38
 
39
- context.machineState.consumeGas(this.gasCost());
39
+ context.machineState.consumeGas(
40
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
41
+ );
40
42
 
41
43
  if (!(this.memberEnum in ContractInstanceMember)) {
42
44
  throw new InstructionExecutionError(`Invalid GETCONSTRACTINSTANCE member enum ${this.memberEnum}`);
@@ -16,7 +16,7 @@ export class Jump extends Instruction {
16
16
  }
17
17
 
18
18
  public async execute(context: AvmContext): Promise<void> {
19
- context.machineState.consumeGas(this.gasCost());
19
+ context.machineState.consumeGas(this.baseGasCost(0, 0));
20
20
 
21
21
  context.machineState.pc = this.jumpOffset;
22
22
  }
@@ -50,7 +50,9 @@ export class JumpI extends Instruction {
50
50
  const memory = context.machineState.memory;
51
51
  const addressing = Addressing.fromWire(this.indirect);
52
52
 
53
- context.machineState.consumeGas(this.gasCost());
53
+ context.machineState.consumeGas(
54
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
55
+ );
54
56
 
55
57
  const operands = [this.condOffset];
56
58
  const [condOffset] = addressing.resolve(operands, memory);
@@ -79,7 +81,7 @@ export class InternalCall extends Instruction {
79
81
  }
80
82
 
81
83
  public async execute(context: AvmContext): Promise<void> {
82
- context.machineState.consumeGas(this.gasCost());
84
+ context.machineState.consumeGas(this.baseGasCost(0, 0));
83
85
 
84
86
  context.machineState.internalCallStack.push({
85
87
  callPc: context.machineState.pc,
@@ -104,7 +106,7 @@ export class InternalReturn extends Instruction {
104
106
  }
105
107
 
106
108
  public async execute(context: AvmContext): Promise<void> {
107
- context.machineState.consumeGas(this.gasCost());
109
+ context.machineState.consumeGas(this.baseGasCost(0, 0));
108
110
 
109
111
  const stackEntry = context.machineState.internalCallStack.pop();
110
112
  if (stackEntry === undefined) {
@@ -35,6 +35,10 @@ export class ToRadixBE extends Instruction {
35
35
  const memory = context.machineState.memory;
36
36
  const addressing = Addressing.fromWire(this.indirect);
37
37
 
38
+ context.machineState.consumeGas(
39
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
40
+ );
41
+
38
42
  const operands = [this.srcOffset, this.radixOffset, this.numLimbsOffset, this.outputBitsOffset, this.dstOffset];
39
43
  const [srcOffset, radixOffset, numLimbsOffset, outputBitsOffset, dstOffset] = addressing.resolve(operands, memory);
40
44
 
@@ -45,11 +49,13 @@ export class ToRadixBE extends Instruction {
45
49
  memory.checkTag(TypeTag.UINT1, outputBitsOffset);
46
50
 
47
51
  const numLimbs = memory.get(numLimbsOffset).toNumber();
48
- context.machineState.consumeGas(this.gasCost(numLimbs));
52
+ const radix: bigint = memory.get(radixOffset).toBigInt();
53
+ context.machineState.consumeGas(
54
+ this.dynamicGasCost(Math.max(numLimbs, radix > 256n ? 32 : MODULUS_LIMBS_PER_RADIX[Number(radix)])),
55
+ );
49
56
  const outputBits = memory.get(outputBitsOffset).toNumber();
50
57
 
51
58
  let value: bigint = memory.get(srcOffset).toBigInt();
52
- const radix: bigint = memory.get(radixOffset).toBigInt();
53
59
 
54
60
  if (radix < 2 || radix > 256) {
55
61
  throw new InvalidToRadixInputsError(`ToRadixBE instruction's radix should be in range [2,256] (was ${radix}).`);
@@ -79,3 +85,16 @@ export class ToRadixBE extends Instruction {
79
85
  memory.setSlice(dstOffset, res);
80
86
  }
81
87
  }
88
+
89
+ // First two are for radix = 0 and 1, which are invalid, so we have 0 limbs for those cases.
90
+ export const MODULUS_LIMBS_PER_RADIX: number[] = [
91
+ 0, 0, 254, 161, 127, 110, 99, 91, 85, 81, 77, 74, 71, 69, 67, 65, 64, 63, 61, 60, 59, 58, 57, 57, 56, 55, 54, 54, 53,
92
+ 53, 52, 52, 51, 51, 50, 50, 50, 49, 49, 48, 48, 48, 48, 47, 47, 47, 46, 46, 46, 46, 45, 45, 45, 45, 45, 44, 44, 44,
93
+ 44, 44, 43, 43, 43, 43, 43, 43, 42, 42, 42, 42, 42, 42, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 40, 40, 40,
94
+ 40, 40, 40, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
95
+ 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
96
+ 36, 36, 36, 36, 36, 36, 36, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35,
97
+ 35, 35, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
98
+ 34, 34, 34, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33,
99
+ 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
100
+ ];
@@ -41,7 +41,9 @@ export class EcAdd extends Instruction {
41
41
  const memory = context.machineState.memory;
42
42
  const addressing = Addressing.fromWire(this.indirect);
43
43
 
44
- context.machineState.consumeGas(this.gasCost());
44
+ context.machineState.consumeGas(
45
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
46
+ );
45
47
 
46
48
  const operands = [
47
49
  this.p1XOffset,
@@ -73,7 +73,9 @@ export class GetEnvVar extends Instruction {
73
73
  const memory = context.machineState.memory;
74
74
  const addressing = Addressing.fromWire(this.indirect);
75
75
 
76
- context.machineState.consumeGas(this.gasCost());
76
+ context.machineState.consumeGas(
77
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
78
+ );
77
79
 
78
80
  if (!(this.varEnum in EnvironmentVariable)) {
79
81
  throw new InstructionExecutionError(`Invalid GETENVVAR var enum ${this.varEnum}`);
@@ -32,11 +32,15 @@ abstract class ExternalCall extends Instruction {
32
32
  const memory = context.machineState.memory;
33
33
  const addressing = Addressing.fromWire(this.indirect);
34
34
 
35
+ context.machineState.consumeGas(
36
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
37
+ );
38
+
35
39
  const operands = [this.l2GasOffset, this.daGasOffset, this.addrOffset, this.argsSizeOffset, this.argsOffset];
36
40
  const [l2GasOffset, daGasOffset, addrOffset, argsSizeOffset, argsOffset] = addressing.resolve(operands, memory);
37
- // TODO: Should be U32
38
- memory.checkTags(TypeTag.FIELD, l2GasOffset);
39
- memory.checkTags(TypeTag.FIELD, daGasOffset);
41
+
42
+ memory.checkTags(TypeTag.UINT32, l2GasOffset);
43
+ memory.checkTags(TypeTag.UINT32, daGasOffset);
40
44
  memory.checkTag(TypeTag.FIELD, addrOffset);
41
45
  memory.checkTag(TypeTag.UINT32, argsSizeOffset);
42
46
 
@@ -48,9 +52,7 @@ abstract class ExternalCall extends Instruction {
48
52
  // If we are already in a static call, we propagate the environment.
49
53
  const callType = context.environment.isStaticCall ? 'STATICCALL' : this.type;
50
54
 
51
- // First we consume the gas for this operation.
52
- context.machineState.consumeGas(this.gasCost(calldataSize));
53
- // Then we consume the gas allocated for the nested call. The excess will be refunded later.
55
+ // We consume the gas allocated for the nested call. The excess will be refunded later.
54
56
  // Gas allocation is capped by the amount of gas left in the current context.
55
57
  // We have to do some dancing here because the gas allocation is a field,
56
58
  // but in the machine state we track gas as a number.
@@ -179,12 +181,15 @@ export class Return extends Instruction {
179
181
  const memory = context.machineState.memory;
180
182
  const addressing = Addressing.fromWire(this.indirect);
181
183
 
184
+ context.machineState.consumeGas(
185
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
186
+ );
187
+
182
188
  const operands = [this.returnSizeOffset, this.returnOffset];
183
189
  const [returnSizeOffset, returnOffset] = addressing.resolve(operands, memory);
184
190
 
185
191
  memory.checkTag(TypeTag.UINT32, returnSizeOffset);
186
192
  const returnSize = memory.get(returnSizeOffset).toNumber();
187
- context.machineState.consumeGas(this.gasCost(returnSize));
188
193
 
189
194
  const output = memory.getSlice(returnOffset, returnSize).map(word => word.toFr());
190
195
 
@@ -225,12 +230,15 @@ export class Revert extends Instruction {
225
230
  const memory = context.machineState.memory;
226
231
  const addressing = Addressing.fromWire(this.indirect);
227
232
 
233
+ context.machineState.consumeGas(
234
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
235
+ );
236
+
228
237
  const operands = [this.retSizeOffset, this.returnOffset];
229
238
  const [retSizeOffset, returnOffset] = addressing.resolve(operands, memory);
230
239
 
231
240
  memory.checkTag(TypeTag.UINT32, retSizeOffset);
232
241
  const retSize = memory.get(retSizeOffset).toNumber();
233
- context.machineState.consumeGas(this.gasCost(retSize));
234
242
  const output = memory.getSlice(returnOffset, retSize).map(word => word.toFr());
235
243
 
236
244
  context.machineState.revert(output);
@@ -31,7 +31,9 @@ export class Poseidon2 extends Instruction {
31
31
  const memory = context.machineState.memory;
32
32
  const addressing = Addressing.fromWire(this.indirect);
33
33
 
34
- context.machineState.consumeGas(this.gasCost());
34
+ context.machineState.consumeGas(
35
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
36
+ );
35
37
 
36
38
  const operands = [this.inputStateOffset, this.outputStateOffset];
37
39
 
@@ -74,9 +76,12 @@ export class KeccakF1600 extends Instruction {
74
76
  const memory = context.machineState.memory;
75
77
  const addressing = Addressing.fromWire(this.indirect);
76
78
 
79
+ context.machineState.consumeGas(
80
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
81
+ );
82
+
77
83
  const operands = [this.dstOffset, this.inputOffset];
78
84
  const [dstOffset, inputOffset] = addressing.resolve(operands, memory);
79
- context.machineState.consumeGas(this.gasCost());
80
85
 
81
86
  const stateData = memory.getSlice(inputOffset, inputSize).map(word => word.toBigInt());
82
87
  memory.checkTagsRange(TypeTag.UINT64, inputOffset, inputSize);
@@ -117,11 +122,14 @@ export class Sha256Compression extends Instruction {
117
122
  const memory = context.machineState.memory;
118
123
  const addressing = Addressing.fromWire(this.indirect);
119
124
 
125
+ context.machineState.consumeGas(
126
+ this.baseGasCost(addressing.indirectOperandsCount(), addressing.relativeOperandsCount()),
127
+ );
128
+
120
129
  const operands = [this.outputOffset, this.stateOffset, this.inputsOffset];
121
130
  const [outputOffset, stateOffset, inputsOffset] = addressing.resolve(operands, memory);
122
131
 
123
132
  // Note: size of output is same as size of state
124
- context.machineState.consumeGas(this.gasCost());
125
133
  const inputs = Uint32Array.from(memory.getSlice(inputsOffset, INPUTS_SIZE).map(word => word.toNumber()));
126
134
  const state = Uint32Array.from(memory.getSlice(stateOffset, STATE_SIZE).map(word => word.toNumber()));
127
135
 
@@ -3,7 +3,7 @@ import type { Bufferable } from '@aztec/foundation/serialize';
3
3
  import { strict as assert } from 'assert';
4
4
 
5
5
  import type { AvmContext } from '../avm_context.js';
6
- import { type Gas, getBaseGasCost, getDynamicGasCost, mulGas, sumGas } from '../avm_gas.js';
6
+ import { type Gas, computeAddressingCost, getBaseGasCost, getDynamicGasCost, mulGas, sumGas } from '../avm_gas.js';
7
7
  import type { BufferCursor } from '../serialization/buffer_cursor.js';
8
8
  import { Opcode, type OperandType, deserialize, serializeAs } from '../serialization/instruction_serialization.js';
9
9
 
@@ -92,13 +92,20 @@ export abstract class Instruction {
92
92
  }
93
93
 
94
94
  /**
95
- * Computes gas cost for the instruction based on its base cost and memory operations.
96
- * @returns Gas cost.
95
+ * Returns the base gas cost for the instruction.
96
+ * @returns The base gas cost.
97
97
  */
98
- protected gasCost(dynMultiplier: number = 0): Gas {
99
- const baseGasCost = getBaseGasCost(this.opcode);
100
- const dynGasCost = mulGas(getDynamicGasCost(this.opcode), dynMultiplier);
101
- return sumGas(baseGasCost, dynGasCost);
98
+ protected baseGasCost(indirectOperandsCount: number, relativeOperandsCount: number): Gas {
99
+ return sumGas(getBaseGasCost(this.opcode), computeAddressingCost(indirectOperandsCount, relativeOperandsCount));
100
+ }
101
+
102
+ /**
103
+ * Computes the dynamic gas cost for the instruction
104
+ * @param dynMultiplier - The multiplier for the dynamic gas cost.
105
+ * @returns The dynamic gas cost.
106
+ */
107
+ protected dynamicGasCost(dynMultiplier: number = 0): Gas {
108
+ return mulGas(getDynamicGasCost(this.opcode), dynMultiplier);
102
109
  }
103
110
 
104
111
  /**