@crediolabs/policy-synth 0.1.10 → 0.1.12
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/dist/adapters/interpreter/adapter.js +83 -11
- package/dist/adapters/oz/adapter.js +4 -0
- package/dist/errors.d.ts +1 -1
- package/dist/install/build-add-context-rule.d.ts +48 -0
- package/dist/install/build-add-context-rule.js +304 -0
- package/dist/ir/types.d.ts +23 -0
- package/dist/predicate/decode.d.ts +11 -0
- package/dist/predicate/decode.js +234 -0
- package/dist/predicate/encode.js +79 -14
- package/dist/predicate/from-json.d.ts +4 -0
- package/dist/predicate/from-json.js +113 -0
- package/dist/predicate/index.d.ts +2 -0
- package/dist/predicate/index.js +5 -1
- package/dist/registry/identify.js +21 -2
- package/dist/registry/protocols.d.ts +50 -1
- package/dist/registry/protocols.js +88 -0
- package/dist/review-card/builder.d.ts +13 -0
- package/dist/review-card/builder.js +63 -5
- package/dist/review-card/cross-check.js +32 -3
- package/dist/review-card/index.d.ts +1 -1
- package/dist/review-card/index.js +1 -1
- package/dist/run/schemas.d.ts +4 -4
- package/dist/synth/compose-from-recording.d.ts +18 -0
- package/dist/synth/compose-from-recording.js +75 -10
- package/dist/synth/deny-cases.d.ts +4 -1
- package/dist/synth/deny-cases.js +3 -1
- package/dist/synth/evaluate.js +130 -3
- package/dist/synth/permit-context.d.ts +15 -0
- package/dist/synth/permit-context.js +116 -0
- package/dist/synth/synthesize-from-recording.js +14 -2
- package/dist/types.d.ts +9 -0
- package/dist-cjs/adapters/interpreter/adapter.js +83 -11
- package/dist-cjs/adapters/oz/adapter.js +4 -0
- package/dist-cjs/errors.d.ts +1 -1
- package/dist-cjs/install/build-add-context-rule.d.ts +48 -0
- package/dist-cjs/install/build-add-context-rule.js +308 -0
- package/dist-cjs/ir/types.d.ts +23 -0
- package/dist-cjs/predicate/decode.d.ts +11 -0
- package/dist-cjs/predicate/decode.js +239 -0
- package/dist-cjs/predicate/encode.js +79 -14
- package/dist-cjs/predicate/from-json.d.ts +4 -0
- package/dist-cjs/predicate/from-json.js +116 -0
- package/dist-cjs/predicate/index.d.ts +2 -0
- package/dist-cjs/predicate/index.js +10 -2
- package/dist-cjs/registry/identify.js +20 -1
- package/dist-cjs/registry/protocols.d.ts +50 -1
- package/dist-cjs/registry/protocols.js +89 -1
- package/dist-cjs/review-card/builder.d.ts +13 -0
- package/dist-cjs/review-card/builder.js +64 -5
- package/dist-cjs/review-card/cross-check.js +32 -3
- package/dist-cjs/review-card/index.d.ts +1 -1
- package/dist-cjs/review-card/index.js +2 -1
- package/dist-cjs/run/schemas.d.ts +4 -4
- package/dist-cjs/synth/compose-from-recording.d.ts +18 -0
- package/dist-cjs/synth/compose-from-recording.js +75 -10
- package/dist-cjs/synth/deny-cases.d.ts +4 -1
- package/dist-cjs/synth/deny-cases.js +3 -0
- package/dist-cjs/synth/evaluate.js +130 -3
- package/dist-cjs/synth/permit-context.d.ts +15 -0
- package/dist-cjs/synth/permit-context.js +119 -0
- package/dist-cjs/synth/synthesize-from-recording.js +14 -2
- package/dist-cjs/types.d.ts +9 -0
- package/package.json +5 -2
- package/src/adapters/interpreter/adapter.ts +93 -11
- package/src/adapters/oz/adapter.ts +4 -0
- package/src/contracts/policy-template/OZ_POLICY_TRAIT.md +28 -3
- package/src/errors.ts +13 -0
- package/src/install/build-add-context-rule.ts +429 -0
- package/src/ir/types.ts +23 -0
- package/src/predicate/decode.ts +242 -0
- package/src/predicate/encode.ts +110 -13
- package/src/predicate/from-json.ts +124 -0
- package/src/predicate/index.ts +5 -1
- package/src/registry/identify.ts +22 -2
- package/src/registry/protocols.ts +90 -1
- package/src/review-card/builder.ts +58 -5
- package/src/review-card/cross-check.ts +36 -3
- package/src/review-card/index.ts +1 -0
- package/src/synth/compose-from-recording.ts +101 -7
- package/src/synth/deny-cases.ts +3 -1
- package/src/synth/evaluate.ts +130 -3
- package/src/synth/permit-context.ts +136 -0
- package/src/synth/synthesize-from-recording.ts +13 -2
- package/src/types.ts +12 -0
|
@@ -812,13 +812,25 @@ function visitOracleLeaves(node, visit) {
|
|
|
812
812
|
return;
|
|
813
813
|
}
|
|
814
814
|
}
|
|
815
|
+
/** An oracle threshold restated on the normalised 9-dp basis prices use.
|
|
816
|
+
* Thresholds carry their own basis, so reading the digits raw would build a
|
|
817
|
+
* permit context off by 10^(decimals-9) and the intended call would not
|
|
818
|
+
* satisfy its own bound. Mirrors NORMALISED_DECIMALS in oracle.rs. */
|
|
815
819
|
function oracleLiteralFromLeaf(leaf) {
|
|
816
|
-
if (leaf.kind !== '
|
|
820
|
+
if (leaf.kind !== 'oracle_threshold')
|
|
817
821
|
return undefined;
|
|
822
|
+
let value;
|
|
818
823
|
try {
|
|
819
|
-
|
|
824
|
+
value = BigInt(leaf.value);
|
|
820
825
|
}
|
|
821
826
|
catch {
|
|
822
827
|
return undefined;
|
|
823
828
|
}
|
|
829
|
+
const normalised = 9n;
|
|
830
|
+
const decimals = BigInt(leaf.decimals);
|
|
831
|
+
if (decimals <= normalised)
|
|
832
|
+
return value * 10n ** (normalised - decimals);
|
|
833
|
+
// Floor: a finer-grained threshold has no exact 9-dp representation; the
|
|
834
|
+
// caller offsets by one from here to land on the right side of the bound.
|
|
835
|
+
return value / 10n ** (decimals - normalised);
|
|
824
836
|
}
|
package/dist-cjs/types.d.ts
CHANGED
|
@@ -190,6 +190,11 @@ export type PredicateLeaf = {
|
|
|
190
190
|
index: number;
|
|
191
191
|
element: number;
|
|
192
192
|
field: string;
|
|
193
|
+
} | {
|
|
194
|
+
kind: 'call_arg_scaled';
|
|
195
|
+
index: number;
|
|
196
|
+
num: string;
|
|
197
|
+
den: string;
|
|
193
198
|
} | {
|
|
194
199
|
kind: 'amount';
|
|
195
200
|
token: string;
|
|
@@ -207,6 +212,10 @@ export type PredicateLeaf = {
|
|
|
207
212
|
} | {
|
|
208
213
|
kind: 'oracle_price';
|
|
209
214
|
asset: string;
|
|
215
|
+
} | {
|
|
216
|
+
kind: 'oracle_threshold';
|
|
217
|
+
value: string;
|
|
218
|
+
decimals: number;
|
|
210
219
|
} | {
|
|
211
220
|
kind: 'literal_address';
|
|
212
221
|
value: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediolabs/policy-synth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Off-chain TypeScript synthesis core for the OZ Accounts Policy Builder. Records Soroban transactions, synthesises the minimal policy that permits exactly that flow, verifies it, and returns an unsigned install transaction.",
|
|
6
6
|
"type": "module",
|
|
@@ -67,11 +67,14 @@
|
|
|
67
67
|
"prepack": "bun run build"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@stellar/stellar-sdk": "14.4.0",
|
|
71
70
|
"zod": "3.25.76"
|
|
72
71
|
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"@stellar/stellar-sdk": "^14.4.0"
|
|
74
|
+
},
|
|
73
75
|
"devDependencies": {
|
|
74
76
|
"@biomejs/biome": "2.5.5",
|
|
77
|
+
"@stellar/stellar-sdk": "14.4.0",
|
|
75
78
|
"@types/node": "^26.1.1",
|
|
76
79
|
"typescript": "5.9.3"
|
|
77
80
|
}
|
|
@@ -90,7 +90,10 @@ export interface InterpreterAdapterConfig {
|
|
|
90
90
|
const CAPABILITIES: CustodyCapabilities = {
|
|
91
91
|
supportsSpendWindow: true,
|
|
92
92
|
supportsThreshold: false, // thresholds are the OZ adapter's job
|
|
93
|
-
|
|
93
|
+
// False for the predicate-leaf path: the interpreter refuses a `valid_until`
|
|
94
|
+
// leaf at install. Expiry is still available, but as the context rule's
|
|
95
|
+
// validUntilLedger, which the smart account enforces - not as a predicate.
|
|
96
|
+
supportsTimeExpiry: false,
|
|
94
97
|
supportsOraclePrice: true,
|
|
95
98
|
supportsInvocationCount: true,
|
|
96
99
|
supportsGeneralPredicate: true,
|
|
@@ -295,33 +298,90 @@ function lowerRule(rule: IRPolicyRule, config: InterpreterAdapterConfig): Lowere
|
|
|
295
298
|
* by `lowerRule` to populate `uncovered` before lowering. */
|
|
296
299
|
function unsupportedConstruct(cond: IRCondition): string | null {
|
|
297
300
|
switch (cond.op) {
|
|
301
|
+
// Expressible: `call_arg_scaled` is exactly this construct.
|
|
302
|
+
case 'slippage_floor':
|
|
303
|
+
return null
|
|
298
304
|
case 'in':
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
305
|
+
// The oracle position rule is a hard error and must be raised before a
|
|
306
|
+
// construct can be written off as uncovered - otherwise a subtree that
|
|
307
|
+
// is BOTH misplaced-oracle and unsourceable would be silently dropped
|
|
308
|
+
// instead of rejected.
|
|
309
|
+
assertNoOracleDescendants(cond)
|
|
310
|
+
return (
|
|
311
|
+
unsourceableSelector(cond.selector) ??
|
|
312
|
+
(cond.selector.kind === 'calldata' || cond.selector.kind === 'value'
|
|
313
|
+
? `EVM \`${cond.selector.kind}\` selector on allowlist (predicate DSL)`
|
|
314
|
+
: null)
|
|
315
|
+
)
|
|
302
316
|
case 'eq_seq':
|
|
303
|
-
return
|
|
304
|
-
|
|
305
|
-
|
|
317
|
+
return (
|
|
318
|
+
unsourceableSelector(cond.selector) ??
|
|
319
|
+
(cond.selector.kind === 'calldata' || cond.selector.kind === 'value'
|
|
320
|
+
? `EVM \`${cond.selector.kind}\` selector on ordered-sequence equality (predicate DSL)`
|
|
321
|
+
: null)
|
|
322
|
+
)
|
|
306
323
|
case 'compare': {
|
|
307
324
|
const s = cond.compare.selector
|
|
308
325
|
if (s.kind === 'calldata') return 'EVM calldata comparison (predicate DSL)'
|
|
309
326
|
if (s.kind === 'value') return 'tx.value comparison (predicate DSL)'
|
|
310
|
-
|
|
327
|
+
// The on-chain interpreter sees ONE authorized call - there is no
|
|
328
|
+
// `Context.sub_invocations` in v1 - so it cannot observe the
|
|
329
|
+
// transaction's token movements. `amount` has no value to read, and
|
|
330
|
+
// `window_spent` accumulates BY that amount, so its counter would never
|
|
331
|
+
// move. Deriving either from the call payload would quietly swap "value
|
|
332
|
+
// actually moved" for "value the caller declared" - a weaker guarantee
|
|
333
|
+
// than the review card would be claiming.
|
|
334
|
+
//
|
|
335
|
+
// Rolling spend caps belong to the OZ `spending_limit` primitive, which
|
|
336
|
+
// is already audited and which the OZ adapter emits. A per-call cap is
|
|
337
|
+
// expressible here as `arg_field`; bounding it with `invocation_count`
|
|
338
|
+
// gives an enforceable ceiling per window.
|
|
339
|
+
return unsourceableSelector(s)
|
|
311
340
|
}
|
|
341
|
+
// Recurse: a nested `and`/`or`/`not` must not smuggle a selector past the
|
|
342
|
+
// pre-scan, which only sees top-level constraints.
|
|
312
343
|
case 'and':
|
|
344
|
+
return cond.children.map(unsupportedConstruct).find((u) => u !== null) ?? null
|
|
313
345
|
case 'or':
|
|
346
|
+
assertNoOracleDescendants(cond)
|
|
347
|
+
return cond.children.map(unsupportedConstruct).find((u) => u !== null) ?? null
|
|
314
348
|
case 'not':
|
|
315
|
-
|
|
349
|
+
assertNoOracleDescendants(cond)
|
|
350
|
+
return unsupportedConstruct(cond.child)
|
|
316
351
|
}
|
|
317
352
|
}
|
|
318
353
|
|
|
354
|
+
/** Selectors whose value the on-chain interpreter has no way to obtain. */
|
|
355
|
+
function unsourceableSelector(s: IRSelector): string | null {
|
|
356
|
+
if (s.kind === 'amount') {
|
|
357
|
+
return `per-call amount comparison on ${s.token} - the interpreter cannot observe token movements; express a per-call cap with arg_field, or a rolling cap with the OZ spending_limit primitive`
|
|
358
|
+
}
|
|
359
|
+
if (s.kind === 'window_spent') {
|
|
360
|
+
return `rolling spend cap on ${s.token} over ${s.windowSeconds}s - not enforceable by the interpreter; use the OZ spending_limit primitive, or bound per-call value with arg_field plus invocation_count`
|
|
361
|
+
}
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
319
365
|
/** Lower one IR condition to a PredicateNode. Enforces the oracle position
|
|
320
366
|
* rule (oracle_price leaves must sit directly under the top-level `and`) and
|
|
321
367
|
* the self-call rule (`in` allowlist / `compare eq` vs the smart account
|
|
322
368
|
* address -> SCOPE_SELF_CALL). */
|
|
323
369
|
function lowerCondition(cond: IRCondition, config: InterpreterAdapterConfig): PredicateNode {
|
|
324
370
|
switch (cond.op) {
|
|
371
|
+
// `out >= in * num/den`. The contract refuses den == 0 or a non-positive
|
|
372
|
+
// ratio at install, so a malformed floor fails loudly rather than
|
|
373
|
+
// silently inverting the comparison.
|
|
374
|
+
case 'slippage_floor':
|
|
375
|
+
return {
|
|
376
|
+
op: 'gte',
|
|
377
|
+
left: { kind: 'call_arg', index: cond.outArgIndex },
|
|
378
|
+
right: {
|
|
379
|
+
kind: 'call_arg_scaled',
|
|
380
|
+
index: cond.inArgIndex,
|
|
381
|
+
num: cond.num,
|
|
382
|
+
den: cond.den,
|
|
383
|
+
},
|
|
384
|
+
}
|
|
325
385
|
case 'and':
|
|
326
386
|
return { op: 'and', children: cond.children.map((c) => lowerCondition(c, config)) }
|
|
327
387
|
case 'or':
|
|
@@ -393,10 +453,15 @@ function lowerSelector(s: IRSelector): PredicateLeaf {
|
|
|
393
453
|
return { kind: 'call_arg_len', index: s.argIndex }
|
|
394
454
|
case 'arg_field':
|
|
395
455
|
return { kind: 'call_arg_field', index: s.argIndex, element: s.element, field: s.field }
|
|
456
|
+
// `amount` / `window_spent` are filtered out by `unsupportedConstruct`
|
|
457
|
+
// before lowering - the interpreter cannot source either on chain. Reaching
|
|
458
|
+
// here means the pre-scan was bypassed, so fail loudly rather than emit a
|
|
459
|
+
// leaf the contract will refuse.
|
|
396
460
|
case 'amount':
|
|
397
|
-
return { kind: 'amount', token: s.token }
|
|
398
461
|
case 'window_spent':
|
|
399
|
-
|
|
462
|
+
throw new Error(
|
|
463
|
+
`interpreter adapter cannot lower \`${s.kind}\`: it should have been reported as uncovered`
|
|
464
|
+
)
|
|
400
465
|
case 'invocation_count':
|
|
401
466
|
return { kind: 'invocation_count_in_window', windowSecs: s.windowSeconds }
|
|
402
467
|
case 'now':
|
|
@@ -427,6 +492,19 @@ function lowerSelector(s: IRSelector): PredicateLeaf {
|
|
|
427
492
|
* - invocation_count -> u32 (counts are small non-negative integers)
|
|
428
493
|
* - now / valid_until -> u64 (unix timestamps in seconds) */
|
|
429
494
|
function literalFromIRCompare(c: IRCompare): PredicateLeaf {
|
|
495
|
+
// An oracle comparand is not a bare literal: the interpreter refuses one,
|
|
496
|
+
// because assuming it shares the normalised 9-dp basis is what made a raw
|
|
497
|
+
// 14-dp threshold permit everything it was written to deny.
|
|
498
|
+
if (c.selector.kind === 'oracle_price') {
|
|
499
|
+
if (c.valueDecimals === undefined) {
|
|
500
|
+
throw toolError(
|
|
501
|
+
'ORACLE_THRESHOLD_BASIS_REQUIRED',
|
|
502
|
+
'An oracle price bound must declare the decimal basis of its threshold ' +
|
|
503
|
+
'(oracle prices normalise to 9 decimals).'
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
return { kind: 'oracle_threshold', value: c.value, decimals: c.valueDecimals }
|
|
507
|
+
}
|
|
430
508
|
const scalarType: IRScalarType = selectorScalarType(c.selector)
|
|
431
509
|
return literalFromScalar(c.value, scalarType)
|
|
432
510
|
}
|
|
@@ -502,6 +580,8 @@ function assertNoOracleDescendants(node: IRCondition): void {
|
|
|
502
580
|
|
|
503
581
|
function containsOracle(node: IRCondition): boolean {
|
|
504
582
|
switch (node.op) {
|
|
583
|
+
case 'slippage_floor':
|
|
584
|
+
return false
|
|
505
585
|
case 'compare':
|
|
506
586
|
return node.compare.selector.kind === 'oracle_price'
|
|
507
587
|
case 'in':
|
|
@@ -555,6 +635,8 @@ function assertOracleParamsTighten(params: InterpreterAdapterConfig['oracleParam
|
|
|
555
635
|
* express. Mirrors the OZ adapter's `describeCondition` for parity. */
|
|
556
636
|
function describeCondition(cond: IRCondition): string {
|
|
557
637
|
switch (cond.op) {
|
|
638
|
+
case 'slippage_floor':
|
|
639
|
+
return `slippage floor: arg[${cond.outArgIndex}] >= arg[${cond.inArgIndex}] * ${cond.num}/${cond.den}`
|
|
558
640
|
case 'in':
|
|
559
641
|
return `value allowlist on ${describeSelector(cond.selector)} (predicate DSL)`
|
|
560
642
|
case 'eq_seq':
|
|
@@ -285,6 +285,10 @@ function matchSpendingLimit(c: IRCondition): SpendingLimitMatch | null {
|
|
|
285
285
|
* cannot express, used to populate `uncovered`. */
|
|
286
286
|
function describeCondition(cond: IRCondition): string {
|
|
287
287
|
switch (cond.op) {
|
|
288
|
+
case 'slippage_floor':
|
|
289
|
+
// The OZ primitives bound a value against a constant; this bounds one
|
|
290
|
+
// call argument against another, which none of them can express.
|
|
291
|
+
return `slippage floor on arg[${cond.outArgIndex}] (OZ built-ins cannot bound one argument against another)`
|
|
288
292
|
case 'in':
|
|
289
293
|
return `value allowlist on ${describeSelector(cond.selector)} (arg allowlist)`
|
|
290
294
|
case 'eq_seq':
|
|
@@ -113,15 +113,40 @@ pub fn add_context_rule(
|
|
|
113
113
|
- `add_context_rule` is called by the **smart account itself** (the contract owning the context rules).
|
|
114
114
|
- Auth is enforced by Soroban's `require_auth` on the calling context — the smart account's master signer (or a delegated signer with sufficient weight) must authorize the call.
|
|
115
115
|
- The interpreter's `install` function is called **within the same transaction** as `add_context_rule` and receives the same `smart_account` address.
|
|
116
|
-
-
|
|
116
|
+
- The interpreter **cannot** read the smart account's master set; it receives only the already-authorized `ContextRule` and `smart_account` address.
|
|
117
|
+
|
|
118
|
+
> **CORRECTED 2026-07-27 - do not read the line above as a safety guarantee.**
|
|
119
|
+
> An earlier version of this file said the interpreter "does NOT need to" read the
|
|
120
|
+
> master set because the master authorizes `add_context_rule` directly. The master
|
|
121
|
+
> *can* authorize it. It is **not** true that only the master can.
|
|
122
|
+
>
|
|
123
|
+
> Verified at this pinned commit: `storage.rs` states `add_policy` /
|
|
124
|
+
> `add_context_rule` modify storage "without requiring authorization. Ensure proper
|
|
125
|
+
> access control is implemented at the contract level"; `mod.rs:439` and `mod.rs:246`
|
|
126
|
+
> implement that as `e.current_contract_address().require_auth()`, a self-auth
|
|
127
|
+
> resolved through the account's own context rules; and `mod.rs:80` states that
|
|
128
|
+
> "a `Default` rule matches any context".
|
|
129
|
+
>
|
|
130
|
+
> Therefore any signer on a `Default` rule can attach or replace a policy - including
|
|
131
|
+
> a delegated agent, on its own rule. See `plans/security-finding-install-authorization.md`.
|
|
117
132
|
|
|
118
133
|
### Safe mechanism for master-only install
|
|
119
134
|
|
|
120
|
-
|
|
135
|
+
Two-step install is the install **path**. It is NOT relied on as the authorization
|
|
136
|
+
**guarantee**, because the auth it inherits is whatever the account's own rules permit:
|
|
137
|
+
|
|
121
138
|
1. Owner calls `add_context_rule` with the interpreter address + initial rule.
|
|
122
|
-
2. Interpreter's `install` is called within the same auth context
|
|
139
|
+
2. Interpreter's `install` is called within the same auth context.
|
|
123
140
|
3. Re-install is structurally possible via `install_nonce` (stored nonce + 1 check).
|
|
124
141
|
|
|
142
|
+
The guarantee comes from the interpreter pinning its own authorized installer set at
|
|
143
|
+
first install for `(smart_account, rule_id)`, and denying later installs and
|
|
144
|
+
uninstalls from outside that set with `MASTER_AUTH_REQUIRED`. Rotation goes through
|
|
145
|
+
`rotate_master_signer_set(new_set)`, gated by the old set.
|
|
146
|
+
|
|
147
|
+
`install_nonce` alone does not close this: it blocks replay of an *old* document, but
|
|
148
|
+
a *fresh* looser document with `nonce + 1` would otherwise be accepted.
|
|
149
|
+
|
|
125
150
|
---
|
|
126
151
|
|
|
127
152
|
## Framework Limits (OZ Hard Limits)
|
package/src/errors.ts
CHANGED
|
@@ -39,9 +39,20 @@ export type ErrorCode =
|
|
|
39
39
|
| 'AMOUNT_OVERFLOW'
|
|
40
40
|
| 'RULE_SIGNERS_CHANGED'
|
|
41
41
|
| 'SCOPE_SELF_CALL'
|
|
42
|
+
| 'ARG_MISMATCH'
|
|
43
|
+
| 'CONTRACT_SCOPE'
|
|
44
|
+
| 'UNSUPPORTED_NODE'
|
|
45
|
+
| 'STATEFUL_BOUND'
|
|
46
|
+
| 'NOT_IN_ALLOWLIST'
|
|
47
|
+
| 'FREQUENCY'
|
|
48
|
+
// A `call_arg >= call_arg_scaled(..)` slippage floor was not met. Distinct
|
|
49
|
+
// from the generic ARG_MISMATCH/STATEFUL_BOUND so a violated swap floor
|
|
50
|
+
// reads as itself on the review card.
|
|
51
|
+
| 'SLIPPAGE_FLOOR'
|
|
42
52
|
// --- oracle denies (fatal evaluator errors) ---
|
|
43
53
|
| 'ORACLE_STALE'
|
|
44
54
|
| 'ORACLE_MISSING'
|
|
55
|
+
| 'ORACLE_NO_CONFIRMATION'
|
|
45
56
|
| 'ORACLE_DEVIATION_EXCEEDED'
|
|
46
57
|
| 'ORACLE_MALFORMED_HISTORY'
|
|
47
58
|
| 'ORACLE_FINGERPRINT_DRIFT'
|
|
@@ -49,6 +60,8 @@ export type ErrorCode =
|
|
|
49
60
|
| 'ORACLE_LEAF_INVALID_POSITION'
|
|
50
61
|
| 'ORACLE_PARAMS_OUT_OF_RANGE'
|
|
51
62
|
| 'ORACLE_DECIMALS_MISMATCH'
|
|
63
|
+
| 'ORACLE_THRESHOLD_DECIMALS_OUT_OF_RANGE'
|
|
64
|
+
| 'ORACLE_THRESHOLD_BASIS_REQUIRED'
|
|
52
65
|
// --- escape-hatch compile gate ---
|
|
53
66
|
| 'COMPILE_OK'
|
|
54
67
|
| 'COMPILE_GATE_FAILED'
|