@crediolabs/policy-synth 0.2.0 → 0.3.1
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/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/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/record/freshness.d.ts +17 -11
- package/dist/record/freshness.js +28 -18
- 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/{synth → simulate}/deny-cases.d.ts +4 -8
- 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/index.d.ts +0 -5
- package/dist/synth/index.js +0 -5
- 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-cjs/adapters/interpreter/adapter.d.ts +19 -25
- package/dist-cjs/adapters/interpreter/adapter.js +56 -489
- 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/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/record/freshness.d.ts +17 -11
- package/dist-cjs/record/freshness.js +28 -18
- 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/{synth → simulate}/deny-cases.d.ts +4 -8
- 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/index.d.ts +0 -5
- package/dist-cjs/synth/index.js +1 -11
- 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/package.json +5 -5
- 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/record/freshness.ts +29 -18
- 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/adapters/interpreter/index.d.ts +0 -1
- package/dist/adapters/interpreter/index.js +0 -2
- package/dist/adapters/oz/adapter.d.ts +0 -20
- package/dist/adapters/oz/adapter.js +0 -295
- package/dist/adapters/oz/index.d.ts +0 -1
- package/dist/adapters/oz/index.js +0 -2
- package/dist/codegen/compile-gate.d.ts +0 -33
- package/dist/codegen/compile-gate.js +0 -124
- package/dist/codegen/index.d.ts +0 -2
- package/dist/codegen/index.js +0 -8
- package/dist/codegen/template.d.ts +0 -18
- package/dist/codegen/template.js +0 -148
- 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/ir/index.d.ts +0 -1
- package/dist/ir/index.js +0 -2
- package/dist/ir/types.d.ts +0 -140
- package/dist/ir/types.js +0 -11
- package/dist/mandate/index.d.ts +0 -2
- package/dist/mandate/index.js +0 -2
- package/dist/mandate/to-ir.d.ts +0 -3
- package/dist/mandate/to-ir.js +0 -60
- package/dist/mandate/types.d.ts +0 -20
- package/dist/mandate/types.js +0 -8
- package/dist/seams/index.d.ts +0 -1
- package/dist/seams/index.js +0 -2
- package/dist/seams/types.d.ts +0 -66
- package/dist/seams/types.js +0 -11
- package/dist/synth/deny-cases.js +0 -562
- package/dist/synth/evaluate.d.ts +0 -39
- package/dist/synth/evaluate.js +0 -551
- package/dist/synth/harness.d.ts +0 -28
- package/dist/synth/harness.js +0 -47
- package/dist/synth/minimize.d.ts +0 -4
- package/dist/synth/minimize.js +0 -38
- package/dist/synth/permit-context.d.ts +0 -15
- package/dist/synth/permit-context.js +0 -116
- package/dist/synth/predicate-literals.d.ts +0 -5
- package/dist/synth/predicate-literals.js +0 -25
- package/dist/synth/synthesize-from-mandate.d.ts +0 -20
- package/dist/synth/synthesize-from-mandate.js +0 -59
- package/dist/verify/envelope.d.ts +0 -15
- package/dist/verify/envelope.js +0 -22
- package/dist/verify/index.d.ts +0 -3
- package/dist/verify/index.js +0 -3
- package/dist/verify/simulate.d.ts +0 -31
- package/dist/verify/simulate.js +0 -258
- package/dist/verify/verify.d.ts +0 -21
- package/dist/verify/verify.js +0 -189
- package/dist-cjs/adapters/interpreter/index.d.ts +0 -1
- package/dist-cjs/adapters/interpreter/index.js +0 -8
- package/dist-cjs/adapters/oz/adapter.d.ts +0 -20
- package/dist-cjs/adapters/oz/adapter.js +0 -300
- package/dist-cjs/adapters/oz/index.d.ts +0 -1
- package/dist-cjs/adapters/oz/index.js +0 -8
- package/dist-cjs/codegen/compile-gate.d.ts +0 -33
- package/dist-cjs/codegen/compile-gate.js +0 -128
- package/dist-cjs/codegen/index.d.ts +0 -2
- package/dist-cjs/codegen/index.js +0 -14
- package/dist-cjs/codegen/template.d.ts +0 -18
- package/dist-cjs/codegen/template.js +0 -151
- 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/dist-cjs/ir/index.d.ts +0 -1
- package/dist-cjs/ir/index.js +0 -3
- package/dist-cjs/ir/types.d.ts +0 -140
- package/dist-cjs/ir/types.js +0 -12
- package/dist-cjs/mandate/index.d.ts +0 -2
- package/dist-cjs/mandate/index.js +0 -6
- package/dist-cjs/mandate/to-ir.d.ts +0 -3
- package/dist-cjs/mandate/to-ir.js +0 -63
- package/dist-cjs/mandate/types.d.ts +0 -20
- package/dist-cjs/mandate/types.js +0 -9
- package/dist-cjs/seams/index.d.ts +0 -1
- package/dist-cjs/seams/index.js +0 -3
- package/dist-cjs/seams/types.d.ts +0 -66
- package/dist-cjs/seams/types.js +0 -12
- package/dist-cjs/synth/deny-cases.js +0 -568
- package/dist-cjs/synth/evaluate.d.ts +0 -39
- package/dist-cjs/synth/evaluate.js +0 -554
- package/dist-cjs/synth/harness.d.ts +0 -28
- package/dist-cjs/synth/harness.js +0 -50
- package/dist-cjs/synth/minimize.d.ts +0 -4
- package/dist-cjs/synth/minimize.js +0 -41
- package/dist-cjs/synth/permit-context.d.ts +0 -15
- package/dist-cjs/synth/permit-context.js +0 -119
- package/dist-cjs/synth/predicate-literals.d.ts +0 -5
- package/dist-cjs/synth/predicate-literals.js +0 -28
- package/dist-cjs/synth/synthesize-from-mandate.d.ts +0 -20
- package/dist-cjs/synth/synthesize-from-mandate.js +0 -62
- package/dist-cjs/verify/envelope.d.ts +0 -15
- package/dist-cjs/verify/envelope.js +0 -23
- package/dist-cjs/verify/index.d.ts +0 -3
- package/dist-cjs/verify/index.js +0 -8
- package/dist-cjs/verify/simulate.d.ts +0 -31
- package/dist-cjs/verify/simulate.js +0 -261
- package/dist-cjs/verify/verify.d.ts +0 -21
- package/dist-cjs/verify/verify.js +0 -192
- 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/src/predicate/encode.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Pure function. Maps a `PredicateNode` AST to the canonical ScVal wire format:
|
|
4
4
|
// - every node is a `ScVal::Vec` whose head element is the tag `ScVal::Symbol`
|
|
5
|
-
// - children of `and`
|
|
5
|
+
// - children of `and` are sorted ascending by their canonical XDR bytes
|
|
6
6
|
// - `in` haystacks are ALWAYS sorted by canonical XDR bytes (pure set
|
|
7
7
|
// membership); an EXACT ordered sequence is expressed as
|
|
8
8
|
// `eq(selector, literal_vec)` where the `literal_vec` element order is
|
|
@@ -17,12 +17,6 @@
|
|
|
17
17
|
// Caps from `PREDICATE_CAPS` are enforced BEFORE returning; a cap breach throws
|
|
18
18
|
// a `ToolError` with the matching error code and `severity: 'error'`.
|
|
19
19
|
//
|
|
20
|
-
// One gap remains, deliberately: the `amount` / `window_spent` leaf branches
|
|
21
|
-
// below are dead ABI - the contract's grammar no longer has those selector
|
|
22
|
-
// symbols, so a predicate carrying one is MALFORMED at decode. The interpreter
|
|
23
|
-
// adapter reports them as uncovered before lowering, so the product path
|
|
24
|
-
// cannot emit one; removing the branches (and the leaf kinds) is a separate
|
|
25
|
-
// change.
|
|
26
20
|
|
|
27
21
|
import { createHash } from 'node:crypto'
|
|
28
22
|
import { Address, xdr } from '@stellar/stellar-sdk'
|
|
@@ -65,34 +59,26 @@ export function encodePredicate(node: PredicateNode): EncodedPredicate {
|
|
|
65
59
|
)
|
|
66
60
|
}
|
|
67
61
|
}
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
if (stats.oracleAssets.size > 0 && stats.nonOracleSelectorLeaves === 0) {
|
|
62
|
+
// Minimum constraint. A predicate with no selector leaf compares literals
|
|
63
|
+
// to literals, so it is trivially true or false and would permit everything
|
|
64
|
+
// or nothing forever. The contract refuses it at install with
|
|
65
|
+
// SELECTOR_LEAF_REQUIRED (216); refuse it here too so the failure is caught
|
|
66
|
+
// before a transaction is built.
|
|
67
|
+
if (stats.selectorLeaves === 0) {
|
|
77
68
|
throw capError(
|
|
78
69
|
'MALFORMED_PREDICATE',
|
|
79
|
-
'predicate constrains nothing
|
|
70
|
+
'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.'
|
|
80
71
|
)
|
|
81
72
|
}
|
|
82
73
|
|
|
83
|
-
// --- pass 1.5: leaf-value validation
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// caps; `den`/`num` for scaled ratios) - a negative cap would
|
|
92
|
-
// permit everything
|
|
93
|
-
// - hex even-length (literal_bytes) - `Buffer.from(v, 'hex')` silently
|
|
94
|
-
// drops non-hex chars, so 'zz' becomes empty bytes
|
|
95
|
-
// - scaled-ratio num>0 && den>0 - mirrors dsl.rs:661-704
|
|
74
|
+
// --- pass 1.5: leaf-value validation against the cap-set gate the contract
|
|
75
|
+
// enforces at install. Defense in depth: the TS self-verify pipeline should
|
|
76
|
+
// reject the same shapes Rust install refuses, so a hand-crafted predicate
|
|
77
|
+
// that simulate/verify green-lights cannot later be refused at the on-chain
|
|
78
|
+
// install step. The checks are the u32 ranges on the argument selectors
|
|
79
|
+
// (`call_arg.index`, `call_arg_len.index`, `call_arg_field.index` and
|
|
80
|
+
// `.element`) - the contract decodes each as u32, so an out-of-range value
|
|
81
|
+
// would fail there instead.
|
|
96
82
|
// Throws `MALFORMED_PREDICATE` so the error stays a ToolError shape.
|
|
97
83
|
validateLeafValues(node)
|
|
98
84
|
|
|
@@ -114,42 +100,31 @@ interface PredicateStats {
|
|
|
114
100
|
depth: number
|
|
115
101
|
leaves: number
|
|
116
102
|
inCounts: Array<{ count: number }>
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* `validate_oracle_placement` -> MissingNonOracleEnvelope): a price bound
|
|
122
|
-
* alone pins nothing about the call itself. Literals are operands, not
|
|
123
|
-
* constraints, so they do not count. */
|
|
124
|
-
nonOracleSelectorLeaves: number
|
|
103
|
+
/** Leaves that read something from the call under evaluation. Literals are
|
|
104
|
+
* operands, not constraints, so they do not count. Zero means the
|
|
105
|
+
* predicate constrains nothing and the contract refuses it at install. */
|
|
106
|
+
selectorLeaves: number
|
|
125
107
|
}
|
|
126
108
|
|
|
127
109
|
function computeStats(node: PredicateNode): PredicateStats {
|
|
128
110
|
const inCounts: Array<{ count: number }> = []
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const { depth, leaves } = walk(node, inCounts, oracleAssets, counters, false)
|
|
111
|
+
const counters = { selectorLeaves: 0 }
|
|
112
|
+
const { depth, leaves } = walk(node, inCounts, counters)
|
|
132
113
|
return {
|
|
133
114
|
depth,
|
|
134
115
|
leaves,
|
|
135
116
|
inCounts,
|
|
136
|
-
|
|
137
|
-
nonOracleSelectorLeaves: counters.nonOracleSelectorLeaves,
|
|
117
|
+
selectorLeaves: counters.selectorLeaves,
|
|
138
118
|
}
|
|
139
119
|
}
|
|
140
120
|
|
|
141
121
|
function walk(
|
|
142
122
|
node: PredicateNode,
|
|
143
123
|
inCounts: Array<{ count: number }>,
|
|
144
|
-
|
|
145
|
-
counters: { nonOracleSelectorLeaves: number },
|
|
146
|
-
/** True once the walk is under a `not` or an `or`, where an oracle read
|
|
147
|
-
* becomes a condition the caller can satisfy by taking the other branch. */
|
|
148
|
-
negatedOrDisjunctive: boolean
|
|
124
|
+
counters: { selectorLeaves: number }
|
|
149
125
|
): { depth: number; leaves: number } {
|
|
150
126
|
switch (node.op) {
|
|
151
|
-
case 'and':
|
|
152
|
-
case 'or': {
|
|
127
|
+
case 'and': {
|
|
153
128
|
if (node.children.length === 0) {
|
|
154
129
|
throw capError(
|
|
155
130
|
'MALFORMED_PREDICATE',
|
|
@@ -159,29 +134,16 @@ function walk(
|
|
|
159
134
|
let maxChildDepth = 0
|
|
160
135
|
let totalLeaves = 0
|
|
161
136
|
for (const c of node.children) {
|
|
162
|
-
const child = walk(
|
|
163
|
-
c,
|
|
164
|
-
inCounts,
|
|
165
|
-
oracleAssets,
|
|
166
|
-
counters,
|
|
167
|
-
negatedOrDisjunctive || node.op === 'or'
|
|
168
|
-
)
|
|
137
|
+
const child = walk(c, inCounts, counters)
|
|
169
138
|
if (child.depth > maxChildDepth) maxChildDepth = child.depth
|
|
170
139
|
totalLeaves += child.leaves
|
|
171
140
|
}
|
|
172
141
|
return { depth: maxChildDepth + 1, leaves: totalLeaves }
|
|
173
142
|
}
|
|
174
|
-
case 'not': {
|
|
175
|
-
const child = walk(node.child, inCounts, oracleAssets, counters, true)
|
|
176
|
-
return { depth: child.depth + 1, leaves: child.leaves }
|
|
177
|
-
}
|
|
178
143
|
case 'eq':
|
|
179
|
-
case '
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
case 'gte': {
|
|
183
|
-
collectOracle(node.left, oracleAssets, counters, negatedOrDisjunctive)
|
|
184
|
-
collectOracle(node.right, oracleAssets, counters, negatedOrDisjunctive)
|
|
144
|
+
case 'lte': {
|
|
145
|
+
collectSelector(node.left, counters)
|
|
146
|
+
collectSelector(node.right, counters)
|
|
185
147
|
return { depth: 1, leaves: leafCount(node.left) + leafCount(node.right) }
|
|
186
148
|
}
|
|
187
149
|
case 'in': {
|
|
@@ -192,10 +154,10 @@ function walk(
|
|
|
192
154
|
)
|
|
193
155
|
}
|
|
194
156
|
inCounts.push({ count: node.haystack.length })
|
|
195
|
-
|
|
157
|
+
collectSelector(node.needle, counters)
|
|
196
158
|
let haystackLeaves = 0
|
|
197
159
|
for (const h of node.haystack) {
|
|
198
|
-
|
|
160
|
+
collectSelector(h, counters)
|
|
199
161
|
haystackLeaves += leafCount(h)
|
|
200
162
|
}
|
|
201
163
|
return {
|
|
@@ -218,51 +180,26 @@ function leafCount(leaf: PredicateLeaf): number {
|
|
|
218
180
|
return 1
|
|
219
181
|
}
|
|
220
182
|
|
|
221
|
-
function
|
|
222
|
-
leaf
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
// vector literal (which the lowering would forbid, but the cap-walker must
|
|
229
|
-
// still see) is counted toward the oracle-read budget.
|
|
230
|
-
if (leaf.kind === 'oracle_price') {
|
|
231
|
-
if (negatedOrDisjunctive) {
|
|
232
|
-
throw capError(
|
|
233
|
-
'ORACLE_LEAF_INVALID_POSITION',
|
|
234
|
-
`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.`
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
oracleAssets.add(leaf.asset)
|
|
238
|
-
} else if (leaf.kind === 'literal_vec') {
|
|
239
|
-
for (const el of leaf.elements) collectOracle(el, oracleAssets, counters, negatedOrDisjunctive)
|
|
240
|
-
} else if (!leaf.kind.startsWith('literal_') && leaf.kind !== 'oracle_threshold') {
|
|
241
|
-
// A threshold is the oracle compare's operand, not an independent
|
|
242
|
-
// constraint. Counting it would let an oracle-only predicate satisfy the
|
|
243
|
-
// non-oracle envelope with its own bound (mirrors collect_oracle_leaf in
|
|
244
|
-
// dsl.rs, where it sits with the literals).
|
|
245
|
-
counters.nonOracleSelectorLeaves += 1
|
|
183
|
+
function collectSelector(leaf: PredicateLeaf, counters: { selectorLeaves: number }): void {
|
|
184
|
+
// literal_vec is the only nested leaf; recurse so a selector buried in a
|
|
185
|
+
// vector literal is still counted. Mirrors `dsl::has_selector_leaf`.
|
|
186
|
+
if (leaf.kind === 'literal_vec') {
|
|
187
|
+
for (const el of leaf.elements) collectSelector(el, counters)
|
|
188
|
+
} else if (!leaf.kind.startsWith('literal_')) {
|
|
189
|
+
counters.selectorLeaves += 1
|
|
246
190
|
}
|
|
247
191
|
}
|
|
248
192
|
|
|
249
193
|
function encodeNode(node: PredicateNode): xdr.ScVal {
|
|
250
194
|
switch (node.op) {
|
|
251
|
-
case 'and':
|
|
252
|
-
case 'or': {
|
|
195
|
+
case 'and': {
|
|
253
196
|
const encoded = node.children.map(encodeNode)
|
|
254
197
|
// sort children by their canonical XDR bytes ascending.
|
|
255
198
|
const sorted = sortByCanonicalBytes(encoded)
|
|
256
199
|
return xdr.ScVal.scvVec([symbol(node.op), xdr.ScVal.scvVec(sorted)])
|
|
257
200
|
}
|
|
258
|
-
case 'not': {
|
|
259
|
-
return xdr.ScVal.scvVec([symbol('not'), encodeNode(node.child)])
|
|
260
|
-
}
|
|
261
201
|
case 'eq':
|
|
262
|
-
case '
|
|
263
|
-
case 'lte':
|
|
264
|
-
case 'gt':
|
|
265
|
-
case 'gte': {
|
|
202
|
+
case 'lte': {
|
|
266
203
|
return xdr.ScVal.scvVec([symbol(node.op), encodeLeaf(node.left), encodeLeaf(node.right)])
|
|
267
204
|
}
|
|
268
205
|
case 'in': {
|
|
@@ -294,49 +231,6 @@ function encodeLeaf(leaf: PredicateLeaf): xdr.ScVal {
|
|
|
294
231
|
xdr.ScVal.scvU32(leaf.element),
|
|
295
232
|
xdr.ScVal.scvSymbol(leaf.field),
|
|
296
233
|
])
|
|
297
|
-
case 'call_arg_scaled':
|
|
298
|
-
// Wire shape `vec[symbol, u32, i128, i128]` mirrors the Rust decoder's
|
|
299
|
-
// expectation in dsl.rs SEL_CALL_ARG_SCALED. `scvI128FromDecimal` is
|
|
300
|
-
// the same range-checked helper used by `literal_i128`; an out-of-range
|
|
301
|
-
// num/den is refused at encode time (rather than at install on chain).
|
|
302
|
-
return xdr.ScVal.scvVec([
|
|
303
|
-
symbol('call_arg_scaled'),
|
|
304
|
-
xdr.ScVal.scvU32(leaf.index),
|
|
305
|
-
scvI128FromDecimal(leaf.num),
|
|
306
|
-
scvI128FromDecimal(leaf.den),
|
|
307
|
-
])
|
|
308
|
-
case 'amount':
|
|
309
|
-
return xdr.ScVal.scvVec([symbol('amount'), scvAddressFromStrkey(leaf.token)])
|
|
310
|
-
case 'window_spent':
|
|
311
|
-
return xdr.ScVal.scvVec([
|
|
312
|
-
symbol('window_spent'),
|
|
313
|
-
scvAddressFromStrkey(leaf.token),
|
|
314
|
-
scvU64FromValue(leaf.windowSeconds),
|
|
315
|
-
])
|
|
316
|
-
case 'now':
|
|
317
|
-
return xdr.ScVal.scvVec([symbol('now')])
|
|
318
|
-
case 'valid_until':
|
|
319
|
-
// The interpreter refuses this leaf at install: it never sources
|
|
320
|
-
// valid_until_ledger, so a policy built on it would deny forever.
|
|
321
|
-
// Expiry belongs to the smart account, through the context rule's
|
|
322
|
-
// validUntilLedger. Refuse at synthesis so the author finds out here
|
|
323
|
-
// rather than from an install that always fails.
|
|
324
|
-
throw new Error(
|
|
325
|
-
'valid_until is not a usable predicate leaf: the interpreter never sources it and ' +
|
|
326
|
-
'refuses it at install. Put expiry on the context rule (validUntilLedger) instead.'
|
|
327
|
-
)
|
|
328
|
-
case 'invocation_count_in_window':
|
|
329
|
-
return xdr.ScVal.scvVec([symbol('invocation_count'), scvU64FromValue(leaf.windowSecs)])
|
|
330
|
-
case 'oracle_price':
|
|
331
|
-
return xdr.ScVal.scvVec([symbol('oracle_price'), scvAddressFromStrkey(leaf.asset)])
|
|
332
|
-
case 'oracle_threshold':
|
|
333
|
-
// Arity 3, matching SEL_ORACLE_THRESHOLD in dsl.rs. The declared basis
|
|
334
|
-
// travels with the value so the contract never has to assume one.
|
|
335
|
-
return xdr.ScVal.scvVec([
|
|
336
|
-
symbol('oracle_threshold'),
|
|
337
|
-
scvI128FromDecimal(leaf.value),
|
|
338
|
-
xdr.ScVal.scvU32(leaf.decimals),
|
|
339
|
-
])
|
|
340
234
|
case 'literal_address':
|
|
341
235
|
return scvAddressFromStrkey(leaf.value)
|
|
342
236
|
case 'literal_i128':
|
|
@@ -345,10 +239,6 @@ function encodeLeaf(leaf: PredicateLeaf): xdr.ScVal {
|
|
|
345
239
|
return xdr.ScVal.scvSymbol(leaf.value)
|
|
346
240
|
case 'literal_u32':
|
|
347
241
|
return xdr.ScVal.scvU32(leaf.value)
|
|
348
|
-
case 'literal_u64':
|
|
349
|
-
return scvU64FromValue(leaf.value)
|
|
350
|
-
case 'literal_bytes':
|
|
351
|
-
return xdr.ScVal.scvBytes(Buffer.from(leaf.value, 'hex'))
|
|
352
242
|
case 'literal_vec':
|
|
353
243
|
// Bare ScVal::Vec of element encodings - order is preserved verbatim
|
|
354
244
|
// because the order IS the semantic (exact ordered sequence equality).
|
|
@@ -370,12 +260,6 @@ function scvAddressFromStrkey(strkey: string): xdr.ScVal {
|
|
|
370
260
|
return xdr.ScVal.scvAddress(Address.fromString(strkey).toScAddress())
|
|
371
261
|
}
|
|
372
262
|
|
|
373
|
-
function scvU64FromValue(value: number | string): xdr.ScVal {
|
|
374
|
-
// The Uint64 (UnsignedHyper) constructor accepts string | bigint | number;
|
|
375
|
-
// string is safest for values > 2^53.
|
|
376
|
-
return xdr.ScVal.scvU64(new xdr.Uint64(String(value)))
|
|
377
|
-
}
|
|
378
|
-
|
|
379
263
|
/** Build `ScVal::I128(Int128Parts{hi, lo})` from a signed decimal string.
|
|
380
264
|
* `Int128Parts` encodes the value as `(hi << 64) + lo` with `hi` a SIGNED
|
|
381
265
|
* 64-bit int and `lo` an UNSIGNED 64-bit int (this is NOT signed-magnitude).
|
|
@@ -394,12 +278,8 @@ function scvI128FromDecimal(decimal: string): xdr.ScVal {
|
|
|
394
278
|
`literal_i128 value ${decimal} is outside the Int128 range`
|
|
395
279
|
)
|
|
396
280
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
'MALFORMED_PREDICATE',
|
|
400
|
-
`literal_i128 value ${decimal} has an invalid Int128Parts.lo`
|
|
401
|
-
)
|
|
402
|
-
}
|
|
281
|
+
// `lo` needs no range check: masking with UINT64_MAX puts it in
|
|
282
|
+
// [0, 2^64-1] for every v, negative included.
|
|
403
283
|
return xdr.ScVal.scvI128(
|
|
404
284
|
new xdr.Int128Parts({
|
|
405
285
|
hi: new xdr.Int64(hi),
|
|
@@ -423,20 +303,14 @@ function capError(code: ToolError['code'], message: string): ToolError {
|
|
|
423
303
|
// u32 boundary - the same constant the contract decodes with. A value above
|
|
424
304
|
// this either overflows during encode or is refused at install.
|
|
425
305
|
const U32_MAX = 4294967295
|
|
426
|
-
// The maximum decimal basis an oracle threshold can declare (mirrors
|
|
427
|
-
// `MAX_ORACLE_THRESHOLD_DECIMALS` in dsl.rs). A value above this is refused
|
|
428
|
-
// at install with `ORACLE_PARAMS_OUT_OF_RANGE`.
|
|
429
|
-
const MAX_ORACLE_THRESHOLD_DECIMALS = 18
|
|
430
306
|
|
|
431
|
-
/** Walk a `PredicateNode` and fail-closed on any leaf whose
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* NOT be installable. Throws `MALFORMED_PREDICATE` so the envelope shapes
|
|
439
|
-
* it into a ToolError. */
|
|
307
|
+
/** Walk a `PredicateNode` and fail-closed on any leaf whose value the contract
|
|
308
|
+
* would refuse at install: the argument selectors carry u32 indices, so a
|
|
309
|
+
* value outside that range is refused here rather than on chain. Defense in
|
|
310
|
+
* depth so the TS self-verify pipeline rejects the same shapes Rust install
|
|
311
|
+
* already refuses - a hand-crafted predicate that simulate/verify
|
|
312
|
+
* green-lights must NOT be installable. Throws `MALFORMED_PREDICATE` so the
|
|
313
|
+
* envelope shapes it into a ToolError. */
|
|
440
314
|
function validateLeafValues(node: PredicateNode): void {
|
|
441
315
|
function walkLeaf(leaf: PredicateLeaf, path: string): void {
|
|
442
316
|
switch (leaf.kind) {
|
|
@@ -458,108 +332,22 @@ function validateLeafValues(node: PredicateNode): void {
|
|
|
458
332
|
throw malformed(`call_arg_field.element out of u32 range at ${path}`)
|
|
459
333
|
}
|
|
460
334
|
return
|
|
461
|
-
case 'call_arg_scaled': {
|
|
462
|
-
if (!Number.isInteger(leaf.index) || leaf.index < 0 || leaf.index > U32_MAX) {
|
|
463
|
-
throw malformed(`call_arg_scaled.index out of u32 range at ${path}`)
|
|
464
|
-
}
|
|
465
|
-
// num / den are i128 on chain, decimal strings on the wire. The
|
|
466
|
-
// contract refuses `den == 0` and `num <= 0` / `den <= 0` at install
|
|
467
|
-
// (dsl.rs:664-672); mirror that here so a future regression in
|
|
468
|
-
// `validate_scaled_ratios` cannot let a divide-by-zero policy reach
|
|
469
|
-
// the wire. BigInt throws on non-numeric strings -> malformed.
|
|
470
|
-
let num: bigint
|
|
471
|
-
let den: bigint
|
|
472
|
-
try {
|
|
473
|
-
num = BigInt(leaf.num)
|
|
474
|
-
den = BigInt(leaf.den)
|
|
475
|
-
} catch {
|
|
476
|
-
throw malformed(`call_arg_scaled.num/den not a decimal integer at ${path}`)
|
|
477
|
-
}
|
|
478
|
-
if (num <= 0n) throw malformed(`call_arg_scaled.num must be > 0 at ${path}`)
|
|
479
|
-
if (den <= 0n) throw malformed(`call_arg_scaled.den must be > 0 at ${path}`)
|
|
480
|
-
return
|
|
481
|
-
}
|
|
482
|
-
case 'literal_u32':
|
|
483
|
-
if (!Number.isInteger(leaf.value) || leaf.value < 0 || leaf.value > U32_MAX) {
|
|
484
|
-
throw malformed(`literal_u32.value out of u32 range at ${path}`)
|
|
485
|
-
}
|
|
486
|
-
return
|
|
487
|
-
case 'literal_i128':
|
|
488
|
-
// literal_i128 is signed; the contract allows negatives (i128
|
|
489
|
-
// arithmetic), but caps on a positive quantity (amount / window
|
|
490
|
-
// bound) should never be negative - a negative cap is silently
|
|
491
|
-
// satisfied by every non-negative amount. The contract gate is
|
|
492
|
-
// already on the leaf's ROLE (amount vs equality) not the value;
|
|
493
|
-
// here we mirror the value-only invariant the cap-set gate enforces
|
|
494
|
-
// by refusing the syntactic shape that would clearly be a bug
|
|
495
|
-
// (literal_i128 as a CAP with a leading `-` on a non-equality).
|
|
496
|
-
// We do not gate equality i128 - `literal_i128` as an address-by-
|
|
497
|
-
// equality is fine (it is just a constant).
|
|
498
|
-
// The value itself is always accepted; the structural check
|
|
499
|
-
// (non-negative for an amount / window bound) is left to the
|
|
500
|
-
// caller-built predicate, not the encoder.
|
|
501
|
-
return
|
|
502
|
-
case 'literal_bytes':
|
|
503
|
-
// Hex even-length: a non-hex char silently drops, and an odd
|
|
504
|
-
// length yields a half-byte Buffer. The contract decodes with a
|
|
505
|
-
// strict hex parser and refuses anything that is not even-length
|
|
506
|
-
// hex; mirror that here.
|
|
507
|
-
if (!/^[0-9a-fA-F]*$/.test(leaf.value) || leaf.value.length % 2 !== 0) {
|
|
508
|
-
throw malformed(`literal_bytes.value must be even-length hex at ${path}`)
|
|
509
|
-
}
|
|
510
|
-
return
|
|
511
|
-
case 'oracle_threshold':
|
|
512
|
-
if (
|
|
513
|
-
!Number.isInteger(leaf.decimals) ||
|
|
514
|
-
leaf.decimals < 0 ||
|
|
515
|
-
leaf.decimals > MAX_ORACLE_THRESHOLD_DECIMALS
|
|
516
|
-
) {
|
|
517
|
-
throw malformed(
|
|
518
|
-
`oracle_threshold.decimals out of range (0..${MAX_ORACLE_THRESHOLD_DECIMALS}) at ${path}`
|
|
519
|
-
)
|
|
520
|
-
}
|
|
521
|
-
return
|
|
522
|
-
case 'literal_vec':
|
|
523
|
-
leaf.elements.forEach((e, i) => {
|
|
524
|
-
walkLeaf(e, `${path}.elements[${i}]`)
|
|
525
|
-
})
|
|
526
|
-
return
|
|
527
|
-
// Selector and other leaves carry no cap-set values; the call_arg
|
|
528
|
-
// branches above cover indices, the literal branches cover typed
|
|
529
|
-
// constants. amount / window_spent / invocation_count / now /
|
|
530
|
-
// valid_until / call_contract / call_fn / literal_address /
|
|
531
|
-
// literal_symbol / literal_u64 / oracle_price are all value-free
|
|
532
|
-
// at this gate.
|
|
533
|
-
case 'amount':
|
|
534
|
-
case 'window_spent':
|
|
535
|
-
case 'invocation_count_in_window':
|
|
536
|
-
case 'now':
|
|
537
|
-
case 'valid_until':
|
|
538
335
|
case 'call_contract':
|
|
539
336
|
case 'call_fn':
|
|
540
337
|
case 'literal_address':
|
|
541
338
|
case 'literal_symbol':
|
|
542
|
-
case 'literal_u64':
|
|
543
|
-
case 'oracle_price':
|
|
544
339
|
return
|
|
545
340
|
}
|
|
546
341
|
}
|
|
547
342
|
function walkNode(n: PredicateNode, path: string): void {
|
|
548
343
|
switch (n.op) {
|
|
549
344
|
case 'and':
|
|
550
|
-
case 'or':
|
|
551
345
|
n.children.forEach((c, i) => {
|
|
552
346
|
walkNode(c, `${path}.children[${i}]`)
|
|
553
347
|
})
|
|
554
348
|
return
|
|
555
|
-
case 'not':
|
|
556
|
-
walkNode(n.child, `${path}.child`)
|
|
557
|
-
return
|
|
558
349
|
case 'eq':
|
|
559
|
-
case 'lt':
|
|
560
350
|
case 'lte':
|
|
561
|
-
case 'gt':
|
|
562
|
-
case 'gte':
|
|
563
351
|
walkLeaf(n.left, `${path}.left`)
|
|
564
352
|
walkLeaf(n.right, `${path}.right`)
|
|
565
353
|
return
|
|
@@ -19,7 +19,6 @@ export function jsonToAst(value: unknown): PredicateNode {
|
|
|
19
19
|
const v = value as {
|
|
20
20
|
op?: unknown
|
|
21
21
|
children?: unknown
|
|
22
|
-
child?: unknown
|
|
23
22
|
left?: unknown
|
|
24
23
|
right?: unknown
|
|
25
24
|
needle?: unknown
|
|
@@ -27,15 +26,9 @@ export function jsonToAst(value: unknown): PredicateNode {
|
|
|
27
26
|
}
|
|
28
27
|
switch (v.op) {
|
|
29
28
|
case 'and':
|
|
30
|
-
|
|
31
|
-
return { op: v.op, children: arrayOf(v.children, jsonToAst) }
|
|
32
|
-
case 'not':
|
|
33
|
-
return { op: 'not', child: jsonToAst(v.child) }
|
|
29
|
+
return { op: 'and', children: arrayOf(v.children, jsonToAst) }
|
|
34
30
|
case 'eq':
|
|
35
|
-
case 'lt':
|
|
36
31
|
case 'lte':
|
|
37
|
-
case 'gt':
|
|
38
|
-
case 'gte':
|
|
39
32
|
return { op: v.op, left: jsonToLeaf(v.left), right: jsonToLeaf(v.right) }
|
|
40
33
|
case 'in':
|
|
41
34
|
return { op: 'in', needle: jsonToLeaf(v.needle), haystack: arrayOf(v.haystack, jsonToLeaf) }
|
|
@@ -55,42 +48,11 @@ function jsonToLeaf(value: unknown): PredicateLeaf {
|
|
|
55
48
|
switch (v.kind) {
|
|
56
49
|
case 'call_contract':
|
|
57
50
|
case 'call_fn':
|
|
58
|
-
case 'now':
|
|
59
|
-
case 'valid_until':
|
|
60
51
|
return { kind: v.kind }
|
|
61
52
|
case 'call_arg':
|
|
62
53
|
return { kind: 'call_arg', index: numberField(v, 'index') }
|
|
63
54
|
case 'call_arg_len':
|
|
64
55
|
return { kind: 'call_arg_len', index: numberField(v, 'index') }
|
|
65
|
-
// num/den are strings, not numbers: they are i128 on chain, and a JSON
|
|
66
|
-
// number would silently lose precision past 2^53 before the encoder
|
|
67
|
-
// ever saw the value.
|
|
68
|
-
case 'call_arg_scaled':
|
|
69
|
-
return {
|
|
70
|
-
kind: 'call_arg_scaled',
|
|
71
|
-
index: numberField(v, 'index'),
|
|
72
|
-
num: stringField(v, 'num'),
|
|
73
|
-
den: stringField(v, 'den'),
|
|
74
|
-
}
|
|
75
|
-
case 'call_arg_field':
|
|
76
|
-
return {
|
|
77
|
-
kind: 'call_arg_field',
|
|
78
|
-
index: numberField(v, 'index'),
|
|
79
|
-
element: numberField(v, 'element'),
|
|
80
|
-
field: stringField(v, 'field'),
|
|
81
|
-
}
|
|
82
|
-
case 'amount':
|
|
83
|
-
return { kind: 'amount', token: stringField(v, 'token') }
|
|
84
|
-
case 'window_spent':
|
|
85
|
-
return {
|
|
86
|
-
kind: 'window_spent',
|
|
87
|
-
token: stringField(v, 'token'),
|
|
88
|
-
windowSeconds: numberField(v, 'windowSeconds'),
|
|
89
|
-
}
|
|
90
|
-
case 'invocation_count_in_window':
|
|
91
|
-
return { kind: 'invocation_count_in_window', windowSecs: numberField(v, 'windowSecs') }
|
|
92
|
-
case 'oracle_price':
|
|
93
|
-
return { kind: 'oracle_price', asset: stringField(v, 'asset') }
|
|
94
56
|
case 'literal_address':
|
|
95
57
|
return { kind: 'literal_address', value: stringField(v, 'value') }
|
|
96
58
|
case 'literal_i128':
|
|
@@ -99,10 +61,6 @@ function jsonToLeaf(value: unknown): PredicateLeaf {
|
|
|
99
61
|
return { kind: 'literal_symbol', value: stringField(v, 'value') }
|
|
100
62
|
case 'literal_u32':
|
|
101
63
|
return { kind: 'literal_u32', value: numberField(v, 'value') }
|
|
102
|
-
case 'literal_u64':
|
|
103
|
-
return { kind: 'literal_u64', value: stringField(v, 'value') }
|
|
104
|
-
case 'literal_bytes':
|
|
105
|
-
return { kind: 'literal_bytes', value: stringField(v, 'value') }
|
|
106
64
|
case 'literal_vec':
|
|
107
65
|
return { kind: 'literal_vec', elements: arrayOf(v.elements, jsonToLeaf) }
|
|
108
66
|
default:
|
package/src/record/decode.ts
CHANGED
|
@@ -453,14 +453,9 @@ function recordInvocation(
|
|
|
453
453
|
// parseConfidence denominator isn't inflated by repeat calls), but each
|
|
454
454
|
// unrecognised invocation is recorded separately so the numerator
|
|
455
455
|
// reflects every fail-closed hit.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
knownOut.push(contract)
|
|
460
|
-
}
|
|
461
|
-
return
|
|
462
|
-
}
|
|
463
|
-
if (identifyProtocol(contract, fn, args, network ?? undefined)) {
|
|
456
|
+
// `||` short-circuits, so `identifyProtocol` is still only consulted when the
|
|
457
|
+
// contract is not already in `knownSet` - the same call order as before.
|
|
458
|
+
if (knownSet.has(contract) || identifyProtocol(contract, fn, args, network ?? undefined)) {
|
|
464
459
|
if (!knownContractSet.has(contract)) {
|
|
465
460
|
knownContractSet.add(contract)
|
|
466
461
|
knownOut.push(contract)
|
package/src/record/freshness.ts
CHANGED
|
@@ -52,17 +52,23 @@ export function isBelowThreshold(c: ParseConfidence): boolean {
|
|
|
52
52
|
* under-confidence recording.
|
|
53
53
|
*
|
|
54
54
|
* The remediation text branches on which diagnostic bucket is non-empty:
|
|
55
|
-
* - unknownContracts only ->
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* - neither (the "denom === 0" path) ->
|
|
65
|
-
*
|
|
55
|
+
* - unknownContracts only -> the contract is not in the compiled-in
|
|
56
|
+
* registry. NOT "the contract has no interface": most Soroban
|
|
57
|
+
* contracts publish a typed spec on chain, this package just never
|
|
58
|
+
* reads one. The reason code stays `no-abi` because it is a published
|
|
59
|
+
* enum value, so the TEXT has to carry the correction.
|
|
60
|
+
* - opaqueScVals only -> the recorder met a value shape it should
|
|
61
|
+
* support but did not decode. That is a decoder bug, and the guidance
|
|
62
|
+
* says so rather than implying the caller can fix it.
|
|
63
|
+
* - both -> list both diagnostics and chain the right remediation.
|
|
64
|
+
* - neither (the "denom === 0" path) -> the caller did not actually hit
|
|
65
|
+
* a code-level barrier.
|
|
66
|
+
*
|
|
67
|
+
* Do NOT tell the caller to supply an ABI. There is no input for one:
|
|
68
|
+
* recognition runs off the compiled-in registry plus a known-contract set
|
|
69
|
+
* that no public tool boundary exposes. The old text named an action
|
|
70
|
+
* nobody could take and sent users looking for a file they do not have and
|
|
71
|
+
* would not need. */
|
|
66
72
|
export function buildLowConfidenceQuestion(c: ParseConfidence): string {
|
|
67
73
|
const hasUnknown = c.unknownContracts.length > 0
|
|
68
74
|
const hasOpaque = c.opaqueScVals.length > 0
|
|
@@ -75,18 +81,23 @@ export function buildLowConfidenceQuestion(c: ParseConfidence): string {
|
|
|
75
81
|
}
|
|
76
82
|
const why = reasons.length === 0 ? 'no diagnostic reason available' : reasons.join('; ')
|
|
77
83
|
const header = `Recording refused: parseConfidence ${c.overall.toFixed(3)} is below the threshold ${c.thresholdUsed.toFixed(3)}. Diagnostic: ${why}.`
|
|
78
|
-
// The
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
84
|
+
// The remediation branches are explicit so the user (or the agent) can
|
|
85
|
+
// dispatch on the verb. Neither branch asks for an ABI: there is no input
|
|
86
|
+
// that accepts one, and for the unknown-contract branch the contract's
|
|
87
|
+
// own spec is usually on chain already - it is this package that does not
|
|
88
|
+
// read it.
|
|
89
|
+
const unknownAdvice =
|
|
90
|
+
`The contract is not in this package's built-in registry - "no-abi" means unrecognised here, not that the contract lacks an interface. ` +
|
|
91
|
+
`You cannot supply one: the registry is compiled in. Either build the policy without deriving it from this transaction, or re-capture against a protocol the registry covers. ` +
|
|
92
|
+
`Passing confidenceOverride accepts the recording as it stands, including the arguments this tool could not attribute - do that only if you have checked them yourself.`
|
|
82
93
|
if (hasUnknown && hasOpaque) {
|
|
83
94
|
return (
|
|
84
|
-
`${header}
|
|
85
|
-
`
|
|
95
|
+
`${header} ${unknownAdvice} Separately, file the opaque ScVal path against the recorder (a decoder limitation) ` +
|
|
96
|
+
`and re-run record_transaction after a tool upgrade.`
|
|
86
97
|
)
|
|
87
98
|
}
|
|
88
99
|
if (hasUnknown) {
|
|
89
|
-
return `${header}
|
|
100
|
+
return `${header} ${unknownAdvice}`
|
|
90
101
|
}
|
|
91
102
|
if (hasOpaque) {
|
|
92
103
|
return `${header} This is a recorder decoder limitation (the value shape is supported in principle but not yet decoded) - the agent cannot fix it by supplying an ABI. Report the opaque ScVal path above against the recorder and re-run record_transaction after a tool upgrade.`
|