@aztec/txe 5.0.0-rc.1 → 5.0.0

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 (133) hide show
  1. package/dest/AuthRegistry-FSRZIMX6.js +3 -0
  2. package/dest/AuthRegistry-FSRZIMX6.js.map +7 -0
  3. package/dest/ContractClassRegistry-N6NA3SFA.js +3 -0
  4. package/dest/ContractClassRegistry-N6NA3SFA.js.map +7 -0
  5. package/dest/ContractInstanceRegistry-NADDSLGL.js +3 -0
  6. package/dest/ContractInstanceRegistry-NADDSLGL.js.map +7 -0
  7. package/dest/FeeJuice-QBH5JWJV.js +3 -0
  8. package/dest/FeeJuice-QBH5JWJV.js.map +7 -0
  9. package/dest/HandshakeRegistry-KKUEU7XW.js +3 -0
  10. package/dest/HandshakeRegistry-KKUEU7XW.js.map +7 -0
  11. package/dest/MultiCallEntrypoint-TGICA67H.js +3 -0
  12. package/dest/MultiCallEntrypoint-TGICA67H.js.map +7 -0
  13. package/dest/SchnorrAccount-J5T6OKR3.js +3 -0
  14. package/dest/SchnorrAccount-J5T6OKR3.js.map +7 -0
  15. package/dest/SchnorrInitializerlessAccount-OOES6Q4F.js +3 -0
  16. package/dest/SchnorrInitializerlessAccount-OOES6Q4F.js.map +7 -0
  17. package/dest/bin/index.js +1 -1
  18. package/dest/bin/oracle_test_server.js +2 -2
  19. package/dest/{chunk-UPW55EJX.js → chunk-PDOEF22H.js} +2 -2
  20. package/dest/{chunk-UPW55EJX.js.map → chunk-PDOEF22H.js.map} +3 -3
  21. package/dest/chunk-YP4ODNMQ.js +268 -0
  22. package/dest/chunk-YP4ODNMQ.js.map +7 -0
  23. package/dest/constants.d.ts +1 -1
  24. package/dest/constants.d.ts.map +1 -1
  25. package/dest/constants.js +1 -1
  26. package/dest/index.d.ts +1 -1
  27. package/dest/index.d.ts.map +1 -1
  28. package/dest/index.js +5 -3
  29. package/dest/metafile.json +9768 -8714
  30. package/dest/oracle/interfaces.d.ts +19 -10
  31. package/dest/oracle/interfaces.d.ts.map +1 -1
  32. package/dest/oracle/tagging_secret_strategy.d.ts +12 -0
  33. package/dest/oracle/tagging_secret_strategy.d.ts.map +1 -0
  34. package/dest/oracle/tagging_secret_strategy.js +12 -0
  35. package/dest/oracle/test-resolver/default_fixtures.d.ts +9 -0
  36. package/dest/oracle/test-resolver/default_fixtures.d.ts.map +1 -0
  37. package/dest/oracle/test-resolver/default_fixtures.js +207 -0
  38. package/dest/oracle/test-resolver/index.d.ts +1 -1
  39. package/dest/oracle/test-resolver/index.d.ts.map +1 -1
  40. package/dest/oracle/test-resolver/index.js +1 -2
  41. package/dest/oracle/test-resolver/resolver.d.ts +22 -8
  42. package/dest/oracle/test-resolver/resolver.d.ts.map +1 -1
  43. package/dest/oracle/test-resolver/resolver.js +100 -33
  44. package/dest/oracle/txe_oracle_public_context.d.ts +9 -9
  45. package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
  46. package/dest/oracle/txe_oracle_public_context.js +15 -9
  47. package/dest/oracle/txe_oracle_registry.d.ts +159 -3
  48. package/dest/oracle/txe_oracle_registry.d.ts.map +1 -1
  49. package/dest/oracle/txe_oracle_registry.js +221 -34
  50. package/dest/oracle/txe_oracle_top_level_context.d.ts +22 -8
  51. package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
  52. package/dest/oracle/txe_oracle_top_level_context.js +107 -49
  53. package/dest/oracle/txe_oracle_version.d.ts +3 -3
  54. package/dest/oracle/txe_oracle_version.js +3 -3
  55. package/dest/rpc_translator.d.ts +14 -4
  56. package/dest/rpc_translator.d.ts.map +1 -1
  57. package/dest/rpc_translator.js +91 -11
  58. package/dest/server.bundle.js +1 -1
  59. package/dest/state_machine/archiver.d.ts +1 -1
  60. package/dest/state_machine/archiver.d.ts.map +1 -1
  61. package/dest/state_machine/archiver.js +1 -2
  62. package/dest/state_machine/dummy_p2p_client.d.ts +4 -2
  63. package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
  64. package/dest/state_machine/dummy_p2p_client.js +6 -0
  65. package/dest/state_machine/global_variable_builder.d.ts +3 -4
  66. package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
  67. package/dest/state_machine/global_variable_builder.js +0 -3
  68. package/dest/state_machine/index.d.ts +8 -6
  69. package/dest/state_machine/index.d.ts.map +1 -1
  70. package/dest/state_machine/index.js +40 -12
  71. package/dest/state_machine/synchronizer.d.ts +5 -2
  72. package/dest/state_machine/synchronizer.d.ts.map +1 -1
  73. package/dest/state_machine/synchronizer.js +6 -2
  74. package/dest/txe_session.d.ts +8 -6
  75. package/dest/txe_session.d.ts.map +1 -1
  76. package/dest/txe_session.js +72 -32
  77. package/dest/utils/block_creation.d.ts +2 -2
  78. package/dest/utils/block_creation.d.ts.map +1 -1
  79. package/dest/utils/block_creation.js +2 -2
  80. package/dest/utils/encoding.d.ts +4 -3
  81. package/dest/utils/encoding.d.ts.map +1 -1
  82. package/dest/utils/tx_effect_creation.d.ts +1 -1
  83. package/dest/utils/tx_effect_creation.d.ts.map +1 -1
  84. package/dest/utils/tx_effect_creation.js +0 -1
  85. package/dest/utils/txe_public_contract_data_source.d.ts +1 -1
  86. package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -1
  87. package/dest/utils/txe_public_contract_data_source.js +7 -4
  88. package/dest/worker.bundle.js +1 -1
  89. package/package.json +17 -17
  90. package/src/bin/oracle_test_server.ts +2 -2
  91. package/src/constants.ts +1 -1
  92. package/src/index.ts +5 -3
  93. package/src/oracle/interfaces.ts +21 -8
  94. package/src/oracle/tagging_secret_strategy.ts +25 -0
  95. package/src/oracle/test-resolver/default_fixtures.ts +282 -0
  96. package/src/oracle/test-resolver/index.ts +1 -2
  97. package/src/oracle/test-resolver/resolver.ts +122 -42
  98. package/src/oracle/txe_oracle_public_context.ts +16 -14
  99. package/src/oracle/txe_oracle_registry.ts +156 -18
  100. package/src/oracle/txe_oracle_top_level_context.ts +147 -50
  101. package/src/oracle/txe_oracle_version.ts +3 -3
  102. package/src/rpc_translator.ts +119 -11
  103. package/src/state_machine/archiver.ts +0 -1
  104. package/src/state_machine/dummy_p2p_client.ts +9 -0
  105. package/src/state_machine/global_variable_builder.ts +2 -16
  106. package/src/state_machine/index.ts +49 -30
  107. package/src/state_machine/synchronizer.ts +8 -2
  108. package/src/txe_session.ts +104 -27
  109. package/src/utils/block_creation.ts +2 -1
  110. package/src/utils/tx_effect_creation.ts +0 -2
  111. package/src/utils/txe_public_contract_data_source.ts +6 -4
  112. package/dest/AuthRegistry-CPGFQR26.js +0 -3
  113. package/dest/AuthRegistry-CPGFQR26.js.map +0 -7
  114. package/dest/ContractClassRegistry-EHVIHGEK.js +0 -3
  115. package/dest/ContractClassRegistry-EHVIHGEK.js.map +0 -7
  116. package/dest/ContractInstanceRegistry-DWZDXHRG.js +0 -3
  117. package/dest/ContractInstanceRegistry-DWZDXHRG.js.map +0 -7
  118. package/dest/FeeJuice-MI32ZO7B.js +0 -3
  119. package/dest/FeeJuice-MI32ZO7B.js.map +0 -7
  120. package/dest/HandshakeRegistry-3KSP3ITH.js +0 -3
  121. package/dest/HandshakeRegistry-3KSP3ITH.js.map +0 -7
  122. package/dest/MultiCallEntrypoint-IU7HYFYE.js +0 -3
  123. package/dest/MultiCallEntrypoint-IU7HYFYE.js.map +0 -7
  124. package/dest/SchnorrAccount-6TUE7JX4.js +0 -3
  125. package/dest/SchnorrAccount-6TUE7JX4.js.map +0 -7
  126. package/dest/SchnorrInitializerlessAccount-S3DU2DJK.js +0 -3
  127. package/dest/SchnorrInitializerlessAccount-S3DU2DJK.js.map +0 -7
  128. package/dest/chunk-5U25VAFR.js +0 -265
  129. package/dest/chunk-5U25VAFR.js.map +0 -7
  130. package/dest/oracle/test-resolver/fixtures.d.ts +0 -43
  131. package/dest/oracle/test-resolver/fixtures.d.ts.map +0 -1
  132. package/dest/oracle/test-resolver/fixtures.js +0 -39
  133. package/src/oracle/test-resolver/fixtures.ts +0 -84
