@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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Pure function. Maps a `PredicateNode` AST to the canonical ScVal wire format:
|
|
5
5
|
// - every node is a `ScVal::Vec` whose head element is the tag `ScVal::Symbol`
|
|
6
|
-
// - children of `and`
|
|
6
|
+
// - children of `and` are sorted ascending by their canonical XDR bytes
|
|
7
7
|
// - `in` haystacks are ALWAYS sorted by canonical XDR bytes (pure set
|
|
8
8
|
// membership); an EXACT ordered sequence is expressed as
|
|
9
9
|
// `eq(selector, literal_vec)` where the `literal_vec` element order is
|
|
@@ -18,12 +18,6 @@
|
|
|
18
18
|
// Caps from `PREDICATE_CAPS` are enforced BEFORE returning; a cap breach throws
|
|
19
19
|
// a `ToolError` with the matching error code and `severity: 'error'`.
|
|
20
20
|
//
|
|
21
|
-
// One gap remains, deliberately: the `amount` / `window_spent` leaf branches
|
|
22
|
-
// below are dead ABI - the contract's grammar no longer has those selector
|
|
23
|
-
// symbols, so a predicate carrying one is MALFORMED at decode. The interpreter
|
|
24
|
-
// adapter reports them as uncovered before lowering, so the product path
|
|
25
|
-
// cannot emit one; removing the branches (and the leaf kinds) is a separate
|
|
26
|
-
// change.
|
|
27
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
22
|
exports.encodePredicate = encodePredicate;
|
|
29
23
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -48,27 +42,22 @@ function encodePredicate(node) {
|
|
|
48
42
|
throw capError('IN_OPERAND_LIMIT', `\`in\` operand count ${c.count} exceeds MAX_IN_OPERAND_COUNT ${types_ts_1.PREDICATE_CAPS.MAX_IN_OPERAND_COUNT}`);
|
|
49
43
|
}
|
|
50
44
|
}
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
// Minimum constraint. A predicate with no selector leaf compares literals
|
|
46
|
+
// to literals, so it is trivially true or false and would permit everything
|
|
47
|
+
// or nothing forever. The contract refuses it at install with
|
|
48
|
+
// SELECTOR_LEAF_REQUIRED (216); refuse it here too so the failure is caught
|
|
49
|
+
// before a transaction is built.
|
|
50
|
+
if (stats.selectorLeaves === 0) {
|
|
51
|
+
throw capError('MALFORMED_PREDICATE', 'predicate constrains nothing: every comparison has literals on both sides. The contract refuses it at install (SELECTOR_LEAF_REQUIRED). Pin the call itself - contract, method or argument.');
|
|
55
52
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// - u32 fields in range (call_arg index, literal_u32 value, oracle
|
|
65
|
-
// threshold decimals, etc.) - the contract decodes as u32
|
|
66
|
-
// - i128 positivity where required (literal_i128 for amount/window
|
|
67
|
-
// caps; `den`/`num` for scaled ratios) - a negative cap would
|
|
68
|
-
// permit everything
|
|
69
|
-
// - hex even-length (literal_bytes) - `Buffer.from(v, 'hex')` silently
|
|
70
|
-
// drops non-hex chars, so 'zz' becomes empty bytes
|
|
71
|
-
// - scaled-ratio num>0 && den>0 - mirrors dsl.rs:661-704
|
|
53
|
+
// --- pass 1.5: leaf-value validation against the cap-set gate the contract
|
|
54
|
+
// enforces at install. Defense in depth: the TS self-verify pipeline should
|
|
55
|
+
// reject the same shapes Rust install refuses, so a hand-crafted predicate
|
|
56
|
+
// that simulate/verify green-lights cannot later be refused at the on-chain
|
|
57
|
+
// install step. The checks are the u32 ranges on the argument selectors
|
|
58
|
+
// (`call_arg.index`, `call_arg_len.index`, `call_arg_field.index` and
|
|
59
|
+
// `.element`) - the contract decodes each as u32, so an out-of-range value
|
|
60
|
+
// would fail there instead.
|
|
72
61
|
// Throws `MALFORMED_PREDICATE` so the error stays a ToolError shape.
|
|
73
62
|
validateLeafValues(node);
|
|
74
63
|
// --- pass 2: build + canonicalise the ScVal ---
|
|
@@ -83,48 +72,35 @@ function encodePredicate(node) {
|
|
|
83
72
|
}
|
|
84
73
|
function computeStats(node) {
|
|
85
74
|
const inCounts = [];
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const { depth, leaves } = walk(node, inCounts, oracleAssets, counters, false);
|
|
75
|
+
const counters = { selectorLeaves: 0 };
|
|
76
|
+
const { depth, leaves } = walk(node, inCounts, counters);
|
|
89
77
|
return {
|
|
90
78
|
depth,
|
|
91
79
|
leaves,
|
|
92
80
|
inCounts,
|
|
93
|
-
|
|
94
|
-
nonOracleSelectorLeaves: counters.nonOracleSelectorLeaves,
|
|
81
|
+
selectorLeaves: counters.selectorLeaves,
|
|
95
82
|
};
|
|
96
83
|
}
|
|
97
|
-
function walk(node, inCounts,
|
|
98
|
-
/** True once the walk is under a `not` or an `or`, where an oracle read
|
|
99
|
-
* becomes a condition the caller can satisfy by taking the other branch. */
|
|
100
|
-
negatedOrDisjunctive) {
|
|
84
|
+
function walk(node, inCounts, counters) {
|
|
101
85
|
switch (node.op) {
|
|
102
|
-
case 'and':
|
|
103
|
-
case 'or': {
|
|
86
|
+
case 'and': {
|
|
104
87
|
if (node.children.length === 0) {
|
|
105
88
|
throw capError('MALFORMED_PREDICATE', `\`${node.op}\` with no children: the contract refuses it at decode (MALFORMED_PREDICATE), so it can never be installed`);
|
|
106
89
|
}
|
|
107
90
|
let maxChildDepth = 0;
|
|
108
91
|
let totalLeaves = 0;
|
|
109
92
|
for (const c of node.children) {
|
|
110
|
-
const child = walk(c, inCounts,
|
|
93
|
+
const child = walk(c, inCounts, counters);
|
|
111
94
|
if (child.depth > maxChildDepth)
|
|
112
95
|
maxChildDepth = child.depth;
|
|
113
96
|
totalLeaves += child.leaves;
|
|
114
97
|
}
|
|
115
98
|
return { depth: maxChildDepth + 1, leaves: totalLeaves };
|
|
116
99
|
}
|
|
117
|
-
case 'not': {
|
|
118
|
-
const child = walk(node.child, inCounts, oracleAssets, counters, true);
|
|
119
|
-
return { depth: child.depth + 1, leaves: child.leaves };
|
|
120
|
-
}
|
|
121
100
|
case 'eq':
|
|
122
|
-
case '
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
case 'gte': {
|
|
126
|
-
collectOracle(node.left, oracleAssets, counters, negatedOrDisjunctive);
|
|
127
|
-
collectOracle(node.right, oracleAssets, counters, negatedOrDisjunctive);
|
|
101
|
+
case 'lte': {
|
|
102
|
+
collectSelector(node.left, counters);
|
|
103
|
+
collectSelector(node.right, counters);
|
|
128
104
|
return { depth: 1, leaves: leafCount(node.left) + leafCount(node.right) };
|
|
129
105
|
}
|
|
130
106
|
case 'in': {
|
|
@@ -132,10 +108,10 @@ negatedOrDisjunctive) {
|
|
|
132
108
|
throw capError('MALFORMED_PREDICATE', '`in` with an empty haystack: the contract refuses it at decode (MALFORMED_PREDICATE), so it can never be installed');
|
|
133
109
|
}
|
|
134
110
|
inCounts.push({ count: node.haystack.length });
|
|
135
|
-
|
|
111
|
+
collectSelector(node.needle, counters);
|
|
136
112
|
let haystackLeaves = 0;
|
|
137
113
|
for (const h of node.haystack) {
|
|
138
|
-
|
|
114
|
+
collectSelector(h, counters);
|
|
139
115
|
haystackLeaves += leafCount(h);
|
|
140
116
|
}
|
|
141
117
|
return {
|
|
@@ -157,45 +133,27 @@ function leafCount(leaf) {
|
|
|
157
133
|
}
|
|
158
134
|
return 1;
|
|
159
135
|
}
|
|
160
|
-
function
|
|
161
|
-
// literal_vec is the only nested leaf; recurse so
|
|
162
|
-
// vector literal
|
|
163
|
-
|
|
164
|
-
if (leaf.kind === 'oracle_price') {
|
|
165
|
-
if (negatedOrDisjunctive) {
|
|
166
|
-
throw capError('ORACLE_LEAF_INVALID_POSITION', `oracle_price(${leaf.asset}) sits under a \`not\`/\`or\`: the contract refuses that position at install (dsl.rs validate_oracle_placement). An oracle bound must be a conjunct the call has to satisfy.`);
|
|
167
|
-
}
|
|
168
|
-
oracleAssets.add(leaf.asset);
|
|
169
|
-
}
|
|
170
|
-
else if (leaf.kind === 'literal_vec') {
|
|
136
|
+
function collectSelector(leaf, counters) {
|
|
137
|
+
// literal_vec is the only nested leaf; recurse so a selector buried in a
|
|
138
|
+
// vector literal is still counted. Mirrors `dsl::has_selector_leaf`.
|
|
139
|
+
if (leaf.kind === 'literal_vec') {
|
|
171
140
|
for (const el of leaf.elements)
|
|
172
|
-
|
|
141
|
+
collectSelector(el, counters);
|
|
173
142
|
}
|
|
174
|
-
else if (!leaf.kind.startsWith('literal_')
|
|
175
|
-
|
|
176
|
-
// constraint. Counting it would let an oracle-only predicate satisfy the
|
|
177
|
-
// non-oracle envelope with its own bound (mirrors collect_oracle_leaf in
|
|
178
|
-
// dsl.rs, where it sits with the literals).
|
|
179
|
-
counters.nonOracleSelectorLeaves += 1;
|
|
143
|
+
else if (!leaf.kind.startsWith('literal_')) {
|
|
144
|
+
counters.selectorLeaves += 1;
|
|
180
145
|
}
|
|
181
146
|
}
|
|
182
147
|
function encodeNode(node) {
|
|
183
148
|
switch (node.op) {
|
|
184
|
-
case 'and':
|
|
185
|
-
case 'or': {
|
|
149
|
+
case 'and': {
|
|
186
150
|
const encoded = node.children.map(encodeNode);
|
|
187
151
|
// sort children by their canonical XDR bytes ascending.
|
|
188
152
|
const sorted = sortByCanonicalBytes(encoded);
|
|
189
153
|
return stellar_sdk_1.xdr.ScVal.scvVec([symbol(node.op), stellar_sdk_1.xdr.ScVal.scvVec(sorted)]);
|
|
190
154
|
}
|
|
191
|
-
case 'not': {
|
|
192
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([symbol('not'), encodeNode(node.child)]);
|
|
193
|
-
}
|
|
194
155
|
case 'eq':
|
|
195
|
-
case '
|
|
196
|
-
case 'lte':
|
|
197
|
-
case 'gt':
|
|
198
|
-
case 'gte': {
|
|
156
|
+
case 'lte': {
|
|
199
157
|
return stellar_sdk_1.xdr.ScVal.scvVec([symbol(node.op), encodeLeaf(node.left), encodeLeaf(node.right)]);
|
|
200
158
|
}
|
|
201
159
|
case 'in': {
|
|
@@ -226,47 +184,6 @@ function encodeLeaf(leaf) {
|
|
|
226
184
|
stellar_sdk_1.xdr.ScVal.scvU32(leaf.element),
|
|
227
185
|
stellar_sdk_1.xdr.ScVal.scvSymbol(leaf.field),
|
|
228
186
|
]);
|
|
229
|
-
case 'call_arg_scaled':
|
|
230
|
-
// Wire shape `vec[symbol, u32, i128, i128]` mirrors the Rust decoder's
|
|
231
|
-
// expectation in dsl.rs SEL_CALL_ARG_SCALED. `scvI128FromDecimal` is
|
|
232
|
-
// the same range-checked helper used by `literal_i128`; an out-of-range
|
|
233
|
-
// num/den is refused at encode time (rather than at install on chain).
|
|
234
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([
|
|
235
|
-
symbol('call_arg_scaled'),
|
|
236
|
-
stellar_sdk_1.xdr.ScVal.scvU32(leaf.index),
|
|
237
|
-
scvI128FromDecimal(leaf.num),
|
|
238
|
-
scvI128FromDecimal(leaf.den),
|
|
239
|
-
]);
|
|
240
|
-
case 'amount':
|
|
241
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([symbol('amount'), scvAddressFromStrkey(leaf.token)]);
|
|
242
|
-
case 'window_spent':
|
|
243
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([
|
|
244
|
-
symbol('window_spent'),
|
|
245
|
-
scvAddressFromStrkey(leaf.token),
|
|
246
|
-
scvU64FromValue(leaf.windowSeconds),
|
|
247
|
-
]);
|
|
248
|
-
case 'now':
|
|
249
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([symbol('now')]);
|
|
250
|
-
case 'valid_until':
|
|
251
|
-
// The interpreter refuses this leaf at install: it never sources
|
|
252
|
-
// valid_until_ledger, so a policy built on it would deny forever.
|
|
253
|
-
// Expiry belongs to the smart account, through the context rule's
|
|
254
|
-
// validUntilLedger. Refuse at synthesis so the author finds out here
|
|
255
|
-
// rather than from an install that always fails.
|
|
256
|
-
throw new Error('valid_until is not a usable predicate leaf: the interpreter never sources it and ' +
|
|
257
|
-
'refuses it at install. Put expiry on the context rule (validUntilLedger) instead.');
|
|
258
|
-
case 'invocation_count_in_window':
|
|
259
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([symbol('invocation_count'), scvU64FromValue(leaf.windowSecs)]);
|
|
260
|
-
case 'oracle_price':
|
|
261
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([symbol('oracle_price'), scvAddressFromStrkey(leaf.asset)]);
|
|
262
|
-
case 'oracle_threshold':
|
|
263
|
-
// Arity 3, matching SEL_ORACLE_THRESHOLD in dsl.rs. The declared basis
|
|
264
|
-
// travels with the value so the contract never has to assume one.
|
|
265
|
-
return stellar_sdk_1.xdr.ScVal.scvVec([
|
|
266
|
-
symbol('oracle_threshold'),
|
|
267
|
-
scvI128FromDecimal(leaf.value),
|
|
268
|
-
stellar_sdk_1.xdr.ScVal.scvU32(leaf.decimals),
|
|
269
|
-
]);
|
|
270
187
|
case 'literal_address':
|
|
271
188
|
return scvAddressFromStrkey(leaf.value);
|
|
272
189
|
case 'literal_i128':
|
|
@@ -275,10 +192,6 @@ function encodeLeaf(leaf) {
|
|
|
275
192
|
return stellar_sdk_1.xdr.ScVal.scvSymbol(leaf.value);
|
|
276
193
|
case 'literal_u32':
|
|
277
194
|
return stellar_sdk_1.xdr.ScVal.scvU32(leaf.value);
|
|
278
|
-
case 'literal_u64':
|
|
279
|
-
return scvU64FromValue(leaf.value);
|
|
280
|
-
case 'literal_bytes':
|
|
281
|
-
return stellar_sdk_1.xdr.ScVal.scvBytes(Buffer.from(leaf.value, 'hex'));
|
|
282
195
|
case 'literal_vec':
|
|
283
196
|
// Bare ScVal::Vec of element encodings - order is preserved verbatim
|
|
284
197
|
// because the order IS the semantic (exact ordered sequence equality).
|
|
@@ -296,11 +209,6 @@ function sortByCanonicalBytes(values) {
|
|
|
296
209
|
function scvAddressFromStrkey(strkey) {
|
|
297
210
|
return stellar_sdk_1.xdr.ScVal.scvAddress(stellar_sdk_1.Address.fromString(strkey).toScAddress());
|
|
298
211
|
}
|
|
299
|
-
function scvU64FromValue(value) {
|
|
300
|
-
// The Uint64 (UnsignedHyper) constructor accepts string | bigint | number;
|
|
301
|
-
// string is safest for values > 2^53.
|
|
302
|
-
return stellar_sdk_1.xdr.ScVal.scvU64(new stellar_sdk_1.xdr.Uint64(String(value)));
|
|
303
|
-
}
|
|
304
212
|
/** Build `ScVal::I128(Int128Parts{hi, lo})` from a signed decimal string.
|
|
305
213
|
* `Int128Parts` encodes the value as `(hi << 64) + lo` with `hi` a SIGNED
|
|
306
214
|
* 64-bit int and `lo` an UNSIGNED 64-bit int (this is NOT signed-magnitude).
|
|
@@ -316,9 +224,8 @@ function scvI128FromDecimal(decimal) {
|
|
|
316
224
|
if (hi < INT64_MIN || hi > INT64_MAX) {
|
|
317
225
|
throw capError('MALFORMED_PREDICATE', `literal_i128 value ${decimal} is outside the Int128 range`);
|
|
318
226
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
227
|
+
// `lo` needs no range check: masking with UINT64_MAX puts it in
|
|
228
|
+
// [0, 2^64-1] for every v, negative included.
|
|
322
229
|
return stellar_sdk_1.xdr.ScVal.scvI128(new stellar_sdk_1.xdr.Int128Parts({
|
|
323
230
|
hi: new stellar_sdk_1.xdr.Int64(hi),
|
|
324
231
|
lo: new stellar_sdk_1.xdr.Uint64(lo),
|
|
@@ -334,19 +241,13 @@ function capError(code, message) {
|
|
|
334
241
|
// u32 boundary - the same constant the contract decodes with. A value above
|
|
335
242
|
// this either overflows during encode or is refused at install.
|
|
336
243
|
const U32_MAX = 4294967295;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* positivity, `literal_bytes` hex even-length, `oracle_threshold` decimals
|
|
345
|
-
* range, `call_arg_scaled` positive-ratio). Defense in depth so the TS
|
|
346
|
-
* self-verify pipeline rejects the same shapes Rust install already
|
|
347
|
-
* refuses - a hand-crafted predicate that simulate/verify green-lights must
|
|
348
|
-
* NOT be installable. Throws `MALFORMED_PREDICATE` so the envelope shapes
|
|
349
|
-
* it into a ToolError. */
|
|
244
|
+
/** Walk a `PredicateNode` and fail-closed on any leaf whose value the contract
|
|
245
|
+
* would refuse at install: the argument selectors carry u32 indices, so a
|
|
246
|
+
* value outside that range is refused here rather than on chain. Defense in
|
|
247
|
+
* depth so the TS self-verify pipeline rejects the same shapes Rust install
|
|
248
|
+
* already refuses - a hand-crafted predicate that simulate/verify
|
|
249
|
+
* green-lights must NOT be installable. Throws `MALFORMED_PREDICATE` so the
|
|
250
|
+
* envelope shapes it into a ToolError. */
|
|
350
251
|
function validateLeafValues(node) {
|
|
351
252
|
function walkLeaf(leaf, path) {
|
|
352
253
|
switch (leaf.kind) {
|
|
@@ -368,107 +269,22 @@ function validateLeafValues(node) {
|
|
|
368
269
|
throw malformed(`call_arg_field.element out of u32 range at ${path}`);
|
|
369
270
|
}
|
|
370
271
|
return;
|
|
371
|
-
case 'call_arg_scaled': {
|
|
372
|
-
if (!Number.isInteger(leaf.index) || leaf.index < 0 || leaf.index > U32_MAX) {
|
|
373
|
-
throw malformed(`call_arg_scaled.index out of u32 range at ${path}`);
|
|
374
|
-
}
|
|
375
|
-
// num / den are i128 on chain, decimal strings on the wire. The
|
|
376
|
-
// contract refuses `den == 0` and `num <= 0` / `den <= 0` at install
|
|
377
|
-
// (dsl.rs:664-672); mirror that here so a future regression in
|
|
378
|
-
// `validate_scaled_ratios` cannot let a divide-by-zero policy reach
|
|
379
|
-
// the wire. BigInt throws on non-numeric strings -> malformed.
|
|
380
|
-
let num;
|
|
381
|
-
let den;
|
|
382
|
-
try {
|
|
383
|
-
num = BigInt(leaf.num);
|
|
384
|
-
den = BigInt(leaf.den);
|
|
385
|
-
}
|
|
386
|
-
catch {
|
|
387
|
-
throw malformed(`call_arg_scaled.num/den not a decimal integer at ${path}`);
|
|
388
|
-
}
|
|
389
|
-
if (num <= 0n)
|
|
390
|
-
throw malformed(`call_arg_scaled.num must be > 0 at ${path}`);
|
|
391
|
-
if (den <= 0n)
|
|
392
|
-
throw malformed(`call_arg_scaled.den must be > 0 at ${path}`);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
case 'literal_u32':
|
|
396
|
-
if (!Number.isInteger(leaf.value) || leaf.value < 0 || leaf.value > U32_MAX) {
|
|
397
|
-
throw malformed(`literal_u32.value out of u32 range at ${path}`);
|
|
398
|
-
}
|
|
399
|
-
return;
|
|
400
|
-
case 'literal_i128':
|
|
401
|
-
// literal_i128 is signed; the contract allows negatives (i128
|
|
402
|
-
// arithmetic), but caps on a positive quantity (amount / window
|
|
403
|
-
// bound) should never be negative - a negative cap is silently
|
|
404
|
-
// satisfied by every non-negative amount. The contract gate is
|
|
405
|
-
// already on the leaf's ROLE (amount vs equality) not the value;
|
|
406
|
-
// here we mirror the value-only invariant the cap-set gate enforces
|
|
407
|
-
// by refusing the syntactic shape that would clearly be a bug
|
|
408
|
-
// (literal_i128 as a CAP with a leading `-` on a non-equality).
|
|
409
|
-
// We do not gate equality i128 - `literal_i128` as an address-by-
|
|
410
|
-
// equality is fine (it is just a constant).
|
|
411
|
-
// The value itself is always accepted; the structural check
|
|
412
|
-
// (non-negative for an amount / window bound) is left to the
|
|
413
|
-
// caller-built predicate, not the encoder.
|
|
414
|
-
return;
|
|
415
|
-
case 'literal_bytes':
|
|
416
|
-
// Hex even-length: a non-hex char silently drops, and an odd
|
|
417
|
-
// length yields a half-byte Buffer. The contract decodes with a
|
|
418
|
-
// strict hex parser and refuses anything that is not even-length
|
|
419
|
-
// hex; mirror that here.
|
|
420
|
-
if (!/^[0-9a-fA-F]*$/.test(leaf.value) || leaf.value.length % 2 !== 0) {
|
|
421
|
-
throw malformed(`literal_bytes.value must be even-length hex at ${path}`);
|
|
422
|
-
}
|
|
423
|
-
return;
|
|
424
|
-
case 'oracle_threshold':
|
|
425
|
-
if (!Number.isInteger(leaf.decimals) ||
|
|
426
|
-
leaf.decimals < 0 ||
|
|
427
|
-
leaf.decimals > MAX_ORACLE_THRESHOLD_DECIMALS) {
|
|
428
|
-
throw malformed(`oracle_threshold.decimals out of range (0..${MAX_ORACLE_THRESHOLD_DECIMALS}) at ${path}`);
|
|
429
|
-
}
|
|
430
|
-
return;
|
|
431
|
-
case 'literal_vec':
|
|
432
|
-
leaf.elements.forEach((e, i) => {
|
|
433
|
-
walkLeaf(e, `${path}.elements[${i}]`);
|
|
434
|
-
});
|
|
435
|
-
return;
|
|
436
|
-
// Selector and other leaves carry no cap-set values; the call_arg
|
|
437
|
-
// branches above cover indices, the literal branches cover typed
|
|
438
|
-
// constants. amount / window_spent / invocation_count / now /
|
|
439
|
-
// valid_until / call_contract / call_fn / literal_address /
|
|
440
|
-
// literal_symbol / literal_u64 / oracle_price are all value-free
|
|
441
|
-
// at this gate.
|
|
442
|
-
case 'amount':
|
|
443
|
-
case 'window_spent':
|
|
444
|
-
case 'invocation_count_in_window':
|
|
445
|
-
case 'now':
|
|
446
|
-
case 'valid_until':
|
|
447
272
|
case 'call_contract':
|
|
448
273
|
case 'call_fn':
|
|
449
274
|
case 'literal_address':
|
|
450
275
|
case 'literal_symbol':
|
|
451
|
-
case 'literal_u64':
|
|
452
|
-
case 'oracle_price':
|
|
453
276
|
return;
|
|
454
277
|
}
|
|
455
278
|
}
|
|
456
279
|
function walkNode(n, path) {
|
|
457
280
|
switch (n.op) {
|
|
458
281
|
case 'and':
|
|
459
|
-
case 'or':
|
|
460
282
|
n.children.forEach((c, i) => {
|
|
461
283
|
walkNode(c, `${path}.children[${i}]`);
|
|
462
284
|
});
|
|
463
285
|
return;
|
|
464
|
-
case 'not':
|
|
465
|
-
walkNode(n.child, `${path}.child`);
|
|
466
|
-
return;
|
|
467
286
|
case 'eq':
|
|
468
|
-
case 'lt':
|
|
469
287
|
case 'lte':
|
|
470
|
-
case 'gt':
|
|
471
|
-
case 'gte':
|
|
472
288
|
walkLeaf(n.left, `${path}.left`);
|
|
473
289
|
walkLeaf(n.right, `${path}.right`);
|
|
474
290
|
return;
|
|
@@ -20,15 +20,9 @@ function jsonToAst(value) {
|
|
|
20
20
|
const v = value;
|
|
21
21
|
switch (v.op) {
|
|
22
22
|
case 'and':
|
|
23
|
-
|
|
24
|
-
return { op: v.op, children: arrayOf(v.children, jsonToAst) };
|
|
25
|
-
case 'not':
|
|
26
|
-
return { op: 'not', child: jsonToAst(v.child) };
|
|
23
|
+
return { op: 'and', children: arrayOf(v.children, jsonToAst) };
|
|
27
24
|
case 'eq':
|
|
28
|
-
case 'lt':
|
|
29
25
|
case 'lte':
|
|
30
|
-
case 'gt':
|
|
31
|
-
case 'gte':
|
|
32
26
|
return { op: v.op, left: jsonToLeaf(v.left), right: jsonToLeaf(v.right) };
|
|
33
27
|
case 'in':
|
|
34
28
|
return { op: 'in', needle: jsonToLeaf(v.needle), haystack: arrayOf(v.haystack, jsonToLeaf) };
|
|
@@ -48,42 +42,11 @@ function jsonToLeaf(value) {
|
|
|
48
42
|
switch (v.kind) {
|
|
49
43
|
case 'call_contract':
|
|
50
44
|
case 'call_fn':
|
|
51
|
-
case 'now':
|
|
52
|
-
case 'valid_until':
|
|
53
45
|
return { kind: v.kind };
|
|
54
46
|
case 'call_arg':
|
|
55
47
|
return { kind: 'call_arg', index: numberField(v, 'index') };
|
|
56
48
|
case 'call_arg_len':
|
|
57
49
|
return { kind: 'call_arg_len', index: numberField(v, 'index') };
|
|
58
|
-
// num/den are strings, not numbers: they are i128 on chain, and a JSON
|
|
59
|
-
// number would silently lose precision past 2^53 before the encoder
|
|
60
|
-
// ever saw the value.
|
|
61
|
-
case 'call_arg_scaled':
|
|
62
|
-
return {
|
|
63
|
-
kind: 'call_arg_scaled',
|
|
64
|
-
index: numberField(v, 'index'),
|
|
65
|
-
num: stringField(v, 'num'),
|
|
66
|
-
den: stringField(v, 'den'),
|
|
67
|
-
};
|
|
68
|
-
case 'call_arg_field':
|
|
69
|
-
return {
|
|
70
|
-
kind: 'call_arg_field',
|
|
71
|
-
index: numberField(v, 'index'),
|
|
72
|
-
element: numberField(v, 'element'),
|
|
73
|
-
field: stringField(v, 'field'),
|
|
74
|
-
};
|
|
75
|
-
case 'amount':
|
|
76
|
-
return { kind: 'amount', token: stringField(v, 'token') };
|
|
77
|
-
case 'window_spent':
|
|
78
|
-
return {
|
|
79
|
-
kind: 'window_spent',
|
|
80
|
-
token: stringField(v, 'token'),
|
|
81
|
-
windowSeconds: numberField(v, 'windowSeconds'),
|
|
82
|
-
};
|
|
83
|
-
case 'invocation_count_in_window':
|
|
84
|
-
return { kind: 'invocation_count_in_window', windowSecs: numberField(v, 'windowSecs') };
|
|
85
|
-
case 'oracle_price':
|
|
86
|
-
return { kind: 'oracle_price', asset: stringField(v, 'asset') };
|
|
87
50
|
case 'literal_address':
|
|
88
51
|
return { kind: 'literal_address', value: stringField(v, 'value') };
|
|
89
52
|
case 'literal_i128':
|
|
@@ -92,10 +55,6 @@ function jsonToLeaf(value) {
|
|
|
92
55
|
return { kind: 'literal_symbol', value: stringField(v, 'value') };
|
|
93
56
|
case 'literal_u32':
|
|
94
57
|
return { kind: 'literal_u32', value: numberField(v, 'value') };
|
|
95
|
-
case 'literal_u64':
|
|
96
|
-
return { kind: 'literal_u64', value: stringField(v, 'value') };
|
|
97
|
-
case 'literal_bytes':
|
|
98
|
-
return { kind: 'literal_bytes', value: stringField(v, 'value') };
|
|
99
58
|
case 'literal_vec':
|
|
100
59
|
return { kind: 'literal_vec', elements: arrayOf(v.elements, jsonToLeaf) };
|
|
101
60
|
default:
|
|
@@ -340,14 +340,9 @@ function recordInvocation(contract, fn, args, network, knownSet, knownContractSe
|
|
|
340
340
|
// parseConfidence denominator isn't inflated by repeat calls), but each
|
|
341
341
|
// unrecognised invocation is recorded separately so the numerator
|
|
342
342
|
// reflects every fail-closed hit.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
knownOut.push(contract);
|
|
347
|
-
}
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
if ((0, identify_ts_1.identifyProtocol)(contract, fn, args, network ?? undefined)) {
|
|
343
|
+
// `||` short-circuits, so `identifyProtocol` is still only consulted when the
|
|
344
|
+
// contract is not already in `knownSet` - the same call order as before.
|
|
345
|
+
if (knownSet.has(contract) || (0, identify_ts_1.identifyProtocol)(contract, fn, args, network ?? undefined)) {
|
|
351
346
|
if (!knownContractSet.has(contract)) {
|
|
352
347
|
knownContractSet.add(contract);
|
|
353
348
|
knownOut.push(contract);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { ContextRuleDraft,
|
|
2
|
-
import type { SimulationResult } from '../verify/envelope.ts';
|
|
1
|
+
import type { ContextRuleDraft, PredicateNode } from '../types.ts';
|
|
3
2
|
export interface ReviewCardSummary {
|
|
4
3
|
ruleName: string;
|
|
5
4
|
plainEnglish: string;
|
|
@@ -17,7 +16,9 @@ export interface ReviewCardSummary {
|
|
|
17
16
|
}
|
|
18
17
|
/** Build a deterministic review-card summary from a policy + context rule +
|
|
19
18
|
* simulation result. Pure: same inputs -> byte-identical output. */
|
|
20
|
-
export declare function buildReviewCardSummary(predicate: PredicateNode | null,
|
|
19
|
+
export declare function buildReviewCardSummary(predicate: PredicateNode | null, contextRule: ContextRuleDraft,
|
|
20
|
+
/** Evaluator identity folded into the content hash. */
|
|
21
|
+
backend: 'interpreter-v1' | 'ts-model'): ReviewCardSummary;
|
|
21
22
|
/** Walk every comparison / membership node of the predicate and invoke
|
|
22
23
|
* `visit` on each. The walk is depth-first, left-to-right, so the
|
|
23
24
|
* constraint list is stable across runs. Pure boolean nodes contribute no
|