@aztec/txe 0.0.1-commit.f2ce05ee → 0.0.1-commit.f5a9928
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-56AMRLSQ.js +3 -0
- package/dest/AuthRegistry-56AMRLSQ.js.map +7 -0
- package/dest/ContractClassRegistry-CRN27URB.js +3 -0
- package/dest/ContractClassRegistry-CRN27URB.js.map +7 -0
- package/dest/ContractInstanceRegistry-GRGL26IC.js +3 -0
- package/dest/ContractInstanceRegistry-GRGL26IC.js.map +7 -0
- package/dest/FeeJuice-J23RVHLW.js +3 -0
- package/dest/FeeJuice-J23RVHLW.js.map +7 -0
- package/dest/HandshakeRegistry-5.0.1-PEFCLEGP.js +3 -0
- package/dest/HandshakeRegistry-5.0.1-PEFCLEGP.js.map +7 -0
- package/dest/HandshakeRegistry-GNFACTEE.js +3 -0
- package/dest/HandshakeRegistry-GNFACTEE.js.map +7 -0
- package/dest/MultiCallEntrypoint-MIDICLQB.js +3 -0
- package/dest/MultiCallEntrypoint-MIDICLQB.js.map +7 -0
- package/dest/SchnorrAccount-ZRENHSMV.js +3 -0
- package/dest/SchnorrAccount-ZRENHSMV.js.map +7 -0
- package/dest/SchnorrInitializerlessAccount-L56LOFSV.js +3 -0
- package/dest/SchnorrInitializerlessAccount-L56LOFSV.js.map +7 -0
- package/dest/bin/check_txe_oracle_version.d.ts +2 -0
- package/dest/bin/check_txe_oracle_version.d.ts.map +1 -0
- package/dest/bin/check_txe_oracle_version.js +53 -0
- package/dest/bin/index.js +3 -30
- package/dest/bin/index.js.map +7 -0
- 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-BJVAAXNA.js +3 -0
- package/dest/chunk-BJVAAXNA.js.map +7 -0
- package/dest/chunk-C2WBKKGS.js +266 -0
- package/dest/chunk-C2WBKKGS.js.map +7 -0
- package/dest/chunk-OFS2CI6U.js +304 -0
- package/dest/chunk-OFS2CI6U.js.map +7 -0
- package/dest/constants.d.ts +5 -1
- package/dest/constants.d.ts.map +1 -1
- package/dest/constants.js +9 -1
- package/dest/dispatcher_pool.d.ts +67 -0
- package/dest/dispatcher_pool.d.ts.map +1 -0
- package/dest/dispatcher_pool.js +286 -0
- package/dest/index.d.ts +51 -7
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +73 -157
- package/dest/metafile.json +40416 -0
- package/dest/msgpackr_fr_extension.d.ts +2 -0
- package/dest/msgpackr_fr_extension.d.ts.map +1 -0
- package/dest/msgpackr_fr_extension.js +21 -0
- package/dest/oracle/interfaces.d.ts +69 -30
- 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 +38 -14
- package/dest/oracle/txe_oracle_public_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_public_context.js +61 -13
- 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 +69 -29
- package/dest/oracle/txe_oracle_top_level_context.d.ts.map +1 -1
- package/dest/oracle/txe_oracle_top_level_context.js +332 -109
- package/dest/oracle/txe_oracle_version.d.ts +17 -0
- package/dest/oracle/txe_oracle_version.d.ts.map +1 -0
- package/dest/oracle/txe_oracle_version.js +14 -0
- package/dest/oracle/txe_private_execution_oracle.d.ts +17 -0
- package/dest/oracle/txe_private_execution_oracle.d.ts.map +1 -0
- package/dest/oracle/txe_private_execution_oracle.js +15 -0
- package/dest/rpc_server.d.ts +14 -0
- package/dest/rpc_server.d.ts.map +1 -0
- package/dest/rpc_server.js +78 -0
- package/dest/rpc_translator.d.ts +115 -228
- package/dest/rpc_translator.d.ts.map +1 -1
- package/dest/rpc_translator.js +901 -624
- package/dest/server.bundle.js +3 -0
- package/dest/server.bundle.js.map +7 -0
- package/dest/state_machine/archiver.d.ts +6 -5
- package/dest/state_machine/archiver.d.ts.map +1 -1
- package/dest/state_machine/archiver.js +28 -20
- package/dest/state_machine/dummy_p2p_client.d.ts +28 -15
- package/dest/state_machine/dummy_p2p_client.d.ts.map +1 -1
- package/dest/state_machine/dummy_p2p_client.js +54 -18
- package/dest/state_machine/global_variable_builder.d.ts +10 -6
- package/dest/state_machine/global_variable_builder.d.ts.map +1 -1
- package/dest/state_machine/global_variable_builder.js +8 -5
- package/dest/state_machine/index.d.ts +11 -5
- package/dest/state_machine/index.d.ts.map +1 -1
- package/dest/state_machine/index.js +50 -10
- package/dest/state_machine/mock_epoch_cache.d.ts +16 -3
- package/dest/state_machine/mock_epoch_cache.d.ts.map +1 -1
- package/dest/state_machine/mock_epoch_cache.js +29 -2
- package/dest/state_machine/synchronizer.d.ts +9 -6
- package/dest/state_machine/synchronizer.d.ts.map +1 -1
- package/dest/state_machine/synchronizer.js +10 -6
- package/dest/txe_session.d.ts +90 -16
- package/dest/txe_session.d.ts.map +1 -1
- package/dest/txe_session.js +365 -55
- 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 +192 -0
- package/dest/utils/encoding.d.ts.map +1 -0
- package/dest/{util → utils}/encoding.js +7 -2
- package/dest/{util → utils}/expected_failure_error.d.ts +1 -1
- package/dest/utils/expected_failure_error.d.ts.map +1 -0
- 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/{util → utils}/txe_account_store.d.ts +1 -1
- package/dest/utils/txe_account_store.d.ts.map +1 -0
- 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 +20 -0
- package/dest/utils/txe_public_contract_data_source.d.ts.map +1 -0
- package/dest/utils/txe_public_contract_data_source.js +51 -0
- package/dest/worker.bundle.js +3 -0
- package/dest/worker.bundle.js.map +7 -0
- package/dest/worker.d.ts +2 -0
- package/dest/worker.d.ts.map +1 -0
- package/dest/worker.js +92 -0
- package/package.json +38 -21
- package/src/bin/check_txe_oracle_version.ts +67 -0
- package/src/bin/index.ts +11 -2
- package/src/bin/oracle_test_server.ts +51 -0
- package/src/constants.ts +11 -1
- package/src/dispatcher_pool.ts +317 -0
- package/src/index.ts +99 -190
- package/src/msgpackr_fr_extension.ts +23 -0
- package/src/oracle/interfaces.ts +75 -35
- 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 +74 -12
- package/src/oracle/txe_oracle_registry.ts +556 -0
- package/src/oracle/txe_oracle_top_level_context.ts +421 -160
- package/src/oracle/txe_oracle_version.ts +17 -0
- package/src/oracle/txe_private_execution_oracle.ts +30 -0
- package/src/rpc_server.ts +87 -0
- package/src/rpc_translator.ts +1116 -1006
- package/src/state_machine/archiver.ts +41 -20
- package/src/state_machine/dummy_p2p_client.ts +80 -30
- package/src/state_machine/global_variable_builder.ts +12 -11
- package/src/state_machine/index.ts +63 -27
- package/src/state_machine/mock_epoch_cache.ts +38 -3
- package/src/state_machine/synchronizer.ts +13 -7
- package/src/txe_session.ts +601 -115
- package/src/utils/block_creation.ts +2 -1
- package/src/{util → utils}/encoding.ts +8 -2
- 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 +60 -0
- package/src/worker.ts +98 -0
- package/dest/util/encoding.d.ts +0 -720
- package/dest/util/encoding.d.ts.map +0 -1
- package/dest/util/expected_failure_error.d.ts.map +0 -1
- package/dest/util/txe_account_store.d.ts.map +0 -1
- package/dest/util/txe_contract_store.d.ts +0 -12
- package/dest/util/txe_contract_store.d.ts.map +0 -1
- package/dest/util/txe_contract_store.js +0 -22
- package/dest/util/txe_public_contract_data_source.d.ts +0 -21
- package/dest/util/txe_public_contract_data_source.d.ts.map +0 -1
- package/dest/util/txe_public_contract_data_source.js +0 -67
- package/src/util/txe_contract_store.ts +0 -36
- package/src/util/txe_public_contract_data_source.ts +0 -86
- /package/dest/{util → utils}/expected_failure_error.js +0 -0
- /package/dest/{util → utils}/txe_account_store.js +0 -0
- /package/src/{util → utils}/expected_failure_error.ts +0 -0
- /package/src/{util → utils}/txe_account_store.ts +0 -0
|
@@ -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/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
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import { createSafeJsonRpcServer } from '@aztec/foundation/json-rpc/server';
|
|
3
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
4
|
+
import type { ApiSchemaFor } from '@aztec/foundation/schemas';
|
|
5
|
+
import { zodFor } from '@aztec/stdlib/schemas';
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
import { ForeignCallArgsSchema, type ForeignCallResult, ForeignCallResultSchema } from '../../utils/encoding.js';
|
|
10
|
+
import { TXE_ORACLE_REGISTRY } from '../txe_oracle_registry.js';
|
|
11
|
+
import { type OracleTestCallInput, OracleTestResolver } from './resolver.js';
|
|
12
|
+
|
|
13
|
+
const OracleTestCallInputSchema = zodFor<OracleTestCallInput>()(
|
|
14
|
+
z.object({
|
|
15
|
+
session_id: z.number().nonnegative(),
|
|
16
|
+
function: z.string(),
|
|
17
|
+
root_path: z.string(),
|
|
18
|
+
package_name: z.string(),
|
|
19
|
+
inputs: ForeignCallArgsSchema,
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/** Narrowed interface exposing only the RPC method. */
|
|
24
|
+
interface OracleTestRpcHandler {
|
|
25
|
+
resolve_foreign_call(callData: OracleTestCallInput): Promise<ForeignCallResult>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const OracleTestRpcHandlerSchema: ApiSchemaFor<OracleTestRpcHandler> = {
|
|
29
|
+
resolve_foreign_call: z.function({
|
|
30
|
+
input: z.tuple([OracleTestCallInputSchema]),
|
|
31
|
+
output: ForeignCallResultSchema,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Creates an RPC server backed by the oracle test resolver. */
|
|
36
|
+
export function createOracleTestRpcServer(logger: Logger) {
|
|
37
|
+
const resolver = OracleTestResolver.fromRegistry(TXE_ORACLE_REGISTRY, logger);
|
|
38
|
+
const server = createSafeJsonRpcServer<OracleTestRpcHandler>(resolver, OracleTestRpcHandlerSchema, {
|
|
39
|
+
http200OnError: true,
|
|
40
|
+
});
|
|
41
|
+
return { server, resolver };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { OracleTestResolver } from './resolver.js';
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { withoutHexPrefix } from '@aztec/foundation/string';
|
|
5
|
+
import { BOUNDED_VEC, BoundedVec, Option, type OracleRegistryEntry, U8, makeEntry } from '@aztec/pxe/simulator';
|
|
6
|
+
|
|
7
|
+
import type { ForeignCallArgs, ForeignCallResult } from '../../utils/encoding.js';
|
|
8
|
+
import { toInputSlots } from '../txe_oracle_registry.js';
|
|
9
|
+
import { synthesizeDefaultFixtures } from './default_fixtures.js';
|
|
10
|
+
|
|
11
|
+
/** Name of the meta-oracle that Noir tests call to announce the next call's scenario by name. */
|
|
12
|
+
const SET_SCENARIO_ORACLE = 'aztec_oracle_test_set_scenario';
|
|
13
|
+
export const SET_SCENARIO_ENTRY = makeEntry({ params: [{ name: 'name', type: BOUNDED_VEC(U8) }] });
|
|
14
|
+
|
|
15
|
+
export type OracleTestCallInput = {
|
|
16
|
+
session_id: number;
|
|
17
|
+
function: string;
|
|
18
|
+
root_path: string;
|
|
19
|
+
package_name: string;
|
|
20
|
+
inputs: ForeignCallArgs;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
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
|
|
36
|
+
* returns the corresponding fixture output.
|
|
37
|
+
*
|
|
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.
|
|
40
|
+
*/
|
|
41
|
+
export class OracleTestResolver {
|
|
42
|
+
private readonly calledOracles = new Set<string>();
|
|
43
|
+
private readonly pendingScenario = new Map<number, string>();
|
|
44
|
+
private readonly logger: Logger;
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly registry: Record<string, OracleRegistryEntry>,
|
|
48
|
+
private readonly fixtures: Record<string, OracleTestScenario[]>,
|
|
49
|
+
logger?: Logger,
|
|
50
|
+
) {
|
|
51
|
+
this.logger = logger ?? createLogger('txe:test-resolver');
|
|
52
|
+
}
|
|
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
|
+
|
|
62
|
+
// eslint-disable-next-line require-await
|
|
63
|
+
async resolve_foreign_call(callData: OracleTestCallInput): Promise<ForeignCallResult> {
|
|
64
|
+
const oracleName = callData.function;
|
|
65
|
+
this.logger.debug('Resolving oracle', { oracleName });
|
|
66
|
+
|
|
67
|
+
if (oracleName === SET_SCENARIO_ORACLE) {
|
|
68
|
+
return this.#handleSetScenario(callData);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!(oracleName in this.registry)) {
|
|
72
|
+
throw new Error(`Oracle '${oracleName}' not found in registry`);
|
|
73
|
+
}
|
|
74
|
+
const entry = this.registry[oracleName];
|
|
75
|
+
|
|
76
|
+
const scenarios = this.fixtures[oracleName];
|
|
77
|
+
if (!scenarios || scenarios.length === 0) {
|
|
78
|
+
throw new Error(`No fixture defined for oracle '${oracleName}'`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const match = this.#selectScenario(callData.session_id, oracleName, scenarios);
|
|
82
|
+
this.#verifyInputs(callData.inputs, entry, match, oracleName);
|
|
83
|
+
this.calledOracles.add(oracleName);
|
|
84
|
+
|
|
85
|
+
this.logger.debug('Verified scenario for oracle', { oracleName });
|
|
86
|
+
|
|
87
|
+
const outputSlots = entry.serializeReturn(match.output);
|
|
88
|
+
return {
|
|
89
|
+
values: outputSlots.map(slot => (Array.isArray(slot) ? slot.map(withoutHexPrefix) : withoutHexPrefix(slot))),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Returns oracles that have a fixture defined but were never called during testing. */
|
|
94
|
+
getUncalledFixtures(): string[] {
|
|
95
|
+
return Object.keys(this.fixtures).filter(name => !this.calledOracles.has(name));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Returns oracles in the registry that have no fixture defined at all. */
|
|
99
|
+
getMissingFixtures(): string[] {
|
|
100
|
+
return Object.keys(this.registry).filter(name => !(name in this.fixtures));
|
|
101
|
+
}
|
|
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
|
+
*/
|
|
108
|
+
#handleSetScenario(callData: { session_id: number; inputs: ForeignCallArgs }): ForeignCallResult {
|
|
109
|
+
const name = decodeScenarioName(callData.inputs);
|
|
110
|
+
const existing = this.pendingScenario.get(callData.session_id);
|
|
111
|
+
const scenario = existing !== undefined ? `${existing}+${name}` : name;
|
|
112
|
+
|
|
113
|
+
this.logger.debug('Setting scenario for next oracle call', { sessionId: callData.session_id, scenario });
|
|
114
|
+
this.pendingScenario.set(callData.session_id, scenario);
|
|
115
|
+
|
|
116
|
+
return { values: [] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Selects the fixture by the announced scenario name (for multi-scenario oracles) or returns the single fixture. */
|
|
120
|
+
#selectScenario(sessionId: number, oracleName: string, fixtures: OracleTestScenario[]): OracleTestScenario {
|
|
121
|
+
const announced = this.pendingScenario.get(sessionId);
|
|
122
|
+
|
|
123
|
+
if (announced !== undefined) {
|
|
124
|
+
this.pendingScenario.delete(sessionId);
|
|
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}`);
|
|
129
|
+
}
|
|
130
|
+
return match;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (fixtures.length === 1) {
|
|
134
|
+
return fixtures[0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new Error(`Oracle '${oracleName}' has ${fixtures.length} scenarios but none was announced before the call.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Deserializes actual inputs and verifies they match the scenario's expected inputs. */
|
|
141
|
+
#verifyInputs(
|
|
142
|
+
rawInputs: ForeignCallArgs,
|
|
143
|
+
entry: OracleRegistryEntry,
|
|
144
|
+
fixture: OracleTestScenario,
|
|
145
|
+
oracleName: string,
|
|
146
|
+
): void {
|
|
147
|
+
const expectedInputs = fixture.inputs;
|
|
148
|
+
if (Object.keys(expectedInputs).length === 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const scenarioLabel = fixture.scenario ? ` (scenario '${fixture.scenario}')` : '';
|
|
153
|
+
const named = entry.deserializeParams(toInputSlots(rawInputs));
|
|
154
|
+
|
|
155
|
+
for (const param of named) {
|
|
156
|
+
if (!(param.name in expectedInputs)) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Unexpected param '${param.name}' for oracle '${oracleName}'. ` +
|
|
159
|
+
`Expected: ${Object.keys(expectedInputs).join(', ')}`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (!valuesEqual(param.value, expectedInputs[param.name])) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Input mismatch for oracle '${oracleName}'${scenarioLabel}: ` +
|
|
165
|
+
`param '${param.name}' expected ${String(expectedInputs[param.name])} ` +
|
|
166
|
+
`but got ${String(param.value)}. ${versionBumpHint(oracleName)}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
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
|
+
|
|
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
|
+
}
|
|
193
|
+
if (Array.isArray(actual) && Array.isArray(expected)) {
|
|
194
|
+
return actual.length === expected.length && actual.every((v, i) => valuesEqual(v, expected[i]));
|
|
195
|
+
}
|
|
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.';
|
|
245
|
+
}
|