@@ -1,14 +1,16 @@
1
1
  /* eslint-disable camelcase */
2
2
  import type { Logger } from '@aztec/foundation/log';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
- import { withHexPrefix, withoutHexPrefix } from '@aztec/foundation/string';
5
- import type { InputSlot, OracleRegistryEntry } from '@aztec/pxe/simulator';
4
+ import { withoutHexPrefix } from '@aztec/foundation/string';
5
+ import { BOUNDED_VEC, BYTE, BoundedVec, Option, type OracleRegistryEntry, makeEntry } from '@aztec/pxe/simulator';
6
6
 
7
7
  import type { ForeignCallArgs, ForeignCallResult } from '../../utils/encoding.js';
8
- import type { OracleTestScenario } from './fixtures.js';
8
+ import { toInputSlots } from '../txe_oracle_registry.js';
9
+ import { synthesizeDefaultFixtures } from './default_fixtures.js';
9
10
 
10
- /** Name of the meta-oracle that Noir tests call to select a fixture scenario by name. */
11
+ /** Name of the meta-oracle that Noir tests call to announce the next call's scenario by name. */
11
12
  const SET_SCENARIO_ORACLE = 'aztec_oracle_test_set_scenario';
13
+ export const SET_SCENARIO_ENTRY = makeEntry({ params: [{ name: 'name', type: BOUNDED_VEC(BYTE) }] });
12
14
 
