@seljs/runtime 1.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.
- package/CHANGELOG.md +8 -0
- package/LICENSE.md +190 -0
- package/README.md +5 -0
- package/dist/analysis/call-collector.d.ts +20 -0
- package/dist/analysis/call-collector.d.ts.map +1 -0
- package/dist/analysis/call-collector.js +272 -0
- package/dist/analysis/dependency-analyzer.d.ts +14 -0
- package/dist/analysis/dependency-analyzer.d.ts.map +1 -0
- package/dist/analysis/dependency-analyzer.js +76 -0
- package/dist/analysis/index.d.ts +2 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +1 -0
- package/dist/analysis/round-planner.d.ts +32 -0
- package/dist/analysis/round-planner.d.ts.map +1 -0
- package/dist/analysis/round-planner.js +69 -0
- package/dist/analysis/types.d.ts +113 -0
- package/dist/analysis/types.d.ts.map +1 -0
- package/dist/analysis/types.js +1 -0
- package/dist/debug.d.ts +13 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +12 -0
- package/dist/environment/context.d.ts +3 -0
- package/dist/environment/context.d.ts.map +1 -0
- package/dist/environment/context.js +8 -0
- package/dist/environment/contract-caller.d.ts +25 -0
- package/dist/environment/contract-caller.d.ts.map +1 -0
- package/dist/environment/contract-caller.js +85 -0
- package/dist/environment/environment.d.ts +81 -0
- package/dist/environment/environment.d.ts.map +1 -0
- package/dist/environment/environment.js +279 -0
- package/dist/environment/error-wrapper.d.ts +11 -0
- package/dist/environment/error-wrapper.d.ts.map +1 -0
- package/dist/environment/error-wrapper.js +33 -0
- package/dist/environment/index.d.ts +3 -0
- package/dist/environment/index.d.ts.map +1 -0
- package/dist/environment/index.js +2 -0
- package/dist/environment/replay-cache.d.ts +23 -0
- package/dist/environment/replay-cache.d.ts.map +1 -0
- package/dist/environment/replay-cache.js +51 -0
- package/dist/environment/types.d.ts +57 -0
- package/dist/environment/types.d.ts.map +1 -0
- package/dist/environment/types.js +1 -0
- package/dist/errors/errors.d.ts +63 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +63 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/execution/index.d.ts +2 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +1 -0
- package/dist/execution/multi-round-executor.d.ts +17 -0
- package/dist/execution/multi-round-executor.d.ts.map +1 -0
- package/dist/execution/multi-round-executor.js +47 -0
- package/dist/execution/multicall-batcher.d.ts +14 -0
- package/dist/execution/multicall-batcher.d.ts.map +1 -0
- package/dist/execution/multicall-batcher.js +53 -0
- package/dist/execution/multicall.d.ts +42 -0
- package/dist/execution/multicall.d.ts.map +1 -0
- package/dist/execution/multicall.js +29 -0
- package/dist/execution/result-cache.d.ts +47 -0
- package/dist/execution/result-cache.d.ts.map +1 -0
- package/dist/execution/result-cache.js +65 -0
- package/dist/execution/round-executor.d.ts +18 -0
- package/dist/execution/round-executor.d.ts.map +1 -0
- package/dist/execution/round-executor.js +95 -0
- package/dist/execution/types.d.ts +55 -0
- package/dist/execution/types.d.ts.map +1 -0
- package/dist/execution/types.js +1 -0
- package/dist/factory.d.ts +3 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +2 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/package.json +76 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { createRuntimeEnvironment } from "@seljs/checker";
|
|
2
|
+
import { normalizeContextForEvaluation } from "./context.js";
|
|
3
|
+
import { CallCounter, buildContractInfoMap, executeContractCall, resolveExecutionBlockNumber, } from "./contract-caller.js";
|
|
4
|
+
import { wrapError } from "./error-wrapper.js";
|
|
5
|
+
import { buildExecutionReplayCache } from "./replay-cache.js";
|
|
6
|
+
import { collectCalls } from "../analysis/call-collector.js";
|
|
7
|
+
import { analyzeDependencies } from "../analysis/dependency-analyzer.js";
|
|
8
|
+
import { planRounds } from "../analysis/round-planner.js";
|
|
9
|
+
import { createLogger } from "../debug.js";
|
|
10
|
+
import { MulticallBatchError, SELContractError, SELTypeError, } from "../errors/index.js";
|
|
11
|
+
import { MultiRoundExecutor } from "../execution/multi-round-executor.js";
|
|
12
|
+
/**
|
|
13
|
+
* Core SEL runtime that bridges CEL expression evaluation with EVM contract reads.
|
|
14
|
+
*
|
|
15
|
+
* Manages a CEL runtime extended with Solidity primitive types (uint256, address, bool, etc.),
|
|
16
|
+
* registered smart contracts whose view functions become callable in expressions, and typed
|
|
17
|
+
* variables that are supplied at evaluation time. Contract calls are automatically batched
|
|
18
|
+
* via multicall3 and executed in dependency-ordered rounds.
|
|
19
|
+
*/
|
|
20
|
+
const debug = createLogger("environment");
|
|
21
|
+
export class SELRuntime {
|
|
22
|
+
env;
|
|
23
|
+
client;
|
|
24
|
+
schema;
|
|
25
|
+
variableTypes = new Map();
|
|
26
|
+
maxRounds;
|
|
27
|
+
maxCalls;
|
|
28
|
+
multicallOptions;
|
|
29
|
+
contractBindings;
|
|
30
|
+
codecRegistry;
|
|
31
|
+
/**
|
|
32
|
+
* Per-evaluation mutable state.
|
|
33
|
+
*
|
|
34
|
+
* These fields are set before CEL evaluation and cleared in `finally`.
|
|
35
|
+
* They exist because the contract call handler closure (created in the
|
|
36
|
+
* constructor) needs access to per-evaluation context, but CEL's
|
|
37
|
+
* `Environment.parse()` returns a function that doesn't accept extra
|
|
38
|
+
* parameters beyond the variable bindings.
|
|
39
|
+
*
|
|
40
|
+
* Thread safety: The `mutex` field serializes concurrent `evaluate()`
|
|
41
|
+
* calls, ensuring these fields are never accessed concurrently.
|
|
42
|
+
*
|
|
43
|
+
* TODO: Consider refactoring to pass an EvaluationContext object through
|
|
44
|
+
* the handler closure instead of mutating instance state. This would
|
|
45
|
+
* eliminate the need for the mutex and make the code more testable.
|
|
46
|
+
* See .omc/drafts/context-object-spike.md for preliminary analysis.
|
|
47
|
+
*/
|
|
48
|
+
currentCache;
|
|
49
|
+
currentClient;
|
|
50
|
+
currentCallCounter;
|
|
51
|
+
/** Mutex to serialize concurrent evaluate() calls (protects current* fields) */
|
|
52
|
+
mutex = Promise.resolve();
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new immutable SEL runtime with Solidity types pre-registered.
|
|
55
|
+
*
|
|
56
|
+
* Initializes the underlying CEL runtime, registers all Solidity primitive types,
|
|
57
|
+
* and processes any contracts and variables from the schema. After construction,
|
|
58
|
+
* the environment is fully configured and immutable.
|
|
59
|
+
*
|
|
60
|
+
* @param config - Configuration containing the SEL schema, optional viem client,
|
|
61
|
+
* multicall settings, and CEL/execution limits (maxRounds defaults to 10, maxCalls to 100)
|
|
62
|
+
*/
|
|
63
|
+
constructor(config) {
|
|
64
|
+
const limits = config.limits;
|
|
65
|
+
const celLimits = limits
|
|
66
|
+
? {
|
|
67
|
+
maxAstNodes: limits.maxAstNodes,
|
|
68
|
+
maxDepth: limits.maxDepth,
|
|
69
|
+
maxListElements: limits.maxListElements,
|
|
70
|
+
maxMapEntries: limits.maxMapEntries,
|
|
71
|
+
maxCallArguments: limits.maxCallArguments,
|
|
72
|
+
}
|
|
73
|
+
: undefined;
|
|
74
|
+
this.maxRounds = limits?.maxRounds ?? 10;
|
|
75
|
+
this.maxCalls = limits?.maxCalls ?? 100;
|
|
76
|
+
this.multicallOptions = config.multicall;
|
|
77
|
+
this.client = config.client;
|
|
78
|
+
this.schema = config.schema;
|
|
79
|
+
// Build handler closure that captures `this` for contract execution
|
|
80
|
+
const handler = (contractName, methodName, args) => {
|
|
81
|
+
const contract = this.findContract(contractName);
|
|
82
|
+
if (!contract) {
|
|
83
|
+
throw new SELContractError(`Unknown contract "${contractName}"`, {
|
|
84
|
+
contractName,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const method = contract.methods.find((m) => m.name === methodName);
|
|
88
|
+
if (!method) {
|
|
89
|
+
throw new SELContractError(`Unknown method "${contractName}.${methodName}"`, { contractName, methodName });
|
|
90
|
+
}
|
|
91
|
+
return executeContractCall(contract, method, args, {
|
|
92
|
+
executionCache: this.currentCache,
|
|
93
|
+
client: this.currentClient ?? this.client,
|
|
94
|
+
codecRegistry: this.codecRegistry,
|
|
95
|
+
callCounter: this.currentCallCounter,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
// Create environment via unified hydration
|
|
99
|
+
const { env, contractBindings, codecRegistry } = createRuntimeEnvironment(this.schema, handler, { limits: celLimits, unlistedVariablesAreDyn: true });
|
|
100
|
+
this.env = env;
|
|
101
|
+
this.contractBindings = contractBindings;
|
|
102
|
+
this.codecRegistry = codecRegistry;
|
|
103
|
+
// Populate variableTypes from schema for normalizeContextForEvaluation
|
|
104
|
+
for (const v of this.schema.variables) {
|
|
105
|
+
this.variableTypes.set(v.name, v.type);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Type-checks an expression against registered variables and contract methods.
|
|
110
|
+
*
|
|
111
|
+
* @param expression - A CEL expression string to type-check
|
|
112
|
+
* @returns The type-check result containing validity, inferred type, and any errors
|
|
113
|
+
* @throws {@link SELTypeError} If the expression contains unrecoverable type errors
|
|
114
|
+
*/
|
|
115
|
+
check(expression) {
|
|
116
|
+
try {
|
|
117
|
+
return this.env.check(expression);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw wrapError(error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Evaluates a SEL expression, executing any embedded contract calls on-chain.
|
|
125
|
+
*
|
|
126
|
+
* When contract calls are present, the full pipeline runs:
|
|
127
|
+
* 1. Parse the expression and collect contract calls from the AST
|
|
128
|
+
* 2. Type-check against registered variables and contract methods
|
|
129
|
+
* 3. Build a dependency graph and plan execution rounds
|
|
130
|
+
* 4. Execute contract calls via multicall3 batching at a pinned block number
|
|
131
|
+
* 5. Evaluate the CEL expression with resolved contract results and context values
|
|
132
|
+
* 6. Unwrap Solidity wrapper types back to native JS values (BigInt, string, etc.)
|
|
133
|
+
*
|
|
134
|
+
* @param expression A CEL expression string
|
|
135
|
+
* @param context Variable bindings for evaluation
|
|
136
|
+
* @param options Optional client override
|
|
137
|
+
* @returns An {@link EvaluateResult} containing the value and optional execution metadata
|
|
138
|
+
* @throws {@link SELParseError} If the expression has invalid syntax
|
|
139
|
+
* @throws {@link SELTypeError} If type-checking fails
|
|
140
|
+
* @throws {@link SELContractError} If a contract call fails or no client is available
|
|
141
|
+
* @throws {@link SELEvaluationError} If CEL evaluation fails
|
|
142
|
+
*/
|
|
143
|
+
async evaluate(expression, context, options) {
|
|
144
|
+
let release;
|
|
145
|
+
const prev = this.mutex;
|
|
146
|
+
this.mutex = new Promise((resolve) => {
|
|
147
|
+
release = resolve;
|
|
148
|
+
});
|
|
149
|
+
await prev;
|
|
150
|
+
try {
|
|
151
|
+
return await this.doEvaluate(expression, context, options);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
155
|
+
release();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
planExecution(expression, evaluationContext) {
|
|
159
|
+
const parseResult = this.env.parse(expression);
|
|
160
|
+
const collectedCalls = collectCalls(parseResult.ast, {
|
|
161
|
+
get: (name) => this.findContract(name),
|
|
162
|
+
});
|
|
163
|
+
debug("evaluate: collected %d calls", collectedCalls.length);
|
|
164
|
+
const normalizedContext = Object.keys(evaluationContext).length
|
|
165
|
+
? normalizeContextForEvaluation(evaluationContext, this.variableTypes, this.codecRegistry)
|
|
166
|
+
: undefined;
|
|
167
|
+
const executionVariables = Object.keys(evaluationContext).length
|
|
168
|
+
? evaluationContext
|
|
169
|
+
: {};
|
|
170
|
+
const typeCheckResult = parseResult.check();
|
|
171
|
+
if (!typeCheckResult.valid) {
|
|
172
|
+
throw new SELTypeError(typeCheckResult.error?.message ?? "Type check failed", { cause: typeCheckResult.error });
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
parseResult,
|
|
176
|
+
collectedCalls,
|
|
177
|
+
normalizedContext,
|
|
178
|
+
executionVariables,
|
|
179
|
+
typeCheckResult,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async executeContractCalls(collectedCalls, executionVariables, resolvedClient) {
|
|
183
|
+
debug("evaluate: calls to execute — %o", collectedCalls.map((c) => `${c.contract}.${c.method}`));
|
|
184
|
+
const graph = analyzeDependencies(collectedCalls);
|
|
185
|
+
const plan = planRounds(graph, {
|
|
186
|
+
maxRounds: this.maxRounds,
|
|
187
|
+
maxCalls: this.maxCalls,
|
|
188
|
+
});
|
|
189
|
+
const executor = new MultiRoundExecutor(resolvedClient, buildContractInfoMap(this.schema.contracts), this.multicallOptions);
|
|
190
|
+
let executionResult;
|
|
191
|
+
try {
|
|
192
|
+
executionResult = await executor.execute(plan, executionVariables, await resolveExecutionBlockNumber(resolvedClient));
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
let failedCall = collectedCalls[0];
|
|
196
|
+
if (error instanceof MulticallBatchError) {
|
|
197
|
+
if (error.contractName && error.methodName) {
|
|
198
|
+
const contractName = error.contractName;
|
|
199
|
+
const methodName = error.methodName;
|
|
200
|
+
const base = failedCall ?? {
|
|
201
|
+
id: "",
|
|
202
|
+
contract: contractName,
|
|
203
|
+
method: methodName,
|
|
204
|
+
args: [],
|
|
205
|
+
astNode: undefined,
|
|
206
|
+
};
|
|
207
|
+
failedCall = {
|
|
208
|
+
...base,
|
|
209
|
+
contract: contractName,
|
|
210
|
+
method: methodName,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
else if (typeof error.failedCallIndex === "number") {
|
|
214
|
+
failedCall = collectedCalls[error.failedCallIndex] ?? failedCall;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (!failedCall) {
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
throw new SELContractError(`Contract call failed: ${failedCall.contract}.${failedCall.method}`, {
|
|
221
|
+
cause: error,
|
|
222
|
+
contractName: failedCall.contract,
|
|
223
|
+
methodName: failedCall.method,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const executionMeta = executionResult.meta;
|
|
227
|
+
const executionCache = buildExecutionReplayCache(collectedCalls, executionResult.results, executionVariables, this.codecRegistry, (contract, method) => {
|
|
228
|
+
const c = this.findContract(contract);
|
|
229
|
+
const m = c?.methods.find((m) => m.name === method);
|
|
230
|
+
return m?.params.map((p) => p.type) ?? [];
|
|
231
|
+
});
|
|
232
|
+
return { executionMeta, executionCache };
|
|
233
|
+
}
|
|
234
|
+
async doEvaluate(expression, context, options) {
|
|
235
|
+
debug("evaluate: %s", expression);
|
|
236
|
+
const resolvedClient = options?.client ?? this.client;
|
|
237
|
+
try {
|
|
238
|
+
const { parseResult, collectedCalls, normalizedContext, executionVariables, typeCheckResult, } = this.planExecution(expression, context ?? {});
|
|
239
|
+
let executionMeta;
|
|
240
|
+
let executionCache;
|
|
241
|
+
if (collectedCalls.length > 0) {
|
|
242
|
+
if (!resolvedClient) {
|
|
243
|
+
throw new SELContractError("No client provided for contract call. Provide a client in SELRuntime config or evaluate() options.", {
|
|
244
|
+
contractName: collectedCalls[0]?.contract,
|
|
245
|
+
methodName: collectedCalls[0]?.method,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
({ executionMeta, executionCache } = await this.executeContractCalls(collectedCalls, executionVariables, resolvedClient));
|
|
249
|
+
}
|
|
250
|
+
// Set per-evaluation state for the handler closure
|
|
251
|
+
this.currentCache = executionCache;
|
|
252
|
+
this.currentClient = resolvedClient;
|
|
253
|
+
this.currentCallCounter = new CallCounter(this.maxCalls, executionMeta?.totalCalls ?? 0);
|
|
254
|
+
let result;
|
|
255
|
+
try {
|
|
256
|
+
result = await parseResult({
|
|
257
|
+
...normalizedContext,
|
|
258
|
+
...this.contractBindings,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
this.currentCache = undefined;
|
|
263
|
+
this.currentClient = undefined;
|
|
264
|
+
this.currentCallCounter = undefined;
|
|
265
|
+
}
|
|
266
|
+
const value = (typeCheckResult.type
|
|
267
|
+
? this.codecRegistry.encode(typeCheckResult.type, result)
|
|
268
|
+
: result);
|
|
269
|
+
debug("evaluate: result type=%s", typeof value);
|
|
270
|
+
return executionMeta ? { value, meta: executionMeta } : { value };
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
throw wrapError(error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
findContract(name) {
|
|
277
|
+
return this.schema.contracts.find((c) => c.name === name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps an unknown error into a known SEL error type.
|
|
3
|
+
* - If the error is already a SEL error, it is returned as is.
|
|
4
|
+
* - If the error is a CEL error, it is wrapped into the corresponding SEL error type.
|
|
5
|
+
* - If the error is an unknown type, it is wrapped into a generic Error.
|
|
6
|
+
*
|
|
7
|
+
* @param error The error to wrap.
|
|
8
|
+
* @returns The wrapped error.
|
|
9
|
+
*/
|
|
10
|
+
export declare const wrapError: (error: unknown) => Error;
|
|
11
|
+
//# sourceMappingURL=error-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-wrapper.d.ts","sourceRoot":"","sources":["../../src/environment/error-wrapper.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,KA2B1C,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TypeError as CelTypeError, EvaluationError, ParseError, } from "@marcbachmann/cel-js";
|
|
2
|
+
import { SELContractError, SELEvaluationError, SELParseError, SELTypeError, } from "../errors/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps an unknown error into a known SEL error type.
|
|
5
|
+
* - If the error is already a SEL error, it is returned as is.
|
|
6
|
+
* - If the error is a CEL error, it is wrapped into the corresponding SEL error type.
|
|
7
|
+
* - If the error is an unknown type, it is wrapped into a generic Error.
|
|
8
|
+
*
|
|
9
|
+
* @param error The error to wrap.
|
|
10
|
+
* @returns The wrapped error.
|
|
11
|
+
*/
|
|
12
|
+
export const wrapError = (error) => {
|
|
13
|
+
if (error instanceof SELContractError) {
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
if (error instanceof ParseError) {
|
|
17
|
+
return new SELParseError(error.message, { cause: error });
|
|
18
|
+
}
|
|
19
|
+
if (error instanceof EvaluationError) {
|
|
20
|
+
const cause = error.cause;
|
|
21
|
+
if (cause instanceof SELContractError) {
|
|
22
|
+
return cause;
|
|
23
|
+
}
|
|
24
|
+
return new SELEvaluationError(error.message, { cause: error });
|
|
25
|
+
}
|
|
26
|
+
if (error instanceof CelTypeError) {
|
|
27
|
+
return new SELTypeError(error.message, { cause: error });
|
|
28
|
+
}
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
return new Error(String(error));
|
|
33
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/environment/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CollectedCall } from "../analysis/types.js";
|
|
2
|
+
import type { CelCodecRegistry } from "@seljs/checker";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a replay cache that maps unique identifiers for contract calls to their results.
|
|
5
|
+
* This allows for efficient retrieval of results during execution replay, ensuring that the same inputs yield the same outputs without needing to re-execute the calls.
|
|
6
|
+
*
|
|
7
|
+
* @param calls An array of collected contract calls, each containing information about the contract, method, arguments, and dependencies.
|
|
8
|
+
* @param results A map of call IDs to their corresponding results, which will be used to resolve arguments that depend on previous calls.
|
|
9
|
+
* @param variables A record of variable names to their values, which will be used to resolve arguments that depend on variables.
|
|
10
|
+
*/
|
|
11
|
+
export declare const buildExecutionReplayCache: (calls: CollectedCall[], results: Map<string, unknown>, variables: Record<string, unknown>, codecRegistry: CelCodecRegistry, getParamTypes: (contract: string, method: string) => string[]) => Map<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a unique identifier for a contract call based on contract name,
|
|
14
|
+
* method, and arguments.
|
|
15
|
+
*
|
|
16
|
+
* IMPORTANT: Args must be pre-encoded via codecRegistry.encode() before
|
|
17
|
+
* calling this function. The encode step normalizes types (e.g., sol_int
|
|
18
|
+
* always produces bigint, sol_address always produces string), which
|
|
19
|
+
* prevents type collisions in String() serialization. If args are NOT
|
|
20
|
+
* pre-encoded, String(42n) === String(42) would cause cache key collisions.
|
|
21
|
+
*/
|
|
22
|
+
export declare const createReplayCallId: (contract: string, method: string, args: unknown[]) => string;
|
|
23
|
+
//# sourceMappingURL=replay-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-cache.d.ts","sourceRoot":"","sources":["../../src/environment/replay-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAiCvD;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,aAAa,EAAE,EACtB,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,eAAe,gBAAgB,EAC/B,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,EAAE,KAE3D,GAAG,CAAC,MAAM,EAAE,OAAO,CAsBrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC9B,UAAU,MAAM,EAChB,QAAQ,MAAM,EACd,MAAM,OAAO,EAAE,KACb,MAIF,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const resolveReplayArgs = (args, variables, results, paramTypes, codecRegistry) => args.map((arg, i) => {
|
|
2
|
+
const celType = paramTypes[i] ?? "dyn";
|
|
3
|
+
if (arg.type === "literal") {
|
|
4
|
+
return codecRegistry.encode(celType, arg.value);
|
|
5
|
+
}
|
|
6
|
+
if (arg.type === "variable") {
|
|
7
|
+
const name = arg.variableName;
|
|
8
|
+
if (!name) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return codecRegistry.encode(celType, variables[name]);
|
|
12
|
+
}
|
|
13
|
+
const id = arg.dependsOnCallId;
|
|
14
|
+
if (!id) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return codecRegistry.encode(celType, results.get(id));
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Builds a replay cache that maps unique identifiers for contract calls to their results.
|
|
21
|
+
* This allows for efficient retrieval of results during execution replay, ensuring that the same inputs yield the same outputs without needing to re-execute the calls.
|
|
22
|
+
*
|
|
23
|
+
* @param calls An array of collected contract calls, each containing information about the contract, method, arguments, and dependencies.
|
|
24
|
+
* @param results A map of call IDs to their corresponding results, which will be used to resolve arguments that depend on previous calls.
|
|
25
|
+
* @param variables A record of variable names to their values, which will be used to resolve arguments that depend on variables.
|
|
26
|
+
*/
|
|
27
|
+
export const buildExecutionReplayCache = (calls, results, variables, codecRegistry, getParamTypes) => {
|
|
28
|
+
const replayCache = new Map();
|
|
29
|
+
for (const call of calls) {
|
|
30
|
+
const result = results.get(call.id);
|
|
31
|
+
const paramTypes = getParamTypes(call.contract, call.method);
|
|
32
|
+
const resolvedArgs = resolveReplayArgs(call.args, variables, results, paramTypes, codecRegistry);
|
|
33
|
+
const replayCallId = createReplayCallId(call.contract, call.method, resolvedArgs);
|
|
34
|
+
replayCache.set(replayCallId, result);
|
|
35
|
+
}
|
|
36
|
+
return replayCache;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Creates a unique identifier for a contract call based on contract name,
|
|
40
|
+
* method, and arguments.
|
|
41
|
+
*
|
|
42
|
+
* IMPORTANT: Args must be pre-encoded via codecRegistry.encode() before
|
|
43
|
+
* calling this function. The encode step normalizes types (e.g., sol_int
|
|
44
|
+
* always produces bigint, sol_address always produces string), which
|
|
45
|
+
* prevents type collisions in String() serialization. If args are NOT
|
|
46
|
+
* pre-encoded, String(42n) === String(42) would cause cache key collisions.
|
|
47
|
+
*/
|
|
48
|
+
export const createReplayCallId = (contract, method, args) => {
|
|
49
|
+
const argKey = args.map((arg) => String(arg)).join(",");
|
|
50
|
+
return `${contract}:${method}:${argKey}`;
|
|
51
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SELSchema } from "@seljs/schema";
|
|
2
|
+
import type { Address, PublicClient } from "viem";
|
|
3
|
+
/**
|
|
4
|
+
* Options for multicall3 batching of contract calls.
|
|
5
|
+
*
|
|
6
|
+
* When multiple independent contract calls are executed in the same round,
|
|
7
|
+
* they are batched into a single multicall3 RPC request for efficiency.
|
|
8
|
+
*/
|
|
9
|
+
export interface MulticallOptions {
|
|
10
|
+
/** Maximum number of calls per multicall3 batch. Unbounded if omitted. */
|
|
11
|
+
batchSize?: number;
|
|
12
|
+
/** Custom multicall3 contract address. Defaults to the canonical deployment. */
|
|
13
|
+
address?: Address;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Limits for expression parsing and contract call execution.
|
|
17
|
+
*
|
|
18
|
+
* AST limits (`maxAstNodes`, `maxDepth`, `maxListElements`, `maxMapEntries`,
|
|
19
|
+
* `maxCallArguments`) are forwarded to the underlying CEL parser to constrain
|
|
20
|
+
* expression complexity.
|
|
21
|
+
*
|
|
22
|
+
* Execution limits (`maxRounds`, `maxCalls`) bound the multi-round contract
|
|
23
|
+
* execution engine. An {@link ExecutionLimitError} is thrown when exceeded.
|
|
24
|
+
*/
|
|
25
|
+
export interface SELLimits {
|
|
26
|
+
/** Maximum number of AST nodes allowed in a parsed expression */
|
|
27
|
+
maxAstNodes?: number;
|
|
28
|
+
/** Maximum nesting depth of the AST */
|
|
29
|
+
maxDepth?: number;
|
|
30
|
+
/** Maximum number of elements in a list literal */
|
|
31
|
+
maxListElements?: number;
|
|
32
|
+
/** Maximum number of entries in a map literal */
|
|
33
|
+
maxMapEntries?: number;
|
|
34
|
+
/** Maximum number of arguments in a single function call */
|
|
35
|
+
maxCallArguments?: number;
|
|
36
|
+
/** Maximum number of dependency-ordered execution rounds (default: 10) */
|
|
37
|
+
maxRounds?: number;
|
|
38
|
+
/** Maximum total number of contract calls across all rounds (default: 100) */
|
|
39
|
+
maxCalls?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Configuration for creating an immutable {@link SELRuntime}.
|
|
43
|
+
*
|
|
44
|
+
* All contracts and context must be declared here — the environment
|
|
45
|
+
* cannot be mutated after construction.
|
|
46
|
+
*/
|
|
47
|
+
export interface SELRuntimeConfig {
|
|
48
|
+
/** SEL schema describing contracts, variables, types, functions, and macros */
|
|
49
|
+
schema: SELSchema;
|
|
50
|
+
/** Viem public client for executing on-chain contract reads */
|
|
51
|
+
client?: PublicClient;
|
|
52
|
+
/** Multicall3 batching options for contract call execution */
|
|
53
|
+
multicall?: MulticallOptions;
|
|
54
|
+
/** AST parsing and execution limits */
|
|
55
|
+
limits?: SELLimits;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/environment/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAElD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACzB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,+EAA+E;IAC/E,MAAM,EAAE,SAAS,CAAC;IAElB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAE7B,uCAAuC;IACvC,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SELError, SELParseError, SELTypeError } from "@seljs/common";
|
|
2
|
+
export { SELError, SELParseError, SELTypeError };
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when CEL expression evaluation fails.
|
|
5
|
+
* Wraps cel-js EvaluationError with additional context.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SELEvaluationError extends SELError {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Thrown when contract validation or execution fails.
|
|
11
|
+
* Includes optional contract name and method name for context.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SELContractError extends SELError {
|
|
14
|
+
readonly contractName?: string;
|
|
15
|
+
readonly methodName?: string;
|
|
16
|
+
constructor(message: string, options?: {
|
|
17
|
+
cause?: unknown;
|
|
18
|
+
contractName?: string;
|
|
19
|
+
methodName?: string;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when a circular dependency is detected in the call dependency graph.
|
|
24
|
+
* Indicates that call A depends on call B which depends on call A (directly or transitively).
|
|
25
|
+
*/
|
|
26
|
+
export declare class CircularDependencyError extends SELError {
|
|
27
|
+
readonly callIds: string[];
|
|
28
|
+
constructor(message: string, options?: {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
callIds?: string[];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Thrown when execution limits are exceeded (maxRounds or maxCalls).
|
|
35
|
+
* Prevents infinite loops and runaway execution.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ExecutionLimitError extends SELError {
|
|
38
|
+
readonly limitType: "maxRounds" | "maxCalls";
|
|
39
|
+
readonly limit: number;
|
|
40
|
+
readonly actual: number;
|
|
41
|
+
constructor(message: string, options?: {
|
|
42
|
+
cause?: unknown;
|
|
43
|
+
limitType?: "maxRounds" | "maxCalls";
|
|
44
|
+
limit?: number;
|
|
45
|
+
actual?: number;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Thrown when a Multicall3 batch execution fails.
|
|
50
|
+
* Includes the failed call index and optional contract name/method name for context.
|
|
51
|
+
*/
|
|
52
|
+
export declare class MulticallBatchError extends SELError {
|
|
53
|
+
readonly failedCallIndex?: number;
|
|
54
|
+
readonly contractName?: string;
|
|
55
|
+
readonly methodName?: string;
|
|
56
|
+
constructor(message: string, options?: {
|
|
57
|
+
cause?: unknown;
|
|
58
|
+
failedCallIndex?: number;
|
|
59
|
+
contractName?: string;
|
|
60
|
+
methodName?: string;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;AAEjD;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;CAAG;AAEnD;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,QAAQ;IAC7C,SAAgB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtC,SAAgB,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGnC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CAM1E;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,QAAQ;IACpD,SAAgB,OAAO,EAAE,MAAM,EAAE,CAAC;gBAGjC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE;CAKlD;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,QAAQ;IAChD,SAAgB,SAAS,EAAE,WAAW,GAAG,UAAU,CAAC;IACpD,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAG9B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;QACrC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB;CAOF;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,QAAQ;IAChD,SAAgB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzC,SAAgB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtC,SAAgB,UAAU,CAAC,EAAE,MAAM,CAAC;gBAGnC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACpB;CAOF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SELError, SELParseError, SELTypeError } from "@seljs/common";
|
|
2
|
+
// Re-export shared errors from @seljs/common
|
|
3
|
+
export { SELError, SELParseError, SELTypeError };
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when CEL expression evaluation fails.
|
|
6
|
+
* Wraps cel-js EvaluationError with additional context.
|
|
7
|
+
*/
|
|
8
|
+
export class SELEvaluationError extends SELError {
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when contract validation or execution fails.
|
|
12
|
+
* Includes optional contract name and method name for context.
|
|
13
|
+
*/
|
|
14
|
+
export class SELContractError extends SELError {
|
|
15
|
+
contractName;
|
|
16
|
+
methodName;
|
|
17
|
+
constructor(message, options) {
|
|
18
|
+
super(message, { cause: options?.cause });
|
|
19
|
+
this.contractName = options?.contractName;
|
|
20
|
+
this.methodName = options?.methodName;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when a circular dependency is detected in the call dependency graph.
|
|
25
|
+
* Indicates that call A depends on call B which depends on call A (directly or transitively).
|
|
26
|
+
*/
|
|
27
|
+
export class CircularDependencyError extends SELError {
|
|
28
|
+
callIds;
|
|
29
|
+
constructor(message, options) {
|
|
30
|
+
super(message, { cause: options?.cause });
|
|
31
|
+
this.callIds = options?.callIds ?? [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when execution limits are exceeded (maxRounds or maxCalls).
|
|
36
|
+
* Prevents infinite loops and runaway execution.
|
|
37
|
+
*/
|
|
38
|
+
export class ExecutionLimitError extends SELError {
|
|
39
|
+
limitType;
|
|
40
|
+
limit;
|
|
41
|
+
actual;
|
|
42
|
+
constructor(message, options) {
|
|
43
|
+
super(message, { cause: options?.cause });
|
|
44
|
+
this.limitType = options?.limitType ?? "maxRounds";
|
|
45
|
+
this.limit = options?.limit ?? 0;
|
|
46
|
+
this.actual = options?.actual ?? 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when a Multicall3 batch execution fails.
|
|
51
|
+
* Includes the failed call index and optional contract name/method name for context.
|
|
52
|
+
*/
|
|
53
|
+
export class MulticallBatchError extends SELError {
|
|
54
|
+
failedCallIndex;
|
|
55
|
+
contractName;
|
|
56
|
+
methodName;
|
|
57
|
+
constructor(message, options) {
|
|
58
|
+
super(message, { cause: options?.cause });
|
|
59
|
+
this.failedCallIndex = options?.failedCallIndex;
|
|
60
|
+
this.contractName = options?.contractName;
|
|
61
|
+
this.methodName = options?.methodName;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./errors.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/execution/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExecutionResult } from "./types.js";
|
|
2
|
+
import type { ExecutionPlan } from "../analysis/types.js";
|
|
3
|
+
import type { Abi, Address, PublicClient } from "viem";
|
|
4
|
+
export declare class MultiRoundExecutor {
|
|
5
|
+
private readonly client;
|
|
6
|
+
private readonly multicallOptions?;
|
|
7
|
+
private readonly contracts;
|
|
8
|
+
constructor(client: PublicClient, contracts: Map<string, {
|
|
9
|
+
abi: Abi;
|
|
10
|
+
address: Address;
|
|
11
|
+
}>, multicallOptions?: {
|
|
12
|
+
address?: Address;
|
|
13
|
+
batchSize?: number;
|
|
14
|
+
} | undefined);
|
|
15
|
+
execute(plan: ExecutionPlan, variables?: Record<string, unknown>, blockNumber?: bigint): Promise<ExecutionResult>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=multi-round-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-round-executor.d.ts","sourceRoot":"","sources":["../../src/execution/multi-round-executor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIvD,qBAAa,kBAAkB;IAI7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IALnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;gBAGpC,MAAM,EAAE,YAAY,EACrC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,EACrC,gBAAgB,CAAC,EAAE;QACnC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,YAAA;IAKW,OAAO,CACnB,IAAI,EAAE,aAAa,EACnB,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACvC,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC;CA4C3B"}
|