@callcium/sdk 0.12.0 → 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/README.md +2 -2
- package/dist/index.d.ts +659 -611
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2285 -2071
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
- package/src/bytes.ts +1 -0
- package/src/calldata-reader.ts +3 -3
- package/src/constants.ts +156 -86
- package/src/constraint.ts +34 -24
- package/src/descriptor-coder.ts +126 -103
- package/src/descriptor.ts +79 -288
- package/src/errors.ts +30 -19
- package/src/hint.ts +237 -0
- package/src/index.ts +29 -10
- package/src/operators.ts +168 -267
- package/src/path.ts +7 -31
- package/src/policy-builder.ts +201 -181
- package/src/policy-coder.ts +1156 -523
- package/src/policy-enforcer.ts +499 -486
- package/src/policy-validator.ts +557 -1046
- package/src/signature.ts +7 -11
- package/src/type-desc.ts +6 -6
- package/src/types.ts +230 -129
- package/src/validation-issue.ts +78 -465
package/src/descriptor-coder.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { concatBytes } from "@noble/hashes/utils.js";
|
|
2
2
|
|
|
3
3
|
import { readU16 } from "./bytes";
|
|
4
|
-
import { DescriptorFormat as DF
|
|
4
|
+
import { DescriptorFormat as DF } from "./constants";
|
|
5
5
|
import { Descriptor, readNode } from "./descriptor";
|
|
6
6
|
import { CallciumError } from "./errors";
|
|
7
|
-
import {
|
|
7
|
+
import { lookupTypeCode } from "./operators";
|
|
8
8
|
import { address, array, bool, bytes, bytesN, function_, intN, string_, tuple, uintN } from "./type-desc";
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type { DecodedDescriptor, DecodedNode } from "./types";
|
|
11
11
|
|
|
12
12
|
///////////////////////////////////////////////////////////////////////////
|
|
13
13
|
// Splitting helpers
|
|
@@ -138,12 +138,19 @@ function parseBaseType(input: string, start: number, end: number): Uint8Array {
|
|
|
138
138
|
if (segment.startsWith("(")) return parseTuple(input, start, end);
|
|
139
139
|
|
|
140
140
|
// A prefix with no width after it spells no type, so it falls through to the unknown-type report rather than being
|
|
141
|
-
// read as a width that is not there.
|
|
142
|
-
if (segment.length > 4 && segment.startsWith("uint"))
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
// read as a width that is not there. A width outside its type's range names no type either.
|
|
142
|
+
if (segment.length > 4 && segment.startsWith("uint")) {
|
|
143
|
+
const bits = parseUint(input, start + 4, end);
|
|
144
|
+
if (bits >= 8 && bits <= 256 && bits % 8 === 0) return uintN(bits);
|
|
145
|
+
} else if (segment.length > 3 && segment.startsWith("int")) {
|
|
146
|
+
const bits = parseUint(input, start + 3, end);
|
|
147
|
+
if (bits >= 8 && bits <= 256 && bits % 8 === 0) return intN(bits);
|
|
148
|
+
} else if (segment.length > 5 && segment.startsWith("bytes")) {
|
|
149
|
+
const length = parseUint(input, start + 5, end);
|
|
150
|
+
if (length >= 1 && length <= 32) return bytesN(length);
|
|
151
|
+
}
|
|
145
152
|
|
|
146
|
-
throw new CallciumError("UNKNOWN_TYPE", `
|
|
153
|
+
throw new CallciumError("UNKNOWN_TYPE", `Unknown type '${segment}'`);
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
///////////////////////////////////////////////////////////////////////////
|
|
@@ -197,7 +204,7 @@ function parseType(input: string, start: number, end: number): Uint8Array {
|
|
|
197
204
|
/**
|
|
198
205
|
* Encode a comma-separated list of ABI type strings into a binary descriptor.
|
|
199
206
|
*
|
|
200
|
-
* @param typesCsv - Comma-separated ABI type strings,
|
|
207
|
+
* @param typesCsv - Comma-separated ABI type strings, for example `"address,uint256,(bool,bytes32)[],string"`.
|
|
201
208
|
* @returns Binary descriptor bytes starting with the version+paramCount header.
|
|
202
209
|
* @throws {CallciumError} With code `MALFORMED_TYPE_STRING` for malformed input.
|
|
203
210
|
* @throws {CallciumError} With code `UNKNOWN_TYPE` for unrecognised type names.
|
|
@@ -228,76 +235,24 @@ function fromTypes(typesCsv: string): Uint8Array {
|
|
|
228
235
|
return result;
|
|
229
236
|
}
|
|
230
237
|
|
|
231
|
-
///////////////////////////////////////////////////////////////////////////
|
|
232
|
-
// Inverse: descriptor bytes → type string
|
|
233
|
-
///////////////////////////////////////////////////////////////////////////
|
|
234
|
-
|
|
235
|
-
/** Reconstruct an ABI type string from a descriptor node at the given offset. */
|
|
236
|
-
function nodeToTypeString(desc: Uint8Array, offset: number): string {
|
|
237
|
-
const { typeCode } = Descriptor.inspect(desc, offset);
|
|
238
|
-
|
|
239
|
-
if (typeCode === TypeCode.TUPLE) {
|
|
240
|
-
const fieldCount = Descriptor.tupleFieldCount(desc, offset);
|
|
241
|
-
const fieldTypes: string[] = [];
|
|
242
|
-
let fieldOffset = offset + DF.TUPLE_HEADER_SIZE;
|
|
243
|
-
for (let i = 0; i < fieldCount; i++) {
|
|
244
|
-
fieldTypes.push(nodeToTypeString(desc, fieldOffset));
|
|
245
|
-
fieldOffset += Descriptor.nodeLength(desc, fieldOffset);
|
|
246
|
-
}
|
|
247
|
-
return `(${fieldTypes.join(",")})`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (typeCode === TypeCode.STATIC_ARRAY) {
|
|
251
|
-
const length = Descriptor.staticArrayLength(desc, offset);
|
|
252
|
-
const elemType = nodeToTypeString(desc, Descriptor.arrayElementOffset(offset));
|
|
253
|
-
return `${elemType}[${length}]`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (typeCode === TypeCode.DYNAMIC_ARRAY) {
|
|
257
|
-
const elemType = nodeToTypeString(desc, Descriptor.arrayElementOffset(offset));
|
|
258
|
-
return `${elemType}[]`;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return lookupTypeCode(typeCode).label;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Reconstruct a comma-separated list of ABI type strings from a binary descriptor.
|
|
266
|
-
*
|
|
267
|
-
* Inverse of `fromTypes`: `toTypes(fromTypes(s))` returns `s` for any valid input.
|
|
268
|
-
*
|
|
269
|
-
* @param desc - Binary descriptor bytes (with version+paramCount header).
|
|
270
|
-
* @returns Comma-separated ABI type strings, e.g. `"address,uint256,(bool,bytes32)[]"`.
|
|
271
|
-
* @throws {CallciumError} If the descriptor is malformed.
|
|
272
|
-
*/
|
|
273
|
-
function toTypes(desc: Uint8Array): string {
|
|
274
|
-
const count = Descriptor.paramCount(desc);
|
|
275
|
-
const types: string[] = [];
|
|
276
|
-
let offset = DF.HEADER_SIZE;
|
|
277
|
-
for (let i = 0; i < count; i++) {
|
|
278
|
-
types.push(nodeToTypeString(desc, offset));
|
|
279
|
-
offset += Descriptor.nodeLength(desc, offset);
|
|
280
|
-
}
|
|
281
|
-
return types.join(",");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
238
|
///////////////////////////////////////////////////////////////////////////
|
|
285
239
|
// Descriptor binary decoder
|
|
286
240
|
///////////////////////////////////////////////////////////////////////////
|
|
287
241
|
|
|
288
|
-
/**
|
|
289
|
-
type ParseResult = {
|
|
242
|
+
/** A parsed descriptor node and the offset just past it. */
|
|
243
|
+
type ParseResult = { node: DecodedNode; next: number };
|
|
290
244
|
|
|
291
|
-
/** Recursively parse a single descriptor node starting at offset
|
|
292
|
-
function parseNode(data: Uint8Array, offset: number, depth: number): ParseResult {
|
|
245
|
+
/** Recursively parse a single descriptor node starting at offset, its span shifted by `base`. */
|
|
246
|
+
function parseNode(data: Uint8Array, offset: number, depth: number, base: number): ParseResult {
|
|
293
247
|
const { typeCode: code, staticWords, nodeLength } = readNode(data, offset);
|
|
294
|
-
const info =
|
|
248
|
+
const info = lookupTypeCode(code);
|
|
295
249
|
const isDynamic = staticWords === 0;
|
|
296
250
|
const staticSize = isDynamic ? 0 : staticWords * 32;
|
|
297
251
|
const nodeEnd = offset + nodeLength;
|
|
252
|
+
const span = { start: base + offset, end: base + nodeEnd };
|
|
298
253
|
|
|
299
254
|
if (info.typeClass === "elementary") {
|
|
300
|
-
return { typeCode: code, isDynamic, staticSize, next: nodeEnd };
|
|
255
|
+
return { node: { typeClass: "elementary", typeCode: code, isDynamic, staticSize, span }, next: nodeEnd };
|
|
301
256
|
}
|
|
302
257
|
|
|
303
258
|
// Only composites nest; a leaf below the deepest allowed composite is fine.
|
|
@@ -311,7 +266,7 @@ function parseNode(data: Uint8Array, offset: number, depth: number): ParseResult
|
|
|
311
266
|
|
|
312
267
|
if (info.typeClass === "tuple") {
|
|
313
268
|
const fieldCountOffset = offset + DF.TYPECODE_SIZE + DF.COMPOSITE_META_SIZE;
|
|
314
|
-
if (fieldCountOffset +
|
|
269
|
+
if (fieldCountOffset + DF.TUPLE_FIELDCOUNT_SIZE > data.length) {
|
|
315
270
|
throw new CallciumError("UNEXPECTED_END", "Incomplete tuple header", offset);
|
|
316
271
|
}
|
|
317
272
|
const fieldCount = readU16(data, fieldCountOffset);
|
|
@@ -326,17 +281,21 @@ function parseNode(data: Uint8Array, offset: number, depth: number): ParseResult
|
|
|
326
281
|
);
|
|
327
282
|
}
|
|
328
283
|
|
|
284
|
+
const fields: DecodedNode[] = [];
|
|
329
285
|
let cursor = offset + DF.TUPLE_HEADER_SIZE;
|
|
330
286
|
for (let i = 0; i < fieldCount; i++) {
|
|
331
|
-
|
|
287
|
+
const field = parseNode(data, cursor, depth + 1, base);
|
|
288
|
+
fields.push(field.node);
|
|
289
|
+
cursor = field.next;
|
|
332
290
|
}
|
|
333
291
|
|
|
334
|
-
return { typeCode: code, isDynamic, staticSize, next: nodeEnd };
|
|
292
|
+
return { node: { typeClass: "tuple", typeCode: code, isDynamic, staticSize, span, fields }, next: nodeEnd };
|
|
335
293
|
}
|
|
336
294
|
|
|
295
|
+
const element = parseNode(data, Descriptor.arrayElementOffset(offset), depth + 1, base);
|
|
296
|
+
|
|
337
297
|
if (info.typeClass === "staticArray") {
|
|
338
|
-
const
|
|
339
|
-
const lengthOffset = elemResult.next;
|
|
298
|
+
const lengthOffset = element.next;
|
|
340
299
|
if (lengthOffset + DF.ARRAY_LENGTH_SIZE > data.length) {
|
|
341
300
|
throw new CallciumError("UNEXPECTED_END", "Missing static array length suffix", offset);
|
|
342
301
|
}
|
|
@@ -352,25 +311,36 @@ function parseNode(data: Uint8Array, offset: number, depth: number): ParseResult
|
|
|
352
311
|
);
|
|
353
312
|
}
|
|
354
313
|
|
|
355
|
-
|
|
314
|
+
const node: DecodedNode = {
|
|
315
|
+
typeClass: "staticArray",
|
|
316
|
+
typeCode: code,
|
|
317
|
+
isDynamic,
|
|
318
|
+
staticSize,
|
|
319
|
+
span,
|
|
320
|
+
length,
|
|
321
|
+
element: element.node,
|
|
322
|
+
};
|
|
323
|
+
return { node, next: nodeEnd };
|
|
356
324
|
}
|
|
357
325
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
326
|
+
const node: DecodedNode = {
|
|
327
|
+
typeClass: "dynamicArray",
|
|
328
|
+
typeCode: code,
|
|
329
|
+
isDynamic: true,
|
|
330
|
+
staticSize: 0,
|
|
331
|
+
span,
|
|
332
|
+
element: element.node,
|
|
333
|
+
};
|
|
334
|
+
return { node, next: nodeEnd };
|
|
365
335
|
}
|
|
366
336
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
export function decodeDescriptor(data: Uint8Array
|
|
337
|
+
/**
|
|
338
|
+
* Decode a binary descriptor blob, returning its structural representation.
|
|
339
|
+
* @param data - The descriptor bytes.
|
|
340
|
+
* @param base - Byte offset of the descriptor in the blob its spans locate.
|
|
341
|
+
* @throws {CallciumError} When the descriptor breaks a well-formedness invariant.
|
|
342
|
+
*/
|
|
343
|
+
export function decodeDescriptor(data: Uint8Array, base = 0): DecodedDescriptor {
|
|
374
344
|
if (data.length < 1) {
|
|
375
345
|
throw new CallciumError("MALFORMED_HEADER", "Descriptor is empty");
|
|
376
346
|
}
|
|
@@ -384,29 +354,21 @@ export function decodeDescriptor(data: Uint8Array): { descriptor: DecodedDescrip
|
|
|
384
354
|
throw new CallciumError("MALFORMED_HEADER", "Descriptor too short for header");
|
|
385
355
|
}
|
|
386
356
|
|
|
387
|
-
const params:
|
|
357
|
+
const params: DecodedNode[] = [];
|
|
388
358
|
let cursor: number = DF.HEADER_SIZE;
|
|
389
359
|
|
|
390
360
|
while (cursor < data.length) {
|
|
391
|
-
const {
|
|
361
|
+
const { node, next } = parseNode(data, cursor, 1, base);
|
|
392
362
|
|
|
393
363
|
if (params.length === DF.MAX_PARAMS) {
|
|
394
364
|
throw new CallciumError("TOO_MANY_PARAMS", `Descriptor exceeds the maximum of ${DF.MAX_PARAMS} top-level params`);
|
|
395
365
|
}
|
|
396
366
|
|
|
397
|
-
params.push(
|
|
398
|
-
index: params.length,
|
|
399
|
-
typeCode,
|
|
400
|
-
isDynamic,
|
|
401
|
-
staticSize,
|
|
402
|
-
path: indexToPath(params.length),
|
|
403
|
-
span: { start: cursor, end: next },
|
|
404
|
-
});
|
|
405
|
-
|
|
367
|
+
params.push(node);
|
|
406
368
|
cursor = next;
|
|
407
369
|
}
|
|
408
370
|
|
|
409
|
-
const declaredCount = data[
|
|
371
|
+
const declaredCount = data[DF.HEADER_PARAMCOUNT_OFFSET]!;
|
|
410
372
|
if (params.length !== declaredCount) {
|
|
411
373
|
throw new CallciumError(
|
|
412
374
|
"PARAM_COUNT_MISMATCH",
|
|
@@ -414,8 +376,69 @@ export function decodeDescriptor(data: Uint8Array): { descriptor: DecodedDescrip
|
|
|
414
376
|
);
|
|
415
377
|
}
|
|
416
378
|
|
|
417
|
-
return {
|
|
379
|
+
return {
|
|
380
|
+
version: { value: version, span: { start: base, end: base + DF.HEADER_PARAMCOUNT_OFFSET } },
|
|
381
|
+
paramCount: {
|
|
382
|
+
value: declaredCount,
|
|
383
|
+
span: { start: base + DF.HEADER_PARAMCOUNT_OFFSET, end: base + DF.HEADER_SIZE },
|
|
384
|
+
},
|
|
385
|
+
params,
|
|
386
|
+
span: { start: base, end: base + data.length },
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
///////////////////////////////////////////////////////////////////////////
|
|
391
|
+
// Descriptor inspection
|
|
392
|
+
///////////////////////////////////////////////////////////////////////////
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Decode a binary descriptor and check every composite node against its children.
|
|
396
|
+
* @param desc - The descriptor bytes.
|
|
397
|
+
* @param base - Byte offset of the descriptor in the blob its spans locate.
|
|
398
|
+
* @throws {CallciumError} If the descriptor is malformed, or a composite node disagrees with its children.
|
|
399
|
+
*/
|
|
400
|
+
export function inspectDescriptor(desc: Uint8Array, base = 0): DecodedDescriptor {
|
|
401
|
+
const decoded = decodeDescriptor(desc, base);
|
|
402
|
+
Descriptor.validateMeta(desc);
|
|
403
|
+
return decoded;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Inspect a binary descriptor: its header fields and its parameters as a tree of nodes, each with the bytes it spans.
|
|
408
|
+
* @param desc - Binary descriptor bytes (with version+paramCount header).
|
|
409
|
+
* @returns The decoded descriptor.
|
|
410
|
+
* @throws {CallciumError} If the descriptor is malformed, or a composite node disagrees with its children.
|
|
411
|
+
*/
|
|
412
|
+
function inspect(desc: Uint8Array): DecodedDescriptor {
|
|
413
|
+
return inspectDescriptor(desc);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Reconstruct the ABI type string of a decoded node. */
|
|
417
|
+
function typeString(node: DecodedNode): string {
|
|
418
|
+
switch (node.typeClass) {
|
|
419
|
+
case "elementary":
|
|
420
|
+
return lookupTypeCode(node.typeCode).label;
|
|
421
|
+
case "tuple":
|
|
422
|
+
return `(${node.fields.map(typeString).join(",")})`;
|
|
423
|
+
case "staticArray":
|
|
424
|
+
return `${typeString(node.element)}[${node.length}]`;
|
|
425
|
+
case "dynamicArray":
|
|
426
|
+
return `${typeString(node.element)}[]`;
|
|
427
|
+
default:
|
|
428
|
+
return node satisfies never;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Reconstruct a comma-separated list of ABI type strings from a binary descriptor. `fromTypes(toTypes(desc))` returns
|
|
434
|
+
* `desc` for every descriptor `inspect` accepts.
|
|
435
|
+
* @param desc - Binary descriptor bytes (with version+paramCount header).
|
|
436
|
+
* @returns Comma-separated ABI type strings, for example `"address,uint256,(bool,bytes32)[]"`.
|
|
437
|
+
* @throws {CallciumError} If the descriptor is malformed, or a composite node disagrees with its children.
|
|
438
|
+
*/
|
|
439
|
+
function toTypes(desc: Uint8Array): string {
|
|
440
|
+
return inspect(desc).params.map(typeString).join(",");
|
|
418
441
|
}
|
|
419
442
|
|
|
420
|
-
/** Encode and
|
|
421
|
-
export const DescriptorCoder = { fromTypes, toTypes };
|
|
443
|
+
/** Encode descriptors from type strings, inspect them and read their type strings back. */
|
|
444
|
+
export const DescriptorCoder = { fromTypes, inspect, toTypes };
|