@callcium/sdk 0.11.1 → 0.13.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/src/signature.ts CHANGED
@@ -31,14 +31,14 @@ function isAlphanum(char: number): boolean {
31
31
  /**
32
32
  * Parse an ABI function signature into its selector and types.
33
33
  *
34
- * Strict mode: no whitespace, name must match `[A-Za-z_][A-Za-z0-9_]*`,
35
- * structure must be `name(types)` with the final `)` as the last character.
34
+ * Strict mode: no space, tab, LF or CR. The name must match `[A-Za-z_][A-Za-z0-9_]*`. The structure must be
35
+ * `name(types)` with the final `)` as the last character.
36
36
  *
37
- * @param signature - A function signature string, e.g. `"transfer(address,uint256)"`.
37
+ * @param signature - A function signature string, for example `"transfer(address,uint256)"`.
38
38
  * @returns The 4-byte selector and the comma-separated types string.
39
39
  * @throws {CallciumError} with code `"MALFORMED_SIGNATURE"`, `"SIGNATURE_CONTAINS_WHITESPACE"`, or
40
- * `"INVALID_FUNCTION_NAME"` if the signature is malformed. The types between the parentheses are
41
- * returned unread; whether they name types is for the type parser to say.
40
+ * `"INVALID_FUNCTION_NAME"` if the signature is malformed. The types between the parentheses are returned unread.
41
+ * The type parser determines whether the types string names types.
42
42
  */
43
43
  function parse(signature: string): ParsedSignature {
44
44
  if (signature.length < 3) {
@@ -68,12 +68,8 @@ function parse(signature: string): ParsedSignature {
68
68
  throw new CallciumError("MALFORMED_SIGNATURE", `Invalid signature: must end with ")", got "${signature}"`);
69
69
  }
70
70
 
71
- if (openParen === 0) {
72
- throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must not be empty");
73
- }
74
-
75
- // Validate function name: [A-Za-z_][A-Za-z0-9_]*. Every character outside ASCII carries a code
76
- // unit above the alphanumeric ranges, so the same test rejects it.
71
+ // Validate function name: [A-Za-z_][A-Za-z0-9_]*. An empty name leaves `(` first, which fails the first-character
72
+ // test; every character outside ASCII carries a code unit above the alphanumeric ranges and fails the same tests.
77
73
  const firstChar = signature.charCodeAt(0);
78
74
  if (!isAlpha(firstChar) && firstChar !== 0x5f) {
79
75
  throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must start with a letter or underscore");
package/src/type-desc.ts CHANGED
@@ -9,10 +9,8 @@ import { CallciumError } from "./errors";
9
9
  /**
10
10
  * Extract the staticWords count from a descriptor node.
11
11
  *
12
- * For elementary types (typeCode < STATIC_ARRAY), returns 1 unless the type is
13
- * BYTES or STRING, which are ABI-dynamic and return 0. For composite types,
14
- * reads the 24-bit meta field at offset 1 and shifts right by
15
- * META_STATIC_WORDS_SHIFT.
12
+ * For elementary types (typeCode < STATIC_ARRAY), returns 1 unless the type is BYTES or STRING, which are ABI-dynamic
13
+ * and return 0. For composite types, returns the 24-bit meta at offset 1 right-shifted by META_STATIC_WORDS_SHIFT.
16
14
  */
17
15
  function extractStaticWords(desc: Uint8Array): number {
18
16
  const typeCode = desc[0]!;
@@ -44,11 +42,11 @@ export function function_(): Uint8Array {
44
42
  /**
45
43
  * Build a descriptor node for `uintN`.
46
44
  * @param bits - Bit width, 8–256 in steps of 8.
47
- * @throws {CallciumError} With code `UNKNOWN_TYPE` if bits is out of range or not a multiple of 8.
45
+ * @throws {CallciumError} With code `INVALID_UINT_BITS` if bits is out of range or not a multiple of 8.
48
46
  */
49
47
  export function uintN(bits: number): Uint8Array {
50
48
  if (bits < 8 || bits > 256 || bits % 8 !== 0) {
51
- throw new CallciumError("UNKNOWN_TYPE", `Invalid uintN bits: ${bits}. Must be 8–256 in steps of 8`);
49
+ throw new CallciumError("INVALID_UINT_BITS", `Invalid uint width ${bits}`);
52
50
  }
53
51
  return new Uint8Array([TypeCode.UINT_MIN + (bits / 8 - 1)]);
54
52
  }
@@ -56,11 +54,11 @@ export function uintN(bits: number): Uint8Array {
56
54
  /**
57
55
  * Build a descriptor node for `intN`.
58
56
  * @param bits - Bit width, 8–256 in steps of 8.
59
- * @throws {CallciumError} With code `UNKNOWN_TYPE` if bits is out of range or not a multiple of 8.
57
+ * @throws {CallciumError} With code `INVALID_INT_BITS` if bits is out of range or not a multiple of 8.
60
58
  */
61
59
  export function intN(bits: number): Uint8Array {
62
60
  if (bits < 8 || bits > 256 || bits % 8 !== 0) {
63
- throw new CallciumError("UNKNOWN_TYPE", `Invalid intN bits: ${bits}. Must be 8–256 in steps of 8`);
61
+ throw new CallciumError("INVALID_INT_BITS", `Invalid int width ${bits}`);
64
62
  }
65
63
  return new Uint8Array([TypeCode.INT_MIN + (bits / 8 - 1)]);
66
64
  }
@@ -78,11 +76,11 @@ export function string_(): Uint8Array {
78
76
  /**
79
77
  * Build a descriptor node for `bytesN`.
80
78
  * @param length - Byte width, 1–32.
81
- * @throws {CallciumError} With code `UNKNOWN_TYPE` if length is out of range.
79
+ * @throws {CallciumError} With code `INVALID_BYTES_LENGTH` if length is out of range.
82
80
  */
83
81
  export function bytesN(length: number): Uint8Array {
84
82
  if (length < 1 || length > 32) {
85
- throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${length}. Must be 1–32`);
83
+ throw new CallciumError("INVALID_BYTES_LENGTH", `Invalid bytes length ${length}`);
86
84
  }
87
85
  return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (length - 1)]);
88
86
  }
@@ -99,7 +97,8 @@ export function bytesN(length: number): Uint8Array {
99
97
  *
100
98
  * @param elemDesc - Descriptor bytes for the element type.
101
99
  * @param length - Fixed array length. When omitted, produces a dynamic array.
102
- * @throws {CallciumError} If length is 0 or exceeds MAX_STATIC_ARRAY_LENGTH.
100
+ * @throws {CallciumError} If length is 0 or above MAX_STATIC_ARRAY_LENGTH.
101
+ * @throws {CallciumError} If the node exceeds MAX_NODE_LENGTH or MAX_STATIC_WORDS.
103
102
  */
104
103
  export function array(elemDesc: Uint8Array, length?: number): Uint8Array {
105
104
  if (length === undefined) {
@@ -156,7 +155,8 @@ export function array(elemDesc: Uint8Array, length?: number): Uint8Array {
156
155
  * Format: `[0x90][meta:3][fieldCount:be16][field0][field1]...`
157
156
  *
158
157
  * @param fieldDescs - Descriptor bytes for each field, in order.
159
- * @throws {CallciumError} If fieldDescs is empty, exceeds MAX_TUPLE_FIELDS, or nodeLength overflows MAX_NODE_LENGTH.
158
+ * @throws {CallciumError} If fieldDescs is empty or above MAX_TUPLE_FIELDS.
159
+ * @throws {CallciumError} If the node exceeds MAX_NODE_LENGTH or MAX_STATIC_WORDS.
160
160
  */
161
161
  export function tuple(fieldDescs: Uint8Array[]): Uint8Array {
162
162
  if (fieldDescs.length === 0 || fieldDescs.length > DF.MAX_TUPLE_FIELDS) {
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { ComparisonCode, QuantifierCode, Scope, ScopeCode, Source } from "./constants";
2
+
1
3
  ///////////////////////////////////////////////////////////////////////////
2
4
  // Primitives
3
5
  ///////////////////////////////////////////////////////////////////////////
@@ -11,14 +13,16 @@ export type Address = `0x${string}`;
11
13
  /** Byte range in a source blob. */
12
14
  export type Span = { start: number; end: number };
13
15
 
16
+ /** A decoded value with its byte position in the source blob. */
17
+ export type Field<T> = { value: T; span: Span };
18
+
14
19
  ///////////////////////////////////////////////////////////////////////////
15
20
  // Enforce
16
21
  ///////////////////////////////////////////////////////////////////////////
17
22
 
18
23
  /**
19
- * Execution context for context-scoped rules.
20
- * Each property maps to a well-known EVM execution environment value.
21
- * Only the properties referenced by the policy need to be supplied.
24
+ * Execution context for context-scoped rules. Each property maps to a well-known EVM execution environment value. Only
25
+ * the properties referenced by the policy need to be supplied.
22
26
  */
23
27
  export type Context = {
24
28
  msgSender?: Address;
@@ -52,53 +56,54 @@ export type SelectorMismatchViolation = {
52
56
  };
53
57
 
54
58
  /**
55
- * A rule referenced a context property not supplied in the execution context — a context-scoped
56
- * subject, or an `EQ_CTX` operand on either scope.
59
+ * A test read a context property not supplied in the execution context — a context test's subject, or a context
60
+ * operand on either kind of test.
61
+ *
62
+ * `group` and `rule` locate the root rule. A test in a loop body adds `bodyRule` and `elementIndex`.
57
63
  */
58
64
  export type MissingContextViolation = {
59
65
  code: "MISSING_CONTEXT";
60
66
  group: number;
61
67
  rule: number;
62
- scope: number;
68
+ scope: ScopeCode;
69
+ /** The test's path: a context test's property ID, or a calldata test's steps (in a loop body, from the element). */
63
70
  path: Hex;
64
71
  /** Declared type of the missing context property. */
65
72
  typeCode: number;
66
- /** Operator code with the `Op.NOT` bit intact; absent for a context-scoped subject. */
67
- opCode?: number;
68
- /** Operator payload; absent for a context-scoped subject. */
69
- operandData?: Hex;
73
+ /** The test's operator, absent for a context test's subject. */
74
+ operator?: DecodedOperator;
75
+ /** Position of the test in the loop body, absent for a root test. */
76
+ bodyRule?: number;
77
+ /** Position of the element the body test read, absent for a root test. */
78
+ elementIndex?: number;
70
79
  };
71
80
 
72
81
  /**
73
- * A rule's operator returned false against the loaded value.
74
- *
75
- * Field combinations:
76
- * - `resolvedValue` present, `elementIndex` absent — scalar leaf or context value that failed the operator.
77
- * - `resolvedValue` present, `elementIndex` present — universal-quantifier per-element failure.
78
- * - `resolvedValue` absent, `elementIndex` absent — existential-aggregate failure (no element satisfied).
79
- * - `resolvedValue` absent, `elementIndex` present — per-element failure where the leaf could not be loaded.
82
+ * A test's operator returned false against the loaded value.
80
83
  *
81
- * For length operations (`isLengthOp(opCode)`), `resolvedValue` is a hex-encoded count
82
- * rather than a 32-byte ABI word.
84
+ * `resolvedValue` is the value the operator compared: a word, or for length operators the length as a 32-byte word.
85
+ * `group` and `rule` locate the root rule. A test in a loop body adds `bodyRule` and `elementIndex`.
83
86
  */
84
87
  export type ValueMismatchViolation = {
85
88
  code: "VALUE_MISMATCH";
86
89
  group: number;
87
90
  rule: number;
88
- scope: number;
91
+ scope: ScopeCode;
92
+ /** The test's path: a context test's property ID, or a calldata test's steps (in a loop body, from the element). */
89
93
  path: Hex;
90
- /** Operator code with the `Op.NOT` bit intact. */
91
- opCode: number;
92
- /** Full untruncated operand bytes declared by the rule. */
93
- operandData: Hex;
94
- /** Type code of the failing value's leaf. */
94
+ /** The test's operator. */
95
+ operator: DecodedOperator;
96
+ /** Type code of the failing value. */
95
97
  typeCode: number;
96
- resolvedValue?: Hex;
98
+ resolvedValue: Hex;
97
99
  /**
98
- * Resolved runtime value of a reference operand as a 32-byte word; present when the
99
- * operator's operand names a reference resolved at enforcement, not a literal.
100
+ * Resolved runtime value of a context operand as a 32-byte word, present when the operator's operand names a context
101
+ * property, not a constant.
100
102
  */
101
103
  resolvedOperand?: Hex;
104
+ /** Position of the test in the loop body, absent for a root test. */
105
+ bodyRule?: number;
106
+ /** Position of the element the body test read, absent for a root test. */
102
107
  elementIndex?: number;
103
108
  };
104
109
 
@@ -110,19 +115,21 @@ type CalldataNavigationVariant<C extends NavigationViolationCode> = {
110
115
  code: C;
111
116
  group: number;
112
117
  rule: number;
113
- scope: number;
118
+ scope: ScopeCode;
114
119
  path: Hex;
115
- opCode?: number;
116
- operandData?: Hex;
120
+ operator?: DecodedOperator;
117
121
  typeCode?: number;
122
+ bodyRule?: number;
118
123
  elementIndex?: number;
119
124
  };
120
125
 
121
126
  /**
122
- * Calldata structure prevented the rule from being evaluated.
127
+ * Calldata structure prevented a rule from being evaluated.
123
128
  *
124
- * The operator was never applied; `opCode`, `operandData`, `typeCode`, and `elementIndex`
125
- * are diagnostic context describing the failing site, not a constraint claim.
129
+ * The operator was never applied. `operator`, `typeCode`, `bodyRule` and `elementIndex` are diagnostic context
130
+ * describing the failing site, not a constraint claim. A failure resolving a loop's array carries the loop
131
+ * rule's scope and array path and no operator. A failure resolving an element adds `elementIndex`. A failure in a body
132
+ * test adds the test's fields and `bodyRule`.
126
133
  *
127
134
  * A union of per-code variants, narrowable with `Extract<Violation, { code: "..." }>`.
128
135
  */
@@ -130,12 +137,13 @@ export type CalldataNavigationViolation =
130
137
  | CalldataNavigationVariant<"CALLDATA_OUT_OF_BOUNDS">
131
138
  | CalldataNavigationVariant<"ARRAY_INDEX_OUT_OF_BOUNDS">;
132
139
 
133
- /** A quantified array exceeded `PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH`. */
140
+ /** A loop's array exceeded `MAX_QUANTIFIED_ARRAY_LENGTH`. */
134
141
  export type QuantifierLimitExceededViolation = {
135
142
  code: "QUANTIFIER_LIMIT_EXCEEDED";
136
143
  group: number;
137
144
  rule: number;
138
- scope: number;
145
+ scope: ScopeCode;
146
+ /** The loop's array path. */
139
147
  path: Hex;
140
148
  /** Hex-encoded element count of the offending array. */
141
149
  resolvedValue: Hex;
@@ -150,31 +158,34 @@ export type NonCanonicalValueViolation = {
150
158
  code: "NON_CANONICAL_VALUE";
151
159
  group: number;
152
160
  rule: number;
153
- scope: number;
161
+ scope: ScopeCode;
154
162
  path: Hex;
155
163
  /** Type code of the target whose encoding was violated. */
156
164
  typeCode: number;
157
165
  /** Raw 32-byte word as it appeared in calldata, before any masking. */
158
166
  resolvedValue: Hex;
159
- opCode?: number;
160
- operandData?: Hex;
167
+ /** The test's operator. */
168
+ operator: DecodedOperator;
169
+ /** Position of the test in the loop body, absent for a root test. */
170
+ bodyRule?: number;
171
+ /** Position of the element the body test read, absent for a root test. */
161
172
  elementIndex?: number;
162
173
  };
163
174
 
164
- /** An `ANY` quantifier was applied to an empty array. */
165
- export type QuantifierEmptyArrayViolation = {
166
- code: "QUANTIFIER_EMPTY_ARRAY";
175
+ /** No element satisfied an `ANY` loop's body, the empty array included. No element and no test is to blame. */
176
+ export type QuantifierNoMatchViolation = {
177
+ code: "QUANTIFIER_NO_MATCH";
167
178
  group: number;
168
179
  rule: number;
169
- scope: number;
180
+ scope: ScopeCode;
181
+ /** The loop's array path. */
170
182
  path: Hex;
171
183
  };
172
184
 
173
185
  /**
174
186
  * Structured details of a single rule failure during enforcement.
175
187
  *
176
- * Carries semantic data only; no message strings.
177
- * Discriminate on `code` to narrow to the matching variant.
188
+ * Carries semantic data only, with no message strings. Discriminate on `code` to narrow to the matching variant.
178
189
  */
179
190
  export type Violation =
180
191
  | MissingSelectorViolation
@@ -184,66 +195,188 @@ export type Violation =
184
195
  | CalldataNavigationViolation
185
196
  | NonCanonicalValueViolation
186
197
  | QuantifierLimitExceededViolation
187
- | QuantifierEmptyArrayViolation;
198
+ | QuantifierNoMatchViolation;
188
199
 
189
200
  /** Machine-readable reason code for an enforcement violation. Derived from `Violation` to prevent drift. */
190
201
  export type ViolationCode = Violation["code"];
191
202
 
192
203
  ///////////////////////////////////////////////////////////////////////////
193
- // Canonical policy types
204
+ // Policy types
194
205
  ///////////////////////////////////////////////////////////////////////////
195
206
 
196
- /** Canonical structured representation of a policy. */
207
+ /** A test or a context test of a loop body, or of a group. */
208
+ export type BodyRule = {
209
+ scope: typeof Scope.CONTEXT | typeof Scope.CALLDATA;
210
+ /** BE16-encoded path from the starting point. A context test's path is its property ID. */
211
+ path: Hex;
212
+ /** The hint a blob carries for the path, absent for a context test and in data not read from a blob. */
213
+ hint?: Hex;
214
+ /** Encoded operator: opCode(1) || each operand value as a 32-byte word, a context property as its ID. */
215
+ operator: Hex;
216
+ };
217
+
218
+ /** A loop: its body over each element of the array at `path`. */
219
+ export type LoopRule = {
220
+ scope: typeof Scope.LOOP;
221
+ quantifier: QuantifierCode;
222
+ /** BE16-encoded path from the arguments to the array. */
223
+ path: Hex;
224
+ /** The frame hint a blob carries for the path, absent in data not read from a blob. */
225
+ hint?: Hex;
226
+ body: BodyRule[];
227
+ };
228
+
229
+ /** A rule of a group: a test, a context test or a loop. */
230
+ export type Rule = BodyRule | LoopRule;
231
+
232
+ /** A policy: its selector, its descriptor and its groups of rules. */
197
233
  export type PolicyData = {
198
234
  isSelectorless: boolean;
199
235
  selector: Hex;
200
236
  descriptor: Hex;
201
- groups: Constraint[][];
202
- span?: Span;
237
+ groups: Rule[][];
203
238
  };
204
239
 
240
+ /**
241
+ * An operator as a policy stores it: its comparison, its polarity and its operands. Constants are loaded to 32-byte
242
+ * words of `operandType`. A context operand names its property.
243
+ */
244
+ export type DecodedOperator = {
245
+ /** Operator code with the `Op.NOT` bit intact. */
246
+ opCode: number;
247
+ /** Comparison bits of `opCode`. */
248
+ comparison: ComparisonCode;
249
+ /** True when `opCode` carries the `Op.NOT` bit. */
250
+ negated: boolean;
251
+ /**
252
+ * Type code of the operands: the target's type, the type a length compares as for a length comparison,
253
+ * or the context property's type.
254
+ */
255
+ operandType: number;
256
+ } & (
257
+ | {
258
+ source: typeof Source.CONSTANT;
259
+ /** Each constant loaded to its 32-byte word. */
260
+ values: Hex[];
261
+ }
262
+ | {
263
+ source: typeof Source.CONTEXT;
264
+ /** The context property the operand names. */
265
+ contextPropertyId: number;
266
+ }
267
+ );
268
+
205
269
  /** A collection of operators targeting a specific value. */
206
270
  export type Constraint = {
207
- scope: number;
271
+ scope: typeof Scope.CONTEXT | typeof Scope.CALLDATA;
208
272
  path: Hex;
209
273
  operators: Hex[];
210
- /** Compiled hint block as carried on the wire; absent when the constraint has no encoding. */
211
- hint?: Hex;
212
- span?: Span;
213
274
  };
214
275
 
215
276
  ///////////////////////////////////////////////////////////////////////////
216
- // Structural inspection types
277
+ // Inspection
217
278
  ///////////////////////////////////////////////////////////////////////////
218
279
 
219
- /** A decoded value with its byte position in the source blob. */
220
- export type Field<T> = { value: T; span: Span };
221
-
222
- /** Structural representation of a decoded parameter within the descriptor. */
223
- export type DecodedParam = {
224
- index: number;
280
+ /** A descriptor node: its type, its head size and the bytes it spans, with its fields or its element. */
281
+ export type DecodedNode = {
225
282
  typeCode: number;
226
283
  isDynamic: boolean;
284
+ /** Head size in bytes, zero for a dynamic type. */
227
285
  staticSize: number;
228
- path: Hex;
286
+ span: Span;
287
+ } & (
288
+ | { typeClass: "elementary" }
289
+ | { typeClass: "tuple"; fields: DecodedNode[] }
290
+ | { typeClass: "staticArray"; length: number; element: DecodedNode }
291
+ | { typeClass: "dynamicArray"; element: DecodedNode }
292
+ );
293
+
294
+ /** A descriptor: its header fields and one node per parameter. */
295
+ export type DecodedDescriptor = {
296
+ version: Field<number>;
297
+ paramCount: Field<number>;
298
+ params: DecodedNode[];
299
+ span: Span;
300
+ };
301
+
302
+ /** An array's meta word. */
303
+ export type ArrayMeta = {
304
+ /** The element type is dynamic. An offset word points to each element. */
305
+ isElementDynamic: boolean;
306
+ /** The array is dynamic. A length word is before the elements. */
307
+ isDynamicArray: boolean;
308
+ /** Element head size in words. */
309
+ strideWords: number;
310
+ };
311
+
312
+ /** A hop of a compiled hint: a plain hop enters the node behind an offset word, an element hop selects an element. */
313
+ export type DecodedHop = {
314
+ kind: "plain" | "element";
315
+ delta: Field<number>;
316
+ index: Field<number>;
317
+ meta: Field<ArrayMeta>;
229
318
  span: Span;
230
319
  };
231
320
 
232
- /** Structural representation of a decoded rule with per-field spans. */
233
- export type DecodedRule = {
321
+ /** A test hint: its hops, then the target's position, meta word and type code. */
322
+ export type DecodedHint = {
323
+ hopCount: Field<number>;
324
+ hops: DecodedHop[];
325
+ targetDelta: Field<number>;
326
+ /** The target's meta word. Every field is false or zero unless the target is a dynamic array. */
327
+ targetMeta: Field<ArrayMeta>;
328
+ typeCode: Field<number>;
329
+ span: Span;
330
+ };
331
+
332
+ /** A frame hint: its hops, then the array's position, declared length and meta word. */
333
+ export type DecodedFrameHint = {
334
+ hopCount: Field<number>;
335
+ hops: DecodedHop[];
336
+ arrayDelta: Field<number>;
337
+ /** Declared length of a static array, or zero for a dynamic array. */
338
+ count: Field<number>;
339
+ meta: Field<ArrayMeta>;
340
+ span: Span;
341
+ };
342
+
343
+ /** A test or a context test, each field with the bytes that contain it. */
344
+ export type DecodedTest = {
234
345
  ruleSize: Field<number>;
235
- scope: Field<number>;
346
+ scope: Field<typeof Scope.CONTEXT | typeof Scope.CALLDATA>;
236
347
  pathDepth: Field<number>;
348
+ /** The path steps, or a context test's property ID. */
237
349
  path: Field<Hex>;
238
- /** Compiled hint block; present for calldata rules only. */
239
- hint?: Field<Hex>;
350
+ /** The test hint, absent for a context test. */
351
+ hint?: DecodedHint;
352
+ /** Type code of the target: the hint's, or the context property's. */
353
+ targetType: number;
240
354
  opCode: Field<number>;
241
355
  dataLength: Field<number>;
356
+ /** The operands as stored. */
242
357
  data: Field<Hex>;
358
+ operator: DecodedOperator;
359
+ span: Span;
360
+ };
361
+
362
+ /** A loop rule, each field with the bytes that contain it. */
363
+ export type DecodedLoop = {
364
+ ruleSize: Field<number>;
365
+ scope: Field<typeof Scope.LOOP>;
366
+ quantifier: Field<QuantifierCode>;
367
+ pathDepth: Field<number>;
368
+ /** The array's path. */
369
+ path: Field<Hex>;
370
+ hint: DecodedFrameHint;
371
+ ruleCount: Field<number>;
372
+ body: DecodedTest[];
243
373
  span: Span;
244
374
  };
245
375
 
246
- /** Structural representation of a decoded group with metadata spans. */
376
+ /** A rule of a group: a test, a context test or a loop. */
377
+ export type DecodedRule = DecodedTest | DecodedLoop;
378
+
379
+ /** A group, each field with the bytes that contain it. */
247
380
  export type DecodedGroup = {
248
381
  ruleCount: Field<number>;
249
382
  groupSize: Field<number>;
@@ -251,102 +384,65 @@ export type DecodedGroup = {
251
384
  span: Span;
252
385
  };
253
386
 
254
- /** Structural representation of a decoded policy with full byte-level spans. */
387
+ /** A policy blob, each field with the bytes that contain it. */
255
388
  export type DecodedPolicy = {
256
- header: Field<number>;
389
+ header: Field<{ major: number; minor: number; isSelectorless: boolean }>;
390
+ /** The selector slot, zero for a selectorless policy. */
257
391
  selector: Field<Hex>;
258
392
  descLength: Field<number>;
259
- descriptor: { raw: Hex; header: Field<{ version: number; paramCount: number }>; params: DecodedParam[]; span: Span };
393
+ descriptor: DecodedDescriptor;
260
394
  groupCount: Field<number>;
261
395
  groups: DecodedGroup[];
262
396
  span: Span;
263
- version: number;
264
- isSelectorless: boolean;
265
397
  };
266
398
 
267
399
  ///////////////////////////////////////////////////////////////////////////
268
- // Validation issues
400
+ // Validation
269
401
  ///////////////////////////////////////////////////////////////////////////
270
402
 
271
403
  /** Severity of a validation issue. */
272
404
  export type IssueSeverity = "info" | "warning" | "error";
273
405
 
274
- /** Category of a validation issue. */
406
+ /** Kind of defect a validation issue reports. */
275
407
  export type IssueCategory = "typeMismatch" | "contradiction" | "redundancy" | "vacuity" | "compatibility";
276
408
 
277
- /**
278
- * Machine-readable code for a policy validation issue.
279
- *
280
- * Each code names one finding of the semantic validator and matches the constant of the same
281
- * name in the Solidity `IssueCode` library.
282
- */
409
+ /** Machine-readable code of a validation issue. */
283
410
  export type IssueCode =
284
- // Type mismatch.
285
- | "VALUE_OP_ON_DYNAMIC"
286
- | "VALUE_OP_ON_COMPOSITE"
411
+ | "INVALID_DESCRIPTOR"
412
+ | "UNNAVIGABLE_PATH"
413
+ | "QUANTIFIER_ON_NON_ARRAY"
414
+ | "HINT_MISMATCH"
287
415
  | "NUMERIC_OP_ON_NON_NUMERIC"
288
416
  | "BITMASK_ON_INVALID"
289
417
  | "IN_ON_BOOL"
290
- | "LENGTH_ON_STATIC"
291
418
  | "CONTEXT_TYPE_MISMATCH"
292
- | "UNKNOWN_OPERATOR"
293
- | "NON_CANONICAL_OPERAND"
294
- | "UNNAVIGABLE_PATH"
295
- | "NESTED_QUANTIFIER"
296
- | "HINT_MISMATCH"
297
- // Contradiction.
298
- | "EQ_NEQ_CONTRADICTION"
299
- | "CONFLICTING_EQUALITY"
300
419
  | "OUT_OF_PHYSICAL_BOUNDS"
301
- | "IMPOSSIBLE_GT"
302
- | "IMPOSSIBLE_LT"
303
- | "BOUNDS_EXCLUDE_EQUALITY"
304
- | "IMPOSSIBLE_RANGE"
305
- | "SET_EXCLUDES_EQUALITY"
306
- | "EMPTY_SET_INTERSECTION"
307
- | "SET_FULLY_EXCLUDED"
308
- | "LENGTH_EQ_NEQ_CONTRADICTION"
309
- | "CONFLICTING_LENGTH"
310
- | "BOUNDS_EXCLUDE_LENGTH"
311
- | "IMPOSSIBLE_LENGTH_RANGE"
312
- | "OUT_OF_PHYSICAL_LENGTH_BOUNDS"
313
- | "IMPOSSIBLE_LENGTH_GT"
314
- | "IMPOSSIBLE_LENGTH_LT"
315
- | "BITMASK_CONTRADICTION"
316
- | "BITMASK_ANY_IMPOSSIBLE"
317
- | "UNSORTED_IN_SET"
318
- | "EMPTY_GROUP"
319
- // Redundancy.
420
+ | "CONTRADICTORY_RULE"
421
+ | "REDUNDANT_RULE"
320
422
  | "DOMINATED_BOUND"
321
- | "REDUNDANT_BOUND"
322
- | "SET_REDUCTION"
323
- | "SET_REDUNDANCY"
324
- | "SET_PARTIALLY_EXCLUDED"
325
423
  | "DOMINATED_LENGTH_BOUND"
326
- | "REDUNDANT_LENGTH_BOUND"
327
- | "REDUNDANT_BITMASK"
328
- | "DUPLICATE_CONSTRAINT"
329
- | "FUSIBLE_RANGE"
330
- | "FUSIBLE_LENGTH_RANGE"
331
- // Vacuity.
332
- | "VACUOUS_GTE"
333
- | "VACUOUS_LTE"
334
- | "VACUOUS_LENGTH_GTE"
335
- | "VACUOUS_LENGTH_LTE"
336
- | "VACUOUS_NEGATED_RANGE"
337
- | "VACUOUS_NEGATED_LENGTH_RANGE"
338
- // Compatibility.
339
- | "PATH_DEPTH_EXCEEDED"
424
+ | "VACUOUS_RULE"
340
425
  | "QUANTIFIER_OVER_STATIC_LIMIT"
341
- | "UNKNOWN_CONTEXT_PROPERTY"
342
- | "NEGATION_UNDER_ANY";
426
+ | "CONTEXT_ONLY_BODY";
427
+
428
+ /**
429
+ * The rule an issue names: its group, its position in the group and, for a test in a loop body, its position in the
430
+ * body. The location also carries the rule's scope, path and operator as decoded.
431
+ * A path in a body starts at the element.
432
+ */
433
+ export type RuleLocation = {
434
+ groupIndex: number;
435
+ ruleIndex: number;
436
+ bodyRuleIndex?: number;
437
+ scope: ScopeCode;
438
+ path: Hex;
439
+ operator?: Hex;
440
+ };
343
441
 
344
- /** A single validation issue found during policy analysis. */
345
- export type Issue = {
442
+ /** A validation issue: the rule it names, absent for one about the descriptor, and the values that show the defect. */
443
+ export type Issue = Partial<RuleLocation> & {
346
444
  severity: IssueSeverity;
347
445
  category: IssueCategory;
348
- groupIndex: number;
349
- constraintIndex: number;
350
446
  code: IssueCode;
351
447
  value1: Hex;
352
448
  value2: Hex;