@callcium/sdk 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/hint.ts ADDED
@@ -0,0 +1,237 @@
1
+ import { writeBE16, writeBE32 } from "./bytes";
2
+ import { DescriptorFormat as DF, PolicyFormat as PF, TypeCode } from "./constants";
3
+ import { Descriptor } from "./descriptor";
4
+ import { CallciumError } from "./errors";
5
+
6
+ ///////////////////////////////////////////////////////////////////////////
7
+ // Types
8
+ ///////////////////////////////////////////////////////////////////////////
9
+
10
+ /**
11
+ * Why a path does not compile: an index past its count (parameter, tuple field or static array), a step into an
12
+ * elementary node, or a loop over a non-array node.
13
+ */
14
+ export type HintFault = "PARAM_INDEX" | "TUPLE_FIELD" | "ARRAY_INDEX" | "NOT_COMPOSITE" | "NOT_ARRAY";
15
+
16
+ /** A compiled hint, and the descriptor offset of the target node or of a loop's element node. */
17
+ export type CompiledHint = { hint: Uint8Array; node: number };
18
+
19
+ /**
20
+ * The fault that stopped a compilation: the index of the step at fault, and the bound it broke (a parameter, field or
21
+ * element count, or a type code).
22
+ */
23
+ export type HintFaultResult = { fault: HintFault; stepIndex: number; bound: number };
24
+
25
+ /** A compiled hint, or the fault that stopped its compilation. */
26
+ export type Compiled = CompiledHint | HintFaultResult;
27
+
28
+ /** State threaded through the walk of a path. */
29
+ type Walk = {
30
+ /** The hops emitted so far: delta, index and meta. */
31
+ hops: [number, number, number][];
32
+ /** Byte offset of the current node's head from the chain's last base. */
33
+ delta: number;
34
+ /** Whether the chain ends inside the current node, which then takes no entry hop. */
35
+ entered: boolean;
36
+ /** Descriptor offset of the current node. */
37
+ node: number;
38
+ };
39
+
40
+ ///////////////////////////////////////////////////////////////////////////
41
+ // Compilation
42
+ ///////////////////////////////////////////////////////////////////////////
43
+
44
+ /**
45
+ * Compile a test's hint for `steps`. Without `element`, the path starts at the arguments and its first step is a
46
+ * parameter index. Given `element`, the path starts at the loop element whose node lies there, and an empty path
47
+ * is the element itself.
48
+ */
49
+ function compileTest(desc: Uint8Array, steps: readonly number[], element?: number): Compiled {
50
+ const walk: Walk = { hops: [], delta: 0, entered: true, node: element ?? 0 };
51
+ const fault = element === undefined ? walkArguments(desc, steps, walk) : walkSteps(desc, steps, 0, walk);
52
+ if (fault !== undefined) return fault;
53
+
54
+ // A dynamic target is entered, so the hint ends where its encoding starts.
55
+ const target = Descriptor.inspect(desc, walk.node);
56
+ enter(walk, target.isDynamic && !walk.entered);
57
+ const targetMeta = target.typeCode === TypeCode.DYNAMIC_ARRAY ? arrayMeta(desc, walk.node, target.typeCode) : 0;
58
+
59
+ const hint = new Uint8Array(PF.HINT_HEADER_SIZE + walk.hops.length * PF.HINT_HOP_SIZE + PF.HINT_TARGET_SIZE);
60
+ const offset = writeHops(hint, walk);
61
+ writeBE32(hint, offset, walk.delta);
62
+ writeBE16(hint, offset + PF.HINT_TARGET_META_OFFSET, targetMeta);
63
+ hint[offset + PF.HINT_TARGET_TYPECODE_OFFSET] = target.typeCode;
64
+ return { hint, node: walk.node };
65
+ }
66
+
67
+ /** Compile a loop's hint for the array at `steps` from the arguments, the first step a parameter index. */
68
+ function compileLoop(desc: Uint8Array, steps: readonly number[]): Compiled {
69
+ const walk: Walk = { hops: [], delta: 0, entered: true, node: 0 };
70
+ const fault = walkArguments(desc, steps, walk);
71
+ if (fault !== undefined) return fault;
72
+
73
+ const array = Descriptor.inspect(desc, walk.node);
74
+ let count = 0;
75
+ if (array.typeCode === TypeCode.STATIC_ARRAY) {
76
+ enter(walk, array.isDynamic && !walk.entered);
77
+ count = Descriptor.staticArrayLength(desc, walk.node);
78
+ } else if (array.typeCode === TypeCode.DYNAMIC_ARRAY) {
79
+ enter(walk, !walk.entered);
80
+ } else {
81
+ return { fault: "NOT_ARRAY", stepIndex: steps.length, bound: array.typeCode };
82
+ }
83
+
84
+ const hint = new Uint8Array(PF.HINT_HEADER_SIZE + walk.hops.length * PF.HINT_HOP_SIZE + PF.HINT_FRAME_PREFIX_SIZE);
85
+ const offset = writeHops(hint, walk);
86
+ writeBE32(hint, offset, walk.delta);
87
+ writeBE16(hint, offset + PF.HINT_FRAME_COUNT_OFFSET, count);
88
+ writeBE16(hint, offset + PF.HINT_FRAME_META_OFFSET, arrayMeta(desc, walk.node, array.typeCode));
89
+ return { hint, node: Descriptor.arrayElementOffset(walk.node) };
90
+ }
91
+
92
+ /**
93
+ * Return the compiled hint, or throw the error that names `compiled`'s fault.
94
+ * @param compiled - The compilation result.
95
+ * @param steps - The path the steps of `compiled` index into.
96
+ * @param stepOffset - The index in `steps` of the first step `compiled` walked.
97
+ * @throws {CallciumError} When the path does not compile.
98
+ */
99
+ function requireCompiled(compiled: Compiled, steps: readonly number[], stepOffset: number): CompiledHint {
100
+ if (!("fault" in compiled)) return compiled;
101
+ const stepIndex = stepOffset + compiled.stepIndex;
102
+ const step = steps[stepIndex];
103
+ switch (compiled.fault) {
104
+ case "PARAM_INDEX":
105
+ throw new CallciumError(
106
+ "PARAM_INDEX_OUT_OF_BOUNDS",
107
+ `Param index ${step} out of range (descriptor has ${compiled.bound} params)`,
108
+ );
109
+ case "TUPLE_FIELD":
110
+ throw new CallciumError(
111
+ "TUPLE_FIELD_OUT_OF_BOUNDS",
112
+ `Tuple field index ${step} out of range (tuple has ${compiled.bound} fields)`,
113
+ );
114
+ case "ARRAY_INDEX":
115
+ throw new CallciumError(
116
+ "STATIC_ARRAY_INDEX_OUT_OF_BOUNDS",
117
+ `Array index ${step} out of range (static array has ${compiled.bound} elements)`,
118
+ );
119
+ case "NOT_COMPOSITE":
120
+ throw new CallciumError("NOT_COMPOSITE", "Cannot descend into an elementary type");
121
+ case "NOT_ARRAY":
122
+ throw new CallciumError("QUANTIFIER_ON_NON_ARRAY", "Quantifier step is only valid on an array node");
123
+ default:
124
+ throw compiled.fault satisfies never;
125
+ }
126
+ }
127
+
128
+ ///////////////////////////////////////////////////////////////////////////
129
+ // Walk
130
+ ///////////////////////////////////////////////////////////////////////////
131
+
132
+ /**
133
+ * Walk `steps` from the arguments, whose encoding starts at the base. The first step selects a parameter, whose head
134
+ * lies past the heads of the parameters before it.
135
+ */
136
+ function walkArguments(desc: Uint8Array, steps: readonly number[], walk: Walk): HintFaultResult | undefined {
137
+ const argIndex = steps[0]!;
138
+ const paramCount = Descriptor.paramCount(desc);
139
+ if (argIndex >= paramCount) return { fault: "PARAM_INDEX", stepIndex: 0, bound: paramCount };
140
+ const arg = headSpan(desc, DF.HEADER_SIZE, argIndex);
141
+ walk.delta = arg.span;
142
+ walk.entered = false;
143
+ walk.node = arg.nodeOffset;
144
+ return walkSteps(desc, steps, 1, walk);
145
+ }
146
+
147
+ /** Walk the steps from `firstStep` on, each into the field or element of the current node. */
148
+ function walkSteps(
149
+ desc: Uint8Array,
150
+ steps: readonly number[],
151
+ firstStep: number,
152
+ walk: Walk,
153
+ ): HintFaultResult | undefined {
154
+ for (let stepIndex = firstStep; stepIndex < steps.length; stepIndex++) {
155
+ const step = steps[stepIndex]!;
156
+ const node = Descriptor.inspect(desc, walk.node);
157
+
158
+ if (node.typeCode === TypeCode.TUPLE) {
159
+ const fieldCount = Descriptor.tupleFieldCount(desc, walk.node);
160
+ if (step >= fieldCount) return { fault: "TUPLE_FIELD", stepIndex, bound: fieldCount };
161
+ enter(walk, node.isDynamic && !walk.entered);
162
+ const field = headSpan(desc, walk.node + DF.TUPLE_HEADER_SIZE, step);
163
+ walk.delta += field.span;
164
+ walk.entered = false;
165
+ walk.node = field.nodeOffset;
166
+ } else if (node.typeCode === TypeCode.STATIC_ARRAY || node.typeCode === TypeCode.DYNAMIC_ARRAY) {
167
+ if (node.typeCode === TypeCode.STATIC_ARRAY) {
168
+ const arrayLength = Descriptor.staticArrayLength(desc, walk.node);
169
+ if (step >= arrayLength) return { fault: "ARRAY_INDEX", stepIndex, bound: arrayLength };
170
+ }
171
+ const element = Descriptor.arrayElementOffset(walk.node);
172
+ const elementInfo = Descriptor.inspect(desc, element);
173
+
174
+ if (node.isDynamic) {
175
+ // An indirected array holds its elements behind an offset word.
176
+ enter(walk, !walk.entered);
177
+ walk.hops.push([0, step, arrayMeta(desc, walk.node, node.typeCode)]);
178
+ walk.delta = 0;
179
+ walk.entered = elementInfo.isDynamic;
180
+ } else {
181
+ // A static array of static elements is inline, so the index folds into the delta.
182
+ walk.delta += step * elementInfo.staticSize;
183
+ walk.entered = false;
184
+ }
185
+ walk.node = element;
186
+ } else {
187
+ return { fault: "NOT_COMPOSITE", stepIndex, bound: node.typeCode };
188
+ }
189
+ }
190
+ return undefined;
191
+ }
192
+
193
+ /** Append the hop entering an indirected node's payload when `needed`, and rebase the delta. */
194
+ function enter(walk: Walk, needed: boolean): void {
195
+ if (!needed) return;
196
+ walk.hops.push([walk.delta, PF.HINT_NO_INDEX, 0]);
197
+ walk.delta = 0;
198
+ }
199
+
200
+ /** Write the hint header, the hop count with the reserved bits zero, and the hops. Return the offset past the hops. */
201
+ function writeHops(hint: Uint8Array, walk: Walk): number {
202
+ hint[0] = walk.hops.length;
203
+ let offset = PF.HINT_HEADER_SIZE;
204
+ for (const [delta, index, meta] of walk.hops) {
205
+ writeBE32(hint, offset, delta);
206
+ writeBE16(hint, offset + PF.HINT_HOP_INDEX_OFFSET, index);
207
+ writeBE16(hint, offset + PF.HINT_HOP_META_OFFSET, meta);
208
+ offset += PF.HINT_HOP_SIZE;
209
+ }
210
+ return offset;
211
+ }
212
+
213
+ /** Return the combined head span of the `count` nodes at `offset`, and the offset of the node past them. */
214
+ function headSpan(desc: Uint8Array, offset: number, count: number): { span: number; nodeOffset: number } {
215
+ let span = 0;
216
+ let nodeOffset = offset;
217
+ for (let i = 0; i < count; i++) {
218
+ const node = Descriptor.inspect(desc, nodeOffset);
219
+ // An indirected node occupies a single offset word.
220
+ span += node.isDynamic ? 32 : node.staticSize;
221
+ nodeOffset += Descriptor.nodeLength(desc, nodeOffset);
222
+ }
223
+ return { span, nodeOffset };
224
+ }
225
+
226
+ /** Return the meta word describing the array node at `arrayOffset`. */
227
+ function arrayMeta(desc: Uint8Array, arrayOffset: number, typeCode: number): number {
228
+ const element = Descriptor.inspect(desc, Descriptor.arrayElementOffset(arrayOffset));
229
+ // A dynamic element occupies one offset word within the element region.
230
+ let meta = element.isDynamic ? 1 : element.staticSize >> 5;
231
+ if (element.isDynamic) meta |= PF.HINT_META_ELEM_DYNAMIC;
232
+ if (typeCode === TypeCode.DYNAMIC_ARRAY) meta |= PF.HINT_META_DYNAMIC_ARRAY;
233
+ return meta;
234
+ }
235
+
236
+ /** Compile paths into the hints that address their targets in calldata. */
237
+ export const Hint = { compileTest, compileLoop, requireCompiled };
package/src/index.ts CHANGED
@@ -4,8 +4,7 @@
4
4
 
