@aztec/txe 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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.
- package/dest/AuthRegistry-QVGDDH5X.js +3 -0
- package/dest/AuthRegistry-QVGDDH5X.js.map +7 -0
- package/dest/ContractClassRegistry-PVNCKGG7.js +3 -0
- package/dest/ContractClassRegistry-PVNCKGG7.js.map +7 -0
- package/dest/ContractInstanceRegistry-WLOZKTR6.js +3 -0
- package/dest/ContractInstanceRegistry-WLOZKTR6.js.map +7 -0
- package/dest/FeeJuice-MAQJU53K.js +3 -0
- package/dest/FeeJuice-MAQJU53K.js.map +7 -0
- package/dest/HandshakeRegistry-GY5CPEPS.js +3 -0
- package/dest/HandshakeRegistry-GY5CPEPS.js.map +7 -0
- package/dest/MultiCallEntrypoint-QG5H33ZZ.js +3 -0
- package/dest/MultiCallEntrypoint-QG5H33ZZ.js.map +7 -0
- package/dest/SchnorrAccount-ADD65DYA.js +3 -0
- package/dest/SchnorrAccount-ADD65DYA.js.map +7 -0
- package/dest/SchnorrInitializerlessAccount-TWGKRUSY.js +3 -0
- package/dest/SchnorrInitializerlessAccount-TWGKRUSY.js.map +7 -0
- package/dest/bin/check_txe_oracle_version.js +9 -19
- package/dest/bin/index.js +1 -1
- package/dest/bin/oracle_test_server.d.ts +3 -0
- package/dest/bin/oracle_test_server.d.ts.map +1 -0
- package/dest/bin/oracle_test_server.js +41 -0
- package/dest/chunk-32UHX7YY.js +3 -0
- package/dest/{chunk-DF6RMOC6.js → chunk-RVEPNDEP.js} +2 -2
- package/dest/{chunk-DF6RMOC6.js.map → chunk-RVEPNDEP.js.map} +3 -3
- package/dest/chunk-XWIMXLKD.js +258 -0
- package/dest/chunk-XWIMXLKD.js.map +7 -0
- package/dest/constants.d.ts +3 -1
- package/dest/constants.d.ts.map +1 -1
- package/dest/constants.js +5 -2
- package/dest/dispatcher_pool.d.ts +2 -2
- package/dest/dispatcher_pool.d.ts.map +1 -1
- package/dest/dispatcher_pool.js +5 -3
- package/dest/index.d.ts +6 -143
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +12 -169
- package/dest/metafile.json +10267 -9581
- package/dest/oracle/interfaces.d.ts +38 -11
- package/dest/oracle/interfaces.d.ts.map +1 -1
- package/dest/oracle/tagging_secret_strategy.d.ts +12 -0
- package/dest/oracle/tagging_secret_strategy.d.ts.map +1 -0
- package/dest/oracle/tagging_secret_strategy.js +12 -0
- package/dest/oracle/test-resolver/default_fixtures.d.ts +16 -0
- package/dest/oracle/test-resolver/default_fixtures.d.ts.map +1 -0
- package/dest/oracle/test-resolver/default_fixtures.js +200 -0
- package/dest/oracle/test-resolver/index.d.ts +9 -0
- package/dest/oracle/test-resolver/index.d.ts.map +1 -0
- package/dest/oracle/test-resolver/index.js +32 -0
- package/dest/oracle/test-resolver/resolver.d.ts +48 -0
- package/dest/oracle/test-resolver/resolver.d.ts.map +1 -0
- package/dest/oracle/test-resolver/resolver.js +181 -0
- package/dest/oracle/txe_oracle_public_context.d.ts +25 -6
- package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_public_context.js +34 -1
- package/dest/oracle/txe_oracle_registry.d.ts +172 -0
- package/dest/oracle/txe_oracle_registry.d.ts.map +1 -0
- package/dest/oracle/txe_oracle_registry.js +767 -0
- package/dest/oracle/txe_oracle_top_level_context.d.ts +39 -14
- package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_top_level_context.js +489 -289
- package/dest/{txe_oracle_version.d.ts → oracle/txe_oracle_version.d.ts} +5 -5
- package/dest/oracle/txe_oracle_version.d.ts.map +1 -0
- package/dest/{txe_oracle_version.js → oracle/txe_oracle_version.js} +4 -4
- package/dest/rpc_translator.d.ts +116 -284
- package/dest/rpc_translator.d.ts.map +1 -1
- package/dest/rpc_translator.js +809 -860
- package/dest/server.bundle.js +1 -1
- package/dest/state_machine/archiver.d.ts +3 -2
- package/dest/state_machine/archiver.d.ts.map +1 -1
- package/dest/state_machine/archiver.js +9 -3
- package/dest/state_machine/dummy_p2p_client.d.ts +5 -2
- package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
- package/dest/state_machine/dummy_p2p_client.js +9 -0
- package/dest/state_machine/global_variable_builder.d.ts +3 -4
- package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
- package/dest/state_machine/global_variable_builder.js +0 -3
- package/dest/state_machine/index.d.ts +8 -6
- package/dest/state_machine/index.d.ts.map +1 -1
- package/dest/state_machine/index.js +41 -19
- package/dest/state_machine/mock_epoch_cache.d.ts +2 -3
- package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
- package/dest/state_machine/mock_epoch_cache.js +3 -6
- package/dest/state_machine/synchronizer.d.ts +10 -2
- package/dest/state_machine/synchronizer.d.ts.map +1 -1
- package/dest/state_machine/synchronizer.js +18 -5
- package/dest/txe_session.d.ts +43 -28
- package/dest/txe_session.d.ts.map +1 -1
- package/dest/txe_session.js +183 -47
- package/dest/utils/block_creation.d.ts +2 -2
- package/dest/utils/block_creation.d.ts.map +1 -1
- package/dest/utils/block_creation.js +2 -2
- package/dest/utils/encoding.d.ts +8 -5
- package/dest/utils/encoding.d.ts.map +1 -1
- package/dest/utils/tx_effect_creation.d.ts +1 -1
- package/dest/utils/tx_effect_creation.d.ts.map +1 -1
- package/dest/utils/tx_effect_creation.js +0 -1
- package/dest/utils/txe_artifact_resolver.d.ts +37 -0
- package/dest/utils/txe_artifact_resolver.d.ts.map +1 -0
- package/dest/utils/txe_artifact_resolver.js +161 -0
- package/dest/utils/txe_public_contract_data_source.d.ts +1 -1
- package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -1
- package/dest/utils/txe_public_contract_data_source.js +7 -4
- package/dest/worker.bundle.js +1 -1
- package/package.json +18 -17
- package/src/bin/check_txe_oracle_version.ts +11 -17
- package/src/bin/oracle_test_server.ts +51 -0
- package/src/constants.ts +6 -2
- package/src/dispatcher_pool.ts +4 -3
- package/src/index.ts +21 -198
- package/src/oracle/interfaces.ts +35 -9
- package/src/oracle/tagging_secret_strategy.ts +25 -0
- package/src/oracle/test-resolver/default_fixtures.ts +271 -0
- package/src/oracle/test-resolver/index.ts +44 -0
- package/src/oracle/test-resolver/resolver.ts +245 -0
- package/src/oracle/txe_oracle_public_context.ts +36 -4
- package/src/oracle/txe_oracle_registry.ts +556 -0
- package/src/oracle/txe_oracle_top_level_context.ts +249 -66
- package/src/{txe_oracle_version.ts → oracle/txe_oracle_version.ts} +4 -4
- package/src/rpc_translator.ts +850 -1065
- package/src/state_machine/archiver.ts +9 -3
- package/src/state_machine/dummy_p2p_client.ts +13 -0
- package/src/state_machine/global_variable_builder.ts +2 -16
- package/src/state_machine/index.ts +50 -34
- package/src/state_machine/mock_epoch_cache.ts +4 -8
- package/src/state_machine/synchronizer.ts +26 -5
- package/src/txe_session.ts +325 -56
- package/src/utils/block_creation.ts +2 -1
- package/src/utils/tx_effect_creation.ts +0 -2
- package/src/utils/txe_artifact_resolver.ts +217 -0
- package/src/utils/txe_public_contract_data_source.ts +6 -4
- package/dest/ContractClassRegistry-KKNT527R.js +0 -3
- package/dest/ContractClassRegistry-KKNT527R.js.map +0 -7
- package/dest/ContractInstanceRegistry-GSKBNL3Q.js +0 -3
- package/dest/ContractInstanceRegistry-GSKBNL3Q.js.map +0 -7
- package/dest/FeeJuice-GUJ5O6GB.js +0 -3
- package/dest/FeeJuice-GUJ5O6GB.js.map +0 -7
- package/dest/MultiCallEntrypoint-ZQPCKVKL.js +0 -3
- package/dest/MultiCallEntrypoint-ZQPCKVKL.js.map +0 -7
- package/dest/SchnorrAccount-QIXE5DXA.js +0 -3
- package/dest/SchnorrAccount-QIXE5DXA.js.map +0 -7
- package/dest/chunk-BJVAAXNA.js +0 -3
- package/dest/chunk-PPJTNNUG.js +0 -265
- package/dest/chunk-PPJTNNUG.js.map +0 -7
- package/dest/txe_oracle_version.d.ts.map +0 -1
- /package/dest/{chunk-BJVAAXNA.js.map → chunk-32UHX7YY.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
import { type NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js/abi';
|
|
2
|
-
import { AztecAddress } from '@aztec/aztec.js/addresses';
|
|
3
|
-
import {
|
|
4
|
-
type ContractInstanceWithAddress,
|
|
5
|
-
getContractInstanceFromInstantiationParams,
|
|
6
|
-
} from '@aztec/aztec.js/contracts';
|
|
7
1
|
import { Fr } from '@aztec/aztec.js/fields';
|
|
8
|
-
import { PublicKeys, deriveKeys } from '@aztec/aztec.js/keys';
|
|
9
2
|
import type { Logger } from '@aztec/foundation/log';
|
|
10
3
|
import { cloneEphemeralStoreFrom } from '@aztec/kv-store/lmdb-v2';
|
|
11
4
|
import type { ProtocolContractName } from '@aztec/protocol-contracts';
|
|
12
5
|
import { ContractStore } from '@aztec/pxe/client/lazy';
|
|
13
|
-
import { computeArtifactHash } from '@aztec/stdlib/contract';
|
|
14
|
-
import type { ContractArtifactWithHash } from '@aztec/stdlib/contract';
|
|
15
6
|
import type { ApiSchemaFor } from '@aztec/stdlib/schemas';
|
|
16
7
|
import { zodFor } from '@aztec/stdlib/schemas';
|
|
17
8
|
|
|
18
|
-
import {
|
|
19
|
-
import { createReadStream } from 'fs';
|
|
20
|
-
import { readFile, readdir } from 'fs/promises';
|
|
21
|
-
import { join, parse } from 'path';
|
|
9
|
+
import { join } from 'path';
|
|
22
10
|
import { z } from 'zod';
|
|
23
11
|
|
|
24
12
|
// Side-effect import: registers the msgpackr Fr extension for the bundled `Fr` class. Must
|
|
@@ -28,64 +16,19 @@ import { type TXEOracleFunctionName, TXESession } from './txe_session.js';
|
|
|
28
16
|
import {
|
|
29
17
|
type ForeignCallArgs,
|
|
30
18
|
ForeignCallArgsSchema,
|
|
31
|
-
type ForeignCallArray,
|
|
32
19
|
type ForeignCallResult,
|
|
33
20
|
ForeignCallResultSchema,
|
|
34
|
-
type ForeignCallSingle,
|
|
35
|
-
addressFromSingle,
|
|
36
|
-
fromArray,
|
|
37
|
-
fromSingle,
|
|
38
|
-
toSingle,
|
|
39
21
|
} from './utils/encoding.js';
|
|
22
|
+
import { TXEArtifactResolver } from './utils/txe_artifact_resolver.js';
|
|
40
23
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
export const TXE_REQUIRED_PROTOCOL_CONTRACTS: ProtocolContractName[] = [
|
|
25
|
+
'ContractClassRegistry',
|
|
26
|
+
'ContractInstanceRegistry',
|
|
27
|
+
'FeeJuice',
|
|
28
|
+
];
|
|
44
29
|
|
|
45
30
|
const sessions = new Map<number, TXESession>();
|
|
46
31
|
|
|
47
|
-
/**
|
|
48
|
-
* Cache + in-flight map pair. Lookup hits the cache, then awaits an in-flight `compute()` if one
|
|
49
|
-
* exists, otherwise starts one and stores it. Guarantees `compute()` runs at most once per `key`
|
|
50
|
-
* across concurrent callers, which matters because `computeArtifactHash` is expensive.
|
|
51
|
-
*/
|
|
52
|
-
class AsyncCache<K, V> {
|
|
53
|
-
private readonly cache = new Map<K, V>();
|
|
54
|
-
private readonly inFlight = new Map<K, Promise<V>>();
|
|
55
|
-
|
|
56
|
-
getOrCompute(key: K, compute: () => Promise<V>): Promise<V> {
|
|
57
|
-
const cached = this.cache.get(key);
|
|
58
|
-
if (cached !== undefined) {
|
|
59
|
-
return Promise.resolve(cached);
|
|
60
|
-
}
|
|
61
|
-
let pending = this.inFlight.get(key);
|
|
62
|
-
if (!pending) {
|
|
63
|
-
pending = (async () => {
|
|
64
|
-
try {
|
|
65
|
-
const value = await compute();
|
|
66
|
-
this.cache.set(key, value);
|
|
67
|
-
return value;
|
|
68
|
-
} finally {
|
|
69
|
-
this.inFlight.delete(key);
|
|
70
|
-
}
|
|
71
|
-
})();
|
|
72
|
-
this.inFlight.set(key, pending);
|
|
73
|
-
}
|
|
74
|
-
return pending;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Full deploys (artifact + computed instance), keyed by the full deploy context (contract +
|
|
79
|
-
// constructor args + publicKeys + salt + deployer). Hits on repeated identical deploys.
|
|
80
|
-
const TXEDeploymentsCache = new AsyncCache<
|
|
81
|
-
string,
|
|
82
|
-
{ artifact: ContractArtifactWithHash; instance: ContractInstanceWithAddress }
|
|
83
|
-
>();
|
|
84
|
-
|
|
85
|
-
// Loaded + hashed contract artifact, keyed by compiled-bytecode hash. Hits across deploys of the
|
|
86
|
-
// same contract when constructor args / salt / deployer differ.
|
|
87
|
-
const TXEArtifactsCache = new AsyncCache<string, ContractArtifactWithHash>();
|
|
88
|
-
|
|
89
32
|
export type TXEForeignCallInput = {
|
|
90
33
|
session_id: number;
|
|
91
34
|
function: TXEOracleFunctionName;
|
|
@@ -94,7 +37,7 @@ export type TXEForeignCallInput = {
|
|
|
94
37
|
inputs: ForeignCallArgs;
|
|
95
38
|
};
|
|
96
39
|
|
|
97
|
-
export const TXEForeignCallInputSchema = zodFor<TXEForeignCallInput>()(
|
|
40
|
+
export const TXEForeignCallInputSchema: z.ZodType<TXEForeignCallInput> = zodFor<TXEForeignCallInput>()(
|
|
98
41
|
z.object({
|
|
99
42
|
// Nargo generates session_id as a u64, which may exceed Number.MAX_SAFE_INTEGER.
|
|
100
43
|
// Zod 4's `.int()` enforces the safe-integer bound, so we drop it here and only require
|
|
@@ -119,15 +62,16 @@ export interface TXEDispatcherOptions {
|
|
|
119
62
|
*/
|
|
120
63
|
contractStoreSourceDir: string;
|
|
121
64
|
/**
|
|
122
|
-
* Class id (hex) of the SchnorrAccount artifact pre-registered in the shared LMDB.
|
|
123
|
-
*
|
|
124
|
-
* recomputing it via `getSchnorrAccountContractArtifact()` + `computeArtifactHash()`.
|
|
65
|
+
* Class id (hex) of the SchnorrAccount artifact pre-registered in the shared LMDB. The
|
|
66
|
+
* {@link TXEArtifactResolver} looks the artifact up from the cloned store via this class id
|
|
67
|
+
* instead of recomputing it via `getSchnorrAccountContractArtifact()` + `computeArtifactHash()`.
|
|
125
68
|
*/
|
|
126
69
|
schnorrClassId: string;
|
|
127
70
|
}
|
|
128
71
|
|
|
129
72
|
export class TXEDispatcher {
|
|
130
73
|
private contractStore!: ContractStore;
|
|
74
|
+
private artifactResolver!: TXEArtifactResolver;
|
|
131
75
|
private readonly contractStoreSourceDir: string;
|
|
132
76
|
private readonly schnorrClassId: Fr;
|
|
133
77
|
|
|
@@ -156,146 +100,25 @@ export class TXEDispatcher {
|
|
|
156
100
|
2,
|
|
157
101
|
);
|
|
158
102
|
this.contractStore = new ContractStore(kvStore);
|
|
103
|
+
this.artifactResolver = new TXEArtifactResolver(this.contractStore, this.schnorrClassId);
|
|
159
104
|
this.logger.debug('Cloned shared protocol-contracts store', { totalMs: Date.now() - t0 });
|
|
160
105
|
}
|
|
161
106
|
|
|
162
|
-
private fastHashFile(path: string): Promise<string> {
|
|
163
|
-
return new Promise(resolve => {
|
|
164
|
-
const fd = createReadStream(path);
|
|
165
|
-
const hash = createHash('sha1');
|
|
166
|
-
hash.setEncoding('hex');
|
|
167
|
-
|
|
168
|
-
fd.on('end', function () {
|
|
169
|
-
hash.end();
|
|
170
|
-
resolve(hash.read() as string);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
fd.pipe(hash);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async #processDeployInputs({ inputs, root_path: rootPath, package_name: packageName }: TXEForeignCallInput) {
|
|
178
|
-
const [contractPath, initializer] = inputs.slice(0, 2).map(input =>
|
|
179
|
-
fromArray(input as ForeignCallArray)
|
|
180
|
-
.map(char => String.fromCharCode(char.toNumber()))
|
|
181
|
-
.join(''),
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
const decodedArgs = fromArray(inputs[3] as ForeignCallArray);
|
|
185
|
-
const secret = fromSingle(inputs[4] as ForeignCallSingle);
|
|
186
|
-
const salt = fromSingle(inputs[5] as ForeignCallSingle);
|
|
187
|
-
const deployer = addressFromSingle(inputs[6] as ForeignCallSingle);
|
|
188
|
-
const publicKeys = secret.equals(Fr.ZERO) ? PublicKeys.default() : (await deriveKeys(secret)).publicKeys;
|
|
189
|
-
const publicKeysHash = await publicKeys.hash();
|
|
190
|
-
|
|
191
|
-
let artifactPath = '';
|
|
192
|
-
const { dir: contractDirectory, base: contractFilename } = parse(contractPath);
|
|
193
|
-
if (contractDirectory) {
|
|
194
|
-
if (contractDirectory.includes('@')) {
|
|
195
|
-
// We're deploying a contract that belongs in a workspace
|
|
196
|
-
// env.deploy("../path/to/workspace/root@packageName/contractName")
|
|
197
|
-
const [workspace, pkg] = contractDirectory.split('@');
|
|
198
|
-
const targetPath = join(rootPath, workspace, '/target');
|
|
199
|
-
this.logger.debug(`Looking for compiled artifact in workspace ${targetPath}`);
|
|
200
|
-
artifactPath = join(targetPath, `${pkg}-${contractFilename}.json`);
|
|
201
|
-
} else {
|
|
202
|
-
// We're deploying a standalone external contract
|
|
203
|
-
// env.deploy("../path/to/contract/root/contractName")
|
|
204
|
-
const targetPath = join(rootPath, contractDirectory, '/target');
|
|
205
|
-
this.logger.debug(`Looking for compiled artifact in ${targetPath}`);
|
|
206
|
-
[artifactPath] = (await readdir(targetPath)).filter(file => file.endsWith(`-${contractFilename}.json`));
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
// We're deploying a local contract
|
|
210
|
-
// env.deploy("contractName")
|
|
211
|
-
artifactPath = join(rootPath, './target', `${packageName}-${contractFilename}.json`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const fileHash = await this.fastHashFile(artifactPath);
|
|
215
|
-
|
|
216
|
-
const cacheKey = `${contractDirectory ?? ''}-${contractFilename}-${initializer}-${decodedArgs
|
|
217
|
-
.map(arg => arg.toString())
|
|
218
|
-
.join('-')}-${publicKeysHash}-${salt}-${deployer}-${fileHash}`;
|
|
219
|
-
|
|
220
|
-
const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
|
|
221
|
-
this.logger.debug(`Loading compiled artifact ${artifactPath}`);
|
|
222
|
-
// Inner cache: artifact load + hash depends only on the compiled bytecode (`fileHash`), so
|
|
223
|
-
// subsequent deploys of the same contract — regardless of constructor args / deployer /
|
|
224
|
-
// salt — reuse the same `ContractArtifactWithHash`.
|
|
225
|
-
const computedArtifact = await TXEArtifactsCache.getOrCompute(fileHash, async () => {
|
|
226
|
-
const artifactJSON = JSON.parse(await readFile(artifactPath, 'utf-8')) as NoirCompiledContract;
|
|
227
|
-
const artifactWithoutHash = loadContractArtifact(artifactJSON);
|
|
228
|
-
return { ...artifactWithoutHash, artifactHash: await computeArtifactHash(artifactWithoutHash) };
|
|
229
|
-
});
|
|
230
|
-
this.logger.debug(
|
|
231
|
-
`Deploy ${computedArtifact.name} with initializer ${initializer}(${decodedArgs}) and public keys hash ${publicKeysHash.toString()}`,
|
|
232
|
-
);
|
|
233
|
-
const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
|
|
234
|
-
constructorArgs: decodedArgs,
|
|
235
|
-
skipArgsDecoding: true,
|
|
236
|
-
salt,
|
|
237
|
-
publicKeys,
|
|
238
|
-
constructorArtifact: initializer ? initializer : undefined,
|
|
239
|
-
deployer,
|
|
240
|
-
});
|
|
241
|
-
return { artifact: computedArtifact, instance: computedInstance };
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
inputs.splice(0, 1, artifact, instance, toSingle(secret));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async #processAddAccountInputs({ inputs }: TXEForeignCallInput) {
|
|
248
|
-
const secret = fromSingle(inputs[0] as ForeignCallSingle);
|
|
249
|
-
|
|
250
|
-
const cacheKey = `SchnorrAccountContract-${secret}`;
|
|
251
|
-
|
|
252
|
-
const { artifact, instance } = await TXEDeploymentsCache.getOrCompute(cacheKey, async () => {
|
|
253
|
-
const [artifactFromStore, classWithPreimage] = await Promise.all([
|
|
254
|
-
this.contractStore.getContractArtifact(this.schnorrClassId),
|
|
255
|
-
this.contractStore.getContractClassWithPreimage(this.schnorrClassId),
|
|
256
|
-
]);
|
|
257
|
-
if (!artifactFromStore || !classWithPreimage) {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`SchnorrAccount not found in shared contract store at class id ${this.schnorrClassId.toString()}`,
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
const computedArtifact = { ...artifactFromStore, artifactHash: classWithPreimage.artifactHash };
|
|
263
|
-
const keys = await deriveKeys(secret);
|
|
264
|
-
const args = [keys.publicKeys.ivpkM.x, keys.publicKeys.ivpkM.y];
|
|
265
|
-
const computedInstance = await getContractInstanceFromInstantiationParams(computedArtifact, {
|
|
266
|
-
constructorArgs: args,
|
|
267
|
-
skipArgsDecoding: true,
|
|
268
|
-
salt: Fr.ONE,
|
|
269
|
-
publicKeys: keys.publicKeys,
|
|
270
|
-
constructorArtifact: 'constructor',
|
|
271
|
-
deployer: AztecAddress.ZERO,
|
|
272
|
-
});
|
|
273
|
-
return { artifact: computedArtifact, instance: computedInstance };
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
inputs.splice(0, 0, artifact, instance);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
107
|
// eslint-disable-next-line camelcase
|
|
280
108
|
async resolve_foreign_call(callData: TXEForeignCallInput): Promise<ForeignCallResult> {
|
|
281
|
-
const {
|
|
109
|
+
const {
|
|
110
|
+
session_id: sessionId,
|
|
111
|
+
function: functionName,
|
|
112
|
+
inputs,
|
|
113
|
+
root_path: rootPath,
|
|
114
|
+
package_name: packageName,
|
|
115
|
+
} = callData;
|
|
282
116
|
this.logger.debug(`Calling ${functionName} on session ${sessionId}`);
|
|
283
117
|
|
|
284
118
|
if (!sessions.has(sessionId)) {
|
|
285
119
|
this.logger.debug(`Creating new session ${sessionId}`);
|
|
286
120
|
await this.warmUp();
|
|
287
|
-
sessions.set(sessionId, await TXESession.init(this.contractStore));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
switch (functionName) {
|
|
291
|
-
case 'aztec_txe_deploy': {
|
|
292
|
-
await this.#processDeployInputs(callData);
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
case 'aztec_txe_addAccount': {
|
|
296
|
-
await this.#processAddAccountInputs(callData);
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
121
|
+
sessions.set(sessionId, await TXESession.init(this.contractStore, this.artifactResolver, rootPath, packageName));
|
|
299
122
|
}
|
|
300
123
|
|
|
301
124
|
return await sessions.get(sessionId)!.processFunction(functionName, inputs);
|
package/src/oracle/interfaces.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { ContractArtifact } from '@aztec/aztec.js/abi';
|
|
2
1
|
import { CompleteAddress } from '@aztec/aztec.js/addresses';
|
|
3
|
-
import type { ContractInstanceWithAddress } from '@aztec/aztec.js/contracts';
|
|
4
2
|
import { TxHash } from '@aztec/aztec.js/tx';
|
|
5
3
|
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
6
4
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
|
+
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
|
+
import type { TaggingSecretStrategy } from '@aztec/pxe/server';
|
|
7
|
+
import type { Option } from '@aztec/pxe/simulator';
|
|
7
8
|
import type { EventSelector, FunctionSelector } from '@aztec/stdlib/abi';
|
|
8
9
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
9
10
|
import type { GasSettings } from '@aztec/stdlib/gas';
|
|
@@ -38,11 +39,15 @@ export interface IAvmExecutionOracle {
|
|
|
38
39
|
nullifierExists(siloedNullifier: Fr): Promise<boolean>;
|
|
39
40
|
storageWrite(slot: Fr, value: Fr): Promise<void>;
|
|
40
41
|
storageRead(slot: Fr, contractAddress: AztecAddress): Promise<Fr>;
|
|
41
|
-
|
|
42
|
+
getContractInstanceDeployer(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
|
|
43
|
+
getContractInstanceClassId(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
|
|
44
|
+
getContractInstanceInitializationHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
|
|
45
|
+
getContractInstanceImmutablesHash(address: AztecAddress): Promise<{ member: Fr; exists: boolean }[]>;
|
|
46
|
+
returndataSize(): Promise<number>;
|
|
42
47
|
returndataCopy(rdOffset: number, copySize: number): Promise<Fr[]>;
|
|
43
|
-
call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<
|
|
44
|
-
staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<
|
|
45
|
-
successCopy(): Promise<
|
|
48
|
+
call(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<void>;
|
|
49
|
+
staticCall(l2Gas: number, daGas: number, address: AztecAddress, argsLength: number, args: Fr[]): Promise<void>;
|
|
50
|
+
successCopy(): Promise<boolean>;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
@@ -56,10 +61,30 @@ export interface ITxeExecutionOracle {
|
|
|
56
61
|
getNextBlockTimestamp(): Promise<UInt64>;
|
|
57
62
|
advanceBlocksBy(blocks: number): Promise<void>;
|
|
58
63
|
advanceTimestampBy(duration: UInt64): void;
|
|
59
|
-
deploy(
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
deploy(
|
|
65
|
+
contractPath: string,
|
|
66
|
+
initializer: string,
|
|
67
|
+
args: Fr[],
|
|
68
|
+
secret: Fr,
|
|
69
|
+
salt: Fr,
|
|
70
|
+
deployer: AztecAddress,
|
|
71
|
+
): Promise<Fr[]>;
|
|
72
|
+
createAccount(secret: Fr, partialAddress: Fr): Promise<CompleteAddress>;
|
|
73
|
+
addAccount(secret: Fr): Promise<CompleteAddress>;
|
|
62
74
|
addAuthWitness(address: AztecAddress, messageHash: Fr): Promise<void>;
|
|
75
|
+
sendL1ToL2Message(content: Fr, secretHash: Fr, sender: EthAddress, recipient: AztecAddress): Promise<Fr>;
|
|
76
|
+
/**
|
|
77
|
+
* Configures the tagging secret strategy the test's simulated wallet resolves for each delivery mode. A `none`
|
|
78
|
+
* clears that mode, so it falls back to the default strategy (or, when both modes end up unset, to no hook at all).
|
|
79
|
+
*/
|
|
80
|
+
setTaggingSecretStrategies(
|
|
81
|
+
unconstrainedStrategy: Option<TaggingSecretStrategy>,
|
|
82
|
+
constrainedStrategy: Option<TaggingSecretStrategy>,
|
|
83
|
+
): void;
|
|
84
|
+
/**
|
|
85
|
+
* Configures whether the test's simulated wallet authorizes every cross-contract utility call target.
|
|
86
|
+
*/
|
|
87
|
+
setAuthorizeAllUtilityCallTargets(authorizeAll: boolean): void;
|
|
63
88
|
getLastBlockTimestamp(): Promise<bigint>;
|
|
64
89
|
getLastTxEffects(): Promise<{
|
|
65
90
|
txHash: TxHash;
|
|
@@ -81,6 +106,7 @@ export interface ITxeExecutionOracle {
|
|
|
81
106
|
gasSettings: GasSettings,
|
|
82
107
|
): Promise<{ returnValues: Fr[]; offchainEffects: Fr[][] }>;
|
|
83
108
|
executeUtilityFunction(
|
|
109
|
+
from: AztecAddress | undefined,
|
|
84
110
|
targetContractAddress: AztecAddress,
|
|
85
111
|
functionSelector: FunctionSelector,
|
|
86
112
|
args: Fr[],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_TAGGING_SECRET_STRATEGY,
|
|
3
|
+
type ResolveTaggingSecretStrategy,
|
|
4
|
+
type TaggingSecretStrategy,
|
|
5
|
+
} from '@aztec/pxe/server';
|
|
6
|
+
import type { AppTaggingSecretKind } from '@aztec/stdlib/logs';
|
|
7
|
+
|
|
8
|
+
/** The tagging secret strategies a TXE test has configured, keyed by delivery mode. Absence means "not configured". */
|
|
9
|
+
export type TXETaggingSecretStrategies = Map<AppTaggingSecretKind, TaggingSecretStrategy>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds the `resolveTaggingSecretStrategy` hook backing the `aztec_txe_setTaggingSecretStrategies` oracle. Returns
|
|
13
|
+
* `undefined` when no strategy is configured, so PXE's own no-hook default path is exercised. When at least one mode
|
|
14
|
+
* is configured, modes without an entry resolve to {@link DEFAULT_TAGGING_SECRET_STRATEGY}, matching what PXE would
|
|
15
|
+
* apply without a hook.
|
|
16
|
+
*/
|
|
17
|
+
export function makeResolveTaggingSecretStrategyHook(
|
|
18
|
+
strategies: TXETaggingSecretStrategies,
|
|
19
|
+
): ResolveTaggingSecretStrategy | undefined {
|
|
20
|
+
if (strategies.size === 0) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return ({ deliveryMode }) => Promise.resolve(strategies.get(deliveryMode) ?? DEFAULT_TAGGING_SECRET_STRATEGY);
|
|
25
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { AztecAddress } from '@aztec/aztec.js/addresses';
|
|
2
|
+
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
|
+
import {
|
|
6
|
+
AZTEC_ADDRESS,
|
|
7
|
+
BIGINT,
|
|
8
|
+
BLOCK_HASH,
|
|
9
|
+
BLOCK_NUMBER,
|
|
10
|
+
BOOL,
|
|
11
|
+
BoundedVec,
|
|
12
|
+
type CompositeMapping,
|
|
13
|
+
DELIVERY_MODE,
|
|
14
|
+
ETH_ADDRESS,
|
|
15
|
+
FIELD,
|
|
16
|
+
FUNCTION_SELECTOR,
|
|
17
|
+
LEAF_INDEX,
|
|
18
|
+
NOTE_SELECTOR,
|
|
19
|
+
Option,
|
|
20
|
+
type OracleRegistryEntry,
|
|
21
|
+
SLOT_NUMBER,
|
|
22
|
+
type StructMapping,
|
|
23
|
+
type TypeMapping,
|
|
24
|
+
U8,
|
|
25
|
+
U32,
|
|
26
|
+
U64,
|
|
27
|
+
U128,
|
|
28
|
+
isArrayMapping,
|
|
29
|
+
isBoundedVecMapping,
|
|
30
|
+
isFixedArrayMapping,
|
|
31
|
+
isFixedBoundedVecMapping,
|
|
32
|
+
isOptionMapping,
|
|
33
|
+
isStructMapping,
|
|
34
|
+
tryFieldWidth,
|
|
35
|
+
} from '@aztec/pxe/simulator';
|
|
36
|
+
import { FunctionSelector, NoteSelector } from '@aztec/stdlib/abi';
|
|
37
|
+
import { BlockHash } from '@aztec/stdlib/block';
|
|
38
|
+
import { AppTaggingSecretKind } from '@aztec/stdlib/logs';
|
|
39
|
+
|
|
40
|
+
import type { OracleTestScenario } from './resolver.js';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Synthesizes fixture scenarios for every oracle in the registry whose parameters and return are built from scalars,
|
|
44
|
+
* arrays/bounded vecs of them, `Option`s of them, or `STRUCT`s over them. The matching Noir serialization tests are
|
|
45
|
+
* auto-generated by `#[generate_oracle_tests]` from the same seed/scenario convention.
|
|
46
|
+
*/
|
|
47
|
+
export function synthesizeDefaultFixtures(
|
|
48
|
+
registry: Record<string, OracleRegistryEntry>,
|
|
49
|
+
): Record<string, OracleTestScenario[]> {
|
|
50
|
+
const synthesized: Record<string, OracleTestScenario[]> = {};
|
|
51
|
+
|
|
52
|
+
for (const [name, entry] of Object.entries(registry)) {
|
|
53
|
+
const scenarios = synthesizeScenarios(entry);
|
|
54
|
+
if (scenarios !== undefined) {
|
|
55
|
+
synthesized[name] = scenarios;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return synthesized;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The scalar test-value implementations, mirroring the Noir synthesizer's `scalars` table in
|
|
64
|
+
* `noir-projects/labs/aztec-nr/.../macros/oracle_testing.nr` exactly.
|
|
65
|
+
*/
|
|
66
|
+
const SCALAR_IMPLS: ScalarImpl[] = [
|
|
67
|
+
scalar(FIELD, seed => new Fr(seed)),
|
|
68
|
+
scalar(U32, seed => seed),
|
|
69
|
+
scalar(BLOCK_NUMBER, seed => BlockNumber(seed)),
|
|
70
|
+
scalar(BIGINT, seed => BigInt(seed)),
|
|
71
|
+
scalar(U64, seed => BigInt(seed)),
|
|
72
|
+
scalar(U128, seed => BigInt(seed)),
|
|
73
|
+
scalar(LEAF_INDEX, seed => seed),
|
|
74
|
+
scalar(U8, seed => seed),
|
|
75
|
+
scalar(BOOL, seed => seed % 2 !== 0),
|
|
76
|
+
scalar(AZTEC_ADDRESS, seed => AztecAddress.fromNumberUnsafe(seed)),
|
|
77
|
+
scalar(ETH_ADDRESS, seed => EthAddress.fromField(new Fr(seed))),
|
|
78
|
+
scalar(FUNCTION_SELECTOR, seed => FunctionSelector.fromField(new Fr(seed))),
|
|
79
|
+
scalar(NOTE_SELECTOR, seed => NoteSelector.fromField(new Fr(seed))),
|
|
80
|
+
scalar(BLOCK_HASH, seed => new BlockHash(new Fr(seed))),
|
|
81
|
+
scalar(SLOT_NUMBER, seed => SlotNumber(seed)),
|
|
82
|
+
// Only two delivery modes are valid for tagging, so the seed alternates between them, matching the Noir impl.
|
|
83
|
+
scalar(DELIVERY_MODE, seed =>
|
|
84
|
+
seed % 2 === 0 ? AppTaggingSecretKind.UNCONSTRAINED : AppTaggingSecretKind.CONSTRAINED,
|
|
85
|
+
),
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The composite test-value implementations, one per combinator, mirroring the Noir synthesizer's `synthesizers` table.
|
|
90
|
+
*/
|
|
91
|
+
const COMPOSITE_IMPLS: CompositeImpl[] = [
|
|
92
|
+
composite(isOptionMapping, (type, seed) => [
|
|
93
|
+
named(Option.some(testValueFor(type.inner, seed)), 'some'),
|
|
94
|
+
named(Option.none(testValueFor(type.inner, seed)), 'none'),
|
|
95
|
+
]),
|
|
96
|
+
composite(isArrayMapping, (type, seed) => [unnamed(collectionData(type.inner, seed, DEFAULT_ARRAY_LENGTH))]),
|
|
97
|
+
composite(isBoundedVecMapping, (type, seed) => [
|
|
98
|
+
unnamed(
|
|
99
|
+
BoundedVec.from({
|
|
100
|
+
data: collectionData(type.inner, seed, DEFAULT_ARRAY_LENGTH),
|
|
101
|
+
maxLength: BOUNDED_VEC_MAX_LENGTH,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
]),
|
|
105
|
+
composite(isStructMapping, (type, seed) => [unnamed(structValue(type, seed))]),
|
|
106
|
+
// A fixed-length array uses its real (signature) length, unlike the generic-length ARRAY which is pinned to
|
|
107
|
+
// DEFAULT_ARRAY_LENGTH.
|
|
108
|
+
composite(isFixedArrayMapping, (type, seed) => [unnamed(collectionData(type.inner, seed, type.length))]),
|
|
109
|
+
// Same for a fixed-capacity bounded vec: real capacity, DEFAULT_ARRAY_LENGTH elements (its value type is a plain
|
|
110
|
+
// element array, unlike the two-slot BOUNDED_VEC), clamped to the capacity so small vecs stay valid.
|
|
111
|
+
composite(isFixedBoundedVecMapping, (type, seed) => [
|
|
112
|
+
unnamed(collectionData(type.inner, seed, Math.min(DEFAULT_ARRAY_LENGTH, type.maxLength))),
|
|
113
|
+
]),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
/** The scalar mappings with a registered test-value impl. */
|
|
117
|
+
export const SCALAR_MAPPINGS: TypeMapping<any>[] = SCALAR_IMPLS.map(impl => impl.mapping);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The base every seed is offset by, applied once where a parameter's position becomes its seed, so generated values
|
|
121
|
+
* stay clear of 0. Must match `SEED_BASE` in the Noir `oracle_testing.nr`.
|
|
122
|
+
*/
|
|
123
|
+
const SEED_BASE = 10;
|
|
124
|
+
|
|
125
|
+
/** Elements per synthesized array and bounded vec. Must match `TEST_COLLECTION_LEN` in the Noir `oracle_testing.nr`. */
|
|
126
|
+
const DEFAULT_ARRAY_LENGTH = 3;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Capacity of synthesized bounded vecs, deliberately larger than the data length so padding is exercised. Must match
|
|
130
|
+
* `TEST_BOUNDED_VEC_CAP` in the Noir `oracle_testing.nr`.
|
|
131
|
+
*/
|
|
132
|
+
const BOUNDED_VEC_MAX_LENGTH = 5;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* One canonical test case for a type: the `value` to put on the wire (or expect back), and the `name` of the
|
|
136
|
+
* serialization shape it exercises (unset for single-scenario types). Mirrors the Noir `Scenario<T>`.
|
|
137
|
+
*/
|
|
138
|
+
interface Scenario {
|
|
139
|
+
value: unknown;
|
|
140
|
+
name?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function unnamed(value: unknown): Scenario {
|
|
144
|
+
return { value };
|
|
145
|
+
}
|
|
146
|
+
function named(value: unknown, name: string): Scenario {
|
|
147
|
+
return { value, name };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** An impl for the single scalar `mapping` it synthesizes for. */
|
|
151
|
+
type ScalarImpl = {
|
|
152
|
+
mapping: TypeMapping<any>;
|
|
153
|
+
scenarios: (type: TypeMapping<any>, seed: number) => Scenario[];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** An impl for a whole family of composite mappings, selected by its `kind` type guard. */
|
|
157
|
+
type CompositeImpl = {
|
|
158
|
+
match: (type: TypeMapping<any>) => boolean;
|
|
159
|
+
scenarios: (type: TypeMapping<any>, seed: number) => Scenario[];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** A scalar impl yielding a single unnamed scenario. */
|
|
163
|
+
function scalar(mapping: TypeMapping<any>, value: (seed: number) => unknown): ScalarImpl {
|
|
164
|
+
return { mapping, scenarios: (_type, seed) => [unnamed(value(seed))] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A composite impl, recursing through the matched mapping's element/inner mapping. */
|
|
168
|
+
function composite<K extends CompositeMapping>(
|
|
169
|
+
guard: (type: TypeMapping<any>) => type is K,
|
|
170
|
+
scenarios: (type: K, seed: number) => Scenario[],
|
|
171
|
+
): CompositeImpl {
|
|
172
|
+
return { match: guard, scenarios: (type, seed) => scenarios(type as K, seed) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Synthesized element values for a collection. */
|
|
176
|
+
function collectionData(element: TypeMapping<any>, seed: number, length: number): unknown[] {
|
|
177
|
+
return Array.from({ length }, (_, i) => testValueFor(element, seed + i));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The synthesized prop bag for a struct: each field's first scenario, seeded at the field's flat wire offset so the
|
|
182
|
+
* values depend on the wire layout rather than on how each side groups its fields. Mirrors the Noir reflected impls.
|
|
183
|
+
*/
|
|
184
|
+
function structValue(type: StructMapping, seed: number): Record<string, unknown> {
|
|
185
|
+
const value: Record<string, unknown> = {};
|
|
186
|
+
let offset = 0;
|
|
187
|
+
for (const field of type.fields) {
|
|
188
|
+
value[field.name] = testValueFor(field.type, seed + offset);
|
|
189
|
+
const width = tryFieldWidth(field.type.shape);
|
|
190
|
+
if (width === undefined) {
|
|
191
|
+
// A variable-width field has no flat offset to seed the next field at, so only this oracle is unsynthesizable.
|
|
192
|
+
throw new UnsynthesizableTypeError(field.type, `struct field '${field.name}' has a variable-width shape`);
|
|
193
|
+
}
|
|
194
|
+
offset += width;
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The [`Scenario`]s for `type` at `seed`, one per serialization *shape*, mirroring the Noir `scenarios_for`
|
|
201
|
+
* dispatcher. Throws if the type has no matching impl.
|
|
202
|
+
*/
|
|
203
|
+
function scenariosForType(type: TypeMapping<any>, seed: number): Scenario[] {
|
|
204
|
+
const impl = SCALAR_IMPLS.find(i => i.mapping === type) ?? COMPOSITE_IMPLS.find(i => i.match(type));
|
|
205
|
+
if (!impl) {
|
|
206
|
+
throw new UnsynthesizableTypeError(type);
|
|
207
|
+
}
|
|
208
|
+
return impl.scenarios(type, seed);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The test value of `type` at `seed`, matching what the Noir synthesizer produces for the same type and seed. Throws
|
|
213
|
+
* for types without a test-value impl.
|
|
214
|
+
*/
|
|
215
|
+
export function testValueFor(type: TypeMapping<any>, seed: number): unknown {
|
|
216
|
+
return scenariosForType(type, seed)[0].value;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
class UnsynthesizableTypeError extends Error {
|
|
220
|
+
constructor(type: TypeMapping<any>, detail?: string) {
|
|
221
|
+
super(detail ?? `No test-value impl for type: ${JSON.stringify(Object.keys(type))}`);
|
|
222
|
+
this.name = 'UnsynthesizableTypeError';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Synthesizes the scenarios for one oracle by zipping every position's [`Scenario`] list to one case per test (a
|
|
228
|
+
* shorter list wraps around via mod), mirroring the Noir macro. Returns `undefined` if any param or return type is
|
|
229
|
+
* unsynthesizable (ephemeral arrays, structs with a variable-width field).
|
|
230
|
+
*/
|
|
231
|
+
function synthesizeScenarios(entry: OracleRegistryEntry): OracleTestScenario[] | undefined {
|
|
232
|
+
try {
|
|
233
|
+
return synthesizeScenariosOrThrow(entry);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
if (e instanceof UnsynthesizableTypeError) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
throw e;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function synthesizeScenariosOrThrow(entry: OracleRegistryEntry): OracleTestScenario[] {
|
|
243
|
+
const params = entry.params.map((param, index) => ({
|
|
244
|
+
name: param.name,
|
|
245
|
+
scenarios: scenariosForType(param.type, SEED_BASE + index),
|
|
246
|
+
}));
|
|
247
|
+
// The return's seed is the slot right after the last parameter, so its value is distinct from every parameter.
|
|
248
|
+
const returnScenarios =
|
|
249
|
+
entry.returnType === undefined ? undefined : scenariosForType(entry.returnType, SEED_BASE + entry.params.length);
|
|
250
|
+
|
|
251
|
+
// Positions with more than one scenario drive the test count; their names are zipped together (joined with `+`) per
|
|
252
|
+
// test, matching the announcements the Noir tests emit and the resolver joins.
|
|
253
|
+
const positions = [...params.map(p => p.scenarios), ...(returnScenarios !== undefined ? [returnScenarios] : [])];
|
|
254
|
+
const namedPositions = positions.filter(s => s.length > 1);
|
|
255
|
+
const testCount = Math.max(1, ...positions.map(s => s.length));
|
|
256
|
+
|
|
257
|
+
const scenarios: OracleTestScenario[] = [];
|
|
258
|
+
for (let index = 0; index < testCount; index++) {
|
|
259
|
+
const inputs: Record<string, unknown> = {};
|
|
260
|
+
for (const { name, scenarios: cases } of params) {
|
|
261
|
+
inputs[name] = cases[index % cases.length].value;
|
|
262
|
+
}
|
|
263
|
+
const output = returnScenarios === undefined ? undefined : returnScenarios[index % returnScenarios.length].value;
|
|
264
|
+
const scenario =
|
|
265
|
+
namedPositions.length === 0
|
|
266
|
+
? undefined
|
|
267
|
+
: namedPositions.map(cases => cases[index % cases.length].name!).join('+');
|
|
268
|
+
scenarios.push({ scenario, inputs, output });
|
|
269
|
+
}
|
|
270
|
+
return scenarios;
|
|
271
|
+
}
|