@thru/abi 0.1.29
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 +174 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +1106 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
- package/src/abiSchema.ts +525 -0
- package/src/decodedValue.ts +69 -0
- package/src/decoder.ts +572 -0
- package/src/errors.ts +32 -0
- package/src/expression.ts +165 -0
- package/src/index.test.ts +249 -0
- package/src/index.ts +15 -0
- package/src/typeRegistry.ts +172 -0
- package/src/utils/bytes.ts +7 -0
- package/test/decode-examples.ts +109 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1106 @@
|
|
|
1
|
+
import YAML from 'yaml';
|
|
2
|
+
|
|
3
|
+
// src/abiSchema.ts
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var AbiError = class extends Error {
|
|
7
|
+
constructor(code, message, details) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.details = details;
|
|
11
|
+
this.name = this.constructor.name;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var AbiParseError = class extends AbiError {
|
|
15
|
+
constructor(message, details) {
|
|
16
|
+
super("PARSE_ERROR", message, details);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var AbiValidationError = class extends AbiError {
|
|
20
|
+
constructor(message, details) {
|
|
21
|
+
super("VALIDATION_ERROR", message, details);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var AbiDecodeError = class extends AbiError {
|
|
25
|
+
constructor(message, details) {
|
|
26
|
+
super("DECODE_ERROR", message, details);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// src/abiSchema.ts
|
|
31
|
+
var PRIMITIVE_NAMES = ["u8", "u16", "u32", "u64", "i8", "i16", "i32", "i64", "f16", "f32", "f64"];
|
|
32
|
+
function parseAbiDocument(yamlText) {
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = YAML.parse(yamlText, { intAsBigInt: true });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new AbiParseError("Failed to parse ABI YAML", {
|
|
38
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const root = requireRecord(parsed, "ABI document");
|
|
42
|
+
const abiNode = requireRecord(root.abi, "abi metadata");
|
|
43
|
+
const typesNode = root.types;
|
|
44
|
+
if (!Array.isArray(typesNode)) {
|
|
45
|
+
throw new AbiValidationError("ABI file must contain a 'types' array");
|
|
46
|
+
}
|
|
47
|
+
const metadata = parseMetadata(abiNode);
|
|
48
|
+
const types = typesNode.map((entry, index) => parseTypeDefinition(entry, index));
|
|
49
|
+
ensureTypeNamesUnique(types);
|
|
50
|
+
return { metadata, types };
|
|
51
|
+
}
|
|
52
|
+
function parseMetadata(node) {
|
|
53
|
+
const pkg = requireString(node.package, "abi.package");
|
|
54
|
+
const abiVersionRaw = node["abi-version"];
|
|
55
|
+
if (abiVersionRaw === void 0 || abiVersionRaw === null) {
|
|
56
|
+
throw new AbiValidationError("abi.abi-version is required");
|
|
57
|
+
}
|
|
58
|
+
const abiVersion = Number(abiVersionRaw);
|
|
59
|
+
if (!Number.isFinite(abiVersion)) {
|
|
60
|
+
throw new AbiValidationError("abi.abi-version must be a number");
|
|
61
|
+
}
|
|
62
|
+
const imports = node.imports;
|
|
63
|
+
if (imports !== void 0) {
|
|
64
|
+
const importList = Array.isArray(imports) ? imports : [];
|
|
65
|
+
if (importList.length > 0) {
|
|
66
|
+
throw new AbiValidationError("Flattened ABI files cannot contain 'imports'");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const metadata = {
|
|
70
|
+
package: pkg,
|
|
71
|
+
abiVersion
|
|
72
|
+
};
|
|
73
|
+
if (typeof node["package-version"] === "string") {
|
|
74
|
+
metadata.packageVersion = node["package-version"];
|
|
75
|
+
}
|
|
76
|
+
if (typeof node.description === "string") {
|
|
77
|
+
metadata.description = node.description;
|
|
78
|
+
}
|
|
79
|
+
return metadata;
|
|
80
|
+
}
|
|
81
|
+
function parseTypeDefinition(entry, index) {
|
|
82
|
+
const node = requireRecord(entry, `types[${index}]`);
|
|
83
|
+
const name = requireString(node.name, `types[${index}].name`);
|
|
84
|
+
const kindNode = node.kind;
|
|
85
|
+
if (!kindNode || typeof kindNode !== "object") {
|
|
86
|
+
throw new AbiValidationError(`Type '${name}' is missing its 'kind' definition`);
|
|
87
|
+
}
|
|
88
|
+
const kind = parseTypeKind(kindNode, name);
|
|
89
|
+
return { name, kind };
|
|
90
|
+
}
|
|
91
|
+
function parseTypeKind(node, context) {
|
|
92
|
+
const keys = Object.keys(node);
|
|
93
|
+
if (keys.length !== 1) {
|
|
94
|
+
throw new AbiValidationError(`Type '${context}' kind must be a single-entry object`);
|
|
95
|
+
}
|
|
96
|
+
const key = keys[0];
|
|
97
|
+
const value = node[key];
|
|
98
|
+
switch (key) {
|
|
99
|
+
case "primitive":
|
|
100
|
+
return parsePrimitiveType(value, context);
|
|
101
|
+
case "struct":
|
|
102
|
+
return parseStructType(requireRecord(value, `struct for ${context}`), context);
|
|
103
|
+
case "array":
|
|
104
|
+
return parseArrayType(requireRecord(value, `array for ${context}`), context);
|
|
105
|
+
case "enum":
|
|
106
|
+
return parseEnumType(requireRecord(value, `enum for ${context}`), context);
|
|
107
|
+
case "union":
|
|
108
|
+
return parseUnionType(requireRecord(value, `union for ${context}`), context);
|
|
109
|
+
case "size-discriminated-union":
|
|
110
|
+
return parseSizeDiscriminatedUnionType(requireRecord(value, `size-discriminated-union for ${context}`), context);
|
|
111
|
+
case "type-ref":
|
|
112
|
+
return parseTypeRef(requireRecord(value, `type-ref for ${context}`), context);
|
|
113
|
+
default:
|
|
114
|
+
throw new AbiValidationError(`Type '${context}' uses unsupported kind '${key}'`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function parsePrimitiveType(value, context) {
|
|
118
|
+
if (typeof value !== "string") {
|
|
119
|
+
throw new AbiValidationError(`Primitive type for '${context}' must be a string`);
|
|
120
|
+
}
|
|
121
|
+
if (!PRIMITIVE_NAMES.includes(value)) {
|
|
122
|
+
throw new AbiValidationError(`Type '${context}' references unknown primitive '${value}'`);
|
|
123
|
+
}
|
|
124
|
+
return { kind: "primitive", primitive: value };
|
|
125
|
+
}
|
|
126
|
+
function parseStructType(node, context) {
|
|
127
|
+
const attributes = parseContainerAttributes(node);
|
|
128
|
+
const fieldsNode = node.fields;
|
|
129
|
+
if (!Array.isArray(fieldsNode)) {
|
|
130
|
+
throw new AbiValidationError(`Struct '${context}' must define a 'fields' array`);
|
|
131
|
+
}
|
|
132
|
+
const fields = fieldsNode.map((fieldNode, index) => {
|
|
133
|
+
const field = requireRecord(fieldNode, `field ${index} in struct '${context}'`);
|
|
134
|
+
const name = requireString(field.name, `field ${index} name in struct '${context}'`);
|
|
135
|
+
const fieldTypeNode = field["field-type"];
|
|
136
|
+
if (!fieldTypeNode || typeof fieldTypeNode !== "object") {
|
|
137
|
+
throw new AbiValidationError(`Field '${name}' in struct '${context}' is missing 'field-type'`);
|
|
138
|
+
}
|
|
139
|
+
const type = parseTypeKind(fieldTypeNode, `${context}.${name}`);
|
|
140
|
+
return { name, type };
|
|
141
|
+
});
|
|
142
|
+
return { kind: "struct", attributes, fields };
|
|
143
|
+
}
|
|
144
|
+
function parseArrayType(node, context) {
|
|
145
|
+
const attributes = parseContainerAttributes(node);
|
|
146
|
+
const elementNode = node["element-type"];
|
|
147
|
+
if (!elementNode || typeof elementNode !== "object") {
|
|
148
|
+
throw new AbiValidationError(`Array '${context}' is missing 'element-type'`);
|
|
149
|
+
}
|
|
150
|
+
const elementType = parseTypeKind(elementNode, `${context}[]`);
|
|
151
|
+
const sizeNode = node.size;
|
|
152
|
+
if (!sizeNode || typeof sizeNode !== "object") {
|
|
153
|
+
throw new AbiValidationError(`Array '${context}' is missing its 'size' expression`);
|
|
154
|
+
}
|
|
155
|
+
const size = parseExpression(sizeNode, `array '${context}' size`);
|
|
156
|
+
return { kind: "array", attributes, elementType, size };
|
|
157
|
+
}
|
|
158
|
+
function parseEnumType(node, context) {
|
|
159
|
+
const attributes = parseContainerAttributes(node);
|
|
160
|
+
const tagRefNode = node["tag-ref"];
|
|
161
|
+
if (!tagRefNode || typeof tagRefNode !== "object") {
|
|
162
|
+
throw new AbiValidationError(`Enum '${context}' must define 'tag-ref'`);
|
|
163
|
+
}
|
|
164
|
+
const tagExpression = parseExpression(tagRefNode, `enum '${context}' tag-ref`);
|
|
165
|
+
const variantsNode = node.variants;
|
|
166
|
+
if (!Array.isArray(variantsNode) || variantsNode.length === 0) {
|
|
167
|
+
throw new AbiValidationError(`Enum '${context}' must include at least one variant`);
|
|
168
|
+
}
|
|
169
|
+
const variants = variantsNode.map((variantNode, index) => {
|
|
170
|
+
const variant = requireRecord(variantNode, `variant ${index} in enum '${context}'`);
|
|
171
|
+
const name = requireString(variant.name, `variant ${index} name in enum '${context}'`);
|
|
172
|
+
const tagValueRaw = variant["tag-value"];
|
|
173
|
+
if (tagValueRaw === void 0 || tagValueRaw === null) {
|
|
174
|
+
throw new AbiValidationError(`Variant '${name}' in enum '${context}' must define 'tag-value'`);
|
|
175
|
+
}
|
|
176
|
+
const tagValue = Number(tagValueRaw);
|
|
177
|
+
if (!Number.isSafeInteger(tagValue)) {
|
|
178
|
+
throw new AbiValidationError(`Variant '${name}' in enum '${context}' has invalid tag-value`);
|
|
179
|
+
}
|
|
180
|
+
const variantTypeNode = variant["variant-type"];
|
|
181
|
+
if (!variantTypeNode || typeof variantTypeNode !== "object") {
|
|
182
|
+
throw new AbiValidationError(`Variant '${name}' in enum '${context}' is missing 'variant-type'`);
|
|
183
|
+
}
|
|
184
|
+
const type = parseTypeKind(variantTypeNode, `${context}.${name}`);
|
|
185
|
+
return { name, tagValue, type };
|
|
186
|
+
});
|
|
187
|
+
return { kind: "enum", attributes, tagExpression, variants };
|
|
188
|
+
}
|
|
189
|
+
function parseUnionType(node, context) {
|
|
190
|
+
const attributes = parseContainerAttributes(node);
|
|
191
|
+
const variantsNode = node.variants;
|
|
192
|
+
if (!Array.isArray(variantsNode) || variantsNode.length === 0) {
|
|
193
|
+
throw new AbiValidationError(`Union '${context}' must include at least one variant`);
|
|
194
|
+
}
|
|
195
|
+
const variants = variantsNode.map((variantNode, index) => {
|
|
196
|
+
const variant = requireRecord(variantNode, `variant ${index} in union '${context}'`);
|
|
197
|
+
const name = requireString(variant.name, `variant ${index} name in union '${context}'`);
|
|
198
|
+
const variantTypeNode = variant["variant-type"];
|
|
199
|
+
if (!variantTypeNode || typeof variantTypeNode !== "object") {
|
|
200
|
+
throw new AbiValidationError(`Variant '${name}' in union '${context}' is missing 'variant-type'`);
|
|
201
|
+
}
|
|
202
|
+
const type = parseTypeKind(variantTypeNode, `${context}.${name}`);
|
|
203
|
+
return { name, type };
|
|
204
|
+
});
|
|
205
|
+
return { kind: "union", attributes, variants };
|
|
206
|
+
}
|
|
207
|
+
function parseSizeDiscriminatedUnionType(node, context) {
|
|
208
|
+
const attributes = parseContainerAttributes(node);
|
|
209
|
+
const variantsNode = node.variants;
|
|
210
|
+
if (!Array.isArray(variantsNode) || variantsNode.length === 0) {
|
|
211
|
+
throw new AbiValidationError(`Size-discriminated union '${context}' must include variants`);
|
|
212
|
+
}
|
|
213
|
+
const variants = variantsNode.map((variantNode, index) => {
|
|
214
|
+
const variant = requireRecord(variantNode, `variant ${index} in size-discriminated union '${context}'`);
|
|
215
|
+
const name = requireString(variant.name, `variant ${index} name in size-discriminated union '${context}'`);
|
|
216
|
+
const sizeRaw = variant["expected-size"];
|
|
217
|
+
if (sizeRaw === void 0 || sizeRaw === null) {
|
|
218
|
+
throw new AbiValidationError(`Variant '${name}' in '${context}' must define 'expected-size'`);
|
|
219
|
+
}
|
|
220
|
+
const expectedSize = Number(sizeRaw);
|
|
221
|
+
if (!Number.isSafeInteger(expectedSize) || expectedSize < 0) {
|
|
222
|
+
throw new AbiValidationError(`Variant '${name}' in '${context}' has invalid expected-size`);
|
|
223
|
+
}
|
|
224
|
+
const variantTypeNode = variant["variant-type"];
|
|
225
|
+
if (!variantTypeNode || typeof variantTypeNode !== "object") {
|
|
226
|
+
throw new AbiValidationError(`Variant '${name}' in '${context}' is missing 'variant-type'`);
|
|
227
|
+
}
|
|
228
|
+
const type = parseTypeKind(variantTypeNode, `${context}.${name}`);
|
|
229
|
+
return { name, expectedSize, type };
|
|
230
|
+
});
|
|
231
|
+
return { kind: "size-discriminated-union", attributes, variants };
|
|
232
|
+
}
|
|
233
|
+
function parseTypeRef(node, context) {
|
|
234
|
+
const name = requireString(node.name, `type-ref in '${context}'`);
|
|
235
|
+
return { kind: "type-ref", name };
|
|
236
|
+
}
|
|
237
|
+
function parseContainerAttributes(node) {
|
|
238
|
+
const packed = node.packed === true;
|
|
239
|
+
const alignedRaw = node.aligned;
|
|
240
|
+
const aligned = alignedRaw === void 0 ? 0 : Number(alignedRaw);
|
|
241
|
+
if (aligned < 0 || !Number.isFinite(aligned)) {
|
|
242
|
+
throw new AbiValidationError("Container alignment must be a positive number when specified");
|
|
243
|
+
}
|
|
244
|
+
const attrs = { packed, aligned };
|
|
245
|
+
if (typeof node.comment === "string") {
|
|
246
|
+
attrs.comment = node.comment;
|
|
247
|
+
}
|
|
248
|
+
return attrs;
|
|
249
|
+
}
|
|
250
|
+
function parseExpression(node, context) {
|
|
251
|
+
const keys = Object.keys(node);
|
|
252
|
+
if (keys.length !== 1) {
|
|
253
|
+
throw new AbiValidationError(`Expression for ${context} must contain exactly one operator`);
|
|
254
|
+
}
|
|
255
|
+
const key = keys[0];
|
|
256
|
+
const value = node[key];
|
|
257
|
+
switch (key) {
|
|
258
|
+
case "literal":
|
|
259
|
+
return parseLiteralExpression(requireRecord(value, `literal expression in ${context}`), context);
|
|
260
|
+
case "field-ref":
|
|
261
|
+
return parseFieldRefExpression(requireRecord(value, `field-ref expression in ${context}`), context);
|
|
262
|
+
case "add":
|
|
263
|
+
case "sub":
|
|
264
|
+
case "mul":
|
|
265
|
+
case "div":
|
|
266
|
+
case "mod":
|
|
267
|
+
case "bit-and":
|
|
268
|
+
case "bit-or":
|
|
269
|
+
case "bit-xor":
|
|
270
|
+
case "left-shift":
|
|
271
|
+
case "right-shift":
|
|
272
|
+
return parseBinaryExpression(key, requireRecord(value, `${key} expression in ${context}`), context);
|
|
273
|
+
case "bit-not":
|
|
274
|
+
return parseUnaryExpression(key, requireRecord(value, `${key} expression in ${context}`), context);
|
|
275
|
+
case "sizeof":
|
|
276
|
+
return parseSizeOfExpression(requireRecord(value, `sizeof expression in ${context}`), context);
|
|
277
|
+
case "alignof":
|
|
278
|
+
return parseAlignOfExpression(requireRecord(value, `alignof expression in ${context}`), context);
|
|
279
|
+
default:
|
|
280
|
+
throw new AbiValidationError(`Expression '${key}' in ${context} is not supported yet`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function parseLiteralExpression(node, context) {
|
|
284
|
+
const keys = Object.keys(node);
|
|
285
|
+
if (keys.length !== 1) {
|
|
286
|
+
throw new AbiValidationError(`Literal expression for ${context} must specify exactly one primitive type`);
|
|
287
|
+
}
|
|
288
|
+
const literalType = keys[0];
|
|
289
|
+
if (!PRIMITIVE_NAMES.includes(literalType)) {
|
|
290
|
+
throw new AbiValidationError(`Literal in ${context} references unknown primitive '${literalType}'`);
|
|
291
|
+
}
|
|
292
|
+
const rawValue = node[literalType];
|
|
293
|
+
if (typeof rawValue !== "number" && typeof rawValue !== "bigint") {
|
|
294
|
+
throw new AbiValidationError(`Literal in ${context} must be a number`);
|
|
295
|
+
}
|
|
296
|
+
const value = toBigInt(rawValue);
|
|
297
|
+
return { type: "literal", literalType, value };
|
|
298
|
+
}
|
|
299
|
+
function parseFieldRefExpression(node, context) {
|
|
300
|
+
const pathNode = node.path;
|
|
301
|
+
if (!Array.isArray(pathNode) || pathNode.length === 0) {
|
|
302
|
+
throw new AbiValidationError(`field-ref in ${context} must define a non-empty path array`);
|
|
303
|
+
}
|
|
304
|
+
const path = pathNode.map((segment, index) => {
|
|
305
|
+
if (typeof segment !== "string") {
|
|
306
|
+
throw new AbiValidationError(`field-ref segment ${index} in ${context} must be a string`);
|
|
307
|
+
}
|
|
308
|
+
return segment;
|
|
309
|
+
});
|
|
310
|
+
return { type: "field-ref", path };
|
|
311
|
+
}
|
|
312
|
+
function parseBinaryExpression(op, node, context) {
|
|
313
|
+
const leftNode = node.left;
|
|
314
|
+
const rightNode = node.right;
|
|
315
|
+
if (!leftNode || typeof leftNode !== "object" || !rightNode || typeof rightNode !== "object") {
|
|
316
|
+
throw new AbiValidationError(`Binary expression '${op}' in ${context} must include 'left' and 'right'`);
|
|
317
|
+
}
|
|
318
|
+
const left = parseExpression(leftNode, `${context} (left operand)`);
|
|
319
|
+
const right = parseExpression(rightNode, `${context} (right operand)`);
|
|
320
|
+
return { type: "binary", op, left, right };
|
|
321
|
+
}
|
|
322
|
+
function parseUnaryExpression(op, node, context) {
|
|
323
|
+
const operandNode = node.operand;
|
|
324
|
+
if (!operandNode || typeof operandNode !== "object") {
|
|
325
|
+
throw new AbiValidationError(`Unary expression '${op}' in ${context} must include 'operand'`);
|
|
326
|
+
}
|
|
327
|
+
const operand = parseExpression(operandNode, `${context} (operand)`);
|
|
328
|
+
return { type: "unary", op, operand };
|
|
329
|
+
}
|
|
330
|
+
function parseSizeOfExpression(node, context) {
|
|
331
|
+
const typeName = requireString(node["type-name"], `${context}.type-name`);
|
|
332
|
+
return { type: "sizeof", typeName };
|
|
333
|
+
}
|
|
334
|
+
function parseAlignOfExpression(node, context) {
|
|
335
|
+
const typeName = requireString(node["type-name"], `${context}.type-name`);
|
|
336
|
+
return { type: "alignof", typeName };
|
|
337
|
+
}
|
|
338
|
+
function ensureTypeNamesUnique(types) {
|
|
339
|
+
const seen = /* @__PURE__ */ new Set();
|
|
340
|
+
for (const type of types) {
|
|
341
|
+
if (seen.has(type.name)) {
|
|
342
|
+
throw new AbiValidationError(`Duplicate type definition '${type.name}' found in ABI`);
|
|
343
|
+
}
|
|
344
|
+
seen.add(type.name);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function requireRecord(value, context) {
|
|
348
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
349
|
+
throw new AbiValidationError(`${context} must be an object`);
|
|
350
|
+
}
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
function requireString(value, context) {
|
|
354
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
355
|
+
throw new AbiValidationError(`${context} must be a non-empty string`);
|
|
356
|
+
}
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
function toBigInt(value) {
|
|
360
|
+
if (typeof value === "bigint") {
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
364
|
+
throw new AbiValidationError("Literal values must be integers");
|
|
365
|
+
}
|
|
366
|
+
return BigInt(value);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/expression.ts
|
|
370
|
+
function evaluateExpression(expression, scope, context, registry) {
|
|
371
|
+
switch (expression.type) {
|
|
372
|
+
case "literal":
|
|
373
|
+
return expression.value;
|
|
374
|
+
case "field-ref":
|
|
375
|
+
return toBigIntValue(resolveFieldPath(expression.path, scope, context), context);
|
|
376
|
+
case "binary":
|
|
377
|
+
return applyBinaryOperator(
|
|
378
|
+
expression.op,
|
|
379
|
+
evaluateExpression(expression.left, scope, `${context} (left)`, registry),
|
|
380
|
+
evaluateExpression(expression.right, scope, `${context} (right)`, registry),
|
|
381
|
+
context
|
|
382
|
+
);
|
|
383
|
+
case "unary":
|
|
384
|
+
return applyUnaryOperator(
|
|
385
|
+
expression.op,
|
|
386
|
+
evaluateExpression(expression.operand, scope, `${context} (operand)`, registry),
|
|
387
|
+
context
|
|
388
|
+
);
|
|
389
|
+
case "sizeof":
|
|
390
|
+
if (!registry) {
|
|
391
|
+
throw new AbiDecodeError(`Cannot evaluate sizeof(${expression.typeName}) without a TypeRegistry`);
|
|
392
|
+
}
|
|
393
|
+
const type = registry.get(expression.typeName);
|
|
394
|
+
const size = getConstSize(type.kind, registry, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Set());
|
|
395
|
+
if (size === null) {
|
|
396
|
+
throw new AbiDecodeError(`sizeof(${expression.typeName}) is not constant`);
|
|
397
|
+
}
|
|
398
|
+
return BigInt(size);
|
|
399
|
+
case "alignof":
|
|
400
|
+
if (!registry) {
|
|
401
|
+
throw new AbiDecodeError(`Cannot evaluate alignof(${expression.typeName}) without a TypeRegistry`);
|
|
402
|
+
}
|
|
403
|
+
const targetType = registry.get(expression.typeName);
|
|
404
|
+
const alignment = Math.max(getTypeAlignment(targetType.kind, registry), 1);
|
|
405
|
+
return BigInt(alignment);
|
|
406
|
+
default:
|
|
407
|
+
throw new AbiDecodeError(`Unsupported expression encountered while decoding ${context}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function resolveFieldPath(path, scope, context) {
|
|
411
|
+
if (path.length === 0) {
|
|
412
|
+
throw new AbiDecodeError(`Invalid field-ref in ${context}: path cannot be empty`);
|
|
413
|
+
}
|
|
414
|
+
if (!scope) {
|
|
415
|
+
throw new AbiDecodeError(`Unable to resolve field '${path.join(".")}' in ${context}`);
|
|
416
|
+
}
|
|
417
|
+
const [head, ...tail] = path;
|
|
418
|
+
if (head === "..") {
|
|
419
|
+
if (!scope.parent) {
|
|
420
|
+
throw new AbiDecodeError(`Field reference in ${context} attempted to access parent scope, but none exists`);
|
|
421
|
+
}
|
|
422
|
+
return resolveFieldPath(tail, scope.parent, context);
|
|
423
|
+
}
|
|
424
|
+
if (scope.fields.has(head)) {
|
|
425
|
+
const value = scope.fields.get(head);
|
|
426
|
+
if (tail.length === 0) {
|
|
427
|
+
return value;
|
|
428
|
+
}
|
|
429
|
+
return resolveNestedStructValue(value, tail, context);
|
|
430
|
+
}
|
|
431
|
+
return resolveFieldPath(path, scope.parent, context);
|
|
432
|
+
}
|
|
433
|
+
function resolveNestedStructValue(value, path, context) {
|
|
434
|
+
if (value.kind !== "struct") {
|
|
435
|
+
throw new AbiDecodeError(
|
|
436
|
+
`Field reference '${path.join(".")}' in ${context} traversed through non-struct value of kind '${value.kind}'`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
const [head, ...tail] = path;
|
|
440
|
+
const nested = value.fields[head];
|
|
441
|
+
if (!nested) {
|
|
442
|
+
throw new AbiDecodeError(`Struct field '${head}' referenced in ${context} does not exist`);
|
|
443
|
+
}
|
|
444
|
+
if (tail.length === 0) {
|
|
445
|
+
return nested;
|
|
446
|
+
}
|
|
447
|
+
return resolveNestedStructValue(nested, tail, context);
|
|
448
|
+
}
|
|
449
|
+
function toBigIntValue(value, context) {
|
|
450
|
+
if (value.kind !== "primitive") {
|
|
451
|
+
throw new AbiDecodeError(`Expression in ${context} referenced non-primitive value of kind '${value.kind}'`);
|
|
452
|
+
}
|
|
453
|
+
return typeof value.value === "bigint" ? value.value : BigInt(value.value);
|
|
454
|
+
}
|
|
455
|
+
function applyBinaryOperator(op, left, right, context) {
|
|
456
|
+
switch (op) {
|
|
457
|
+
case "add":
|
|
458
|
+
return left + right;
|
|
459
|
+
case "sub":
|
|
460
|
+
return left - right;
|
|
461
|
+
case "mul":
|
|
462
|
+
return left * right;
|
|
463
|
+
case "div":
|
|
464
|
+
if (right === 0n) {
|
|
465
|
+
throw new AbiDecodeError(`Division by zero while evaluating expression for ${context}`);
|
|
466
|
+
}
|
|
467
|
+
return left / right;
|
|
468
|
+
case "mod":
|
|
469
|
+
if (right === 0n) {
|
|
470
|
+
throw new AbiDecodeError(`Modulo by zero while evaluating expression for ${context}`);
|
|
471
|
+
}
|
|
472
|
+
return left % right;
|
|
473
|
+
case "bit-and":
|
|
474
|
+
return left & right;
|
|
475
|
+
case "bit-or":
|
|
476
|
+
return left | right;
|
|
477
|
+
case "bit-xor":
|
|
478
|
+
return left ^ right;
|
|
479
|
+
case "left-shift":
|
|
480
|
+
return left << right;
|
|
481
|
+
case "right-shift":
|
|
482
|
+
return left >> right;
|
|
483
|
+
default:
|
|
484
|
+
throw new AbiDecodeError(`Binary operator '${op}' is not supported in ${context}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function applyUnaryOperator(op, operand, context) {
|
|
488
|
+
switch (op) {
|
|
489
|
+
case "bit-not":
|
|
490
|
+
return ~operand;
|
|
491
|
+
default:
|
|
492
|
+
throw new AbiDecodeError(`Unary operator '${op}' is not supported in ${context}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function createScope(parent) {
|
|
496
|
+
return { fields: /* @__PURE__ */ new Map(), parent };
|
|
497
|
+
}
|
|
498
|
+
function addFieldToScope(scope, name, value) {
|
|
499
|
+
if (scope) {
|
|
500
|
+
scope.fields.set(name, value);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/typeRegistry.ts
|
|
505
|
+
var TypeRegistry = class {
|
|
506
|
+
constructor(definitions) {
|
|
507
|
+
this.types = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const def of definitions) {
|
|
509
|
+
this.types.set(def.name, def);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
get(typeName) {
|
|
513
|
+
const definition = this.types.get(typeName);
|
|
514
|
+
if (!definition) {
|
|
515
|
+
throw new AbiValidationError(`Type '${typeName}' is not defined in this ABI file`, { typeName });
|
|
516
|
+
}
|
|
517
|
+
return definition;
|
|
518
|
+
}
|
|
519
|
+
has(typeName) {
|
|
520
|
+
return this.types.has(typeName);
|
|
521
|
+
}
|
|
522
|
+
entries() {
|
|
523
|
+
return this.types.entries();
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
function buildTypeRegistry(document) {
|
|
527
|
+
const registry = new TypeRegistry(document.types);
|
|
528
|
+
validateTypeReferences(registry);
|
|
529
|
+
detectReferenceCycles(registry);
|
|
530
|
+
return registry;
|
|
531
|
+
}
|
|
532
|
+
function validateTypeReferences(registry) {
|
|
533
|
+
for (const [, type] of registry.entries()) {
|
|
534
|
+
validateTypeKindReferences(type.kind, registry, type.name);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function validateTypeKindReferences(kind, registry, context) {
|
|
538
|
+
switch (kind.kind) {
|
|
539
|
+
case "type-ref":
|
|
540
|
+
ensureTypeExists(kind, registry, context);
|
|
541
|
+
break;
|
|
542
|
+
case "struct":
|
|
543
|
+
kind.fields.forEach((field) => validateTypeKindReferences(field.type, registry, `${context}.${field.name}`));
|
|
544
|
+
break;
|
|
545
|
+
case "array":
|
|
546
|
+
validateTypeKindReferences(kind.elementType, registry, `${context}[]`);
|
|
547
|
+
break;
|
|
548
|
+
case "enum":
|
|
549
|
+
kind.variants.forEach((variant) => validateTypeKindReferences(variant.type, registry, `${context}.${variant.name}`));
|
|
550
|
+
break;
|
|
551
|
+
case "union":
|
|
552
|
+
kind.variants.forEach((variant) => validateTypeKindReferences(variant.type, registry, `${context}.${variant.name}`));
|
|
553
|
+
break;
|
|
554
|
+
case "size-discriminated-union":
|
|
555
|
+
kind.variants.forEach((variant) => validateTypeKindReferences(variant.type, registry, `${context}.${variant.name}`));
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function ensureTypeExists(typeRef, registry, context) {
|
|
560
|
+
if (!registry.has(typeRef.name)) {
|
|
561
|
+
throw new AbiValidationError(`Type '${context}' references unknown type '${typeRef.name}'`, {
|
|
562
|
+
typeName: context,
|
|
563
|
+
referencedType: typeRef.name
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function detectReferenceCycles(registry) {
|
|
568
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
569
|
+
const visited = /* @__PURE__ */ new Set();
|
|
570
|
+
const visit = (typeName, stack) => {
|
|
571
|
+
if (visited.has(typeName)) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (visiting.has(typeName)) {
|
|
575
|
+
const cyclePath = [...stack, typeName];
|
|
576
|
+
throw new AbiValidationError(`Cyclic type reference detected: ${cyclePath.join(" -> ")}`, { cycle: cyclePath });
|
|
577
|
+
}
|
|
578
|
+
visiting.add(typeName);
|
|
579
|
+
const type = registry.get(typeName);
|
|
580
|
+
const referencedTypes = collectTypeReferences(type.kind);
|
|
581
|
+
for (const referenced of referencedTypes) {
|
|
582
|
+
if (registry.has(referenced)) {
|
|
583
|
+
visit(referenced, [...stack, typeName]);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
visiting.delete(typeName);
|
|
587
|
+
visited.add(typeName);
|
|
588
|
+
};
|
|
589
|
+
for (const [typeName] of registry.entries()) {
|
|
590
|
+
if (!visited.has(typeName)) {
|
|
591
|
+
visit(typeName, []);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function collectTypeReferences(kind, refs = /* @__PURE__ */ new Set()) {
|
|
596
|
+
switch (kind.kind) {
|
|
597
|
+
case "type-ref":
|
|
598
|
+
refs.add(kind.name);
|
|
599
|
+
break;
|
|
600
|
+
case "struct":
|
|
601
|
+
collectStructRefs(kind, refs);
|
|
602
|
+
break;
|
|
603
|
+
case "array":
|
|
604
|
+
collectTypeReferences(kind.elementType, refs);
|
|
605
|
+
break;
|
|
606
|
+
case "enum":
|
|
607
|
+
collectEnumRefs(kind, refs);
|
|
608
|
+
break;
|
|
609
|
+
case "union":
|
|
610
|
+
collectUnionRefs(kind, refs);
|
|
611
|
+
break;
|
|
612
|
+
case "size-discriminated-union":
|
|
613
|
+
collectSizeUnionRefs(kind, refs);
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
return refs;
|
|
617
|
+
}
|
|
618
|
+
function collectStructRefs(struct, refs) {
|
|
619
|
+
for (const field of struct.fields) {
|
|
620
|
+
collectTypeReferences(field.type, refs);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function collectEnumRefs(enumType, refs) {
|
|
624
|
+
for (const variant of enumType.variants) {
|
|
625
|
+
collectTypeReferences(variant.type, refs);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function collectUnionRefs(union, refs) {
|
|
629
|
+
for (const variant of union.variants) {
|
|
630
|
+
collectTypeReferences(variant.type, refs);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function collectSizeUnionRefs(union, refs) {
|
|
634
|
+
for (const variant of union.variants) {
|
|
635
|
+
collectTypeReferences(variant.type, refs);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/utils/bytes.ts
|
|
640
|
+
function bytesToHex(bytes) {
|
|
641
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
642
|
+
}
|
|
643
|
+
function alignUp(offset, alignment) {
|
|
644
|
+
return offset + alignment - 1 & ~(alignment - 1);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/decoder.ts
|
|
648
|
+
function decodeData(yamlText, typeName, data) {
|
|
649
|
+
if (!(data instanceof Uint8Array)) {
|
|
650
|
+
throw new AbiDecodeError("decodeData expects account data as a Uint8Array");
|
|
651
|
+
}
|
|
652
|
+
const trimmedTypeName = typeName?.trim();
|
|
653
|
+
if (!trimmedTypeName) {
|
|
654
|
+
throw new AbiDecodeError("decodeData requires a non-empty typeName argument");
|
|
655
|
+
}
|
|
656
|
+
const abiDocument = parseAbiDocument(yamlText);
|
|
657
|
+
const registry = buildTypeRegistry(abiDocument);
|
|
658
|
+
return decodeWithRegistry(registry, trimmedTypeName, data);
|
|
659
|
+
}
|
|
660
|
+
function decodeWithRegistry(registry, typeName, data) {
|
|
661
|
+
const type = registry.get(typeName);
|
|
662
|
+
const state = {
|
|
663
|
+
registry,
|
|
664
|
+
data,
|
|
665
|
+
view: new DataView(data.buffer, data.byteOffset, data.byteLength),
|
|
666
|
+
offset: 0,
|
|
667
|
+
scope: void 0
|
|
668
|
+
};
|
|
669
|
+
const value = decodeKind(type.kind, state, typeName, type.name, state.view.byteLength);
|
|
670
|
+
if (state.offset !== data.byteLength) {
|
|
671
|
+
throw new AbiDecodeError("Decoded data did not consume the full buffer", {
|
|
672
|
+
expectedLength: data.byteLength,
|
|
673
|
+
consumedLength: state.offset,
|
|
674
|
+
remainingBytes: data.byteLength - state.offset
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return value;
|
|
678
|
+
}
|
|
679
|
+
function decodeKind(kind, state, context, typeName, byteBudget) {
|
|
680
|
+
switch (kind.kind) {
|
|
681
|
+
case "primitive":
|
|
682
|
+
return decodePrimitive(kind.primitive, state, context, typeName);
|
|
683
|
+
case "struct":
|
|
684
|
+
return decodeStruct(kind, state, context, typeName, byteBudget);
|
|
685
|
+
case "array":
|
|
686
|
+
return decodeArray(kind, state, context, typeName);
|
|
687
|
+
case "enum":
|
|
688
|
+
return decodeEnum(kind, state, context, typeName);
|
|
689
|
+
case "union":
|
|
690
|
+
return decodeUnion(kind, state, context, typeName);
|
|
691
|
+
case "size-discriminated-union":
|
|
692
|
+
return decodeSizeDiscriminatedUnion(kind, state, context, typeName, byteBudget);
|
|
693
|
+
case "type-ref":
|
|
694
|
+
return decodeTypeReference(kind, state, context, byteBudget);
|
|
695
|
+
default:
|
|
696
|
+
throw new AbiDecodeError(`Type '${context}' uses unsupported kind '${kind.kind}'`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function decodeTypeReference(typeRef, state, context, byteBudget) {
|
|
700
|
+
const referenced = state.registry.get(typeRef.name);
|
|
701
|
+
return decodeKind(referenced.kind, state, context, typeRef.name, byteBudget);
|
|
702
|
+
}
|
|
703
|
+
function decodePrimitive(primitive, state, context, typeName) {
|
|
704
|
+
const info = primitiveInfo[primitive];
|
|
705
|
+
if (!info) {
|
|
706
|
+
throw new AbiDecodeError(`Primitive type '${primitive}' is not supported yet`, { context });
|
|
707
|
+
}
|
|
708
|
+
if (info.byteLength > 0) {
|
|
709
|
+
ensureAvailable(state, info.byteLength, context);
|
|
710
|
+
}
|
|
711
|
+
const start = state.offset;
|
|
712
|
+
const value = info.read(state.view, state.offset);
|
|
713
|
+
state.offset += info.byteLength;
|
|
714
|
+
const rawHex = sliceHex(state.data, start, state.offset);
|
|
715
|
+
return {
|
|
716
|
+
kind: "primitive",
|
|
717
|
+
primitiveType: primitive,
|
|
718
|
+
value,
|
|
719
|
+
byteOffset: start,
|
|
720
|
+
byteLength: info.byteLength,
|
|
721
|
+
rawHex,
|
|
722
|
+
typeName
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function decodeStruct(struct, state, context, typeName, byteBudget) {
|
|
726
|
+
const start = state.offset;
|
|
727
|
+
const previousScope = state.scope;
|
|
728
|
+
const scope = createScope(previousScope);
|
|
729
|
+
state.scope = scope;
|
|
730
|
+
const trailingSizes = computeTrailingConstantSizes(struct, state.registry);
|
|
731
|
+
const fields = {};
|
|
732
|
+
const fieldOrder = [];
|
|
733
|
+
try {
|
|
734
|
+
struct.fields.forEach((field, index) => {
|
|
735
|
+
const fieldContext = `${context}.${field.name}`;
|
|
736
|
+
if (!struct.attributes.packed) {
|
|
737
|
+
const alignment = getTypeAlignment(field.type, state.registry);
|
|
738
|
+
state.offset = alignUp(state.offset, alignment);
|
|
739
|
+
}
|
|
740
|
+
const tailSize = trailingSizes[index];
|
|
741
|
+
const bytesConsumed = state.offset - start;
|
|
742
|
+
const availableBytes = byteBudget !== void 0 ? Math.max(byteBudget - bytesConsumed, 0) : state.view.byteLength - state.offset;
|
|
743
|
+
const fieldBudget = tailSize !== null ? Math.max(availableBytes - tailSize, 0) : void 0;
|
|
744
|
+
const value = decodeKind(field.type, state, fieldContext, void 0, fieldBudget);
|
|
745
|
+
fields[field.name] = value;
|
|
746
|
+
fieldOrder.push({ name: field.name, value });
|
|
747
|
+
addFieldToScope(scope, field.name, value);
|
|
748
|
+
});
|
|
749
|
+
} finally {
|
|
750
|
+
state.scope = previousScope;
|
|
751
|
+
}
|
|
752
|
+
const end = state.offset;
|
|
753
|
+
return {
|
|
754
|
+
kind: "struct",
|
|
755
|
+
typeName,
|
|
756
|
+
fields,
|
|
757
|
+
fieldOrder,
|
|
758
|
+
byteOffset: start,
|
|
759
|
+
byteLength: end - start,
|
|
760
|
+
rawHex: sliceHex(state.data, start, end)
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
function decodeArray(array, state, context, typeName) {
|
|
764
|
+
const lengthBigInt = evaluateExpression(array.size, state.scope, `${context}[size]`, state.registry);
|
|
765
|
+
const length = bigintToLength(lengthBigInt, context);
|
|
766
|
+
const elements = [];
|
|
767
|
+
const start = state.offset;
|
|
768
|
+
for (let i = 0; i < length; i++) {
|
|
769
|
+
const elementContext = `${context}[${i}]`;
|
|
770
|
+
elements.push(decodeKind(array.elementType, state, elementContext));
|
|
771
|
+
}
|
|
772
|
+
const end = state.offset;
|
|
773
|
+
return {
|
|
774
|
+
kind: "array",
|
|
775
|
+
typeName,
|
|
776
|
+
length,
|
|
777
|
+
elements,
|
|
778
|
+
byteOffset: start,
|
|
779
|
+
byteLength: end - start,
|
|
780
|
+
rawHex: sliceHex(state.data, start, end)
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function decodeEnum(enumType, state, context, typeName) {
|
|
784
|
+
const tagBigInt = evaluateExpression(enumType.tagExpression, state.scope, `${context}[tag]`, state.registry);
|
|
785
|
+
const tagValue = bigintToNumber(tagBigInt, context);
|
|
786
|
+
const variant = enumType.variants.find((entry) => entry.tagValue === tagValue);
|
|
787
|
+
if (!variant) {
|
|
788
|
+
throw new AbiDecodeError(`Enum '${context}' has no variant with tag ${tagValue}`, {
|
|
789
|
+
availableVariants: enumType.variants.map((entry) => entry.tagValue)
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const start = state.offset;
|
|
793
|
+
const value = decodeKind(variant.type, state, `${context}.${variant.name}`);
|
|
794
|
+
const end = state.offset;
|
|
795
|
+
return {
|
|
796
|
+
kind: "enum",
|
|
797
|
+
typeName,
|
|
798
|
+
tagValue,
|
|
799
|
+
variantName: variant.name,
|
|
800
|
+
value,
|
|
801
|
+
byteOffset: start,
|
|
802
|
+
byteLength: end - start,
|
|
803
|
+
rawHex: sliceHex(state.data, start, end)
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function decodeUnion(union, state, context, typeName) {
|
|
807
|
+
const start = state.offset;
|
|
808
|
+
const variantViews = [];
|
|
809
|
+
let unionSize = 0;
|
|
810
|
+
for (const variant of union.variants) {
|
|
811
|
+
const preview = previewDecode(variant.type, state, `${context}.${variant.name}`, start);
|
|
812
|
+
if (preview.value) {
|
|
813
|
+
unionSize = Math.max(unionSize, preview.size ?? 0);
|
|
814
|
+
variantViews.push({ name: variant.name, value: preview.value });
|
|
815
|
+
} else if (preview.error) {
|
|
816
|
+
variantViews.push({
|
|
817
|
+
name: variant.name,
|
|
818
|
+
value: createOpaqueValue(preview.error.message, state.data, start, start, variant.name)
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
ensureAvailable(state, unionSize, context);
|
|
823
|
+
state.offset = start + unionSize;
|
|
824
|
+
return {
|
|
825
|
+
kind: "union",
|
|
826
|
+
typeName,
|
|
827
|
+
variants: variantViews,
|
|
828
|
+
note: "Union decoding is ambiguous; showing all variant interpretations.",
|
|
829
|
+
byteOffset: start,
|
|
830
|
+
byteLength: unionSize,
|
|
831
|
+
rawHex: sliceHex(state.data, start, start + unionSize)
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function decodeSizeDiscriminatedUnion(union, state, context, typeName, byteBudget) {
|
|
835
|
+
const start = state.offset;
|
|
836
|
+
const matches = [];
|
|
837
|
+
const attempts = {};
|
|
838
|
+
for (const variant of union.variants) {
|
|
839
|
+
if (byteBudget !== void 0 && variant.expectedSize > byteBudget) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const preview = previewDecode(variant.type, state, `${context}.${variant.name}`, start, variant.expectedSize);
|
|
843
|
+
if (preview.value && preview.size === variant.expectedSize) {
|
|
844
|
+
matches.push({ variantName: variant.name, value: preview.value, size: preview.size ?? 0, expected: variant.expectedSize });
|
|
845
|
+
} else if (preview.error) {
|
|
846
|
+
attempts[variant.name] = preview.error.message;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (matches.length === 0) {
|
|
850
|
+
throw new AbiDecodeError(`No size-discriminated union variant in '${context}' matched the provided data`, {
|
|
851
|
+
attempts
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
let winner = matches[0];
|
|
855
|
+
if (matches.length > 1) {
|
|
856
|
+
if (byteBudget !== void 0) {
|
|
857
|
+
const exact = matches.filter((match) => match.expected === byteBudget);
|
|
858
|
+
if (exact.length === 1) {
|
|
859
|
+
winner = exact[0];
|
|
860
|
+
} else {
|
|
861
|
+
throw new AbiDecodeError(`Multiple size-discriminated union variants in '${context}' matched the provided data`, {
|
|
862
|
+
matches: matches.map((match) => match.variantName)
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
} else {
|
|
866
|
+
throw new AbiDecodeError(`Multiple size-discriminated union variants in '${context}' matched the provided data`, {
|
|
867
|
+
matches: matches.map((match) => match.variantName)
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
ensureAvailable(state, winner.size, context);
|
|
872
|
+
state.offset = start + winner.size;
|
|
873
|
+
return {
|
|
874
|
+
kind: "size-discriminated-union",
|
|
875
|
+
typeName,
|
|
876
|
+
variantName: winner.variantName,
|
|
877
|
+
expectedSize: winner.expected,
|
|
878
|
+
value: winner.value,
|
|
879
|
+
byteOffset: start,
|
|
880
|
+
byteLength: winner.size,
|
|
881
|
+
rawHex: sliceHex(state.data, start, start + winner.size)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function previewDecode(kind, state, context, start, limit) {
|
|
885
|
+
const snapshotOffset = state.offset;
|
|
886
|
+
const snapshotScope = state.scope;
|
|
887
|
+
const snapshotView = state.view;
|
|
888
|
+
const snapshotData = state.data;
|
|
889
|
+
if (limit !== void 0) {
|
|
890
|
+
if (start + limit > snapshotView.byteLength) {
|
|
891
|
+
return { error: new AbiDecodeError(`Variant '${context}' requires ${limit} bytes but only ${snapshotView.byteLength - start} remain`) };
|
|
892
|
+
}
|
|
893
|
+
state.view = new DataView(snapshotView.buffer, snapshotView.byteOffset + start, limit);
|
|
894
|
+
state.data = new Uint8Array(snapshotData.buffer, snapshotData.byteOffset + start, limit);
|
|
895
|
+
state.offset = 0;
|
|
896
|
+
} else {
|
|
897
|
+
state.offset = start;
|
|
898
|
+
}
|
|
899
|
+
try {
|
|
900
|
+
const value = decodeKind(kind, state, context);
|
|
901
|
+
const size = limit !== void 0 ? state.offset : state.offset - start;
|
|
902
|
+
return { value, size };
|
|
903
|
+
} catch (error) {
|
|
904
|
+
if (error instanceof AbiDecodeError) {
|
|
905
|
+
return { error };
|
|
906
|
+
}
|
|
907
|
+
return { error: new AbiDecodeError(error.message ?? `Failed to decode variant for ${context}`) };
|
|
908
|
+
} finally {
|
|
909
|
+
state.offset = snapshotOffset;
|
|
910
|
+
state.scope = snapshotScope;
|
|
911
|
+
state.view = snapshotView;
|
|
912
|
+
state.data = snapshotData;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
function bigintToLength(length, context) {
|
|
916
|
+
if (length < 0n) {
|
|
917
|
+
throw new AbiDecodeError(`Array length expression in '${context}' evaluated to a negative value`);
|
|
918
|
+
}
|
|
919
|
+
const number = Number(length);
|
|
920
|
+
if (!Number.isSafeInteger(number)) {
|
|
921
|
+
throw new AbiDecodeError(`Array length expression in '${context}' exceeds JavaScript's safe integer range`);
|
|
922
|
+
}
|
|
923
|
+
return number;
|
|
924
|
+
}
|
|
925
|
+
function bigintToNumber(value, context) {
|
|
926
|
+
const number = Number(value);
|
|
927
|
+
if (!Number.isSafeInteger(number)) {
|
|
928
|
+
throw new AbiDecodeError(`Expression in '${context}' resulted in a value outside of JS safe integer range`);
|
|
929
|
+
}
|
|
930
|
+
return number;
|
|
931
|
+
}
|
|
932
|
+
function ensureAvailable(state, size, context) {
|
|
933
|
+
if (state.offset + size > state.view.byteLength) {
|
|
934
|
+
throw new AbiDecodeError(`Insufficient data while decoding '${context}'`, {
|
|
935
|
+
requested: size,
|
|
936
|
+
remaining: state.view.byteLength - state.offset
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
function sliceHex(buffer, start, end) {
|
|
941
|
+
return bytesToHex(buffer.subarray(start, end));
|
|
942
|
+
}
|
|
943
|
+
function createOpaqueValue(description, buffer, start, end, typeName) {
|
|
944
|
+
return {
|
|
945
|
+
kind: "opaque",
|
|
946
|
+
description,
|
|
947
|
+
byteOffset: start,
|
|
948
|
+
byteLength: end - start,
|
|
949
|
+
rawHex: sliceHex(buffer, start, end),
|
|
950
|
+
typeName
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function computeTrailingConstantSizes(struct, registry) {
|
|
954
|
+
const memo = /* @__PURE__ */ new Map();
|
|
955
|
+
const sizes = [];
|
|
956
|
+
for (let i = 0; i < struct.fields.length; i++) {
|
|
957
|
+
let total = 0;
|
|
958
|
+
let deterministic = true;
|
|
959
|
+
for (let j = i + 1; j < struct.fields.length; j++) {
|
|
960
|
+
const size = getConstSize(struct.fields[j].type, registry, memo, /* @__PURE__ */ new Set());
|
|
961
|
+
if (size === null) {
|
|
962
|
+
deterministic = false;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
total += size;
|
|
966
|
+
}
|
|
967
|
+
sizes.push(deterministic ? total : null);
|
|
968
|
+
}
|
|
969
|
+
return sizes;
|
|
970
|
+
}
|
|
971
|
+
function getConstSize(kind, registry, memo, stack) {
|
|
972
|
+
switch (kind.kind) {
|
|
973
|
+
case "primitive":
|
|
974
|
+
return primitiveInfo[kind.primitive].byteLength;
|
|
975
|
+
case "array": {
|
|
976
|
+
const elementSize = getConstSize(kind.elementType, registry, memo, stack);
|
|
977
|
+
if (elementSize === null) return null;
|
|
978
|
+
const length = evaluateConstExpression(kind.size);
|
|
979
|
+
if (length === null) return null;
|
|
980
|
+
return elementSize * Number(length);
|
|
981
|
+
}
|
|
982
|
+
case "struct": {
|
|
983
|
+
let total = 0;
|
|
984
|
+
for (const field of kind.fields) {
|
|
985
|
+
const size = getConstSize(field.type, registry, memo, stack);
|
|
986
|
+
if (size === null) return null;
|
|
987
|
+
total += size;
|
|
988
|
+
}
|
|
989
|
+
return total;
|
|
990
|
+
}
|
|
991
|
+
case "enum": {
|
|
992
|
+
let variantSize = null;
|
|
993
|
+
for (const variant of kind.variants) {
|
|
994
|
+
const size = getConstSize(variant.type, registry, memo, stack);
|
|
995
|
+
if (size === null) return null;
|
|
996
|
+
if (variantSize === null) variantSize = size;
|
|
997
|
+
else if (variantSize !== size) return null;
|
|
998
|
+
}
|
|
999
|
+
return variantSize;
|
|
1000
|
+
}
|
|
1001
|
+
case "union": {
|
|
1002
|
+
let maxSize = 0;
|
|
1003
|
+
for (const variant of kind.variants) {
|
|
1004
|
+
const size = getConstSize(variant.type, registry, memo, stack);
|
|
1005
|
+
if (size === null) return null;
|
|
1006
|
+
maxSize = Math.max(maxSize, size);
|
|
1007
|
+
}
|
|
1008
|
+
return maxSize;
|
|
1009
|
+
}
|
|
1010
|
+
case "size-discriminated-union":
|
|
1011
|
+
return null;
|
|
1012
|
+
case "type-ref":
|
|
1013
|
+
if (memo.has(kind.name)) {
|
|
1014
|
+
return memo.get(kind.name) ?? null;
|
|
1015
|
+
}
|
|
1016
|
+
if (stack.has(kind.name)) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
stack.add(kind.name);
|
|
1020
|
+
const resolved = registry.get(kind.name);
|
|
1021
|
+
const resolvedSize = getConstSize(resolved.kind, registry, memo, stack);
|
|
1022
|
+
stack.delete(kind.name);
|
|
1023
|
+
memo.set(kind.name, resolvedSize);
|
|
1024
|
+
return resolvedSize;
|
|
1025
|
+
default:
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
function evaluateConstExpression(expression) {
|
|
1030
|
+
switch (expression.type) {
|
|
1031
|
+
case "literal":
|
|
1032
|
+
return expression.value;
|
|
1033
|
+
case "binary": {
|
|
1034
|
+
const left = evaluateConstExpression(expression.left);
|
|
1035
|
+
const right = evaluateConstExpression(expression.right);
|
|
1036
|
+
if (left === null || right === null) return null;
|
|
1037
|
+
switch (expression.op) {
|
|
1038
|
+
case "add":
|
|
1039
|
+
return left + right;
|
|
1040
|
+
case "sub":
|
|
1041
|
+
return left - right;
|
|
1042
|
+
case "mul":
|
|
1043
|
+
return left * right;
|
|
1044
|
+
case "div":
|
|
1045
|
+
if (right === 0n) return null;
|
|
1046
|
+
return left / right;
|
|
1047
|
+
default:
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
case "field-ref":
|
|
1052
|
+
case "unary":
|
|
1053
|
+
case "sizeof":
|
|
1054
|
+
case "alignof":
|
|
1055
|
+
return null;
|
|
1056
|
+
default:
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
var primitiveInfo = {
|
|
1061
|
+
u8: { byteLength: 1, read: (view, offset) => view.getUint8(offset) },
|
|
1062
|
+
i8: { byteLength: 1, read: (view, offset) => view.getInt8(offset) },
|
|
1063
|
+
u16: { byteLength: 2, read: (view, offset) => view.getUint16(offset, true) },
|
|
1064
|
+
i16: { byteLength: 2, read: (view, offset) => view.getInt16(offset, true) },
|
|
1065
|
+
u32: { byteLength: 4, read: (view, offset) => view.getUint32(offset, true) },
|
|
1066
|
+
i32: { byteLength: 4, read: (view, offset) => view.getInt32(offset, true) },
|
|
1067
|
+
u64: { byteLength: 8, read: (view, offset) => view.getBigUint64(offset, true) },
|
|
1068
|
+
i64: { byteLength: 8, read: (view, offset) => view.getBigInt64(offset, true) },
|
|
1069
|
+
f32: { byteLength: 4, read: (view, offset) => view.getFloat32(offset, true) },
|
|
1070
|
+
f64: { byteLength: 8, read: (view, offset) => view.getFloat64(offset, true) },
|
|
1071
|
+
f16: {
|
|
1072
|
+
byteLength: 2,
|
|
1073
|
+
read: (view, offset) => {
|
|
1074
|
+
return view.getUint16(offset, true);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
function getTypeAlignment(kind, registry) {
|
|
1079
|
+
switch (kind.kind) {
|
|
1080
|
+
case "primitive":
|
|
1081
|
+
return primitiveInfo[kind.primitive].byteLength;
|
|
1082
|
+
case "struct":
|
|
1083
|
+
if (kind.attributes.aligned > 0) return kind.attributes.aligned;
|
|
1084
|
+
return kind.fields.reduce((max, field) => Math.max(max, getTypeAlignment(field.type, registry)), 1);
|
|
1085
|
+
case "array":
|
|
1086
|
+
return getTypeAlignment(kind.elementType, registry);
|
|
1087
|
+
case "enum":
|
|
1088
|
+
if (kind.attributes.aligned > 0) return kind.attributes.aligned;
|
|
1089
|
+
return 1;
|
|
1090
|
+
// Fallback
|
|
1091
|
+
case "union":
|
|
1092
|
+
if (kind.attributes.aligned > 0) return kind.attributes.aligned;
|
|
1093
|
+
return kind.variants.reduce((max, variant) => Math.max(max, getTypeAlignment(variant.type, registry)), 1);
|
|
1094
|
+
case "size-discriminated-union":
|
|
1095
|
+
if (kind.attributes.aligned > 0) return kind.attributes.aligned;
|
|
1096
|
+
return kind.variants.reduce((max, variant) => Math.max(max, getTypeAlignment(variant.type, registry)), 1);
|
|
1097
|
+
case "type-ref":
|
|
1098
|
+
return getTypeAlignment(registry.get(kind.name).kind, registry);
|
|
1099
|
+
default:
|
|
1100
|
+
return 1;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export { AbiDecodeError, AbiError, AbiParseError, AbiValidationError, TypeRegistry, buildTypeRegistry, decodeData, parseAbiDocument };
|
|
1105
|
+
//# sourceMappingURL=index.js.map
|
|
1106
|
+
//# sourceMappingURL=index.js.map
|