@crediolabs/policy-synth 0.1.8 → 0.1.9

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.
@@ -216,6 +216,33 @@ function evalCompare(
216
216
  return evalArgEq(op, actual, right, ctx)
217
217
  }
218
218
 
219
+ // --- step 4c: call_arg_len: the length of a vec-typed argument as a u32.
220
+ // Fails closed on a non-vec arg, an absent arg, or a non-u32 literal.
221
+ if (left.kind === 'call_arg_len') {
222
+ const actual = ctx.args[left.index]
223
+ if (!actual || actual.type !== 'vec') return { permit: false, reason: 'ARG_MISMATCH' }
224
+ if (right.kind !== 'literal_u32') return { permit: false, reason: 'ARG_MISMATCH' }
225
+ return actual.value.length === right.value
226
+ ? { permit: true }
227
+ : { permit: false, reason: 'ARG_MISMATCH' }
228
+ }
229
+
230
+ // --- step 4d: call_arg_field: the value of a field in the map at element i
231
+ // of the vec at argument index. Fails closed on a non-vec arg, an
232
+ // out-of-range element, a missing field, a non-map element, or a type
233
+ // mismatch between the field ScVal and the literal leaf.
234
+ if (left.kind === 'call_arg_field') {
235
+ const actual = ctx.args[left.index]
236
+ if (!actual || actual.type !== 'vec') return { permit: false, reason: 'ARG_MISMATCH' }
237
+ const element = actual.value[left.element]
238
+ if (!element || element.type !== 'map') return { permit: false, reason: 'ARG_MISMATCH' }
239
+ if (!Array.isArray(element.value)) return { permit: false, reason: 'ARG_MISMATCH' }
240
+ const entry = element.value.find((e) => e.key === left.field)
241
+ if (!entry) return { permit: false, reason: 'ARG_MISMATCH' }
242
+ if (op === 'eq') return evalArgEq(op, entry.val, right, ctx)
243
+ return evalArgOrderedCompare(op, entry.val, right)
244
+ }
245
+
219
246
  // --- step 6: AMOUNT_BOUND ---
220
247
  if (left.kind === 'amount' && op !== 'eq') {
221
248
  return evalAmountCompare(op, left.token, right, ctx)
@@ -383,6 +410,20 @@ function resolveLeaf(leaf: PredicateLeaf, ctx: EvalContext): ScVal | undefined {
383
410
  return { type: 'symbol', value: ctx.fn }
384
411
  case 'call_arg':
385
412
  return ctx.args[leaf.index]
413
+ case 'call_arg_len':
414
+ // No direct ScVal projection: the length is an integer the comparator
415
+ // resolves against the right-hand literal. Returning undefined keeps
416
+ // the `in` membership path structurally informed (no haystack match).
417
+ return undefined
418
+ case 'call_arg_field': {
419
+ const actual = ctx.args[leaf.index]
420
+ if (!actual || actual.type !== 'vec') return undefined
421
+ const element = actual.value[leaf.element]
422
+ if (!element || element.type !== 'map') return undefined
423
+ if (!Array.isArray(element.value)) return undefined
424
+ const entry = element.value.find((e) => e.key === leaf.field)
425
+ return entry ? entry.val : undefined
426
+ }
386
427
  case 'amount':
387
428
  case 'window_spent':
388
429
  case 'oracle_price':
package/src/types.ts CHANGED
@@ -145,6 +145,16 @@ export type PredicateLeaf =
145
145
  | { kind: 'call_contract' }
146
146
  | { kind: 'call_fn' }
147
147
  | { kind: 'call_arg'; index: number }
148
+ // Length of a vec-typed argument as a u32. Binds the OUTER vec length so
149
+ // a caller cannot append a new element to defeat a per-element pin.
150
+ // Paired with `call_arg_field` per element - pinning only the field leaves
151
+ // an element-append vulnerability; this leaf closes it.
152
+ | { kind: 'call_arg_len'; index: number }
153
+ // Value of `field` in the map at `element` of the vec at argument `index`.
154
+ // Fixed-arity: index, element, field. The leaf is resolved against the
155
+ // recorded ScVal type. A non-vec arg, missing element, missing field, or
156
+ // type mismatch all DENY (fail-closed).
157
+ | { kind: 'call_arg_field'; index: number; element: number; field: string }
148
158
  // `call_sub_invocation[i]` is NOT in v1 grammar. OZ `Policy::enforce`
149
159
  // receives one `Context`, not a sub-invocation tree; sub-invocations live under
150
160
  // `InvokerContractAuthEntry::Contract(SubContractInvocation)`, not in the