@callcium/sdk 0.9.0 → 0.10.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/dist/index.d.ts +54 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +315 -323
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bytes.ts +9 -9
- package/src/constants.ts +36 -175
- package/src/constraint.ts +7 -32
- package/src/descriptor-coder.ts +18 -20
- package/src/descriptor.ts +12 -11
- package/src/errors.ts +0 -3
- package/src/index.ts +7 -17
- package/src/operators.ts +114 -9
- package/src/path.ts +71 -0
- package/src/policy-builder.ts +17 -16
- package/src/policy-coder.ts +11 -41
- package/src/policy-enforcer.ts +22 -34
- package/src/policy-validator.ts +20 -23
- package/src/type-desc.ts +10 -10
- package/src/types.ts +70 -3
- package/src/validation-issue.ts +4 -4
package/src/policy-validator.ts
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
import { bigintToHex, bytesToHex, hexToBytes } from "./bytes";
|
|
2
|
-
import {
|
|
3
|
-
classifyTypeCode,
|
|
4
|
-
isQuantifier,
|
|
5
|
-
isValidOperatorData,
|
|
6
|
-
Limits,
|
|
7
|
-
lookupContextProperty,
|
|
8
|
-
MAX_CONTEXT_PROPERTY_ID,
|
|
9
|
-
Op,
|
|
10
|
-
Quantifier,
|
|
11
|
-
Scope,
|
|
12
|
-
TypeCode,
|
|
13
|
-
} from "./constants";
|
|
2
|
+
import { PolicyFormat, lookupContextProperty, MAX_CONTEXT_PROPERTY_ID, Op, Scope, TypeCode } from "./constants";
|
|
14
3
|
import { Descriptor, type TypeInfo } from "./descriptor";
|
|
15
4
|
import { decodeDescriptor } from "./descriptor-coder";
|
|
16
5
|
import { CallciumError, type CallciumErrorCode } from "./errors";
|
|
17
|
-
import {
|
|
18
|
-
|
|
6
|
+
import {
|
|
7
|
+
canonicalize,
|
|
8
|
+
isLeftAligned,
|
|
9
|
+
isSigned,
|
|
10
|
+
isLengthOp,
|
|
11
|
+
isLengthValidType,
|
|
12
|
+
classifyTypeCode,
|
|
13
|
+
isValidOperatorData,
|
|
14
|
+
} from "./operators";
|
|
15
|
+
import { Quantifier, isQuantifier, parsePathSteps } from "./path";
|
|
19
16
|
import * as ValidationIssue from "./validation-issue";
|
|
20
17
|
|
|
21
|
-
import type { Constraint, Hex, Issue, PolicyData } from "./types";
|
|
18
|
+
import type { Constraint, Hex, Issue, IssueCode, PolicyData } from "./types";
|
|
22
19
|
|
|
23
20
|
///////////////////////////////////////////////////////////////////////////
|
|
24
21
|
// Internal types
|
|
25
22
|
///////////////////////////////////////////////////////////////////////////
|
|
26
23
|
|
|
27
24
|
/** Codes a path walk raises when the path does not navigate the descriptor (PV-1). */
|
|
28
|
-
const
|
|
25
|
+
const navigabilityCodes: ReadonlySet<CallciumErrorCode> = new Set<CallciumErrorCode>([
|
|
29
26
|
"EMPTY_PATH",
|
|
30
27
|
"PARAM_INDEX_OUT_OF_BOUNDS",
|
|
31
28
|
"TUPLE_FIELD_OUT_OF_BOUNDS",
|
|
@@ -159,7 +156,7 @@ function isBitmaskOp(opBase: number): boolean {
|
|
|
159
156
|
///////////////////////////////////////////////////////////////////////////
|
|
160
157
|
|
|
161
158
|
/** Return the incompatibility reason, or null if the operator is allowed. */
|
|
162
|
-
function getIncompat(opBase: number, typeInfo: TypeInfo): { code:
|
|
159
|
+
function getIncompat(opBase: number, typeInfo: TypeInfo): { code: IssueCode; message: string } | null {
|
|
163
160
|
const { typeCode, isDynamic, staticSize } = typeInfo;
|
|
164
161
|
if (isValueOp(opBase)) {
|
|
165
162
|
if (isDynamic || staticSize !== 32) {
|
|
@@ -1036,13 +1033,13 @@ function validateGroup(data: PolicyData, descBytes: Uint8Array, groupIndex: numb
|
|
|
1036
1033
|
let typeInfo: TypeInfo;
|
|
1037
1034
|
if (constraint.scope === Scope.CALLDATA) {
|
|
1038
1035
|
// Compatibility warnings against the limits the spec fixes (PWF-17, PV-7).
|
|
1039
|
-
if (steps.length >
|
|
1036
|
+
if (steps.length > PolicyFormat.MAX_PATH_DEPTH) {
|
|
1040
1037
|
issues.push(
|
|
1041
1038
|
ValidationIssue.pathDepthExceeded(
|
|
1042
1039
|
groupIndex,
|
|
1043
1040
|
constraintIndex,
|
|
1044
1041
|
bigintToHex(BigInt(steps.length)),
|
|
1045
|
-
bigintToHex(BigInt(
|
|
1042
|
+
bigintToHex(BigInt(PolicyFormat.MAX_PATH_DEPTH)),
|
|
1046
1043
|
),
|
|
1047
1044
|
);
|
|
1048
1045
|
}
|
|
@@ -1051,7 +1048,7 @@ function validateGroup(data: PolicyData, descBytes: Uint8Array, groupIndex: numb
|
|
|
1051
1048
|
walk = Descriptor.walkPath(descBytes, steps);
|
|
1052
1049
|
} catch (error) {
|
|
1053
1050
|
// Navigability faults become issues; anything else propagates.
|
|
1054
|
-
if (error instanceof CallciumError &&
|
|
1051
|
+
if (error instanceof CallciumError && navigabilityCodes.has(error.code)) {
|
|
1055
1052
|
issues.push(ValidationIssue.unnavigablePath(groupIndex, constraintIndex));
|
|
1056
1053
|
continue;
|
|
1057
1054
|
}
|
|
@@ -1062,13 +1059,13 @@ function validateGroup(data: PolicyData, descBytes: Uint8Array, groupIndex: numb
|
|
|
1062
1059
|
issues.push(ValidationIssue.nestedQuantifier(groupIndex, constraintIndex));
|
|
1063
1060
|
continue;
|
|
1064
1061
|
}
|
|
1065
|
-
if (walk.quantifiedStaticLength >
|
|
1062
|
+
if (walk.quantifiedStaticLength > PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH) {
|
|
1066
1063
|
issues.push(
|
|
1067
1064
|
ValidationIssue.quantifierOverStaticLimit(
|
|
1068
1065
|
groupIndex,
|
|
1069
1066
|
constraintIndex,
|
|
1070
1067
|
bigintToHex(BigInt(walk.quantifiedStaticLength)),
|
|
1071
|
-
bigintToHex(BigInt(
|
|
1068
|
+
bigintToHex(BigInt(PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH)),
|
|
1072
1069
|
),
|
|
1073
1070
|
);
|
|
1074
1071
|
}
|
|
@@ -1103,7 +1100,7 @@ function validateGroup(data: PolicyData, descBytes: Uint8Array, groupIndex: numb
|
|
|
1103
1100
|
if (
|
|
1104
1101
|
constraint.scope === Scope.CALLDATA &&
|
|
1105
1102
|
constraint.hint !== undefined &&
|
|
1106
|
-
ctx.steps.length <=
|
|
1103
|
+
ctx.steps.length <= PolicyFormat.MAX_PATH_DEPTH
|
|
1107
1104
|
) {
|
|
1108
1105
|
const compiled = bytesToHex(Descriptor.compileHint(descBytes, ctx.steps));
|
|
1109
1106
|
if (compiled !== constraint.hint.toLowerCase()) {
|
package/src/type-desc.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function function_(): Uint8Array {
|
|
|
48
48
|
*/
|
|
49
49
|
export function uintN(bits: number): Uint8Array {
|
|
50
50
|
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
|
|
51
|
+
throw new CallciumError("UNKNOWN_TYPE", `Invalid uintN bits: ${bits}. Must be 8–256 in steps of 8`);
|
|
52
52
|
}
|
|
53
53
|
return new Uint8Array([TypeCode.UINT_MIN + (bits / 8 - 1)]);
|
|
54
54
|
}
|
|
@@ -60,7 +60,7 @@ export function uintN(bits: number): Uint8Array {
|
|
|
60
60
|
*/
|
|
61
61
|
export function intN(bits: number): Uint8Array {
|
|
62
62
|
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
|
|
63
|
+
throw new CallciumError("UNKNOWN_TYPE", `Invalid intN bits: ${bits}. Must be 8–256 in steps of 8`);
|
|
64
64
|
}
|
|
65
65
|
return new Uint8Array([TypeCode.INT_MIN + (bits / 8 - 1)]);
|
|
66
66
|
}
|
|
@@ -82,7 +82,7 @@ export function string_(): Uint8Array {
|
|
|
82
82
|
*/
|
|
83
83
|
export function bytesN(n: number): Uint8Array {
|
|
84
84
|
if (n < 1 || n > 32) {
|
|
85
|
-
throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${n}. Must be 1–32
|
|
85
|
+
throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${n}. Must be 1–32`);
|
|
86
86
|
}
|
|
87
87
|
return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (n - 1)]);
|
|
88
88
|
}
|
|
@@ -108,7 +108,7 @@ export function array(elemDesc: Uint8Array, length?: number): Uint8Array {
|
|
|
108
108
|
if (nodeLength > DF.MAX_NODE_LENGTH) {
|
|
109
109
|
throw new CallciumError(
|
|
110
110
|
"NODE_LENGTH_TOO_LARGE",
|
|
111
|
-
`Dynamic array node length ${nodeLength} exceeds maximum ${DF.MAX_NODE_LENGTH}
|
|
111
|
+
`Dynamic array node length ${nodeLength} exceeds maximum ${DF.MAX_NODE_LENGTH}`,
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
const meta24 = (0 << DF.META_STATIC_WORDS_SHIFT) | nodeLength;
|
|
@@ -123,14 +123,14 @@ export function array(elemDesc: Uint8Array, length?: number): Uint8Array {
|
|
|
123
123
|
if (length <= 0 || length > DF.MAX_STATIC_ARRAY_LENGTH) {
|
|
124
124
|
throw new CallciumError(
|
|
125
125
|
"INVALID_ARRAY_LENGTH",
|
|
126
|
-
`Static array length ${length} out of range. Must be 1–${DF.MAX_STATIC_ARRAY_LENGTH}
|
|
126
|
+
`Static array length ${length} out of range. Must be 1–${DF.MAX_STATIC_ARRAY_LENGTH}`,
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
129
|
const nodeLength = DF.ARRAY_HEADER_SIZE + elemDesc.length + DF.ARRAY_LENGTH_SIZE;
|
|
130
130
|
if (nodeLength > DF.MAX_NODE_LENGTH) {
|
|
131
131
|
throw new CallciumError(
|
|
132
132
|
"NODE_LENGTH_TOO_LARGE",
|
|
133
|
-
`Static array node length ${nodeLength} exceeds maximum ${DF.MAX_NODE_LENGTH}
|
|
133
|
+
`Static array node length ${nodeLength} exceeds maximum ${DF.MAX_NODE_LENGTH}`,
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
const elemStaticWords = extractStaticWords(elemDesc);
|
|
@@ -138,7 +138,7 @@ export function array(elemDesc: Uint8Array, length?: number): Uint8Array {
|
|
|
138
138
|
if (staticWords > DF.MAX_STATIC_WORDS) {
|
|
139
139
|
throw new CallciumError(
|
|
140
140
|
"STATIC_WORDS_TOO_LARGE",
|
|
141
|
-
`Static array static words ${staticWords} exceeds maximum ${DF.MAX_STATIC_WORDS}
|
|
141
|
+
`Static array static words ${staticWords} exceeds maximum ${DF.MAX_STATIC_WORDS}`,
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
const meta24 = (staticWords << DF.META_STATIC_WORDS_SHIFT) | nodeLength;
|
|
@@ -162,7 +162,7 @@ export function tuple(fieldDescs: Uint8Array[]): Uint8Array {
|
|
|
162
162
|
if (fieldDescs.length === 0 || fieldDescs.length > DF.MAX_TUPLE_FIELDS) {
|
|
163
163
|
throw new CallciumError(
|
|
164
164
|
"INVALID_TUPLE_FIELD_COUNT",
|
|
165
|
-
`Tuple field count ${fieldDescs.length} out of range. Must be 1–${DF.MAX_TUPLE_FIELDS}
|
|
165
|
+
`Tuple field count ${fieldDescs.length} out of range. Must be 1–${DF.MAX_TUPLE_FIELDS}`,
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
168
|
const totalFieldBytes = fieldDescs.reduce((sum, f) => sum + f.length, 0);
|
|
@@ -170,7 +170,7 @@ export function tuple(fieldDescs: Uint8Array[]): Uint8Array {
|
|
|
170
170
|
if (nodeLength > DF.MAX_NODE_LENGTH) {
|
|
171
171
|
throw new CallciumError(
|
|
172
172
|
"NODE_LENGTH_TOO_LARGE",
|
|
173
|
-
`Tuple node length ${nodeLength} exceeds maximum ${DF.MAX_NODE_LENGTH}
|
|
173
|
+
`Tuple node length ${nodeLength} exceeds maximum ${DF.MAX_NODE_LENGTH}`,
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -187,7 +187,7 @@ export function tuple(fieldDescs: Uint8Array[]): Uint8Array {
|
|
|
187
187
|
if (staticWords > DF.MAX_STATIC_WORDS) {
|
|
188
188
|
throw new CallciumError(
|
|
189
189
|
"STATIC_WORDS_TOO_LARGE",
|
|
190
|
-
`Tuple static words ${staticWords} exceeds maximum ${DF.MAX_STATIC_WORDS}
|
|
190
|
+
`Tuple static words ${staticWords} exceeds maximum ${DF.MAX_STATIC_WORDS}`,
|
|
191
191
|
);
|
|
192
192
|
}
|
|
193
193
|
const meta24 = (staticWords << DF.META_STATIC_WORDS_SHIFT) | nodeLength;
|
package/src/types.ts
CHANGED
|
@@ -130,7 +130,7 @@ export type CalldataNavigationViolation =
|
|
|
130
130
|
| CalldataNavigationVariant<"CALLDATA_OUT_OF_BOUNDS">
|
|
131
131
|
| CalldataNavigationVariant<"ARRAY_INDEX_OUT_OF_BOUNDS">;
|
|
132
132
|
|
|
133
|
-
/** A quantified array exceeded `
|
|
133
|
+
/** A quantified array exceeded `PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH`. */
|
|
134
134
|
export type QuantifierLimitExceededViolation = {
|
|
135
135
|
code: "QUANTIFIER_LIMIT_EXCEEDED";
|
|
136
136
|
group: number;
|
|
@@ -142,7 +142,7 @@ export type QuantifierLimitExceededViolation = {
|
|
|
142
142
|
};
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* A resolved word is not the canonical encoding of its declared type
|
|
145
|
+
* A resolved word is not the canonical encoding of its declared type.
|
|
146
146
|
*
|
|
147
147
|
* `resolvedValue` is the raw 32-byte word as it appeared in calldata, before any masking.
|
|
148
148
|
*/
|
|
@@ -274,13 +274,80 @@ export type IssueSeverity = "info" | "warning" | "error";
|
|
|
274
274
|
/** Category of a validation issue. */
|
|
275
275
|
export type IssueCategory = "typeMismatch" | "contradiction" | "redundancy" | "vacuity" | "compatibility";
|
|
276
276
|
|
|
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
|
+
*/
|
|
283
|
+
export type IssueCode =
|
|
284
|
+
// Type mismatch.
|
|
285
|
+
| "VALUE_OP_ON_DYNAMIC"
|
|
286
|
+
| "VALUE_OP_ON_COMPOSITE"
|
|
287
|
+
| "NUMERIC_OP_ON_NON_NUMERIC"
|
|
288
|
+
| "BITMASK_ON_INVALID"
|
|
289
|
+
| "IN_ON_BOOL"
|
|
290
|
+
| "LENGTH_ON_STATIC"
|
|
291
|
+
| "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
|
+
| "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.
|
|
320
|
+
| "DOMINATED_BOUND"
|
|
321
|
+
| "REDUNDANT_BOUND"
|
|
322
|
+
| "SET_REDUCTION"
|
|
323
|
+
| "SET_REDUNDANCY"
|
|
324
|
+
| "SET_PARTIALLY_EXCLUDED"
|
|
325
|
+
| "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"
|
|
340
|
+
| "QUANTIFIER_OVER_STATIC_LIMIT"
|
|
341
|
+
| "UNKNOWN_CONTEXT_PROPERTY"
|
|
342
|
+
| "NEGATION_UNDER_ANY";
|
|
343
|
+
|
|
277
344
|
/** A single validation issue found during policy analysis. */
|
|
278
345
|
export type Issue = {
|
|
279
346
|
severity: IssueSeverity;
|
|
280
347
|
category: IssueCategory;
|
|
281
348
|
groupIndex: number;
|
|
282
349
|
constraintIndex: number;
|
|
283
|
-
code:
|
|
350
|
+
code: IssueCode;
|
|
284
351
|
value1: Hex;
|
|
285
352
|
value2: Hex;
|
|
286
353
|
message: string;
|
package/src/validation-issue.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Hex, Issue } from "./types";
|
|
1
|
+
import type { Hex, Issue, IssueCode } from "./types";
|
|
2
2
|
|
|
3
3
|
///////////////////////////////////////////////////////////////////////////
|
|
4
4
|
// Constants
|
|
@@ -12,7 +12,7 @@ const ZERO: Hex = `0x${"0".repeat(64)}`;
|
|
|
12
12
|
|
|
13
13
|
/** Create an issue from operator compatibility results. */
|
|
14
14
|
export function fromOpRule(
|
|
15
|
-
code:
|
|
15
|
+
code: IssueCode,
|
|
16
16
|
message: string,
|
|
17
17
|
groupIndex: number,
|
|
18
18
|
constraintIndex: number,
|
|
@@ -304,7 +304,7 @@ export function unsortedInSet(groupIndex: number, constraintIndex: number): Issu
|
|
|
304
304
|
code: "UNSORTED_IN_SET",
|
|
305
305
|
value1: ZERO,
|
|
306
306
|
value2: ZERO,
|
|
307
|
-
message: "isIn/notIn set is not strictly sorted and deduplicated
|
|
307
|
+
message: "isIn/notIn set is not strictly sorted and deduplicated",
|
|
308
308
|
};
|
|
309
309
|
}
|
|
310
310
|
|
|
@@ -318,7 +318,7 @@ export function emptyGroup(groupIndex: number): Issue {
|
|
|
318
318
|
code: "EMPTY_GROUP",
|
|
319
319
|
value1: ZERO,
|
|
320
320
|
value2: ZERO,
|
|
321
|
-
message: "Group contains zero constraints
|
|
321
|
+
message: "Group contains zero constraints",
|
|
322
322
|
};
|
|
323
323
|
}
|
|
324
324
|
|