@crediolabs/policy-synth 0.2.0 → 0.3.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/README.md +4 -4
- package/dist/adapters/interpreter/adapter.d.ts +19 -25
- package/dist/adapters/interpreter/adapter.js +54 -487
- package/dist/adapters/interpreter/index.d.ts +1 -1
- package/dist/adapters/interpreter/index.js +1 -1
- package/dist/adapters/oz/adapter.js +1 -15
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/install/build-add-context-rule.d.ts +3 -19
- package/dist/install/build-add-context-rule.js +3 -98
- package/dist/install/build-install-policy.d.ts +4 -53
- package/dist/install/build-install-policy.js +44 -123
- package/dist/install/index.d.ts +0 -2
- package/dist/install/index.js +0 -7
- package/dist/ir/index.d.ts +1 -1
- package/dist/ir/types.d.ts +7 -76
- package/dist/ir/types.js +0 -2
- package/dist/predicate/decode.d.ts +1 -1
- package/dist/predicate/decode.js +2 -70
- package/dist/predicate/encode.js +45 -229
- package/dist/predicate/from-json.js +1 -42
- package/dist/record/decode.js +3 -8
- package/dist/review-card/builder.d.ts +4 -3
- package/dist/review-card/builder.js +18 -162
- package/dist/review-card/cross-check.js +2 -105
- package/dist/review-card/render-leaf.d.ts +4 -0
- package/dist/review-card/render-leaf.js +47 -0
- package/dist/run/index.d.ts +36 -139
- package/dist/run/index.js +125 -437
- package/dist/run/schemas.d.ts +168 -1929
- package/dist/run/schemas.js +40 -269
- package/dist/seams/types.d.ts +0 -2
- package/dist/simulate/deny-cases.d.ts +26 -0
- package/dist/simulate/deny-cases.js +282 -0
- package/dist/simulate/evaluate.d.ts +18 -0
- package/dist/simulate/evaluate.js +271 -0
- package/dist/simulate/index.d.ts +4 -0
- package/dist/simulate/index.js +8 -0
- package/dist/synth/compose-from-recording.d.ts +24 -51
- package/dist/synth/compose-from-recording.js +120 -282
- package/dist/synth/deny-cases.d.ts +5 -7
- package/dist/synth/deny-cases.js +23 -373
- package/dist/synth/evaluate.d.ts +0 -21
- package/dist/synth/evaluate.js +56 -336
- package/dist/synth/index.d.ts +0 -5
- package/dist/synth/index.js +0 -5
- package/dist/synth/permit-context.d.ts +2 -9
- package/dist/synth/permit-context.js +1 -82
- package/dist/synth/synthesize-from-recording.d.ts +1 -29
- package/dist/synth/synthesize-from-recording.js +41 -356
- package/dist/types.d.ts +8 -60
- package/dist/types.js +5 -2
- package/dist/verify/simulate.d.ts +1 -14
- package/dist/verify/simulate.js +2 -88
- package/dist/verify/verify.d.ts +1 -7
- package/dist/verify/verify.js +0 -8
- package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
- package/dist-cjs/adapters/interpreter/adapter.js +56 -489
- package/dist-cjs/adapters/interpreter/index.d.ts +1 -1
- package/dist-cjs/adapters/interpreter/index.js +1 -2
- package/dist-cjs/adapters/oz/adapter.js +1 -15
- package/dist-cjs/errors.d.ts +1 -1
- package/dist-cjs/index.d.ts +1 -4
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/install/build-add-context-rule.d.ts +3 -19
- package/dist-cjs/install/build-add-context-rule.js +1 -97
- package/dist-cjs/install/build-install-policy.d.ts +4 -53
- package/dist-cjs/install/build-install-policy.js +44 -122
- package/dist-cjs/install/index.d.ts +0 -2
- package/dist-cjs/install/index.js +2 -23
- package/dist-cjs/ir/index.d.ts +1 -1
- package/dist-cjs/ir/types.d.ts +7 -76
- package/dist-cjs/ir/types.js +0 -2
- package/dist-cjs/predicate/decode.d.ts +1 -1
- package/dist-cjs/predicate/decode.js +2 -70
- package/dist-cjs/predicate/encode.js +45 -229
- package/dist-cjs/predicate/from-json.js +1 -42
- package/dist-cjs/record/decode.js +3 -8
- package/dist-cjs/review-card/builder.d.ts +4 -3
- package/dist-cjs/review-card/builder.js +23 -167
- package/dist-cjs/review-card/cross-check.js +7 -110
- package/dist-cjs/review-card/render-leaf.d.ts +4 -0
- package/dist-cjs/review-card/render-leaf.js +52 -0
- package/dist-cjs/run/index.d.ts +36 -139
- package/dist-cjs/run/index.js +125 -444
- package/dist-cjs/run/schemas.d.ts +168 -1929
- package/dist-cjs/run/schemas.js +41 -270
- package/dist-cjs/seams/types.d.ts +0 -2
- package/dist-cjs/simulate/deny-cases.d.ts +26 -0
- package/dist-cjs/simulate/deny-cases.js +286 -0
- package/dist-cjs/simulate/evaluate.d.ts +18 -0
- package/dist-cjs/simulate/evaluate.js +274 -0
- package/dist-cjs/simulate/index.d.ts +4 -0
- package/dist-cjs/simulate/index.js +13 -0
- package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
- package/dist-cjs/synth/compose-from-recording.js +120 -282
- package/dist-cjs/synth/deny-cases.d.ts +5 -7
- package/dist-cjs/synth/deny-cases.js +23 -374
- package/dist-cjs/synth/evaluate.d.ts +0 -21
- package/dist-cjs/synth/evaluate.js +57 -337
- package/dist-cjs/synth/index.d.ts +0 -5
- package/dist-cjs/synth/index.js +1 -11
- package/dist-cjs/synth/permit-context.d.ts +2 -9
- package/dist-cjs/synth/permit-context.js +1 -82
- package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
- package/dist-cjs/synth/synthesize-from-recording.js +39 -354
- package/dist-cjs/types.d.ts +8 -60
- package/dist-cjs/types.js +6 -3
- package/dist-cjs/verify/simulate.d.ts +1 -14
- package/dist-cjs/verify/simulate.js +2 -88
- package/dist-cjs/verify/verify.d.ts +1 -7
- package/dist-cjs/verify/verify.js +0 -8
- package/package.json +4 -4
- package/src/adapters/interpreter/adapter.ts +76 -572
- package/src/errors.ts +0 -19
- package/src/index.ts +1 -4
- package/src/install/build-add-context-rule.ts +5 -139
- package/src/install/build-install-policy.ts +93 -214
- package/src/install/index.ts +0 -34
- package/src/predicate/decode.ts +2 -70
- package/src/predicate/encode.ts +49 -261
- package/src/predicate/from-json.ts +1 -43
- package/src/record/decode.ts +3 -8
- package/src/review-card/builder.ts +19 -168
- package/src/review-card/cross-check.ts +3 -105
- package/src/review-card/render-leaf.ts +48 -0
- package/src/run/index.ts +144 -572
- package/src/run/schemas.ts +42 -291
- package/src/simulate/deny-cases.ts +334 -0
- package/src/simulate/evaluate.ts +284 -0
- package/src/simulate/index.ts +11 -0
- package/src/synth/compose-from-recording.ts +148 -347
- package/src/synth/index.ts +0 -13
- package/src/synth/synthesize-from-recording.ts +43 -456
- package/src/types.ts +13 -49
- package/dist/install/authority-overlap.d.ts +0 -134
- package/dist/install/authority-overlap.js +0 -0
- package/dist/install/build-install-predicate.d.ts +0 -96
- package/dist/install/build-install-predicate.js +0 -444
- package/dist/install/build-merge-policy.d.ts +0 -70
- package/dist/install/build-merge-policy.js +0 -130
- package/dist/install/plan-merge-policy.d.ts +0 -49
- package/dist/install/plan-merge-policy.js +0 -86
- package/dist/install/read-account-rules.d.ts +0 -100
- package/dist/install/read-account-rules.js +0 -283
- package/dist-cjs/install/authority-overlap.d.ts +0 -134
- package/dist-cjs/install/authority-overlap.js +0 -0
- package/dist-cjs/install/build-install-predicate.d.ts +0 -96
- package/dist-cjs/install/build-install-predicate.js +0 -479
- package/dist-cjs/install/build-merge-policy.d.ts +0 -70
- package/dist-cjs/install/build-merge-policy.js +0 -134
- package/dist-cjs/install/plan-merge-policy.d.ts +0 -49
- package/dist-cjs/install/plan-merge-policy.js +0 -90
- package/dist-cjs/install/read-account-rules.d.ts +0 -100
- package/dist-cjs/install/read-account-rules.js +0 -296
- package/src/adapters/interpreter/index.ts +0 -8
- package/src/adapters/oz/adapter.ts +0 -376
- package/src/adapters/oz/index.ts +0 -9
- package/src/codegen/compile-gate.ts +0 -167
- package/src/codegen/index.ts +0 -17
- package/src/codegen/template.ts +0 -165
- package/src/install/authority-overlap.ts +0 -0
- package/src/install/build-merge-policy.ts +0 -219
- package/src/install/plan-merge-policy.ts +0 -133
- package/src/install/read-account-rules.ts +0 -376
- package/src/ir/index.ts +0 -13
- package/src/ir/types.ts +0 -132
- package/src/mandate/index.ts +0 -4
- package/src/mandate/to-ir.ts +0 -71
- package/src/mandate/types.ts +0 -21
- package/src/seams/index.ts +0 -11
- package/src/seams/types.ts +0 -81
- package/src/synth/deny-cases.ts +0 -663
- package/src/synth/evaluate.ts +0 -613
- package/src/synth/harness.ts +0 -68
- package/src/synth/minimize.ts +0 -48
- package/src/synth/permit-context.ts +0 -136
- package/src/synth/predicate-literals.ts +0 -27
- package/src/synth/synthesize-from-mandate.ts +0 -82
- package/src/verify/envelope.ts +0 -28
- package/src/verify/index.ts +0 -5
- package/src/verify/simulate.ts +0 -311
- package/src/verify/verify.ts +0 -243
package/dist/synth/evaluate.js
CHANGED
|
@@ -1,142 +1,48 @@
|
|
|
1
|
-
// src/synth/evaluate.ts -
|
|
1
|
+
// src/synth/evaluate.ts - TypeScript reference evaluator for grammar version 3.
|
|
2
2
|
//
|
|
3
3
|
// Pure function. Determinism: same `(predicate, ctx)` -> byte-identical result,
|
|
4
4
|
// no clock, no randomness. Deny order (deny on FIRST violation, stable reason):
|
|
5
|
-
// 1.
|
|
6
|
-
// 2.
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// 5. `in` membership; empty haystack ALWAYS denies -> 'NOT_IN_ALLOWLIST'
|
|
11
|
-
// 6. amount / window_spent via BigInt -> 'AMOUNT_BOUND'
|
|
12
|
-
// 7. invocation_count_in_window -> 'FREQUENCY'
|
|
13
|
-
// 8. oracle_price: fatal -> throw OracleError, deny with
|
|
14
|
-
// underlying ORACLE_* reason -> 'ORACLE_*'
|
|
15
|
-
// 9. boolean nodes (and/or/not)
|
|
16
|
-
// 10. signer threshold gate -> 'THRESHOLD_NOT_MET'
|
|
17
|
-
// 11. otherwise permit.
|
|
5
|
+
// 1. contract mismatch -> 'CONTRACT_SCOPE'
|
|
6
|
+
// 2. per-ScVal equality on fn/args; EXACT ordered vector
|
|
7
|
+
// equality; fail-closed on opaque args -> 'ARG_MISMATCH'
|
|
8
|
+
// 3. `in` membership; empty haystack ALWAYS denies -> 'NOT_IN_ALLOWLIST'
|
|
9
|
+
// 4. otherwise permit.
|
|
18
10
|
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/** Mirrors MAX_ORACLE_THRESHOLD_DECIMALS in dsl.rs. */
|
|
26
|
-
const MAX_ORACLE_THRESHOLD_DECIMALS = 18;
|
|
27
|
-
/** Internal fatal thrown by the oracle path; caught at the top of `evaluate`
|
|
28
|
-
* and converted to the matching `ORACLE_*` deny reason. NOT a boolean-false
|
|
29
|
-
* that `not` / `or` could mask. */
|
|
30
|
-
class OracleError extends Error {
|
|
31
|
-
code;
|
|
32
|
-
constructor(code) {
|
|
33
|
-
super(`oracle fatal: ${code}`);
|
|
34
|
-
this.code = code;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const ORACLE_ERROR_CODES = {
|
|
38
|
-
stale: 'ORACLE_STALE',
|
|
39
|
-
missing: 'ORACLE_MISSING',
|
|
40
|
-
deviation: 'ORACLE_DEVIATION_EXCEEDED',
|
|
41
|
-
paused: 'ORACLE_PAUSED',
|
|
42
|
-
decimals: 'ORACLE_DECIMALS_MISMATCH',
|
|
43
|
-
fingerprint: 'ORACLE_FINGERPRINT_DRIFT',
|
|
44
|
-
};
|
|
11
|
+
// Grammar version 3 nodes: and, eq, lte
|
|
12
|
+
// Grammar version 3 leaves: call_contract, call_fn, call_arg(i),
|
|
13
|
+
// call_arg_len(i), call_arg_field(i, element, field),
|
|
14
|
+
// literal_address, literal_i128, literal_symbol, literal_u32, literal_vec
|
|
15
|
+
// Grammar version 3 deny reasons: ARG_MISMATCH, CONTRACT_SCOPE,
|
|
16
|
+
// UNSUPPORTED_NODE, NOT_IN_ALLOWLIST
|
|
45
17
|
/** Evaluate a `PredicateNode` against the candidate call described by `ctx`.
|
|
46
18
|
* Pure function. Returns `{ permit: true }` or `{ permit: false; reason }`. */
|
|
47
19
|
export function evaluate(predicate, ctx) {
|
|
48
|
-
|
|
49
|
-
// --- step 1: ledger-time expiry ---
|
|
50
|
-
if (ctx.validUntilLedger !== undefined && ctx.atLedger > ctx.validUntilLedger) {
|
|
51
|
-
return { permit: false, reason: 'EXPIRED' };
|
|
52
|
-
}
|
|
53
|
-
// --- step 2..9: predicate tree ---
|
|
54
|
-
const decision = walk(predicate, ctx);
|
|
55
|
-
// --- step 10: signer threshold gate (only when the predicate permitted) ---
|
|
56
|
-
if (decision.permit && ctx.signerWeights !== undefined) {
|
|
57
|
-
let totalWeight = 0n;
|
|
58
|
-
for (const w of Object.values(ctx.signerWeights)) {
|
|
59
|
-
// weights are non-negative integers; BigInt keeps the gate bounded
|
|
60
|
-
totalWeight += BigInt(w);
|
|
61
|
-
}
|
|
62
|
-
if (totalWeight === 0n)
|
|
63
|
-
return { permit: false, reason: 'THRESHOLD_NOT_MET' };
|
|
64
|
-
}
|
|
65
|
-
return decision;
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
if (e instanceof OracleError) {
|
|
69
|
-
return { permit: false, reason: e.code };
|
|
70
|
-
}
|
|
71
|
-
throw e;
|
|
72
|
-
}
|
|
20
|
+
return walk(predicate, ctx);
|
|
73
21
|
}
|
|
74
22
|
/** Walk the predicate tree. Returns the FIRST deny reason encountered on
|
|
75
|
-
* the active branch (so `and` fails-fast
|
|
23
|
+
* the active branch (so `and` fails-fast). */
|
|
76
24
|
function walk(node, ctx) {
|
|
77
25
|
switch (node.op) {
|
|
78
26
|
case 'and': {
|
|
79
27
|
let lastDeny = null;
|
|
80
28
|
for (const child of node.children) {
|
|
81
29
|
const r = walk(child, ctx);
|
|
82
|
-
if (!r.permit)
|
|
83
|
-
// deny-on-first: short-circuit on the failing child.
|
|
30
|
+
if (!r.permit)
|
|
84
31
|
return r;
|
|
85
|
-
}
|
|
86
32
|
lastDeny = r;
|
|
87
33
|
}
|
|
88
34
|
return lastDeny ?? { permit: true };
|
|
89
35
|
}
|
|
90
|
-
case 'or': {
|
|
91
|
-
let lastDeny = null;
|
|
92
|
-
for (const child of node.children) {
|
|
93
|
-
const r = walk(child, ctx);
|
|
94
|
-
if (r.permit)
|
|
95
|
-
return r;
|
|
96
|
-
lastDeny = r;
|
|
97
|
-
}
|
|
98
|
-
return lastDeny ?? { permit: false, reason: 'NOT_IN_ALLOWLIST' };
|
|
99
|
-
}
|
|
100
|
-
case 'not': {
|
|
101
|
-
// `not` structurally inverts the child unless the child contains an
|
|
102
|
-
// oracle leaf (compile-time rule: no oracle leaf under not/or). A
|
|
103
|
-
// child `OracleError` is re-thrown to the catch at the top of `evaluate`.
|
|
104
|
-
const r = walk(node.child, ctx);
|
|
105
|
-
if (r.permit)
|
|
106
|
-
return { permit: false, reason: 'FN_MISMATCH' };
|
|
107
|
-
return { permit: true };
|
|
108
|
-
}
|
|
109
36
|
case 'eq':
|
|
110
|
-
case 'lt':
|
|
111
37
|
case 'lte':
|
|
112
|
-
case 'gt':
|
|
113
|
-
case 'gte':
|
|
114
38
|
return evalCompare(node.op, node.left, node.right, ctx);
|
|
115
39
|
case 'in':
|
|
116
40
|
return evalIn(node.needle, node.haystack, ctx);
|
|
117
41
|
}
|
|
118
42
|
}
|
|
119
|
-
/**
|
|
43
|
+
/** Comparison leaf evaluation. */
|
|
120
44
|
function evalCompare(op, left, right, ctx) {
|
|
121
|
-
//
|
|
122
|
-
if (left.kind === 'now' && right.kind === 'valid_until') {
|
|
123
|
-
const expired = op === 'gt' || op === 'gte' ? ctx.nowSeconds >= 0 : ctx.nowSeconds < 0;
|
|
124
|
-
// `valid_until` is modelled as a synthetic future timestamp far past
|
|
125
|
-
// `nowSeconds` so the only true-positive expired path is the `gt`/`gte`
|
|
126
|
-
// shapes callers actually write.
|
|
127
|
-
if (op === 'gt' || op === 'gte') {
|
|
128
|
-
if (ctx.nowSeconds > 0)
|
|
129
|
-
return { permit: false, reason: 'EXPIRED' };
|
|
130
|
-
}
|
|
131
|
-
else if (op === 'lt' || op === 'lte') {
|
|
132
|
-
return { permit: true };
|
|
133
|
-
}
|
|
134
|
-
return expired ? { permit: false, reason: 'EXPIRED' } : { permit: true };
|
|
135
|
-
}
|
|
136
|
-
if (right.kind === 'now' && left.kind === 'valid_until') {
|
|
137
|
-
return evalCompare(op, right, left, ctx);
|
|
138
|
-
}
|
|
139
|
-
// --- step 3: CONTRACT_SCOPE on call_contract eq ---
|
|
45
|
+
// CONTRACT_SCOPE on call_contract eq
|
|
140
46
|
if (left.kind === 'call_contract' && op === 'eq') {
|
|
141
47
|
if (right.kind !== 'literal_address')
|
|
142
48
|
return { permit: false, reason: 'CONTRACT_SCOPE' };
|
|
@@ -144,32 +50,20 @@ function evalCompare(op, left, right, ctx) {
|
|
|
144
50
|
? { permit: true }
|
|
145
51
|
: { permit: false, reason: 'CONTRACT_SCOPE' };
|
|
146
52
|
}
|
|
147
|
-
//
|
|
53
|
+
// call_fn equality
|
|
148
54
|
if (left.kind === 'call_fn' && op === 'eq') {
|
|
149
55
|
if (right.kind !== 'literal_symbol')
|
|
150
|
-
return { permit: false, reason: '
|
|
151
|
-
return right.value === ctx.fn ? { permit: true } : { permit: false, reason: '
|
|
56
|
+
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
57
|
+
return right.value === ctx.fn ? { permit: true } : { permit: false, reason: 'ARG_MISMATCH' };
|
|
152
58
|
}
|
|
153
|
-
//
|
|
59
|
+
// call_arg comparison (eq / exact-vec, or lte ordered numeric bound)
|
|
154
60
|
if (left.kind === 'call_arg') {
|
|
155
|
-
// The swap's canonical form is `call_arg[out] >= call_arg_scaled(in, num, den)`.
|
|
156
|
-
// Dispatched here so the floor's dedicated reason codes (ARITHMETIC_OVERFLOW
|
|
157
|
-
// -> SLIPPAGE_FLOOR) reach the user; routing the scaled RHS through the
|
|
158
|
-
// generic `evalArgOrderedCompare` would mask overflow as ARG_MISMATCH.
|
|
159
|
-
if (right.kind === 'call_arg_scaled') {
|
|
160
|
-
return evalScaledArgCompare(op, left, right, ctx);
|
|
161
|
-
}
|
|
162
61
|
const actual = ctx.args[left.index];
|
|
163
62
|
if (op !== 'eq')
|
|
164
63
|
return evalArgOrderedCompare(op, actual, right);
|
|
165
64
|
return evalArgEq(op, actual, right, ctx);
|
|
166
65
|
}
|
|
167
|
-
//
|
|
168
|
-
// Symmetric form so a policy that phrases the floor either way works.
|
|
169
|
-
if (left.kind === 'call_arg_scaled') {
|
|
170
|
-
return evalScaledArgCompare(op, right, left, ctx);
|
|
171
|
-
}
|
|
172
|
-
// --- step 4c: call_arg_len: length of a vec-typed argument as u32.
|
|
66
|
+
// call_arg_len: length of a vec-typed argument as u32.
|
|
173
67
|
// Fails closed on a non-vec / absent arg or a non-u32 literal.
|
|
174
68
|
if (left.kind === 'call_arg_len') {
|
|
175
69
|
const actual = ctx.args[left.index];
|
|
@@ -181,7 +75,7 @@ function evalCompare(op, left, right, ctx) {
|
|
|
181
75
|
? { permit: true }
|
|
182
76
|
: { permit: false, reason: 'ARG_MISMATCH' };
|
|
183
77
|
}
|
|
184
|
-
//
|
|
78
|
+
// call_arg_field: value of a field in the map at element i of
|
|
185
79
|
// the vec at argument index. Fails closed on shape / type / range issues.
|
|
186
80
|
if (left.kind === 'call_arg_field') {
|
|
187
81
|
const actual = ctx.args[left.index];
|
|
@@ -199,34 +93,18 @@ function evalCompare(op, left, right, ctx) {
|
|
|
199
93
|
return evalArgEq(op, entry.val, right, ctx);
|
|
200
94
|
return evalArgOrderedCompare(op, entry.val, right);
|
|
201
95
|
}
|
|
202
|
-
// --- step 6: AMOUNT_BOUND ---
|
|
203
|
-
if (left.kind === 'amount' && op !== 'eq') {
|
|
204
|
-
return evalAmountCompare(op, left.token, right, ctx, 'amountByToken');
|
|
205
|
-
}
|
|
206
|
-
if (left.kind === 'window_spent' && op !== 'eq') {
|
|
207
|
-
return evalWindowSpentCompare(op, left.token, right, ctx);
|
|
208
|
-
}
|
|
209
|
-
// `eq` on amount / window_spent not defined as a bound - fall through.
|
|
210
|
-
// --- step 7: FREQUENCY ---
|
|
211
|
-
if (left.kind === 'invocation_count_in_window') {
|
|
212
|
-
return evalFrequencyCompare(op, left.windowSecs, right, ctx);
|
|
213
|
-
}
|
|
214
|
-
// --- step 8: oracle_price (FATAL via throw) ---
|
|
215
|
-
if (left.kind === 'oracle_price') {
|
|
216
|
-
return evalOracleCompare(op, left.asset, right, ctx);
|
|
217
|
-
}
|
|
218
96
|
// Unknown leaf/op combination - structural fail-closed.
|
|
219
|
-
return { permit: false, reason: '
|
|
97
|
+
return { permit: false, reason: 'UNSUPPORTED_NODE' };
|
|
220
98
|
}
|
|
221
|
-
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
function evalArgEq(op, actual, right,
|
|
99
|
+
/** Per-ScVal equality. Handles literal_vec as an EXACT ordered sequence:
|
|
100
|
+
* compare element-by-element in order; deny if length or any element differs.
|
|
101
|
+
* Opaque args (`type: 'other'`) fail closed. */
|
|
102
|
+
function evalArgEq(op, actual, right, _ctx) {
|
|
225
103
|
// eq(call_arg[i], literal_vec) -> EXACT ordered vector equality.
|
|
226
104
|
if (op === 'eq' && right.kind === 'literal_vec') {
|
|
227
105
|
if (actual?.type !== 'vec')
|
|
228
106
|
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
229
|
-
return compareVecExact(actual.value, right.elements
|
|
107
|
+
return compareVecExact(actual.value, right.elements)
|
|
230
108
|
? { permit: true }
|
|
231
109
|
: { permit: false, reason: 'ARG_MISMATCH' };
|
|
232
110
|
}
|
|
@@ -260,109 +138,15 @@ function evalArgEq(op, actual, right, ctx) {
|
|
|
260
138
|
? { permit: true }
|
|
261
139
|
: { permit: false, reason: 'ARG_MISMATCH' };
|
|
262
140
|
}
|
|
263
|
-
if (op === 'eq' && right.kind === 'literal_u64') {
|
|
264
|
-
if (!actual)
|
|
265
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
266
|
-
return actual.type === 'u64' && actual.value === right.value
|
|
267
|
-
? { permit: true }
|
|
268
|
-
: { permit: false, reason: 'ARG_MISMATCH' };
|
|
269
|
-
}
|
|
270
|
-
if (op === 'eq' && right.kind === 'literal_bytes') {
|
|
271
|
-
if (!actual)
|
|
272
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
273
|
-
return actual.type === 'bytes' && actual.value === right.value
|
|
274
|
-
? { permit: true }
|
|
275
|
-
: { permit: false, reason: 'ARG_MISMATCH' };
|
|
276
|
-
}
|
|
277
141
|
// Anything else: fail closed on opacity (cannot decode the arg reliably).
|
|
278
142
|
if (!actual || actual.type === 'other')
|
|
279
143
|
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
280
144
|
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
281
145
|
}
|
|
282
|
-
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* `ARG_MISMATCH`. A scaled-on-scaled compare denies `ARG_MISMATCH`
|
|
287
|
-
* (no definable semantics). The non-scaled operand must be numeric
|
|
288
|
-
* (`call_arg` carrying a number, or a numeric literal); else `ARG_MISMATCH`. */
|
|
289
|
-
function evalScaledArgCompare(op, left, right, ctx) {
|
|
290
|
-
let scaled;
|
|
291
|
-
let other;
|
|
292
|
-
let scaledOnRight;
|
|
293
|
-
if (left.kind === 'call_arg_scaled') {
|
|
294
|
-
scaled = left;
|
|
295
|
-
other = right;
|
|
296
|
-
scaledOnRight = false;
|
|
297
|
-
}
|
|
298
|
-
else if (right.kind === 'call_arg_scaled') {
|
|
299
|
-
scaled = right;
|
|
300
|
-
other = left;
|
|
301
|
-
scaledOnRight = true;
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
// Programming error in the dispatcher; fail closed.
|
|
305
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
306
|
-
}
|
|
307
|
-
// Out-of-bounds or non-numeric arg fails closed as ARG_MISMATCH (not
|
|
308
|
-
// SLIPPAGE_FLOOR) - a violated floor is the wrong code when the operand
|
|
309
|
-
// itself could not be read.
|
|
310
|
-
if (scaled.index >= ctx.args.length) {
|
|
311
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
312
|
-
}
|
|
313
|
-
const input = argNumericBigInt(ctx.args[scaled.index]);
|
|
314
|
-
if (input === null)
|
|
315
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
316
|
-
let num;
|
|
317
|
-
let den;
|
|
318
|
-
try {
|
|
319
|
-
num = BigInt(scaled.num);
|
|
320
|
-
den = BigInt(scaled.den);
|
|
321
|
-
}
|
|
322
|
-
catch {
|
|
323
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
324
|
-
}
|
|
325
|
-
// Install refuses `den == 0` and `num <= 0` / `den <= 0`. The runtime
|
|
326
|
-
// check is defensive belt-and-braces so a validator regression cannot
|
|
327
|
-
// panic the frame on a divide-by-zero.
|
|
328
|
-
if (den === 0n)
|
|
329
|
-
return { permit: false, reason: 'ARITHMETIC_OVERFLOW' };
|
|
330
|
-
const product = input * num;
|
|
331
|
-
let scaledValue;
|
|
332
|
-
try {
|
|
333
|
-
// BigInt division truncates toward zero (matches Rust `i128::checked_div`).
|
|
334
|
-
scaledValue = product / den;
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
return { permit: false, reason: 'ARITHMETIC_OVERFLOW' };
|
|
338
|
-
}
|
|
339
|
-
// i128 range check: the contract would have wrapped on i128 arithmetic;
|
|
340
|
-
// surface the same deny.
|
|
341
|
-
const I128_MAX = (1n << 127n) - 1n;
|
|
342
|
-
const I128_MIN = -(1n << 127n);
|
|
343
|
-
if (scaledValue > I128_MAX || scaledValue < I128_MIN) {
|
|
344
|
-
return { permit: false, reason: 'ARITHMETIC_OVERFLOW' };
|
|
345
|
-
}
|
|
346
|
-
let otherVal;
|
|
347
|
-
if (other.kind === 'call_arg') {
|
|
348
|
-
otherVal = argNumericBigInt(ctx.args[other.index]);
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
otherVal = literalNumericBigInt(other);
|
|
352
|
-
}
|
|
353
|
-
if (otherVal === null)
|
|
354
|
-
return { permit: false, reason: 'ARG_MISMATCH' };
|
|
355
|
-
const pass = scaledOnRight
|
|
356
|
-
? bigintCmp(op, otherVal.toString(), scaledValue.toString())
|
|
357
|
-
: bigintCmp(op, scaledValue.toString(), otherVal.toString());
|
|
358
|
-
return pass ? { permit: true } : { permit: false, reason: 'SLIPPAGE_FLOOR' };
|
|
359
|
-
}
|
|
360
|
-
/** Ordered numeric comparison (lt/lte/gt/gte) on a `call_arg`. The interpreter
|
|
361
|
-
* reads the arg as an integer (i128 / u64 / u32 on the recorder's ScVal
|
|
362
|
-
* surface) and compares it to a numeric literal via BigInt. A non-numeric arg
|
|
363
|
-
* or a non-numeric literal fails closed (ARG_MISMATCH) rather than permitting
|
|
364
|
-
* an undecidable bound. Backs the SoroSwap input-amount cap
|
|
365
|
-
* (`call_arg[0] <= limit`). */
|
|
146
|
+
/** Ordered numeric comparison (lte) on a `call_arg`. The interpreter reads the
|
|
147
|
+
* arg as an integer (i128 on the recorder's ScVal surface) and compares it
|
|
148
|
+
* to a numeric literal via BigInt. A non-numeric arg or a non-numeric literal
|
|
149
|
+
* fails closed (ARG_MISMATCH) rather than permitting an undecidable bound. */
|
|
366
150
|
function evalArgOrderedCompare(op, actual, right) {
|
|
367
151
|
const actualInt = argNumericBigInt(actual);
|
|
368
152
|
const literalInt = literalNumericBigInt(right);
|
|
@@ -373,12 +157,12 @@ function evalArgOrderedCompare(op, actual, right) {
|
|
|
373
157
|
? { permit: true }
|
|
374
158
|
: { permit: false, reason: 'ARG_MISMATCH' };
|
|
375
159
|
}
|
|
376
|
-
/** BigInt value of a numeric-integer ScVal arg (i128 /
|
|
160
|
+
/** BigInt value of a numeric-integer ScVal arg (i128 / u32), or null when
|
|
377
161
|
* the arg is absent, opaque, or a non-numeric type. */
|
|
378
162
|
function argNumericBigInt(actual) {
|
|
379
163
|
if (!actual)
|
|
380
164
|
return null;
|
|
381
|
-
if (actual.type === 'i128' || actual.type === '
|
|
165
|
+
if (actual.type === 'i128' || actual.type === 'u32') {
|
|
382
166
|
try {
|
|
383
167
|
return BigInt(actual.value);
|
|
384
168
|
}
|
|
@@ -388,10 +172,27 @@ function argNumericBigInt(actual) {
|
|
|
388
172
|
}
|
|
389
173
|
return null;
|
|
390
174
|
}
|
|
175
|
+
/** BigInt value of a numeric-integer literal leaf (`literal_i128` / `literal_u32`),
|
|
176
|
+
* or null for a non-numeric literal. Callers fail closed on null. */
|
|
177
|
+
function literalNumericBigInt(leaf) {
|
|
178
|
+
switch (leaf.kind) {
|
|
179
|
+
case 'literal_i128':
|
|
180
|
+
try {
|
|
181
|
+
return BigInt(leaf.value);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
case 'literal_u32':
|
|
187
|
+
return BigInt(leaf.value);
|
|
188
|
+
default:
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
391
192
|
/** Element-by-element ordered comparison of an `ScVal[]` against a
|
|
392
193
|
* `PredicateLeaf[]`. Equal-length and equal at every index => permit.
|
|
393
194
|
* Length mismatch OR any element mismatch => deny ARG_MISMATCH. */
|
|
394
|
-
function compareVecExact(actual, expected
|
|
195
|
+
function compareVecExact(actual, expected) {
|
|
395
196
|
if (actual.length !== expected.length)
|
|
396
197
|
return false;
|
|
397
198
|
for (let i = 0; i < expected.length; i++) {
|
|
@@ -399,14 +200,13 @@ function compareVecExact(actual, expected, ctx) {
|
|
|
399
200
|
const a = actual[i];
|
|
400
201
|
if (!e || !a)
|
|
401
202
|
return false;
|
|
402
|
-
const r = evalArgEq('eq', a, e,
|
|
203
|
+
const r = evalArgEq('eq', a, e, {});
|
|
403
204
|
if (!r.permit)
|
|
404
205
|
return false;
|
|
405
206
|
}
|
|
406
207
|
return true;
|
|
407
208
|
}
|
|
408
|
-
/**
|
|
409
|
-
* fails closed. */
|
|
209
|
+
/** `in` membership. Empty haystack ALWAYS denies. Opaque needle fails closed. */
|
|
410
210
|
function evalIn(needle, haystack, ctx) {
|
|
411
211
|
if (haystack.length === 0)
|
|
412
212
|
return { permit: false, reason: 'NOT_IN_ALLOWLIST' };
|
|
@@ -446,14 +246,6 @@ function resolveLeaf(leaf, ctx) {
|
|
|
446
246
|
const entry = element.value.find((e) => e.key === leaf.field);
|
|
447
247
|
return entry ? entry.val : undefined;
|
|
448
248
|
}
|
|
449
|
-
case 'amount':
|
|
450
|
-
case 'window_spent':
|
|
451
|
-
case 'oracle_price':
|
|
452
|
-
case 'invocation_count_in_window':
|
|
453
|
-
case 'now':
|
|
454
|
-
case 'valid_until':
|
|
455
|
-
case 'call_arg_scaled':
|
|
456
|
-
return undefined; // selector leaves with no ScVal projection
|
|
457
249
|
case 'literal_address':
|
|
458
250
|
return { type: 'address', value: leaf.value };
|
|
459
251
|
case 'literal_i128':
|
|
@@ -462,90 +254,18 @@ function resolveLeaf(leaf, ctx) {
|
|
|
462
254
|
return { type: 'symbol', value: leaf.value };
|
|
463
255
|
case 'literal_u32':
|
|
464
256
|
return { type: 'u32', value: String(leaf.value) };
|
|
465
|
-
case 'literal_u64':
|
|
466
|
-
return { type: 'u64', value: leaf.value };
|
|
467
|
-
case 'literal_bytes':
|
|
468
|
-
return { type: 'bytes', value: leaf.value };
|
|
469
257
|
case 'literal_vec':
|
|
470
258
|
return undefined;
|
|
471
259
|
}
|
|
472
260
|
}
|
|
473
|
-
/**
|
|
474
|
-
* per-token BigInt record (current vs. rolling) to an i128 literal; the
|
|
475
|
-
* compare + denial reason are identical, so a single helper handles both. */
|
|
476
|
-
function evalAmountCompare(op, token, right, ctx, record) {
|
|
477
|
-
const literal = right.kind === 'literal_i128' ? right.value : null;
|
|
478
|
-
if (literal === null)
|
|
479
|
-
return { permit: false, reason: 'AMOUNT_BOUND' };
|
|
480
|
-
const actual = ctx[record][token] ?? '0';
|
|
481
|
-
return bigintCmp(op, actual, literal)
|
|
482
|
-
? { permit: true }
|
|
483
|
-
: { permit: false, reason: 'AMOUNT_BOUND' };
|
|
484
|
-
}
|
|
485
|
-
/** Step 6: window_spent compare on BigInt. */
|
|
486
|
-
function evalWindowSpentCompare(op, token, right, ctx) {
|
|
487
|
-
return evalAmountCompare(op, token, right, ctx, 'windowSpentByToken');
|
|
488
|
-
}
|
|
489
|
-
/** Step 7: invocation_count_in_window compare. */
|
|
490
|
-
function evalFrequencyCompare(op, windowSecs, right, ctx) {
|
|
491
|
-
const literal = right.kind === 'literal_u32' ? String(right.value) : null;
|
|
492
|
-
if (literal === null)
|
|
493
|
-
return { permit: false, reason: 'FREQUENCY' };
|
|
494
|
-
const actual = String(ctx.invocationCountByWindow[windowSecs] ?? 0);
|
|
495
|
-
return bigintCmp(op, actual, literal) ? { permit: true } : { permit: false, reason: 'FREQUENCY' };
|
|
496
|
-
}
|
|
497
|
-
/** Step 8: oracle_price compare. Reads `ctx.oraclePriceByAsset[asset]`. Any
|
|
498
|
-
* error entry OR a missing key throws `OracleError` (FATAL). A satisfied
|
|
499
|
-
* compare permits. */
|
|
500
|
-
function evalOracleCompare(op, asset, right, ctx) {
|
|
501
|
-
const entry = ctx.oraclePriceByAsset[asset];
|
|
502
|
-
if (!entry)
|
|
503
|
-
throw new OracleError('ORACLE_STALE');
|
|
504
|
-
if ('error' in entry) {
|
|
505
|
-
const mapped = ORACLE_ERROR_CODES[entry.error];
|
|
506
|
-
if (!mapped)
|
|
507
|
-
throw new OracleError('ORACLE_STALE');
|
|
508
|
-
throw new OracleError(mapped);
|
|
509
|
-
}
|
|
510
|
-
// Mirrors eval_oracle_compare in dsl.rs. The threshold MUST declare its
|
|
511
|
-
// decimal basis: prices are on the normalised 9-dp basis, and a bare literal
|
|
512
|
-
// is refused (not assumed) because a raw 14-dp threshold that assumed the
|
|
513
|
-
// 9-dp basis would permit everything.
|
|
514
|
-
if (right.kind !== 'oracle_threshold')
|
|
515
|
-
throw new OracleError('ORACLE_DECIMALS_MISMATCH');
|
|
516
|
-
if (right.decimals > MAX_ORACLE_THRESHOLD_DECIMALS) {
|
|
517
|
-
throw new OracleError('ORACLE_THRESHOLD_DECIMALS_OUT_OF_RANGE');
|
|
518
|
-
}
|
|
519
|
-
// Scale BOTH sides up to the wider basis; dividing the threshold down
|
|
520
|
-
// truncates and moves the permit boundary.
|
|
521
|
-
const decimals = BigInt(right.decimals);
|
|
522
|
-
const normalised = BigInt(NORMALISED_DECIMALS);
|
|
523
|
-
const common = decimals > normalised ? decimals : normalised;
|
|
524
|
-
const priceScaled = BigInt(entry.price) * 10n ** (common - normalised);
|
|
525
|
-
const literalScaled = BigInt(right.value) * 10n ** (common - decimals);
|
|
526
|
-
// A violated oracle threshold is a stateful bound (Rust dsl.rs:276 returns
|
|
527
|
-
// `DenyReason::StatefulBound`, deny code #104). The TS evaluator must mirror
|
|
528
|
-
// the same reason so a future TS/Rust reason divergence fails CI; the
|
|
529
|
-
// cross-layer harness in this file now asserts reason codes match.
|
|
530
|
-
return bigintCmp(op, String(priceScaled), String(literalScaled))
|
|
531
|
-
? { permit: true }
|
|
532
|
-
: { permit: false, reason: 'STATEFUL_BOUND' };
|
|
533
|
-
}
|
|
534
|
-
/** BigInt compare helper. `eq` is also supported by callers (selector-vs-literal
|
|
535
|
-
* equal checks), but the dedicated arg path uses it via `BigInt(actual) === BigInt(right)`. */
|
|
261
|
+
/** BigInt compare helper. */
|
|
536
262
|
function bigintCmp(op, aStr, bStr) {
|
|
537
263
|
const a = BigInt(aStr);
|
|
538
264
|
const b = BigInt(bStr);
|
|
539
265
|
switch (op) {
|
|
540
266
|
case 'eq':
|
|
541
267
|
return a === b;
|
|
542
|
-
case 'lt':
|
|
543
|
-
return a < b;
|
|
544
268
|
case 'lte':
|
|
545
269
|
return a <= b;
|
|
546
|
-
case 'gt':
|
|
547
|
-
return a > b;
|
|
548
|
-
case 'gte':
|
|
549
|
-
return a >= b;
|
|
550
270
|
}
|
|
551
271
|
}
|
package/dist/synth/index.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
export { isStellarAddress } from './address.ts';
|
|
2
2
|
export { type ComposeOptions, type ComposeResult, composeFromRecording, } from './compose-from-recording.ts';
|
|
3
|
-
export { type DenyCase, type GeneratedCases, generateCases, } from './deny-cases.ts';
|
|
4
|
-
export { type EvalContext, type EvalResult, evaluate } from './evaluate.ts';
|
|
5
|
-
export { type HarnessFailure, type HarnessResult, runHarness, } from './harness.ts';
|
|
6
3
|
export { type IntentFacts, lower } from './lower.ts';
|
|
7
|
-
export { minimize } from './minimize.ts';
|
|
8
4
|
export { type DecideScopeOptions, decideScope, type ScopeDecision, scopeToContextRuleType, } from './scope.ts';
|
|
9
|
-
export { synthesizeFromMandate } from './synthesize-from-mandate.ts';
|
|
10
5
|
export { type __TestInterpreterAdapterOptions, type SynthesizeFromRecordingOptions, synthesizeFromRecording, } from './synthesize-from-recording.ts';
|
package/dist/synth/index.js
CHANGED
|
@@ -4,11 +4,6 @@
|
|
|
4
4
|
// and re-deriving it elsewhere means a second address check that can drift.
|
|
5
5
|
export { isStellarAddress } from "./address.js";
|
|
6
6
|
export { composeFromRecording, } from "./compose-from-recording.js";
|
|
7
|
-
export { generateCases, } from "./deny-cases.js";
|
|
8
|
-
export { evaluate } from "./evaluate.js";
|
|
9
|
-
export { runHarness, } from "./harness.js";
|
|
10
7
|
export { lower } from "./lower.js";
|
|
11
|
-
export { minimize } from "./minimize.js";
|
|
12
8
|
export { decideScope, scopeToContextRuleType, } from "./scope.js";
|
|
13
|
-
export { synthesizeFromMandate } from "./synthesize-from-mandate.js";
|
|
14
9
|
export { synthesizeFromRecording, } from "./synthesize-from-recording.js";
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RecordedTransaction } from '../types.ts';
|
|
2
2
|
import type { EvalContext } from './evaluate.ts';
|
|
3
3
|
export interface PermitContextResponses {
|
|
4
4
|
windowSeconds: number;
|
|
5
|
-
invocationLimit?: number;
|
|
6
5
|
limitAmount?: string;
|
|
7
6
|
validUntilLedger: number;
|
|
8
|
-
oraclePriceBound?: Array<{
|
|
9
|
-
asset: string;
|
|
10
|
-
operator: string;
|
|
11
|
-
value: string;
|
|
12
|
-
decimals: number;
|
|
13
|
-
}>;
|
|
14
7
|
}
|
|
15
|
-
export declare function buildPermitContext(tx: RecordedTransaction, responses: PermitContextResponses
|
|
8
|
+
export declare function buildPermitContext(tx: RecordedTransaction, responses: PermitContextResponses): EvalContext;
|