@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 ADDED
@@ -0,0 +1,174 @@
1
+ # @thru/abi – TypeScript ABI Reflection Layer
2
+
3
+ This package provides a browser-friendly ABI reflection+decoding engine that matches the thru-net on-chain ABI semantics. The goal is to let Explorer-grade UIs take any **flattened ABI YAML** plus raw account bytes and produce fully structured, well-annotated decoded values without generating code ahead of time.
4
+
5
+ The README is intentionally verbose so a future engineer (human or AI) can understand the full behavior surface without spelunking through source.
6
+
7
+ ---
8
+
9
+ ## Quick Start
10
+
11
+ ```ts
12
+ import { decodeData } from "@thru/abi";
13
+ import myAbi from "./abi/counter.abi.yaml?raw";
14
+
15
+ const decoded = decodeData(myAbi, "CounterAccount", accountDataUint8Array);
16
+
17
+ if (decoded.kind === "struct") {
18
+ console.log(decoded.fields.count);
19
+ }
20
+ ```
21
+
22
+ * `decodeData(yamlText, typeName, data)` is the only public runtime API.
23
+ * `yamlText` **must be flattened** (imports already resolved). This is enforced at parse time.
24
+ * The returned `DecodedValue` tree contains both semantic data and raw byte slices for UI inspection.
25
+
26
+ ---
27
+
28
+ ## Package Layout
29
+
30
+ | Path | Purpose |
31
+ | ---- | ------- |
32
+ | `src/abiSchema.ts` | YAML parser + TypeScript interfaces aligned with the thru-net ABI schema. |
33
+ | `src/typeRegistry.ts` | Builds validated registry, resolves type refs, detects cycles. |
34
+ | `src/decoder.ts` | Core reflection engine. Handles arrays, structs, unions, enums, SDUs, padding, f16, etc. |
35
+ | `src/expression.ts` | Evaluates ABI expressions (field refs, arithmetic, bitwise, sizeof/alignof). |
36
+ | `src/decodedValue.ts` | Canonical decoded shape consumed by Explorer UI. |
37
+ | `test/` | Hand-authored fixtures mirroring Rust compliance tests (e.g., `structs.abi.yaml`). |
38
+
39
+ ---
40
+
41
+ ## Feature Matrix
42
+
43
+ ### ✅ Implemented
44
+
45
+ * **Schema Parsing**
46
+ * Matches the thru-net ABI AST (primitives, structs, arrays, unions, enums, size-discriminated unions, type-refs).
47
+ * Validates flattened files (no `imports`), duplicate names, dangling refs, type cycles.
48
+
49
+ * **Expression Engine**
50
+ * Literals (u/i 8–64), field references with lexical scopes (`["..","parent"]` supported).
51
+ * Arithmetic: `add`, `sub`, `mul`, `div`, `mod`.
52
+ * Bitwise: `bit-and`, `bit-or`, `bit-xor`, `bit-not`, `left-shift`, `right-shift`.
53
+ * Meta: `sizeof(type-name)`, `alignof(type-name)` leveraging shared footprint helpers.
54
+
55
+ * **Decoding Semantics**
56
+ * **Structs:** assumes `packed: true` containers (our blockchain layout never inserts padding). `aligned` overrides are reserved for future use.
57
+ * **Arrays:** dynamic element count via expressions referencing previously decoded fields.
58
+ * **Enums:** tag derived from expressions; variant payload decoded inline.
59
+ * **Unions:** best-effort “preview all variants” strategy—each variant is decoded in isolation, results presented side-by-side (important for Explorer UX).
60
+ * **Size-Discriminated Unions:** tries each variant with byte budgets, supports placement mid-struct by reserving trailing fixed sizes.
61
+ * **Type-Refs:** recursion-safe (cycle detection done in registry); `decodeKind` transparently resolves nested refs.
62
+ * **Primitives:** all integer + float types from ABI spec, including `f16` (returned as `number` representing raw `u16` for now).
63
+
64
+ * **DecodedValue Shape**
65
+ * Each node exposes `kind`, `typeName`, `byteOffset`, `byteLength`, `rawHex`.
66
+ * Structs provide both `fields` (object) and `fieldOrder` (array preserving declaration order).
67
+ * Arrays expose `length`, `elements`.
68
+ * Enums/Unions/SDUs capture tag or variant metadata.
69
+ * When something can’t be safely decoded (e.g., ambiguous union variant), an `opaque` node contains context + `rawHex`.
70
+
71
+ * **Testing Harness**
72
+ * Uses `tsx`/`vitest`. The repo includes sample fixture script `test/verify-rectangle.ts` showing end-to-end usage.
73
+ * Additional tests exist under `src/index.test.ts` covering primitives, arrays, expressions, unions, etc.
74
+
75
+ ### ⚠️ Known Limitations (as of this snapshot)
76
+
77
+ * **Runtime Performance**
78
+ * YAML is parsed on every `decodeData` call. No caching or schema memoization yet.
79
+ * Expressions evaluate with BigInt math. That’s correct but slower than precomputed constants.
80
+
81
+ * **Instruction Decoding**
82
+ * Current focus is account data. Instruction decoding isn’t implemented yet (needs call-site context + discriminants).
83
+
84
+ * **Union Heuristics**
85
+ * Unlike Rust (which requires external hints), we decode *every* variant. That’s user-friendly but does not pick a “canonical” variant automatically. Upstream UI must choose how to present ambiguous unions.
86
+
87
+ * **Float16 Conversion**
88
+ * We currently return the raw `u16` bits. A helper to convert to IEEE-754 half floats can be added later if needed for display accuracy.
89
+
90
+ * **Error Surfacing**
91
+ * Errors throw `AbiParseError`, `AbiValidationError`, or `AbiDecodeError`. Explorer code should catch and surface the message. There’s no structured warning channel yet (e.g., partial decodes).
92
+
93
+ * **Security / Untrusted ABIs**
94
+ * The parser enforces flattened files and forbids unknown fields, but we still assume ABIs come from trusted sources. Malicious ABIs could attempt to trick UIs (e.g., wrong type names). A future enhancement could add allow-lists or signatures.
95
+
96
+ ---
97
+
98
+ ## How Decoding Works (Step-by-step)
99
+
100
+ 1. **Parse & Validate**
101
+ * `parseAbiDocument` -> `AbiDocument`.
102
+ * `buildTypeRegistry` indexes types, verifies refs, catches cycles.
103
+
104
+ 2. **Prepare State**
105
+ * `decodeData` creates `DecodeState` (`Uint8Array`, `DataView`, root scope).
106
+
107
+ 3. **Walk Type Tree**
108
+ * `decodeKind` dispatches by `kind` (primitive, struct, array, etc.).
109
+ * Each decoder carries a byte budget so flexible members can live mid-struct while respecting trailing fixed-size fields.
110
+
111
+ 4. **Field Scope & Expressions**
112
+ * After each field decode, `addFieldToScope` records the result so later `field-ref`s can use it.
113
+ * Expressions are evaluated lazily during decoding (e.g., array lengths, enum tags).
114
+
115
+ 5. **Alignment Rules**
116
+ * Structs are emitted and decoded as `packed: true`, so offsets advance exactly by the previous field’s byte length.
117
+ * Alignment metadata is currently ignored because thru-net ABIs never request padding.
118
+
119
+ 6. **Result Assembly**
120
+ * Every decoded chunk captures offset, size, and raw hex slice so UIs can show byte-level views alongside structured data.
121
+
122
+ ---
123
+
124
+ ## CLI / Development Workflow
125
+
126
+ ```bash
127
+ # Install deps
128
+ pnpm install
129
+
130
+ # Build ESM bundle + type declarations
131
+ pnpm --filter @thru/abi build
132
+
133
+ # Run test suite (vitest)
134
+ pnpm --filter @thru/abi test
135
+
136
+ # One-off verification script example
137
+ npx tsx web/packages/abi/test/verify-rectangle.ts
138
+ ```
139
+
140
+ CI typically runs `build` and `test`. The package outputs ESM suitable for modern bundlers (Vite, Next.js, etc.).
141
+
142
+ ---
143
+
144
+ ## Integration Guidance
145
+
146
+ * **Explorer**
147
+ * Keep ABIs as `.yaml` + `.bin` fixtures or embed them using `?raw`.
148
+ * Wrap `decodeData` in a try/catch; surface errors in the UI.
149
+ * Use `fieldOrder` to render tables with deterministic ordering.
150
+ * Use `rawHex` for fallback views when a field type is `opaque`.
151
+
152
+ * **Other Consumers**
153
+ * Libraries/tools can reuse `parseAbiDocument` + `TypeRegistry` to build higher-level abstractions (e.g., caching parsed schemas, precomputing footprints).
154
+
155
+ ---
156
+
157
+ ## Future Roadmap Ideas
158
+
159
+ 1. **Schema Caching** – hash YAML text and reuse parsed `AbiDocument`/`TypeRegistry`.
160
+ 2. **Instruction Decoding** – need discriminants + context (program ID, variant mapping).
161
+ 3. **Float16 Conversion Helper** – convert raw u16 to JS number with proper rounding.
162
+ 4. **Diagnostics API** – return warnings for ambiguous unions, unsupported expressions, etc., instead of throwing.
163
+ 5. **Web Worker Integration** – offload heavy decodes to worker threads for large accounts.
164
+
165
+ ---
166
+
167
+ ## Support / Contact
168
+
169
+ * Code owner: thru-net Explorer team.
170
+ * If something decodes differently from the Rust validator, cross-check against the reference implementation under `abi/abi_reflect` and open an issue referencing the specific ABI + binary pair.
171
+ * When adding new ABI features, update both this README and `GAPS_AND_PLAN.md` so future iterations know the exact capability boundaries.
172
+
173
+ Happy decoding! 🎯
174
+
@@ -0,0 +1,185 @@
1
+ type PrimitiveName = "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f16" | "f32" | "f64";
2
+ interface ContainerAttributes {
3
+ packed: boolean;
4
+ aligned: number;
5
+ comment?: string;
6
+ }
7
+ interface AbiMetadata {
8
+ package: string;
9
+ abiVersion: number;
10
+ packageVersion?: string;
11
+ description?: string;
12
+ }
13
+ interface AbiDocument {
14
+ metadata: AbiMetadata;
15
+ types: TypeDefinition[];
16
+ }
17
+ interface TypeDefinition {
18
+ name: string;
19
+ kind: TypeKind;
20
+ }
21
+ type TypeKind = PrimitiveType | StructType | ArrayType | EnumType | UnionType | SizeDiscriminatedUnionType | TypeRefType;
22
+ interface PrimitiveType {
23
+ kind: "primitive";
24
+ primitive: PrimitiveName;
25
+ }
26
+ interface StructField {
27
+ name: string;
28
+ type: TypeKind;
29
+ }
30
+ interface StructType {
31
+ kind: "struct";
32
+ attributes: ContainerAttributes;
33
+ fields: StructField[];
34
+ }
35
+ interface ArrayType {
36
+ kind: "array";
37
+ attributes: ContainerAttributes;
38
+ elementType: TypeKind;
39
+ size: Expression;
40
+ }
41
+ interface EnumVariant {
42
+ name: string;
43
+ tagValue: number;
44
+ type: TypeKind;
45
+ }
46
+ interface EnumType {
47
+ kind: "enum";
48
+ attributes: ContainerAttributes;
49
+ tagExpression: Expression;
50
+ variants: EnumVariant[];
51
+ }
52
+ interface UnionVariant {
53
+ name: string;
54
+ type: TypeKind;
55
+ }
56
+ interface UnionType {
57
+ kind: "union";
58
+ attributes: ContainerAttributes;
59
+ variants: UnionVariant[];
60
+ }
61
+ interface SizeDiscriminatedVariant {
62
+ name: string;
63
+ expectedSize: number;
64
+ type: TypeKind;
65
+ }
66
+ interface SizeDiscriminatedUnionType {
67
+ kind: "size-discriminated-union";
68
+ attributes: ContainerAttributes;
69
+ variants: SizeDiscriminatedVariant[];
70
+ }
71
+ interface TypeRefType {
72
+ kind: "type-ref";
73
+ name: string;
74
+ }
75
+ type Expression = LiteralExpression | FieldRefExpression | BinaryExpression | UnaryExpression | SizeOfExpression | AlignOfExpression;
76
+ interface LiteralExpression {
77
+ type: "literal";
78
+ literalType: PrimitiveName;
79
+ value: bigint;
80
+ }
81
+ interface FieldRefExpression {
82
+ type: "field-ref";
83
+ path: string[];
84
+ }
85
+ type BinaryOperator = "add" | "sub" | "mul" | "div" | "mod" | "bit-and" | "bit-or" | "bit-xor" | "left-shift" | "right-shift";
86
+ interface BinaryExpression {
87
+ type: "binary";
88
+ op: BinaryOperator;
89
+ left: Expression;
90
+ right: Expression;
91
+ }
92
+ type UnaryOperator = "bit-not";
93
+ interface UnaryExpression {
94
+ type: "unary";
95
+ op: UnaryOperator;
96
+ operand: Expression;
97
+ }
98
+ interface SizeOfExpression {
99
+ type: "sizeof";
100
+ typeName: string;
101
+ }
102
+ interface AlignOfExpression {
103
+ type: "alignof";
104
+ typeName: string;
105
+ }
106
+ declare function parseAbiDocument(yamlText: string): AbiDocument;
107
+
108
+ type DecodedValueKind = "primitive" | "struct" | "array" | "enum" | "union" | "size-discriminated-union" | "opaque";
109
+ interface BaseDecodedValue {
110
+ kind: DecodedValueKind;
111
+ typeName?: string;
112
+ byteOffset: number;
113
+ byteLength: number;
114
+ rawHex: string;
115
+ }
116
+ interface DecodedPrimitiveValue extends BaseDecodedValue {
117
+ kind: "primitive";
118
+ primitiveType: PrimitiveName;
119
+ value: number | bigint;
120
+ }
121
+ interface DecodedField {
122
+ name: string;
123
+ value: DecodedValue;
124
+ }
125
+ interface DecodedStructValue extends BaseDecodedValue {
126
+ kind: "struct";
127
+ fields: Record<string, DecodedValue>;
128
+ fieldOrder: DecodedField[];
129
+ }
130
+ interface DecodedArrayValue extends BaseDecodedValue {
131
+ kind: "array";
132
+ length: number;
133
+ elements: DecodedValue[];
134
+ }
135
+ interface DecodedEnumValue extends BaseDecodedValue {
136
+ kind: "enum";
137
+ tagValue: number;
138
+ variantName: string;
139
+ value: DecodedValue | null;
140
+ }
141
+ interface DecodedUnionValue extends BaseDecodedValue {
142
+ kind: "union";
143
+ variants: DecodedField[];
144
+ note?: string;
145
+ }
146
+ interface DecodedSizeDiscriminatedUnionValue extends BaseDecodedValue {
147
+ kind: "size-discriminated-union";
148
+ variantName: string;
149
+ expectedSize: number;
150
+ value: DecodedValue;
151
+ }
152
+ interface DecodedOpaqueValue extends BaseDecodedValue {
153
+ kind: "opaque";
154
+ description: string;
155
+ }
156
+ type DecodedValue = DecodedPrimitiveValue | DecodedStructValue | DecodedArrayValue | DecodedEnumValue | DecodedUnionValue | DecodedSizeDiscriminatedUnionValue | DecodedOpaqueValue;
157
+
158
+ declare class TypeRegistry {
159
+ private readonly types;
160
+ constructor(definitions: Iterable<TypeDefinition>);
161
+ get(typeName: string): TypeDefinition;
162
+ has(typeName: string): boolean;
163
+ entries(): IterableIterator<[string, TypeDefinition]>;
164
+ }
165
+ declare function buildTypeRegistry(document: AbiDocument): TypeRegistry;
166
+
167
+ declare function decodeData(yamlText: string, typeName: string, data: Uint8Array): DecodedValue;
168
+
169
+ type AbiErrorCode = "PARSE_ERROR" | "VALIDATION_ERROR" | "DECODE_ERROR";
170
+ declare class AbiError extends Error {
171
+ readonly code: AbiErrorCode;
172
+ readonly details?: Record<string, unknown>;
173
+ constructor(code: AbiErrorCode, message: string, details?: Record<string, unknown>);
174
+ }
175
+ declare class AbiParseError extends AbiError {
176
+ constructor(message: string, details?: Record<string, unknown>);
177
+ }
178
+ declare class AbiValidationError extends AbiError {
179
+ constructor(message: string, details?: Record<string, unknown>);
180
+ }
181
+ declare class AbiDecodeError extends AbiError {
182
+ constructor(message: string, details?: Record<string, unknown>);
183
+ }
184
+
185
+ export { AbiDecodeError, AbiError, AbiParseError, AbiValidationError, type DecodedArrayValue, type DecodedEnumValue, type DecodedField, type DecodedPrimitiveValue, type DecodedSizeDiscriminatedUnionValue, type DecodedStructValue, type DecodedUnionValue, type DecodedValue, TypeRegistry, buildTypeRegistry, decodeData, parseAbiDocument };