@callcium/sdk 0.10.0 → 0.11.1

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.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { keccak_256 } from "@noble/hashes/sha3.js";
2
+ import { utf8ToBytes } from "@noble/hashes/utils.js";
2
3
  //#region src/errors.ts
3
4
  var CallciumError = class extends Error {
4
5
  code;
@@ -8,6 +9,7 @@ var CallciumError = class extends Error {
8
9
  super(`${prefix}${message}`);
9
10
  this.name = "CallciumError";
10
11
  this.code = code;
12
+ if (offset !== void 0) this.offset = offset;
11
13
  }
12
14
  };
13
15
  var ValidationError = class extends CallciumError {
@@ -115,6 +117,11 @@ const DescriptorFormat = {
115
117
  MAX_NESTING_DEPTH: 64
116
118
  };
117
119
  const RULE_DATALENGTH_SIZE = 2;
120
+ const PATH_STEP_SIZE = 2;
121
+ const RULE_FIXED_OVERHEAD = 7;
122
+ function fieldMax(byteWidth) {
123
+ return 256 ** byteWidth - 1;
124
+ }
118
125
  const PolicyFormat = {
119
126
  VERSION: 2,
120
127
  VERSION_MASK: 15,
@@ -134,11 +141,12 @@ const PolicyFormat = {
134
141
  RULE_SCOPE_OFFSET: 2,
135
142
  RULE_DEPTH_OFFSET: 3,
136
143
  RULE_PATH_OFFSET: 4,
137
- PATH_STEP_SIZE: 2,
144
+ PATH_STEP_SIZE,
145
+ PATH_STEP_MAX: fieldMax(PATH_STEP_SIZE),
138
146
  RULE_OPCODE_SIZE: 1,
139
147
  RULE_DATALENGTH_SIZE,
140
- RULE_FIXED_OVERHEAD: 7,
141
- RULE_MIN_SIZE: 9,
148
+ RULE_FIXED_OVERHEAD,
149
+ RULE_MIN_SIZE: RULE_FIXED_OVERHEAD + PATH_STEP_SIZE,
142
150
  HINT_HEADER_SIZE: 1,
143
151
  HINT_HOP_SIZE: 8,
144
152
  HINT_HOP_INDEX_OFFSET: 4,
@@ -164,10 +172,14 @@ const PolicyFormat = {
164
172
  HINT_META_STRIDE_MASK: 4095,
165
173
  MAX_PATH_DEPTH: 32,
166
174
  MAX_QUANTIFIED_ARRAY_LENGTH: 256,
167
- MAX_SET_MEMBERS: Math.floor((256 ** RULE_DATALENGTH_SIZE - 1) / 32)
175
+ MAX_SET_MEMBERS: Math.floor(fieldMax(RULE_DATALENGTH_SIZE) / 32)
168
176
  };
177
+ function formatCode(code, byteWidth) {
178
+ if (!Number.isInteger(code) || code < 0 || code > fieldMax(byteWidth)) return String(code);
179
+ return `0x${code.toString(16).padStart(byteWidth * 2, "0")}`;
180
+ }
169
181
  function buildCodeMap(table) {
170
- return Object.fromEntries(table.map((e) => [e.key, e.code]));
182
+ return Object.fromEntries(table.map((entry) => [entry.key, entry.code]));
171
183
  }
172
184
  const SCOPE_TABLE = [{
173
185
  key: "CONTEXT",
@@ -179,7 +191,7 @@ const SCOPE_TABLE = [{
179
191
  label: "calldata"
180
192
  }];
181
193
  const Scope = buildCodeMap(SCOPE_TABLE);
182
- const scopeByCode = new Map(SCOPE_TABLE.map((e) => [e.code, { label: e.label }]));
194
+ const scopeByCode = new Map(SCOPE_TABLE.map((entry) => [entry.code, { label: entry.label }]));
183
195
  function lookupScope(code) {
184
196
  const info = scopeByCode.get(code);
185
197
  if (!info) throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${code}`);
@@ -260,15 +272,18 @@ const CTX_PROPERTY_TABLE = [
260
272
  }
261
273
  ];
262
274
  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, {
265
- label: e.label,
266
- contextKey: e.contextKey,
267
- typeCode: e.typeCode
275
+ const MAX_CONTEXT_PROPERTY_ID = Math.max(...CTX_PROPERTY_TABLE.map((entry) => entry.code));
276
+ const ctxPropertyByCode = new Map(CTX_PROPERTY_TABLE.map((entry) => [entry.code, {
277
+ label: entry.label,
278
+ contextKey: entry.contextKey,
279
+ typeCode: entry.typeCode
268
280
  }]));
281
+ function findContextProperty(code) {
282
+ return ctxPropertyByCode.get(code);
283
+ }
269
284
  function lookupContextProperty(code) {
270
285
  const info = ctxPropertyByCode.get(code);
271
- if (!info) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ${code}`);
286
+ if (!info) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID ${formatCode(code, PATH_STEP_SIZE)}`);
272
287
  return info;
273
288
  }
274
289
  const OP_TABLE = [
@@ -379,14 +394,14 @@ const Op = {
379
394
  ...buildCodeMap(OP_TABLE),
380
395
  NOT: 128
381
396
  };
382
- const opByCode = new Map(OP_TABLE.map((e) => [e.code, {
383
- label: e.label,
384
- operands: e.operands
397
+ const opByCode = new Map(OP_TABLE.map((entry) => [entry.code, {
398
+ label: entry.label,
399
+ operands: entry.operands
385
400
  }]));
386
401
  function lookupOp(code) {
387
402
  const base = code & ~Op.NOT;
388
403
  const info = opByCode.get(base);
389
- if (!info) throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}`);
404
+ if (!info) throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code ${formatCode(base, PolicyFormat.RULE_OPCODE_SIZE)}`);
390
405
  return info;
391
406
  }
392
407
  function operandsOf(opBase) {
@@ -404,18 +419,22 @@ const QUANTIFIER_TABLE = [{
404
419
  label: "any"
405
420
  }];
406
421
  const Quantifier = buildCodeMap(QUANTIFIER_TABLE);
407
- const quantifierByCode = new Map(QUANTIFIER_TABLE.map((e) => [e.code, { label: e.label }]));
422
+ const quantifierByCode = new Map(QUANTIFIER_TABLE.map((entry) => [entry.code, { label: entry.label }]));
408
423
  function isQuantifier(step) {
409
424
  return step >= Quantifier.ANY;
410
425
  }
411
426
  function lookupQuantifier(code) {
412
427
  const info = quantifierByCode.get(code);
413
- if (!info) throw new CallciumError("UNKNOWN_QUANTIFIER", `Unknown quantifier step 0x${code.toString(16)}`);
428
+ if (!info) throw new CallciumError("UNKNOWN_QUANTIFIER", `Unknown quantifier step ${formatCode(code, PolicyFormat.PATH_STEP_SIZE)}`);
414
429
  return info;
415
430
  }
416
431
  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]);
432
+ const buffer = new Uint8Array(steps.length * PolicyFormat.PATH_STEP_SIZE);
433
+ for (let i = 0; i < steps.length; i++) {
434
+ const step = steps[i];
435
+ if (!Number.isInteger(step) || step < 0 || step > PolicyFormat.PATH_STEP_MAX) throw new CallciumError("PATH_STEP_OVERFLOW", `Path step ${step} is outside the path step field`);
436
+ writeBE16(buffer, i * PolicyFormat.PATH_STEP_SIZE, step);
437
+ }
419
438
  return bytesToHex(buffer);
420
439
  }
421
440
  function parsePathSteps(path) {
@@ -426,221 +445,181 @@ function parsePathSteps(path) {
426
445
  return steps;
427
446
  }
428
447
  //#endregion
429
- //#region src/descriptor.ts
430
- function nodeLength(desc, offset) {
431
- if (desc[offset] < TypeCode.STATIC_ARRAY) return DescriptorFormat.TYPECODE_SIZE;
432
- return readU24(desc, offset + 1) & DescriptorFormat.META_NODE_LENGTH_MASK;
433
- }
434
- function tupleFieldOffset(desc, tupleOffset, fieldIndex) {
435
- let cursor = tupleOffset + DescriptorFormat.TUPLE_HEADER_SIZE;
436
- for (let i = 0; i < fieldIndex; i++) cursor += nodeLength(desc, cursor);
437
- return cursor;
448
+ //#region src/constraint.ts
449
+ const OPERAND_MIN = -(2n ** 255n);
450
+ const OPERAND_MAX = 2n ** 256n - 1n;
451
+ function checkOperandRange(value) {
452
+ if (value < OPERAND_MIN || value > OPERAND_MAX) throw new CallciumError("OPERAND_OVERFLOW", `Operand ${value} is outside the range a 32-byte word represents`);
453
+ return value;
438
454
  }
439
- function paramCount(desc) {
440
- return desc[1];
455
+ function addressBody(value) {
456
+ return toAddress(value).slice(2);
441
457
  }
442
- function paramOffset(desc, index) {
443
- const count = desc[1];
444
- if (index >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${index} out of range (paramCount=${count})`);
445
- let cursor = DescriptorFormat.HEADER_SIZE;
446
- for (let i = 0; i < index; i++) cursor += nodeLength(desc, cursor);
447
- return cursor;
458
+ function encodeWord(value) {
459
+ const word = new Uint8Array(32);
460
+ if (typeof value === "boolean") {
461
+ word[31] = value ? 1 : 0;
462
+ return word;
463
+ }
464
+ if (typeof value === "string") {
465
+ word.set(hexToBytes(addressBody(value)), 12);
466
+ return word;
467
+ }
468
+ let bigValue = checkOperandRange(value);
469
+ for (let i = 31; i >= 0; i--) {
470
+ word[i] = Number(bigValue & 255n);
471
+ bigValue >>= 8n;
472
+ }
473
+ return word;
448
474
  }
449
- function inspect$1(desc, offset) {
450
- const typeCode = desc[offset];
451
- let staticWords;
452
- if (typeCode < TypeCode.STATIC_ARRAY) staticWords = typeCode === TypeCode.BYTES || typeCode === TypeCode.STRING ? 0 : 1;
453
- else staticWords = readU24(desc, offset + 1) >> DescriptorFormat.META_STATIC_WORDS_SHIFT;
454
- return {
455
- typeCode,
456
- isDynamic: staticWords === 0,
457
- staticSize: staticWords * 32
458
- };
475
+ function singleOp(opCode, value) {
476
+ const buffer = new Uint8Array(33);
477
+ buffer[0] = opCode;
478
+ buffer.set(encodeWord(value), 1);
479
+ return bytesToHex(buffer);
459
480
  }
460
- function typeAt(desc, steps) {
461
- return walkPath(desc, steps).typeInfo;
481
+ function rangeOp(opCode, min, max) {
482
+ const minValue = checkOperandRange(min);
483
+ const maxValue = checkOperandRange(max);
484
+ if (minValue > maxValue) throw new CallciumError("INVALID_RANGE", `Range min (${minValue}) must not exceed max (${maxValue})`);
485
+ const buffer = new Uint8Array(65);
486
+ buffer[0] = opCode;
487
+ buffer.set(encodeWord(minValue), 1);
488
+ buffer.set(encodeWord(maxValue), 33);
489
+ return bytesToHex(buffer);
462
490
  }
463
- function walkPath(desc, steps) {
464
- if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
465
- const paramIndex = steps[0];
466
- const count = desc[1];
467
- if (paramIndex >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${paramIndex} out of range (paramCount=${count})`);
468
- let cursor = paramOffset(desc, paramIndex);
469
- let quantifiedStaticLength = 0;
470
- for (let stepIndex = 1; stepIndex < steps.length; stepIndex++) {
471
- const typeCode = desc[cursor];
472
- const step = steps[stepIndex];
473
- if (typeCode === TypeCode.TUPLE) {
474
- const fields = tupleFieldCount(desc, cursor);
475
- if (step >= fields) throw new CallciumError("TUPLE_FIELD_OUT_OF_BOUNDS", `Tuple field index ${step} out of range (tuple has ${fields} fields)`);
476
- cursor = tupleFieldOffset(desc, cursor, step);
477
- } else if (typeCode === TypeCode.STATIC_ARRAY || typeCode === TypeCode.DYNAMIC_ARRAY) {
478
- if (typeCode === TypeCode.STATIC_ARRAY) if (isQuantifier(step)) quantifiedStaticLength = staticArrayLength(desc, cursor);
479
- else {
480
- const length = staticArrayLength(desc, cursor);
481
- if (step >= length) throw new CallciumError("STATIC_ARRAY_INDEX_OUT_OF_BOUNDS", `Array index ${step} out of range (length=${length})`);
482
- }
483
- cursor = arrayElementOffset(cursor);
484
- } else throw new CallciumError("NOT_COMPOSITE", `Cannot descend into elementary type at offset ${cursor}`);
485
- }
486
- return {
487
- typeInfo: inspect$1(desc, cursor),
488
- quantifiedStaticLength
489
- };
491
+ function contextOperand(contextPropertyId) {
492
+ lookupContextProperty(contextPropertyId);
493
+ return BigInt(contextPropertyId);
490
494
  }
491
- function tupleFieldCount(desc, offset) {
492
- return readU16(desc, offset + 4);
495
+ function setOp(opCode, values) {
496
+ const words = values.map((value) => {
497
+ if (typeof value === "bigint") return BigInt.asUintN(256, checkOperandRange(value));
498
+ if (typeof value === "boolean") return value ? 1n : 0n;
499
+ return BigInt("0x" + addressBody(value));
500
+ });
501
+ const deduped = [...new Set(words)].toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0);
502
+ if (deduped.length === 0) throw new CallciumError("EMPTY_SET", "Set must contain at least one value");
503
+ if (deduped.length > PolicyFormat.MAX_SET_MEMBERS) throw new CallciumError("SET_TOO_LARGE", `Set must contain at most ${PolicyFormat.MAX_SET_MEMBERS} values`);
504
+ const buffer = new Uint8Array(1 + deduped.length * 32);
505
+ buffer[0] = opCode;
506
+ for (let i = 0; i < deduped.length; i++) buffer.set(encodeWord(deduped[i]), 1 + i * 32);
507
+ return bytesToHex(buffer);
493
508
  }
494
- function staticArrayLength(desc, offset) {
495
- const elemOffset = arrayElementOffset(offset);
496
- return readU16(desc, elemOffset + nodeLength(desc, elemOffset));
509
+ function readOperandExtremes(constraint) {
510
+ return "operandExtremes" in constraint ? constraint.operandExtremes : void 0;
497
511
  }
498
- function arrayElementOffset(offset) {
499
- return offset + DescriptorFormat.ARRAY_HEADER_SIZE;
512
+ var ConstraintBuilder = class {
513
+ scope;
514
+ path;
515
+ operators;
516
+ operandExtremes;
517
+ constructor(scope, path) {
518
+ this.scope = scope;
519
+ this.path = path;
520
+ this.operators = [];
521
+ this.operandExtremes = {
522
+ leastNegative: 0n,
523
+ greatest: 0n
524
+ };
525
+ }
526
+ push(opHex, values = []) {
527
+ for (const value of values) {
528
+ if (typeof value !== "bigint") continue;
529
+ if (value < this.operandExtremes.leastNegative) this.operandExtremes.leastNegative = value;
530
+ if (value > this.operandExtremes.greatest) this.operandExtremes.greatest = value;
531
+ }
532
+ this.operators.push(opHex);
533
+ return this;
534
+ }
535
+ eq(value) {
536
+ return this.push(singleOp(Op.EQ, value), [value]);
537
+ }
538
+ neq(value) {
539
+ return this.push(singleOp(Op.EQ | Op.NOT, value), [value]);
540
+ }
541
+ eqCtx(contextPropertyId) {
542
+ return this.push(singleOp(Op.EQ_CTX, contextOperand(contextPropertyId)));
543
+ }
544
+ neqCtx(contextPropertyId) {
545
+ return this.push(singleOp(Op.EQ_CTX | Op.NOT, contextOperand(contextPropertyId)));
546
+ }
547
+ gt(bound) {
548
+ return this.push(singleOp(Op.GT, bound), [bound]);
549
+ }
550
+ lt(bound) {
551
+ return this.push(singleOp(Op.LT, bound), [bound]);
552
+ }
553
+ gte(bound) {
554
+ return this.push(singleOp(Op.GTE, bound), [bound]);
555
+ }
556
+ lte(bound) {
557
+ return this.push(singleOp(Op.LTE, bound), [bound]);
558
+ }
559
+ between(min, max) {
560
+ return this.push(rangeOp(Op.BETWEEN, min, max), [min, max]);
561
+ }
562
+ isIn(values) {
563
+ return this.push(setOp(Op.IN, values), values);
564
+ }
565
+ notIn(values) {
566
+ return this.push(setOp(Op.IN | Op.NOT, values), values);
567
+ }
568
+ bitmaskAll(mask) {
569
+ return this.push(singleOp(Op.BITMASK_ALL, mask));
570
+ }
571
+ bitmaskAny(mask) {
572
+ return this.push(singleOp(Op.BITMASK_ANY, mask));
573
+ }
574
+ bitmaskNone(mask) {
575
+ return this.push(singleOp(Op.BITMASK_NONE, mask));
576
+ }
577
+ lengthEq(length) {
578
+ return this.push(singleOp(Op.LENGTH_EQ, length));
579
+ }
580
+ lengthGt(length) {
581
+ return this.push(singleOp(Op.LENGTH_GT, length));
582
+ }
583
+ lengthLt(length) {
584
+ return this.push(singleOp(Op.LENGTH_LT, length));
585
+ }
586
+ lengthGte(length) {
587
+ return this.push(singleOp(Op.LENGTH_GTE, length));
588
+ }
589
+ lengthLte(length) {
590
+ return this.push(singleOp(Op.LENGTH_LTE, length));
591
+ }
592
+ lengthBetween(min, max) {
593
+ return this.push(rangeOp(Op.LENGTH_BETWEEN, min, max));
594
+ }
595
+ };
596
+ function arg(...steps) {
597
+ return new ConstraintBuilder(Scope.CALLDATA, encodePath(steps));
500
598
  }
501
- function uncompilablePath(stepIndex) {
502
- return new CallciumError("UNCOMPILABLE_PATH", `Path step ${stepIndex} does not resolve to a hop-chain target`);
599
+ function msgSender() {
600
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.MSG_SENDER]));
503
601
  }
504
- function headSpan(desc, offset, count) {
505
- let span = 0;
506
- let nodeOffset = offset;
507
- for (let i = 0; i < count; i++) {
508
- const node = inspect$1(desc, nodeOffset);
509
- span += node.isDynamic ? 32 : node.staticSize;
510
- nodeOffset += nodeLength(desc, nodeOffset);
511
- }
512
- return {
513
- span,
514
- nodeOffset
515
- };
602
+ function msgValue() {
603
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.MSG_VALUE]));
516
604
  }
517
- function arrayMeta(desc, arrayOffset, code) {
518
- const element = inspect$1(desc, arrayElementOffset(arrayOffset));
519
- let meta = element.isDynamic ? 1 : element.staticSize >> 5;
520
- if (element.isDynamic) meta |= PolicyFormat.HINT_META_ELEM_DYNAMIC;
521
- if (code === TypeCode.DYNAMIC_ARRAY) meta |= PolicyFormat.HINT_META_DYNAMIC_ARRAY;
522
- return {
523
- meta,
524
- elemIsDynamic: element.isDynamic
525
- };
605
+ function blockTimestamp() {
606
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.BLOCK_TIMESTAMP]));
526
607
  }
527
- function enterNode(walk, needed) {
528
- if (!needed) return;
529
- walk.chain.push([
530
- walk.delta,
531
- PolicyFormat.HINT_NO_INDEX,
532
- 0
533
- ]);
534
- walk.delta = 0;
608
+ function blockNumber() {
609
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.BLOCK_NUMBER]));
535
610
  }
536
- function walkStep(desc, walk, descOffset, step, stepIndex) {
537
- const node = inspect$1(desc, descOffset);
538
- if (node.typeCode === TypeCode.TUPLE) {
539
- if (step >= tupleFieldCount(desc, descOffset)) throw uncompilablePath(stepIndex);
540
- enterNode(walk, node.isDynamic && !walk.entered);
541
- const field = headSpan(desc, descOffset + DescriptorFormat.TUPLE_HEADER_SIZE, step);
542
- walk.delta += field.span;
543
- walk.entered = false;
544
- return field.nodeOffset;
545
- }
546
- if (node.typeCode !== TypeCode.STATIC_ARRAY && node.typeCode !== TypeCode.DYNAMIC_ARRAY) throw uncompilablePath(stepIndex);
547
- if (node.typeCode === TypeCode.STATIC_ARRAY && !isQuantifier(step) && step >= staticArrayLength(desc, descOffset)) throw uncompilablePath(stepIndex);
548
- const { meta, elemIsDynamic } = arrayMeta(desc, descOffset, node.typeCode);
549
- if (isQuantifier(step)) {
550
- if (walk.kind !== PolicyFormat.HINT_KIND_NONE) throw uncompilablePath(stepIndex);
551
- enterNode(walk, node.isDynamic && !walk.entered);
552
- const count = node.typeCode === TypeCode.DYNAMIC_ARRAY ? 0 : staticArrayLength(desc, descOffset);
553
- walk.frame = {
554
- arrayDelta: walk.delta,
555
- count,
556
- meta
557
- };
558
- walk.kind = step === Quantifier.ALL ? PolicyFormat.HINT_KIND_ALL : PolicyFormat.HINT_KIND_ANY;
559
- walk.mainHops = walk.chain;
560
- walk.chain = [];
561
- walk.delta = 0;
562
- } else if (node.isDynamic) {
563
- enterNode(walk, !walk.entered);
564
- walk.chain.push([
565
- 0,
566
- step,
567
- meta
568
- ]);
569
- walk.delta = 0;
570
- } else walk.delta += step * (meta & PolicyFormat.HINT_META_STRIDE_MASK) * 32;
571
- walk.entered = elemIsDynamic;
572
- return arrayElementOffset(descOffset);
611
+ function chainId() {
612
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.CHAIN_ID]));
573
613
  }
574
- function writeHops(out, offset, hops) {
575
- for (const [delta, index, meta] of hops) {
576
- writeBE32(out, offset, delta);
577
- writeBE16(out, offset + PolicyFormat.HINT_HOP_INDEX_OFFSET, index);
578
- writeBE16(out, offset + PolicyFormat.HINT_HOP_META_OFFSET, meta);
579
- offset += PolicyFormat.HINT_HOP_SIZE;
580
- }
581
- return offset;
614
+ function txOrigin() {
615
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.TX_ORIGIN]));
582
616
  }
583
- function encodeHint(walk, targetMeta, typeCode, depth) {
584
- let suffixHops = [];
585
- if (walk.kind === PolicyFormat.HINT_KIND_NONE) walk.mainHops = walk.chain;
586
- else suffixHops = walk.chain;
587
- const mainHopCount = walk.mainHops.length;
588
- const suffixHopCount = suffixHops.length;
589
- if (mainHopCount > PolicyFormat.HINT_HOP_COUNT_MASK || suffixHopCount > PolicyFormat.HINT_HOP_COUNT_MASK) throw uncompilablePath(depth);
590
- const quantified = walk.kind !== PolicyFormat.HINT_KIND_NONE;
591
- const frameSize = quantified ? PolicyFormat.HINT_FRAME_PREFIX_SIZE + PolicyFormat.HINT_HEADER_SIZE + suffixHopCount * PolicyFormat.HINT_HOP_SIZE : 0;
592
- const out = new Uint8Array(PolicyFormat.HINT_HEADER_SIZE + mainHopCount * PolicyFormat.HINT_HOP_SIZE + frameSize + PolicyFormat.HINT_TARGET_SIZE);
593
- let offset = 0;
594
- out[offset] = walk.kind << PolicyFormat.HINT_KIND_SHIFT | mainHopCount;
595
- offset = writeHops(out, offset + PolicyFormat.HINT_HEADER_SIZE, walk.mainHops);
596
- if (quantified) {
597
- writeBE32(out, offset, walk.frame.arrayDelta);
598
- writeBE16(out, offset + PolicyFormat.HINT_FRAME_COUNT_OFFSET, walk.frame.count);
599
- writeBE16(out, offset + PolicyFormat.HINT_FRAME_META_OFFSET, walk.frame.meta);
600
- out[offset + PolicyFormat.HINT_FRAME_PREFIX_SIZE] = suffixHopCount;
601
- offset = writeHops(out, offset + PolicyFormat.HINT_FRAME_PREFIX_SIZE + PolicyFormat.HINT_HEADER_SIZE, suffixHops);
602
- }
603
- writeBE32(out, offset, walk.delta);
604
- writeBE16(out, offset + PolicyFormat.HINT_TARGET_META_OFFSET, targetMeta);
605
- out[offset + PolicyFormat.HINT_TARGET_TYPECODE_OFFSET] = typeCode;
606
- return out;
617
+ function baseFee() {
618
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.BASE_FEE]));
607
619
  }
608
- function compileHint(desc, steps) {
609
- if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
610
- const argIndex = steps[0];
611
- if (argIndex >= paramCount(desc)) throw uncompilablePath(0);
612
- const arg = headSpan(desc, DescriptorFormat.HEADER_SIZE, argIndex);
613
- const walk = {
614
- chain: [],
615
- mainHops: [],
616
- delta: arg.span,
617
- frame: {
618
- arrayDelta: 0,
619
- count: 0,
620
- meta: 0
621
- },
622
- kind: PolicyFormat.HINT_KIND_NONE,
623
- entered: false
624
- };
625
- let descOffset = arg.nodeOffset;
626
- for (let stepIndex = 1; stepIndex < steps.length; stepIndex++) descOffset = walkStep(desc, walk, descOffset, steps[stepIndex], stepIndex);
627
- const target = inspect$1(desc, descOffset);
628
- enterNode(walk, target.isDynamic && !walk.entered);
629
- return encodeHint(walk, target.typeCode === TypeCode.DYNAMIC_ARRAY ? arrayMeta(desc, descOffset, target.typeCode).meta : 0, target.typeCode, steps.length);
620
+ function gasPrice() {
621
+ return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.GAS_PRICE]));
630
622
  }
631
- const Descriptor = {
632
- paramCount,
633
- paramOffset,
634
- inspect: inspect$1,
635
- typeAt,
636
- tupleFieldOffset,
637
- tupleFieldCount,
638
- staticArrayLength,
639
- nodeLength,
640
- arrayElementOffset,
641
- walkPath,
642
- compileHint
643
- };
644
623
  //#endregion
645
624
  //#region src/operators.ts
646
625
  function toBigInt(bytes, offset = 0) {
@@ -712,7 +691,7 @@ function isIn(value, data) {
712
691
  function applyOperator(opCode, value, valueLength, operandData, typeCode) {
713
692
  let base = opCode & ~Op.NOT;
714
693
  if (isLengthOp(base)) {
715
- value = BigInt(valueLength);
694
+ value = valueLength;
716
695
  typeCode = TypeCode.UINT_MAX;
717
696
  base = lengthToValueOp(base);
718
697
  }
@@ -757,7 +736,7 @@ function applyOperator(opCode, value, valueLength, operandData, typeCode) {
757
736
  result = (value & mask) === 0n;
758
737
  break;
759
738
  }
760
- default: throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code 0x${base.toString(16).padStart(2, "0")}`);
739
+ default: throw new CallciumError("UNKNOWN_OPERATOR", `Unknown operator code ${formatCode(base, PolicyFormat.RULE_OPCODE_SIZE)}`);
761
740
  }
762
741
  return (opCode & Op.NOT) !== 0 ? !result : result;
763
742
  }
@@ -768,8 +747,13 @@ function isValidOperatorData(opBase, dataLength) {
768
747
  if (operands === "variadic") return dataLength > 0 && dataLength % 32 === 0;
769
748
  return false;
770
749
  }
750
+ function toOperatorBytes(operator) {
751
+ const bytes = hexToBytes(operator);
752
+ if (bytes.length < 1) throw new CallciumError("INVALID_OPERATOR_BYTES", "Operator must have at least one byte (opcode)");
753
+ return bytes;
754
+ }
771
755
  function unknownTypeCode(code) {
772
- throw new CallciumError("UNKNOWN_TYPE_CODE", `Unknown type code 0x${code.toString(16).padStart(2, "0")}`);
756
+ throw new CallciumError("UNKNOWN_TYPE_CODE", `Unknown type code ${formatCode(code, DescriptorFormat.TYPECODE_SIZE)}`);
773
757
  }
774
758
  const ELEMENTARY = {
775
759
  typeClass: "elementary",
@@ -805,6 +789,13 @@ function classifyTypeCode(code) {
805
789
  if (code === TypeCode.TUPLE) return TUPLE;
806
790
  unknownTypeCode(code);
807
791
  }
792
+ function isElementary(code) {
793
+ try {
794
+ return classifyTypeCode(code).typeClass === "elementary";
795
+ } catch {
796
+ return false;
797
+ }
798
+ }
808
799
  function isAddressableTarget(code) {
809
800
  let info;
810
801
  try {
@@ -828,12 +819,243 @@ function typeCodeLabel(code) {
828
819
  if (code === TypeCode.TUPLE) return "tuple";
829
820
  return `0x${code.toString(16).padStart(2, "0")}`;
830
821
  }
831
- function lookupTypeCode(code) {
832
- return {
833
- label: typeCodeLabel(code),
834
- ...classifyTypeCode(code)
822
+ function lookupTypeCode(code) {
823
+ return {
824
+ label: typeCodeLabel(code),
825
+ ...classifyTypeCode(code)
826
+ };
827
+ }
828
+ //#endregion
829
+ //#region src/descriptor.ts
830
+ function readNode(desc, offset) {
831
+ if (offset >= desc.length) throw new CallciumError("UNEXPECTED_END", "Unexpected end of descriptor", offset);
832
+ const typeCode = desc[offset];
833
+ const info = classifyTypeCode(typeCode);
834
+ if (info.typeClass === "elementary") return {
835
+ typeCode,
836
+ staticWords: info.isDynamic ? 0 : 1,
837
+ nodeLength: DescriptorFormat.TYPECODE_SIZE
838
+ };
839
+ if (offset + DescriptorFormat.TYPECODE_SIZE + DescriptorFormat.COMPOSITE_META_SIZE > desc.length) throw new CallciumError("UNEXPECTED_END", "Incomplete composite metadata", offset);
840
+ const meta = readU24(desc, offset + DescriptorFormat.TYPECODE_SIZE);
841
+ const length = meta & DescriptorFormat.META_NODE_LENGTH_MASK;
842
+ const minHeader = info.typeClass === "tuple" ? DescriptorFormat.TUPLE_HEADER_SIZE : DescriptorFormat.ARRAY_HEADER_SIZE;
843
+ if (length < minHeader) throw new CallciumError("NODE_LENGTH_TOO_SMALL", `Composite node length ${length} is smaller than minimum header ${minHeader}`, offset);
844
+ if (offset + length > desc.length) throw new CallciumError("NODE_OVERFLOW", "Composite node extends beyond descriptor", offset);
845
+ return {
846
+ typeCode,
847
+ staticWords: meta >> DescriptorFormat.META_STATIC_WORDS_SHIFT,
848
+ nodeLength: length
849
+ };
850
+ }
851
+ function nodeLength(desc, offset) {
852
+ return readNode(desc, offset).nodeLength;
853
+ }
854
+ function tupleFieldOffset(desc, tupleOffset, fieldIndex) {
855
+ let cursor = tupleOffset + DescriptorFormat.TUPLE_HEADER_SIZE;
856
+ for (let i = 0; i < fieldIndex; i++) cursor += nodeLength(desc, cursor);
857
+ return cursor;
858
+ }
859
+ function paramCount(desc) {
860
+ if (desc.length < DescriptorFormat.HEADER_SIZE) throw new CallciumError("MALFORMED_HEADER", "Descriptor too short for header");
861
+ return desc[1];
862
+ }
863
+ function paramOffset(desc, index) {
864
+ const count = paramCount(desc);
865
+ if (index >= count) throw new CallciumError("PARAM_INDEX_OUT_OF_BOUNDS", `Param index ${index} out of range (paramCount=${count})`);
866
+ let cursor = DescriptorFormat.HEADER_SIZE;
867
+ for (let i = 0; i < index; i++) cursor += nodeLength(desc, cursor);
868
+ return cursor;
869
+ }
870
+ function inspect$1(desc, offset) {
871
+ const { typeCode, staticWords } = readNode(desc, offset);
872
+ return {
873
+ typeCode,
874
+ isDynamic: staticWords === 0,
875
+ staticSize: staticWords * 32
876
+ };
877
+ }
878
+ function typeAt(desc, steps) {
879
+ return walkPath(desc, steps).typeInfo;
880
+ }
881
+ function walkPath(desc, steps) {
882
+ if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
883
+ let cursor = paramOffset(desc, steps[0]);
884
+ let quantifiedStaticLength = 0;
885
+ for (let stepIndex = 1; stepIndex < steps.length; stepIndex++) {
886
+ const typeCode = desc[cursor];
887
+ const step = steps[stepIndex];
888
+ if (typeCode === TypeCode.TUPLE) {
889
+ const fields = tupleFieldCount(desc, cursor);
890
+ if (step >= fields) throw new CallciumError("TUPLE_FIELD_OUT_OF_BOUNDS", `Tuple field index ${step} out of range (tuple has ${fields} fields)`);
891
+ cursor = tupleFieldOffset(desc, cursor, step);
892
+ } else if (typeCode === TypeCode.STATIC_ARRAY || typeCode === TypeCode.DYNAMIC_ARRAY) {
893
+ if (typeCode === TypeCode.STATIC_ARRAY) if (isQuantifier(step)) quantifiedStaticLength = staticArrayLength(desc, cursor);
894
+ else {
895
+ const length = staticArrayLength(desc, cursor);
896
+ if (step >= length) throw new CallciumError("STATIC_ARRAY_INDEX_OUT_OF_BOUNDS", `Array index ${step} out of range (length=${length})`);
897
+ }
898
+ cursor = arrayElementOffset(cursor);
899
+ } else throw new CallciumError("NOT_COMPOSITE", `Cannot descend into elementary type at offset ${cursor}`);
900
+ }
901
+ return {
902
+ typeInfo: inspect$1(desc, cursor),
903
+ quantifiedStaticLength
904
+ };
905
+ }
906
+ function tupleFieldCount(desc, offset) {
907
+ return readU16(desc, offset + 4);
908
+ }
909
+ function staticArrayLength(desc, offset) {
910
+ const elemOffset = arrayElementOffset(offset);
911
+ return readU16(desc, elemOffset + nodeLength(desc, elemOffset));
912
+ }
913
+ function arrayElementOffset(offset) {
914
+ return offset + DescriptorFormat.ARRAY_HEADER_SIZE;
915
+ }
916
+ function uncompilablePath(stepIndex) {
917
+ return new CallciumError("UNCOMPILABLE_PATH", `Path step ${stepIndex} does not resolve to a hop-chain target`);
918
+ }
919
+ function headSpan(desc, offset, count) {
920
+ let span = 0;
921
+ let nodeOffset = offset;
922
+ for (let i = 0; i < count; i++) {
923
+ const node = inspect$1(desc, nodeOffset);
924
+ span += node.isDynamic ? 32 : node.staticSize;
925
+ nodeOffset += nodeLength(desc, nodeOffset);
926
+ }
927
+ return {
928
+ span,
929
+ nodeOffset
930
+ };
931
+ }
932
+ function arrayMeta(desc, arrayOffset, code) {
933
+ const element = inspect$1(desc, arrayElementOffset(arrayOffset));
934
+ let meta = element.isDynamic ? 1 : element.staticSize >> 5;
935
+ if (element.isDynamic) meta |= PolicyFormat.HINT_META_ELEM_DYNAMIC;
936
+ if (code === TypeCode.DYNAMIC_ARRAY) meta |= PolicyFormat.HINT_META_DYNAMIC_ARRAY;
937
+ return {
938
+ meta,
939
+ elemIsDynamic: element.isDynamic
940
+ };
941
+ }
942
+ function enterNode(walk, needed) {
943
+ if (!needed) return;
944
+ walk.chain.push([
945
+ walk.delta,
946
+ PolicyFormat.HINT_NO_INDEX,
947
+ 0
948
+ ]);
949
+ walk.delta = 0;
950
+ }
951
+ function walkStep(desc, walk, descOffset, step, stepIndex) {
952
+ const node = inspect$1(desc, descOffset);
953
+ if (node.typeCode === TypeCode.TUPLE) {
954
+ if (step >= tupleFieldCount(desc, descOffset)) throw uncompilablePath(stepIndex);
955
+ enterNode(walk, node.isDynamic && !walk.entered);
956
+ const field = headSpan(desc, descOffset + DescriptorFormat.TUPLE_HEADER_SIZE, step);
957
+ walk.delta += field.span;
958
+ walk.entered = false;
959
+ return field.nodeOffset;
960
+ }
961
+ if (node.typeCode !== TypeCode.STATIC_ARRAY && node.typeCode !== TypeCode.DYNAMIC_ARRAY) throw uncompilablePath(stepIndex);
962
+ if (node.typeCode === TypeCode.STATIC_ARRAY && !isQuantifier(step) && step >= staticArrayLength(desc, descOffset)) throw uncompilablePath(stepIndex);
963
+ const { meta, elemIsDynamic } = arrayMeta(desc, descOffset, node.typeCode);
964
+ if (isQuantifier(step)) {
965
+ if (walk.kind !== PolicyFormat.HINT_KIND_NONE) throw uncompilablePath(stepIndex);
966
+ enterNode(walk, node.isDynamic && !walk.entered);
967
+ const count = node.typeCode === TypeCode.DYNAMIC_ARRAY ? 0 : staticArrayLength(desc, descOffset);
968
+ walk.frame = {
969
+ arrayDelta: walk.delta,
970
+ count,
971
+ meta
972
+ };
973
+ walk.kind = step === Quantifier.ALL ? PolicyFormat.HINT_KIND_ALL : PolicyFormat.HINT_KIND_ANY;
974
+ walk.mainHops = walk.chain;
975
+ walk.chain = [];
976
+ walk.delta = 0;
977
+ } else if (node.isDynamic) {
978
+ enterNode(walk, !walk.entered);
979
+ walk.chain.push([
980
+ 0,
981
+ step,
982
+ meta
983
+ ]);
984
+ walk.delta = 0;
985
+ } else walk.delta += step * (meta & PolicyFormat.HINT_META_STRIDE_MASK) * 32;
986
+ walk.entered = elemIsDynamic;
987
+ return arrayElementOffset(descOffset);
988
+ }
989
+ function writeHops(out, offset, hops) {
990
+ for (const [delta, index, meta] of hops) {
991
+ writeBE32(out, offset, delta);
992
+ writeBE16(out, offset + PolicyFormat.HINT_HOP_INDEX_OFFSET, index);
993
+ writeBE16(out, offset + PolicyFormat.HINT_HOP_META_OFFSET, meta);
994
+ offset += PolicyFormat.HINT_HOP_SIZE;
995
+ }
996
+ return offset;
997
+ }
998
+ function encodeHint(walk, targetMeta, typeCode, depth) {
999
+ let suffixHops = [];
1000
+ if (walk.kind === PolicyFormat.HINT_KIND_NONE) walk.mainHops = walk.chain;
1001
+ else suffixHops = walk.chain;
1002
+ const mainHopCount = walk.mainHops.length;
1003
+ const suffixHopCount = suffixHops.length;
1004
+ if (mainHopCount > PolicyFormat.HINT_HOP_COUNT_MASK || suffixHopCount > PolicyFormat.HINT_HOP_COUNT_MASK) throw uncompilablePath(depth);
1005
+ const quantified = walk.kind !== PolicyFormat.HINT_KIND_NONE;
1006
+ const frameSize = quantified ? PolicyFormat.HINT_FRAME_PREFIX_SIZE + PolicyFormat.HINT_HEADER_SIZE + suffixHopCount * PolicyFormat.HINT_HOP_SIZE : 0;
1007
+ const out = new Uint8Array(PolicyFormat.HINT_HEADER_SIZE + mainHopCount * PolicyFormat.HINT_HOP_SIZE + frameSize + PolicyFormat.HINT_TARGET_SIZE);
1008
+ let offset = 0;
1009
+ out[offset] = walk.kind << PolicyFormat.HINT_KIND_SHIFT | mainHopCount;
1010
+ offset = writeHops(out, offset + PolicyFormat.HINT_HEADER_SIZE, walk.mainHops);
1011
+ if (quantified) {
1012
+ writeBE32(out, offset, walk.frame.arrayDelta);
1013
+ writeBE16(out, offset + PolicyFormat.HINT_FRAME_COUNT_OFFSET, walk.frame.count);
1014
+ writeBE16(out, offset + PolicyFormat.HINT_FRAME_META_OFFSET, walk.frame.meta);
1015
+ out[offset + PolicyFormat.HINT_FRAME_PREFIX_SIZE] = suffixHopCount;
1016
+ offset = writeHops(out, offset + PolicyFormat.HINT_FRAME_PREFIX_SIZE + PolicyFormat.HINT_HEADER_SIZE, suffixHops);
1017
+ }
1018
+ writeBE32(out, offset, walk.delta);
1019
+ writeBE16(out, offset + PolicyFormat.HINT_TARGET_META_OFFSET, targetMeta);
1020
+ out[offset + PolicyFormat.HINT_TARGET_TYPECODE_OFFSET] = typeCode;
1021
+ return out;
1022
+ }
1023
+ function compileHint(desc, steps) {
1024
+ if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
1025
+ const argIndex = steps[0];
1026
+ if (argIndex >= paramCount(desc)) throw uncompilablePath(0);
1027
+ const arg = headSpan(desc, DescriptorFormat.HEADER_SIZE, argIndex);
1028
+ const walk = {
1029
+ chain: [],
1030
+ mainHops: [],
1031
+ delta: arg.span,
1032
+ frame: {
1033
+ arrayDelta: 0,
1034
+ count: 0,
1035
+ meta: 0
1036
+ },
1037
+ kind: PolicyFormat.HINT_KIND_NONE,
1038
+ entered: false
835
1039
  };
1040
+ let descOffset = arg.nodeOffset;
1041
+ for (let stepIndex = 1; stepIndex < steps.length; stepIndex++) descOffset = walkStep(desc, walk, descOffset, steps[stepIndex], stepIndex);
1042
+ const target = inspect$1(desc, descOffset);
1043
+ enterNode(walk, target.isDynamic && !walk.entered);
1044
+ return encodeHint(walk, target.typeCode === TypeCode.DYNAMIC_ARRAY ? arrayMeta(desc, descOffset, target.typeCode).meta : 0, target.typeCode, steps.length);
836
1045
  }
1046
+ const Descriptor = {
1047
+ paramCount,
1048
+ paramOffset,
1049
+ inspect: inspect$1,
1050
+ typeAt,
1051
+ tupleFieldOffset,
1052
+ tupleFieldCount,
1053
+ staticArrayLength,
1054
+ nodeLength,
1055
+ arrayElementOffset,
1056
+ walkPath,
1057
+ compileHint
1058
+ };
837
1059
  //#endregion
838
1060
  //#region src/type-desc.ts
839
1061
  function extractStaticWords(desc) {
@@ -864,9 +1086,9 @@ function bytes() {
864
1086
  function string_() {
865
1087
  return new Uint8Array([TypeCode.STRING]);
866
1088
  }
867
- function bytesN(n) {
868
- if (n < 1 || n > 32) throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${n}. Must be 1–32`);
869
- return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (n - 1)]);
1089
+ function bytesN(length) {
1090
+ if (length < 1 || length > 32) throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${length}. Must be 1–32`);
1091
+ return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (length - 1)]);
870
1092
  }
871
1093
  function array(elemDesc, length) {
872
1094
  if (length === void 0) {
@@ -927,9 +1149,12 @@ function commaPositions(input, start, end) {
927
1149
  for (let i = start; i < end; i++) {
928
1150
  const char = input[i];
929
1151
  if (char === "(" || char === "[") depth++;
930
- else if (char === ")" || char === "]") depth--;
931
- else if (char === "," && depth === 0) positions.push(i);
1152
+ else if (char === ")" || char === "]") {
1153
+ if (depth === 0) throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched '${char}' at position ${i}`);
1154
+ depth--;
1155
+ } else if (char === "," && depth === 0) positions.push(i);
932
1156
  }
1157
+ if (depth !== 0) throw new CallciumError("MALFORMED_TYPE_STRING", `Unclosed '${input.slice(start, end)}'`);
933
1158
  return positions;
934
1159
  }
935
1160
  const MAX_TYPE_STRING_NUMBER = 256 ** DescriptorFormat.ARRAY_LENGTH_SIZE - 1;
@@ -963,12 +1188,12 @@ function parseTuple(input, start, end) {
963
1188
  const innerEnd = closeIndex;
964
1189
  const commas = commaPositions(input, innerStart, innerEnd);
965
1190
  const segments = [];
966
- let segStart = innerStart;
1191
+ let segmentStart = innerStart;
967
1192
  for (const comma of commas) {
968
- segments.push([segStart, comma]);
969
- segStart = comma + 1;
1193
+ segments.push([segmentStart, comma]);
1194
+ segmentStart = comma + 1;
970
1195
  }
971
- segments.push([segStart, innerEnd]);
1196
+ segments.push([segmentStart, innerEnd]);
972
1197
  if (segments.length === 1 && segments[0][0] === segments[0][1]) return tuple([]);
973
1198
  return tuple(segments.map(([fieldStart, fieldEnd]) => parseType(input, fieldStart, fieldEnd)));
974
1199
  }
@@ -980,28 +1205,25 @@ function parseBaseType(input, start, end) {
980
1205
  if (segment === "bytes") return bytes();
981
1206
  if (segment === "string") return string_();
982
1207
  if (segment.startsWith("(")) return parseTuple(input, start, end);
983
- if (segment.startsWith("uint")) return uintN(parseUint(input, start + 4, end));
984
- if (segment.startsWith("int")) return intN(parseUint(input, start + 3, end));
985
- if (segment.startsWith("bytes")) return bytesN(parseUint(input, start + 5, end));
1208
+ if (segment.length > 4 && segment.startsWith("uint")) return uintN(parseUint(input, start + 4, end));
1209
+ if (segment.length > 3 && segment.startsWith("int")) return intN(parseUint(input, start + 3, end));
1210
+ if (segment.length > 5 && segment.startsWith("bytes")) return bytesN(parseUint(input, start + 5, end));
986
1211
  throw new CallciumError("UNKNOWN_TYPE", `Unrecognised type '${segment}'`);
987
1212
  }
988
1213
  function parseType(input, start, end) {
989
1214
  if (start >= end) throw new CallciumError("MALFORMED_TYPE_STRING", "Empty type segment");
990
- const suffixes = [];
1215
+ const spans = [];
991
1216
  let baseEnd = end;
992
1217
  while (baseEnd > start && input[baseEnd - 1] === "]") {
993
1218
  const closeIndex = baseEnd - 1;
994
1219
  let openIndex = closeIndex - 1;
995
1220
  while (openIndex > start && input[openIndex] !== "[") openIndex--;
996
1221
  if (input[openIndex] !== "[") throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched ']' at position ${closeIndex}`);
997
- const innerStart = openIndex + 1;
998
- const innerEnd = closeIndex;
999
- suffixes.unshift(innerStart === innerEnd ? void 0 : parseUint(input, innerStart, innerEnd));
1222
+ spans.unshift([openIndex + 1, closeIndex]);
1000
1223
  baseEnd = openIndex;
1001
1224
  }
1002
- if (input[start] !== "(" && input.slice(start, baseEnd).includes("[")) throw new CallciumError("MALFORMED_TYPE_STRING", `Unmatched '[' in '${input.slice(start, baseEnd)}'`);
1003
1225
  let desc = parseBaseType(input, start, baseEnd);
1004
- for (const length of suffixes) desc = array(desc, length);
1226
+ for (const [innerStart, innerEnd] of spans) desc = array(desc, innerStart === innerEnd ? void 0 : parseUint(input, innerStart, innerEnd));
1005
1227
  return desc;
1006
1228
  }
1007
1229
  function fromTypes(typesCsv) {
@@ -1025,10 +1247,11 @@ function fromTypes(typesCsv) {
1025
1247
  result.set(desc, offset);
1026
1248
  offset += desc.length;
1027
1249
  }
1250
+ decodeDescriptor(result);
1028
1251
  return result;
1029
1252
  }
1030
1253
  function nodeToTypeString(desc, offset) {
1031
- const typeCode = desc[offset];
1254
+ const { typeCode } = Descriptor.inspect(desc, offset);
1032
1255
  if (typeCode === TypeCode.TUPLE) {
1033
1256
  const fieldCount = Descriptor.tupleFieldCount(desc, offset);
1034
1257
  const fieldTypes = [];
@@ -1057,30 +1280,20 @@ function toTypes(desc) {
1057
1280
  return types.join(",");
1058
1281
  }
1059
1282
  function parseNode(data, offset, depth) {
1060
- if (offset >= data.length) throw new CallciumError("UNEXPECTED_END", "Unexpected end of descriptor", offset);
1061
- const code = data[offset];
1283
+ const { typeCode: code, staticWords, nodeLength } = readNode(data, offset);
1062
1284
  const info = classifyTypeCode(code);
1063
- const metaOffset = offset + DescriptorFormat.TYPECODE_SIZE;
1285
+ const isDynamic = staticWords === 0;
1286
+ const staticSize = isDynamic ? 0 : staticWords * 32;
1287
+ const nodeEnd = offset + nodeLength;
1064
1288
  if (info.typeClass === "elementary") return {
1065
1289
  typeCode: code,
1066
- isDynamic: info.isDynamic,
1067
- staticSize: info.isDynamic ? 0 : 32,
1068
- next: metaOffset
1290
+ isDynamic,
1291
+ staticSize,
1292
+ next: nodeEnd
1069
1293
  };
1070
1294
  if (depth > DescriptorFormat.MAX_NESTING_DEPTH) throw new CallciumError("NESTING_TOO_DEEP", `Composite nesting exceeds maximum depth ${DescriptorFormat.MAX_NESTING_DEPTH}`, offset);
1071
- const metaEnd = metaOffset + DescriptorFormat.COMPOSITE_META_SIZE;
1072
- if (metaEnd > data.length) throw new CallciumError("UNEXPECTED_END", "Incomplete composite metadata", offset);
1073
- const meta = readU24(data, metaOffset);
1074
- const staticWords = meta >> DescriptorFormat.META_STATIC_WORDS_SHIFT;
1075
- const nodeLength = meta & DescriptorFormat.META_NODE_LENGTH_MASK;
1076
- const minHeader = info.typeClass === "tuple" ? DescriptorFormat.TUPLE_HEADER_SIZE : DescriptorFormat.ARRAY_HEADER_SIZE;
1077
- if (nodeLength < minHeader) throw new CallciumError("NODE_LENGTH_TOO_SMALL", `Composite node length ${nodeLength} is smaller than minimum header ${minHeader}`, offset);
1078
- if (offset + nodeLength > data.length) throw new CallciumError("NODE_OVERFLOW", "Composite node extends beyond descriptor", offset);
1079
- const isDynamic = staticWords === 0;
1080
- const staticSize = isDynamic ? 0 : staticWords * 32;
1081
- const nodeEnd = offset + nodeLength;
1082
1295
  if (info.typeClass === "tuple") {
1083
- const fieldCountOffset = metaEnd;
1296
+ const fieldCountOffset = offset + DescriptorFormat.TYPECODE_SIZE + DescriptorFormat.COMPOSITE_META_SIZE;
1084
1297
  if (fieldCountOffset + 2 > data.length) throw new CallciumError("UNEXPECTED_END", "Incomplete tuple header", offset);
1085
1298
  const fieldCount = readU16(data, fieldCountOffset);
1086
1299
  if (fieldCount === 0) throw new CallciumError("INVALID_TUPLE_FIELD_COUNT", "Tuple must have at least one field", offset);
@@ -1126,8 +1339,8 @@ function decodeDescriptor(data) {
1126
1339
  const params = [];
1127
1340
  let cursor = DescriptorFormat.HEADER_SIZE;
1128
1341
  while (cursor < data.length) {
1129
- if (params.length >= DescriptorFormat.MAX_PARAMS) throw new CallciumError("TOO_MANY_PARAMS", `Descriptor exceeds the maximum of ${DescriptorFormat.MAX_PARAMS} top-level params`);
1130
1342
  const { typeCode, isDynamic, staticSize, next } = parseNode(data, cursor, 1);
1343
+ if (params.length === DescriptorFormat.MAX_PARAMS) throw new CallciumError("TOO_MANY_PARAMS", `Descriptor exceeds the maximum of ${DescriptorFormat.MAX_PARAMS} top-level params`);
1131
1344
  params.push({
1132
1345
  index: params.length,
1133
1346
  typeCode,
@@ -1163,6 +1376,21 @@ function field(value, start, end) {
1163
1376
  }
1164
1377
  };
1165
1378
  }
1379
+ function hexField(data, start, end) {
1380
+ let hex;
1381
+ return {
1382
+ get value() {
1383
+ return hex ??= toHex(data, start, end);
1384
+ },
1385
+ set value(replacement) {
1386
+ hex = replacement;
1387
+ },
1388
+ span: {
1389
+ start,
1390
+ end
1391
+ }
1392
+ };
1393
+ }
1166
1394
  function resolveHintSize(data, hintStart, ruleEnd, ruleOffset) {
1167
1395
  const overrun = () => {
1168
1396
  throw new CallciumError("RULE_FIELD_OUT_OF_BOUNDS", "Rule is too small to hold a hint block", ruleOffset);
@@ -1219,7 +1447,7 @@ function hintTargetTypeCode(data, hintStart, hintSize) {
1219
1447
  }
1220
1448
  function decodePolicy(blob) {
1221
1449
  const data = hexToBytes(blob);
1222
- if (data.length < PolicyFormat.DESC_OFFSET + 1) throw new CallciumError("MALFORMED_HEADER", "Policy blob is too short");
1450
+ if (data.length < PolicyFormat.DESC_OFFSET) throw new CallciumError("MALFORMED_HEADER", "Policy blob is too short");
1223
1451
  const headerByte = data[0];
1224
1452
  const version = headerByte & PolicyFormat.VERSION_MASK;
1225
1453
  if (version !== PolicyFormat.VERSION) throw new CallciumError("UNSUPPORTED_VERSION", `Version ${version} is not supported (expected ${PolicyFormat.VERSION})`);
@@ -1244,7 +1472,7 @@ function decodePolicy(blob) {
1244
1472
  end: descStart + param.span.end
1245
1473
  }
1246
1474
  }));
1247
- const descriptorRaw = toHex(data, descStart, descEnd);
1475
+ const descriptorRaw = hexField(data, descStart, descEnd);
1248
1476
  const groupCountStart = groupCountOffset;
1249
1477
  const groupCountEnd = groupCountStart + PolicyFormat.GROUP_COUNT_SIZE;
1250
1478
  const groupCountValue = data[groupCountOffset];
@@ -1257,18 +1485,18 @@ function decodePolicy(blob) {
1257
1485
  const groupSizeValue = readU32(data, groupSizeStart);
1258
1486
  const groupBodyStart = offset + PolicyFormat.GROUP_HEADER_SIZE;
1259
1487
  const groupEnd = groupBodyStart + groupSizeValue;
1488
+ if (groupEnd > data.length) throw new CallciumError("GROUP_OVERFLOW", "Group extends beyond policy blob", offset);
1260
1489
  const ruleCountStart = offset;
1261
1490
  const ruleCountValue = readU16(data, ruleCountStart);
1262
1491
  if (ruleCountValue === 0) throw new CallciumError("EMPTY_GROUP", "Group must contain at least one rule", offset);
1263
1492
  if (groupSizeValue < ruleCountValue * PolicyFormat.RULE_MIN_SIZE) throw new CallciumError("GROUP_TOO_SMALL", "Declared group size is too small for its rule count", offset);
1264
- if (groupEnd > data.length) throw new CallciumError("GROUP_OVERFLOW", "Group extends beyond policy blob", offset);
1265
1493
  const rules = [];
1266
1494
  let ruleOffset = groupBodyStart;
1267
1495
  for (let ruleIndex = 0; ruleIndex < ruleCountValue; ruleIndex++) {
1268
1496
  if (ruleOffset + PolicyFormat.RULE_SIZE_SIZE > data.length) throw new CallciumError("UNEXPECTED_END", "Unexpected end while reading rule size", ruleOffset);
1269
1497
  const ruleSizeValue = readU16(data, ruleOffset);
1270
1498
  if (ruleSizeValue < PolicyFormat.RULE_MIN_SIZE) throw new CallciumError("RULE_TOO_SMALL", `Rule size ${ruleSizeValue} is below minimum ${PolicyFormat.RULE_MIN_SIZE}`, ruleOffset);
1271
- if (ruleOffset + ruleSizeValue > groupEnd) throw new CallciumError("RULE_OVERFLOW", "Rule extends beyond group boundary", ruleOffset);
1499
+ if (ruleOffset + ruleSizeValue > data.length) throw new CallciumError("RULE_OVERFLOW", "Rule extends beyond policy blob", ruleOffset);
1272
1500
  const scopeOffset = ruleOffset + PolicyFormat.RULE_SCOPE_OFFSET;
1273
1501
  const scopeValue = data[scopeOffset];
1274
1502
  if (scopeValue !== Scope.CONTEXT && scopeValue !== Scope.CALLDATA) throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${scopeValue}`, ruleOffset);
@@ -1297,7 +1525,12 @@ function decodePolicy(blob) {
1297
1525
  const dataStart = dataLengthOffset + PolicyFormat.RULE_DATALENGTH_SIZE;
1298
1526
  if (opBase === Op.IN) {
1299
1527
  const wordCount = dataLengthValue / 32;
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);
1528
+ let prev = toBigInt(data, dataStart);
1529
+ for (let word = 1; word < wordCount; word++) {
1530
+ const cur = toBigInt(data, dataStart + word * 32);
1531
+ if (cur <= prev) throw new CallciumError("UNSORTED_IN_SET", "IN operands must be strictly ascending", ruleOffset);
1532
+ prev = cur;
1533
+ }
1301
1534
  }
1302
1535
  if (opBase === Op.EQ_CTX) {
1303
1536
  if (toBigInt(data, dataStart) > BigInt(MAX_CONTEXT_PROPERTY_ID)) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", "EQ_CTX operand references an undefined context property", ruleOffset);
@@ -1307,7 +1540,7 @@ function decodePolicy(blob) {
1307
1540
  if (scopeValue === Scope.CONTEXT) {
1308
1541
  if (depthValue !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope rules must have exactly one path step", ruleOffset);
1309
1542
  const contextPropertyId = readU16(data, pathStart);
1310
- if (contextPropertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Context rule references undefined context property 0x${contextPropertyId.toString(16).padStart(4, "0")}`, ruleOffset);
1543
+ if (contextPropertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Context rule references undefined context property ${formatCode(contextPropertyId, PolicyFormat.PATH_STEP_SIZE)}`, ruleOffset);
1311
1544
  }
1312
1545
  const pathHex = toHex(data, pathStart, pathStart + pathLength);
1313
1546
  rules.push({
@@ -1315,15 +1548,16 @@ function decodePolicy(blob) {
1315
1548
  scope: field(scopeValue, scopeOffset, scopeOffset + 1),
1316
1549
  pathDepth: field(depthValue, depthOffset, depthOffset + 1),
1317
1550
  path: field(pathHex, pathStart, pathStart + pathLength),
1318
- ...hintSize > 0 && { hint: field(toHex(data, hintStart, hintStart + hintSize), hintStart, hintStart + hintSize) },
1551
+ ...hintSize > 0 && { hint: hexField(data, hintStart, hintStart + hintSize) },
1319
1552
  opCode: field(opCodeValue, opCodeOffset, opCodeOffset + PolicyFormat.RULE_OPCODE_SIZE),
1320
1553
  dataLength: field(dataLengthValue, dataLengthOffset, dataLengthOffset + PolicyFormat.RULE_DATALENGTH_SIZE),
1321
- data: field(toHex(data, dataStart, dataStart + dataLengthValue), dataStart, dataStart + dataLengthValue),
1554
+ data: hexField(data, dataStart, dataStart + dataLengthValue),
1322
1555
  span: {
1323
1556
  start: ruleOffset,
1324
1557
  end: ruleOffset + ruleSizeValue
1325
1558
  }
1326
1559
  });
1560
+ if (ruleOffset + ruleSizeValue > groupEnd) throw new CallciumError("RULE_OVERFLOW", "Rule extends beyond group boundary", ruleOffset);
1327
1561
  ruleOffset += ruleSizeValue;
1328
1562
  }
1329
1563
  if (ruleOffset !== groupEnd) throw new CallciumError("GROUP_SIZE_MISMATCH", "Rules do not exactly fill the declared group size", offset);
@@ -1345,7 +1579,12 @@ function decodePolicy(blob) {
1345
1579
  selector: field(selectorHex, selectorStart, selectorEnd),
1346
1580
  descLength: field(descLengthValue, descLengthStart, descLengthStart + PolicyFormat.DESC_LENGTH_SIZE),
1347
1581
  descriptor: {
1348
- raw: descriptorRaw,
1582
+ get raw() {
1583
+ return descriptorRaw.value;
1584
+ },
1585
+ set raw(replacement) {
1586
+ descriptorRaw.value = replacement;
1587
+ },
1349
1588
  header: field({
1350
1589
  version: desc.version,
1351
1590
  paramCount: desc.params.length
@@ -1368,21 +1607,28 @@ function decodePolicy(blob) {
1368
1607
  data
1369
1608
  };
1370
1609
  }
1371
- function flattenConstraint(constraint, desc) {
1372
- const operators = constraint.operators.map(hexToBytes);
1373
- for (const operator of operators) if (operator.length < 1) throw new CallciumError("INVALID_OPERATOR_BYTES", "Operator must have at least one byte (opcode)");
1610
+ function readConstraintTarget(constraint, desc) {
1374
1611
  const path = hexToBytes(constraint.path);
1375
1612
  if (path.length === 0) throw new CallciumError("EMPTY_PATH", "Rule path must have at least one step");
1376
1613
  if ((path.length & 1) !== 0) throw new CallciumError("MALFORMED_PATH", "Path byte length must be even");
1377
1614
  const depth = path.length / 2;
1378
1615
  if (depth > PolicyFormat.MAX_PATH_DEPTH) throw new CallciumError("PATH_TOO_DEEP", `Path depth ${depth} exceeds maximum ${PolicyFormat.MAX_PATH_DEPTH}`);
1379
- const hint = constraint.scope === Scope.CALLDATA ? Descriptor.compileHint(desc, parsePathSteps(constraint.path)) : new Uint8Array(0);
1380
- return operators.map((operator) => ({
1381
- scope: constraint.scope,
1616
+ return {
1382
1617
  path,
1383
- operator,
1384
- hint
1385
- }));
1618
+ hint: constraint.scope === Scope.CALLDATA ? Descriptor.compileHint(desc, parsePathSteps(constraint.path)) : new Uint8Array(0)
1619
+ };
1620
+ }
1621
+ function flattenConstraint(constraint, desc) {
1622
+ let target;
1623
+ return constraint.operators.map((operatorHex) => {
1624
+ const operator = toOperatorBytes(operatorHex);
1625
+ target ??= readConstraintTarget(constraint, desc);
1626
+ return {
1627
+ scope: constraint.scope,
1628
+ operator,
1629
+ ...target
1630
+ };
1631
+ });
1386
1632
  }
1387
1633
  function compareBytes(a, b) {
1388
1634
  const length = Math.min(a.length, b.length);
@@ -1400,12 +1646,16 @@ function sortRules(rules) {
1400
1646
  return compareBytes(a.operator, b.operator);
1401
1647
  });
1402
1648
  }
1649
+ function requireFieldFits(value, byteWidth, code, subject) {
1650
+ const max = fieldMax(byteWidth);
1651
+ if (value > max) throw new CallciumError(code, `${subject} ${value} exceeds maximum ${max}`);
1652
+ }
1403
1653
  function encodeRule(rule) {
1404
1654
  const depth = rule.path.length / 2;
1405
1655
  if (rule.scope === Scope.CONTEXT && depth !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope rules must have exactly one path step");
1406
1656
  const data = rule.operator.subarray(1);
1407
1657
  const ruleSize = PolicyFormat.RULE_FIXED_OVERHEAD + rule.path.length + rule.hint.length + data.length;
1408
- if (ruleSize > 65535) throw new CallciumError("RULE_SIZE_OVERFLOW", `Rule size ${ruleSize} exceeds maximum 65535`);
1658
+ requireFieldFits(ruleSize, PolicyFormat.RULE_SIZE_SIZE, "RULE_SIZE_OVERFLOW", "Rule size");
1409
1659
  const buf = new Uint8Array(ruleSize);
1410
1660
  writeBE16(buf, 0, ruleSize);
1411
1661
  buf[2] = rule.scope;
@@ -1435,16 +1685,16 @@ function buildOperatorHex(rule) {
1435
1685
  function encode(data) {
1436
1686
  const descBytes = hexToBytes(data.descriptor);
1437
1687
  if (data.groups.length === 0) throw new CallciumError("EMPTY_POLICY", "Policy must contain at least one group");
1438
- if (data.groups.length > 255) throw new CallciumError("GROUP_COUNT_OVERFLOW", `Group count ${data.groups.length} exceeds maximum 255`);
1688
+ requireFieldFits(data.groups.length, PolicyFormat.GROUP_COUNT_SIZE, "GROUP_COUNT_OVERFLOW", "Group count");
1439
1689
  const sortedGroups = data.groups.map((group) => {
1440
1690
  const rules = group.flatMap((constraint) => flattenConstraint(constraint, descBytes));
1441
1691
  sortRules(rules);
1442
1692
  return rules;
1443
1693
  });
1444
- if (descBytes.length > 65535) throw new CallciumError("DESC_LENGTH_OVERFLOW", `Descriptor length ${descBytes.length} exceeds maximum 65535`);
1694
+ requireFieldFits(descBytes.length, PolicyFormat.DESC_LENGTH_SIZE, "DESC_LENGTH_OVERFLOW", "Descriptor length");
1445
1695
  const encodedGroups = sortedGroups.map((rules, groupIndex) => {
1446
1696
  if (rules.length === 0) throw new CallciumError("EMPTY_GROUP", `Group ${groupIndex} is empty`);
1447
- if (rules.length > 65535) throw new CallciumError("RULE_COUNT_OVERFLOW", `Group ${groupIndex} rule count ${rules.length} exceeds maximum 65535`);
1697
+ requireFieldFits(rules.length, PolicyFormat.GROUP_RULECOUNT_SIZE, "RULE_COUNT_OVERFLOW", `Group ${groupIndex} rule count`);
1448
1698
  return {
1449
1699
  wireBytes: encodeGroupRules(rules),
1450
1700
  ruleCount: rules.length
@@ -1454,7 +1704,8 @@ function encode(data) {
1454
1704
  const hashes = new Map(encodedGroups.map((group) => [group, keccak_256(group.wireBytes)]));
1455
1705
  encodedGroups.sort((a, b) => compareBytes(hashes.get(a), hashes.get(b)));
1456
1706
  }
1457
- const selectorBytes = data.isSelectorless ? new Uint8Array(4) : hexToBytes(data.selector);
1707
+ const selectorBytes = data.isSelectorless ? new Uint8Array(PolicyFormat.SELECTOR_SIZE) : hexToBytes(data.selector);
1708
+ if (selectorBytes.length !== PolicyFormat.SELECTOR_SIZE) throw new CallciumError("MALFORMED_SELECTOR", `Selector must be ${PolicyFormat.SELECTOR_SIZE} bytes, got ${selectorBytes.length}`);
1458
1709
  const headerByte = PolicyFormat.VERSION | (data.isSelectorless ? PolicyFormat.FLAG_NO_SELECTOR : 0);
1459
1710
  let totalSize = PolicyFormat.HEADER_SIZE + PolicyFormat.SELECTOR_SIZE + PolicyFormat.DESC_LENGTH_SIZE + descBytes.length + PolicyFormat.GROUP_COUNT_SIZE;
1460
1711
  for (const g of encodedGroups) totalSize += PolicyFormat.GROUP_HEADER_SIZE + g.wireBytes.length;
@@ -1482,7 +1733,6 @@ function decode(blob) {
1482
1733
  const { policy } = decodePolicy(blob);
1483
1734
  const groups = policy.groups.map((group) => {
1484
1735
  const constraintMap = /* @__PURE__ */ new Map();
1485
- const constraintOrder = [];
1486
1736
  for (const rule of group.rules) {
1487
1737
  const hintHex = rule.hint?.value;
1488
1738
  const key = `${rule.scope.value}:${rule.path.value}:${hintHex ?? ""}`;
@@ -1498,10 +1748,9 @@ function decode(blob) {
1498
1748
  span: rule.span
1499
1749
  };
1500
1750
  constraintMap.set(key, constraint);
1501
- constraintOrder.push(key);
1502
1751
  }
1503
1752
  }
1504
- return constraintOrder.map((k) => constraintMap.get(k));
1753
+ return [...constraintMap.values()];
1505
1754
  });
1506
1755
  return {
1507
1756
  isSelectorless: policy.isSelectorless,
@@ -1945,7 +2194,6 @@ function negationUnderAny(groupIndex, constraintIndex, opCode) {
1945
2194
  //#endregion
1946
2195
  //#region src/policy-validator.ts
1947
2196
  const navigabilityCodes = new Set([
1948
- "EMPTY_PATH",
1949
2197
  "PARAM_INDEX_OUT_OF_BOUNDS",
1950
2198
  "TUPLE_FIELD_OUT_OF_BOUNDS",
1951
2199
  "STATIC_ARRAY_INDEX_OUT_OF_BOUNDS",
@@ -1956,8 +2204,11 @@ const UINT256_MAX = (1n << 256n) - 1n;
1956
2204
  const INT256_SIGN_BIT = 1n << 255n;
1957
2205
  function signedCompare(a, b, signed) {
1958
2206
  if (!signed) return a - b;
1959
- const toS = (v) => v >= INT256_SIGN_BIT ? v - (1n << 256n) : v;
1960
- return toS(a) - toS(b);
2207
+ const toSigned = (word) => word >= INT256_SIGN_BIT ? word - (1n << 256n) : word;
2208
+ return toSigned(a) - toSigned(b);
2209
+ }
2210
+ function isOutsideDomain(domain, value) {
2211
+ return signedCompare(value, domain.min, domain.isSigned) < 0n || signedCompare(value, domain.max, domain.isSigned) > 0n;
1961
2212
  }
1962
2213
  function isNumericType(typeCode) {
1963
2214
  return typeCode >= TypeCode.UINT_MIN && typeCode <= TypeCode.UINT_MAX || typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX;
@@ -2015,7 +2266,7 @@ function getIncompat(opBase, typeInfo) {
2015
2266
  code: "VALUE_OP_ON_DYNAMIC",
2016
2267
  message: "Value operator used on dynamic type"
2017
2268
  };
2018
- if (classifyTypeCode(typeCode).typeClass !== "elementary") return {
2269
+ if (!isElementary(typeCode)) return {
2019
2270
  code: "VALUE_OP_ON_COMPOSITE",
2020
2271
  message: "Value operator used on composite type"
2021
2272
  };
@@ -2126,7 +2377,7 @@ function initContext(scope, path, steps, typeInfo) {
2126
2377
  numeric: emptyBoundDomain(isSigned(typeInfo.typeCode), min, max),
2127
2378
  bitmask: {
2128
2379
  mustBeOne: 0n,
2129
- mustBeZero: 0n
2380
+ mustBeZero: isBitmaskCompatible(typeInfo.typeCode) ? UINT256_MAX ^ max : 0n
2130
2381
  },
2131
2382
  length: emptyBoundDomain(false, 0n, LENGTH_MAX),
2132
2383
  set: {
@@ -2139,6 +2390,7 @@ function initContext(scope, path, steps, typeInfo) {
2139
2390
  function updateBound(domain, base, isNegated, value, isLength, groupIndex, constraintIndex, issues) {
2140
2391
  if (isNegated) {
2141
2392
  if (base === Op.EQ) {
2393
+ if (isOutsideDomain(domain, value)) issues.push(outOfPhysicalBounds(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2142
2394
  if (domain.hasEq && domain.eq === value) issues.push(eqNeqContradiction(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2143
2395
  let alreadyHole = false;
2144
2396
  for (let j = 0; j < domain.holes.length; j++) if (domain.holes[j] === value) {
@@ -2151,7 +2403,7 @@ function updateBound(domain, base, isNegated, value, isLength, groupIndex, const
2151
2403
  }
2152
2404
  if (base === Op.GTE && value === domain.min) issues.push(vacuousGte(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2153
2405
  else if (base === Op.LTE && value === domain.max) issues.push(vacuousLte(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2154
- if (signedCompare(value, domain.min, domain.isSigned) < 0n || signedCompare(value, domain.max, domain.isSigned) > 0n) issues.push(outOfPhysicalBounds(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2406
+ if (isOutsideDomain(domain, value)) issues.push(outOfPhysicalBounds(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2155
2407
  else if (base === Op.GT && value === domain.max) issues.push(impossibleGt(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2156
2408
  else if (base === Op.LT && value === domain.min) issues.push(impossibleLt(isLength, groupIndex, constraintIndex, bigintToHex(value)));
2157
2409
  let changedEq = false;
@@ -2266,6 +2518,7 @@ function checkSetEmpty(ctx, groupIndex, constraintIndex, issues) {
2266
2518
  else if (possibleCount < inCount) issues.push(setPartiallyExcluded(groupIndex, constraintIndex, bigintToHex(BigInt(inCount - possibleCount))));
2267
2519
  }
2268
2520
  function updateSet(ctx, isNegated, values, groupIndex, constraintIndex, issues) {
2521
+ for (const value of values) if (isOutsideDomain(ctx.numeric, value)) issues.push(outOfPhysicalBounds(false, groupIndex, constraintIndex, bigintToHex(value)));
2269
2522
  if (isNegated) {
2270
2523
  for (const value of values) {
2271
2524
  if (ctx.numeric.hasEq && ctx.numeric.eq === value) issues.push(setExcludesEquality(groupIndex, constraintIndex, bigintToHex(value)));
@@ -2281,7 +2534,6 @@ function updateSet(ctx, isNegated, values, groupIndex, constraintIndex, issues)
2281
2534
  }
2282
2535
  checkSetEmpty(ctx, groupIndex, constraintIndex, issues);
2283
2536
  } else {
2284
- for (const value of values) if (signedCompare(value, ctx.numeric.min, ctx.numeric.isSigned) < 0n || signedCompare(value, ctx.numeric.max, ctx.numeric.isSigned) > 0n) issues.push(outOfPhysicalBounds(false, groupIndex, constraintIndex, bigintToHex(value)));
2285
2537
  if (ctx.numeric.hasEq) {
2286
2538
  let found = false;
2287
2539
  for (const candidate of values) if (candidate === ctx.numeric.eq) {
@@ -2323,6 +2575,9 @@ function checkFusibleRange(ctx, operators, groupIndex, constraintIndex, issues)
2323
2575
  const lengthPair = findLonePair(operators, Op.LENGTH_GTE, Op.LENGTH_LTE);
2324
2576
  if (lengthPair && getIncompat(Op.LENGTH_GTE, ctx.typeInfo) === null && lengthPair.low <= lengthPair.high) issues.push(fusibleRange(true, groupIndex, constraintIndex, bigintToHex(lengthPair.low), bigintToHex(lengthPair.high)));
2325
2577
  }
2578
+ function hasValidPayload(opBase, dataLength) {
2579
+ return dataLength <= fieldMax(PolicyFormat.RULE_DATALENGTH_SIZE) && isValidOperatorData(opBase, dataLength);
2580
+ }
2326
2581
  function findLonePair(operators, lowerOp, upperOp) {
2327
2582
  let lowerCount = 0;
2328
2583
  let upperCount = 0;
@@ -2331,7 +2586,7 @@ function findLonePair(operators, lowerOp, upperOp) {
2331
2586
  for (const opHex of operators) {
2332
2587
  const opCode = parseInt(opHex.slice(2, 4), 16);
2333
2588
  const dataLength = (opHex.length - 4) / 2;
2334
- if (dataLength > 65535 || !isValidOperatorData(opCode & ~Op.NOT, dataLength)) continue;
2589
+ if (!hasValidPayload(opCode & ~Op.NOT, dataLength)) continue;
2335
2590
  if (opCode === lowerOp) {
2336
2591
  lowerCount++;
2337
2592
  low = readValue(opHex);
@@ -2349,10 +2604,10 @@ function validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues
2349
2604
  const operators = constraint.operators;
2350
2605
  const underAny = constraint.scope === Scope.CALLDATA && ctx.steps.includes(Quantifier.ANY);
2351
2606
  for (const opHex of operators) {
2352
- const opCode = parseInt(opHex.slice(2, 4), 16);
2607
+ const opBytes = toOperatorBytes(opHex);
2608
+ const opCode = opBytes[0];
2353
2609
  const base = opCode & ~Op.NOT;
2354
- const dataLength = (opHex.length - 4) / 2;
2355
- if (dataLength > 65535 || !isValidOperatorData(base, dataLength)) {
2610
+ if (!hasValidPayload(base, opBytes.length - 1)) {
2356
2611
  issues.push(fromOpRule("UNKNOWN_OPERATOR", "Unknown operator code", groupIndex, constraintIndex, bigintToHex(BigInt(opCode))));
2357
2612
  continue;
2358
2613
  }
@@ -2365,8 +2620,9 @@ function validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues
2365
2620
  }
2366
2621
  if (base === Op.EQ_CTX) {
2367
2622
  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))));
2623
+ const property = findContextProperty(Number(ctxOperand));
2624
+ if (!property) issues.push(unknownContextProperty(groupIndex, constraintIndex, bigintToHex(ctxOperand), bigintToHex(BigInt(MAX_CONTEXT_PROPERTY_ID))));
2625
+ else if (property.typeCode === TypeCode.ADDRESS !== (ctx.typeInfo.typeCode === TypeCode.ADDRESS)) issues.push(contextTypeMismatch(groupIndex, constraintIndex, bigintToHex(ctxOperand), bigintToHex(BigInt(ctx.typeInfo.typeCode))));
2370
2626
  } else if (isValueOp(base)) {
2371
2627
  const typeCode = ctx.typeInfo.typeCode;
2372
2628
  if (isLeftAligned(typeCode)) {
@@ -2420,6 +2676,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
2420
2676
  }
2421
2677
  if (!ctx) {
2422
2678
  const steps = parsePathSteps(constraint.path);
2679
+ if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
2423
2680
  let typeInfo;
2424
2681
  if (constraint.scope === Scope.CALLDATA) {
2425
2682
  if (steps.length > PolicyFormat.MAX_PATH_DEPTH) issues.push(pathDepthExceeded(groupIndex, constraintIndex, bigintToHex(BigInt(steps.length)), bigintToHex(BigInt(PolicyFormat.MAX_PATH_DEPTH))));
@@ -2441,15 +2698,10 @@ function validateGroup(data, descBytes, groupIndex, issues) {
2441
2698
  typeInfo = walk.typeInfo;
2442
2699
  } else {
2443
2700
  const ctxId = steps[0];
2444
- if (ctxId > MAX_CONTEXT_PROPERTY_ID) {
2445
- issues.push(unknownContextProperty(groupIndex, constraintIndex, bigintToHex(BigInt(ctxId)), bigintToHex(BigInt(MAX_CONTEXT_PROPERTY_ID))));
2446
- typeInfo = {
2447
- typeCode: TypeCode.UINT_MAX,
2448
- isDynamic: false,
2449
- staticSize: 32
2450
- };
2451
- } else typeInfo = {
2452
- typeCode: lookupContextProperty(ctxId).typeCode,
2701
+ const property = findContextProperty(ctxId);
2702
+ if (!property) issues.push(unknownContextProperty(groupIndex, constraintIndex, bigintToHex(BigInt(ctxId)), bigintToHex(BigInt(MAX_CONTEXT_PROPERTY_ID))));
2703
+ typeInfo = {
2704
+ typeCode: property?.typeCode ?? TypeCode.UINT_MAX,
2453
2705
  isDynamic: false,
2454
2706
  staticSize: 32
2455
2707
  };
@@ -2457,7 +2709,7 @@ function validateGroup(data, descBytes, groupIndex, issues) {
2457
2709
  ctx = initContext(constraint.scope, normalizedPath, steps, typeInfo);
2458
2710
  contexts.push(ctx);
2459
2711
  }
2460
- if (constraint.scope === Scope.CALLDATA && constraint.hint !== void 0 && ctx.steps.length <= PolicyFormat.MAX_PATH_DEPTH) {
2712
+ if (constraint.scope === Scope.CALLDATA && constraint.hint !== void 0 && constraint.hint !== "0x" && ctx.steps.length <= PolicyFormat.MAX_PATH_DEPTH) {
2461
2713
  if (bytesToHex(Descriptor.compileHint(descBytes, ctx.steps)) !== constraint.hint.toLowerCase()) issues.push(hintMismatch(groupIndex, constraintIndex));
2462
2714
  }
2463
2715
  validateConstraint(ctx, constraint, groupIndex, constraintIndex, issues);
@@ -2480,33 +2732,31 @@ function validate(data) {
2480
2732
  const PolicyValidator = { validate };
2481
2733
  //#endregion
2482
2734
  //#region src/signature.ts
2483
- function isAlpha(c) {
2484
- return c >= 65 && c <= 90 || c >= 97 && c <= 122;
2735
+ function isAlpha(char) {
2736
+ return char >= 65 && char <= 90 || char >= 97 && char <= 122;
2485
2737
  }
2486
- function isAlphanum(c) {
2487
- return isAlpha(c) || c >= 48 && c <= 57;
2738
+ function isAlphanum(char) {
2739
+ return isAlpha(char) || char >= 48 && char <= 57;
2488
2740
  }
2489
2741
  function parse(signature) {
2490
2742
  if (signature.length < 3) throw new CallciumError("MALFORMED_SIGNATURE", `Invalid signature: too short, got "${signature}"`);
2491
2743
  let openParen = -1;
2492
2744
  for (let i = 0; i < signature.length; i++) {
2493
- const c = signature.charCodeAt(i);
2494
- if (c === 32 || c === 9 || c === 10 || c === 13) throw new CallciumError("SIGNATURE_CONTAINS_WHITESPACE", "Signature must not contain whitespace");
2495
- if (c > 126) throw new CallciumError("MALFORMED_SIGNATURE", "Signature must contain only ASCII characters");
2496
- if (openParen === -1 && c === 40) openParen = i;
2745
+ const char = signature.charCodeAt(i);
2746
+ if (char === 32 || char === 9 || char === 10 || char === 13) throw new CallciumError("SIGNATURE_CONTAINS_WHITESPACE", "Signature must not contain whitespace");
2747
+ if (openParen === -1 && char === 40) openParen = i;
2497
2748
  }
2498
- if (openParen < 1) throw new CallciumError("MALFORMED_SIGNATURE", `Invalid signature: must have a function name followed by parentheses, got "${signature}"`);
2749
+ if (openParen === -1) throw new CallciumError("MALFORMED_SIGNATURE", `Invalid signature: must have a function name followed by parentheses, got "${signature}"`);
2499
2750
  if (!signature.endsWith(")")) throw new CallciumError("MALFORMED_SIGNATURE", `Invalid signature: must end with ")", got "${signature}"`);
2751
+ if (openParen === 0) throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must not be empty");
2500
2752
  const firstChar = signature.charCodeAt(0);
2501
2753
  if (!isAlpha(firstChar) && firstChar !== 95) throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must start with a letter or underscore");
2502
2754
  for (let i = 1; i < openParen; i++) {
2503
- const c = signature.charCodeAt(i);
2504
- if (!isAlphanum(c) && c !== 95) throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must contain only alphanumeric characters or underscores");
2755
+ const char = signature.charCodeAt(i);
2756
+ if (!isAlphanum(char) && char !== 95) throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must contain only alphanumeric characters or underscores");
2505
2757
  }
2506
- const encoded = new Uint8Array(signature.length);
2507
- for (let i = 0; i < signature.length; i++) encoded[i] = signature.charCodeAt(i);
2508
2758
  return {
2509
- selector: bytesToHex(keccak_256(encoded).subarray(0, 4)),
2759
+ selector: bytesToHex(keccak_256(utf8ToBytes(signature)).subarray(0, 4)),
2510
2760
  types: signature.slice(openParen + 1, signature.length - 1)
2511
2761
  };
2512
2762
  }
@@ -2515,8 +2765,7 @@ const SignatureParser = { parse };
2515
2765
  //#region src/policy-builder.ts
2516
2766
  function validateContextPath(steps) {
2517
2767
  if (steps.length !== 1) throw new CallciumError("INVALID_CONTEXT_PATH", "Context-scope path must be exactly one step");
2518
- const step = steps[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")}`);
2768
+ return lookupContextProperty(steps[0]).typeCode;
2520
2769
  }
2521
2770
  function validateCalldataPath(steps, desc) {
2522
2771
  const argIndex = steps[0];
@@ -2545,6 +2794,33 @@ function validateCalldataPath(steps, desc) {
2545
2794
  offset = Descriptor.arrayElementOffset(offset);
2546
2795
  } else throw new CallciumError("NOT_COMPOSITE", "Cannot descend into an elementary type");
2547
2796
  }
2797
+ return Descriptor.inspect(desc, offset).typeCode;
2798
+ }
2799
+ const WORD_VALUES = 1n << 256n;
2800
+ function targetBounds(typeCode) {
2801
+ if (typeCode >= TypeCode.UINT_MIN && typeCode <= TypeCode.UINT_MAX) return {
2802
+ min: 0n,
2803
+ max: (1n << BigInt(typeCode - TypeCode.UINT_MIN + 1) * 8n) - 1n
2804
+ };
2805
+ if (typeCode >= TypeCode.INT_MIN && typeCode <= TypeCode.INT_MAX) {
2806
+ const half = 1n << BigInt(typeCode - TypeCode.INT_MIN + 1) * 8n - 1n;
2807
+ return {
2808
+ min: -half,
2809
+ max: half - 1n
2810
+ };
2811
+ }
2812
+ return null;
2813
+ }
2814
+ function reject(written, folded) {
2815
+ throw new CallciumError("OUT_OF_PHYSICAL_BOUNDS", `Operand ${written} is outside the physical range of the type and encodes as ${folded}`);
2816
+ }
2817
+ function checkOperandDomain(constraint, typeCode) {
2818
+ const bounds = targetBounds(typeCode);
2819
+ const extremes = readOperandExtremes(constraint);
2820
+ if (bounds === null || extremes === void 0) return;
2821
+ if (extremes.leastNegative < 0n && typeCode === TypeCode.UINT_MAX) reject(extremes.leastNegative, extremes.leastNegative + WORD_VALUES);
2822
+ if (!isSigned(typeCode)) return;
2823
+ if (extremes.greatest >= WORD_VALUES + bounds.min) reject(extremes.greatest, extremes.greatest - WORD_VALUES);
2548
2824
  }
2549
2825
  var PolicyBuilder = class PolicyBuilder {
2550
2826
  draft;
@@ -2559,7 +2835,7 @@ var PolicyBuilder = class PolicyBuilder {
2559
2835
  selector: parsed.selector,
2560
2836
  descriptor,
2561
2837
  groups: [[]],
2562
- pathHashes: [/* @__PURE__ */ new Set()]
2838
+ groupPathKeys: /* @__PURE__ */ new Set()
2563
2839
  });
2564
2840
  }
2565
2841
  static createRaw(typesCsv) {
@@ -2568,32 +2844,34 @@ var PolicyBuilder = class PolicyBuilder {
2568
2844
  selector: "0x00000000",
2569
2845
  descriptor: DescriptorCoder.fromTypes(typesCsv),
2570
2846
  groups: [[]],
2571
- pathHashes: [/* @__PURE__ */ new Set()]
2847
+ groupPathKeys: /* @__PURE__ */ new Set()
2572
2848
  });
2573
2849
  }
2574
2850
  add(constraint) {
2575
- const c = {
2851
+ const added = {
2576
2852
  scope: constraint.scope,
2577
2853
  path: constraint.path,
2578
- operators: [...constraint.operators]
2854
+ operators: [...constraint.operators],
2855
+ ..."hint" in constraint && constraint.hint !== void 0 && { hint: constraint.hint }
2579
2856
  };
2580
- if (c.operators.length === 0) throw new CallciumError("NO_CONSTRAINT_OPERATORS", "Constraint must have at least one operator");
2581
- const steps = parsePathSteps(c.path);
2857
+ if (added.operators.length === 0) throw new CallciumError("NO_CONSTRAINT_OPERATORS", "Constraint must have at least one operator");
2858
+ const steps = parsePathSteps(added.path);
2582
2859
  if (steps.length === 0) throw new CallciumError("EMPTY_PATH", "Path must have at least one step");
2583
- if (c.scope === Scope.CONTEXT) validateContextPath(steps);
2584
- else if (c.scope === Scope.CALLDATA) validateCalldataPath(steps, this.draft.descriptor);
2585
- else throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${c.scope}`);
2586
- const key = `${c.scope}:${c.path.toLowerCase()}`;
2587
- const currentHashes = this.draft.pathHashes[this.draft.pathHashes.length - 1];
2588
- if (currentHashes.has(key)) throw new CallciumError("DUPLICATE_PATH_IN_GROUP", `Duplicate path ${c.path} in the same group`);
2589
- currentHashes.add(key);
2590
- this.draft.groups[this.draft.groups.length - 1].push(c);
2860
+ let targetTypeCode;
2861
+ if (added.scope === Scope.CONTEXT) targetTypeCode = validateContextPath(steps);
2862
+ else if (added.scope === Scope.CALLDATA) targetTypeCode = validateCalldataPath(steps, this.draft.descriptor);
2863
+ else throw new CallciumError("INVALID_SCOPE", `Unknown scope value ${added.scope}`);
2864
+ checkOperandDomain(constraint, targetTypeCode);
2865
+ const key = `${added.scope}:${added.path.toLowerCase()}`;
2866
+ if (this.draft.groupPathKeys.has(key)) throw new CallciumError("DUPLICATE_PATH_IN_GROUP", `Duplicate path ${added.path} in the same group`);
2867
+ this.draft.groupPathKeys.add(key);
2868
+ this.draft.groups[this.draft.groups.length - 1].push(added);
2591
2869
  return this;
2592
2870
  }
2593
2871
  or() {
2594
2872
  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");
2595
2873
  this.draft.groups.push([]);
2596
- this.draft.pathHashes.push(/* @__PURE__ */ new Set());
2874
+ this.draft.groupPathKeys.clear();
2597
2875
  return this;
2598
2876
  }
2599
2877
  build() {
@@ -2625,167 +2903,16 @@ var PolicyBuilder = class PolicyBuilder {
2625
2903
  }
2626
2904
  };
2627
2905
  //#endregion
2628
- //#region src/constraint.ts
2629
- function addressBody(value) {
2630
- return toAddress(value).slice(2);
2631
- }
2632
- function encodeWord(value) {
2633
- const word = new Uint8Array(32);
2634
- if (typeof value === "boolean") {
2635
- word[31] = value ? 1 : 0;
2636
- return word;
2637
- }
2638
- if (typeof value === "string") {
2639
- word.set(hexToBytes(addressBody(value)), 12);
2640
- return word;
2641
- }
2642
- let bigValue = typeof value === "number" ? BigInt(value) : value;
2643
- for (let i = 31; i >= 0; i--) {
2644
- word[i] = Number(bigValue & 255n);
2645
- bigValue >>= 8n;
2646
- }
2647
- return word;
2648
- }
2649
- function singleOp(opCode, value) {
2650
- const buffer = new Uint8Array(33);
2651
- buffer[0] = opCode;
2652
- buffer.set(encodeWord(value), 1);
2653
- return bytesToHex(buffer);
2654
- }
2655
- function rangeOp(opCode, min, max) {
2656
- if (min > max) throw new CallciumError("INVALID_RANGE", `Range min (${min}) must not exceed max (${max})`);
2657
- const buffer = new Uint8Array(65);
2658
- buffer[0] = opCode;
2659
- buffer.set(encodeWord(min), 1);
2660
- buffer.set(encodeWord(max), 33);
2661
- return bytesToHex(buffer);
2662
- }
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
- }
2667
- function setOp(opCode, values) {
2668
- const bigs = values.map((v) => {
2669
- if (typeof v === "bigint") return v;
2670
- if (typeof v === "number") return BigInt(v);
2671
- if (typeof v === "boolean") return v ? 1n : 0n;
2672
- return BigInt("0x" + addressBody(v));
2673
- });
2674
- const deduped = [...new Set(bigs)].toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0);
2675
- if (deduped.length === 0) throw new CallciumError("EMPTY_SET", "Set must contain at least one value");
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
- const buffer = new Uint8Array(1 + deduped.length * 32);
2678
- buffer[0] = opCode;
2679
- for (let i = 0; i < deduped.length; i++) buffer.set(encodeWord(deduped[i]), 1 + i * 32);
2680
- return bytesToHex(buffer);
2681
- }
2682
- var ConstraintBuilder = class {
2683
- scope;
2684
- path;
2685
- operators;
2686
- constructor(scope, path) {
2687
- this.scope = scope;
2688
- this.path = path;
2689
- this.operators = [];
2690
- }
2691
- push(opHex) {
2692
- this.operators.push(opHex);
2693
- return this;
2694
- }
2695
- eq(value) {
2696
- return this.push(singleOp(Op.EQ, value));
2697
- }
2698
- neq(value) {
2699
- return this.push(singleOp(Op.EQ | Op.NOT, value));
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
- }
2707
- gt(bound) {
2708
- return this.push(singleOp(Op.GT, bound));
2709
- }
2710
- lt(bound) {
2711
- return this.push(singleOp(Op.LT, bound));
2712
- }
2713
- gte(bound) {
2714
- return this.push(singleOp(Op.GTE, bound));
2715
- }
2716
- lte(bound) {
2717
- return this.push(singleOp(Op.LTE, bound));
2718
- }
2719
- between(min, max) {
2720
- return this.push(rangeOp(Op.BETWEEN, BigInt(min), BigInt(max)));
2721
- }
2722
- isIn(values) {
2723
- return this.push(setOp(Op.IN, values));
2724
- }
2725
- notIn(values) {
2726
- return this.push(setOp(Op.IN | Op.NOT, values));
2727
- }
2728
- bitmaskAll(mask) {
2729
- return this.push(singleOp(Op.BITMASK_ALL, mask));
2730
- }
2731
- bitmaskAny(mask) {
2732
- return this.push(singleOp(Op.BITMASK_ANY, mask));
2733
- }
2734
- bitmaskNone(mask) {
2735
- return this.push(singleOp(Op.BITMASK_NONE, mask));
2736
- }
2737
- lengthEq(n) {
2738
- return this.push(singleOp(Op.LENGTH_EQ, n));
2739
- }
2740
- lengthGt(n) {
2741
- return this.push(singleOp(Op.LENGTH_GT, n));
2742
- }
2743
- lengthLt(n) {
2744
- return this.push(singleOp(Op.LENGTH_LT, n));
2745
- }
2746
- lengthGte(n) {
2747
- return this.push(singleOp(Op.LENGTH_GTE, n));
2748
- }
2749
- lengthLte(n) {
2750
- return this.push(singleOp(Op.LENGTH_LTE, n));
2751
- }
2752
- lengthBetween(min, max) {
2753
- return this.push(rangeOp(Op.LENGTH_BETWEEN, BigInt(min), BigInt(max)));
2754
- }
2755
- };
2756
- function arg(...steps) {
2757
- return new ConstraintBuilder(Scope.CALLDATA, encodePath(steps));
2758
- }
2759
- function msgSender() {
2760
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.MSG_SENDER]));
2761
- }
2762
- function msgValue() {
2763
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.MSG_VALUE]));
2764
- }
2765
- function blockTimestamp() {
2766
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.BLOCK_TIMESTAMP]));
2767
- }
2768
- function blockNumber() {
2769
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.BLOCK_NUMBER]));
2770
- }
2771
- function chainId() {
2772
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.CHAIN_ID]));
2773
- }
2774
- function txOrigin() {
2775
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.TX_ORIGIN]));
2776
- }
2777
- function baseFee() {
2778
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.BASE_FEE]));
2779
- }
2780
- function gasPrice() {
2781
- return new ConstraintBuilder(Scope.CONTEXT, encodePath([ContextProperty.GAS_PRICE]));
2782
- }
2783
- //#endregion
2784
2906
  //#region src/calldata-reader.ts
2785
2907
  function loadWord(callData, offset) {
2786
2908
  if (offset < 0 || offset + 32 > callData.length) return { code: "CALLDATA_OUT_OF_BOUNDS" };
2787
2909
  return callData.subarray(offset, offset + 32);
2788
2910
  }
2911
+ function readLength(callData, head) {
2912
+ const word = loadWord(callData, head);
2913
+ if (word instanceof Uint8Array) return toBigInt(word, 0);
2914
+ return word;
2915
+ }
2789
2916
  function readPointer(callData, head) {
2790
2917
  const word = loadWord(callData, head);
2791
2918
  if (word instanceof Uint8Array) {
@@ -2802,8 +2929,10 @@ const abortViolationCodes = new Set([
2802
2929
  "NON_CANONICAL_VALUE",
2803
2930
  "QUANTIFIER_LIMIT_EXCEEDED"
2804
2931
  ]);
2805
- function addressToBigInt(hex) {
2806
- return BigInt(toAddress(hex));
2932
+ function contextValueToWord(value) {
2933
+ if (typeof value === "string") return BigInt(toAddress(value));
2934
+ if (BigInt.asUintN(256, value) !== value) throw new CallciumError("CONTEXT_VALUE_OVERFLOW", `Context value ${value} is outside the range a 32-byte word represents`);
2935
+ return value;
2807
2936
  }
2808
2937
  function check(policy, callData, context) {
2809
2938
  const { policy: decoded, data: policyBytes } = decodePolicy(policy);
@@ -2870,7 +2999,11 @@ function resolveContextOperand(operandData, context) {
2870
2999
  const propertyInfo = lookupContextProperty(Number(toBigInt(operandData, 0)));
2871
3000
  const value = context?.[propertyInfo.contextKey];
2872
3001
  if (value === void 0) return { ctxTypeCode: propertyInfo.typeCode };
2873
- return typeof value === "string" ? addressToBigInt(value) : value;
3002
+ return contextValueToWord(value);
3003
+ }
3004
+ function referenceOperand(opCode, operandData, context) {
3005
+ if ((opCode & ~Op.NOT) !== Op.EQ_CTX) return void 0;
3006
+ return resolveContextOperand(operandData, context);
2874
3007
  }
2875
3008
  function readTargetBlock(hint, targetOffset) {
2876
3009
  return {
@@ -2882,6 +3015,7 @@ function readTargetBlock(hint, targetOffset) {
2882
3015
  function follow(callData, from, at) {
2883
3016
  const word = readPointer(callData, at);
2884
3017
  if (typeof word !== "number") return word;
3018
+ if (from > callData.length || word > callData.length - from) return { code: "CALLDATA_OUT_OF_BOUNDS" };
2885
3019
  return from + word;
2886
3020
  }
2887
3021
  function chainResolve(hint, callData, base, hopsOffset, hopCount) {
@@ -2897,8 +3031,8 @@ function chainResolve(hint, callData, base, hopsOffset, hopCount) {
2897
3031
  const meta = readU16(hint, hop + PolicyFormat.HINT_HOP_META_OFFSET);
2898
3032
  let elems = base;
2899
3033
  if ((meta & PolicyFormat.HINT_META_DYNAMIC_ARRAY) !== 0) {
2900
- const length = readPointer(callData, elems);
2901
- if (typeof length !== "number") return length;
3034
+ const length = readLength(callData, elems);
3035
+ if (typeof length !== "bigint") return length;
2902
3036
  if (index >= length) return { code: "ARRAY_INDEX_OUT_OF_BOUNDS" };
2903
3037
  elems += 32;
2904
3038
  }
@@ -2911,21 +3045,19 @@ function chainResolve(hint, callData, base, hopsOffset, hopCount) {
2911
3045
  }
2912
3046
  return base;
2913
3047
  }
2914
- function evalTarget(callData, target, block, opCode, operandData, context) {
3048
+ function evalTarget(callData, target, block, opCode, operandData, ctxOperand) {
2915
3049
  const typeCode = block.typeCode;
2916
3050
  if (isLengthValidType(typeCode)) {
2917
- if (!isLengthOp(opCode)) throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Value operator on a target without a scalar word");
2918
- const length = readPointer(callData, target);
2919
- if (typeof length !== "number") return { error: length.code };
3051
+ const length = readLength(callData, target);
3052
+ if (typeof length !== "bigint") return { error: length.code };
2920
3053
  const stride = typeCode === TypeCode.DYNAMIC_ARRAY ? (block.targetMeta & PolicyFormat.HINT_META_STRIDE_MASK) * 32 : 1;
2921
3054
  const room = callData.length - (target + 32);
2922
- if (stride !== 0 && length > Math.floor(room / stride)) return { error: "CALLDATA_OUT_OF_BOUNDS" };
3055
+ if (stride !== 0 && length > BigInt(Math.floor(room / stride))) return { error: "CALLDATA_OUT_OF_BOUNDS" };
2923
3056
  return {
2924
3057
  passed: applyOperator(opCode, 0n, length, operandData, typeCode),
2925
- value: BigInt(length)
3058
+ value: length
2926
3059
  };
2927
3060
  }
2928
- if (classifyTypeCode(typeCode).typeClass !== "elementary") throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Operator target does not carry a scalar word");
2929
3061
  const word = loadWord(callData, target);
2930
3062
  if (!(word instanceof Uint8Array)) return { error: word.code };
2931
3063
  const value = toBigInt(word, 0);
@@ -2933,57 +3065,50 @@ function evalTarget(callData, target, block, opCode, operandData, context) {
2933
3065
  error: "NON_CANONICAL_VALUE",
2934
3066
  value
2935
3067
  };
2936
- if ((opCode & ~Op.NOT) === Op.EQ_CTX) {
2937
- const operand = resolveContextOperand(operandData, context);
2938
- if (typeof operand !== "bigint") return {
3068
+ if (ctxOperand !== void 0) {
3069
+ if (typeof ctxOperand !== "bigint") return {
2939
3070
  error: "MISSING_CONTEXT",
2940
- ctxTypeCode: operand.ctxTypeCode
3071
+ ctxTypeCode: ctxOperand.ctxTypeCode
2941
3072
  };
2942
3073
  return {
2943
- passed: value === operand !== ((opCode & Op.NOT) !== 0),
3074
+ passed: value === ctxOperand !== ((opCode & Op.NOT) !== 0),
2944
3075
  value,
2945
- ctxOperand: operand
3076
+ ctxOperand
2946
3077
  };
2947
3078
  }
2948
3079
  return {
2949
- passed: applyOperator(opCode, value, 32, operandData, typeCode),
3080
+ passed: applyOperator(opCode, value, 32n, operandData, typeCode),
2950
3081
  value
2951
3082
  };
2952
3083
  }
2953
- function navigationViolation(frame, code, elementIndex) {
3084
+ function ruleFields(frame) {
2954
3085
  return {
2955
3086
  group: frame.group,
2956
3087
  rule: frame.rule,
2957
- code,
2958
3088
  scope: Scope.CALLDATA,
2959
3089
  path: frame.path,
2960
3090
  opCode: frame.opCode,
2961
3091
  operandData: bytesToHex(frame.operandData),
2962
- typeCode: frame.typeCode,
3092
+ typeCode: frame.typeCode
3093
+ };
3094
+ }
3095
+ function navigationViolation(frame, code, elementIndex) {
3096
+ return {
3097
+ code,
3098
+ ...ruleFields(frame),
2963
3099
  ...elementIndex !== void 0 && { elementIndex }
2964
3100
  };
2965
3101
  }
2966
3102
  function targetViolation(frame, result, elementIndex) {
2967
3103
  if ("error" in result) {
2968
3104
  if (result.error === "MISSING_CONTEXT") return {
2969
- group: frame.group,
2970
- rule: frame.rule,
2971
3105
  code: result.error,
2972
- scope: Scope.CALLDATA,
2973
- path: frame.path,
2974
- opCode: frame.opCode,
2975
- operandData: bytesToHex(frame.operandData),
3106
+ ...ruleFields(frame),
2976
3107
  typeCode: result.ctxTypeCode
2977
3108
  };
2978
3109
  if (result.error === "NON_CANONICAL_VALUE") return {
2979
- group: frame.group,
2980
- rule: frame.rule,
2981
3110
  code: result.error,
2982
- scope: Scope.CALLDATA,
2983
- path: frame.path,
2984
- opCode: frame.opCode,
2985
- operandData: bytesToHex(frame.operandData),
2986
- typeCode: frame.typeCode,
3111
+ ...ruleFields(frame),
2987
3112
  resolvedValue: bigintToHex(result.value),
2988
3113
  ...elementIndex !== void 0 && { elementIndex }
2989
3114
  };
@@ -2991,14 +3116,8 @@ function targetViolation(frame, result, elementIndex) {
2991
3116
  }
2992
3117
  if (result.passed) return null;
2993
3118
  return {
2994
- group: frame.group,
2995
- rule: frame.rule,
2996
3119
  code: "VALUE_MISMATCH",
2997
- scope: Scope.CALLDATA,
2998
- path: frame.path,
2999
- opCode: frame.opCode,
3000
- operandData: bytesToHex(frame.operandData),
3001
- typeCode: frame.typeCode,
3120
+ ...ruleFields(frame),
3002
3121
  resolvedValue: bigintToHex(result.value),
3003
3122
  ...result.ctxOperand !== void 0 && { resolvedOperand: bigintToHex(result.ctxOperand) },
3004
3123
  ...elementIndex !== void 0 && { elementIndex }
@@ -3021,7 +3140,7 @@ function evaluateCalldataRule(hint, callDataBytes, baseOffset, opCode, operandDa
3021
3140
  };
3022
3141
  const base = chainResolve(hint, callDataBytes, baseOffset, hopsOffset, hopCount);
3023
3142
  if (typeof base !== "number") return navigationViolation(frame, base.code);
3024
- return targetViolation(frame, evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, context));
3143
+ return targetViolation(frame, evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, referenceOperand(opCode, operandData, context)));
3025
3144
  }
3026
3145
  function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, hopCount, opCode, operandData, groupIndex, ruleIndex, pathHex, context) {
3027
3146
  const frameOffset = hopsOffset + hopCount * PolicyFormat.HINT_HOP_SIZE;
@@ -3040,23 +3159,23 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
3040
3159
  const chained = chainResolve(hint, callDataBytes, baseOffset, hopsOffset, hopCount);
3041
3160
  if (typeof chained !== "number") return navigationViolation(frame, chained.code);
3042
3161
  let elems = chained + readU32(hint, frameOffset);
3043
- let count = readU16(hint, frameOffset + PolicyFormat.HINT_FRAME_COUNT_OFFSET);
3044
- if (count === 0) {
3045
- const length = readPointer(callDataBytes, elems);
3046
- if (typeof length !== "number") return navigationViolation(frame, length.code);
3162
+ let count = BigInt(readU16(hint, frameOffset + PolicyFormat.HINT_FRAME_COUNT_OFFSET));
3163
+ if (count === 0n) {
3164
+ const length = readLength(callDataBytes, elems);
3165
+ if (typeof length !== "bigint") return navigationViolation(frame, length.code);
3047
3166
  count = length;
3048
3167
  elems += 32;
3049
3168
  }
3050
- if (count > PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH) return {
3169
+ if (count > BigInt(PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH)) return {
3051
3170
  group: groupIndex,
3052
3171
  rule: ruleIndex,
3053
3172
  code: "QUANTIFIER_LIMIT_EXCEEDED",
3054
3173
  scope: Scope.CALLDATA,
3055
3174
  path: pathHex,
3056
- resolvedValue: bigintToHex(BigInt(count))
3175
+ resolvedValue: bigintToHex(count)
3057
3176
  };
3058
3177
  const isUniversal = kind === PolicyFormat.HINT_KIND_ALL;
3059
- if (count === 0) {
3178
+ if (count === 0n) {
3060
3179
  if (isUniversal) return null;
3061
3180
  return {
3062
3181
  group: groupIndex,
@@ -3069,7 +3188,7 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
3069
3188
  const meta = readU16(hint, frameOffset + PolicyFormat.HINT_FRAME_META_OFFSET);
3070
3189
  const elemStride = (meta & PolicyFormat.HINT_META_STRIDE_MASK) * 32;
3071
3190
  const elemIsDynamic = (meta & PolicyFormat.HINT_META_ELEM_DYNAMIC) !== 0;
3072
- let ctxOperand;
3191
+ const reference = referenceOperand(opCode, operandData, context);
3073
3192
  for (let elemIndex = 0; elemIndex < count; elemIndex++) {
3074
3193
  const slot = elems + elemIndex * elemStride;
3075
3194
  let elem = slot;
@@ -3084,65 +3203,51 @@ function evaluateQuantified(hint, callDataBytes, baseOffset, kind, hopsOffset, h
3084
3203
  if (typeof chainedElem !== "number") return navigationViolation(frame, chainedElem.code, elemIndex);
3085
3204
  base = chainedElem;
3086
3205
  }
3087
- const applied = evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, context);
3206
+ const applied = evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, reference);
3088
3207
  if ("error" in applied) return targetViolation(frame, applied, elemIndex);
3089
3208
  if (applied.passed) {
3090
3209
  if (!isUniversal) return null;
3091
3210
  } else if (isUniversal) return targetViolation(frame, applied, elemIndex);
3092
- ctxOperand = applied.ctxOperand;
3093
3211
  }
3094
3212
  if (isUniversal) return null;
3095
3213
  return {
3096
- group: groupIndex,
3097
- rule: ruleIndex,
3098
3214
  code: "VALUE_MISMATCH",
3099
- scope: Scope.CALLDATA,
3100
- path: pathHex,
3101
- opCode,
3102
- operandData: bytesToHex(operandData),
3103
- typeCode: block.typeCode,
3104
- ...ctxOperand !== void 0 && { resolvedOperand: bigintToHex(ctxOperand) }
3215
+ ...ruleFields(frame),
3216
+ ...typeof reference === "bigint" && { resolvedOperand: bigintToHex(reference) }
3105
3217
  };
3106
3218
  }
3107
3219
  function evaluateContextRule(pathBytes, opCode, operandData, groupIndex, ruleIndex, pathHex, context) {
3108
- const propertyId = readU16(pathBytes, 0);
3109
- if (propertyId > MAX_CONTEXT_PROPERTY_ID) throw new CallciumError("UNKNOWN_CONTEXT_PROPERTY", `Unknown context property ID 0x${propertyId.toString(16).padStart(4, "0")}`);
3110
- const propertyInfo = lookupContextProperty(propertyId);
3220
+ const propertyInfo = lookupContextProperty(readU16(pathBytes, 0));
3111
3221
  const contextValue = context?.[propertyInfo.contextKey];
3112
- if (contextValue === void 0) return {
3222
+ const site = {
3113
3223
  group: groupIndex,
3114
3224
  rule: ruleIndex,
3115
- code: "MISSING_CONTEXT",
3116
3225
  scope: Scope.CONTEXT,
3117
- path: pathHex,
3226
+ path: pathHex
3227
+ };
3228
+ if (contextValue === void 0) return {
3229
+ code: "MISSING_CONTEXT",
3230
+ ...site,
3118
3231
  typeCode: propertyInfo.typeCode
3119
3232
  };
3120
- let value;
3121
- if (typeof contextValue === "string") value = addressToBigInt(contextValue);
3122
- else value = contextValue;
3233
+ const value = contextValueToWord(contextValue);
3123
3234
  let result;
3124
3235
  let ctxOperand;
3125
3236
  if ((opCode & ~Op.NOT) === Op.EQ_CTX) {
3126
3237
  const operand = resolveContextOperand(operandData, context);
3127
3238
  if (typeof operand !== "bigint") return {
3128
- group: groupIndex,
3129
- rule: ruleIndex,
3130
3239
  code: "MISSING_CONTEXT",
3131
- scope: Scope.CONTEXT,
3132
- path: pathHex,
3240
+ ...site,
3133
3241
  opCode,
3134
3242
  operandData: bytesToHex(operandData),
3135
3243
  typeCode: operand.ctxTypeCode
3136
3244
  };
3137
3245
  result = value === operand !== ((opCode & Op.NOT) !== 0);
3138
3246
  ctxOperand = operand;
3139
- } else result = applyOperator(opCode, value, 32, operandData, propertyInfo.typeCode);
3247
+ } else result = applyOperator(opCode, value, 32n, operandData, propertyInfo.typeCode);
3140
3248
  if (!result) return {
3141
- group: groupIndex,
3142
- rule: ruleIndex,
3143
3249
  code: "VALUE_MISMATCH",
3144
- scope: Scope.CONTEXT,
3145
- path: pathHex,
3250
+ ...site,
3146
3251
  opCode,
3147
3252
  operandData: bytesToHex(operandData),
3148
3253
  typeCode: propertyInfo.typeCode,
@@ -3156,6 +3261,6 @@ const PolicyEnforcer = {
3156
3261
  enforce
3157
3262
  };
3158
3263
  //#endregion
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 };
3264
+ 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, findContextProperty, gasPrice, hexToBytes, isLengthOp, isOpAllowed, isQuantifier, lookupContextProperty, lookupOp, lookupQuantifier, lookupScope, lookupTypeCode, msgSender, msgValue, parsePathSteps, toAddress, txOrigin };
3160
3265
 
3161
3266
  //# sourceMappingURL=index.js.map