13
15
  export type OracleTestCallInput = {
14
16
  session_id: number;
@@ -19,12 +21,22 @@ export type OracleTestCallInput = {
19
21
  };
20
22
 
21
23
  /**
22
- * Oracle resolver for roundtrip tests. Selects a fixture scenario, verifies that the received inputs match, and
24
+ * One test case for an oracle: the expected deserialized `inputs`, the `output` to serialize back to Noir, and the
25
+ * `scenario` name (`some`/`none`) the matching Noir test announces. Single-scenario oracles leave `scenario` undefined
26
+ * and are selected automatically.
27
+ */
28
+ export interface OracleTestScenario {
29
+ scenario?: string;
30
+ inputs: Record<string, unknown>;
31
+ output: unknown;
32
+ }
33
+
34
+ /**
35
+ * Oracle resolver for serialization tests. Selects a fixture scenario, verifies that the received inputs match, and
23
36
  * returns the corresponding fixture output.
24
37
  *
25
- * For oracles with a single fixture, it is selected automatically. For oracles with multiple fixtures, Noir tests
26
- * must call the `oracle_test_set_scenario` meta-oracle to select a specific scenario by name before calling the
27
- * real oracle.
38
+ * For oracles with a single scenario, it is selected automatically. For oracles with multiple scenarios, the Noir test
39
+ * announces the scenario by name via the `aztec_oracle_test_set_scenario` meta-oracle before calling the real oracle.
28
40
  */
29
41
  export class OracleTestResolver {
30
42
  private readonly calledOracles = new Set<string>();
@@ -33,12 +45,20 @@ export class OracleTestResolver {
33
45
 
34
46
  constructor(
35
47
  private readonly registry: Record<string, OracleRegistryEntry>,
36
- private readonly fixtures: Partial<Record<string, OracleTestScenario[]>>,
48
+ private readonly fixtures: Record<string, OracleTestScenario[]>,
37
49
  logger?: Logger,
38
50
  ) {
39
51
  this.logger = logger ?? createLogger('txe:test-resolver');
40
52
  }
41
53
 
54
+ /**
55
+ * Builds a resolver whose fixtures are the default scenarios synthesized from the registry's type mappings. The
56
+ * matching Noir serialization tests are auto-generated by `#[generate_oracle_tests]` from the same convention.
57
+ */
58
+ static fromRegistry(registry: Record<string, OracleRegistryEntry>, logger?: Logger): OracleTestResolver {
59
+ return new OracleTestResolver(registry, synthesizeDefaultFixtures(registry), logger);
60
+ }
61
+
42
62
  // eslint-disable-next-line require-await
43
63
  async resolve_foreign_call(callData: OracleTestCallInput): Promise<ForeignCallResult> {
44
64
  const oracleName = callData.function;
@@ -48,8 +68,6 @@ export class OracleTestResolver {
48
68
  return this.#handleSetScenario(callData);
49
69
  }
50
70
 
51
- this.calledOracles.add(oracleName);
52
-
53
71
  if (!(oracleName in this.registry)) {
54
72
  throw new Error(`Oracle '${oracleName}' not found in registry`);
55
73
  }
@@ -62,6 +80,7 @@ export class OracleTestResolver {
62
80
 
63
81
  const match = this.#selectScenario(callData.session_id, oracleName, scenarios);
64
82
  this.#verifyInputs(callData.inputs, entry, match, oracleName);
83
+ this.calledOracles.add(oracleName);
65
84
 
66
85
  this.logger.debug('Verified scenario for oracle', { oracleName });
67
86
 
@@ -81,62 +100,57 @@ export class OracleTestResolver {
81
100
  return Object.keys(this.registry).filter(name => !(name in this.fixtures));
82
101
  }
83
102
 
103
+ /**
104
+ * Records the scenario name for the session's next oracle call. A test announces once per multi-scenario position
105
+ * (param or return), so successive announcements accumulate joined with `+`, matching the names the fixture
106
+ * synthesizer builds (e.g. `some+some`).
107
+ */
84
108
  #handleSetScenario(callData: { session_id: number; inputs: ForeignCallArgs }): ForeignCallResult {
85
- const charCodes = callData.inputs[0] as string[];
86
- const scenarioName = charCodes
87
- .map(hex => Number(BigInt(hex.startsWith('0x') ? hex : `0x${hex}`)))
88
- .map(code => String.fromCharCode(code))
89
- .join('');
109
+ const name = decodeScenarioName(callData.inputs);
110
+ const existing = this.pendingScenario.get(callData.session_id);
111
+ const scenario = existing !== undefined ? `${existing}+${name}` : name;
90
112
 
91
- this.logger.debug('Setting scenario for next oracle call', { sessionId: callData.session_id, scenarioName });
92
- this.pendingScenario.set(callData.session_id, scenarioName);
113
+ this.logger.debug('Setting scenario for next oracle call', { sessionId: callData.session_id, scenario });
114
+ this.pendingScenario.set(callData.session_id, scenario);
93
115
 
94
116
  return { values: [] };
95
117
  }
96
118
 
97
- /** Selects a scenario by pending name (for multi-fixture oracles) or returns the single fixture. */
119
+ /** Selects the fixture by the announced scenario name (for multi-scenario oracles) or returns the single fixture. */
98
120
  #selectScenario(sessionId: number, oracleName: string, fixtures: OracleTestScenario[]): OracleTestScenario {
99
- const selectedName = this.pendingScenario.get(sessionId);
121
+ const announced = this.pendingScenario.get(sessionId);
100
122
 
101
- if (selectedName !== undefined) {
123
+ if (announced !== undefined) {
102
124
  this.pendingScenario.delete(sessionId);
103
- const matches = fixtures.filter(s => s.scenario === selectedName);
104
- if (matches.length === 0) {
105
- const available = fixtures.map(s => s.scenario).join(', ');
106
- throw new Error(`No scenario named '${selectedName}' for oracle '${oracleName}'. Available: ${available}`);
125
+ const match = fixtures.find(s => s.scenario === announced);
126
+ if (!match) {
127
+ const available = fixtures.map(s => s.scenario ?? '<default>').join(', ');
128
+ throw new Error(`No scenario '${announced}' for oracle '${oracleName}'. Available: ${available}`);
107
129
  }
108
- if (matches.length > 1) {
109
- throw new Error(`Duplicate scenario name '${selectedName}' for oracle '${oracleName}'`);
110
- }
111
- return matches[0];
130
+ return match;
112
131
  }
113
132
 
114
133
  if (fixtures.length === 1) {
115
134
  return fixtures[0];
116
135
  }
117
136
 
118
- throw new Error(
119
- `Oracle '${oracleName}' has ${fixtures.length} fixture scenarios. ` +
120
- `Use #[oracle_test("scenario_name")] to select one.`,
121
- );
137
+ throw new Error(`Oracle '${oracleName}' has ${fixtures.length} scenarios but none was announced before the call.`);
122
138
  }
123
139
 
124
140
  /** Deserializes actual inputs and verifies they match the scenario's expected inputs. */
125
141
  #verifyInputs(
126
142
  rawInputs: ForeignCallArgs,
127
143
  entry: OracleRegistryEntry,
128
- scenario: OracleTestScenario,
144
+ fixture: OracleTestScenario,
129
145
  oracleName: string,
130
146
  ): void {
131
- const expectedInputs = scenario.inputs as Record<string, unknown>;
147
+ const expectedInputs = fixture.inputs;
132
148
  if (Object.keys(expectedInputs).length === 0) {
133
149
  return;
134
150
  }
135
151
 
136
- const normalized: InputSlot[] = rawInputs.map(v =>
137
- Array.isArray(v) ? (v as string[]).map(withHexPrefix) : [withHexPrefix(v as string)],
138
- );
139
- const named = entry.deserializeParams(normalized);
152
+ const scenarioLabel = fixture.scenario ? ` (scenario '${fixture.scenario}')` : '';
153
+ const named = entry.deserializeParams(toInputSlots(rawInputs));
140
154
 
141
155
  for (const param of named) {
142
156
  if (!(param.name in expectedInputs)) {
@@ -146,20 +160,86 @@ export class OracleTestResolver {
146
160
  );
147
161
  }
148
162
  if (!valuesEqual(param.value, expectedInputs[param.name])) {
149
- const scenarioLabel = scenario.scenario ? ` (scenario '${scenario.scenario}')` : '';
150
163
  throw new Error(
151
164
  `Input mismatch for oracle '${oracleName}'${scenarioLabel}: ` +
152
165
  `param '${param.name}' expected ${String(expectedInputs[param.name])} ` +
153
- `but got ${String(param.value)}`,
166
+ `but got ${String(param.value)}. ${versionBumpHint(oracleName)}`,
154
167
  );
155
168
  }
156
169
  }
157
170
  }
158
171
  }
159
172
 
173
+ /**
174
+ * Decodes the scenario name announced by `set_oracle_test_scenario`, whose argument is a `BoundedVec<u8, N>`
175
+ */
176
+ function decodeScenarioName(inputs: ForeignCallArgs): string {
177
+ const [{ value }] = SET_SCENARIO_ENTRY.deserializeParams(toInputSlots(inputs));
178
+ return String.fromCharCode(...value.data);
179
+ }
180
+
160
181
  function valuesEqual(actual: unknown, expected: unknown): boolean {
182
+ if (!isNonNullObject(actual) || !isNonNullObject(expected)) {
183
+ // Primitive scalars; String() bridges bigint vs number representations of the same seed. A primitive compared
184
+ // against an object is never equal.
185
+ return isNonNullObject(actual) === isNonNullObject(expected) && String(actual) === String(expected);
186
+ }
187
+ if (actual instanceof Option && expected instanceof Option) {
188
+ return actual.equals(expected, valuesEqual);
189
+ }
190
+ if (actual instanceof BoundedVec && expected instanceof BoundedVec) {
191
+ return actual.equals(expected, valuesEqual);
192
+ }
161
193
  if (Array.isArray(actual) && Array.isArray(expected)) {
162
194
  return actual.length === expected.length && actual.every((v, i) => valuesEqual(v, expected[i]));
163
195
  }
164
- return String(actual) === String(expected);
196
+ if (isPlainObject(actual) && isPlainObject(expected)) {
197
+ const expectedKeys = Object.keys(expected);
198
+ return (
199
+ Object.keys(actual).length === expectedKeys.length &&
200
+ expectedKeys.every(key => valuesEqual(actual[key], expected[key]))
201
+ );
202
+ }
203
+ if (actual.constructor !== expected.constructor) {
204
+ return false;
205
+ }
206
+ if (hasEquals(actual)) {
207
+ return actual.equals(expected);
208
+ }
209
+ const actualStr = customStringForm(actual);
210
+ const expectedStr = customStringForm(expected);
211
+ if (actualStr !== undefined && expectedStr !== undefined) {
212
+ return actualStr === expectedStr;
213
+ }
214
+ // Comparing blindly here could pass vacuously (e.g. two '[object Object]' strings), so refuse instead.
215
+ throw new Error(
216
+ `valuesEqual cannot compare a '${actual.constructor?.name}' against a '${expected.constructor?.name}'. Add a branch for the type.`,
217
+ );
218
+ }
219
+
220
+ function isNonNullObject(value: unknown): value is object {
221
+ return typeof value === 'object' && value !== null;
222
+ }
223
+
224
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
225
+ return isNonNullObject(value) && value.constructor === Object;
226
+ }
227
+
228
+ function hasEquals(value: object): value is { equals(other: unknown): boolean } {
229
+ return typeof (value as { equals?: unknown }).equals === 'function';
230
+ }
231
+
232
+ /** The value's string form when its class overrides `toString` (a meaningful representation), undefined otherwise. */
233
+ function customStringForm(value: object): string | undefined {
234
+ const { toString } = value;
235
+ if (typeof toString !== 'function' || toString === Object.prototype.toString) {
236
+ return undefined;
237
+ }
238
+ return toString.call(value);
239
+ }
240
+
241
+ function versionBumpHint(oracle: string): string {
242
+ return oracle.startsWith('aztec_txe_') || oracle.startsWith('aztec_avm_')
243
+ ? 'If you changed this oracle, consider bumping the TXE oracle version in yarn-project/txe/src/oracle/txe_oracle_version.ts.'
244
+ : 'If you changed this oracle, consider bumping the PXE oracle version in yarn-project/pxe/src/oracle_version.ts.';
165
245
  }
@@ -5,7 +5,7 @@ import type { ContractStore } from '@aztec/pxe/server';
5
5
  import { PublicDataWrite } from '@aztec/stdlib/avm';
6
6
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
7
7
  import type { L2Block } from '@aztec/stdlib/block';
8
- import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract';
8
+ import type { ContractInstancePreimageWithAddress } from '@aztec/stdlib/contract';
9
9
  import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/stdlib/hash';
10
10
  import {
11
11
  MerkleTreeId,
@@ -125,34 +125,36 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
125
125
  return value;
126
126
  }
127
127
 
128
- getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
128
+ getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
129
129
  return this.getContractInstanceMember(address, i => i.deployer.toField());
130
130
  }
131
131
 
132
- getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
133
- return this.getContractInstanceMember(address, i => i.currentContractClassId);
132
+ getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
133
+ // TXE has no contract updates, so the current class always equals the original.
134
+ return this.getContractInstanceMember(address, i => i.originalContractClassId);
134
135
  }
