@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/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
2
2
|
//#region src/errors.ts
|
|
3
|
-
var CallciumError = class
|
|
3
|
+
var CallciumError = class extends Error {
|
|
4
4
|
code;
|
|
5
5
|
offset;
|
|
6
6
|
constructor(code, message, offset) {
|
|
@@ -8,25 +8,22 @@ var CallciumError = class CallciumError extends Error {
|
|
|
8
8
|
super(`${prefix}${message}`);
|
|
9
9
|
this.name = "CallciumError";
|
|
10
10
|
this.code = code;
|
|
11
|
-
Object.setPrototypeOf(this, CallciumError.prototype);
|
|
12
11
|
}
|
|
13
12
|
};
|
|
14
|
-
var ValidationError = class
|
|
13
|
+
var ValidationError = class extends CallciumError {
|
|
15
14
|
issues;
|
|
16
15
|
constructor(issues) {
|
|
17
16
|
super("VALIDATION_ERROR", issues[0]?.message ?? "Policy validation failed");
|
|
18
17
|
this.name = "ValidationError";
|
|
19
18
|
this.issues = issues;
|
|
20
|
-
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
21
19
|
}
|
|
22
20
|
};
|
|
23
|
-
var PolicyViolationError = class
|
|
21
|
+
var PolicyViolationError = class extends Error {
|
|
24
22
|
violations;
|
|
25
23
|
constructor(violations) {
|
|
26
24
|
super(formatDiagnostic(violations[0]));
|
|
27
25
|
this.name = "PolicyViolationError";
|
|
28
26
|
this.violations = violations;
|
|
29
|
-
Object.setPrototypeOf(this, PolicyViolationError.prototype);
|
|
30
27
|
}
|
|
31
28
|
};
|
|
32
29
|
function formatDiagnostic(violation) {
|
|
@@ -36,29 +33,29 @@ function formatDiagnostic(violation) {
|
|
|
36
33
|
}
|
|
37
34
|
//#endregion
|
|
38
35
|
//#region src/bytes.ts
|
|
39
|
-
const
|
|
36
|
+
const hexBodyPattern = /^[0-9a-fA-F]*$/;
|
|
40
37
|
const HEX_DIGITS = "0123456789abcdef";
|
|
41
|
-
const
|
|
38
|
+
const nibble = new Int8Array(128).fill(-1);
|
|
42
39
|
for (let value = 0; value < 16; value++) {
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
nibble[HEX_DIGITS.charCodeAt(value)] = value;
|
|
41
|
+
nibble[HEX_DIGITS.toUpperCase().charCodeAt(value)] = value;
|
|
45
42
|
}
|
|
46
43
|
function hexToBytes(hex) {
|
|
47
44
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
48
45
|
if (clean.length % 2 !== 0) throw new CallciumError("INVALID_HEX", "Odd-length hex string");
|
|
49
46
|
const bytes = new Uint8Array(clean.length / 2);
|
|
50
47
|
for (let i = 0; i < bytes.length; i++) {
|
|
51
|
-
const high =
|
|
52
|
-
const low =
|
|
48
|
+
const high = nibble[clean.charCodeAt(i * 2)] ?? -1;
|
|
49
|
+
const low = nibble[clean.charCodeAt(i * 2 + 1)] ?? -1;
|
|
53
50
|
if (high < 0 || low < 0) throw new CallciumError("INVALID_HEX", "Invalid hex characters");
|
|
54
51
|
bytes[i] = high << 4 | low;
|
|
55
52
|
}
|
|
56
53
|
return bytes;
|
|
57
54
|
}
|
|
58
|
-
const
|
|
55
|
+
const hexPairs = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
59
56
|
function bytesToHex(bytes) {
|
|
60
57
|
let body = "";
|
|
61
|
-
for (let i = 0; i < bytes.length; i++) body +=
|
|
58
|
+
for (let i = 0; i < bytes.length; i++) body += hexPairs[bytes[i]];
|
|
62
59
|
return `0x${body}`;
|
|
63
60
|
}
|
|
64
61
|
function toHex(data, start, end) {
|
|
@@ -94,7 +91,7 @@ function bigintToHex(value) {
|
|
|
94
91
|
function toAddress(hex) {
|
|
95
92
|
const body = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
96
93
|
if (body.length !== 40) throw new CallciumError("INVALID_HEX", `Invalid address length: expected 40 hex chars, got ${body.length}`);
|
|
97
|
-
if (!
|
|
94
|
+
if (!hexBodyPattern.test(body)) throw new CallciumError("INVALID_HEX", "Invalid hex characters in address");
|
|
98
95
|
return `0x${body}`;
|
|
99
96
|
}
|
|
100
97
|
//#endregion
|
|
@@ -117,6 +114,7 @@ const DescriptorFormat = {
|
|
|
117
114
|
MAX_PARAMS: 255,
|
|
118
115
|
MAX_NESTING_DEPTH: 64
|
|
119
116
|
};
|
|
117
|
+
const RULE_DATALENGTH_SIZE = 2;
|
|
120
118
|
const PolicyFormat = {
|
|
121
119
|
VERSION: 2,
|
|
122
120
|
VERSION_MASK: 15,
|
|
@@ -138,7 +136,7 @@ const PolicyFormat = {
|
|
|
138
136
|
RULE_PATH_OFFSET: 4,
|
|
139
137
|
PATH_STEP_SIZE: 2,
|
|
140
138
|
RULE_OPCODE_SIZE: 1,
|
|
141
|
-
RULE_DATALENGTH_SIZE
|
|
139
|
+
RULE_DATALENGTH_SIZE,
|
|
142
140
|
RULE_FIXED_OVERHEAD: 7,
|
|
143
141
|
RULE_MIN_SIZE: 9,
|
|
144
142
|
HINT_HEADER_SIZE: 1,
|
|
@@ -163,7 +161,10 @@ const PolicyFormat = {
|
|
|
163
161
|
HINT_META_ELEM_DYNAMIC: 32768,
|
|
164
162
|
HINT_META_DYNAMIC_ARRAY: 16384,
|
|
165
163
|
HINT_META_RESERVED_MASK: 12288,
|
|
166
|
-
HINT_META_STRIDE_MASK: 4095
|
|
164
|
+
HINT_META_STRIDE_MASK: 4095,
|
|
165
|
+
MAX_PATH_DEPTH: 32,
|
|
166
|
+
MAX_QUANTIFIED_ARRAY_LENGTH: 256,
|
|
167
|
+
MAX_SET_MEMBERS: Math.floor((256 ** RULE_DATALENGTH_SIZE - 1) / 32)
|
|
167
168
|
};
|
|
168
169
|
function buildCodeMap(table) {
|
|
169
170
|
return Object.fromEntries(table.map((e) => [e.key, e.code]));
|
|
@@ -200,83 +201,76 @@ const TypeCode = {
|
|
|
200
201
|
DYNAMIC_ARRAY: 129,
|
|
201
202
|
TUPLE: 144
|
|
202
203
|
};
|
|
203
|
-
const
|
|
204
|
+
const CTX_PROPERTY_TABLE = [
|
|
204
205
|
{
|
|
205
206
|
key: "MSG_SENDER",
|
|
206
207
|
code: 0,
|
|
207
208
|
label: "msg.sender",
|
|
209
|
+
contextKey: "msgSender",
|
|
208
210
|
typeCode: TypeCode.ADDRESS
|
|
209
211
|
},
|
|
210
212
|
{
|
|
211
213
|
key: "MSG_VALUE",
|
|
212
214
|
code: 1,
|
|
213
215
|
label: "msg.value",
|
|
216
|
+
contextKey: "msgValue",
|
|
214
217
|
typeCode: TypeCode.UINT_MAX
|
|
215
218
|
},
|
|
216
219
|
{
|
|
217
220
|
key: "BLOCK_TIMESTAMP",
|
|
218
221
|
code: 2,
|
|
219
222
|
label: "block.timestamp",
|
|
223
|
+
contextKey: "blockTimestamp",
|
|
220
224
|
typeCode: TypeCode.UINT_MAX
|
|
221
225
|
},
|
|
222
226
|
{
|
|
223
227
|
key: "BLOCK_NUMBER",
|
|
224
228
|
code: 3,
|
|
225
229
|
label: "block.number",
|
|
230
|
+
contextKey: "blockNumber",
|
|
226
231
|
typeCode: TypeCode.UINT_MAX
|
|
227
232
|
},
|
|
228
233
|
{
|
|
229
234
|
key: "CHAIN_ID",
|
|
230
235
|
code: 4,
|
|
231
236
|
label: "block.chainid",
|
|
237
|
+
contextKey: "chainId",
|
|
232
238
|
typeCode: TypeCode.UINT_MAX
|
|
233
239
|
},
|
|
234
240
|
{
|
|
235
241
|
key: "TX_ORIGIN",
|
|
236
242
|
code: 5,
|
|
237
243
|
label: "tx.origin",
|
|
244
|
+
contextKey: "txOrigin",
|
|
238
245
|
typeCode: TypeCode.ADDRESS
|
|
239
246
|
},
|
|
240
247
|
{
|
|
241
248
|
key: "BASE_FEE",
|
|
242
249
|
code: 6,
|
|
243
250
|
label: "block.basefee",
|
|
251
|
+
contextKey: "baseFee",
|
|
244
252
|
typeCode: TypeCode.UINT_MAX
|
|
245
253
|
},
|
|
246
254
|
{
|
|
247
255
|
key: "GAS_PRICE",
|
|
248
256
|
code: 7,
|
|
249
257
|
label: "tx.gasprice",
|
|
258
|
+
contextKey: "gasPrice",
|
|
250
259
|
typeCode: TypeCode.UINT_MAX
|
|
251
260
|
}
|
|
252
261
|
];
|
|
253
|
-
const ContextProperty = buildCodeMap(
|
|
254
|
-
const MAX_CONTEXT_PROPERTY_ID = Math.max(...
|
|
255
|
-
const
|
|
256
|
-
MSG_SENDER: "msgSender",
|
|
257
|
-
MSG_VALUE: "msgValue",
|
|
258
|
-
BLOCK_TIMESTAMP: "blockTimestamp",
|
|
259
|
-
BLOCK_NUMBER: "blockNumber",
|
|
260
|
-
CHAIN_ID: "chainId",
|
|
261
|
-
TX_ORIGIN: "txOrigin",
|
|
262
|
-
BASE_FEE: "baseFee",
|
|
263
|
-
GAS_PRICE: "gasPrice"
|
|
264
|
-
};
|
|
265
|
-
const ctxPropByCode = new Map(CTX_PROP_TABLE.map((e) => [e.code, {
|
|
262
|
+
const ContextProperty = buildCodeMap(CTX_PROPERTY_TABLE);
|
|
263
|
+
const MAX_CONTEXT_PROPERTY_ID = Math.max(...CTX_PROPERTY_TABLE.map((e) => e.code));
|
|
264
|
+
const ctxPropertyByCode = new Map(CTX_PROPERTY_TABLE.map((e) => [e.code, {
|
|
266
265
|
label: e.label,
|
|
267
|
-
contextKey:
|
|
266
|
+
contextKey: e.contextKey,
|
|
268
267
|
typeCode: e.typeCode
|
|
269
268
|
}]));
|
|
270
269
|
function lookupContextProperty(code) {
|
|
271
|
-
const info =
|
|
270
|
+
const info = ctxPropertyByCode.get(code);
|
|
272
271
|
if (!info) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ${code}`);
|
|
273
272
|
return info;
|
|
274
273
|
}
|
|
275
|
-
const Limits = {
|
|
276
|
-
MAX_PATH_DEPTH: 32,
|
|
277
|
-
MAX_QUANTIFIED_ARRAY_LENGTH: 256,
|
|
278
|
-
MAX_SET_MEMBERS: Math.floor((256 ** PolicyFormat.RULE_DATALENGTH_SIZE - 1) / 32)
|
|
279
|
-
};
|
|
280
274
|
const OP_TABLE = [
|
|
281
275
|
{
|
|
282
276
|
key: "EQ",
|
|
@@ -395,13 +389,11 @@ function lookupOp(code) {
|
|
|
395
389
|
if (!info) throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}`);
|
|
396
390
|
return info;
|
|
397
391
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
if (operands === "single") return dataLength === 32;
|
|
401
|
-
if (operands === "range") return dataLength === 64;
|
|
402
|
-
if (operands === "variadic") return dataLength > 0 && dataLength % 32 === 0;
|
|
403
|
-
return false;
|
|
392
|
+
function operandsOf(opBase) {
|
|
393
|
+
return opByCode.get(opBase)?.operands;
|
|
404
394
|
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/path.ts
|
|
405
397
|
const QUANTIFIER_TABLE = [{
|
|
406
398
|
key: "ALL",
|
|
407
399
|
code: 65535,
|
|
@@ -421,71 +413,17 @@ function lookupQuantifier(code) {
|
|
|
421
413
|
if (!info) throw new CallciumError("UNKNOWN_QUANTIFIER", `Unknown quantifier step 0x${code.toString(16)}`);
|
|
422
414
|
return info;
|
|
423
415
|
}
|
|
424
|
-
function
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
typeClass: "elementary",
|
|
429
|
-
isDynamic: false
|
|
430
|
-
};
|
|
431
|
-
const ELEMENTARY_DYN = {
|
|
432
|
-
typeClass: "elementary",
|
|
433
|
-
isDynamic: true
|
|
434
|
-
};
|
|
435
|
-
const STATIC_ARR = {
|
|
436
|
-
typeClass: "staticArray",
|
|
437
|
-
isDynamic: false
|
|
438
|
-
};
|
|
439
|
-
const DYNAMIC_ARR = {
|
|
440
|
-
typeClass: "dynamicArray",
|
|
441
|
-
isDynamic: true
|
|
442
|
-
};
|
|
443
|
-
const TUPLE = {
|
|
444
|
-
typeClass: "tuple",
|
|
445
|
-
isDynamic: false
|
|
446
|
-
};
|
|
447
|
-
function classifyTypeCode(code) {
|
|
448
|
-
if (code >= TypeCode.UINT_MIN && code <= TypeCode.UINT_MAX) return ELEMENTARY;
|
|
449
|
-
if (code >= TypeCode.INT_MIN && code <= TypeCode.INT_MAX) return ELEMENTARY;
|
|
450
|
-
if (code === TypeCode.ADDRESS || code === TypeCode.BOOL || code === TypeCode.FUNCTION) return ELEMENTARY;
|
|
451
|
-
if (code >= 68 && code <= 79) unknownTypeCode(code);
|
|
452
|
-
if (code >= TypeCode.FIXED_BYTES_MIN && code <= TypeCode.FIXED_BYTES_MAX) return ELEMENTARY;
|
|
453
|
-
if (code === TypeCode.BYTES || code === TypeCode.STRING) return ELEMENTARY_DYN;
|
|
454
|
-
if (code >= 114 && code <= 127) unknownTypeCode(code);
|
|
455
|
-
if (code === TypeCode.STATIC_ARRAY) return STATIC_ARR;
|
|
456
|
-
if (code === TypeCode.DYNAMIC_ARRAY) return DYNAMIC_ARR;
|
|
457
|
-
if (code >= 130 && code <= 143) unknownTypeCode(code);
|
|
458
|
-
if (code === TypeCode.TUPLE) return TUPLE;
|
|
459
|
-
unknownTypeCode(code);
|
|
460
|
-
}
|
|
461
|
-
function isAddressableTarget(code) {
|
|
462
|
-
let info;
|
|
463
|
-
try {
|
|
464
|
-
info = classifyTypeCode(code);
|
|
465
|
-
} catch {
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
return info.typeClass === "elementary" || info.typeClass === "dynamicArray";
|
|
469
|
-
}
|
|
470
|
-
function typeCodeLabel(code) {
|
|
471
|
-
if (code >= TypeCode.UINT_MIN && code <= TypeCode.UINT_MAX) return `uint${(code - TypeCode.UINT_MIN + 1) * 8}`;
|
|
472
|
-
if (code >= TypeCode.INT_MIN && code <= TypeCode.INT_MAX) return `int${(code - TypeCode.INT_MIN + 1) * 8}`;
|
|
473
|
-
if (code === TypeCode.ADDRESS) return "address";
|
|
474
|
-
if (code === TypeCode.BOOL) return "bool";
|
|
475
|
-
if (code === TypeCode.FUNCTION) return "function";
|
|
476
|
-
if (code >= TypeCode.FIXED_BYTES_MIN && code <= TypeCode.FIXED_BYTES_MAX) return `bytes${code - TypeCode.FIXED_BYTES_MIN + 1}`;
|
|
477
|
-
if (code === TypeCode.BYTES) return "bytes";
|
|
478
|
-
if (code === TypeCode.STRING) return "string";
|
|
479
|
-
if (code === TypeCode.STATIC_ARRAY) return "T[k]";
|
|
480
|
-
if (code === TypeCode.DYNAMIC_ARRAY) return "T[]";
|
|
481
|
-
if (code === TypeCode.TUPLE) return "tuple";
|
|
482
|
-
return `0x${code.toString(16).padStart(2, "0")}`;
|
|
416
|
+
function encodePath(steps) {
|
|
417
|
+
const buffer = new Uint8Array(steps.length * 2);
|
|
418
|
+
for (let i = 0; i < steps.length; i++) writeBE16(buffer, i * 2, steps[i]);
|
|
419
|
+
return bytesToHex(buffer);
|
|
483
420
|
}
|
|
484
|
-
function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
421
|
+
function parsePathSteps(path) {
|
|
422
|
+
const bytes = hexToBytes(path);
|
|
423
|
+
if (bytes.length % PolicyFormat.PATH_STEP_SIZE !== 0) throw new CallciumError("MALFORMED_PATH", `Path byte length ${bytes.length} is not a whole number of steps`);
|
|
424
|
+
const steps = [];
|
|
425
|
+
for (let offset = 0; offset < bytes.length; offset += PolicyFormat.PATH_STEP_SIZE) steps.push(readU16(bytes, offset));
|
|
426
|
+
return steps;
|
|
489
427
|
}
|
|
490
428
|
//#endregion
|
|
491
429
|
//#region src/descriptor.ts
|
|
@@ -503,7 +441,7 @@ function paramCount(desc) {
|
|
|
503
441
|
}
|
|
504
442
|
function paramOffset(desc, index) {
|
|
505
443
|
const count = desc[1];
|
|
506
|
-
if (index >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${index} out of range (paramCount=${count})
|
|
444
|
+
if (index >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${index} out of range (paramCount=${count})`);
|
|
507
445
|
let cursor = DescriptorFormat.HEADER_SIZE;
|
|
508
446
|
for (let i = 0; i < index; i++) cursor += nodeLength(desc, cursor);
|
|
509
447
|
return cursor;
|
|
@@ -523,10 +461,10 @@ function typeAt(desc, steps) {
|
|
|
523
461
|
return walkPath(desc, steps).typeInfo;
|
|
524
462
|
}
|
|
525
463
|
function walkPath(desc, steps) {
|
|
526
|
-
if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step
|
|
464
|
+
if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
|
|
527
465
|
const paramIndex = steps[0];
|
|
528
466
|
const count = desc[1];
|
|
529
|
-
if (paramIndex >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${paramIndex} out of range (paramCount=${count})
|
|
467
|
+
if (paramIndex >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${paramIndex} out of range (paramCount=${count})`);
|
|
530
468
|
let cursor = paramOffset(desc, paramIndex);
|
|
531
469
|
let quantifiedStaticLength = 0;
|
|
532
470
|
for (let stepIndex = 1; stepIndex < steps.length; stepIndex++) {
|
|
@@ -534,16 +472,16 @@ function walkPath(desc, steps) {
|
|
|
534
472
|
const step = steps[stepIndex];
|
|
535
473
|
if (typeCode === TypeCode.TUPLE) {
|
|
536
474
|
const fields = tupleFieldCount(desc, cursor);
|
|
537
|
-
if (step >= fields) throw new CallciumError("TUPLE_FIELD_OUT_OF_BOUNDS", `Tuple field index ${step} out of range (tuple has ${fields} fields)
|
|
475
|
+
if (step >= fields) throw new CallciumError("TUPLE_FIELD_OUT_OF_BOUNDS", `Tuple field index ${step} out of range (tuple has ${fields} fields)`);
|
|
538
476
|
cursor = tupleFieldOffset(desc, cursor, step);
|
|
539
477
|
} else if (typeCode === TypeCode.STATIC_ARRAY || typeCode === TypeCode.DYNAMIC_ARRAY) {
|
|
540
478
|
if (typeCode === TypeCode.STATIC_ARRAY) if (isQuantifier(step)) quantifiedStaticLength = staticArrayLength(desc, cursor);
|
|
541
479
|
else {
|
|
542
480
|
const length = staticArrayLength(desc, cursor);
|
|
543
|
-
if (step >= length) throw new CallciumError("STATIC_ARRAY_INDEX_OUT_OF_BOUNDS", `Array index ${step} out of range (length=${length})
|
|
481
|
+
if (step >= length) throw new CallciumError("STATIC_ARRAY_INDEX_OUT_OF_BOUNDS", `Array index ${step} out of range (length=${length})`);
|
|
544
482
|
}
|
|
545
483
|
cursor = arrayElementOffset(cursor);
|
|
546
|
-
} else throw new CallciumError("NOT_COMPOSITE", `Cannot descend into elementary type at offset ${cursor}
|
|
484
|
+
} else throw new CallciumError("NOT_COMPOSITE", `Cannot descend into elementary type at offset ${cursor}`);
|
|
547
485
|
}
|
|
548
486
|
return {
|
|
549
487
|
typeInfo: inspect$1(desc, cursor),
|
|
@@ -561,7 +499,7 @@ function arrayElementOffset(offset) {
|
|
|
561
499
|
return offset + DescriptorFormat.ARRAY_HEADER_SIZE;
|
|
562
500
|
}
|
|
563
501
|
function uncompilablePath(stepIndex) {
|
|
564
|
-
return new CallciumError("UNCOMPILABLE_PATH", `Path step ${stepIndex} does not resolve to a hop-chain target
|
|
502
|
+
return new CallciumError("UNCOMPILABLE_PATH", `Path step ${stepIndex} does not resolve to a hop-chain target`);
|
|
565
503
|
}
|
|
566
504
|
function headSpan(desc, offset, count) {
|
|
567
505
|
let span = 0;
|
|
@@ -668,7 +606,7 @@ function encodeHint(walk, targetMeta, typeCode, depth) {
|
|
|
668
606
|
return out;
|
|
669
607
|
}
|
|
670
608
|
function compileHint(desc, steps) {
|
|
671
|
-
if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step
|
|
609
|
+
if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
|
|
672
610
|
const argIndex = steps[0];
|
|
673
611
|
if (argIndex >= paramCount(desc)) throw uncompilablePath(0);
|
|
674
612
|
const arg = headSpan(desc, DescriptorFormat.HEADER_SIZE, argIndex);
|
|
@@ -704,6 +642,199 @@ const Descriptor = {
|
|
|
704
642
|
compileHint
|
|
705
643
|
};
|
|
706
644
|
//#endregion
|
|
645
|
+
//#region src/operators.ts
|
|
646
|
+
function toBigInt(bytes, offset = 0) {
|
|
647
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, 32);
|
|
648
|
+
return view.getBigUint64(0, false) << 192n | view.getBigUint64(8, false) << 128n | view.getBigUint64(16, false) << 64n | view.getBigUint64(24, false);
|
|
649
|
+
}
|
|
650
|
+
function isSigned(typeCode) {
|
|
651
|
+
return typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX;
|
|
652
|
+
}
|
|
653
|
+
function isLeftAligned(typeCode) {
|
|
654
|
+
return typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX || typeCode === TypeCode.FUNCTION;
|
|
655
|
+
}
|
|
656
|
+
function canonicalize(value, typeCode) {
|
|
657
|
+
if (typeCode >= TypeCode.UINT_MIN && typeCode <= TypeCode.UINT_MAX) {
|
|
658
|
+
const bits = BigInt(typeCode * 8);
|
|
659
|
+
return bits === 256n ? value : value & (1n << bits) - 1n;
|
|
660
|
+
}
|
|
661
|
+
if (typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX) {
|
|
662
|
+
const bits = (typeCode - TypeCode.INT_MIN + 1) * 8;
|
|
663
|
+
return bits === 256 ? value : BigInt.asUintN(256, BigInt.asIntN(bits, value));
|
|
664
|
+
}
|
|
665
|
+
if (typeCode === TypeCode.ADDRESS) return value & (1n << 160n) - 1n;
|
|
666
|
+
if (typeCode === TypeCode.BOOL) return value & 1n;
|
|
667
|
+
if (typeCode === TypeCode.FUNCTION) return value >> 64n << 64n;
|
|
668
|
+
if (typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX) {
|
|
669
|
+
const n = typeCode - TypeCode.FIXED_BYTES_MIN + 1;
|
|
670
|
+
if (n === 32) return value;
|
|
671
|
+
const padBits = BigInt((32 - n) * 8);
|
|
672
|
+
return value >> padBits << padBits;
|
|
673
|
+
}
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
function isLengthOp(opCode) {
|
|
677
|
+
const base = opCode & ~Op.NOT;
|
|
678
|
+
return base >= Op.LENGTH_EQ && base <= Op.LENGTH_BETWEEN;
|
|
679
|
+
}
|
|
680
|
+
function lengthToValueOp(base) {
|
|
681
|
+
if (base === Op.LENGTH_EQ) return Op.EQ;
|
|
682
|
+
if (base === Op.LENGTH_GT) return Op.GT;
|
|
683
|
+
if (base === Op.LENGTH_LT) return Op.LT;
|
|
684
|
+
if (base === Op.LENGTH_GTE) return Op.GTE;
|
|
685
|
+
if (base === Op.LENGTH_LTE) return Op.LTE;
|
|
686
|
+
return Op.BETWEEN;
|
|
687
|
+
}
|
|
688
|
+
function isLengthValidType(typeCode) {
|
|
689
|
+
return typeCode === TypeCode.BYTES || typeCode === TypeCode.STRING || typeCode === TypeCode.DYNAMIC_ARRAY;
|
|
690
|
+
}
|
|
691
|
+
function compareTyped(a, b, typeCode) {
|
|
692
|
+
if (isSigned(typeCode)) {
|
|
693
|
+
const sa = BigInt.asIntN(256, a);
|
|
694
|
+
const sb = BigInt.asIntN(256, b);
|
|
695
|
+
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
696
|
+
}
|
|
697
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
698
|
+
}
|
|
699
|
+
function isIn(value, data) {
|
|
700
|
+
const count = data.length / 32;
|
|
701
|
+
let low = 0;
|
|
702
|
+
let high = count - 1;
|
|
703
|
+
while (low <= high) {
|
|
704
|
+
const mid = low + high >>> 1;
|
|
705
|
+
const elem = toBigInt(data, mid * 32);
|
|
706
|
+
if (value === elem) return true;
|
|
707
|
+
if (value < elem) high = mid - 1;
|
|
708
|
+
else low = mid + 1;
|
|
709
|
+
}
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
function applyOperator(opCode, value, valueLength, operandData, typeCode) {
|
|
713
|
+
let base = opCode & ~Op.NOT;
|
|
714
|
+
if (isLengthOp(base)) {
|
|
715
|
+
value = BigInt(valueLength);
|
|
716
|
+
typeCode = TypeCode.UINT_MAX;
|
|
717
|
+
base = lengthToValueOp(base);
|
|
718
|
+
}
|
|
719
|
+
let result;
|
|
720
|
+
switch (base) {
|
|
721
|
+
case Op.EQ:
|
|
722
|
+
result = value === toBigInt(operandData, 0);
|
|
723
|
+
break;
|
|
724
|
+
case Op.GT:
|
|
725
|
+
result = compareTyped(value, toBigInt(operandData, 0), typeCode) > 0;
|
|
726
|
+
break;
|
|
727
|
+
case Op.LT:
|
|
728
|
+
result = compareTyped(value, toBigInt(operandData, 0), typeCode) < 0;
|
|
729
|
+
break;
|
|
730
|
+
case Op.GTE:
|
|
731
|
+
result = compareTyped(value, toBigInt(operandData, 0), typeCode) >= 0;
|
|
732
|
+
break;
|
|
733
|
+
case Op.LTE:
|
|
734
|
+
result = compareTyped(value, toBigInt(operandData, 0), typeCode) <= 0;
|
|
735
|
+
break;
|
|
736
|
+
case Op.BETWEEN: {
|
|
737
|
+
const min = toBigInt(operandData, 0);
|
|
738
|
+
const max = toBigInt(operandData, 32);
|
|
739
|
+
result = compareTyped(value, min, typeCode) >= 0 && compareTyped(value, max, typeCode) <= 0;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
case Op.IN:
|
|
743
|
+
result = isIn(value, operandData);
|
|
744
|
+
break;
|
|
745
|
+
case Op.BITMASK_ALL: {
|
|
746
|
+
const mask = toBigInt(operandData, 0);
|
|
747
|
+
result = (value & mask) === mask;
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
case Op.BITMASK_ANY: {
|
|
751
|
+
const mask = toBigInt(operandData, 0);
|
|
752
|
+
result = (value & mask) !== 0n;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
case Op.BITMASK_NONE: {
|
|
756
|
+
const mask = toBigInt(operandData, 0);
|
|
757
|
+
result = (value & mask) === 0n;
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
default: throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}`);
|
|
761
|
+
}
|
|
762
|
+
return (opCode & Op.NOT) !== 0 ? !result : result;
|
|
763
|
+
}
|
|
764
|
+
function isValidOperatorData(opBase, dataLength) {
|
|
765
|
+
const operands = operandsOf(opBase);
|
|
766
|
+
if (operands === "single") return dataLength === 32;
|
|
767
|
+
if (operands === "range") return dataLength === 64;
|
|
768
|
+
if (operands === "variadic") return dataLength > 0 && dataLength % 32 === 0;
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
function unknownTypeCode(code) {
|
|
772
|
+
throw new CallciumError("UNKNOWN_TYPE_CODE", `Unknown type code 0x${code.toString(16).padStart(2, "0")}`);
|
|
773
|
+
}
|
|
774
|
+
const ELEMENTARY = {
|
|
775
|
+
typeClass: "elementary",
|
|
776
|
+
isDynamic: false
|
|
777
|
+
};
|
|
778
|
+
const ELEMENTARY_DYNAMIC = {
|
|
779
|
+
typeClass: "elementary",
|
|
780
|
+
isDynamic: true
|
|
781
|
+
};
|
|
782
|
+
const STATIC_ARR = {
|
|
783
|
+
typeClass: "staticArray",
|
|
784
|
+
isDynamic: false
|
|
785
|
+
};
|
|
786
|
+
const DYNAMIC_ARR = {
|
|
787
|
+
typeClass: "dynamicArray",
|
|
788
|
+
isDynamic: true
|
|
789
|
+
};
|
|
790
|
+
const TUPLE = {
|
|
791
|
+
typeClass: "tuple",
|
|
792
|
+
isDynamic: false
|
|
793
|
+
};
|
|
794
|
+
function classifyTypeCode(code) {
|
|
795
|
+
if (code >= TypeCode.UINT_MIN && code <= TypeCode.UINT_MAX) return ELEMENTARY;
|
|
796
|
+
if (code >= TypeCode.INT_MIN && code <= TypeCode.INT_MAX) return ELEMENTARY;
|
|
797
|
+
if (code === TypeCode.ADDRESS || code === TypeCode.BOOL || code === TypeCode.FUNCTION) return ELEMENTARY;
|
|
798
|
+
if (code >= 68 && code <= 79) unknownTypeCode(code);
|
|
799
|
+
if (code >= TypeCode.FIXED_BYTES_MIN && code <= TypeCode.FIXED_BYTES_MAX) return ELEMENTARY;
|
|
800
|
+
if (code === TypeCode.BYTES || code === TypeCode.STRING) return ELEMENTARY_DYNAMIC;
|
|
801
|
+
if (code >= 114 && code <= 127) unknownTypeCode(code);
|
|
802
|
+
if (code === TypeCode.STATIC_ARRAY) return STATIC_ARR;
|
|
803
|
+
if (code === TypeCode.DYNAMIC_ARRAY) return DYNAMIC_ARR;
|
|
804
|
+
if (code >= 130 && code <= 143) unknownTypeCode(code);
|
|
805
|
+
if (code === TypeCode.TUPLE) return TUPLE;
|
|
806
|
+
unknownTypeCode(code);
|
|
807
|
+
}
|
|
808
|
+
function isAddressableTarget(code) {
|
|
809
|
+
let info;
|
|
810
|
+
try {
|
|
811
|
+
info = classifyTypeCode(code);
|
|
812
|
+
} catch {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
return info.typeClass === "elementary" || info.typeClass === "dynamicArray";
|
|
816
|
+
}
|
|
817
|
+
function typeCodeLabel(code) {
|
|
818
|
+
if (code >= TypeCode.UINT_MIN && code <= TypeCode.UINT_MAX) return `uint${(code - TypeCode.UINT_MIN + 1) * 8}`;
|
|
819
|
+
if (code >= TypeCode.INT_MIN && code <= TypeCode.INT_MAX) return `int${(code - TypeCode.INT_MIN + 1) * 8}`;
|
|
820
|
+
if (code === TypeCode.ADDRESS) return "address";
|
|
821
|
+
if (code === TypeCode.BOOL) return "bool";
|
|
822
|
+
if (code === TypeCode.FUNCTION) return "function";
|
|
823
|
+
if (code >= TypeCode.FIXED_BYTES_MIN && code <= TypeCode.FIXED_BYTES_MAX) return `bytes${code - TypeCode.FIXED_BYTES_MIN + 1}`;
|
|
824
|
+
if (code === TypeCode.BYTES) return "bytes";
|
|
825
|
+
if (code === TypeCode.STRING) return "string";
|
|
826
|
+
if (code === TypeCode.STATIC_ARRAY) return "T[k]";
|
|
827
|
+
if (code === TypeCode.DYNAMIC_ARRAY) return "T[]";
|
|
828
|
+
if (code === TypeCode.TUPLE) return "tuple";
|
|
829
|
+
return `0x${code.toString(16).padStart(2, "0")}`;
|
|
830
|
+
}
|
|
831
|
+
function lookupTypeCode(code) {
|
|
832
|
+
return {
|
|
833
|
+
label: typeCodeLabel(code),
|
|
834
|
+
...classifyTypeCode(code)
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
//#endregion
|
|
707
838
|
//#region src/type-desc.ts
|
|
708
839
|
function extractStaticWords(desc) {
|
|
709
840
|
const typeCode = desc[0];
|
|
@@ -720,11 +851,11 @@ function function_() {
|
|
|
720
851
|
return new Uint8Array([TypeCode.FUNCTION]);
|
|
721
852
|
}
|
|
722
853
|
function uintN(bits) {
|
|
723
|
-
if (bits < 8 || bits > 256 || bits % 8 !== 0) throw new CallciumError("UNKNOWN_TYPE", `Invalid uintN bits: ${bits}. Must be 8–256 in steps of 8
|
|
854
|
+
if (bits < 8 || bits > 256 || bits % 8 !== 0) throw new CallciumError("UNKNOWN_TYPE", `Invalid uintN bits: ${bits}. Must be 8–256 in steps of 8`);
|
|
724
855
|
return new Uint8Array([TypeCode.UINT_MIN + (bits / 8 - 1)]);
|
|
725
856
|
}
|
|
726
857
|
function intN(bits) {
|
|
727
|
-
if (bits < 8 || bits > 256 || bits % 8 !== 0) throw new CallciumError("UNKNOWN_TYPE", `Invalid intN bits: ${bits}. Must be 8–256 in steps of 8
|
|
858
|
+
if (bits < 8 || bits > 256 || bits % 8 !== 0) throw new CallciumError("UNKNOWN_TYPE", `Invalid intN bits: ${bits}. Must be 8–256 in steps of 8`);
|
|
728
859
|
return new Uint8Array([TypeCode.INT_MIN + (bits / 8 - 1)]);
|
|
729
860
|
}
|
|
730
861
|
function bytes() {
|
|
@@ -734,13 +865,13 @@ function string_() {
|
|
|
734
865
|
return new Uint8Array([TypeCode.STRING]);
|
|
735
866
|
}
|
|
736
867
|
function bytesN(n) {
|
|
737
|
-
if (n < 1 || n > 32) throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${n}. Must be 1–32
|
|
868
|
+
if (n < 1 || n > 32) throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${n}. Must be 1–32`);
|
|
738
869
|
return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (n - 1)]);
|
|
739
870
|
}
|
|
740
871
|
function array(elemDesc, length) {
|
|
741
872
|
if (length === void 0) {
|
|
742
873
|
const nodeLength = DescriptorFormat.ARRAY_HEADER_SIZE + elemDesc.length;
|
|
743
|
-
if (nodeLength > DescriptorFormat.MAX_NODE_LENGTH) throw new CallciumError("NODE_LENGTH_TOO_LARGE", `Dynamic array node length ${nodeLength} exceeds maximum ${DescriptorFormat.MAX_NODE_LENGTH}
|
|
874
|
+
if (nodeLength > DescriptorFormat.MAX_NODE_LENGTH) throw new CallciumError("NODE_LENGTH_TOO_LARGE", `Dynamic array node length ${nodeLength} exceeds maximum ${DescriptorFormat.MAX_NODE_LENGTH}`);
|
|
744
875
|
const meta24 = 0 << DescriptorFormat.META_STATIC_WORDS_SHIFT | nodeLength;
|
|
745
876
|
const buf = new Uint8Array(1 + DescriptorFormat.COMPOSITE_META_SIZE + elemDesc.length);
|
|
746
877
|
buf[0] = TypeCode.DYNAMIC_ARRAY;
|
|
@@ -748,12 +879,12 @@ function array(elemDesc, length) {
|
|
|
748
879
|
buf.set(elemDesc, 4);
|
|
749
880
|
return buf;
|
|
750
881
|
}
|
|
751
|
-
if (length <= 0 || length > DescriptorFormat.MAX_STATIC_ARRAY_LENGTH) throw new CallciumError("INVALID_ARRAY_LENGTH", `Static array length ${length} out of range. Must be 1–${DescriptorFormat.MAX_STATIC_ARRAY_LENGTH}
|
|
882
|
+
if (length <= 0 || length > DescriptorFormat.MAX_STATIC_ARRAY_LENGTH) throw new CallciumError("INVALID_ARRAY_LENGTH", `Static array length ${length} out of range. Must be 1–${DescriptorFormat.MAX_STATIC_ARRAY_LENGTH}`);
|
|
752
883
|
const nodeLength = DescriptorFormat.ARRAY_HEADER_SIZE + elemDesc.length + DescriptorFormat.ARRAY_LENGTH_SIZE;
|
|
753
|
-
if (nodeLength > DescriptorFormat.MAX_NODE_LENGTH) throw new CallciumError("NODE_LENGTH_TOO_LARGE", `Static array node length ${nodeLength} exceeds maximum ${DescriptorFormat.MAX_NODE_LENGTH}
|
|
884
|
+
if (nodeLength > DescriptorFormat.MAX_NODE_LENGTH) throw new CallciumError("NODE_LENGTH_TOO_LARGE", `Static array node length ${nodeLength} exceeds maximum ${DescriptorFormat.MAX_NODE_LENGTH}`);
|
|
754
885
|
const elemStaticWords = extractStaticWords(elemDesc);
|
|
755
886
|
const staticWords = elemStaticWords === 0 ? 0 : length * elemStaticWords;
|
|
756
|
-
if (staticWords > DescriptorFormat.MAX_STATIC_WORDS) throw new CallciumError("STATIC_WORDS_TOO_LARGE", `Static array static words ${staticWords} exceeds maximum ${DescriptorFormat.MAX_STATIC_WORDS}
|
|
887
|
+
if (staticWords > DescriptorFormat.MAX_STATIC_WORDS) throw new CallciumError("STATIC_WORDS_TOO_LARGE", `Static array static words ${staticWords} exceeds maximum ${DescriptorFormat.MAX_STATIC_WORDS}`);
|
|
757
888
|
const meta24 = staticWords << DescriptorFormat.META_STATIC_WORDS_SHIFT | nodeLength;
|
|
758
889
|
const buf = new Uint8Array(1 + DescriptorFormat.COMPOSITE_META_SIZE + elemDesc.length + DescriptorFormat.ARRAY_LENGTH_SIZE);
|
|
759
890
|
buf[0] = TypeCode.STATIC_ARRAY;
|
|
@@ -763,10 +894,10 @@ function array(elemDesc, length) {
|
|
|
763
894
|
return buf;
|
|
764
895
|
}
|
|
765
896
|
function tuple(fieldDescs) {
|
|
766
|
-
if (fieldDescs.length === 0 || fieldDescs.length > DescriptorFormat.MAX_TUPLE_FIELDS) throw new CallciumError("INVALID_TUPLE_FIELD_COUNT", `Tuple field count ${fieldDescs.length} out of range. Must be 1–${DescriptorFormat.MAX_TUPLE_FIELDS}
|
|
897
|
+
if (fieldDescs.length === 0 || fieldDescs.length > DescriptorFormat.MAX_TUPLE_FIELDS) throw new CallciumError("INVALID_TUPLE_FIELD_COUNT", `Tuple field count ${fieldDescs.length} out of range. Must be 1–${DescriptorFormat.MAX_TUPLE_FIELDS}`);
|
|
767
898
|
const totalFieldBytes = fieldDescs.reduce((sum, f) => sum + f.length, 0);
|
|
768
899
|
const nodeLength = DescriptorFormat.TUPLE_HEADER_SIZE + totalFieldBytes;
|
|
769
|
-
if (nodeLength > DescriptorFormat.MAX_NODE_LENGTH) throw new CallciumError("NODE_LENGTH_TOO_LARGE", `Tuple node length ${nodeLength} exceeds maximum ${DescriptorFormat.MAX_NODE_LENGTH}
|
|
900
|
+
if (nodeLength > DescriptorFormat.MAX_NODE_LENGTH) throw new CallciumError("NODE_LENGTH_TOO_LARGE", `Tuple node length ${nodeLength} exceeds maximum ${DescriptorFormat.MAX_NODE_LENGTH}`);
|
|
770
901
|
let anyDynamic = false;
|
|
771
902
|
let staticWordsSum = 0;
|
|
772
903
|
for (const field of fieldDescs) {
|
|
@@ -775,7 +906,7 @@ function tuple(fieldDescs) {
|
|
|
775
906
|
staticWordsSum += staticWords;
|
|
776
907
|
}
|
|
777
908
|
const staticWords = anyDynamic ? 0 : staticWordsSum;
|
|
778
|
-
if (staticWords > DescriptorFormat.MAX_STATIC_WORDS) throw new CallciumError("STATIC_WORDS_TOO_LARGE", `Tuple static words ${staticWords} exceeds maximum ${DescriptorFormat.MAX_STATIC_WORDS}
|
|
909
|
+
if (staticWords > DescriptorFormat.MAX_STATIC_WORDS) throw new CallciumError("STATIC_WORDS_TOO_LARGE", `Tuple static words ${staticWords} exceeds maximum ${DescriptorFormat.MAX_STATIC_WORDS}`);
|
|
779
910
|
const meta24 = staticWords << DescriptorFormat.META_STATIC_WORDS_SHIFT | nodeLength;
|
|
780
911
|
const buf = new Uint8Array(1 + DescriptorFormat.COMPOSITE_META_SIZE + DescriptorFormat.TUPLE_FIELDCOUNT_SIZE + totalFieldBytes);
|
|
781
912
|
buf[0] = TypeCode.TUPLE;
|
|
@@ -817,19 +948,19 @@ function parseUint(input, start, end) {
|
|
|
817
948
|
function parseTuple(input, start, end) {
|
|
818
949
|
if (input[start] !== "(") throw new CallciumError("MALFORMED_TYPE_STRING", `Expected '(' at position ${start}`);
|
|
819
950
|
let depth = 0;
|
|
820
|
-
let
|
|
951
|
+
let closeIndex = -1;
|
|
821
952
|
for (let i = start; i < end; i++) if (input[i] === "(") depth++;
|
|
822
953
|
else if (input[i] === ")") {
|
|
823
954
|
depth--;
|
|
824
955
|
if (depth === 0) {
|
|
825
|
-
|
|
956
|
+
closeIndex = i;
|
|
826
957
|
break;
|
|
827
958
|
}
|
|
828
959
|
}
|
|
829
|
-
if (
|
|
830
|
-
if (
|
|
960
|
+
if (closeIndex === -1) throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched '(' at position ${start}`);
|
|
961
|
+
if (closeIndex !== end - 1) throw new CallciumError("MALFORMED_TYPE_STRING", `Unexpected characters after ')' at position ${closeIndex + 1}`);
|
|
831
962
|
const innerStart = start + 1;
|
|
832
|
-
const innerEnd =
|
|
963
|
+
const innerEnd = closeIndex;
|
|
833
964
|
const commas = commaPositions(input, innerStart, innerEnd);
|
|
834
965
|
const segments = [];
|
|
835
966
|
let segStart = innerStart;
|
|
@@ -859,14 +990,14 @@ function parseType(input, start, end) {
|
|
|
859
990
|
const suffixes = [];
|
|
860
991
|
let baseEnd = end;
|
|
861
992
|
while (baseEnd > start && input[baseEnd - 1] === "]") {
|
|
862
|
-
const
|
|
863
|
-
let
|
|
864
|
-
while (
|
|
865
|
-
if (input[
|
|
866
|
-
const innerStart =
|
|
867
|
-
const innerEnd =
|
|
993
|
+
const closeIndex = baseEnd - 1;
|
|
994
|
+
let openIndex = closeIndex - 1;
|
|
995
|
+
while (openIndex > start && input[openIndex] !== "[") openIndex--;
|
|
996
|
+
if (input[openIndex] !== "[") throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched ']' at position ${closeIndex}`);
|
|
997
|
+
const innerStart = openIndex + 1;
|
|
998
|
+
const innerEnd = closeIndex;
|
|
868
999
|
suffixes.unshift(innerStart === innerEnd ? void 0 : parseUint(input, innerStart, innerEnd));
|
|
869
|
-
baseEnd =
|
|
1000
|
+
baseEnd = openIndex;
|
|
870
1001
|
}
|
|
871
1002
|
if (input[start] !== "(" && input.slice(start, baseEnd).includes("[")) throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched '[' in '${input.slice(start, baseEnd)}'`);
|
|
872
1003
|
let desc = parseBaseType(input, start, baseEnd);
|
|
@@ -884,7 +1015,7 @@ function fromTypes(typesCsv) {
|
|
|
884
1015
|
}
|
|
885
1016
|
segments.push([segStart, typesCsv.length]);
|
|
886
1017
|
const paramDescs = segments.map(([start, end]) => parseType(typesCsv, start, end));
|
|
887
|
-
if (paramDescs.length > DescriptorFormat.MAX_PARAMS) throw new CallciumError("TOO_MANY_PARAMS", `Parameter count ${paramDescs.length} exceeds maximum ${DescriptorFormat.MAX_PARAMS}
|
|
1018
|
+
if (paramDescs.length > DescriptorFormat.MAX_PARAMS) throw new CallciumError("TOO_MANY_PARAMS", `Parameter count ${paramDescs.length} exceeds maximum ${DescriptorFormat.MAX_PARAMS}`);
|
|
888
1019
|
const totalBytes = paramDescs.reduce((sum, d) => sum + d.length, 0);
|
|
889
1020
|
const result = new Uint8Array(DescriptorFormat.HEADER_SIZE + totalBytes);
|
|
890
1021
|
result[0] = DescriptorFormat.VERSION;
|
|
@@ -1022,134 +1153,7 @@ const DescriptorCoder = {
|
|
|
1022
1153
|
toTypes
|
|
1023
1154
|
};
|
|
1024
1155
|
//#endregion
|
|
1025
|
-
//#region src/operators.ts
|
|
1026
|
-
function toBigInt(bytes, offset = 0) {
|
|
1027
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, 32);
|
|
1028
|
-
return view.getBigUint64(0, false) << 192n | view.getBigUint64(8, false) << 128n | view.getBigUint64(16, false) << 64n | view.getBigUint64(24, false);
|
|
1029
|
-
}
|
|
1030
|
-
function isSigned(typeCode) {
|
|
1031
|
-
return typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX;
|
|
1032
|
-
}
|
|
1033
|
-
function isLeftAligned(typeCode) {
|
|
1034
|
-
return typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX || typeCode === TypeCode.FUNCTION;
|
|
1035
|
-
}
|
|
1036
|
-
function canonicalize(value, typeCode) {
|
|
1037
|
-
if (typeCode >= TypeCode.UINT_MIN && typeCode <= TypeCode.UINT_MAX) {
|
|
1038
|
-
const bits = BigInt(typeCode * 8);
|
|
1039
|
-
return bits === 256n ? value : value & (1n << bits) - 1n;
|
|
1040
|
-
}
|
|
1041
|
-
if (typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX) {
|
|
1042
|
-
const bits = (typeCode - TypeCode.INT_MIN + 1) * 8;
|
|
1043
|
-
return bits === 256 ? value : BigInt.asUintN(256, BigInt.asIntN(bits, value));
|
|
1044
|
-
}
|
|
1045
|
-
if (typeCode === TypeCode.ADDRESS) return value & (1n << 160n) - 1n;
|
|
1046
|
-
if (typeCode === TypeCode.BOOL) return value & 1n;
|
|
1047
|
-
if (typeCode === TypeCode.FUNCTION) return value >> 64n << 64n;
|
|
1048
|
-
if (typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX) {
|
|
1049
|
-
const n = typeCode - TypeCode.FIXED_BYTES_MIN + 1;
|
|
1050
|
-
if (n === 32) return value;
|
|
1051
|
-
const padBits = BigInt((32 - n) * 8);
|
|
1052
|
-
return value >> padBits << padBits;
|
|
1053
|
-
}
|
|
1054
|
-
return value;
|
|
1055
|
-
}
|
|
1056
|
-
function isLengthOp(opCode) {
|
|
1057
|
-
const base = opCode & ~Op.NOT;
|
|
1058
|
-
return base >= Op.LENGTH_EQ && base <= Op.LENGTH_BETWEEN;
|
|
1059
|
-
}
|
|
1060
|
-
function lengthToValueOp(base) {
|
|
1061
|
-
if (base === Op.LENGTH_EQ) return Op.EQ;
|
|
1062
|
-
if (base === Op.LENGTH_GT) return Op.GT;
|
|
1063
|
-
if (base === Op.LENGTH_LT) return Op.LT;
|
|
1064
|
-
if (base === Op.LENGTH_GTE) return Op.GTE;
|
|
1065
|
-
if (base === Op.LENGTH_LTE) return Op.LTE;
|
|
1066
|
-
return Op.BETWEEN;
|
|
1067
|
-
}
|
|
1068
|
-
function isLengthValidType(typeCode) {
|
|
1069
|
-
return typeCode === TypeCode.BYTES || typeCode === TypeCode.STRING || typeCode === TypeCode.DYNAMIC_ARRAY;
|
|
1070
|
-
}
|
|
1071
|
-
function compareTyped(a, b, typeCode) {
|
|
1072
|
-
if (isSigned(typeCode)) {
|
|
1073
|
-
const sa = BigInt.asIntN(256, a);
|
|
1074
|
-
const sb = BigInt.asIntN(256, b);
|
|
1075
|
-
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
1076
|
-
}
|
|
1077
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
1078
|
-
}
|
|
1079
|
-
function isIn(value, data) {
|
|
1080
|
-
const count = data.length / 32;
|
|
1081
|
-
let lo = 0;
|
|
1082
|
-
let hi = count - 1;
|
|
1083
|
-
while (lo <= hi) {
|
|
1084
|
-
const mid = lo + hi >>> 1;
|
|
1085
|
-
const elem = toBigInt(data, mid * 32);
|
|
1086
|
-
if (value === elem) return true;
|
|
1087
|
-
if (value < elem) hi = mid - 1;
|
|
1088
|
-
else lo = mid + 1;
|
|
1089
|
-
}
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
function applyOperator(opCode, value, valueLength, operandData, typeCode) {
|
|
1093
|
-
let base = opCode & ~Op.NOT;
|
|
1094
|
-
if (isLengthOp(base)) {
|
|
1095
|
-
value = BigInt(valueLength);
|
|
1096
|
-
typeCode = TypeCode.UINT_MAX;
|
|
1097
|
-
base = lengthToValueOp(base);
|
|
1098
|
-
}
|
|
1099
|
-
let result;
|
|
1100
|
-
switch (base) {
|
|
1101
|
-
case Op.EQ:
|
|
1102
|
-
result = value === toBigInt(operandData, 0);
|
|
1103
|
-
break;
|
|
1104
|
-
case Op.GT:
|
|
1105
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) > 0;
|
|
1106
|
-
break;
|
|
1107
|
-
case Op.LT:
|
|
1108
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) < 0;
|
|
1109
|
-
break;
|
|
1110
|
-
case Op.GTE:
|
|
1111
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) >= 0;
|
|
1112
|
-
break;
|
|
1113
|
-
case Op.LTE:
|
|
1114
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) <= 0;
|
|
1115
|
-
break;
|
|
1116
|
-
case Op.BETWEEN: {
|
|
1117
|
-
const min = toBigInt(operandData, 0);
|
|
1118
|
-
const max = toBigInt(operandData, 32);
|
|
1119
|
-
result = compareTyped(value, min, typeCode) >= 0 && compareTyped(value, max, typeCode) <= 0;
|
|
1120
|
-
break;
|
|
1121
|
-
}
|
|
1122
|
-
case Op.IN:
|
|
1123
|
-
result = isIn(value, operandData);
|
|
1124
|
-
break;
|
|
1125
|
-
case Op.BITMASK_ALL: {
|
|
1126
|
-
const mask = toBigInt(operandData, 0);
|
|
1127
|
-
result = (value & mask) === mask;
|
|
1128
|
-
break;
|
|
1129
|
-
}
|
|
1130
|
-
case Op.BITMASK_ANY: {
|
|
1131
|
-
const mask = toBigInt(operandData, 0);
|
|
1132
|
-
result = (value & mask) !== 0n;
|
|
1133
|
-
break;
|
|
1134
|
-
}
|
|
1135
|
-
case Op.BITMASK_NONE: {
|
|
1136
|
-
const mask = toBigInt(operandData, 0);
|
|
1137
|
-
result = (value & mask) === 0n;
|
|
1138
|
-
break;
|
|
1139
|
-
}
|
|
1140
|
-
default: throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}.`);
|
|
1141
|
-
}
|
|
1142
|
-
return (opCode & Op.NOT) !== 0 ? !result : result;
|
|
1143
|
-
}
|
|
1144
|
-
//#endregion
|
|
1145
1156
|
//#region src/policy-coder.ts
|
|
1146
|
-
function parsePathSteps(path) {
|
|
1147
|
-
const bytes = hexToBytes(path);
|
|
1148
|
-
if (bytes.length % PolicyFormat.PATH_STEP_SIZE !== 0) throw new CallciumError("MALFORMED_PATH", `Path byte length ${bytes.length} is not a whole number of steps`);
|
|
1149
|
-
const steps = [];
|
|
1150
|
-
for (let offset = 0; offset < bytes.length; offset += PolicyFormat.PATH_STEP_SIZE) steps.push(readU16(bytes, offset));
|
|
1151
|
-
return steps;
|
|
1152
|
-
}
|
|
1153
1157
|
function field(value, start, end) {
|
|
1154
1158
|
return {
|
|
1155
1159
|
value,
|
|
@@ -1299,7 +1303,7 @@ function decodePolicy(blob) {
|
|
|
1299
1303
|
if (toBigInt(data, dataStart) > BigInt(MAX_CONTEXT_PROPERTY_ID)) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", "EQ_CTX operand references an undefined context property", ruleOffset);
|
|
1300
1304
|
}
|
|
1301
1305
|
if (depthValue === 0) throw new CallciumError("EMPTY_PATH", "Rule path must have at least one step", ruleOffset);
|
|
1302
|
-
if (depthValue >
|
|
1306
|
+
if (depthValue > PolicyFormat.MAX_PATH_DEPTH) throw new CallciumError("PATH_TOO_DEEP", `Path depth ${depthValue} exceeds maximum ${PolicyFormat.MAX_PATH_DEPTH}`, ruleOffset);
|
|
1303
1307
|
if (scopeValue === Scope.CONTEXT) {
|
|
1304
1308
|
if (depthValue !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope rules must have exactly one path step", ruleOffset);
|
|
1305
1309
|
const contextPropertyId = readU16(data, pathStart);
|
|
@@ -1366,12 +1370,12 @@ function decodePolicy(blob) {
|
|
|
1366
1370
|
}
|
|
1367
1371
|
function flattenConstraint(constraint, desc) {
|
|
1368
1372
|
const operators = constraint.operators.map(hexToBytes);
|
|
1369
|
-
for (const operator of operators) if (operator.length < 1) throw new CallciumError("INVALID_OPERATOR_BYTES", "Operator must have at least one byte (opcode)
|
|
1373
|
+
for (const operator of operators) if (operator.length < 1) throw new CallciumError("INVALID_OPERATOR_BYTES", "Operator must have at least one byte (opcode)");
|
|
1370
1374
|
const path = hexToBytes(constraint.path);
|
|
1371
|
-
if (path.length === 0) throw new CallciumError("EMPTY_PATH", "Rule path must have at least one step
|
|
1372
|
-
if ((path.length & 1) !== 0) throw new CallciumError("MALFORMED_PATH", "Path byte length must be even
|
|
1375
|
+
if (path.length === 0) throw new CallciumError("EMPTY_PATH", "Rule path must have at least one step");
|
|
1376
|
+
if ((path.length & 1) !== 0) throw new CallciumError("MALFORMED_PATH", "Path byte length must be even");
|
|
1373
1377
|
const depth = path.length / 2;
|
|
1374
|
-
if (depth >
|
|
1378
|
+
if (depth > PolicyFormat.MAX_PATH_DEPTH) throw new CallciumError("PATH_TOO_DEEP", `Path depth ${depth} exceeds maximum ${PolicyFormat.MAX_PATH_DEPTH}`);
|
|
1375
1379
|
const hint = constraint.scope === Scope.CALLDATA ? Descriptor.compileHint(desc, parsePathSteps(constraint.path)) : new Uint8Array(0);
|
|
1376
1380
|
return operators.map((operator) => ({
|
|
1377
1381
|
scope: constraint.scope,
|
|
@@ -1398,7 +1402,7 @@ function sortRules(rules) {
|
|
|
1398
1402
|
}
|
|
1399
1403
|
function encodeRule(rule) {
|
|
1400
1404
|
const depth = rule.path.length / 2;
|
|
1401
|
-
if (rule.scope === Scope.CONTEXT && depth !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope rules must have exactly one path step
|
|
1405
|
+
if (rule.scope === Scope.CONTEXT && depth !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope rules must have exactly one path step");
|
|
1402
1406
|
const data = rule.operator.subarray(1);
|
|
1403
1407
|
const ruleSize = PolicyFormat.RULE_FIXED_OVERHEAD + rule.path.length + rule.hint.length + data.length;
|
|
1404
1408
|
if (ruleSize > 65535) throw new CallciumError("RULE_SIZE_OVERFLOW", `Rule size ${ruleSize} exceeds maximum 65535`);
|
|
@@ -1743,7 +1747,7 @@ function unsortedInSet(groupIndex, constraintIndex) {
|
|
|
1743
1747
|
code: "UNSORTED_IN_SET",
|
|
1744
1748
|
value1: ZERO,
|
|
1745
1749
|
value2: ZERO,
|
|
1746
|
-
message: "isIn/notIn set is not strictly sorted and deduplicated
|
|
1750
|
+
message: "isIn/notIn set is not strictly sorted and deduplicated"
|
|
1747
1751
|
};
|
|
1748
1752
|
}
|
|
1749
1753
|
function emptyGroup(groupIndex) {
|
|
@@ -1755,7 +1759,7 @@ function emptyGroup(groupIndex) {
|
|
|
1755
1759
|
code: "EMPTY_GROUP",
|
|
1756
1760
|
value1: ZERO,
|
|
1757
1761
|
value2: ZERO,
|
|
1758
|
-
message: "Group contains zero constraints
|
|
1762
|
+
message: "Group contains zero constraints"
|
|
1759
1763
|
};
|
|
1760
1764
|
}
|
|
1761
1765
|
function dominatedBound(isLength, groupIndex, constraintIndex, value) {
|
|
@@ -1940,7 +1944,7 @@ function negationUnderAny(groupIndex, constraintIndex, opCode) {
|
|
|
1940
1944
|
}
|
|
1941
1945
|
//#endregion
|
|
1942
1946
|
//#region src/policy-validator.ts
|
|
1943
|
-
const
|
|
1947
|
+
const navigabilityCodes = new Set([
|
|
1944
1948
|
"EMPTY_PATH",
|
|
1945
1949
|
"PARAM_INDEX_OUT_OF_BOUNDS",
|
|
1946
1950
|
"TUPLE_FIELD_OUT_OF_BOUNDS",
|
|
@@ -2418,12 +2422,12 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2418
2422
|
const steps = parsePathSteps(constraint.path);
|
|
2419
2423
|
let typeInfo;
|
|
2420
2424
|
if (constraint.scope === Scope.CALLDATA) {
|
|
2421
|
-
if (steps.length >
|
|
2425
|
+
if (steps.length > PolicyFormat.MAX_PATH_DEPTH) issues.push(pathDepthExceeded(groupIndex, constraintIndex, bigintToHex(BigInt(steps.length)), bigintToHex(BigInt(PolicyFormat.MAX_PATH_DEPTH))));
|
|
2422
2426
|
let walk;
|
|
2423
2427
|
try {
|
|
2424
2428
|
walk = Descriptor.walkPath(descBytes, steps);
|
|
2425
2429
|
} catch (error) {
|
|
2426
|
-
if (error instanceof CallciumError &&
|
|
2430
|
+
if (error instanceof CallciumError && navigabilityCodes.has(error.code)) {
|
|
2427
2431
|
issues.push(unnavigablePath(groupIndex, constraintIndex));
|
|
2428
2432
|
continue;
|
|
2429
2433
|
}
|
|
@@ -2433,7 +2437,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2433
2437
|
issues.push(nestedQuantifier(groupIndex, constraintIndex));
|
|
2434
2438
|
continue;
|
|
2435
2439
|
}
|
|
2436
|
-
if (walk.quantifiedStaticLength >
|
|
2440
|
+
if (walk.quantifiedStaticLength > PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH) issues.push(quantifierOverStaticLimit(groupIndex, constraintIndex, bigintToHex(BigInt(walk.quantifiedStaticLength)), bigintToHex(BigInt(PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH))));
|
|
2437
2441
|
typeInfo = walk.typeInfo;
|
|
2438
2442
|
} else {
|
|
2439
2443
|
const ctxId = steps[0];
|
|
@@ -2453,7 +2457,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2453
2457
|
ctx = initContext(constraint.scope, normalizedPath, steps, typeInfo);
|
|
2454
2458
|
contexts.push(ctx);
|
|
2455
2459
|
}
|
|
2456
|
-
if (constraint.scope === Scope.CALLDATA && constraint.hint !== void 0 && ctx.steps.length <=
|
|
2460
|
+
if (constraint.scope === Scope.CALLDATA && constraint.hint !== void 0 && ctx.steps.length <= PolicyFormat.MAX_PATH_DEPTH) {
|
|
2457
2461
|
if (bytesToHex(Descriptor.compileHint(descBytes, ctx.steps)) !== constraint.hint.toLowerCase()) issues.push(hintMismatch(groupIndex, constraintIndex));
|
|
2458
2462
|
}
|
|
2459
2463
|
validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues);
|
|
@@ -2510,14 +2514,14 @@ const SignatureParser = { parse };
|
|
|
2510
2514
|
//#endregion
|
|
2511
2515
|
//#region src/policy-builder.ts
|
|
2512
2516
|
function validateContextPath(steps) {
|
|
2513
|
-
if (steps.length !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope path must be exactly one step
|
|
2517
|
+
if (steps.length !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope path must be exactly one step");
|
|
2514
2518
|
const step = steps[0];
|
|
2515
|
-
if (step > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID 0x${step.toString(16).padStart(4, "0")}
|
|
2519
|
+
if (step > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID 0x${step.toString(16).padStart(4, "0")}`);
|
|
2516
2520
|
}
|
|
2517
2521
|
function validateCalldataPath(steps, desc) {
|
|
2518
2522
|
const argIndex = steps[0];
|
|
2519
2523
|
const paramCount = Descriptor.paramCount(desc);
|
|
2520
|
-
if (argIndex >= paramCount) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${argIndex} out of range (descriptor has ${paramCount} params)
|
|
2524
|
+
if (argIndex >= paramCount) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${argIndex} out of range (descriptor has ${paramCount} params)`);
|
|
2521
2525
|
let offset = Descriptor.paramOffset(desc, argIndex);
|
|
2522
2526
|
let hasQuantifier = false;
|
|
2523
2527
|
for (let i = 1; i < steps.length; i++) {
|
|
@@ -2525,21 +2529,21 @@ function validateCalldataPath(steps, desc) {
|
|
|
2525
2529
|
const info = Descriptor.inspect(desc, offset);
|
|
2526
2530
|
const isArray = info.typeCode === TypeCode.STATIC_ARRAY || info.typeCode === TypeCode.DYNAMIC_ARRAY;
|
|
2527
2531
|
if (isQuantifier(step)) {
|
|
2528
|
-
if (!isArray) throw new CallciumError("QUANTIFIER_ON_NON_ARRAY", "Quantifier step is only valid on an array node
|
|
2529
|
-
if (hasQuantifier) throw new CallciumError("NESTED_QUANTIFIER", "Nested quantifiers are not allowed
|
|
2532
|
+
if (!isArray) throw new CallciumError("QUANTIFIER_ON_NON_ARRAY", "Quantifier step is only valid on an array node");
|
|
2533
|
+
if (hasQuantifier) throw new CallciumError("NESTED_QUANTIFIER", "Nested quantifiers are not allowed");
|
|
2530
2534
|
hasQuantifier = true;
|
|
2531
2535
|
}
|
|
2532
2536
|
if (info.typeCode === TypeCode.TUPLE) {
|
|
2533
2537
|
const fieldCount = Descriptor.tupleFieldCount(desc, offset);
|
|
2534
|
-
if (step >= fieldCount) throw new CallciumError("TUPLE_FIELD_OUT_OF_BOUNDS", `Tuple field index ${step} out of range (tuple has ${fieldCount} fields)
|
|
2538
|
+
if (step >= fieldCount) throw new CallciumError("TUPLE_FIELD_OUT_OF_BOUNDS", `Tuple field index ${step} out of range (tuple has ${fieldCount} fields)`);
|
|
2535
2539
|
offset = Descriptor.tupleFieldOffset(desc, offset, step);
|
|
2536
2540
|
} else if (isArray) {
|
|
2537
2541
|
if (!isQuantifier(step) && info.typeCode === TypeCode.STATIC_ARRAY) {
|
|
2538
2542
|
const arrayLength = Descriptor.staticArrayLength(desc, offset);
|
|
2539
|
-
if (step >= arrayLength) throw new CallciumError("STATIC_ARRAY_INDEX_OUT_OF_BOUNDS", `Array index ${step} out of range (static array has ${arrayLength} elements)
|
|
2543
|
+
if (step >= arrayLength) throw new CallciumError("STATIC_ARRAY_INDEX_OUT_OF_BOUNDS", `Array index ${step} out of range (static array has ${arrayLength} elements)`);
|
|
2540
2544
|
}
|
|
2541
2545
|
offset = Descriptor.arrayElementOffset(offset);
|
|
2542
|
-
} else throw new CallciumError("NOT_COMPOSITE", "Cannot descend into an elementary type
|
|
2546
|
+
} else throw new CallciumError("NOT_COMPOSITE", "Cannot descend into an elementary type");
|
|
2543
2547
|
}
|
|
2544
2548
|
}
|
|
2545
2549
|
var PolicyBuilder = class PolicyBuilder {
|
|
@@ -2573,21 +2577,21 @@ var PolicyBuilder = class PolicyBuilder {
|
|
|
2573
2577
|
path: constraint.path,
|
|
2574
2578
|
operators: [...constraint.operators]
|
|
2575
2579
|
};
|
|
2576
|
-
if (c.operators.length === 0) throw new CallciumError("NO_CONSTRAINT_OPERATORS", "Constraint must have at least one operator
|
|
2580
|
+
if (c.operators.length === 0) throw new CallciumError("NO_CONSTRAINT_OPERATORS", "Constraint must have at least one operator");
|
|
2577
2581
|
const steps = parsePathSteps(c.path);
|
|
2578
|
-
if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step
|
|
2582
|
+
if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
|
|
2579
2583
|
if (c.scope === Scope.CONTEXT) validateContextPath(steps);
|
|
2580
2584
|
else if (c.scope === Scope.CALLDATA) validateCalldataPath(steps, this.draft.descriptor);
|
|
2581
|
-
else throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${c.scope}
|
|
2585
|
+
else throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${c.scope}`);
|
|
2582
2586
|
const key = `${c.scope}:${c.path.toLowerCase()}`;
|
|
2583
2587
|
const currentHashes = this.draft.pathHashes[this.draft.pathHashes.length - 1];
|
|
2584
|
-
if (currentHashes.has(key)) throw new CallciumError("DUPLICATE_PATH_IN_GROUP", `Duplicate path ${c.path} in the same group
|
|
2588
|
+
if (currentHashes.has(key)) throw new CallciumError("DUPLICATE_PATH_IN_GROUP", `Duplicate path ${c.path} in the same group`);
|
|
2585
2589
|
currentHashes.add(key);
|
|
2586
2590
|
this.draft.groups[this.draft.groups.length - 1].push(c);
|
|
2587
2591
|
return this;
|
|
2588
2592
|
}
|
|
2589
2593
|
or() {
|
|
2590
|
-
if (this.draft.groups[this.draft.groups.length - 1].length === 0) throw new CallciumError("EMPTY_GROUP", "Cannot start a new group when the current group is empty
|
|
2594
|
+
if (this.draft.groups[this.draft.groups.length - 1].length === 0) throw new CallciumError("EMPTY_GROUP", "Cannot start a new group when the current group is empty");
|
|
2591
2595
|
this.draft.groups.push([]);
|
|
2592
2596
|
this.draft.pathHashes.push(/* @__PURE__ */ new Set());
|
|
2593
2597
|
return this;
|
|
@@ -2609,7 +2613,7 @@ var PolicyBuilder = class PolicyBuilder {
|
|
|
2609
2613
|
return PolicyValidator.validate(policyData);
|
|
2610
2614
|
}
|
|
2611
2615
|
checkGroups() {
|
|
2612
|
-
for (let i = 0; i < this.draft.groups.length; i++) if (this.draft.groups[i].length === 0) throw new CallciumError("EMPTY_GROUP", `Group ${i} is empty
|
|
2616
|
+
for (let i = 0; i < this.draft.groups.length; i++) if (this.draft.groups[i].length === 0) throw new CallciumError("EMPTY_GROUP", `Group ${i} is empty`);
|
|
2613
2617
|
}
|
|
2614
2618
|
toPolicyData() {
|
|
2615
2619
|
return {
|
|
@@ -2632,8 +2636,7 @@ function encodeWord(value) {
|
|
|
2632
2636
|
return word;
|
|
2633
2637
|
}
|
|
2634
2638
|
if (typeof value === "string") {
|
|
2635
|
-
|
|
2636
|
-
for (let i = 0; i < 20; i++) word[12 + i] = parseInt(body.substring(i * 2, i * 2 + 2), 16);
|
|
2639
|
+
word.set(hexToBytes(addressBody(value)), 12);
|
|
2637
2640
|
return word;
|
|
2638
2641
|
}
|
|
2639
2642
|
let bigValue = typeof value === "number" ? BigInt(value) : value;
|
|
@@ -2661,7 +2664,6 @@ function checkContextPropertyId(contextPropertyId) {
|
|
|
2661
2664
|
if (!Number.isInteger(contextPropertyId) || contextPropertyId < 0 || contextPropertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID ${contextPropertyId}`);
|
|
2662
2665
|
return contextPropertyId;
|
|
2663
2666
|
}
|
|
2664
|
-
const MAX_SET_MEMBERS = Limits.MAX_SET_MEMBERS;
|
|
2665
2667
|
function setOp(opCode, values) {
|
|
2666
2668
|
const bigs = values.map((v) => {
|
|
2667
2669
|
if (typeof v === "bigint") return v;
|
|
@@ -2669,21 +2671,14 @@ function setOp(opCode, values) {
|
|
|
2669
2671
|
if (typeof v === "boolean") return v ? 1n : 0n;
|
|
2670
2672
|
return BigInt("0x" + addressBody(v));
|
|
2671
2673
|
});
|
|
2672
|
-
bigs.
|
|
2673
|
-
const deduped = [];
|
|
2674
|
-
for (const value of bigs) if (deduped.length === 0 || deduped[deduped.length - 1] !== value) deduped.push(value);
|
|
2674
|
+
const deduped = [...new Set(bigs)].toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2675
2675
|
if (deduped.length === 0) throw new CallciumError("EMPTY_SET", "Set must contain at least one value");
|
|
2676
|
-
if (deduped.length > MAX_SET_MEMBERS) throw new CallciumError("SET_TOO_LARGE", `Set must contain at most ${MAX_SET_MEMBERS} values`);
|
|
2676
|
+
if (deduped.length > PolicyFormat.MAX_SET_MEMBERS) throw new CallciumError("SET_TOO_LARGE", `Set must contain at most ${PolicyFormat.MAX_SET_MEMBERS} values`);
|
|
2677
2677
|
const buffer = new Uint8Array(1 + deduped.length * 32);
|
|
2678
2678
|
buffer[0] = opCode;
|
|
2679
2679
|
for (let i = 0; i < deduped.length; i++) buffer.set(encodeWord(deduped[i]), 1 + i * 32);
|
|
2680
2680
|
return bytesToHex(buffer);
|
|
2681
2681
|
}
|
|
2682
|
-
function encodePath(steps) {
|
|
2683
|
-
const buffer = new Uint8Array(steps.length * 2);
|
|
2684
|
-
for (let i = 0; i < steps.length; i++) writeBE16(buffer, i * 2, steps[i]);
|
|
2685
|
-
return bytesToHex(buffer);
|
|
2686
|
-
}
|
|
2687
2682
|
var ConstraintBuilder = class {
|
|
2688
2683
|
scope;
|
|
2689
2684
|
path;
|
|
@@ -2801,17 +2796,14 @@ function readPointer(callData, head) {
|
|
|
2801
2796
|
}
|
|
2802
2797
|
//#endregion
|
|
2803
2798
|
//#region src/policy-enforcer.ts
|
|
2804
|
-
const
|
|
2799
|
+
const abortViolationCodes = new Set([
|
|
2805
2800
|
"CALLDATA_OUT_OF_BOUNDS",
|
|
2806
2801
|
"ARRAY_INDEX_OUT_OF_BOUNDS",
|
|
2807
2802
|
"NON_CANONICAL_VALUE",
|
|
2808
2803
|
"QUANTIFIER_LIMIT_EXCEEDED"
|
|
2809
2804
|
]);
|
|
2810
2805
|
function addressToBigInt(hex) {
|
|
2811
|
-
|
|
2812
|
-
const padded = new Uint8Array(32);
|
|
2813
|
-
padded.set(bytes, 32 - bytes.length);
|
|
2814
|
-
return toBigInt(padded, 0);
|
|
2806
|
+
return BigInt(toAddress(hex));
|
|
2815
2807
|
}
|
|
2816
2808
|
function check(policy, callData, context) {
|
|
2817
2809
|
const { policy: decoded, data: policyBytes } = decodePolicy(policy);
|
|
@@ -2842,7 +2834,7 @@ function check(policy, callData, context) {
|
|
|
2842
2834
|
const violation = evaluateRule(rule, policyBytes, callDataBytes, baseOffset, groupIndex, ruleIndex, context);
|
|
2843
2835
|
if (violation !== null) {
|
|
2844
2836
|
allViolations.push(violation);
|
|
2845
|
-
if (
|
|
2837
|
+
if (abortViolationCodes.has(violation.code)) return {
|
|
2846
2838
|
ok: false,
|
|
2847
2839
|
violations: allViolations
|
|
2848
2840
|
};
|
|
@@ -2875,9 +2867,9 @@ function evaluateRule(rule, policyBytes, callDataBytes, baseOffset, groupIndex,
|
|
|
2875
2867
|
return evaluateCalldataRule(fieldBytes(policyBytes, rule.hint), callDataBytes, baseOffset, opCode, operandData, groupIndex, ruleIndex, rule.path.value, context);
|
|
2876
2868
|
}
|
|
2877
2869
|
function resolveContextOperand(operandData, context) {
|
|
2878
|
-
const
|
|
2879
|
-
const value = context?.[
|
|
2880
|
-
if (value === void 0) return { ctxTypeCode:
|
|
2870
|
+
const propertyInfo = lookupContextProperty(Number(toBigInt(operandData, 0)));
|
|
2871
|
+
const value = context?.[propertyInfo.contextKey];
|
|
2872
|
+
if (value === void 0) return { ctxTypeCode: propertyInfo.typeCode };
|
|
2881
2873
|
return typeof value === "string" ? addressToBigInt(value) : value;
|
|
2882
2874
|
}
|
|
2883
2875
|
function readTargetBlock(hint, targetOffset) {
|
|
@@ -2922,7 +2914,7 @@ function chainResolve(hint, callData, base, hopsOffset, hopCount) {
|
|
|
2922
2914
|
function evalTarget(callData, target, block, opCode, operandData, context) {
|
|
2923
2915
|
const typeCode = block.typeCode;
|
|
2924
2916
|
if (isLengthValidType(typeCode)) {
|
|
2925
|
-
if (!isLengthOp(opCode)) throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Value operator on a target without a scalar word
|
|
2917
|
+
if (!isLengthOp(opCode)) throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Value operator on a target without a scalar word");
|
|
2926
2918
|
const length = readPointer(callData, target);
|
|
2927
2919
|
if (typeof length !== "number") return { error: length.code };
|
|
2928
2920
|
const stride = typeCode === TypeCode.DYNAMIC_ARRAY ? (block.targetMeta & PolicyFormat.HINT_META_STRIDE_MASK) * 32 : 1;
|
|
@@ -2933,7 +2925,7 @@ function evalTarget(callData, target, block, opCode, operandData, context) {
|
|
|
2933
2925
|
value: BigInt(length)
|
|
2934
2926
|
};
|
|
2935
2927
|
}
|
|
2936
|
-
if (classifyTypeCode(typeCode).typeClass !== "elementary") throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Operator target does not carry a scalar word
|
|
2928
|
+
if (classifyTypeCode(typeCode).typeClass !== "elementary") throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Operator target does not carry a scalar word");
|
|
2937
2929
|
const word = loadWord(callData, target);
|
|
2938
2930
|
if (!(word instanceof Uint8Array)) return { error: word.code };
|
|
2939
2931
|
const value = toBigInt(word, 0);
|
|
@@ -3055,7 +3047,7 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
|
|
|
3055
3047
|
count = length;
|
|
3056
3048
|
elems += 32;
|
|
3057
3049
|
}
|
|
3058
|
-
if (count >
|
|
3050
|
+
if (count > PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH) return {
|
|
3059
3051
|
group: groupIndex,
|
|
3060
3052
|
rule: ruleIndex,
|
|
3061
3053
|
code: "QUANTIFIER_LIMIT_EXCEEDED",
|
|
@@ -3115,15 +3107,15 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
|
|
|
3115
3107
|
function evaluateContextRule(pathBytes, opCode, operandData, groupIndex, ruleIndex, pathHex, context) {
|
|
3116
3108
|
const propertyId = readU16(pathBytes, 0);
|
|
3117
3109
|
if (propertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID 0x${propertyId.toString(16).padStart(4, "0")}`);
|
|
3118
|
-
const
|
|
3119
|
-
const contextValue = context?.[
|
|
3110
|
+
const propertyInfo = lookupContextProperty(propertyId);
|
|
3111
|
+
const contextValue = context?.[propertyInfo.contextKey];
|
|
3120
3112
|
if (contextValue === void 0) return {
|
|
3121
3113
|
group: groupIndex,
|
|
3122
3114
|
rule: ruleIndex,
|
|
3123
3115
|
code: "MISSING_CONTEXT",
|
|
3124
3116
|
scope: Scope.CONTEXT,
|
|
3125
3117
|
path: pathHex,
|
|
3126
|
-
typeCode:
|
|
3118
|
+
typeCode: propertyInfo.typeCode
|
|
3127
3119
|
};
|
|
3128
3120
|
let value;
|
|
3129
3121
|
if (typeof contextValue === "string") value = addressToBigInt(contextValue);
|
|
@@ -3144,7 +3136,7 @@ function evaluateContextRule(pathBytes, opCode, operandData, groupIndex, ruleInd
|
|
|
3144
3136
|
};
|
|
3145
3137
|
result = value === operand !== ((opCode & Op.NOT) !== 0);
|
|
3146
3138
|
ctxOperand = operand;
|
|
3147
|
-
} else result = applyOperator(opCode, value, 32, operandData,
|
|
3139
|
+
} else result = applyOperator(opCode, value, 32, operandData, propertyInfo.typeCode);
|
|
3148
3140
|
if (!result) return {
|
|
3149
3141
|
group: groupIndex,
|
|
3150
3142
|
rule: ruleIndex,
|
|
@@ -3153,7 +3145,7 @@ function evaluateContextRule(pathBytes, opCode, operandData, groupIndex, ruleInd
|
|
|
3153
3145
|
path: pathHex,
|
|
3154
3146
|
opCode,
|
|
3155
3147
|
operandData: bytesToHex(operandData),
|
|
3156
|
-
typeCode:
|
|
3148
|
+
typeCode: propertyInfo.typeCode,
|
|
3157
3149
|
resolvedValue: bigintToHex(value),
|
|
3158
3150
|
...ctxOperand !== void 0 && { resolvedOperand: bigintToHex(ctxOperand) }
|
|
3159
3151
|
};
|
|
@@ -3164,6 +3156,6 @@ const PolicyEnforcer = {
|
|
|
3164
3156
|
enforce
|
|
3165
3157
|
};
|
|
3166
3158
|
//#endregion
|
|
3167
|
-
export { CallciumError, ContextProperty, Descriptor, DescriptorCoder,
|
|
3159
|
+
export { CallciumError, ContextProperty, Descriptor, DescriptorCoder, MAX_CONTEXT_PROPERTY_ID, Op, PolicyBuilder, PolicyCoder, PolicyEnforcer, PolicyFormat, PolicyValidator, PolicyViolationError, Quantifier, Scope, TypeCode, ValidationError, arg, baseFee, blockNumber, blockTimestamp, bytesToHex, chainId, gasPrice, hexToBytes, isLengthOp, isOpAllowed, isQuantifier, lookupContextProperty, lookupOp, lookupQuantifier, lookupScope, lookupTypeCode, msgSender, msgValue, parsePathSteps, toAddress, txOrigin };
|
|
3168
3160
|
|
|
3169
3161
|
//# sourceMappingURL=index.js.map
|