5
5
  export { PolicyBuilder } from "./policy-builder";
6
6
  export { PolicyCoder } from "./policy-coder";
7
- export { isQuantifier, lookupQuantifier, parsePathSteps, Quantifier } from "./path";
8
- export type { QuantifierInfo } from "./path";
7
+ export { parsePathSteps, Quantifier } from "./path";
9
8
  export { isOpAllowed, PolicyValidator } from "./policy-validator";
10
9
  export {
11
10
  arg,
@@ -30,10 +29,8 @@ export { PolicyEnforcer } from "./policy-enforcer";
30
29
  // Utilities
31
30
  ///////////////////////////////////////////////////////////////////////////
32
31
 
33
- export { toAddress, hexToBytes, bytesToHex } from "./bytes";
34
- export { Descriptor } from "./descriptor";
32
+ export { hexToBytes, bytesToHex } from "./bytes";
35
33
  export { DescriptorCoder } from "./descriptor-coder";
36
- export type { TypeInfo } from "./descriptor";
37
34
 
38
35
  ///////////////////////////////////////////////////////////////////////////
39
36
  // Constants
@@ -41,19 +38,28 @@ export type { TypeInfo } from "./descriptor";
41
38
 
42
39
  export {
43
40
  Op,
41
+ Source,
44
42
  TypeCode,
45
- PolicyFormat,
46
43
  Scope,
44
+ LoopQuantifier,
47
45
  ContextProperty,
48
- MAX_CONTEXT_PROPERTY_ID,
49
- findContextProperty,
50
46
  lookupOp,
51
47
  lookupScope,
48
+ lookupQuantifier,
52
49
  lookupContextProperty,
53
50
  } from "./constants";
54
- export { isLengthOp, lookupTypeCode } from "./operators";
51
+ export { lookupTypeCode } from "./operators";
55
52
  export type { TypeCodeInfo, TypeClassInfo, TypeClass } from "./operators";
56
- export type { Operands, OpInfo, ScopeInfo, ContextPropertyInfo } from "./constants";
53
+ export type {
54
+ Operands,
55
+ OpInfo,
56
+ ScopeInfo,
57
+ QuantifierInfo,
58
+ ContextPropertyInfo,
59
+ ComparisonCode,
60
+ ScopeCode,
61
+ QuantifierCode,
62
+ } from "./constants";
57
63
 
58
64
  ///////////////////////////////////////////////////////////////////////////
59
65
  // Types
@@ -65,15 +71,27 @@ export type {
65
71
  Span,
66
72
  Field,
67
73
  PolicyData,
74
+ Rule,
75
+ BodyRule,
76
+ LoopRule,
68
77
  Constraint,
69
78
  DecodedPolicy,
70
79
  DecodedGroup,
71
80
  DecodedRule,
72
- DecodedParam,
81
+ DecodedTest,
82
+ DecodedLoop,
83
+ DecodedHint,
84
+ DecodedFrameHint,
85
+ DecodedHop,
86
+ ArrayMeta,
87
+ DecodedOperator,
88
+ DecodedDescriptor,
89
+ DecodedNode,
73
90
  Issue,
74
91
  IssueCode,
75
92
  IssueSeverity,
76
93
  IssueCategory,
94
+ RuleLocation,
77
95
  Context,
78
96
  EnforceResult,
79
97
  Violation,
@@ -86,7 +104,7 @@ export type {
86
104
  CalldataNavigationViolation,
87
105
  NonCanonicalValueViolation,
88
106
  QuantifierLimitExceededViolation,
89
- QuantifierEmptyArrayViolation,
107
+ QuantifierNoMatchViolation,
90
108
  } from "./types";
91
109
 
92
110
  ///////////////////////////////////////////////////////////////////////////