@callcium/sdk 0.6.0 → 0.8.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.
@@ -0,0 +1,569 @@
1
+ import type { Hex, Issue } from "./types";
2
+
3
+ ///////////////////////////////////////////////////////////////////////////
4
+ // Constants
5
+ ///////////////////////////////////////////////////////////////////////////
6
+
7
+ const ZERO: Hex = `0x${"0".repeat(64)}`;
8
+
9
+ ///////////////////////////////////////////////////////////////////////////
10
+ // Type mismatch
11
+ ///////////////////////////////////////////////////////////////////////////
12
+
13
+ /** Create an issue from operator compatibility results. */
14
+ export function fromOpRule(
15
+ code: string,
16
+ message: string,
17
+ groupIndex: number,
18
+ constraintIndex: number,
19
+ opCode: Hex,
20
+ ): Issue {
21
+ return {
22
+ severity: "error",
23
+ category: "typeMismatch",
24
+ groupIndex,
25
+ constraintIndex,
26
+ code,
27
+ value1: opCode,
28
+ value2: ZERO,
29
+ message,
30
+ };
31
+ }
32
+
33
+ /** Issue for a calldata path that does not navigate the descriptor. */
34
+ export function unnavigablePath(groupIndex: number, constraintIndex: number): Issue {
35
+ return {
36
+ severity: "error",
37
+ category: "typeMismatch",
38
+ groupIndex,
39
+ constraintIndex,
40
+ code: "UNNAVIGABLE_PATH",
41
+ value1: ZERO,
42
+ value2: ZERO,
43
+ message: "Path does not navigate the descriptor",
44
+ };
45
+ }
46
+
47
+ /** Calldata path carrying more than one quantifier step. */
48
+ export function nestedQuantifier(groupIndex: number, constraintIndex: number): Issue {
49
+ return {
50
+ severity: "error",
51
+ category: "typeMismatch",
52
+ groupIndex,
53
+ constraintIndex,
54
+ code: "NESTED_QUANTIFIER",
55
+ value1: ZERO,
56
+ value2: ZERO,
57
+ message: "Path applies more than one quantifier",
58
+ };
59
+ }
60
+
61
+ /** Issue for a hint block that does not match its path compiled against the descriptor. */
62
+ export function hintMismatch(groupIndex: number, constraintIndex: number): Issue {
63
+ return {
64
+ severity: "error",
65
+ category: "typeMismatch",
66
+ groupIndex,
67
+ constraintIndex,
68
+ code: "HINT_MISMATCH",
69
+ value1: ZERO,
70
+ value2: ZERO,
71
+ message: "Hint does not match the path compiled against the descriptor",
72
+ };
73
+ }
74
+
75
+ /** Issue for an operand that is not canonically encoded for the declared type. */
76
+ export function nonCanonicalOperand(groupIndex: number, constraintIndex: number, operand: Hex, canonical: Hex): Issue {
77
+ return {
78
+ severity: "error",
79
+ category: "typeMismatch",
80
+ groupIndex,
81
+ constraintIndex,
82
+ code: "NON_CANONICAL_OPERAND",
83
+ value1: operand,
84
+ value2: canonical,
85
+ message: "Operand is not the canonical encoding for the declared type",
86
+ };
87
+ }
88
+
89
+ ///////////////////////////////////////////////////////////////////////////
90
+ // Contradiction
91
+ ///////////////////////////////////////////////////////////////////////////
92
+
93
+ /** eq(v)/neq(v) or lengthEq(v)/lengthNeq(v) on the same path. */
94
+ export function eqNeqContradiction(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
95
+ return {
96
+ severity: "error",
97
+ category: "contradiction",
98
+ groupIndex,
99
+ constraintIndex,
100
+ code: isLength ? "LENGTH_EQ_NEQ_CONTRADICTION" : "EQ_NEQ_CONTRADICTION",
101
+ value1: value,
102
+ value2: ZERO,
103
+ message: isLength ? "lengthEq(v) and lengthNeq(v) on same path" : "eq(v) and neq(v) on same path",
104
+ };
105
+ }
106
+
107
+ /** Multiple eq()/lengthEq() operators with different values. */
108
+ export function conflictingEquality(
109
+ isLength: boolean,
110
+ groupIndex: number,
111
+ constraintIndex: number,
112
+ existing: Hex,
113
+ newValue: Hex,
114
+ ): Issue {
115
+ return {
116
+ severity: "error",
117
+ category: "contradiction",
118
+ groupIndex,
119
+ constraintIndex,
120
+ code: isLength ? "CONFLICTING_LENGTH" : "CONFLICTING_EQUALITY",
121
+ value1: existing,
122
+ value2: newValue,
123
+ message: isLength
124
+ ? "Multiple lengthEq() operators with different values"
125
+ : "Multiple eq() operators with different values",
126
+ };
127
+ }
128
+
129
+ /** Value/length is outside the physical range of its type. */
130
+ export function outOfPhysicalBounds(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
131
+ return {
132
+ severity: "error",
133
+ category: "contradiction",
134
+ groupIndex,
135
+ constraintIndex,
136
+ code: isLength ? "OUT_OF_PHYSICAL_LENGTH_BOUNDS" : "OUT_OF_PHYSICAL_BOUNDS",
137
+ value1: value,
138
+ value2: ZERO,
139
+ message: isLength
140
+ ? "Length value is outside the physical range"
141
+ : "Value is outside the physical range of the type",
142
+ };
143
+ }
144
+
145
+ /** gt()/lengthGt() on the maximum is impossible. */
146
+ export function impossibleGt(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
147
+ return {
148
+ severity: "error",
149
+ category: "contradiction",
150
+ groupIndex,
151
+ constraintIndex,
152
+ code: isLength ? "IMPOSSIBLE_LENGTH_GT" : "IMPOSSIBLE_GT",
153
+ value1: value,
154
+ value2: ZERO,
155
+ message: isLength ? "lengthGt() on maximum length is impossible" : "gt() on type maximum is impossible",
156
+ };
157
+ }
158
+
159
+ /** lt()/lengthLt() on the minimum is impossible. */
160
+ export function impossibleLt(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
161
+ return {
162
+ severity: "error",
163
+ category: "contradiction",
164
+ groupIndex,
165
+ constraintIndex,
166
+ code: isLength ? "IMPOSSIBLE_LENGTH_LT" : "IMPOSSIBLE_LT",
167
+ value1: value,
168
+ value2: ZERO,
169
+ message: isLength ? "lengthLt() on minimum length is impossible" : "lt() on type minimum is impossible",
170
+ };
171
+ }
172
+
173
+ /** eq()/lengthEq() value is excluded by a bound. */
174
+ export function boundsExcludeEquality(
175
+ isLength: boolean,
176
+ groupIndex: number,
177
+ constraintIndex: number,
178
+ eq: Hex,
179
+ bound: Hex,
180
+ ): Issue {
181
+ return {
182
+ severity: "error",
183
+ category: "contradiction",
184
+ groupIndex,
185
+ constraintIndex,
186
+ code: isLength ? "BOUNDS_EXCLUDE_LENGTH" : "BOUNDS_EXCLUDE_EQUALITY",
187
+ value1: eq,
188
+ value2: bound,
189
+ message: isLength ? "lengthEq() value is excluded by bound" : "eq() value is excluded by bound",
190
+ };
191
+ }
192
+
193
+ /** Lower bound/length exceeds the upper bound. */
194
+ export function impossibleRange(
195
+ isLength: boolean,
196
+ groupIndex: number,
197
+ constraintIndex: number,
198
+ lower: Hex,
199
+ upper: Hex,
200
+ ): Issue {
201
+ return {
202
+ severity: "error",
203
+ category: "contradiction",
204
+ groupIndex,
205
+ constraintIndex,
206
+ code: isLength ? "IMPOSSIBLE_LENGTH_RANGE" : "IMPOSSIBLE_RANGE",
207
+ value1: lower,
208
+ value2: upper,
209
+ message: isLength ? "Lower length bound exceeds upper bound" : "Lower bound exceeds upper bound",
210
+ };
211
+ }
212
+
213
+ /** Set operation excludes existing eq() value. */
214
+ export function setExcludesEquality(groupIndex: number, constraintIndex: number, value: Hex): Issue {
215
+ return {
216
+ severity: "error",
217
+ category: "contradiction",
218
+ groupIndex,
219
+ constraintIndex,
220
+ code: "SET_EXCLUDES_EQUALITY",
221
+ value1: value,
222
+ value2: ZERO,
223
+ message: "Set operation excludes existing eq() value",
224
+ };
225
+ }
226
+
227
+ /** Multiple isIn() sets have no intersection. */
228
+ export function emptySetIntersection(groupIndex: number, constraintIndex: number): Issue {
229
+ return {
230
+ severity: "error",
231
+ category: "contradiction",
232
+ groupIndex,
233
+ constraintIndex,
234
+ code: "EMPTY_SET_INTERSECTION",
235
+ value1: ZERO,
236
+ value2: ZERO,
237
+ message: "Multiple isIn() sets have no intersection",
238
+ };
239
+ }
240
+
241
+ /** All values in isIn() set are excluded by neq/notIn. */
242
+ export function setFullyExcluded(groupIndex: number, constraintIndex: number): Issue {
243
+ return {
244
+ severity: "error",
245
+ category: "contradiction",
246
+ groupIndex,
247
+ constraintIndex,
248
+ code: "SET_FULLY_EXCLUDED",
249
+ value1: ZERO,
250
+ value2: ZERO,
251
+ message: "All values in isIn() set are excluded by neq/notIn",
252
+ };
253
+ }
254
+
255
+ /** Bitmask operators conflict. */
256
+ export function bitmaskContradiction(groupIndex: number, constraintIndex: number, mask: Hex, conflicting: Hex): Issue {
257
+ return {
258
+ severity: "error",
259
+ category: "contradiction",
260
+ groupIndex,
261
+ constraintIndex,
262
+ code: "BITMASK_CONTRADICTION",
263
+ value1: mask,
264
+ value2: conflicting,
265
+ message: "Bitmask operators conflict",
266
+ };
267
+ }
268
+
269
+ /** bitmaskAny is impossible because all bits are forbidden. */
270
+ export function bitmaskAnyImpossible(groupIndex: number, constraintIndex: number, mask: Hex, mustBeZero: Hex): Issue {
271
+ return {
272
+ severity: "error",
273
+ category: "contradiction",
274
+ groupIndex,
275
+ constraintIndex,
276
+ code: "BITMASK_ANY_IMPOSSIBLE",
277
+ value1: mask,
278
+ value2: mustBeZero,
279
+ message: "bitmaskAny is impossible because all bits are forbidden",
280
+ };
281
+ }
282
+
283
+ /** isIn/notIn set is not strictly sorted and deduplicated. */
284
+ export function unsortedInSet(groupIndex: number, constraintIndex: number): Issue {
285
+ return {
286
+ severity: "error",
287
+ category: "contradiction",
288
+ groupIndex,
289
+ constraintIndex,
290
+ code: "UNSORTED_IN_SET",
291
+ value1: ZERO,
292
+ value2: ZERO,
293
+ message: "isIn/notIn set is not strictly sorted and deduplicated.",
294
+ };
295
+ }
296
+
297
+ /** Group contains zero constraints. */
298
+ export function emptyGroup(groupIndex: number): Issue {
299
+ return {
300
+ severity: "error",
301
+ category: "vacuity",
302
+ groupIndex,
303
+ constraintIndex: 0,
304
+ code: "EMPTY_GROUP",
305
+ value1: ZERO,
306
+ value2: ZERO,
307
+ message: "Group contains zero constraints.",
308
+ };
309
+ }
310
+
311
+ ///////////////////////////////////////////////////////////////////////////
312
+ // Redundancy
313
+ ///////////////////////////////////////////////////////////////////////////
314
+
315
+ /** Numeric/length bound dominated by a stricter bound. */
316
+ export function dominatedBound(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
317
+ return {
318
+ severity: "warning",
319
+ category: "redundancy",
320
+ groupIndex,
321
+ constraintIndex,
322
+ code: isLength ? "DOMINATED_LENGTH_BOUND" : "DOMINATED_BOUND",
323
+ value1: value,
324
+ value2: ZERO,
325
+ message: isLength
326
+ ? "Length bound is dominated by a stricter bound"
327
+ : "Numeric bound is dominated by a stricter bound",
328
+ };
329
+ }
330
+
331
+ /** Bound redundant because eq()/lengthEq() is set. */
332
+ export function redundantBound(
333
+ isLength: boolean,
334
+ groupIndex: number,
335
+ constraintIndex: number,
336
+ bound: Hex,
337
+ eq: Hex,
338
+ ): Issue {
339
+ return {
340
+ severity: "warning",
341
+ category: "redundancy",
342
+ groupIndex,
343
+ constraintIndex,
344
+ code: isLength ? "REDUNDANT_LENGTH_BOUND" : "REDUNDANT_BOUND",
345
+ value1: bound,
346
+ value2: eq,
347
+ message: isLength
348
+ ? "Length bound is redundant because lengthEq() is set"
349
+ : "Bound is redundant because eq() is set",
350
+ };
351
+ }
352
+
353
+ /** notIn() value was present in isIn() set. */
354
+ export function setReduction(groupIndex: number, constraintIndex: number, value: Hex): Issue {
355
+ return {
356
+ severity: "warning",
357
+ category: "redundancy",
358
+ groupIndex,
359
+ constraintIndex,
360
+ code: "SET_REDUCTION",
361
+ value1: value,
362
+ value2: ZERO,
363
+ message: "notIn() value was present in isIn() set",
364
+ };
365
+ }
366
+
367
+ /** isIn() sets partially overlap. */
368
+ export function setRedundancy(groupIndex: number, constraintIndex: number, intersectionCount: Hex): Issue {
369
+ return {
370
+ severity: "warning",
371
+ category: "redundancy",
372
+ groupIndex,
373
+ constraintIndex,
374
+ code: "SET_REDUNDANCY",
375
+ value1: intersectionCount,
376
+ value2: ZERO,
377
+ message: "isIn() sets partially overlap",
378
+ };
379
+ }
380
+
381
+ /** Some values in isIn() set are excluded by neq/notIn. */
382
+ export function setPartiallyExcluded(groupIndex: number, constraintIndex: number, excludedCount: Hex): Issue {
383
+ return {
384
+ severity: "warning",
385
+ category: "redundancy",
386
+ groupIndex,
387
+ constraintIndex,
388
+ code: "SET_PARTIALLY_EXCLUDED",
389
+ value1: excludedCount,
390
+ value2: ZERO,
391
+ message: "Some values in isIn() set are excluded by neq/notIn",
392
+ };
393
+ }
394
+
395
+ /** Bitmask operation is redundant. */
396
+ export function redundantBitmask(groupIndex: number, constraintIndex: number, mask: Hex, existing: Hex): Issue {
397
+ return {
398
+ severity: "warning",
399
+ category: "redundancy",
400
+ groupIndex,
401
+ constraintIndex,
402
+ code: "REDUNDANT_BITMASK",
403
+ value1: mask,
404
+ value2: existing,
405
+ message: "Bitmask operation is redundant",
406
+ };
407
+ }
408
+
409
+ /** gte/lte pair fusible into a single range operator. */
410
+ export function fusibleRange(
411
+ isLength: boolean,
412
+ groupIndex: number,
413
+ constraintIndex: number,
414
+ low: Hex,
415
+ high: Hex,
416
+ ): Issue {
417
+ return {
418
+ severity: "warning",
419
+ category: "redundancy",
420
+ groupIndex,
421
+ constraintIndex,
422
+ code: isLength ? "FUSIBLE_LENGTH_RANGE" : "FUSIBLE_RANGE",
423
+ value1: low,
424
+ value2: high,
425
+ message: isLength
426
+ ? "lengthGte() and lengthLte() fuse into a single lengthBetween()"
427
+ : "gte() and lte() fuse into a single between()",
428
+ };
429
+ }
430
+
431
+ /** Duplicate operator in constraint. */
432
+ export function duplicateConstraint(groupIndex: number, constraintIndex: number): Issue {
433
+ return {
434
+ severity: "warning",
435
+ category: "redundancy",
436
+ groupIndex,
437
+ constraintIndex,
438
+ code: "DUPLICATE_CONSTRAINT",
439
+ value1: ZERO,
440
+ value2: ZERO,
441
+ message: "Duplicate operator in constraint",
442
+ };
443
+ }
444
+
445
+ ///////////////////////////////////////////////////////////////////////////
446
+ // Vacuity
447
+ ///////////////////////////////////////////////////////////////////////////
448
+
449
+ /** gte(min)/lengthGte(0) is always true. */
450
+ export function vacuousGte(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
451
+ return {
452
+ severity: "info",
453
+ category: "vacuity",
454
+ groupIndex,
455
+ constraintIndex,
456
+ code: isLength ? "VACUOUS_LENGTH_GTE" : "VACUOUS_GTE",
457
+ value1: value,
458
+ value2: ZERO,
459
+ message: isLength ? "lengthGte(0) is always true" : "gte() bound equals type minimum (always true)",
460
+ };
461
+ }
462
+
463
+ /** lte(max)/lengthLte(max) is always true. */
464
+ export function vacuousLte(isLength: boolean, groupIndex: number, constraintIndex: number, value: Hex): Issue {
465
+ return {
466
+ severity: "info",
467
+ category: "vacuity",
468
+ groupIndex,
469
+ constraintIndex,
470
+ code: isLength ? "VACUOUS_LENGTH_LTE" : "VACUOUS_LTE",
471
+ value1: value,
472
+ value2: ZERO,
473
+ message: isLength
474
+ ? "lengthLte() bound equals maximum (always true)"
475
+ : "lte() bound equals type maximum (always true)",
476
+ };
477
+ }
478
+
479
+ /** Negated range with inverted bounds excludes nothing (always true). */
480
+ export function vacuousNegatedRange(
481
+ isLength: boolean,
482
+ groupIndex: number,
483
+ constraintIndex: number,
484
+ low: Hex,
485
+ high: Hex,
486
+ ): Issue {
487
+ return {
488
+ severity: "info",
489
+ category: "vacuity",
490
+ groupIndex,
491
+ constraintIndex,
492
+ code: isLength ? "VACUOUS_NEGATED_LENGTH_RANGE" : "VACUOUS_NEGATED_RANGE",
493
+ value1: low,
494
+ value2: high,
495
+ message: isLength
496
+ ? "Negated lengthBetween() with low > high excludes nothing (always true)"
497
+ : "Negated between() with low > high excludes nothing (always true)",
498
+ };
499
+ }
500
+
501
+ ///////////////////////////////////////////////////////////////////////////
502
+ // Compatibility
503
+ ///////////////////////////////////////////////////////////////////////////
504
+
505
+ /** Path deeper than the cap PWF-17 fixes. */
506
+ export function pathDepthExceeded(groupIndex: number, constraintIndex: number, depth: Hex, maxDepth: Hex): Issue {
507
+ return {
508
+ severity: "warning",
509
+ category: "compatibility",
510
+ groupIndex,
511
+ constraintIndex,
512
+ code: "PATH_DEPTH_EXCEEDED",
513
+ value1: depth,
514
+ value2: maxDepth,
515
+ message: "Path depth exceeds the maximum a well-formed policy may declare",
516
+ };
517
+ }
518
+
519
+ /** Quantifier over a static array beyond the iteration cap PV-7 fixes. */
520
+ export function quantifierOverStaticLimit(
521
+ groupIndex: number,
522
+ constraintIndex: number,
523
+ arrayLength: Hex,
524
+ maxLength: Hex,
525
+ ): Issue {
526
+ return {
527
+ severity: "error",
528
+ category: "compatibility",
529
+ groupIndex,
530
+ constraintIndex,
531
+ code: "QUANTIFIER_OVER_STATIC_LIMIT",
532
+ value1: arrayLength,
533
+ value2: maxLength,
534
+ message: "Quantifier over static array exceeds the maximum quantified length",
535
+ };
536
+ }
537
+
538
+ /** Context property ID outside the assigned set. */
539
+ export function unknownContextProperty(
540
+ groupIndex: number,
541
+ constraintIndex: number,
542
+ contextId: Hex,
543
+ maxContextId: Hex,
544
+ ): Issue {
545
+ return {
546
+ severity: "warning",
547
+ category: "compatibility",
548
+ groupIndex,
549
+ constraintIndex,
550
+ code: "UNKNOWN_CONTEXT_PROPERTY",
551
+ value1: contextId,
552
+ value2: maxContextId,
553
+ message: "Unknown context property ID",
554
+ };
555
+ }
556
+
557
+ /** Negated operator under the existential quantifier. */
558
+ export function negationUnderAny(groupIndex: number, constraintIndex: number, opCode: Hex): Issue {
559
+ return {
560
+ severity: "warning",
561
+ category: "compatibility",
562
+ groupIndex,
563
+ constraintIndex,
564
+ code: "NEGATION_UNDER_ANY",
565
+ value1: opCode,
566
+ value2: ZERO,
567
+ message: "Negated operator under any() passes when a decoy element differs",
568
+ };
569
+ }