@callcium/sdk 0.8.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 +74 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +411 -330
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/bytes.ts +9 -9
- package/src/constants.ts +37 -175
- package/src/constraint.ts +24 -31
- package/src/descriptor-coder.ts +18 -20
- package/src/descriptor.ts +12 -11
- package/src/errors.ts +0 -3
- package/src/index.ts +8 -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 +22 -41
- package/src/policy-enforcer.ts +90 -35
- package/src/policy-validator.ts +58 -25
- package/src/type-desc.ts +10 -10
- package/src/types.ts +79 -5
- package/src/validation-issue.ts +18 -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",
|
|
@@ -320,6 +314,12 @@ const OP_TABLE = [
|
|
|
320
314
|
label: "in",
|
|
321
315
|
operands: "variadic"
|
|
322
316
|
},
|
|
317
|
+
{
|
|
318
|
+
key: "EQ_CTX",
|
|
319
|
+
code: 8,
|
|
320
|
+
label: "== ctx",
|
|
321
|
+
operands: "single"
|
|
322
|
+
},
|
|
323
323
|
{
|
|
324
324
|
key: "BITMASK_ALL",
|
|
325
325
|
code: 16,
|
|
@@ -389,13 +389,11 @@ function lookupOp(code) {
|
|
|
389
389
|
if (!info) throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}`);
|
|
390
390
|
return info;
|
|
391
391
|
}
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
if (operands === "single") return dataLength === 32;
|
|
395
|
-
if (operands === "range") return dataLength === 64;
|
|
396
|
-
if (operands === "variadic") return dataLength > 0 && dataLength % 32 === 0;
|
|
397
|
-
return false;
|
|
392
|
+
function operandsOf(opBase) {
|
|
393
|
+
return opByCode.get(opBase)?.operands;
|
|
398
394
|
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/path.ts
|
|
399
397
|
const QUANTIFIER_TABLE = [{
|
|
400
398
|
key: "ALL",
|
|
401
399
|
code: 65535,
|
|
@@ -415,71 +413,17 @@ function lookupQuantifier(code) {
|
|
|
415
413
|
if (!info) throw new CallciumError("UNKNOWN_QUANTIFIER", `Unknown quantifier step 0x${code.toString(16)}`);
|
|
416
414
|
return info;
|
|
417
415
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
typeClass: "elementary",
|
|
423
|
-
isDynamic: false
|
|
424
|
-
};
|
|
425
|
-
const ELEMENTARY_DYN = {
|
|
426
|
-
typeClass: "elementary",
|
|
427
|
-
isDynamic: true
|
|
428
|
-
};
|
|
429
|
-
const STATIC_ARR = {
|
|
430
|
-
typeClass: "staticArray",
|
|
431
|
-
isDynamic: false
|
|
432
|
-
};
|
|
433
|
-
const DYNAMIC_ARR = {
|
|
434
|
-
typeClass: "dynamicArray",
|
|
435
|
-
isDynamic: true
|
|
436
|
-
};
|
|
437
|
-
const TUPLE = {
|
|
438
|
-
typeClass: "tuple",
|
|
439
|
-
isDynamic: false
|
|
440
|
-
};
|
|
441
|
-
function classifyTypeCode(code) {
|
|
442
|
-
if (code >= TypeCode.UINT_MIN && code <= TypeCode.UINT_MAX) return ELEMENTARY;
|
|
443
|
-
if (code >= TypeCode.INT_MIN && code <= TypeCode.INT_MAX) return ELEMENTARY;
|
|
444
|
-
if (code === TypeCode.ADDRESS || code === TypeCode.BOOL || code === TypeCode.FUNCTION) return ELEMENTARY;
|
|
445
|
-
if (code >= 68 && code <= 79) unknownTypeCode(code);
|
|
446
|
-
if (code >= TypeCode.FIXED_BYTES_MIN && code <= TypeCode.FIXED_BYTES_MAX) return ELEMENTARY;
|
|
447
|
-
if (code === TypeCode.BYTES || code === TypeCode.STRING) return ELEMENTARY_DYN;
|
|
448
|
-
if (code >= 114 && code <= 127) unknownTypeCode(code);
|
|
449
|
-
if (code === TypeCode.STATIC_ARRAY) return STATIC_ARR;
|
|
450
|
-
if (code === TypeCode.DYNAMIC_ARRAY) return DYNAMIC_ARR;
|
|
451
|
-
if (code >= 130 && code <= 143) unknownTypeCode(code);
|
|
452
|
-
if (code === TypeCode.TUPLE) return TUPLE;
|
|
453
|
-
unknownTypeCode(code);
|
|
454
|
-
}
|
|
455
|
-
function isAddressableTarget(code) {
|
|
456
|
-
let info;
|
|
457
|
-
try {
|
|
458
|
-
info = classifyTypeCode(code);
|
|
459
|
-
} catch {
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
return info.typeClass === "elementary" || info.typeClass === "dynamicArray";
|
|
463
|
-
}
|
|
464
|
-
function typeCodeLabel(code) {
|
|
465
|
-
if (code >= TypeCode.UINT_MIN && code <= TypeCode.UINT_MAX) return `uint${(code - TypeCode.UINT_MIN + 1) * 8}`;
|
|
466
|
-
if (code >= TypeCode.INT_MIN && code <= TypeCode.INT_MAX) return `int${(code - TypeCode.INT_MIN + 1) * 8}`;
|
|
467
|
-
if (code === TypeCode.ADDRESS) return "address";
|
|
468
|
-
if (code === TypeCode.BOOL) return "bool";
|
|
469
|
-
if (code === TypeCode.FUNCTION) return "function";
|
|
470
|
-
if (code >= TypeCode.FIXED_BYTES_MIN && code <= TypeCode.FIXED_BYTES_MAX) return `bytes${code - TypeCode.FIXED_BYTES_MIN + 1}`;
|
|
471
|
-
if (code === TypeCode.BYTES) return "bytes";
|
|
472
|
-
if (code === TypeCode.STRING) return "string";
|
|
473
|
-
if (code === TypeCode.STATIC_ARRAY) return "T[k]";
|
|
474
|
-
if (code === TypeCode.DYNAMIC_ARRAY) return "T[]";
|
|
475
|
-
if (code === TypeCode.TUPLE) return "tuple";
|
|
476
|
-
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);
|
|
477
420
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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;
|
|
483
427
|
}
|
|
484
428
|
//#endregion
|
|
485
429
|
//#region src/descriptor.ts
|
|
@@ -497,7 +441,7 @@ function paramCount(desc) {
|
|
|
497
441
|
}
|
|
498
442
|
function paramOffset(desc, index) {
|
|
499
443
|
const count = desc[1];
|
|
500
|
-
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})`);
|
|
501
445
|
let cursor = DescriptorFormat.HEADER_SIZE;
|
|
502
446
|
for (let i = 0; i < index; i++) cursor += nodeLength(desc, cursor);
|
|
503
447
|
return cursor;
|
|
@@ -517,10 +461,10 @@ function typeAt(desc, steps) {
|
|
|
517
461
|
return walkPath(desc, steps).typeInfo;
|
|
518
462
|
}
|
|
519
463
|
function walkPath(desc, steps) {
|
|
520
|
-
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");
|
|
521
465
|
const paramIndex = steps[0];
|
|
522
466
|
const count = desc[1];
|
|
523
|
-
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})`);
|
|
524
468
|
let cursor = paramOffset(desc, paramIndex);
|
|
525
469
|
let quantifiedStaticLength = 0;
|
|
526
470
|
for (let stepIndex = 1; stepIndex < steps.length; stepIndex++) {
|
|
@@ -528,16 +472,16 @@ function walkPath(desc, steps) {
|
|
|
528
472
|
const step = steps[stepIndex];
|
|
529
473
|
if (typeCode === TypeCode.TUPLE) {
|
|
530
474
|
const fields = tupleFieldCount(desc, cursor);
|
|
531
|
-
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)`);
|
|
532
476
|
cursor = tupleFieldOffset(desc, cursor, step);
|
|
533
477
|
} else if (typeCode === TypeCode.STATIC_ARRAY || typeCode === TypeCode.DYNAMIC_ARRAY) {
|
|
534
478
|
if (typeCode === TypeCode.STATIC_ARRAY) if (isQuantifier(step)) quantifiedStaticLength = staticArrayLength(desc, cursor);
|
|
535
479
|
else {
|
|
536
480
|
const length = staticArrayLength(desc, cursor);
|
|
537
|
-
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})`);
|
|
538
482
|
}
|
|
539
483
|
cursor = arrayElementOffset(cursor);
|
|
540
|
-
} 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}`);
|
|
541
485
|
}
|
|
542
486
|
return {
|
|
543
487
|
typeInfo: inspect$1(desc, cursor),
|
|
@@ -555,7 +499,7 @@ function arrayElementOffset(offset) {
|
|
|
555
499
|
return offset + DescriptorFormat.ARRAY_HEADER_SIZE;
|
|
556
500
|
}
|
|
557
501
|
function uncompilablePath(stepIndex) {
|
|
558
|
-
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`);
|
|
559
503
|
}
|
|
560
504
|
function headSpan(desc, offset, count) {
|
|
561
505
|
let span = 0;
|
|
@@ -662,7 +606,7 @@ function encodeHint(walk, targetMeta, typeCode, depth) {
|
|
|
662
606
|
return out;
|
|
663
607
|
}
|
|
664
608
|
function compileHint(desc, steps) {
|
|
665
|
-
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");
|
|
666
610
|
const argIndex = steps[0];
|
|
667
611
|
if (argIndex >= paramCount(desc)) throw uncompilablePath(0);
|
|
668
612
|
const arg = headSpan(desc, DescriptorFormat.HEADER_SIZE, argIndex);
|
|
@@ -698,6 +642,199 @@ const Descriptor = {
|
|
|
698
642
|
compileHint
|
|
699
643
|
};
|
|
700
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
|
|
701
838
|
//#region src/type-desc.ts
|
|
702
839
|
function extractStaticWords(desc) {
|
|
703
840
|
const typeCode = desc[0];
|
|
@@ -714,11 +851,11 @@ function function_() {
|
|
|
714
851
|
return new Uint8Array([TypeCode.FUNCTION]);
|
|
715
852
|
}
|
|
716
853
|
function uintN(bits) {
|
|
717
|
-
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`);
|
|
718
855
|
return new Uint8Array([TypeCode.UINT_MIN + (bits / 8 - 1)]);
|
|
719
856
|
}
|
|
720
857
|
function intN(bits) {
|
|
721
|
-
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`);
|
|
722
859
|
return new Uint8Array([TypeCode.INT_MIN + (bits / 8 - 1)]);
|
|
723
860
|
}
|
|
724
861
|
function bytes() {
|
|
@@ -728,13 +865,13 @@ function string_() {
|
|
|
728
865
|
return new Uint8Array([TypeCode.STRING]);
|
|
729
866
|
}
|
|
730
867
|
function bytesN(n) {
|
|
731
|
-
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`);
|
|
732
869
|
return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (n - 1)]);
|
|
733
870
|
}
|
|
734
871
|
function array(elemDesc, length) {
|
|
735
872
|
if (length === void 0) {
|
|
736
873
|
const nodeLength = DescriptorFormat.ARRAY_HEADER_SIZE + elemDesc.length;
|
|
737
|
-
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}`);
|
|
738
875
|
const meta24 = 0 << DescriptorFormat.META_STATIC_WORDS_SHIFT | nodeLength;
|
|
739
876
|
const buf = new Uint8Array(1 + DescriptorFormat.COMPOSITE_META_SIZE + elemDesc.length);
|
|
740
877
|
buf[0] = TypeCode.DYNAMIC_ARRAY;
|
|
@@ -742,12 +879,12 @@ function array(elemDesc, length) {
|
|
|
742
879
|
buf.set(elemDesc, 4);
|
|
743
880
|
return buf;
|
|
744
881
|
}
|
|
745
|
-
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}`);
|
|
746
883
|
const nodeLength = DescriptorFormat.ARRAY_HEADER_SIZE + elemDesc.length + DescriptorFormat.ARRAY_LENGTH_SIZE;
|
|
747
|
-
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}`);
|
|
748
885
|
const elemStaticWords = extractStaticWords(elemDesc);
|
|
749
886
|
const staticWords = elemStaticWords === 0 ? 0 : length * elemStaticWords;
|
|
750
|
-
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}`);
|
|
751
888
|
const meta24 = staticWords << DescriptorFormat.META_STATIC_WORDS_SHIFT | nodeLength;
|
|
752
889
|
const buf = new Uint8Array(1 + DescriptorFormat.COMPOSITE_META_SIZE + elemDesc.length + DescriptorFormat.ARRAY_LENGTH_SIZE);
|
|
753
890
|
buf[0] = TypeCode.STATIC_ARRAY;
|
|
@@ -757,10 +894,10 @@ function array(elemDesc, length) {
|
|
|
757
894
|
return buf;
|
|
758
895
|
}
|
|
759
896
|
function tuple(fieldDescs) {
|
|
760
|
-
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}`);
|
|
761
898
|
const totalFieldBytes = fieldDescs.reduce((sum, f) => sum + f.length, 0);
|
|
762
899
|
const nodeLength = DescriptorFormat.TUPLE_HEADER_SIZE + totalFieldBytes;
|
|
763
|
-
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}`);
|
|
764
901
|
let anyDynamic = false;
|
|
765
902
|
let staticWordsSum = 0;
|
|
766
903
|
for (const field of fieldDescs) {
|
|
@@ -769,7 +906,7 @@ function tuple(fieldDescs) {
|
|
|
769
906
|
staticWordsSum += staticWords;
|
|
770
907
|
}
|
|
771
908
|
const staticWords = anyDynamic ? 0 : staticWordsSum;
|
|
772
|
-
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}`);
|
|
773
910
|
const meta24 = staticWords << DescriptorFormat.META_STATIC_WORDS_SHIFT | nodeLength;
|
|
774
911
|
const buf = new Uint8Array(1 + DescriptorFormat.COMPOSITE_META_SIZE + DescriptorFormat.TUPLE_FIELDCOUNT_SIZE + totalFieldBytes);
|
|
775
912
|
buf[0] = TypeCode.TUPLE;
|
|
@@ -811,19 +948,19 @@ function parseUint(input, start, end) {
|
|
|
811
948
|
function parseTuple(input, start, end) {
|
|
812
949
|
if (input[start] !== "(") throw new CallciumError("MALFORMED_TYPE_STRING", `Expected '(' at position ${start}`);
|
|
813
950
|
let depth = 0;
|
|
814
|
-
let
|
|
951
|
+
let closeIndex = -1;
|
|
815
952
|
for (let i = start; i < end; i++) if (input[i] === "(") depth++;
|
|
816
953
|
else if (input[i] === ")") {
|
|
817
954
|
depth--;
|
|
818
955
|
if (depth === 0) {
|
|
819
|
-
|
|
956
|
+
closeIndex = i;
|
|
820
957
|
break;
|
|
821
958
|
}
|
|
822
959
|
}
|
|
823
|
-
if (
|
|
824
|
-
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}`);
|
|
825
962
|
const innerStart = start + 1;
|
|
826
|
-
const innerEnd =
|
|
963
|
+
const innerEnd = closeIndex;
|
|
827
964
|
const commas = commaPositions(input, innerStart, innerEnd);
|
|
828
965
|
const segments = [];
|
|
829
966
|
let segStart = innerStart;
|
|
@@ -853,14 +990,14 @@ function parseType(input, start, end) {
|
|
|
853
990
|
const suffixes = [];
|
|
854
991
|
let baseEnd = end;
|
|
855
992
|
while (baseEnd > start && input[baseEnd - 1] === "]") {
|
|
856
|
-
const
|
|
857
|
-
let
|
|
858
|
-
while (
|
|
859
|
-
if (input[
|
|
860
|
-
const innerStart =
|
|
861
|
-
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;
|
|
862
999
|
suffixes.unshift(innerStart === innerEnd ? void 0 : parseUint(input, innerStart, innerEnd));
|
|
863
|
-
baseEnd =
|
|
1000
|
+
baseEnd = openIndex;
|
|
864
1001
|
}
|
|
865
1002
|
if (input[start] !== "(" && input.slice(start, baseEnd).includes("[")) throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched '[' in '${input.slice(start, baseEnd)}'`);
|
|
866
1003
|
let desc = parseBaseType(input, start, baseEnd);
|
|
@@ -878,7 +1015,7 @@ function fromTypes(typesCsv) {
|
|
|
878
1015
|
}
|
|
879
1016
|
segments.push([segStart, typesCsv.length]);
|
|
880
1017
|
const paramDescs = segments.map(([start, end]) => parseType(typesCsv, start, end));
|
|
881
|
-
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}`);
|
|
882
1019
|
const totalBytes = paramDescs.reduce((sum, d) => sum + d.length, 0);
|
|
883
1020
|
const result = new Uint8Array(DescriptorFormat.HEADER_SIZE + totalBytes);
|
|
884
1021
|
result[0] = DescriptorFormat.VERSION;
|
|
@@ -1016,134 +1153,7 @@ const DescriptorCoder = {
|
|
|
1016
1153
|
toTypes
|
|
1017
1154
|
};
|
|
1018
1155
|
//#endregion
|
|
1019
|
-
//#region src/operators.ts
|
|
1020
|
-
function toBigInt(bytes, offset = 0) {
|
|
1021
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, 32);
|
|
1022
|
-
return view.getBigUint64(0, false) << 192n | view.getBigUint64(8, false) << 128n | view.getBigUint64(16, false) << 64n | view.getBigUint64(24, false);
|
|
1023
|
-
}
|
|
1024
|
-
function isSigned(typeCode) {
|
|
1025
|
-
return typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX;
|
|
1026
|
-
}
|
|
1027
|
-
function isLeftAligned(typeCode) {
|
|
1028
|
-
return typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX || typeCode === TypeCode.FUNCTION;
|
|
1029
|
-
}
|
|
1030
|
-
function canonicalize(value, typeCode) {
|
|
1031
|
-
if (typeCode >= TypeCode.UINT_MIN && typeCode <= TypeCode.UINT_MAX) {
|
|
1032
|
-
const bits = BigInt(typeCode * 8);
|
|
1033
|
-
return bits === 256n ? value : value & (1n << bits) - 1n;
|
|
1034
|
-
}
|
|
1035
|
-
if (typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX) {
|
|
1036
|
-
const bits = (typeCode - TypeCode.INT_MIN + 1) * 8;
|
|
1037
|
-
return bits === 256 ? value : BigInt.asUintN(256, BigInt.asIntN(bits, value));
|
|
1038
|
-
}
|
|
1039
|
-
if (typeCode === TypeCode.ADDRESS) return value & (1n << 160n) - 1n;
|
|
1040
|
-
if (typeCode === TypeCode.BOOL) return value & 1n;
|
|
1041
|
-
if (typeCode === TypeCode.FUNCTION) return value >> 64n << 64n;
|
|
1042
|
-
if (typeCode >= TypeCode.FIXED_BYTES_MIN && typeCode <= TypeCode.FIXED_BYTES_MAX) {
|
|
1043
|
-
const n = typeCode - TypeCode.FIXED_BYTES_MIN + 1;
|
|
1044
|
-
if (n === 32) return value;
|
|
1045
|
-
const padBits = BigInt((32 - n) * 8);
|
|
1046
|
-
return value >> padBits << padBits;
|
|
1047
|
-
}
|
|
1048
|
-
return value;
|
|
1049
|
-
}
|
|
1050
|
-
function isLengthOp(opCode) {
|
|
1051
|
-
const base = opCode & ~Op.NOT;
|
|
1052
|
-
return base >= Op.LENGTH_EQ && base <= Op.LENGTH_BETWEEN;
|
|
1053
|
-
}
|
|
1054
|
-
function lengthToValueOp(base) {
|
|
1055
|
-
if (base === Op.LENGTH_EQ) return Op.EQ;
|
|
1056
|
-
if (base === Op.LENGTH_GT) return Op.GT;
|
|
1057
|
-
if (base === Op.LENGTH_LT) return Op.LT;
|
|
1058
|
-
if (base === Op.LENGTH_GTE) return Op.GTE;
|
|
1059
|
-
if (base === Op.LENGTH_LTE) return Op.LTE;
|
|
1060
|
-
return Op.BETWEEN;
|
|
1061
|
-
}
|
|
1062
|
-
function isLengthValidType(typeCode) {
|
|
1063
|
-
return typeCode === TypeCode.BYTES || typeCode === TypeCode.STRING || typeCode === TypeCode.DYNAMIC_ARRAY;
|
|
1064
|
-
}
|
|
1065
|
-
function compareTyped(a, b, typeCode) {
|
|
1066
|
-
if (isSigned(typeCode)) {
|
|
1067
|
-
const sa = BigInt.asIntN(256, a);
|
|
1068
|
-
const sb = BigInt.asIntN(256, b);
|
|
1069
|
-
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
1070
|
-
}
|
|
1071
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
1072
|
-
}
|
|
1073
|
-
function isIn(value, data) {
|
|
1074
|
-
const count = data.length / 32;
|
|
1075
|
-
let lo = 0;
|
|
1076
|
-
let hi = count - 1;
|
|
1077
|
-
while (lo <= hi) {
|
|
1078
|
-
const mid = lo + hi >>> 1;
|
|
1079
|
-
const elem = toBigInt(data, mid * 32);
|
|
1080
|
-
if (value === elem) return true;
|
|
1081
|
-
if (value < elem) hi = mid - 1;
|
|
1082
|
-
else lo = mid + 1;
|
|
1083
|
-
}
|
|
1084
|
-
return false;
|
|
1085
|
-
}
|
|
1086
|
-
function applyOperator(opCode, value, valueLength, operandData, typeCode) {
|
|
1087
|
-
let base = opCode & ~Op.NOT;
|
|
1088
|
-
if (isLengthOp(base)) {
|
|
1089
|
-
value = BigInt(valueLength);
|
|
1090
|
-
typeCode = TypeCode.UINT_MAX;
|
|
1091
|
-
base = lengthToValueOp(base);
|
|
1092
|
-
}
|
|
1093
|
-
let result;
|
|
1094
|
-
switch (base) {
|
|
1095
|
-
case Op.EQ:
|
|
1096
|
-
result = value === toBigInt(operandData, 0);
|
|
1097
|
-
break;
|
|
1098
|
-
case Op.GT:
|
|
1099
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) > 0;
|
|
1100
|
-
break;
|
|
1101
|
-
case Op.LT:
|
|
1102
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) < 0;
|
|
1103
|
-
break;
|
|
1104
|
-
case Op.GTE:
|
|
1105
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) >= 0;
|
|
1106
|
-
break;
|
|
1107
|
-
case Op.LTE:
|
|
1108
|
-
result = compareTyped(value, toBigInt(operandData, 0), typeCode) <= 0;
|
|
1109
|
-
break;
|
|
1110
|
-
case Op.BETWEEN: {
|
|
1111
|
-
const min = toBigInt(operandData, 0);
|
|
1112
|
-
const max = toBigInt(operandData, 32);
|
|
1113
|
-
result = compareTyped(value, min, typeCode) >= 0 && compareTyped(value, max, typeCode) <= 0;
|
|
1114
|
-
break;
|
|
1115
|
-
}
|
|
1116
|
-
case Op.IN:
|
|
1117
|
-
result = isIn(value, operandData);
|
|
1118
|
-
break;
|
|
1119
|
-
case Op.BITMASK_ALL: {
|
|
1120
|
-
const mask = toBigInt(operandData, 0);
|
|
1121
|
-
result = (value & mask) === mask;
|
|
1122
|
-
break;
|
|
1123
|
-
}
|
|
1124
|
-
case Op.BITMASK_ANY: {
|
|
1125
|
-
const mask = toBigInt(operandData, 0);
|
|
1126
|
-
result = (value & mask) !== 0n;
|
|
1127
|
-
break;
|
|
1128
|
-
}
|
|
1129
|
-
case Op.BITMASK_NONE: {
|
|
1130
|
-
const mask = toBigInt(operandData, 0);
|
|
1131
|
-
result = (value & mask) === 0n;
|
|
1132
|
-
break;
|
|
1133
|
-
}
|
|
1134
|
-
default: throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}.`);
|
|
1135
|
-
}
|
|
1136
|
-
return (opCode & Op.NOT) !== 0 ? !result : result;
|
|
1137
|
-
}
|
|
1138
|
-
//#endregion
|
|
1139
1156
|
//#region src/policy-coder.ts
|
|
1140
|
-
function parsePathSteps(path) {
|
|
1141
|
-
const bytes = hexToBytes(path);
|
|
1142
|
-
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`);
|
|
1143
|
-
const steps = [];
|
|
1144
|
-
for (let offset = 0; offset < bytes.length; offset += PolicyFormat.PATH_STEP_SIZE) steps.push(readU16(bytes, offset));
|
|
1145
|
-
return steps;
|
|
1146
|
-
}
|
|
1147
1157
|
function field(value, start, end) {
|
|
1148
1158
|
return {
|
|
1149
1159
|
value,
|
|
@@ -1289,8 +1299,11 @@ function decodePolicy(blob) {
|
|
|
1289
1299
|
const wordCount = dataLengthValue / 32;
|
|
1290
1300
|
for (let word = 1; word < wordCount; word++) if (compareBytes(data.subarray(dataStart + (word - 1) * 32, dataStart + word * 32), data.subarray(dataStart + word * 32, dataStart + (word + 1) * 32)) >= 0) throw new CallciumError("UNSORTED_IN_SET", "IN operands must be strictly ascending", ruleOffset);
|
|
1291
1301
|
}
|
|
1302
|
+
if (opBase === Op.EQ_CTX) {
|
|
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);
|
|
1304
|
+
}
|
|
1292
1305
|
if (depthValue === 0) throw new CallciumError("EMPTY_PATH", "Rule path must have at least one step", ruleOffset);
|
|
1293
|
-
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);
|
|
1294
1307
|
if (scopeValue === Scope.CONTEXT) {
|
|
1295
1308
|
if (depthValue !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope rules must have exactly one path step", ruleOffset);
|
|
1296
1309
|
const contextPropertyId = readU16(data, pathStart);
|
|
@@ -1357,12 +1370,12 @@ function decodePolicy(blob) {
|
|
|
1357
1370
|
}
|
|
1358
1371
|
function flattenConstraint(constraint, desc) {
|
|
1359
1372
|
const operators = constraint.operators.map(hexToBytes);
|
|
1360
|
-
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)");
|
|
1361
1374
|
const path = hexToBytes(constraint.path);
|
|
1362
|
-
if (path.length === 0) throw new CallciumError("EMPTY_PATH", "Rule path must have at least one step
|
|
1363
|
-
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");
|
|
1364
1377
|
const depth = path.length / 2;
|
|
1365
|
-
if (depth >
|
|
1378
|
+
if (depth > PolicyFormat.MAX_PATH_DEPTH) throw new CallciumError("PATH_TOO_DEEP", `Path depth ${depth} exceeds maximum ${PolicyFormat.MAX_PATH_DEPTH}`);
|
|
1366
1379
|
const hint = constraint.scope === Scope.CALLDATA ? Descriptor.compileHint(desc, parsePathSteps(constraint.path)) : new Uint8Array(0);
|
|
1367
1380
|
return operators.map((operator) => ({
|
|
1368
1381
|
scope: constraint.scope,
|
|
@@ -1389,7 +1402,7 @@ function sortRules(rules) {
|
|
|
1389
1402
|
}
|
|
1390
1403
|
function encodeRule(rule) {
|
|
1391
1404
|
const depth = rule.path.length / 2;
|
|
1392
|
-
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");
|
|
1393
1406
|
const data = rule.operator.subarray(1);
|
|
1394
1407
|
const ruleSize = PolicyFormat.RULE_FIXED_OVERHEAD + rule.path.length + rule.hint.length + data.length;
|
|
1395
1408
|
if (ruleSize > 65535) throw new CallciumError("RULE_SIZE_OVERFLOW", `Rule size ${ruleSize} exceeds maximum 65535`);
|
|
@@ -1557,6 +1570,18 @@ function hintMismatch(groupIndex, constraintIndex) {
|
|
|
1557
1570
|
message: "Hint does not match the path compiled against the descriptor"
|
|
1558
1571
|
};
|
|
1559
1572
|
}
|
|
1573
|
+
function contextTypeMismatch(groupIndex, constraintIndex, contextId, typeCode) {
|
|
1574
|
+
return {
|
|
1575
|
+
severity: "error",
|
|
1576
|
+
category: "typeMismatch",
|
|
1577
|
+
groupIndex,
|
|
1578
|
+
constraintIndex,
|
|
1579
|
+
code: "CONTEXT_TYPE_MISMATCH",
|
|
1580
|
+
value1: contextId,
|
|
1581
|
+
value2: typeCode,
|
|
1582
|
+
message: "Context property type does not match the target type"
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1560
1585
|
function nonCanonicalOperand(groupIndex, constraintIndex, operand, canonical) {
|
|
1561
1586
|
return {
|
|
1562
1587
|
severity: "error",
|
|
@@ -1722,7 +1747,7 @@ function unsortedInSet(groupIndex, constraintIndex) {
|
|
|
1722
1747
|
code: "UNSORTED_IN_SET",
|
|
1723
1748
|
value1: ZERO,
|
|
1724
1749
|
value2: ZERO,
|
|
1725
|
-
message: "isIn/notIn set is not strictly sorted and deduplicated
|
|
1750
|
+
message: "isIn/notIn set is not strictly sorted and deduplicated"
|
|
1726
1751
|
};
|
|
1727
1752
|
}
|
|
1728
1753
|
function emptyGroup(groupIndex) {
|
|
@@ -1734,7 +1759,7 @@ function emptyGroup(groupIndex) {
|
|
|
1734
1759
|
code: "EMPTY_GROUP",
|
|
1735
1760
|
value1: ZERO,
|
|
1736
1761
|
value2: ZERO,
|
|
1737
|
-
message: "Group contains zero constraints
|
|
1762
|
+
message: "Group contains zero constraints"
|
|
1738
1763
|
};
|
|
1739
1764
|
}
|
|
1740
1765
|
function dominatedBound(isLength, groupIndex, constraintIndex, value) {
|
|
@@ -1919,7 +1944,7 @@ function negationUnderAny(groupIndex, constraintIndex, opCode) {
|
|
|
1919
1944
|
}
|
|
1920
1945
|
//#endregion
|
|
1921
1946
|
//#region src/policy-validator.ts
|
|
1922
|
-
const
|
|
1947
|
+
const navigabilityCodes = new Set([
|
|
1923
1948
|
"EMPTY_PATH",
|
|
1924
1949
|
"PARAM_INDEX_OUT_OF_BOUNDS",
|
|
1925
1950
|
"TUPLE_FIELD_OUT_OF_BOUNDS",
|
|
@@ -2006,6 +2031,10 @@ function getIncompat(opBase, typeInfo) {
|
|
|
2006
2031
|
code: "IN_ON_BOOL",
|
|
2007
2032
|
message: "IN operator used on boolean type"
|
|
2008
2033
|
};
|
|
2034
|
+
if (opBase === Op.EQ_CTX && typeCode !== TypeCode.ADDRESS && !(typeCode >= TypeCode.UINT_MIN && typeCode <= TypeCode.UINT_MAX)) return {
|
|
2035
|
+
code: "CONTEXT_TYPE_MISMATCH",
|
|
2036
|
+
message: "Context reference operator used on incompatible type"
|
|
2037
|
+
};
|
|
2009
2038
|
return null;
|
|
2010
2039
|
}
|
|
2011
2040
|
if (isLengthOp(opBase)) {
|
|
@@ -2334,7 +2363,11 @@ function validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues
|
|
|
2334
2363
|
issues.push(fromOpRule(incompat.code, incompat.message, groupIndex, constraintIndex, bigintToHex(BigInt(opCode))));
|
|
2335
2364
|
continue;
|
|
2336
2365
|
}
|
|
2337
|
-
if (
|
|
2366
|
+
if (base === Op.EQ_CTX) {
|
|
2367
|
+
const ctxOperand = readValue(opHex);
|
|
2368
|
+
if (ctxOperand > BigInt(MAX_CONTEXT_PROPERTY_ID)) issues.push(unknownContextProperty(groupIndex, constraintIndex, bigintToHex(ctxOperand), bigintToHex(BigInt(MAX_CONTEXT_PROPERTY_ID))));
|
|
2369
|
+
else if (lookupContextProperty(Number(ctxOperand)).typeCode === TypeCode.ADDRESS !== (ctx.typeInfo.typeCode === TypeCode.ADDRESS)) issues.push(contextTypeMismatch(groupIndex, constraintIndex, bigintToHex(ctxOperand), bigintToHex(BigInt(ctx.typeInfo.typeCode))));
|
|
2370
|
+
} else if (isValueOp(base)) {
|
|
2338
2371
|
const typeCode = ctx.typeInfo.typeCode;
|
|
2339
2372
|
if (isLeftAligned(typeCode)) {
|
|
2340
2373
|
const nonCanonical = findNonCanonicalWord(opHex, typeCode);
|
|
@@ -2389,12 +2422,12 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2389
2422
|
const steps = parsePathSteps(constraint.path);
|
|
2390
2423
|
let typeInfo;
|
|
2391
2424
|
if (constraint.scope === Scope.CALLDATA) {
|
|
2392
|
-
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))));
|
|
2393
2426
|
let walk;
|
|
2394
2427
|
try {
|
|
2395
2428
|
walk = Descriptor.walkPath(descBytes, steps);
|
|
2396
2429
|
} catch (error) {
|
|
2397
|
-
if (error instanceof CallciumError &&
|
|
2430
|
+
if (error instanceof CallciumError && navigabilityCodes.has(error.code)) {
|
|
2398
2431
|
issues.push(unnavigablePath(groupIndex, constraintIndex));
|
|
2399
2432
|
continue;
|
|
2400
2433
|
}
|
|
@@ -2404,7 +2437,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2404
2437
|
issues.push(nestedQuantifier(groupIndex, constraintIndex));
|
|
2405
2438
|
continue;
|
|
2406
2439
|
}
|
|
2407
|
-
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))));
|
|
2408
2441
|
typeInfo = walk.typeInfo;
|
|
2409
2442
|
} else {
|
|
2410
2443
|
const ctxId = steps[0];
|
|
@@ -2424,7 +2457,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
|
|
|
2424
2457
|
ctx = initContext(constraint.scope, normalizedPath, steps, typeInfo);
|
|
2425
2458
|
contexts.push(ctx);
|
|
2426
2459
|
}
|
|
2427
|
-
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) {
|
|
2428
2461
|
if (bytesToHex(Descriptor.compileHint(descBytes, ctx.steps)) !== constraint.hint.toLowerCase()) issues.push(hintMismatch(groupIndex, constraintIndex));
|
|
2429
2462
|
}
|
|
2430
2463
|
validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues);
|
|
@@ -2481,14 +2514,14 @@ const SignatureParser = { parse };
|
|
|
2481
2514
|
//#endregion
|
|
2482
2515
|
//#region src/policy-builder.ts
|
|
2483
2516
|
function validateContextPath(steps) {
|
|
2484
|
-
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");
|
|
2485
2518
|
const step = steps[0];
|
|
2486
|
-
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")}`);
|
|
2487
2520
|
}
|
|
2488
2521
|
function validateCalldataPath(steps, desc) {
|
|
2489
2522
|
const argIndex = steps[0];
|
|
2490
2523
|
const paramCount = Descriptor.paramCount(desc);
|
|
2491
|
-
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)`);
|
|
2492
2525
|
let offset = Descriptor.paramOffset(desc, argIndex);
|
|
2493
2526
|
let hasQuantifier = false;
|
|
2494
2527
|
for (let i = 1; i < steps.length; i++) {
|
|
@@ -2496,21 +2529,21 @@ function validateCalldataPath(steps, desc) {
|
|
|
2496
2529
|
const info = Descriptor.inspect(desc, offset);
|
|
2497
2530
|
const isArray = info.typeCode === TypeCode.STATIC_ARRAY || info.typeCode === TypeCode.DYNAMIC_ARRAY;
|
|
2498
2531
|
if (isQuantifier(step)) {
|
|
2499
|
-
if (!isArray) throw new CallciumError("QUANTIFIER_ON_NON_ARRAY", "Quantifier step is only valid on an array node
|
|
2500
|
-
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");
|
|
2501
2534
|
hasQuantifier = true;
|
|
2502
2535
|
}
|
|
2503
2536
|
if (info.typeCode === TypeCode.TUPLE) {
|
|
2504
2537
|
const fieldCount = Descriptor.tupleFieldCount(desc, offset);
|
|
2505
|
-
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)`);
|
|
2506
2539
|
offset = Descriptor.tupleFieldOffset(desc, offset, step);
|
|
2507
2540
|
} else if (isArray) {
|
|
2508
2541
|
if (!isQuantifier(step) && info.typeCode === TypeCode.STATIC_ARRAY) {
|
|
2509
2542
|
const arrayLength = Descriptor.staticArrayLength(desc, offset);
|
|
2510
|
-
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)`);
|
|
2511
2544
|
}
|
|
2512
2545
|
offset = Descriptor.arrayElementOffset(offset);
|
|
2513
|
-
} 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");
|
|
2514
2547
|
}
|
|
2515
2548
|
}
|
|
2516
2549
|
var PolicyBuilder = class PolicyBuilder {
|
|
@@ -2544,21 +2577,21 @@ var PolicyBuilder = class PolicyBuilder {
|
|
|
2544
2577
|
path: constraint.path,
|
|
2545
2578
|
operators: [...constraint.operators]
|
|
2546
2579
|
};
|
|
2547
|
-
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");
|
|
2548
2581
|
const steps = parsePathSteps(c.path);
|
|
2549
|
-
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");
|
|
2550
2583
|
if (c.scope === Scope.CONTEXT) validateContextPath(steps);
|
|
2551
2584
|
else if (c.scope === Scope.CALLDATA) validateCalldataPath(steps, this.draft.descriptor);
|
|
2552
|
-
else throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${c.scope}
|
|
2585
|
+
else throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${c.scope}`);
|
|
2553
2586
|
const key = `${c.scope}:${c.path.toLowerCase()}`;
|
|
2554
2587
|
const currentHashes = this.draft.pathHashes[this.draft.pathHashes.length - 1];
|
|
2555
|
-
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`);
|
|
2556
2589
|
currentHashes.add(key);
|
|
2557
2590
|
this.draft.groups[this.draft.groups.length - 1].push(c);
|
|
2558
2591
|
return this;
|
|
2559
2592
|
}
|
|
2560
2593
|
or() {
|
|
2561
|
-
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");
|
|
2562
2595
|
this.draft.groups.push([]);
|
|
2563
2596
|
this.draft.pathHashes.push(/* @__PURE__ */ new Set());
|
|
2564
2597
|
return this;
|
|
@@ -2580,7 +2613,7 @@ var PolicyBuilder = class PolicyBuilder {
|
|
|
2580
2613
|
return PolicyValidator.validate(policyData);
|
|
2581
2614
|
}
|
|
2582
2615
|
checkGroups() {
|
|
2583
|
-
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`);
|
|
2584
2617
|
}
|
|
2585
2618
|
toPolicyData() {
|
|
2586
2619
|
return {
|
|
@@ -2603,8 +2636,7 @@ function encodeWord(value) {
|
|
|
2603
2636
|
return word;
|
|
2604
2637
|
}
|
|
2605
2638
|
if (typeof value === "string") {
|
|
2606
|
-
|
|
2607
|
-
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);
|
|
2608
2640
|
return word;
|
|
2609
2641
|
}
|
|
2610
2642
|
let bigValue = typeof value === "number" ? BigInt(value) : value;
|
|
@@ -2628,7 +2660,10 @@ function rangeOp(opCode, min, max) {
|
|
|
2628
2660
|
buffer.set(encodeWord(max), 33);
|
|
2629
2661
|
return bytesToHex(buffer);
|
|
2630
2662
|
}
|
|
2631
|
-
|
|
2663
|
+
function checkContextPropertyId(contextPropertyId) {
|
|
2664
|
+
if (!Number.isInteger(contextPropertyId) || contextPropertyId < 0 || contextPropertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID ${contextPropertyId}`);
|
|
2665
|
+
return contextPropertyId;
|
|
2666
|
+
}
|
|
2632
2667
|
function setOp(opCode, values) {
|
|
2633
2668
|
const bigs = values.map((v) => {
|
|
2634
2669
|
if (typeof v === "bigint") return v;
|
|
@@ -2636,21 +2671,14 @@ function setOp(opCode, values) {
|
|
|
2636
2671
|
if (typeof v === "boolean") return v ? 1n : 0n;
|
|
2637
2672
|
return BigInt("0x" + addressBody(v));
|
|
2638
2673
|
});
|
|
2639
|
-
bigs.
|
|
2640
|
-
const deduped = [];
|
|
2641
|
-
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);
|
|
2642
2675
|
if (deduped.length === 0) throw new CallciumError("EMPTY_SET", "Set must contain at least one value");
|
|
2643
|
-
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`);
|
|
2644
2677
|
const buffer = new Uint8Array(1 + deduped.length * 32);
|
|
2645
2678
|
buffer[0] = opCode;
|
|
2646
2679
|
for (let i = 0; i < deduped.length; i++) buffer.set(encodeWord(deduped[i]), 1 + i * 32);
|
|
2647
2680
|
return bytesToHex(buffer);
|
|
2648
2681
|
}
|
|
2649
|
-
function encodePath(steps) {
|
|
2650
|
-
const buffer = new Uint8Array(steps.length * 2);
|
|
2651
|
-
for (let i = 0; i < steps.length; i++) writeBE16(buffer, i * 2, steps[i]);
|
|
2652
|
-
return bytesToHex(buffer);
|
|
2653
|
-
}
|
|
2654
2682
|
var ConstraintBuilder = class {
|
|
2655
2683
|
scope;
|
|
2656
2684
|
path;
|
|
@@ -2670,6 +2698,12 @@ var ConstraintBuilder = class {
|
|
|
2670
2698
|
neq(value) {
|
|
2671
2699
|
return this.push(singleOp(Op.EQ | Op.NOT, value));
|
|
2672
2700
|
}
|
|
2701
|
+
eqCtx(contextPropertyId) {
|
|
2702
|
+
return this.push(singleOp(Op.EQ_CTX, checkContextPropertyId(contextPropertyId)));
|
|
2703
|
+
}
|
|
2704
|
+
neqCtx(contextPropertyId) {
|
|
2705
|
+
return this.push(singleOp(Op.EQ_CTX | Op.NOT, checkContextPropertyId(contextPropertyId)));
|
|
2706
|
+
}
|
|
2673
2707
|
gt(bound) {
|
|
2674
2708
|
return this.push(singleOp(Op.GT, bound));
|
|
2675
2709
|
}
|
|
@@ -2762,17 +2796,14 @@ function readPointer(callData, head) {
|
|
|
2762
2796
|
}
|
|
2763
2797
|
//#endregion
|
|
2764
2798
|
//#region src/policy-enforcer.ts
|
|
2765
|
-
const
|
|
2799
|
+
const abortViolationCodes = new Set([
|
|
2766
2800
|
"CALLDATA_OUT_OF_BOUNDS",
|
|
2767
2801
|
"ARRAY_INDEX_OUT_OF_BOUNDS",
|
|
2768
2802
|
"NON_CANONICAL_VALUE",
|
|
2769
2803
|
"QUANTIFIER_LIMIT_EXCEEDED"
|
|
2770
2804
|
]);
|
|
2771
2805
|
function addressToBigInt(hex) {
|
|
2772
|
-
|
|
2773
|
-
const padded = new Uint8Array(32);
|
|
2774
|
-
padded.set(bytes, 32 - bytes.length);
|
|
2775
|
-
return toBigInt(padded, 0);
|
|
2806
|
+
return BigInt(toAddress(hex));
|
|
2776
2807
|
}
|
|
2777
2808
|
function check(policy, callData, context) {
|
|
2778
2809
|
const { policy: decoded, data: policyBytes } = decodePolicy(policy);
|
|
@@ -2803,7 +2834,7 @@ function check(policy, callData, context) {
|
|
|
2803
2834
|
const violation = evaluateRule(rule, policyBytes, callDataBytes, baseOffset, groupIndex, ruleIndex, context);
|
|
2804
2835
|
if (violation !== null) {
|
|
2805
2836
|
allViolations.push(violation);
|
|
2806
|
-
if (
|
|
2837
|
+
if (abortViolationCodes.has(violation.code)) return {
|
|
2807
2838
|
ok: false,
|
|
2808
2839
|
violations: allViolations
|
|
2809
2840
|
};
|
|
@@ -2833,7 +2864,13 @@ function evaluateRule(rule, policyBytes, callDataBytes, baseOffset, groupIndex,
|
|
|
2833
2864
|
const operandData = fieldBytes(policyBytes, rule.data);
|
|
2834
2865
|
if (rule.scope.value === Scope.CONTEXT) return evaluateContextRule(fieldBytes(policyBytes, rule.path), opCode, operandData, groupIndex, ruleIndex, rule.path.value, context);
|
|
2835
2866
|
if (rule.hint === void 0) throw new CallciumError("MALFORMED_HINT", "Calldata rule carries no hint block");
|
|
2836
|
-
return evaluateCalldataRule(fieldBytes(policyBytes, rule.hint), callDataBytes, baseOffset, opCode, operandData, groupIndex, ruleIndex, rule.path.value);
|
|
2867
|
+
return evaluateCalldataRule(fieldBytes(policyBytes, rule.hint), callDataBytes, baseOffset, opCode, operandData, groupIndex, ruleIndex, rule.path.value, context);
|
|
2868
|
+
}
|
|
2869
|
+
function resolveContextOperand(operandData, context) {
|
|
2870
|
+
const propertyInfo = lookupContextProperty(Number(toBigInt(operandData, 0)));
|
|
2871
|
+
const value = context?.[propertyInfo.contextKey];
|
|
2872
|
+
if (value === void 0) return { ctxTypeCode: propertyInfo.typeCode };
|
|
2873
|
+
return typeof value === "string" ? addressToBigInt(value) : value;
|
|
2837
2874
|
}
|
|
2838
2875
|
function readTargetBlock(hint, targetOffset) {
|
|
2839
2876
|
return {
|
|
@@ -2874,10 +2911,10 @@ function chainResolve(hint, callData, base, hopsOffset, hopCount) {
|
|
|
2874
2911
|
}
|
|
2875
2912
|
return base;
|
|
2876
2913
|
}
|
|
2877
|
-
function evalTarget(callData, target, block, opCode, operandData) {
|
|
2914
|
+
function evalTarget(callData, target, block, opCode, operandData, context) {
|
|
2878
2915
|
const typeCode = block.typeCode;
|
|
2879
2916
|
if (isLengthValidType(typeCode)) {
|
|
2880
|
-
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");
|
|
2881
2918
|
const length = readPointer(callData, target);
|
|
2882
2919
|
if (typeof length !== "number") return { error: length.code };
|
|
2883
2920
|
const stride = typeCode === TypeCode.DYNAMIC_ARRAY ? (block.targetMeta & PolicyFormat.HINT_META_STRIDE_MASK) * 32 : 1;
|
|
@@ -2888,7 +2925,7 @@ function evalTarget(callData, target, block, opCode, operandData) {
|
|
|
2888
2925
|
value: BigInt(length)
|
|
2889
2926
|
};
|
|
2890
2927
|
}
|
|
2891
|
-
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");
|
|
2892
2929
|
const word = loadWord(callData, target);
|
|
2893
2930
|
if (!(word instanceof Uint8Array)) return { error: word.code };
|
|
2894
2931
|
const value = toBigInt(word, 0);
|
|
@@ -2896,6 +2933,18 @@ function evalTarget(callData, target, block, opCode, operandData) {
|
|
|
2896
2933
|
error: "NON_CANONICAL_VALUE",
|
|
2897
2934
|
value
|
|
2898
2935
|
};
|
|
2936
|
+
if ((opCode & ~Op.NOT) === Op.EQ_CTX) {
|
|
2937
|
+
const operand = resolveContextOperand(operandData, context);
|
|
2938
|
+
if (typeof operand !== "bigint") return {
|
|
2939
|
+
error: "MISSING_CONTEXT",
|
|
2940
|
+
ctxTypeCode: operand.ctxTypeCode
|
|
2941
|
+
};
|
|
2942
|
+
return {
|
|
2943
|
+
passed: value === operand !== ((opCode & Op.NOT) !== 0),
|
|
2944
|
+
value,
|
|
2945
|
+
ctxOperand: operand
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2899
2948
|
return {
|
|
2900
2949
|
passed: applyOperator(opCode, value, 32, operandData, typeCode),
|
|
2901
2950
|
value
|
|
@@ -2916,6 +2965,16 @@ function navigationViolation(frame, code, elementIndex) {
|
|
|
2916
2965
|
}
|
|
2917
2966
|
function targetViolation(frame, result, elementIndex) {
|
|
2918
2967
|
if ("error" in result) {
|
|
2968
|
+
if (result.error === "MISSING_CONTEXT") return {
|
|
2969
|
+
group: frame.group,
|
|
2970
|
+
rule: frame.rule,
|
|
2971
|
+
code: result.error,
|
|
2972
|
+
scope: Scope.CALLDATA,
|
|
2973
|
+
path: frame.path,
|
|
2974
|
+
opCode: frame.opCode,
|
|
2975
|
+
operandData: bytesToHex(frame.operandData),
|
|
2976
|
+
typeCode: result.ctxTypeCode
|
|
2977
|
+
};
|
|
2919
2978
|
if (result.error === "NON_CANONICAL_VALUE") return {
|
|
2920
2979
|
group: frame.group,
|
|
2921
2980
|
rule: frame.rule,
|
|
@@ -2941,15 +3000,16 @@ function targetViolation(frame, result, elementIndex) {
|
|
|
2941
3000
|
operandData: bytesToHex(frame.operandData),
|
|
2942
3001
|
typeCode: frame.typeCode,
|
|
2943
3002
|
resolvedValue: bigintToHex(result.value),
|
|
3003
|
+
...result.ctxOperand !== void 0 && { resolvedOperand: bigintToHex(result.ctxOperand) },
|
|
2944
3004
|
...elementIndex !== void 0 && { elementIndex }
|
|
2945
3005
|
};
|
|
2946
3006
|
}
|
|
2947
|
-
function evaluateCalldataRule(hint, callDataBytes, baseOffset, opCode, operandData, groupIndex, ruleIndex, pathHex) {
|
|
3007
|
+
function evaluateCalldataRule(hint, callDataBytes, baseOffset, opCode, operandData, groupIndex, ruleIndex, pathHex, context) {
|
|
2948
3008
|
const header = hint[0];
|
|
2949
3009
|
const hopCount = header & PolicyFormat.HINT_HOP_COUNT_MASK;
|
|
2950
3010
|
const kind = header >> PolicyFormat.HINT_KIND_SHIFT;
|
|
2951
3011
|
const hopsOffset = PolicyFormat.HINT_HEADER_SIZE;
|
|
2952
|
-
if (kind !== PolicyFormat.HINT_KIND_NONE) return evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, hopCount, opCode, operandData, groupIndex, ruleIndex, pathHex);
|
|
3012
|
+
if (kind !== PolicyFormat.HINT_KIND_NONE) return evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, hopCount, opCode, operandData, groupIndex, ruleIndex, pathHex, context);
|
|
2953
3013
|
const block = readTargetBlock(hint, hopsOffset + hopCount * PolicyFormat.HINT_HOP_SIZE);
|
|
2954
3014
|
const frame = {
|
|
2955
3015
|
group: groupIndex,
|
|
@@ -2961,9 +3021,9 @@ function evaluateCalldataRule(hint, callDataBytes, baseOffset, opCode, operandDa
|
|
|
2961
3021
|
};
|
|
2962
3022
|
const base = chainResolve(hint, callDataBytes, baseOffset, hopsOffset, hopCount);
|
|
2963
3023
|
if (typeof base !== "number") return navigationViolation(frame, base.code);
|
|
2964
|
-
return targetViolation(frame, evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData));
|
|
3024
|
+
return targetViolation(frame, evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, context));
|
|
2965
3025
|
}
|
|
2966
|
-
function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, hopCount, opCode, operandData, groupIndex, ruleIndex, pathHex) {
|
|
3026
|
+
function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, hopCount, opCode, operandData, groupIndex, ruleIndex, pathHex, context) {
|
|
2967
3027
|
const frameOffset = hopsOffset + hopCount * PolicyFormat.HINT_HOP_SIZE;
|
|
2968
3028
|
const suffixHeaderOffset = frameOffset + PolicyFormat.HINT_FRAME_PREFIX_SIZE;
|
|
2969
3029
|
const suffixHopCount = hint[suffixHeaderOffset] & PolicyFormat.HINT_HOP_COUNT_MASK;
|
|
@@ -2987,7 +3047,7 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
|
|
|
2987
3047
|
count = length;
|
|
2988
3048
|
elems += 32;
|
|
2989
3049
|
}
|
|
2990
|
-
if (count >
|
|
3050
|
+
if (count > PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH) return {
|
|
2991
3051
|
group: groupIndex,
|
|
2992
3052
|
rule: ruleIndex,
|
|
2993
3053
|
code: "QUANTIFIER_LIMIT_EXCEEDED",
|
|
@@ -3009,6 +3069,7 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
|
|
|
3009
3069
|
const meta = readU16(hint, frameOffset + PolicyFormat.HINT_FRAME_META_OFFSET);
|
|
3010
3070
|
const elemStride = (meta & PolicyFormat.HINT_META_STRIDE_MASK) * 32;
|
|
3011
3071
|
const elemIsDynamic = (meta & PolicyFormat.HINT_META_ELEM_DYNAMIC) !== 0;
|
|
3072
|
+
let ctxOperand;
|
|
3012
3073
|
for (let elemIndex = 0; elemIndex < count; elemIndex++) {
|
|
3013
3074
|
const slot = elems + elemIndex * elemStride;
|
|
3014
3075
|
let elem = slot;
|
|
@@ -3023,11 +3084,12 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
|
|
|
3023
3084
|
if (typeof chainedElem !== "number") return navigationViolation(frame, chainedElem.code, elemIndex);
|
|
3024
3085
|
base = chainedElem;
|
|
3025
3086
|
}
|
|
3026
|
-
const applied = evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData);
|
|
3087
|
+
const applied = evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, context);
|
|
3027
3088
|
if ("error" in applied) return targetViolation(frame, applied, elemIndex);
|
|
3028
3089
|
if (applied.passed) {
|
|
3029
3090
|
if (!isUniversal) return null;
|
|
3030
3091
|
} else if (isUniversal) return targetViolation(frame, applied, elemIndex);
|
|
3092
|
+
ctxOperand = applied.ctxOperand;
|
|
3031
3093
|
}
|
|
3032
3094
|
if (isUniversal) return null;
|
|
3033
3095
|
return {
|
|
@@ -3038,26 +3100,44 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
|
|
|
3038
3100
|
path: pathHex,
|
|
3039
3101
|
opCode,
|
|
3040
3102
|
operandData: bytesToHex(operandData),
|
|
3041
|
-
typeCode: block.typeCode
|
|
3103
|
+
typeCode: block.typeCode,
|
|
3104
|
+
...ctxOperand !== void 0 && { resolvedOperand: bigintToHex(ctxOperand) }
|
|
3042
3105
|
};
|
|
3043
3106
|
}
|
|
3044
3107
|
function evaluateContextRule(pathBytes, opCode, operandData, groupIndex, ruleIndex, pathHex, context) {
|
|
3045
3108
|
const propertyId = readU16(pathBytes, 0);
|
|
3046
3109
|
if (propertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID 0x${propertyId.toString(16).padStart(4, "0")}`);
|
|
3047
|
-
const
|
|
3048
|
-
const contextValue = context?.[
|
|
3110
|
+
const propertyInfo = lookupContextProperty(propertyId);
|
|
3111
|
+
const contextValue = context?.[propertyInfo.contextKey];
|
|
3049
3112
|
if (contextValue === void 0) return {
|
|
3050
3113
|
group: groupIndex,
|
|
3051
3114
|
rule: ruleIndex,
|
|
3052
3115
|
code: "MISSING_CONTEXT",
|
|
3053
3116
|
scope: Scope.CONTEXT,
|
|
3054
3117
|
path: pathHex,
|
|
3055
|
-
typeCode:
|
|
3118
|
+
typeCode: propertyInfo.typeCode
|
|
3056
3119
|
};
|
|
3057
3120
|
let value;
|
|
3058
3121
|
if (typeof contextValue === "string") value = addressToBigInt(contextValue);
|
|
3059
3122
|
else value = contextValue;
|
|
3060
|
-
|
|
3123
|
+
let result;
|
|
3124
|
+
let ctxOperand;
|
|
3125
|
+
if ((opCode & ~Op.NOT) === Op.EQ_CTX) {
|
|
3126
|
+
const operand = resolveContextOperand(operandData, context);
|
|
3127
|
+
if (typeof operand !== "bigint") return {
|
|
3128
|
+
group: groupIndex,
|
|
3129
|
+
rule: ruleIndex,
|
|
3130
|
+
code: "MISSING_CONTEXT",
|
|
3131
|
+
scope: Scope.CONTEXT,
|
|
3132
|
+
path: pathHex,
|
|
3133
|
+
opCode,
|
|
3134
|
+
operandData: bytesToHex(operandData),
|
|
3135
|
+
typeCode: operand.ctxTypeCode
|
|
3136
|
+
};
|
|
3137
|
+
result = value === operand !== ((opCode & Op.NOT) !== 0);
|
|
3138
|
+
ctxOperand = operand;
|
|
3139
|
+
} else result = applyOperator(opCode, value, 32, operandData, propertyInfo.typeCode);
|
|
3140
|
+
if (!result) return {
|
|
3061
3141
|
group: groupIndex,
|
|
3062
3142
|
rule: ruleIndex,
|
|
3063
3143
|
code: "VALUE_MISMATCH",
|
|
@@ -3065,8 +3145,9 @@ function evaluateContextRule(pathBytes, opCode, operandData, groupIndex, ruleInd
|
|
|
3065
3145
|
path: pathHex,
|
|
3066
3146
|
opCode,
|
|
3067
3147
|
operandData: bytesToHex(operandData),
|
|
3068
|
-
typeCode:
|
|
3069
|
-
resolvedValue: bigintToHex(value)
|
|
3148
|
+
typeCode: propertyInfo.typeCode,
|
|
3149
|
+
resolvedValue: bigintToHex(value),
|
|
3150
|
+
...ctxOperand !== void 0 && { resolvedOperand: bigintToHex(ctxOperand) }
|
|
3070
3151
|
};
|
|
3071
3152
|
return null;
|
|
3072
3153
|
}
|
|
@@ -3075,6 +3156,6 @@ const PolicyEnforcer = {
|
|
|
3075
3156
|
enforce
|
|
3076
3157
|
};
|
|
3077
3158
|
//#endregion
|
|
3078
|
-
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 };
|
|
3079
3160
|
|
|
3080
3161
|
//# sourceMappingURL=index.js.map
|