135
136
 
136
- getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
137
+ getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
137
138
  return this.getContractInstanceMember(address, i => i.initializationHash);
138
139
  }
139
140
 
140
- getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }> {
141
+ getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]> {
141
142
  return this.getContractInstanceMember(address, i => i.immutablesHash);
142
143
  }
143
144
 
145
+ // The one-element array mirrors the oracles' Noir return type, `[GetContractInstanceResult; 1]`.
144
146
  private async getContractInstanceMember(
145
147
  address: AztecAddress,
146
- accessor: (instance: ContractInstanceWithAddress) => Fr,
147
- ): Promise<{ member: Fr; exists: boolean }> {
148
+ accessor: (instance: ContractInstancePreimageWithAddress) => Fr,
149
+ ): Promise<{ member: Fr; exists: boolean }[]> {
148
150
  const instance = await this.contractStore.getContractInstance(address);
149
151
  if (!instance) {
150
- return { member: Fr.ZERO, exists: false };
152
+ return [{ member: Fr.ZERO, exists: false }];
151
153
  }
152
- return { member: accessor(instance), exists: true };
154
+ return [{ member: accessor(instance), exists: true }];
153
155
  }
154
156
 
155
- returndataSize(): Promise<Fr> {
157
+ returndataSize(): Promise<number> {
156
158
  throw new Error(
157
159
  'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
158
160
  );
@@ -164,19 +166,19 @@ export class TXEOraclePublicContext implements IAvmExecutionOracle {
164
166
  );
165
167
  }
166
168
 
167
- call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise<Fr[]> {
169
+ call(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise<void> {
168
170
  throw new Error(
169
171
  'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
170
172
  );
171
173
  }
172
174
 
173
- staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise<Fr[]> {
175
+ staticCall(_l2Gas: number, _daGas: number, _address: AztecAddress, _argsLength: number, _args: Fr[]): Promise<void> {
174
176
  throw new Error(
175
177
  'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
176
178
  );
177
179
  }
178
180
 
179
- successCopy(): Promise<Fr> {
181
+ successCopy(): Promise<boolean> {
180
182
  throw new Error(
181
183
  'Contract calls are forbidden inside a `TestEnvironment::public_context`, use `public_call` instead',
182
184
  );
@@ -4,27 +4,36 @@ import {
4
4
  MAX_NOTE_HASHES_PER_TX,
5
5
  MAX_NULLIFIERS_PER_TX,
6
6
  MAX_PRIVATE_LOGS_PER_TX,
7
+ PRIVATE_CONTEXT_INPUTS_LENGTH,
7
8
  PRIVATE_LOG_SIZE_IN_FIELDS,
8
9
  } from '@aztec/constants';
9
10
  import { Fr } from '@aztec/foundation/curves/bn254';
11
+ import { Point } from '@aztec/foundation/curves/grumpkin';
10
12
  import { withHexPrefix, withoutHexPrefix } from '@aztec/foundation/string';
13
+ import type { TaggingSecretStrategy } from '@aztec/pxe/server';
11
14
  import {
12
15
  ARRAY,
13
16
  AZTEC_ADDRESS,
14
17
  BIGINT,
15
18
  BLOCK_NUMBER,
16
19
  BOOL,
20
+ ETH_ADDRESS,
17
21
  FIELD,
22
+ FIXED_ARRAY,
18
23
  FUNCTION_SELECTOR,
19
24
  type InputSlot,
20
25
  type MaybePromise,
21
26
  OPTION,
22
27
  ORACLE_REGISTRY,
23
28
  type OracleRegistryEntry,
29
+ type OutputSlot,
24
30
  type ParamTypes,
25
31
  STR,
32
+ STRUCT,
33
+ type SlotShape,
26
34
  type TypeMapping,
27
35
  U32,
36
+ buildACIRCallback,
28
37
  makeEntry,
29
38
  } from '@aztec/pxe/simulator';
30
39
  import { EventSelector } from '@aztec/stdlib/abi';
@@ -42,20 +51,73 @@ import {
42
51
  } from '../constants.js';
43
52
  import type { ForeignCallArgs, ForeignCallResult } from '../utils/encoding.js';
44
53
 
54
+ // Spreading `ORACLE_REGISTRY` re-materializes its entries into `TXE_ORACLE_REGISTRY`'s inferred type, which names the
55
+ // protocol types below. Re-exporting them gives tsc a portable path to each instead of falling back to a deep
56
+ // node_modules path that breaks .d.ts portability (TS2742).
57
+ export type { ContractClassLogData, EmbeddedCurvePoint, TxEffectData } from '@aztec/pxe/simulator';
58
+ export type { BlockHash } from '@aztec/stdlib/block';
59
+ export type { MembershipWitness } from '@aztec/foundation/trees';
60
+ export type { NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
61
+
45
62
  const GAS_SETTINGS: TypeMapping<GasSettings> = {
46
- serialization: { fn: v => v.toFields() },
47
63
  deserialization: {
48
64
  fn: ([reader]) => GasSettings.fromFields(reader.readFieldArray(GAS_SETTINGS_LENGTH)),
49
- slots: 1,
50
65
  },
66
+ shape: [{ len: GAS_SETTINGS_LENGTH }],
67
+ };
68
+
69
+ // Tagging secret strategy discriminants. Must match the Noir test helper `TaggingSecretStrategy` in
70
+ // aztec-nr `test/helpers/tagging_secret_strategy.nr`. This is a test-only oracle (only `set_tagging_secret_strategies`
71
+ // reads it), so the mapping lives here on the TXE side rather than in the production oracle type mappings.
72
+ const STRATEGY_NON_INTERACTIVE_HANDSHAKE = 1;
73
+ const STRATEGY_ARBITRARY_SECRET = 2;
74
+ const STRATEGY_ADDRESS_DERIVED = 3;
75
+ const STRATEGY_INTERACTIVE_HANDSHAKE = 4;
76
+
77
+ const TAGGING_SECRET_STRATEGY: TypeMapping<TaggingSecretStrategy> = {
78
+ serialization: {
79
+ fn: strategy => {
80
+ switch (strategy.type) {
81
+ case 'non-interactive-handshake':
82
+ return [new Fr(STRATEGY_NON_INTERACTIVE_HANDSHAKE), Fr.ZERO, Fr.ZERO];
83
+ case 'interactive-handshake':
84
+ return [new Fr(STRATEGY_INTERACTIVE_HANDSHAKE), Fr.ZERO, Fr.ZERO];
85
+ case 'address-derived':
86
+ return [new Fr(STRATEGY_ADDRESS_DERIVED), Fr.ZERO, Fr.ZERO];
87
+ case 'arbitrary-secret':
88
+ return [new Fr(STRATEGY_ARBITRARY_SECRET), strategy.secret.x, strategy.secret.y];
89
+ }
90
+ },
91
+ },
92
+ deserialization: {
93
+ fn: ([kindReader, xReader, yReader]) => {
94
+ const kind = kindReader.readField().toNumber();
95
+ const [x, y] = [xReader.readField(), yReader.readField()];
96
+ switch (kind) {
97
+ case STRATEGY_NON_INTERACTIVE_HANDSHAKE:
98
+ return { type: 'non-interactive-handshake' };
99
+ case STRATEGY_INTERACTIVE_HANDSHAKE:
100
+ return { type: 'interactive-handshake' };
101
+ case STRATEGY_ADDRESS_DERIVED:
102
+ return { type: 'address-derived' };
103
+ case STRATEGY_ARBITRARY_SECRET:
104
+ return { type: 'arbitrary-secret', secret: Point.fromFields([x, y]) };
105
+ default:
106
+ throw new Error(`Unrecognized tagging secret strategy kind: ${kind}`);
107
+ }
108
+ },
109
+ },
110
+ shape: ['scalar', 'scalar', 'scalar'],
51
111
  };
52
112
 
53
113
  const PRIVATE_CONTEXT_INPUTS: TypeMapping<PrivateContextInputs> = {
54
114
  serialization: { fn: v => v.toFields() },
115
+ shape: Array<SlotShape>(PRIVATE_CONTEXT_INPUTS_LENGTH).fill('scalar'),
55
116
  };
56
117
 
57
118
  const COMPLETE_ADDRESS: TypeMapping<CompleteAddress> = {
58
119
  serialization: { fn: v => [v.address.toField(), ...v.publicKeys.toFields()] },
120
+ shape: Array<SlotShape>(8).fill('scalar'), // address + 7 public-key fields
59
121
  };
60
122
 
61
123
  const TXE_TX_EFFECTS: TypeMapping<{
@@ -91,6 +153,17 @@ const TXE_TX_EFFECTS: TypeMapping<{
91
153
  ] as (Fr | Fr[])[];
92
154
  },
93
155
  },
156
+ // txHash, padded note hashes + count, padded nullifiers + count, flattened private-log storage + lengths + count.
157
+ shape: [
158
+ 'scalar',
159
+ { len: MAX_NOTE_HASHES_PER_TX },
160
+ 'scalar',
161
+ { len: MAX_NULLIFIERS_PER_TX },
162
+ 'scalar',
163
+ { len: MAX_PRIVATE_LOGS_PER_TX * PRIVATE_LOG_SIZE_IN_FIELDS },
164
+ { len: MAX_PRIVATE_LOGS_PER_TX },
165
+ 'scalar',
166
+ ],
94
167
  };
95
168
 
96
169
  const TXE_OFFCHAIN_EFFECTS: TypeMapping<{ effects: Fr[][] }> = {
@@ -109,6 +182,12 @@ const TXE_OFFCHAIN_EFFECTS: TypeMapping<{ effects: Fr[][] }> = {
109
182
  return [rawArrayStorage, effectLengths, new Fr(effects.length)] as (Fr | Fr[])[];
110
183
  },
111
184
  },
185
+ // Flattened effect storage, per-effect lengths, then the effect count.
186
+ shape: [
187
+ { len: MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY * MAX_OFFCHAIN_EFFECT_LEN },
188
+ { len: MAX_OFFCHAIN_EFFECTS_PER_TXE_QUERY },
189
+ 'scalar',
190
+ ],
112
191
  };
113
192
 
114
193
  const TXE_CALL_CONTEXT: TypeMapping<{ txHash: Fr; anchorBlockTimestamp: bigint }> = {
@@ -118,15 +197,21 @@ const TXE_CALL_CONTEXT: TypeMapping<{ txHash: Fr; anchorBlockTimestamp: bigint }
118
197
  return [new Fr(isSome), txHash, new Fr(anchorBlockTimestamp)];
119
198
  },
120
199
  },
200
+ shape: ['scalar', 'scalar', 'scalar'], // discriminant, txHash, anchor block timestamp
121
201
  };
122
202
 
123
- const CONTRACT_INSTANCE_MEMBER: TypeMapping<{ member: Fr; exists: boolean }> = {
124
- serialization: { fn: ({ member, exists }) => [member, new Fr(exists)] },
125
- };
203
+ const CONTRACT_INSTANCE_MEMBER: TypeMapping<{ exists: boolean; member: Fr }[]> = FIXED_ARRAY(
204
+ STRUCT([
205
+ { name: 'exists', type: BOOL },
206
+ { name: 'member', type: FIELD },
207
+ ]),
208
+ 1,
209
+ );
126
210
 
127
211
  const EVENT_SELECTOR: TypeMapping<EventSelector> = {
128
212
  serialization: { fn: v => [v.toField()] },
129
- deserialization: { fn: ([reader]) => EventSelector.fromField(reader.readField()), slots: 1 },
213
+ deserialization: { fn: ([reader]) => EventSelector.fromField(reader.readField()) },
214
+ shape: ['scalar'],
130
215
  };
131
216
 
132
217
  const TXE_PRIVATE_EVENTS: TypeMapping<Fr[][]> = {
@@ -144,9 +229,15 @@ const TXE_PRIVATE_EVENTS: TypeMapping<Fr[][]> = {
144
229
  return [rawArrayStorage, eventLengths, new Fr(events.length)] as (Fr | Fr[])[];
145
230
  },
146
231
  },
232
+ // Flattened event storage, per-event lengths, then the event count.
233
+ shape: [
234
+ { len: MAX_PRIVATE_EVENTS_PER_TXE_QUERY * MAX_PRIVATE_EVENT_LEN },
235
+ { len: MAX_PRIVATE_EVENTS_PER_TXE_QUERY },
236
+ 'scalar',
237
+ ],
147
238
  };
148
239
 
149
- export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
240
+ export const TXE_ORACLE_REGISTRY = {
150
241
  ...ORACLE_REGISTRY,
151
242
 
152
243
  aztec_txe_assertCompatibleOracleVersion: makeEntry({
@@ -219,6 +310,23 @@ export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
219
310
  ],
220
311
  }),
221
312
 
313
+ aztec_txe_sendL1ToL2Message: makeEntry({
314
+ params: [
315
+ { name: 'content', type: FIELD },
316
+ { name: 'secretHash', type: FIELD },
317
+ { name: 'sender', type: ETH_ADDRESS },
318
+ { name: 'recipient', type: AZTEC_ADDRESS },
319
+ ],
320
+ returnType: FIELD,
321
+ }),
322
+
323
+ aztec_txe_setTaggingSecretStrategies: makeEntry({
324
+ params: [
325
+ { name: 'unconstrainedStrategy', type: OPTION(TAGGING_SECRET_STRATEGY) },
326
+ { name: 'constrainedStrategy', type: OPTION(TAGGING_SECRET_STRATEGY) },
327
+ ],
328
+ }),
329
+
222
330
  aztec_txe_getLastBlockTimestamp: makeEntry({
223
331
  returnType: BIGINT,
224
332
  }),
@@ -253,6 +361,7 @@ export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
253
361
 
254
362
  aztec_txe_executeUtilityFunction: makeEntry({
255
363
  params: [
364
+ { name: 'from', type: OPTION(AZTEC_ADDRESS) },
256
365
  { name: 'targetContractAddress', type: AZTEC_ADDRESS },
257
366
  { name: 'functionSelector', type: FUNCTION_SELECTOR },
258
367
  { name: 'args', type: ARRAY(FIELD) },
@@ -318,7 +427,7 @@ export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
318
427
  params: [{ name: 'message', type: ARRAY(FIELD) }],
319
428
  }),
320
429
 
321
- aztec_avm_returndataSize: makeEntry({ returnType: FIELD }),
430
+ aztec_avm_returndataSize: makeEntry({ returnType: U32 }),
322
431
 
323
432
  aztec_avm_returndataCopy: makeEntry({
324
433
  params: [
@@ -336,7 +445,6 @@ export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
336
445
  { name: 'argsLength', type: U32 },
337
446
  { name: 'args', type: ARRAY(FIELD) },
338
447
  ],
339
- returnType: ARRAY(FIELD),
340
448
  }),
341
449
 
342
450
  aztec_avm_staticCall: makeEntry({
@@ -347,10 +455,9 @@ export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
347
455
  { name: 'argsLength', type: U32 },
348
456
  { name: 'args', type: ARRAY(FIELD) },
349
457
  ],
350
- returnType: ARRAY(FIELD),
351
458
  }),
352
459
 
353
- aztec_avm_successCopy: makeEntry({ returnType: FIELD }),
460
+ aztec_avm_successCopy: makeEntry({ returnType: BOOL }),
354
461
 
355
462
  aztec_avm_getContractInstanceDeployer: makeEntry({
356
463
  params: [{ name: 'address', type: AZTEC_ADDRESS }],
@@ -368,7 +475,12 @@ export const TXE_ORACLE_REGISTRY: Record<string, OracleRegistryEntry> = {
368
475
  params: [{ name: 'address', type: AZTEC_ADDRESS }],
369
476
  returnType: CONTRACT_INSTANCE_MEMBER,
370
477
  }),
371
- };
478
+ } satisfies Record<string, OracleRegistryEntry>;
479
+
480
+ export function toInputSlots(inputs: ForeignCallArgs): InputSlot[] {
481
+ // TXE foreign calls use bare hex strings, but Fr.fromString requires a 0x prefix to parse as hex.
482
+ return inputs.map(v => (Array.isArray(v) ? (v as string[]).map(withHexPrefix) : [withHexPrefix(v as string)]));
483
+ }
372
484
 
