@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/README.md +2 -2
- package/dist/index.d.ts +683 -622
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2297 -2051
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
- package/src/bytes.ts +9 -25
- package/src/calldata-reader.ts +3 -3
- package/src/constants.ts +156 -82
- package/src/constraint.ts +61 -33
- package/src/descriptor-coder.ts +139 -125
- package/src/descriptor.ts +82 -292
- package/src/errors.ts +38 -30
- package/src/hint.ts +237 -0
- package/src/index.ts +30 -12
- package/src/operators.ts +173 -242
- package/src/path.ts +7 -32
- package/src/policy-builder.ts +205 -190
- package/src/policy-coder.ts +1160 -536
- package/src/policy-enforcer.ts +499 -488
- package/src/policy-validator.ts +563 -1045
- package/src/signature.ts +7 -11
- package/src/type-desc.ts +12 -12
- package/src/types.ts +232 -136
- package/src/validation-issue.ts +78 -465
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
|
|
35
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
*
|
|
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 `
|
|
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("
|
|
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 `
|
|
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("
|
|
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 `
|
|
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("
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
56
|
-
*
|
|
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:
|
|
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
|
-
/**
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
|
|
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
|
|
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
|
-
*
|
|
82
|
-
*
|
|
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:
|
|
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
|
-
/**
|
|
91
|
-
|
|
92
|
-
/**
|
|
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
|
|
98
|
+
resolvedValue: Hex;
|
|
97
99
|
/**
|
|
98
|
-
* Resolved runtime value of a
|
|
99
|
-
*
|
|
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:
|
|
118
|
+
scope: ScopeCode;
|
|
114
119
|
path: Hex;
|
|
115
|
-
|
|
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
|
|
127
|
+
* Calldata structure prevented a rule from being evaluated.
|
|
123
128
|
*
|
|
124
|
-
* The operator was never applied
|
|
125
|
-
*
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
/**
|
|
165
|
-
export type
|
|
166
|
-
code: "
|
|
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:
|
|
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
|
|
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
|
-
|
|
|
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
|
-
//
|
|
204
|
+
// Policy types
|
|
194
205
|
///////////////////////////////////////////////////////////////////////////
|
|
195
206
|
|
|
196
|
-
/**
|
|
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:
|
|
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:
|
|
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
|
-
//
|
|
277
|
+
// Inspection
|
|
217
278
|
///////////////////////////////////////////////////////////////////////////
|
|
218
279
|
|
|
219
|
-
/** A
|
|
220
|
-
export type
|
|
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
|
-
|
|
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
|
-
/**
|
|
233
|
-
export type
|
|
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<
|
|
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
|
-
/**
|
|
239
|
-
hint?:
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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:
|
|
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
|
|
400
|
+
// Validation
|
|
269
401
|
///////////////////////////////////////////////////////////////////////////
|
|
270
402
|
|
|
271
403
|
/** Severity of a validation issue. */
|
|
272
404
|
export type IssueSeverity = "info" | "warning" | "error";
|
|
273
405
|
|
|
274
|
-
/**
|
|
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
|
-
|
|
285
|
-
| "
|
|
286
|
-
| "
|
|
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
|
-
| "
|
|
302
|
-
| "
|
|
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
|
-
| "
|
|
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
|
-
| "
|
|
342
|
-
|
|
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
|
|
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;
|