@callcium/sdk 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +202 -145
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +763 -667
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/bytes.ts +9 -9
- package/src/calldata-reader.ts +14 -1
- package/src/constants.ts +85 -181
- package/src/constraint.ts +131 -94
- package/src/descriptor-coder.ts +53 -84
- package/src/descriptor.ts +65 -36
- package/src/errors.ts +12 -5
- package/src/index.ts +8 -17
- package/src/operators.ts +147 -11
- package/src/path.ts +80 -0
- package/src/policy-builder.ts +107 -44
- package/src/policy-coder.ts +102 -77
- package/src/policy-enforcer.ts +98 -128
- package/src/policy-validator.ts +70 -56
- package/src/signature.ts +21 -19
- package/src/type-desc.ts +15 -15
- package/src/types.ts +70 -3
- package/src/validation-issue.ts +4 -4
package/src/policy-enforcer.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import { hexToBytes, bytesToHex, readU16, readU32, bigintToHex } from "./bytes";
|
|
2
|
-
import { loadWord, readPointer } from "./calldata-reader";
|
|
3
|
-
import {
|
|
4
|
-
PolicyFormat as PF,
|
|
5
|
-
Scope,
|
|
6
|
-
MAX_CONTEXT_PROPERTY_ID,
|
|
7
|
-
Limits,
|
|
8
|
-
Op,
|
|
9
|
-
TypeCode,
|
|
10
|
-
classifyTypeCode,
|
|
11
|
-
lookupContextProperty,
|
|
12
|
-
} from "./constants";
|
|
1
|
+
import { hexToBytes, bytesToHex, readU16, readU32, bigintToHex, toAddress } from "./bytes";
|
|
2
|
+
import { loadWord, readLength, readPointer } from "./calldata-reader";
|
|
3
|
+
import { PolicyFormat as PF, Scope, Op, TypeCode, lookupContextProperty } from "./constants";
|
|
13
4
|
import { CallciumError, PolicyViolationError } from "./errors";
|
|
14
|
-
import { applyOperator, toBigInt,
|
|
5
|
+
import { applyOperator, toBigInt, isLengthValidType, canonicalize } from "./operators";
|
|
15
6
|
import { decodePolicy } from "./policy-coder";
|
|
16
7
|
|
|
17
8
|
import type { ReadResult } from "./calldata-reader";
|
|
18
9
|
import type {
|
|
10
|
+
Address,
|
|
19
11
|
Context,
|
|
20
12
|
DecodedRule,
|
|
21
13
|
EnforceResult,
|
|
@@ -30,20 +22,27 @@ import type {
|
|
|
30
22
|
// Helpers
|
|
31
23
|
///////////////////////////////////////////////////////////////////////////
|
|
32
24
|
|
|
33
|
-
/** Violations that abort evaluation instead of failing only their group
|
|
34
|
-
const
|
|
25
|
+
/** Violations that abort evaluation instead of failing only their group. */
|
|
26
|
+
const abortViolationCodes: ReadonlySet<ViolationCode> = new Set<ViolationCode>([
|
|
35
27
|
"CALLDATA_OUT_OF_BOUNDS",
|
|
36
28
|
"ARRAY_INDEX_OUT_OF_BOUNDS",
|
|
37
29
|
"NON_CANONICAL_VALUE",
|
|
38
30
|
"QUANTIFIER_LIMIT_EXCEEDED",
|
|
39
31
|
]);
|
|
40
32
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Read a supplied context value as the 32-byte word that carries it.
|
|
35
|
+
* @throws {CallciumError} When an integer lies outside the range a 32-byte word represents.
|
|
36
|
+
*/
|
|
37
|
+
function contextValueToWord(value: bigint | Address): bigint {
|
|
38
|
+
if (typeof value === "string") return BigInt(toAddress(value));
|
|
39
|
+
if (BigInt.asUintN(256, value) !== value) {
|
|
40
|
+
throw new CallciumError(
|
|
41
|
+
"CONTEXT_VALUE_OVERFLOW",
|
|
42
|
+
`Context value ${value} is outside the range a 32-byte word represents`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -56,7 +55,8 @@ function addressToBigInt(hex: string): bigint {
|
|
|
56
55
|
* @param callData - ABI-encoded call data as 0x-prefixed hex string.
|
|
57
56
|
* @param context - Optional execution context for context-scoped rules.
|
|
58
57
|
* @returns Pass with matched group index, or fail with one violation per evaluated failing group.
|
|
59
|
-
* @throws {CallciumError} If the policy blob is structurally malformed
|
|
58
|
+
* @throws {CallciumError} If the policy blob is structurally malformed, or a context value a rule
|
|
59
|
+
* reads is not an address or an integer a 32-byte word represents.
|
|
60
60
|
*/
|
|
61
61
|
function check(policy: Hex, callData: Hex, context?: Context): EnforceResult {
|
|
62
62
|
const { policy: decoded, data: policyBytes } = decodePolicy(policy);
|
|
@@ -100,8 +100,8 @@ function check(policy: Hex, callData: Hex, context?: Context): EnforceResult {
|
|
|
100
100
|
const violation = evaluateRule(rule, policyBytes, callDataBytes, baseOffset, groupIndex, ruleIndex, context);
|
|
101
101
|
if (violation !== null) {
|
|
102
102
|
allViolations.push(violation);
|
|
103
|
-
if (
|
|
104
|
-
// Abort violations reject the policy outright; later groups are not consulted
|
|
103
|
+
if (abortViolationCodes.has(violation.code)) {
|
|
104
|
+
// Abort violations reject the policy outright; later groups are not consulted.
|
|
105
105
|
return { ok: false, violations: allViolations };
|
|
106
106
|
}
|
|
107
107
|
groupFailed = true;
|
|
@@ -123,7 +123,8 @@ function check(policy: Hex, callData: Hex, context?: Context): EnforceResult {
|
|
|
123
123
|
* @param callData - ABI-encoded call data as 0x-prefixed hex string.
|
|
124
124
|
* @param context - Optional execution context for context-scoped rules.
|
|
125
125
|
* @throws {PolicyViolationError} If the policy rejects the call data.
|
|
126
|
-
* @throws {CallciumError} If the policy blob is structurally malformed
|
|
126
|
+
* @throws {CallciumError} If the policy blob is structurally malformed, or a context value a rule
|
|
127
|
+
* reads is not an address or an integer a 32-byte word represents.
|
|
127
128
|
*/
|
|
128
129
|
function enforce(policy: Hex, callData: Hex, context?: Context): void {
|
|
129
130
|
const result = check(policy, callData, context);
|
|
@@ -177,15 +178,24 @@ function evaluateRule(
|
|
|
177
178
|
);
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
/** An `EQ_CTX` operand resolved to its word, or the missing property's type code. */
|
|
182
|
+
type ReferenceOperand = bigint | { ctxTypeCode: number };
|
|
183
|
+
|
|
180
184
|
/**
|
|
181
185
|
* Resolve an EQ_CTX operand word to the referenced context property's value as a raw word.
|
|
182
186
|
* Returns the missing property's type code when the context does not supply it.
|
|
183
187
|
*/
|
|
184
|
-
function resolveContextOperand(operandData: Uint8Array, context?: Context):
|
|
185
|
-
const
|
|
186
|
-
const value = context?.[
|
|
187
|
-
if (value === undefined) return { ctxTypeCode:
|
|
188
|
-
return
|
|
188
|
+
function resolveContextOperand(operandData: Uint8Array, context?: Context): ReferenceOperand {
|
|
189
|
+
const propertyInfo = lookupContextProperty(Number(toBigInt(operandData, 0)));
|
|
190
|
+
const value = context?.[propertyInfo.contextKey];
|
|
191
|
+
if (value === undefined) return { ctxTypeCode: propertyInfo.typeCode };
|
|
192
|
+
return contextValueToWord(value);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Resolve the reference operand of an `EQ_CTX` rule; every other operator reads a literal. */
|
|
196
|
+
function referenceOperand(opCode: number, operandData: Uint8Array, context?: Context): ReferenceOperand | undefined {
|
|
197
|
+
if ((opCode & ~Op.NOT) !== Op.EQ_CTX) return undefined;
|
|
198
|
+
return resolveContextOperand(operandData, context);
|
|
189
199
|
}
|
|
190
200
|
|
|
191
201
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -237,8 +247,8 @@ function chainResolve(
|
|
|
237
247
|
const meta = readU16(hint, hop + PF.HINT_HOP_META_OFFSET);
|
|
238
248
|
let elems = base;
|
|
239
249
|
if ((meta & PF.HINT_META_DYNAMIC_ARRAY) !== 0) {
|
|
240
|
-
const length =
|
|
241
|
-
if (typeof length !== "
|
|
250
|
+
const length = readLength(callData, elems);
|
|
251
|
+
if (typeof length !== "bigint") return length;
|
|
242
252
|
if (index >= length) return { code: "ARRAY_INDEX_OUT_OF_BOUNDS" };
|
|
243
253
|
elems += 32;
|
|
244
254
|
}
|
|
@@ -278,30 +288,24 @@ function evalTarget(
|
|
|
278
288
|
block: TargetBlock,
|
|
279
289
|
opCode: number,
|
|
280
290
|
operandData: Uint8Array,
|
|
281
|
-
|
|
291
|
+
ctxOperand?: ReferenceOperand,
|
|
282
292
|
): TargetResult {
|
|
283
293
|
const typeCode = block.typeCode;
|
|
284
294
|
|
|
285
295
|
// A dynamic target's chain ends at its payload, so the word there is the declared length.
|
|
286
296
|
if (isLengthValidType(typeCode)) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
const length = readPointer(callData, target);
|
|
291
|
-
if (typeof length !== "number") return { error: length.code };
|
|
297
|
+
const length = readLength(callData, target);
|
|
298
|
+
if (typeof length !== "bigint") return { error: length.code };
|
|
292
299
|
|
|
293
300
|
// The declared payload of `length` items must lie within calldata past the length word.
|
|
294
301
|
const stride = typeCode === TypeCode.DYNAMIC_ARRAY ? (block.targetMeta & PF.HINT_META_STRIDE_MASK) * 32 : 1;
|
|
295
302
|
const room = callData.length - (target + 32);
|
|
296
|
-
if (stride !== 0 && length > Math.floor(room / stride)) return { error: "CALLDATA_OUT_OF_BOUNDS" };
|
|
303
|
+
if (stride !== 0 && length > BigInt(Math.floor(room / stride))) return { error: "CALLDATA_OUT_OF_BOUNDS" };
|
|
297
304
|
|
|
298
305
|
const passed = applyOperator(opCode, 0n, length, operandData, typeCode);
|
|
299
|
-
return { passed, value:
|
|
306
|
+
return { passed, value: length };
|
|
300
307
|
}
|
|
301
308
|
|
|
302
|
-
if (classifyTypeCode(typeCode).typeClass !== "elementary") {
|
|
303
|
-
throw new CallciumError("OPERATOR_TARGET_MISMATCH", "Operator target does not carry a scalar word.");
|
|
304
|
-
}
|
|
305
309
|
const word = loadWord(callData, target);
|
|
306
310
|
if (!(word instanceof Uint8Array)) return { error: word.code };
|
|
307
311
|
|
|
@@ -310,14 +314,13 @@ function evalTarget(
|
|
|
310
314
|
return { error: "NON_CANONICAL_VALUE", value };
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return { passed, value, ctxOperand: operand };
|
|
317
|
+
if (ctxOperand !== undefined) {
|
|
318
|
+
if (typeof ctxOperand !== "bigint") return { error: "MISSING_CONTEXT", ctxTypeCode: ctxOperand.ctxTypeCode };
|
|
319
|
+
const passed = (value === ctxOperand) !== ((opCode & Op.NOT) !== 0);
|
|
320
|
+
return { passed, value, ctxOperand };
|
|
318
321
|
}
|
|
319
322
|
|
|
320
|
-
const passed = applyOperator(opCode, value,
|
|
323
|
+
const passed = applyOperator(opCode, value, 32n, operandData, typeCode);
|
|
321
324
|
return { passed, value };
|
|
322
325
|
}
|
|
323
326
|
|
|
@@ -335,17 +338,24 @@ type RuleFrame = {
|
|
|
335
338
|
typeCode: number;
|
|
336
339
|
};
|
|
337
340
|
|
|
338
|
-
/**
|
|
339
|
-
function
|
|
341
|
+
/** The coordinates, target and operator fields every violation of a calldata rule carries. */
|
|
342
|
+
function ruleFields(frame: RuleFrame) {
|
|
340
343
|
return {
|
|
341
344
|
group: frame.group,
|
|
342
345
|
rule: frame.rule,
|
|
343
|
-
code,
|
|
344
346
|
scope: Scope.CALLDATA,
|
|
345
347
|
path: frame.path,
|
|
346
348
|
opCode: frame.opCode,
|
|
347
349
|
operandData: bytesToHex(frame.operandData),
|
|
348
350
|
typeCode: frame.typeCode,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Build a navigation violation for a read the enforcer cannot perform. */
|
|
355
|
+
function navigationViolation(frame: RuleFrame, code: NavigationViolationCode, elementIndex?: number): Violation {
|
|
356
|
+
return {
|
|
357
|
+
code,
|
|
358
|
+
...ruleFields(frame),
|
|
349
359
|
...(elementIndex !== undefined && { elementIndex }),
|
|
350
360
|
};
|
|
351
361
|
}
|
|
@@ -354,27 +364,17 @@ function navigationViolation(frame: RuleFrame, code: NavigationViolationCode, el
|
|
|
354
364
|
function targetViolation(frame: RuleFrame, result: TargetResult, elementIndex?: number): Violation | null {
|
|
355
365
|
if ("error" in result) {
|
|
356
366
|
if (result.error === "MISSING_CONTEXT") {
|
|
367
|
+
// The type reported is the absent property's, not the target the operator reads.
|
|
357
368
|
return {
|
|
358
|
-
group: frame.group,
|
|
359
|
-
rule: frame.rule,
|
|
360
369
|
code: result.error,
|
|
361
|
-
|
|
362
|
-
path: frame.path,
|
|
363
|
-
opCode: frame.opCode,
|
|
364
|
-
operandData: bytesToHex(frame.operandData),
|
|
370
|
+
...ruleFields(frame),
|
|
365
371
|
typeCode: result.ctxTypeCode,
|
|
366
372
|
};
|
|
367
373
|
}
|
|
368
374
|
if (result.error === "NON_CANONICAL_VALUE") {
|
|
369
375
|
return {
|
|
370
|
-
group: frame.group,
|
|
371
|
-
rule: frame.rule,
|
|
372
376
|
code: result.error,
|
|
373
|
-
|
|
374
|
-
path: frame.path,
|
|
375
|
-
opCode: frame.opCode,
|
|
376
|
-
operandData: bytesToHex(frame.operandData),
|
|
377
|
-
typeCode: frame.typeCode,
|
|
377
|
+
...ruleFields(frame),
|
|
378
378
|
resolvedValue: bigintToHex(result.value),
|
|
379
379
|
...(elementIndex !== undefined && { elementIndex }),
|
|
380
380
|
};
|
|
@@ -384,14 +384,8 @@ function targetViolation(frame: RuleFrame, result: TargetResult, elementIndex?:
|
|
|
384
384
|
|
|
385
385
|
if (result.passed) return null;
|
|
386
386
|
return {
|
|
387
|
-
group: frame.group,
|
|
388
|
-
rule: frame.rule,
|
|
389
387
|
code: "VALUE_MISMATCH",
|
|
390
|
-
|
|
391
|
-
path: frame.path,
|
|
392
|
-
opCode: frame.opCode,
|
|
393
|
-
operandData: bytesToHex(frame.operandData),
|
|
394
|
-
typeCode: frame.typeCode,
|
|
388
|
+
...ruleFields(frame),
|
|
395
389
|
resolvedValue: bigintToHex(result.value),
|
|
396
390
|
...(result.ctxOperand !== undefined && { resolvedOperand: bigintToHex(result.ctxOperand) }),
|
|
397
391
|
...(elementIndex !== undefined && { elementIndex }),
|
|
@@ -447,7 +441,14 @@ function evaluateCalldataRule(
|
|
|
447
441
|
|
|
448
442
|
return targetViolation(
|
|
449
443
|
frame,
|
|
450
|
-
evalTarget(
|
|
444
|
+
evalTarget(
|
|
445
|
+
callDataBytes,
|
|
446
|
+
base + block.targetDelta,
|
|
447
|
+
block,
|
|
448
|
+
opCode,
|
|
449
|
+
operandData,
|
|
450
|
+
referenceOperand(opCode, operandData, context),
|
|
451
|
+
),
|
|
451
452
|
);
|
|
452
453
|
}
|
|
453
454
|
|
|
@@ -492,27 +493,27 @@ function evaluateQuantified(
|
|
|
492
493
|
let elems = chained + arrayDelta;
|
|
493
494
|
|
|
494
495
|
// A frame declaring no count spans a dynamic array, whose length word precedes its elements.
|
|
495
|
-
let count = readU16(hint, frameOffset + PF.HINT_FRAME_COUNT_OFFSET);
|
|
496
|
-
if (count ===
|
|
497
|
-
const length =
|
|
498
|
-
if (typeof length !== "
|
|
496
|
+
let count = BigInt(readU16(hint, frameOffset + PF.HINT_FRAME_COUNT_OFFSET));
|
|
497
|
+
if (count === 0n) {
|
|
498
|
+
const length = readLength(callDataBytes, elems);
|
|
499
|
+
if (typeof length !== "bigint") return navigationViolation(frame, length.code);
|
|
499
500
|
count = length;
|
|
500
501
|
elems += 32;
|
|
501
502
|
}
|
|
502
503
|
|
|
503
|
-
if (count >
|
|
504
|
+
if (count > BigInt(PF.MAX_QUANTIFIED_ARRAY_LENGTH)) {
|
|
504
505
|
return {
|
|
505
506
|
group: groupIndex,
|
|
506
507
|
rule: ruleIndex,
|
|
507
508
|
code: "QUANTIFIER_LIMIT_EXCEEDED",
|
|
508
509
|
scope: Scope.CALLDATA,
|
|
509
510
|
path: pathHex,
|
|
510
|
-
resolvedValue: bigintToHex(
|
|
511
|
+
resolvedValue: bigintToHex(count),
|
|
511
512
|
};
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
const isUniversal = kind === PF.HINT_KIND_ALL;
|
|
515
|
-
if (count ===
|
|
516
|
+
if (count === 0n) {
|
|
516
517
|
if (isUniversal) return null;
|
|
517
518
|
return {
|
|
518
519
|
group: groupIndex,
|
|
@@ -527,15 +528,16 @@ function evaluateQuantified(
|
|
|
527
528
|
const elemStride = (meta & PF.HINT_META_STRIDE_MASK) * 32;
|
|
528
529
|
const elemIsDynamic = (meta & PF.HINT_META_ELEM_DYNAMIC) !== 0;
|
|
529
530
|
|
|
530
|
-
// A reference operand resolves identically for every element
|
|
531
|
-
|
|
531
|
+
// A reference operand resolves identically for every element, so it resolves before iterating.
|
|
532
|
+
const reference = referenceOperand(opCode, operandData, context);
|
|
533
|
+
|
|
532
534
|
for (let elemIndex = 0; elemIndex < count; elemIndex++) {
|
|
533
535
|
const slot = elems + elemIndex * elemStride;
|
|
534
536
|
let elem: number = slot;
|
|
535
537
|
if (elemIsDynamic) {
|
|
536
538
|
const followed = follow(callDataBytes, elems, slot);
|
|
537
539
|
// Navigation failures abort under every quantifier: calldata the enforcer cannot read is
|
|
538
|
-
// not an element that merely fails the operator
|
|
540
|
+
// not an element that merely fails the operator.
|
|
539
541
|
if (typeof followed !== "number") return navigationViolation(frame, followed.code, elemIndex);
|
|
540
542
|
elem = followed;
|
|
541
543
|
}
|
|
@@ -547,10 +549,10 @@ function evaluateQuantified(
|
|
|
547
549
|
base = chainedElem;
|
|
548
550
|
}
|
|
549
551
|
|
|
550
|
-
const applied = evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData,
|
|
552
|
+
const applied = evalTarget(callDataBytes, base + block.targetDelta, block, opCode, operandData, reference);
|
|
551
553
|
if ("error" in applied) {
|
|
552
554
|
// Error results end the rule whatever the quantifier: a later element cannot rescue
|
|
553
|
-
// calldata the enforcer cannot read (abort
|
|
555
|
+
// calldata the enforcer cannot read (an abort effect), and a missing context
|
|
554
556
|
// property is missing for every element alike (group-local).
|
|
555
557
|
return targetViolation(frame, applied, elemIndex);
|
|
556
558
|
}
|
|
@@ -561,21 +563,14 @@ function evaluateQuantified(
|
|
|
561
563
|
} else if (isUniversal) {
|
|
562
564
|
return targetViolation(frame, applied, elemIndex);
|
|
563
565
|
}
|
|
564
|
-
ctxOperand = applied.ctxOperand;
|
|
565
566
|
}
|
|
566
567
|
|
|
567
568
|
if (isUniversal) return null;
|
|
568
569
|
// Existential (ANY) failure: every element rejected the constraint.
|
|
569
570
|
return {
|
|
570
|
-
group: groupIndex,
|
|
571
|
-
rule: ruleIndex,
|
|
572
571
|
code: "VALUE_MISMATCH",
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
opCode,
|
|
576
|
-
operandData: bytesToHex(operandData),
|
|
577
|
-
typeCode: block.typeCode,
|
|
578
|
-
...(ctxOperand !== undefined && { resolvedOperand: bigintToHex(ctxOperand) }),
|
|
572
|
+
...ruleFields(frame),
|
|
573
|
+
...(typeof reference === "bigint" && { resolvedOperand: bigintToHex(reference) }),
|
|
579
574
|
};
|
|
580
575
|
}
|
|
581
576
|
|
|
@@ -594,34 +589,15 @@ function evaluateContextRule(
|
|
|
594
589
|
context?: Context,
|
|
595
590
|
): Violation | null {
|
|
596
591
|
const propertyId = readU16(pathBytes, 0);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
"UNKNOWN_CONTEXT_PROPERTY",
|
|
601
|
-
`Unknown context property ID 0x${propertyId.toString(16).padStart(4, "0")}`,
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
const propInfo = lookupContextProperty(propertyId);
|
|
606
|
-
const contextValue = context?.[propInfo.contextKey];
|
|
592
|
+
const propertyInfo = lookupContextProperty(propertyId);
|
|
593
|
+
const contextValue = context?.[propertyInfo.contextKey];
|
|
594
|
+
const site = { group: groupIndex, rule: ruleIndex, scope: Scope.CONTEXT, path: pathHex };
|
|
607
595
|
|
|
608
596
|
if (contextValue === undefined) {
|
|
609
|
-
return {
|
|
610
|
-
group: groupIndex,
|
|
611
|
-
rule: ruleIndex,
|
|
612
|
-
code: "MISSING_CONTEXT",
|
|
613
|
-
scope: Scope.CONTEXT,
|
|
614
|
-
path: pathHex,
|
|
615
|
-
typeCode: propInfo.typeCode,
|
|
616
|
-
};
|
|
597
|
+
return { code: "MISSING_CONTEXT", ...site, typeCode: propertyInfo.typeCode };
|
|
617
598
|
}
|
|
618
599
|
|
|
619
|
-
|
|
620
|
-
if (typeof contextValue === "string") {
|
|
621
|
-
value = addressToBigInt(contextValue);
|
|
622
|
-
} else {
|
|
623
|
-
value = contextValue;
|
|
624
|
-
}
|
|
600
|
+
const value = contextValueToWord(contextValue);
|
|
625
601
|
|
|
626
602
|
let result: boolean;
|
|
627
603
|
let ctxOperand: bigint | undefined;
|
|
@@ -629,11 +605,8 @@ function evaluateContextRule(
|
|
|
629
605
|
const operand = resolveContextOperand(operandData, context);
|
|
630
606
|
if (typeof operand !== "bigint") {
|
|
631
607
|
return {
|
|
632
|
-
group: groupIndex,
|
|
633
|
-
rule: ruleIndex,
|
|
634
608
|
code: "MISSING_CONTEXT",
|
|
635
|
-
|
|
636
|
-
path: pathHex,
|
|
609
|
+
...site,
|
|
637
610
|
opCode,
|
|
638
611
|
operandData: bytesToHex(operandData),
|
|
639
612
|
typeCode: operand.ctxTypeCode,
|
|
@@ -642,19 +615,16 @@ function evaluateContextRule(
|
|
|
642
615
|
result = (value === operand) !== ((opCode & Op.NOT) !== 0);
|
|
643
616
|
ctxOperand = operand;
|
|
644
617
|
} else {
|
|
645
|
-
result = applyOperator(opCode, value,
|
|
618
|
+
result = applyOperator(opCode, value, 32n, operandData, propertyInfo.typeCode);
|
|
646
619
|
}
|
|
647
620
|
|
|
648
621
|
if (!result) {
|
|
649
622
|
return {
|
|
650
|
-
group: groupIndex,
|
|
651
|
-
rule: ruleIndex,
|
|
652
623
|
code: "VALUE_MISMATCH",
|
|
653
|
-
|
|
654
|
-
path: pathHex,
|
|
624
|
+
...site,
|
|
655
625
|
opCode,
|
|
656
626
|
operandData: bytesToHex(operandData),
|
|
657
|
-
typeCode:
|
|
627
|
+
typeCode: propertyInfo.typeCode,
|
|
658
628
|
resolvedValue: bigintToHex(value),
|
|
659
629
|
...(ctxOperand !== undefined && { resolvedOperand: bigintToHex(ctxOperand) }),
|
|
660
630
|
};
|