373
485
  /**
374
486
  * Deserializes oracle inputs, calls the handler with typed params, serializes the result, and wraps
@@ -387,14 +499,40 @@ export async function callTxeHandler<K extends keyof typeof TXE_ORACLE_REGISTRY>
387
499
  ) => MaybePromise<Parameters<(typeof TXE_ORACLE_REGISTRY)[K]['serializeReturn']>[0]>;
388
500
  }): Promise<ForeignCallResult> {
389
501
  const entry = TXE_ORACLE_REGISTRY[oracle] as OracleRegistryEntry;
390
- // TXE foreign calls use bare hex strings, but Fr.fromString requires a 0x prefix to parse as hex.
391
- const normalized: InputSlot[] = inputs.map(v =>
392
- Array.isArray(v) ? (v as string[]).map(withHexPrefix) : [withHexPrefix(v as string)],
393
- );
394
- const named = entry.deserializeParams(normalized);
502
+ const named = entry.deserializeParams(toInputSlots(inputs));
395
503
  const positional = named.map((p: { value: unknown }) => p.value);
396
504
  const result = await handler(positional as any);
397
- const outputSlots = entry.serializeReturn(result);
505
+ return outputSlotsToForeignCallResult(entry.serializeReturn(result));
506
+ }
507
+
508
+ /**
509
+ * Dispatches an oracle that has been retired into the PXE legacy registry. TXE has no explicit handler method for such
510
+ * names; this runs them through the same `buildACIRCallback` legacy path that contract execution already uses, so
511
+ * TXE's top-level oracle path and its in-contract path treat the legacy registry identically.
512
+ */
513
+ const legacyCallbacksByHandler = new WeakMap<object, ReturnType<typeof buildACIRCallback>>();
514
+
515
+ export async function callTxeLegacyHandler(
516
+ oracle: string,
517
+ inputs: ForeignCallArgs,
518
+ oracleHandler: Parameters<typeof buildACIRCallback>[0],
519
+ ): Promise<ForeignCallResult> {
520
+ // `buildACIRCallback` materializes the whole real+legacy closure set, so memoize it per handler rather than
521
+ // rebuilding on every call (TXE routes one legacy oracle through here on each top-level discovery).
522
+ let callback = legacyCallbacksByHandler.get(oracleHandler);
523
+ if (!callback) {
524
+ callback = buildACIRCallback(oracleHandler);
525
+ legacyCallbacksByHandler.set(oracleHandler, callback);
526
+ }
527
+ const outputSlots = await callback[oracle](...toInputSlots(inputs));
528
+ return outputSlotsToForeignCallResult(outputSlots);
529
+ }
530
+
531
+ /**
532
+ * Strips the `0x` prefix from each serialized output slot (TXE foreign calls use bare hex strings) and wraps them in a
533
+ * `ForeignCallResult`.
534
+ */
535
+ function outputSlotsToForeignCallResult(outputSlots: OutputSlot[]): ForeignCallResult {
398
536
  return {
399
537
  values: outputSlots.map(slot => (Array.isArray(slot) ? slot.map(withoutHexPrefix) : withoutHexPrefix(slot))),
400
538
  };