@callcium/sdk 0.12.0 → 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 +659 -611
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2285 -2071
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
- package/src/bytes.ts +1 -0
- package/src/calldata-reader.ts +3 -3
- package/src/constants.ts +156 -86
- package/src/constraint.ts +34 -24
- package/src/descriptor-coder.ts +126 -103
- package/src/descriptor.ts +79 -288
- package/src/errors.ts +30 -19
- package/src/hint.ts +237 -0
- package/src/index.ts +29 -10
- package/src/operators.ts +168 -267
- package/src/path.ts +7 -31
- package/src/policy-builder.ts +201 -181
- package/src/policy-coder.ts +1156 -523
- package/src/policy-enforcer.ts +499 -486
- package/src/policy-validator.ts +557 -1046
- package/src/signature.ts +7 -11
- package/src/type-desc.ts +6 -6
- package/src/types.ts +230 -129
- package/src/validation-issue.ts +78 -465
package/src/constants.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { CallciumError } from "./errors";
|
|
|
7
7
|
/** Binary layout constants for the Callcium descriptor format. */
|
|
8
8
|
export const DescriptorFormat = {
|
|
9
9
|
VERSION: 0x02,
|
|
10
|
+
HEADER_PARAMCOUNT_OFFSET: 1,
|
|
10
11
|
HEADER_SIZE: 2,
|
|
11
12
|
TYPECODE_SIZE: 1,
|
|
12
13
|
COMPOSITE_META_SIZE: 3,
|
|
@@ -28,27 +29,23 @@ export const DescriptorFormat = {
|
|
|
28
29
|
// Policy format
|
|
29
30
|
///////////////////////////////////////////////////////////////////////////
|
|
30
31
|
|
|
31
|
-
/**
|
|
32
|
-
const RULE_DATALENGTH_SIZE = 2;
|
|
33
|
-
|
|
34
|
-
/** Byte width of one path step. */
|
|
35
|
-
const PATH_STEP_SIZE = 2;
|
|
36
|
-
|
|
37
|
-
/** Byte overhead of a rule header, excluding the path, the hint block, and the operator payload. */
|
|
38
|
-
const RULE_FIXED_OVERHEAD = 7;
|
|
39
|
-
|
|
40
|
-
/** Highest value a big-endian field of `byteWidth` bytes holds. */
|
|
32
|
+
/** Highest value a big-endian field of `byteWidth` bytes can represent. */
|
|
41
33
|
export function fieldMax(byteWidth: number): number {
|
|
42
34
|
return 256 ** byteWidth - 1;
|
|
43
35
|
}
|
|
44
36
|
|
|
37
|
+
/** Lowest minor version, the minor of a blob that uses no extension. */
|
|
38
|
+
const MINOR = 0;
|
|
39
|
+
|
|
45
40
|
/** Binary layout constants and normative limits for the Callcium policy format. */
|
|
46
41
|
export const PolicyFormat = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
MAJOR: 3,
|
|
43
|
+
MINOR,
|
|
44
|
+
SUPPORTED_MINOR: MINOR,
|
|
45
|
+
MAJOR_SHIFT: 5,
|
|
46
|
+
MINOR_SHIFT: 1,
|
|
47
|
+
MINOR_MASK: 0x0f,
|
|
48
|
+
FLAG_NO_SELECTOR: 0x01,
|
|
52
49
|
SELECTOR_OFFSET: 1,
|
|
53
50
|
SELECTOR_SIZE: 4,
|
|
54
51
|
DESC_LENGTH_OFFSET: 5,
|
|
@@ -56,18 +53,20 @@ export const PolicyFormat = {
|
|
|
56
53
|
DESC_OFFSET: 7,
|
|
57
54
|
GROUP_COUNT_SIZE: 1,
|
|
58
55
|
GROUP_RULECOUNT_SIZE: 2,
|
|
59
|
-
GROUP_SIZE_SIZE: 4,
|
|
60
56
|
GROUP_HEADER_SIZE: 6,
|
|
61
57
|
RULE_SIZE_SIZE: 2,
|
|
62
58
|
RULE_SCOPE_OFFSET: 2,
|
|
59
|
+
RULE_SCOPE_SIZE: 1,
|
|
63
60
|
RULE_DEPTH_OFFSET: 3,
|
|
61
|
+
RULE_DEPTH_SIZE: 1,
|
|
64
62
|
RULE_PATH_OFFSET: 4,
|
|
65
|
-
PATH_STEP_SIZE,
|
|
66
|
-
|
|
63
|
+
PATH_STEP_SIZE: 2,
|
|
64
|
+
PROPERTY_ID_SIZE: 2,
|
|
65
|
+
RULE_QUANTIFIER_OFFSET: 3,
|
|
66
|
+
RULE_QUANTIFIER_SIZE: 1,
|
|
67
|
+
LOOP_DEPTH_OFFSET: 4,
|
|
67
68
|
RULE_OPCODE_SIZE: 1,
|
|
68
|
-
RULE_DATALENGTH_SIZE,
|
|
69
|
-
RULE_FIXED_OVERHEAD,
|
|
70
|
-
RULE_MIN_SIZE: RULE_FIXED_OVERHEAD + PATH_STEP_SIZE,
|
|
69
|
+
RULE_DATALENGTH_SIZE: 2,
|
|
71
70
|
HINT_HEADER_SIZE: 1,
|
|
72
71
|
HINT_HOP_SIZE: 8,
|
|
73
72
|
HINT_HOP_INDEX_OFFSET: 4,
|
|
@@ -78,15 +77,10 @@ export const PolicyFormat = {
|
|
|
78
77
|
HINT_TARGET_SIZE: 7,
|
|
79
78
|
HINT_TARGET_META_OFFSET: 4,
|
|
80
79
|
HINT_TARGET_TYPECODE_OFFSET: 6,
|
|
81
|
-
HINT_KIND_NONE: 0x0,
|
|
82
|
-
HINT_KIND_ALL: 0x1,
|
|
83
|
-
HINT_KIND_ANY: 0x2,
|
|
84
|
-
HINT_KIND_MAX: 0x2,
|
|
85
|
-
HINT_KIND_SHIFT: 6,
|
|
86
80
|
HINT_HOP_COUNT_MASK: 0x3f,
|
|
87
|
-
|
|
81
|
+
HINT_HEADER_RESERVED_MASK: 0xc0,
|
|
88
82
|
HINT_NO_INDEX: 0xffff,
|
|
89
|
-
|
|
83
|
+
PATH_MARKER_BASE: 0xff00,
|
|
90
84
|
HINT_META_ELEM_DYNAMIC: 0x8000,
|
|
91
85
|
HINT_META_DYNAMIC_ARRAY: 0x4000,
|
|
92
86
|
HINT_META_RESERVED_MASK: 0x3000,
|
|
@@ -94,7 +88,6 @@ export const PolicyFormat = {
|
|
|
94
88
|
// Normative limits.
|
|
95
89
|
MAX_PATH_DEPTH: 32,
|
|
96
90
|
MAX_QUANTIFIED_ARRAY_LENGTH: 256,
|
|
97
|
-
MAX_SET_MEMBERS: Math.floor(fieldMax(RULE_DATALENGTH_SIZE) / 32),
|
|
98
91
|
} as const satisfies Record<string, number>;
|
|
99
92
|
|
|
100
93
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -113,7 +106,7 @@ type CodeMap<T extends readonly { readonly key: string; readonly code: number }[
|
|
|
113
106
|
* Render a wire code as fixed-width hex.
|
|
114
107
|
* @param code - Code value.
|
|
115
108
|
* @param byteWidth - Byte width of the field the code occupies.
|
|
116
|
-
* @returns The code as `0x`-prefixed hex, or its decimal form when no such field
|
|
109
|
+
* @returns The code as `0x`-prefixed hex, or its decimal form when no such field can represent it.
|
|
117
110
|
*/
|
|
118
111
|
export function formatCode(code: number, byteWidth: number): string {
|
|
119
112
|
if (!Number.isInteger(code) || code < 0 || code > fieldMax(byteWidth)) return String(code);
|
|
@@ -128,37 +121,6 @@ export function buildCodeMap<T extends readonly { readonly key: string; readonly
|
|
|
128
121
|
return Object.fromEntries(table.map((entry) => [entry.key, entry.code])) as CodeMap<T>;
|
|
129
122
|
}
|
|
130
123
|
|
|
131
|
-
///////////////////////////////////////////////////////////////////////////
|
|
132
|
-
// Scope codes
|
|
133
|
-
///////////////////////////////////////////////////////////////////////////
|
|
134
|
-
|
|
135
|
-
const SCOPE_TABLE = [
|
|
136
|
-
{ key: "CONTEXT", code: 0x00, label: "context" },
|
|
137
|
-
{ key: "CALLDATA", code: 0x01, label: "calldata" },
|
|
138
|
-
] as const;
|
|
139
|
-
|
|
140
|
-
/** Rule scope discriminant: context (EVM environment) vs. calldata (ABI payload). */
|
|
141
|
-
export const Scope = buildCodeMap(SCOPE_TABLE);
|
|
142
|
-
|
|
143
|
-
/** Display metadata for a scope code. */
|
|
144
|
-
export type ScopeInfo = { label: string };
|
|
145
|
-
|
|
146
|
-
const scopeByCode: ReadonlyMap<number, ScopeInfo> = new Map<number, ScopeInfo>(
|
|
147
|
-
SCOPE_TABLE.map((entry) => [entry.code, { label: entry.label }]),
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Map a scope code to its display label.
|
|
152
|
-
* @param code - Scope byte value.
|
|
153
|
-
* @returns Display metadata for the scope.
|
|
154
|
-
* @throws {CallciumError} If the code is not a recognised scope.
|
|
155
|
-
*/
|
|
156
|
-
export function lookupScope(code: number): ScopeInfo {
|
|
157
|
-
const info = scopeByCode.get(code);
|
|
158
|
-
if (!info) throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${code}`);
|
|
159
|
-
return info;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
124
|
///////////////////////////////////////////////////////////////////////////
|
|
163
125
|
// Type code ranges
|
|
164
126
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -233,17 +195,96 @@ export function lookupContextProperty(code: number): ContextPropertyInfo {
|
|
|
233
195
|
if (!info) {
|
|
234
196
|
throw new CallciumError(
|
|
235
197
|
"UNKNOWN_CONTEXT_PROPERTY",
|
|
236
|
-
`Unknown context property ID ${formatCode(code,
|
|
198
|
+
`Unknown context property ID ${formatCode(code, PolicyFormat.PROPERTY_ID_SIZE)}`,
|
|
237
199
|
);
|
|
238
200
|
}
|
|
239
201
|
return info;
|
|
240
202
|
}
|
|
241
203
|
|
|
204
|
+
///////////////////////////////////////////////////////////////////////////
|
|
205
|
+
// Scope codes
|
|
206
|
+
///////////////////////////////////////////////////////////////////////////
|
|
207
|
+
|
|
208
|
+
const SCOPE_TABLE = [
|
|
209
|
+
{ key: "CONTEXT", code: 0x00, label: "context" },
|
|
210
|
+
{ key: "CALLDATA", code: 0x01, label: "calldata" },
|
|
211
|
+
{ key: "LOOP", code: 0x02, label: "loop" },
|
|
212
|
+
] as const;
|
|
213
|
+
|
|
214
|
+
/** Rule scope discriminant: context (EVM environment), calldata (ABI payload) or loop (array elements). */
|
|
215
|
+
export const Scope = buildCodeMap(SCOPE_TABLE);
|
|
216
|
+
|
|
217
|
+
/** A defined scope code. */
|
|
218
|
+
export type ScopeCode = (typeof SCOPE_TABLE)[number]["code"];
|
|
219
|
+
|
|
220
|
+
/** Display metadata for a scope code. */
|
|
221
|
+
export type ScopeInfo = { label: string };
|
|
222
|
+
|
|
223
|
+
const scopeByCode: ReadonlyMap<number, ScopeInfo> = new Map<number, ScopeInfo>(
|
|
224
|
+
SCOPE_TABLE.map((entry) => [entry.code, { label: entry.label }]),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
/** Check whether a scope byte is a defined scope code. */
|
|
228
|
+
export function isScopeCode(code: number): code is ScopeCode {
|
|
229
|
+
return scopeByCode.has(code);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Map a scope code to its display label.
|
|
234
|
+
* @param code - Scope byte value.
|
|
235
|
+
* @returns Display metadata for the scope.
|
|
236
|
+
* @throws {CallciumError} If the code is not a recognised scope.
|
|
237
|
+
*/
|
|
238
|
+
export function lookupScope(code: number): ScopeInfo {
|
|
239
|
+
const info = scopeByCode.get(code);
|
|
240
|
+
if (!info) throw new CallciumError("UNKNOWN_SCOPE", `Unknown scope value ${code}`);
|
|
241
|
+
return info;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
///////////////////////////////////////////////////////////////////////////
|
|
245
|
+
// Quantifier codes
|
|
246
|
+
///////////////////////////////////////////////////////////////////////////
|
|
247
|
+
|
|
248
|
+
const QUANTIFIER_TABLE = [
|
|
249
|
+
{ key: "ALL", code: 0x01, label: "all" },
|
|
250
|
+
{ key: "ANY", code: 0x02, label: "any" },
|
|
251
|
+
] as const;
|
|
252
|
+
|
|
253
|
+
/** Loop quantifier: the body holds for every element (`ALL`) or for some element (`ANY`). */
|
|
254
|
+
export const LoopQuantifier = buildCodeMap(QUANTIFIER_TABLE);
|
|
255
|
+
|
|
256
|
+
/** A defined loop quantifier code. */
|
|
257
|
+
export type QuantifierCode = (typeof QUANTIFIER_TABLE)[number]["code"];
|
|
258
|
+
|
|
259
|
+
/** Display metadata for a loop quantifier code. */
|
|
260
|
+
export type QuantifierInfo = { label: string };
|
|
261
|
+
|
|
262
|
+
const quantifierByCode: ReadonlyMap<number, QuantifierInfo> = new Map<number, QuantifierInfo>(
|
|
263
|
+
QUANTIFIER_TABLE.map((entry) => [entry.code, { label: entry.label }]),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
/** Check whether a quantifier byte is a defined quantifier code. */
|
|
267
|
+
export function isQuantifierCode(code: number): code is QuantifierCode {
|
|
268
|
+
return quantifierByCode.has(code);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Map a loop quantifier code to its display label.
|
|
273
|
+
* @param code - Quantifier byte value.
|
|
274
|
+
* @returns Display metadata for the quantifier.
|
|
275
|
+
* @throws {CallciumError} If the code is not a recognised quantifier.
|
|
276
|
+
*/
|
|
277
|
+
export function lookupQuantifier(code: number): QuantifierInfo {
|
|
278
|
+
const info = quantifierByCode.get(code);
|
|
279
|
+
if (!info) throw new CallciumError("UNKNOWN_QUANTIFIER", `Unknown quantifier value ${code}`);
|
|
280
|
+
return info;
|
|
281
|
+
}
|
|
282
|
+
|
|
242
283
|
///////////////////////////////////////////////////////////////////////////
|
|
243
284
|
// Operator codes
|
|
244
285
|
///////////////////////////////////////////////////////////////////////////
|
|
245
286
|
|
|
246
|
-
/** Operand count category
|
|
287
|
+
/** Operand count category of an operator. */
|
|
247
288
|
export type Operands = "single" | "range" | "variadic";
|
|
248
289
|
|
|
249
290
|
/** @internal */
|
|
@@ -255,24 +296,33 @@ const OP_TABLE = [
|
|
|
255
296
|
{ key: "LTE", code: 0x05, label: "<=", operands: "single" },
|
|
256
297
|
{ key: "BETWEEN", code: 0x06, label: "between", operands: "range" },
|
|
257
298
|
{ key: "IN", code: 0x07, label: "in", operands: "variadic" },
|
|
258
|
-
{ key: "EQ_CTX", code: 0x08, label: "== ctx", operands: "single" },
|
|
259
|
-
{ key: "GT_CTX", code: 0x09, label: "> ctx", operands: "single" },
|
|
260
|
-
{ key: "LT_CTX", code: 0x0a, label: "< ctx", operands: "single" },
|
|
261
|
-
{ key: "GTE_CTX", code: 0x0b, label: ">= ctx", operands: "single" },
|
|
262
|
-
{ key: "LTE_CTX", code: 0x0c, label: "<= ctx", operands: "single" },
|
|
263
299
|
{ key: "BITMASK_ALL", code: 0x10, label: "bitmask all", operands: "single" },
|
|
264
300
|
{ key: "BITMASK_ANY", code: 0x11, label: "bitmask any", operands: "single" },
|
|
265
301
|
{ key: "BITMASK_NONE", code: 0x12, label: "bitmask none", operands: "single" },
|
|
266
|
-
{ key: "LENGTH_EQ", code:
|
|
267
|
-
{ key: "LENGTH_GT", code:
|
|
268
|
-
{ key: "LENGTH_LT", code:
|
|
269
|
-
{ key: "LENGTH_GTE", code:
|
|
270
|
-
{ key: "LENGTH_LTE", code:
|
|
271
|
-
{ key: "LENGTH_BETWEEN", code:
|
|
302
|
+
{ key: "LENGTH_EQ", code: 0x18, label: "length ==", operands: "single" },
|
|
303
|
+
{ key: "LENGTH_GT", code: 0x19, label: "length >", operands: "single" },
|
|
304
|
+
{ key: "LENGTH_LT", code: 0x1a, label: "length <", operands: "single" },
|
|
305
|
+
{ key: "LENGTH_GTE", code: 0x1b, label: "length >=", operands: "single" },
|
|
306
|
+
{ key: "LENGTH_LTE", code: 0x1c, label: "length <=", operands: "single" },
|
|
307
|
+
{ key: "LENGTH_BETWEEN", code: 0x1d, label: "length between", operands: "range" },
|
|
272
308
|
] as const;
|
|
273
309
|
|
|
274
|
-
/** Callcium policy operator codes. */
|
|
275
|
-
export const Op
|
|
310
|
+
/** Callcium policy operator codes, with the NOT flag and the operand source fields. */
|
|
311
|
+
export const Op = { ...buildCodeMap(OP_TABLE), NOT: 0x80, SOURCE_SHIFT: 5, COMPARISON_MASK: 0x1f } as const;
|
|
312
|
+
|
|
313
|
+
/** A defined comparison code. */
|
|
314
|
+
export type ComparisonCode = (typeof OP_TABLE)[number]["code"];
|
|
315
|
+
|
|
316
|
+
const SOURCE_TABLE = [
|
|
317
|
+
{ key: "CONSTANT", code: 0, labelSuffix: "" },
|
|
318
|
+
{ key: "CONTEXT", code: 1, labelSuffix: " ctx" },
|
|
319
|
+
] as const;
|
|
320
|
+
|
|
321
|
+
/** Operand source: constant values, or a context property. */
|
|
322
|
+
export const Source = buildCodeMap(SOURCE_TABLE);
|
|
323
|
+
|
|
324
|
+
/** A defined operand source code. */
|
|
325
|
+
export type SourceCode = (typeof SOURCE_TABLE)[number]["code"];
|
|
276
326
|
|
|
277
327
|
/** Display metadata for an operator code. */
|
|
278
328
|
export type OpInfo = { label: string; operands: Operands };
|
|
@@ -281,24 +331,44 @@ const opByCode: ReadonlyMap<number, OpInfo> = new Map<number, OpInfo>(
|
|
|
281
331
|
OP_TABLE.map((entry) => [entry.code, { label: entry.label, operands: entry.operands }]),
|
|
282
332
|
);
|
|
283
333
|
|
|
334
|
+
const labelSuffixBySource: ReadonlyMap<number, string> = new Map<number, string>(
|
|
335
|
+
SOURCE_TABLE.map((entry) => [entry.code, entry.labelSuffix]),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
/** Check whether a comparison is a defined comparison code. */
|
|
339
|
+
export function isComparisonCode(code: number): code is ComparisonCode {
|
|
340
|
+
return opByCode.has(code);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Check whether a source is a defined operand source code. */
|
|
344
|
+
export function isSourceCode(code: number): code is SourceCode {
|
|
345
|
+
return labelSuffixBySource.has(code);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** The comparison bits of an operator code. */
|
|
349
|
+
export function comparisonOf(opCode: number): number {
|
|
350
|
+
return opCode & Op.COMPARISON_MASK;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** The operand source bits of an operator code. */
|
|
354
|
+
export function sourceOf(opCode: number): number {
|
|
355
|
+
return (opCode & ~Op.NOT) >> Op.SOURCE_SHIFT;
|
|
356
|
+
}
|
|
357
|
+
|
|
284
358
|
/**
|
|
285
|
-
* Map an operator code to its display label. Strips the NOT flag automatically.
|
|
286
|
-
* @param code - Operator byte value (
|
|
359
|
+
* Map an operator code to its display label. Strips the NOT flag automatically. A context source adds ` ctx`.
|
|
360
|
+
* @param code - Operator byte value (can include the NOT flag).
|
|
287
361
|
* @returns Display metadata for the base operator.
|
|
288
362
|
* @throws {CallciumError} If the base code is not a recognised operator.
|
|
289
363
|
*/
|
|
290
364
|
export function lookupOp(code: number): OpInfo {
|
|
291
365
|
const base = code & ~Op.NOT;
|
|
292
|
-
const info = opByCode.get(base);
|
|
293
|
-
|
|
366
|
+
const info = opByCode.get(comparisonOf(base));
|
|
367
|
+
const labelSuffix = labelSuffixBySource.get(sourceOf(base));
|
|
368
|
+
if (!info || labelSuffix === undefined)
|
|
294
369
|
throw new CallciumError(
|
|
295
370
|
"UNKNOWN_OPERATOR",
|
|
296
371
|
`Unknown operator code ${formatCode(base, PolicyFormat.RULE_OPCODE_SIZE)}`,
|
|
297
372
|
);
|
|
298
|
-
return info;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/** Operand count category for an operator code, or undefined when unrecognised. */
|
|
302
|
-
export function operandsOf(opBase: number): Operands | undefined {
|
|
303
|
-
return opByCode.get(opBase)?.operands;
|
|
373
|
+
return { label: `${info.label}${labelSuffix}`, operands: info.operands };
|
|
304
374
|
}
|
package/src/constraint.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { bytesToHex, hexToBytes, toAddress } from "./bytes";
|
|
2
|
-
import {
|
|
2
|
+
import { ContextProperty, lookupContextProperty, Op, Scope, Source } from "./constants";
|
|
3
3
|
import { CallciumError } from "./errors";
|
|
4
4
|
import { encodePath } from "./path";
|
|
5
5
|
|
|
6
|
-
import type { Address, Hex
|
|
6
|
+
import type { Address, Hex } from "./types";
|
|
7
|
+
import type { Constraint } from "./types";
|
|
7
8
|
|
|
8
9
|
///////////////////////////////////////////////////////////////////////////
|
|
9
10
|
// Value encoding helpers
|
|
@@ -32,6 +33,17 @@ function addressBody(value: string): string {
|
|
|
32
33
|
return toAddress(value).slice(2);
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Require an operand that reached the builder untyped to be a bigint and return it.
|
|
38
|
+
* @throws {CallciumError} When the operand is of another type, as a JavaScript `number` is.
|
|
39
|
+
*/
|
|
40
|
+
function requireBigint(value: unknown): bigint {
|
|
41
|
+
if (typeof value !== "bigint") {
|
|
42
|
+
throw new CallciumError("MALFORMED_OPERAND", "Operand is not a bigint, a boolean or an address string");
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
/** Convert a scalar value to a 32-byte big-endian word. */
|
|
36
48
|
function encodeWord(value: ScalarValue): Uint8Array {
|
|
37
49
|
const word = new Uint8Array(32);
|
|
@@ -47,6 +59,8 @@ function encodeWord(value: ScalarValue): Uint8Array {
|
|
|
47
59
|
return word;
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
requireBigint(value);
|
|
63
|
+
|
|
50
64
|
// A negative operand occupies the word in two's complement, which is the signed target's encoding.
|
|
51
65
|
let bigValue = checkOperandRange(value);
|
|
52
66
|
for (let i = 31; i >= 0; i--) {
|
|
@@ -79,6 +93,9 @@ function rangeOp(opCode: number, min: bigint, max: bigint): Hex {
|
|
|
79
93
|
return bytesToHex(buffer);
|
|
80
94
|
}
|
|
81
95
|
|
|
96
|
+
/** The `source` bits of an operator code whose operand is a context property. */
|
|
97
|
+
const CONTEXT_SOURCE = Source.CONTEXT << Op.SOURCE_SHIFT;
|
|
98
|
+
|
|
82
99
|
/**
|
|
83
100
|
* Require an assigned context property ID and return it as an operand word.
|
|
84
101
|
* @throws {CallciumError} When the ID names no assigned property.
|
|
@@ -95,8 +112,8 @@ function setOp(opCode: number, values: readonly ScalarValue[]): Hex {
|
|
|
95
112
|
const words = values.map((value) => {
|
|
96
113
|
if (typeof value === "bigint") return BigInt.asUintN(256, checkOperandRange(value));
|
|
97
114
|
if (typeof value === "boolean") return value ? 1n : 0n;
|
|
98
|
-
|
|
99
|
-
return
|
|
115
|
+
if (typeof value === "string") return BigInt("0x" + addressBody(value));
|
|
116
|
+
return requireBigint(value);
|
|
100
117
|
});
|
|
101
118
|
|
|
102
119
|
const deduped = [...new Set(words)].toSorted((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
@@ -104,9 +121,6 @@ function setOp(opCode: number, values: readonly ScalarValue[]): Hex {
|
|
|
104
121
|
if (deduped.length === 0) {
|
|
105
122
|
throw new CallciumError("EMPTY_SET", "Set must contain at least one value");
|
|
106
123
|
}
|
|
107
|
-
if (deduped.length > PolicyFormat.MAX_SET_MEMBERS) {
|
|
108
|
-
throw new CallciumError("SET_TOO_LARGE", `Set must contain at most ${PolicyFormat.MAX_SET_MEMBERS} values`);
|
|
109
|
-
}
|
|
110
124
|
|
|
111
125
|
const buffer = new Uint8Array(1 + deduped.length * 32);
|
|
112
126
|
buffer[0] = opCode;
|
|
@@ -149,19 +163,17 @@ export function readOperandExtremes(constraint: Constraint | ConstraintBuilder):
|
|
|
149
163
|
* be passed directly to policy builders.
|
|
150
164
|
*/
|
|
151
165
|
export class ConstraintBuilder<Operand extends ScalarValue = ScalarValue> implements Constraint {
|
|
152
|
-
readonly scope:
|
|
166
|
+
readonly scope: Constraint["scope"];
|
|
153
167
|
readonly path: Hex;
|
|
154
168
|
readonly operators: Hex[];
|
|
155
169
|
/**
|
|
156
|
-
* Extremes of the numeric operands the value operators received, as written.
|
|
157
|
-
* folds into a target's range from below it or from above it, and whichever reaches furthest arrives first. Read it
|
|
158
|
-
* through {@link readOperandExtremes}.
|
|
170
|
+
* Extremes of the numeric operands the value operators received, as written.
|
|
159
171
|
* @internal
|
|
160
172
|
*/
|
|
161
173
|
readonly operandExtremes: OperandExtremes;
|
|
162
174
|
|
|
163
175
|
/** @internal */
|
|
164
|
-
constructor(scope:
|
|
176
|
+
constructor(scope: Constraint["scope"], path: Hex) {
|
|
165
177
|
this.scope = scope;
|
|
166
178
|
this.path = path;
|
|
167
179
|
this.operators = [];
|
|
@@ -169,7 +181,7 @@ export class ConstraintBuilder<Operand extends ScalarValue = ScalarValue> implem
|
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
/**
|
|
172
|
-
* Push a pre-encoded operator hex string and return this for chaining. `values` are the operands as written
|
|
184
|
+
* Push a pre-encoded operator hex string and return this for chaining. `values` are the operands as written. The
|
|
173
185
|
* numeric ones join the record.
|
|
174
186
|
*/
|
|
175
187
|
private push(opHex: Hex, values: readonly ScalarValue[] = []): this {
|
|
@@ -198,32 +210,32 @@ export class ConstraintBuilder<Operand extends ScalarValue = ScalarValue> implem
|
|
|
198
210
|
|
|
199
211
|
/** Assert the value equals the context property `contextPropertyId`. */
|
|
200
212
|
eqCtx(contextPropertyId: number): this {
|
|
201
|
-
return this.push(singleOp(Op.
|
|
213
|
+
return this.push(singleOp(Op.EQ | CONTEXT_SOURCE, contextOperand(contextPropertyId)));
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
/** Assert the value does not equal the context property `contextPropertyId`. */
|
|
205
217
|
neqCtx(contextPropertyId: number): this {
|
|
206
|
-
return this.push(singleOp(Op.
|
|
218
|
+
return this.push(singleOp(Op.EQ | CONTEXT_SOURCE | Op.NOT, contextOperand(contextPropertyId)));
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
/** Assert the value is greater than the context property `contextPropertyId`. */
|
|
210
222
|
gtCtx(contextPropertyId: number): this {
|
|
211
|
-
return this.push(singleOp(Op.
|
|
223
|
+
return this.push(singleOp(Op.GT | CONTEXT_SOURCE, contextOperand(contextPropertyId)));
|
|
212
224
|
}
|
|
213
225
|
|
|
214
226
|
/** Assert the value is less than the context property `contextPropertyId`. */
|
|
215
227
|
ltCtx(contextPropertyId: number): this {
|
|
216
|
-
return this.push(singleOp(Op.
|
|
228
|
+
return this.push(singleOp(Op.LT | CONTEXT_SOURCE, contextOperand(contextPropertyId)));
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
/** Assert the value is greater than or equal to the context property `contextPropertyId`. */
|
|
220
232
|
gteCtx(contextPropertyId: number): this {
|
|
221
|
-
return this.push(singleOp(Op.
|
|
233
|
+
return this.push(singleOp(Op.GTE | CONTEXT_SOURCE, contextOperand(contextPropertyId)));
|
|
222
234
|
}
|
|
223
235
|
|
|
224
236
|
/** Assert the value is less than or equal to the context property `contextPropertyId`. */
|
|
225
237
|
lteCtx(contextPropertyId: number): this {
|
|
226
|
-
return this.push(singleOp(Op.
|
|
238
|
+
return this.push(singleOp(Op.LTE | CONTEXT_SOURCE, contextOperand(contextPropertyId)));
|
|
227
239
|
}
|
|
228
240
|
|
|
229
241
|
/** Assert the value is greater than `bound`. */
|
|
@@ -260,8 +272,7 @@ export class ConstraintBuilder<Operand extends ScalarValue = ScalarValue> implem
|
|
|
260
272
|
|
|
261
273
|
/**
|
|
262
274
|
* Assert the value is a member of the set. Values are sorted and deduplicated before encoding.
|
|
263
|
-
* @throws {CallciumError} If the set is empty
|
|
264
|
-
* encodable as an operand word.
|
|
275
|
+
* @throws {CallciumError} If the set is empty or a value is not encodable as an operand word.
|
|
265
276
|
*/
|
|
266
277
|
isIn(values: readonly Operand[]): this {
|
|
267
278
|
return this.push(setOp(Op.IN, values), values);
|
|
@@ -269,8 +280,7 @@ export class ConstraintBuilder<Operand extends ScalarValue = ScalarValue> implem
|
|
|
269
280
|
|
|
270
281
|
/**
|
|
271
282
|
* Assert the value is not a member of the set.
|
|
272
|
-
* @throws {CallciumError} If the set is empty
|
|
273
|
-
* encodable as an operand word.
|
|
283
|
+
* @throws {CallciumError} If the set is empty or a value is not encodable as an operand word.
|
|
274
284
|
*/
|
|
275
285
|
notIn(values: readonly Operand[]): this {
|
|
276
286
|
return this.push(setOp(Op.IN | Op.NOT, values), values);
|
|
@@ -338,7 +348,7 @@ export class ConstraintBuilder<Operand extends ScalarValue = ScalarValue> implem
|
|
|
338
348
|
///////////////////////////////////////////////////////////////////////////
|
|
339
349
|
|
|
340
350
|
/**
|
|
341
|
-
* Target a calldata argument by path. Each argument is a big-endian uint16 step
|
|
351
|
+
* Target a calldata argument by path. Each argument is a big-endian uint16 step. Further steps navigate nested types.
|
|
342
352
|
*/
|
|
343
353
|
export function arg(p0: number, ...rest: number[]): ConstraintBuilder;
|
|
344
354
|
export function arg(...steps: number[]): ConstraintBuilder {
|