@subsquid/evm-codec 0.2.1 → 1.0.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/lib/codec.d.ts +59 -12
- package/lib/codec.d.ts.map +1 -1
- package/lib/codec.js.map +1 -1
- package/lib/codecs/array.d.ts +3 -3
- package/lib/codecs/array.d.ts.map +1 -1
- package/lib/codecs/array.js +13 -7
- package/lib/codecs/array.js.map +1 -1
- package/lib/codecs/primitives.d.ts +2 -16
- package/lib/codecs/primitives.d.ts.map +1 -1
- package/lib/codecs/primitives.js +52 -141
- package/lib/codecs/primitives.js.map +1 -1
- package/lib/codecs/struct.d.ts +17 -9
- package/lib/codecs/struct.d.ts.map +1 -1
- package/lib/codecs/struct.js +53 -39
- package/lib/codecs/struct.js.map +1 -1
- package/lib/dsl.d.ts +11 -0
- package/lib/dsl.d.ts.map +1 -0
- package/lib/dsl.js +33 -0
- package/lib/dsl.js.map +1 -0
- package/lib/index.d.ts +7 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -6
- package/lib/index.js.map +1 -1
- package/lib/sink/bounds.d.ts +21 -0
- package/lib/sink/bounds.d.ts.map +1 -0
- package/lib/sink/bounds.js +24 -0
- package/lib/sink/bounds.js.map +1 -0
- package/lib/sink/bytes.d.ts +41 -0
- package/lib/sink/bytes.d.ts.map +1 -0
- package/lib/sink/bytes.js +261 -0
- package/lib/sink/bytes.js.map +1 -0
- package/lib/sink/hex.d.ts +33 -0
- package/lib/sink/hex.d.ts.map +1 -0
- package/lib/sink/hex.js +289 -0
- package/lib/sink/hex.js.map +1 -0
- package/lib/{src.d.ts → src/bytes.d.ts} +7 -5
- package/lib/src/bytes.d.ts.map +1 -0
- package/lib/src/bytes.js +161 -0
- package/lib/src/bytes.js.map +1 -0
- package/lib/src/hex.d.ts +33 -0
- package/lib/src/hex.d.ts.map +1 -0
- package/lib/src/hex.js +164 -0
- package/lib/src/hex.js.map +1 -0
- package/lib/util.d.ts +6 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +20 -0
- package/lib/util.js.map +1 -0
- package/package.json +6 -8
- package/src/codec.ts +67 -18
- package/src/codecs/array.test.ts +87 -0
- package/src/codecs/array.ts +9 -12
- package/src/codecs/primitives.test.ts +27 -0
- package/src/codecs/primitives.ts +87 -191
- package/src/codecs/struct.test.ts +69 -0
- package/src/codecs/struct.ts +80 -60
- package/src/dsl.ts +16 -0
- package/src/index.ts +7 -4
- package/src/sink/bounds.ts +26 -0
- package/src/sink/bytes.test.ts +92 -0
- package/src/sink/bytes.ts +290 -0
- package/src/sink/hex.ts +311 -0
- package/src/src/bytes.test.ts +114 -0
- package/src/src/bytes.ts +187 -0
- package/src/src/hex.ts +191 -0
- package/src/util.ts +19 -0
- package/lib/safeToNumber.d.ts +0 -2
- package/lib/safeToNumber.d.ts.map +0 -1
- package/lib/safeToNumber.js +0 -11
- package/lib/safeToNumber.js.map +0 -1
- package/lib/sink.d.ts +0 -43
- package/lib/sink.d.ts.map +0 -1
- package/lib/sink.js +0 -215
- package/lib/sink.js.map +0 -1
- package/lib/src.d.ts.map +0 -1
- package/lib/src.js +0 -141
- package/lib/src.js.map +0 -1
- package/src/safeToNumber.ts +0 -6
- package/src/sink.ts +0 -241
- package/src/src.ts +0 -158
package/src/codecs/primitives.ts
CHANGED
|
@@ -1,185 +1,93 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Codec } from '../codec'
|
|
3
|
-
import { Sink } from '../sink'
|
|
4
|
-
import { Src } from '../src'
|
|
5
|
-
import { ArrayCodec, FixedSizeArrayCodec } from './array'
|
|
6
|
-
import { StructCodec } from './struct'
|
|
7
|
-
import { safeToNumber } from '../safeToNumber'
|
|
1
|
+
import type {Codec, Sink, Src} from '../codec'
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
decode(src: Src): boolean {
|
|
16
|
-
return src.bool()
|
|
17
|
-
},
|
|
18
|
-
isDynamic: false,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const uint8: Codec<Numberish, number> = {
|
|
22
|
-
encode(sink: Sink, val: Numberish) {
|
|
23
|
-
sink.u8(safeToNumber(val))
|
|
24
|
-
},
|
|
25
|
-
decode(src: Src): number {
|
|
26
|
-
return src.u8()
|
|
27
|
-
},
|
|
28
|
-
isDynamic: false,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const int8: Codec<Numberish, number> = {
|
|
32
|
-
encode(sink: Sink, val: Numberish) {
|
|
33
|
-
sink.i8(safeToNumber(val))
|
|
34
|
-
},
|
|
35
|
-
decode(src: Src): number {
|
|
36
|
-
return src.i8()
|
|
37
|
-
},
|
|
38
|
-
isDynamic: false,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const uint16: Codec<Numberish, number> = {
|
|
42
|
-
encode(sink: Sink, val: Numberish) {
|
|
43
|
-
sink.u16(safeToNumber(val))
|
|
44
|
-
},
|
|
45
|
-
decode(src: Src): number {
|
|
46
|
-
return src.u16()
|
|
47
|
-
},
|
|
48
|
-
isDynamic: false,
|
|
3
|
+
function safeToNumber(value: number | bigint): number {
|
|
4
|
+
if (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) {
|
|
5
|
+
throw new Error(`${value} is not a safe integer`)
|
|
6
|
+
}
|
|
7
|
+
return Number(value)
|
|
49
8
|
}
|
|
50
9
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
decode(src: Src): number {
|
|
56
|
-
return src.i16()
|
|
57
|
-
},
|
|
58
|
-
isDynamic: false,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const uint32: Codec<Numberish, number> = {
|
|
62
|
-
encode(sink: Sink, val: Numberish) {
|
|
63
|
-
sink.u32(safeToNumber(val))
|
|
64
|
-
},
|
|
65
|
-
decode(src: Src): number {
|
|
66
|
-
return src.u32()
|
|
67
|
-
},
|
|
68
|
-
isDynamic: false,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const int32: Codec<Numberish, number> = {
|
|
72
|
-
encode(sink: Sink, val: Numberish) {
|
|
73
|
-
sink.i32(safeToNumber(val))
|
|
74
|
-
},
|
|
75
|
-
decode(src: Src): number {
|
|
76
|
-
return src.i32()
|
|
77
|
-
},
|
|
78
|
-
isDynamic: false,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const uint64: Codec<Numberish, bigint> = {
|
|
82
|
-
encode(sink: Sink, val: Numberish) {
|
|
83
|
-
sink.u64(BigInt(val))
|
|
84
|
-
},
|
|
85
|
-
decode(src: Src): bigint {
|
|
86
|
-
return src.u64()
|
|
87
|
-
},
|
|
88
|
-
isDynamic: false,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const int64: Codec<Numberish, bigint> = {
|
|
92
|
-
encode(sink: Sink, val: Numberish) {
|
|
93
|
-
sink.i64(BigInt(val))
|
|
94
|
-
},
|
|
95
|
-
decode(src: Src): bigint {
|
|
96
|
-
return src.i64()
|
|
97
|
-
},
|
|
98
|
-
isDynamic: false,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export const uint128: Codec<Numberish, bigint> = {
|
|
102
|
-
encode(sink: Sink, val: Numberish) {
|
|
103
|
-
sink.u128(BigInt(val))
|
|
104
|
-
},
|
|
105
|
-
decode(src: Src): bigint {
|
|
106
|
-
return src.u128()
|
|
107
|
-
},
|
|
108
|
-
isDynamic: false,
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export const int128: Codec<Numberish, bigint> = {
|
|
112
|
-
encode(sink: Sink, val: Numberish) {
|
|
113
|
-
sink.i128(BigInt(val))
|
|
114
|
-
},
|
|
115
|
-
decode(src: Src): bigint {
|
|
116
|
-
return src.i128()
|
|
117
|
-
},
|
|
118
|
-
isDynamic: false,
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export const uint256: Codec<Numberish, bigint> = {
|
|
122
|
-
encode(sink: Sink, val: Numberish) {
|
|
123
|
-
sink.u256(BigInt(val))
|
|
124
|
-
},
|
|
125
|
-
decode(src: Src): bigint {
|
|
126
|
-
return src.u256()
|
|
127
|
-
},
|
|
128
|
-
isDynamic: false,
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export const int256: Codec<Numberish, bigint> = {
|
|
132
|
-
encode(sink: Sink, val: Numberish) {
|
|
133
|
-
sink.i256(BigInt(val))
|
|
134
|
-
},
|
|
135
|
-
decode(src: Src): bigint {
|
|
136
|
-
return src.i256()
|
|
137
|
-
},
|
|
138
|
-
isDynamic: false,
|
|
139
|
-
}
|
|
10
|
+
type Numberish = number | bigint
|
|
11
|
+
type NumberType = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32'
|
|
12
|
+
type BigIntType = 'u64' | 'i64' | 'u128' | 'i128' | 'u256' | 'i256'
|
|
140
13
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return src.string()
|
|
149
|
-
},
|
|
150
|
-
isDynamic: true,
|
|
14
|
+
function numberCodec(method: NumberType): Codec<Numberish, number> {
|
|
15
|
+
return {
|
|
16
|
+
encode: (sink, val) => sink[method](safeToNumber(val)),
|
|
17
|
+
decode: (src) => src[method](),
|
|
18
|
+
isDynamic: false,
|
|
19
|
+
baseType: 'int',
|
|
20
|
+
}
|
|
151
21
|
}
|
|
152
22
|
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return decodeHex(val)
|
|
23
|
+
function bigintCodec(method: BigIntType): Codec<Numberish, bigint> {
|
|
24
|
+
return {
|
|
25
|
+
encode: (sink, val) => sink[method](BigInt(val)),
|
|
26
|
+
decode: (src) => src[method](),
|
|
27
|
+
isDynamic: false,
|
|
28
|
+
baseType: 'int',
|
|
29
|
+
}
|
|
161
30
|
}
|
|
162
31
|
|
|
163
|
-
export const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
32
|
+
export const bool: Codec<boolean> = {
|
|
33
|
+
encode(sink: Sink, val: boolean) {
|
|
34
|
+
sink.bool(val)
|
|
35
|
+
},
|
|
36
|
+
decode(src: Src): boolean {
|
|
37
|
+
return src.bool()
|
|
38
|
+
},
|
|
39
|
+
isDynamic: false,
|
|
40
|
+
baseType: 'bool',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const uint8 = numberCodec('u8')
|
|
44
|
+
export const int8 = numberCodec('i8')
|
|
45
|
+
export const uint16 = numberCodec('u16')
|
|
46
|
+
export const int16 = numberCodec('i16')
|
|
47
|
+
export const uint32 = numberCodec('u32')
|
|
48
|
+
export const int32 = numberCodec('i32')
|
|
49
|
+
export const uint64 = bigintCodec('u64')
|
|
50
|
+
export const int64 = bigintCodec('i64')
|
|
51
|
+
export const uint128 = bigintCodec('u128')
|
|
52
|
+
export const int128 = bigintCodec('i128')
|
|
53
|
+
export const uint256 = bigintCodec('u256')
|
|
54
|
+
export const int256 = bigintCodec('i256')
|
|
55
|
+
|
|
56
|
+
export const string: Codec<string> = {
|
|
57
|
+
encode(sink: Sink, val: string) {
|
|
58
|
+
sink.openTail()
|
|
59
|
+
sink.string(val)
|
|
60
|
+
sink.closeTail()
|
|
61
|
+
},
|
|
62
|
+
decode(src: Src): string {
|
|
63
|
+
return src.string()
|
|
64
|
+
},
|
|
65
|
+
isDynamic: true,
|
|
66
|
+
baseType: 'string',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const bytes: Codec<Uint8Array | string, string> = {
|
|
70
|
+
encode(sink: Sink, val: Uint8Array | string) {
|
|
71
|
+
sink.openTail()
|
|
72
|
+
sink.bytes(val)
|
|
73
|
+
sink.closeTail()
|
|
74
|
+
},
|
|
75
|
+
decode(src: Src): string {
|
|
76
|
+
return src.bytesHex()
|
|
77
|
+
},
|
|
78
|
+
isDynamic: true,
|
|
79
|
+
baseType: 'bytes',
|
|
173
80
|
}
|
|
174
81
|
|
|
175
82
|
const bytesN = (size: number): Codec<Uint8Array | string, string> => ({
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
83
|
+
encode(sink: Sink, val: Uint8Array | string) {
|
|
84
|
+
sink.staticBytes(size, val)
|
|
85
|
+
},
|
|
86
|
+
decode(src: Src): string {
|
|
87
|
+
return src.staticBytesHex(size)
|
|
88
|
+
},
|
|
89
|
+
isDynamic: false,
|
|
90
|
+
baseType: 'bytes',
|
|
183
91
|
})
|
|
184
92
|
|
|
185
93
|
export const bytes0 = bytesN(0)
|
|
@@ -217,27 +125,16 @@ export const bytes31 = bytesN(31)
|
|
|
217
125
|
export const bytes32 = bytesN(32)
|
|
218
126
|
|
|
219
127
|
export const address: Codec<string> = {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
export const fixedSizeArray = <TIn, TOut>(item: Codec<TIn, TOut>, size: number): Codec<TIn[], TOut[]> => new FixedSizeArrayCodec(item, size)
|
|
230
|
-
|
|
231
|
-
export const array = <TIn, TOut>(item: Codec<TIn, TOut>): Codec<TIn[], TOut[]> => new ArrayCodec(item)
|
|
232
|
-
|
|
233
|
-
type Struct = {
|
|
234
|
-
[key: string]: Codec<any>
|
|
128
|
+
encode(sink: Sink, val: string) {
|
|
129
|
+
sink.address(val)
|
|
130
|
+
},
|
|
131
|
+
decode(src: Src): string {
|
|
132
|
+
return src.address()
|
|
133
|
+
},
|
|
134
|
+
isDynamic: false,
|
|
135
|
+
baseType: 'address',
|
|
235
136
|
}
|
|
236
137
|
|
|
237
|
-
export const struct = <const T extends Struct>(components: T) => new StructCodec<T>(components)
|
|
238
|
-
|
|
239
|
-
export const tuple = struct
|
|
240
|
-
|
|
241
138
|
export const uint24 = uint32
|
|
242
139
|
export const int24 = int32
|
|
243
140
|
export const uint40 = uint64
|
|
@@ -290,4 +187,3 @@ export const uint240 = uint256
|
|
|
290
187
|
export const int240 = int256
|
|
291
188
|
export const uint248 = uint256
|
|
292
189
|
export const int248 = int256
|
|
293
|
-
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {address, array, bytes4, int8, BytesSink, BytesSrc, struct, uint256} from '..'
|
|
3
|
+
|
|
4
|
+
function roundtrip<T>(codec: {encode: (s: BytesSink, v: T) => void; decode: (s: BytesSrc) => T; slotsCount?: number}, value: T) {
|
|
5
|
+
const sink = new BytesSink(codec.slotsCount ?? 1)
|
|
6
|
+
codec.encode(sink, value)
|
|
7
|
+
expect(codec.decode(new BytesSrc(sink.result()))).toStrictEqual(value)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('StructCodec', () => {
|
|
11
|
+
it('static tuple', () => {
|
|
12
|
+
roundtrip(
|
|
13
|
+
struct({
|
|
14
|
+
a: int8,
|
|
15
|
+
b: uint256,
|
|
16
|
+
c: struct({e: address}),
|
|
17
|
+
}),
|
|
18
|
+
{
|
|
19
|
+
a: 1,
|
|
20
|
+
b: 2n,
|
|
21
|
+
c: {e: '0x1234567890123456789012345678901234567890'},
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('dynamic tuple', () => {
|
|
27
|
+
roundtrip(
|
|
28
|
+
struct({
|
|
29
|
+
a: array(uint256),
|
|
30
|
+
b: uint256,
|
|
31
|
+
c: struct({d: array(uint256), e: address}),
|
|
32
|
+
}),
|
|
33
|
+
{
|
|
34
|
+
a: [100n, 1n, 123n],
|
|
35
|
+
b: 2n,
|
|
36
|
+
c: {
|
|
37
|
+
d: [3n, 4n],
|
|
38
|
+
e: '0x1234567890123456789012345678901234567890',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('dynamic tuple with bytes4', () => {
|
|
45
|
+
const s = struct({
|
|
46
|
+
foo: uint256,
|
|
47
|
+
bar: array(uint256),
|
|
48
|
+
str: struct({foo: uint256, bar: bytes4}),
|
|
49
|
+
})
|
|
50
|
+
const value = {
|
|
51
|
+
foo: 100n,
|
|
52
|
+
bar: [1n, 2n, 3n],
|
|
53
|
+
str: {
|
|
54
|
+
foo: 123n,
|
|
55
|
+
bar: '0x12345678',
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
const sink = new BytesSink(1)
|
|
59
|
+
// `bytes4` accepts either `Uint8Array` or the `0x…`-prefixed hex form
|
|
60
|
+
// on the encode side but always returns the hex form on decode — check
|
|
61
|
+
// both input paths explicitly.
|
|
62
|
+
s.encode(sink, {
|
|
63
|
+
...value,
|
|
64
|
+
str: {...value.str, bar: Uint8Array.from([0x12, 0x34, 0x56, 0x78])},
|
|
65
|
+
})
|
|
66
|
+
expect(s.decode(new BytesSrc(sink.result()))).toStrictEqual(value)
|
|
67
|
+
roundtrip(s, value)
|
|
68
|
+
})
|
|
69
|
+
})
|
package/src/codecs/struct.ts
CHANGED
|
@@ -1,74 +1,94 @@
|
|
|
1
|
-
import {Codec,
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import type {Codec, Sink, Src, Struct} from '../codec'
|
|
2
|
+
import {type Pretty, propAccess, propName} from '../util'
|
|
3
|
+
|
|
4
|
+
export type DecodedStruct<T extends Struct> = Pretty<{
|
|
5
|
+
[K in keyof T]: T[K] extends Codec<any, infer U> ? U : never
|
|
6
|
+
}>
|
|
7
|
+
|
|
8
|
+
export type EncodedStruct<T extends Struct> = Pretty<{
|
|
9
|
+
[K in keyof T]: T[K] extends Codec<infer U, any> ? U : never
|
|
10
|
+
}>
|
|
4
11
|
|
|
5
12
|
function slotsCount(codecs: readonly Codec<any>[]) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
let count = 0
|
|
14
|
+
for (const codec of codecs) {
|
|
15
|
+
count += codec.slotsCount ?? 1
|
|
16
|
+
}
|
|
17
|
+
return count
|
|
11
18
|
}
|
|
12
19
|
|
|
13
20
|
export class StructCodec<const T extends Struct> implements Codec<EncodedStruct<T>, DecodedStruct<T>> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
public readonly baseType = 'struct'
|
|
22
|
+
public readonly isDynamic: boolean
|
|
23
|
+
public readonly slotsCount: number
|
|
24
|
+
public readonly childrenSlotsCount: number
|
|
25
|
+
public readonly components: T
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const codecs = Object.values(components)
|
|
22
|
-
this.isDynamic = codecs.some((codec) => codec.isDynamic)
|
|
23
|
-
this.childrenSlotsCount = slotsCount(codecs)
|
|
24
|
-
if (this.isDynamic) {
|
|
25
|
-
this.slotsCount = 1
|
|
26
|
-
} else {
|
|
27
|
-
this.slotsCount = this.childrenSlotsCount
|
|
28
|
-
}
|
|
29
|
-
}
|
|
27
|
+
readonly encodeInline: (sink: Sink, val: EncodedStruct<T>) => void
|
|
28
|
+
readonly decodeInline: (src: Src) => DecodedStruct<T>
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.encodeDynamic(sink, val)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
for (let i in this.components) {
|
|
37
|
-
let prop = this.components[i]
|
|
38
|
-
prop.encode(sink, val[i])
|
|
39
|
-
}
|
|
40
|
-
}
|
|
30
|
+
readonly encode: (sink: Sink, val: EncodedStruct<T>) => void
|
|
31
|
+
readonly decode: (src: Src) => DecodedStruct<T>
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
33
|
+
constructor(components: T) {
|
|
34
|
+
this.components = components
|
|
35
|
+
const entries = Object.entries(components) as Array<[string, Codec<any>]>
|
|
36
|
+
const codecs = entries.map(([, c]) => c)
|
|
37
|
+
this.isDynamic = codecs.some((codec) => codec.isDynamic)
|
|
38
|
+
this.childrenSlotsCount = slotsCount(codecs)
|
|
39
|
+
this.slotsCount = this.isDynamic ? 1 : this.childrenSlotsCount
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
this.encodeInline = this.createEncode(entries, false)
|
|
42
|
+
this.decodeInline = this.createDecode(entries, false)
|
|
43
|
+
this.encode = this.isDynamic ? this.createEncode(entries, true) : this.encodeInline
|
|
44
|
+
this.decode = this.isDynamic ? this.createDecode(entries, true) : this.decodeInline
|
|
54
45
|
}
|
|
55
|
-
let result: any = {}
|
|
56
|
-
for (let i in this.components) {
|
|
57
|
-
let prop = this.components[i]
|
|
58
|
-
result[i] = prop.decode(src)
|
|
59
|
-
}
|
|
60
|
-
return result
|
|
61
|
-
}
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
// JIT-generates a straight-line encode function. Each child's encode is bound once
|
|
48
|
+
// at construction time to avoid property lookups on the hot path. When wrap=true
|
|
49
|
+
// and the struct is dynamic, openTail/closeTail bracket the field writes so the
|
|
50
|
+
// function can be used as a nested ABI field.
|
|
51
|
+
private createEncode(
|
|
52
|
+
entries: Array<[string, Codec<any>]>,
|
|
53
|
+
wrap: boolean,
|
|
54
|
+
): (sink: Sink, val: EncodedStruct<T>) => void {
|
|
55
|
+
const closureNames: string[] = []
|
|
56
|
+
const closureValues: any[] = []
|
|
57
|
+
let body = ''
|
|
58
|
+
if (wrap && this.isDynamic) {
|
|
59
|
+
body += `sink.openTail(${this.childrenSlotsCount});`
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < entries.length; i++) {
|
|
62
|
+
const [key, child] = entries[i]
|
|
63
|
+
const name = `__e${i}`
|
|
64
|
+
closureNames.push(name)
|
|
65
|
+
closureValues.push(child.encode.bind(child))
|
|
66
|
+
body += `${name}(sink, val${propAccess(key)});`
|
|
67
|
+
}
|
|
68
|
+
if (wrap && this.isDynamic) {
|
|
69
|
+
body += 'sink.closeTail();'
|
|
70
|
+
}
|
|
71
|
+
const fn = new Function(...closureNames, 'sink', 'val', body)
|
|
72
|
+
return fn.bind(null, ...closureValues)
|
|
73
|
+
}
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
private createDecode(entries: Array<[string, Codec<any>]>, wrap: boolean): (src: Src) => DecodedStruct<T> {
|
|
76
|
+
const closureNames: string[] = []
|
|
77
|
+
const closureValues: any[] = []
|
|
78
|
+
let body = ''
|
|
79
|
+
if (wrap && this.isDynamic) {
|
|
80
|
+
body += 'src = src.slice(src.u32());'
|
|
81
|
+
}
|
|
82
|
+
const fields: string[] = []
|
|
83
|
+
for (let i = 0; i < entries.length; i++) {
|
|
84
|
+
const [key, child] = entries[i]
|
|
85
|
+
const name = `__d${i}`
|
|
86
|
+
closureNames.push(name)
|
|
87
|
+
closureValues.push(child.decode.bind(child))
|
|
88
|
+
fields.push(`${propName(key)}:${name}(src)`)
|
|
89
|
+
}
|
|
90
|
+
body += `return {${fields.join(',')}};`
|
|
91
|
+
const fn = new Function(...closureNames, 'src', body)
|
|
92
|
+
return fn.bind(null, ...closureValues)
|
|
71
93
|
}
|
|
72
|
-
return result
|
|
73
|
-
}
|
|
74
94
|
}
|
package/src/dsl.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {Codec, Struct} from './codec'
|
|
2
|
+
import {ArrayCodec, FixedSizeArrayCodec} from './codecs/array'
|
|
3
|
+
import {StructCodec} from './codecs/struct'
|
|
4
|
+
|
|
5
|
+
export * from './codecs/primitives'
|
|
6
|
+
export {ArrayCodec, FixedSizeArrayCodec} from './codecs/array'
|
|
7
|
+
export {StructCodec, type DecodedStruct, type EncodedStruct} from './codecs/struct'
|
|
8
|
+
|
|
9
|
+
export const fixedSizeArray = <TIn, TOut>(item: Codec<TIn, TOut>, size: number): FixedSizeArrayCodec<TIn, TOut> =>
|
|
10
|
+
new FixedSizeArrayCodec(item, size)
|
|
11
|
+
|
|
12
|
+
export const array = <TIn, TOut>(item: Codec<TIn, TOut>): ArrayCodec<TIn, TOut> => new ArrayCodec(item)
|
|
13
|
+
|
|
14
|
+
export const struct = <const T extends Struct>(components: T): StructCodec<T> => new StructCodec<T>(components)
|
|
15
|
+
|
|
16
|
+
export const tuple = struct
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export * from './
|
|
1
|
+
export * from './codec'
|
|
2
|
+
export * from './dsl'
|
|
3
|
+
export * from './sink/bytes'
|
|
4
|
+
export * from './sink/hex'
|
|
5
|
+
export * from './src/bytes'
|
|
6
|
+
export * from './src/hex'
|
|
7
|
+
export * from './util'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const TEXT_ENCODER = new TextEncoder()
|
|
2
|
+
|
|
3
|
+
export const U256_BASE = 1n << 256n
|
|
4
|
+
|
|
5
|
+
export const U8_MAX = 0xff
|
|
6
|
+
export const U16_MAX = 0xffff
|
|
7
|
+
export const U32_MAX = 0xffffffff
|
|
8
|
+
|
|
9
|
+
export const I8_MIN = -0x80
|
|
10
|
+
export const I8_MAX = 0x7f
|
|
11
|
+
export const I16_MIN = -0x8000
|
|
12
|
+
export const I16_MAX = 0x7fff
|
|
13
|
+
export const I32_MIN = -0x80000000
|
|
14
|
+
export const I32_MAX = 0x7fffffff
|
|
15
|
+
|
|
16
|
+
export const U64_MAX_BI = 0xffffffffffffffffn
|
|
17
|
+
export const I64_MIN_BI = -0x8000000000000000n
|
|
18
|
+
export const I64_MAX_BI = 0x7ffffffffffffffen
|
|
19
|
+
|
|
20
|
+
export const U128_MAX_BI = 0xffffffffffffffffffffffffffffffffn
|
|
21
|
+
export const I128_MIN_BI = -0x80000000000000000000000000000000n
|
|
22
|
+
export const I128_MAX_BI = 0x7fffffffffffffffffffffffffffffffn
|
|
23
|
+
|
|
24
|
+
export const U256_MAX_BI = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
|
|
25
|
+
export const I256_MIN_BI = -0x8000000000000000000000000000000000000000000000000000000000000000n
|
|
26
|
+
export const I256_MAX_BI = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
import {BytesSink, BytesSrc} from '..'
|
|
3
|
+
|
|
4
|
+
describe('sink', () => {
|
|
5
|
+
it('negative numbers round-trip through Src', () => {
|
|
6
|
+
const sink = new BytesSink(6)
|
|
7
|
+
sink.i8(-1)
|
|
8
|
+
sink.i16(-123)
|
|
9
|
+
sink.i32(-123456)
|
|
10
|
+
sink.i64(-1234567890n)
|
|
11
|
+
sink.i128(-12345678901234567890n)
|
|
12
|
+
sink.i256(-1234567890123456789012345678901234567890n)
|
|
13
|
+
|
|
14
|
+
const src = new BytesSrc(sink.result())
|
|
15
|
+
expect(src.i8()).toBe(-1)
|
|
16
|
+
expect(src.i16()).toBe(-123)
|
|
17
|
+
expect(src.i32()).toBe(-123456)
|
|
18
|
+
expect(src.i64()).toBe(-1234567890n)
|
|
19
|
+
expect(src.i128()).toBe(-12345678901234567890n)
|
|
20
|
+
expect(src.i256()).toBe(-1234567890123456789012345678901234567890n)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('number overflow', () => {
|
|
24
|
+
const sink = new BytesSink(1)
|
|
25
|
+
expect(() => sink.u8(0x1234567890)).toThrowError('78187493520 is out of bounds for uint8[0, 255]')
|
|
26
|
+
expect(() => sink.i8(0x1234567890)).toThrowError('78187493520 is out of bounds for int8[-128, 127]')
|
|
27
|
+
expect(() => sink.u16(0x1234567890)).toThrowError('78187493520 is out of bounds for uint16[0, 65535]')
|
|
28
|
+
expect(() => sink.i16(0x1234567890)).toThrowError('78187493520 is out of bounds for int16[-32768, 32767]')
|
|
29
|
+
expect(() => sink.u32(0x1234567890)).toThrowError('78187493520 is out of bounds for uint32[0, 4294967295]')
|
|
30
|
+
expect(() => sink.i32(0x1234567890)).toThrowError('78187493520 is out of bounds for int32[-2147483648, 2147483647]')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('mixed static types round-trip through Src', () => {
|
|
34
|
+
const sink = new BytesSink(5)
|
|
35
|
+
sink.u8(1)
|
|
36
|
+
sink.i8(-2)
|
|
37
|
+
sink.address('0x1234567890123456789012345678901234567890')
|
|
38
|
+
sink.u256(3n)
|
|
39
|
+
sink.staticBytes(7, Buffer.from('1234567890abcd', 'hex'))
|
|
40
|
+
|
|
41
|
+
const src = new BytesSrc(sink.result())
|
|
42
|
+
expect(src.u8()).toBe(1)
|
|
43
|
+
expect(src.i8()).toBe(-2)
|
|
44
|
+
expect(src.address()).toBe('0x1234567890123456789012345678901234567890')
|
|
45
|
+
expect(src.u256()).toBe(3n)
|
|
46
|
+
expect(src.staticBytes(7)).toStrictEqual(new Uint8Array(Buffer.from('1234567890abcd', 'hex')))
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('string', () => {
|
|
50
|
+
function testString(value: string) {
|
|
51
|
+
const sink = new BytesSink(1)
|
|
52
|
+
sink.openTail()
|
|
53
|
+
sink.string(value)
|
|
54
|
+
sink.closeTail()
|
|
55
|
+
expect(new BytesSrc(sink.result()).string()).toBe(value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
it('short string', () => testString('hello'))
|
|
59
|
+
it('32 byte string', () => testString('this string length is 32 bytes!!'))
|
|
60
|
+
it('longer string', () => testString('this string length is 33 bytes!!!'))
|
|
61
|
+
it('UTF', () => testString('привет 👍'))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('bytes', () => {
|
|
65
|
+
const sink = new BytesSink(1)
|
|
66
|
+
sink.openTail()
|
|
67
|
+
const buffer = Buffer.alloc(150)
|
|
68
|
+
buffer.fill('xd')
|
|
69
|
+
sink.bytes(buffer)
|
|
70
|
+
sink.closeTail()
|
|
71
|
+
expect(new BytesSrc(sink.result()).bytes()).toStrictEqual(new Uint8Array(buffer))
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('string encodes to canonical 32-byte-aligned ABI layout', () => {
|
|
75
|
+
// Pin the exact on-wire layout for one value so we catch regressions
|
|
76
|
+
// in the offset/length word without needing an external reference
|
|
77
|
+
// implementation on the hot path.
|
|
78
|
+
const sink = new BytesSink(1)
|
|
79
|
+
sink.openTail()
|
|
80
|
+
sink.string('hello')
|
|
81
|
+
sink.closeTail()
|
|
82
|
+
expect(sink.toString()).toBe(
|
|
83
|
+
'0x' +
|
|
84
|
+
// offset to the dynamic region (0x20)
|
|
85
|
+
'0000000000000000000000000000000000000000000000000000000000000020' +
|
|
86
|
+
// length (5)
|
|
87
|
+
'0000000000000000000000000000000000000000000000000000000000000005' +
|
|
88
|
+
// "hello" left-aligned, zero-padded to 32 bytes
|
|
89
|
+
'68656c6c6f000000000000000000000000000000000000000000000000000000',
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
})
|