@wormhole-foundation/sdk-base 0.1.0-beta.3
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/__tests__/finality.ts +8 -0
- package/__tests__/layout.ts +173 -0
- package/package.json +15 -0
- package/src/constants/chains.ts +104 -0
- package/src/constants/circle.ts +119 -0
- package/src/constants/contracts/circle.ts +99 -0
- package/src/constants/contracts/core.ts +125 -0
- package/src/constants/contracts/index.ts +15 -0
- package/src/constants/contracts/nftBridge.ts +68 -0
- package/src/constants/contracts/relayer.ts +40 -0
- package/src/constants/contracts/tokenBridge.ts +117 -0
- package/src/constants/decimals.ts +18 -0
- package/src/constants/explorer.ts +326 -0
- package/src/constants/finality.ts +46 -0
- package/src/constants/index.ts +12 -0
- package/src/constants/networks.ts +9 -0
- package/src/constants/platforms.ts +128 -0
- package/src/constants/protocols.ts +36 -0
- package/src/constants/rpc.ts +48 -0
- package/src/index.ts +2 -0
- package/src/utils/array.ts +102 -0
- package/src/utils/hexstring.ts +62 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/layout/deserialize.ts +177 -0
- package/src/utils/layout/discriminate.ts +464 -0
- package/src/utils/layout/fixedDynamic.ts +122 -0
- package/src/utils/layout/index.ts +26 -0
- package/src/utils/layout/layout.ts +140 -0
- package/src/utils/layout/serialize.ts +191 -0
- package/src/utils/layout/utils.ts +23 -0
- package/src/utils/mapping.ts +426 -0
- package/src/utils/metaprogramming.ts +60 -0
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.esm.json +8 -0
- package/typedoc.json +4 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Layout,
|
|
3
|
+
LayoutItem,
|
|
4
|
+
LengthPrefixedBytesLayoutItem,
|
|
5
|
+
isPrimitiveType,
|
|
6
|
+
} from "./layout";
|
|
7
|
+
|
|
8
|
+
import { serializeUint } from "./serialize";
|
|
9
|
+
|
|
10
|
+
//using a Bounds type (even though currently the upper bound can only either be equal to the lower
|
|
11
|
+
// bound or Infinity) in anticipation of a future switch layout item that might contain multiple
|
|
12
|
+
// sublayouts which, unlike arrays currently, could all be bounded but potentially with
|
|
13
|
+
// different sizes
|
|
14
|
+
type Bounds = readonly [number, number];
|
|
15
|
+
type FixedBytes = (readonly [number, Uint8Array])[];
|
|
16
|
+
|
|
17
|
+
function layoutItemMeta(
|
|
18
|
+
item: LayoutItem,
|
|
19
|
+
offset: number,
|
|
20
|
+
fixedBytes: FixedBytes,
|
|
21
|
+
): Bounds {
|
|
22
|
+
function knownFixed(size: number, serialized: Uint8Array): Bounds {
|
|
23
|
+
if (Number.isFinite(offset))
|
|
24
|
+
fixedBytes.push([offset, serialized]);
|
|
25
|
+
|
|
26
|
+
return [size, size];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch (item.binary) {
|
|
30
|
+
case "object": {
|
|
31
|
+
return createLayoutMeta(item.layout, offset, fixedBytes);
|
|
32
|
+
}
|
|
33
|
+
case "array": {
|
|
34
|
+
return [item.lengthSize !== undefined ? item.lengthSize : 0, Infinity];
|
|
35
|
+
}
|
|
36
|
+
case "bytes": {
|
|
37
|
+
if ("size" in item && item.size !== undefined)
|
|
38
|
+
return [item.size, item.size];
|
|
39
|
+
|
|
40
|
+
if (item?.custom instanceof Uint8Array)
|
|
41
|
+
return knownFixed(item.custom.length, item.custom);
|
|
42
|
+
|
|
43
|
+
if (item?.custom?.from instanceof Uint8Array)
|
|
44
|
+
return knownFixed(item.custom.from.length, item.custom.from);
|
|
45
|
+
|
|
46
|
+
//TODO typescript should be able to infer that at this point the only possible remaining
|
|
47
|
+
// type for item is LengthPrefixedBytesLayoutItem, but for some reason it doesn't
|
|
48
|
+
item = item as LengthPrefixedBytesLayoutItem;
|
|
49
|
+
return [item.lengthSize !== undefined ? item.lengthSize : 0, Infinity];
|
|
50
|
+
}
|
|
51
|
+
case "uint": {
|
|
52
|
+
if (isPrimitiveType(item.custom)) {
|
|
53
|
+
const serialized = new Uint8Array(item.size);
|
|
54
|
+
serializeUint(serialized, 0, item.custom, item.size)
|
|
55
|
+
return knownFixed(item.size, serialized);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [item.size, item.size];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createLayoutMeta(
|
|
64
|
+
layout: Layout,
|
|
65
|
+
offset: number,
|
|
66
|
+
fixedBytes: FixedBytes
|
|
67
|
+
): Bounds {
|
|
68
|
+
let bounds = [0, 0] as Bounds;
|
|
69
|
+
for (const item of layout) {
|
|
70
|
+
bounds = layoutItemMeta(item, offset, fixedBytes)
|
|
71
|
+
//we need the cast because of course mapping tuples to tuples is an unsolved problem in TS:
|
|
72
|
+
//https://stackoverflow.com/questions/57913193/how-to-use-array-map-with-tuples-in-typescript#answer-57913509
|
|
73
|
+
.map((b, i) => bounds[i] + b) as unknown as Bounds;
|
|
74
|
+
offset = bounds[0] === bounds[1] ? bounds[0] : Infinity;
|
|
75
|
+
}
|
|
76
|
+
return bounds;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function calcAscendingBoundsAndPower(sizeBounds: readonly Bounds[]) {
|
|
80
|
+
let maxOverlap = 0;
|
|
81
|
+
const ascendingBounds = new Map<number, number[]>();
|
|
82
|
+
//sortedCandidates tracks all layouts that have a size bound that contains the size that's
|
|
83
|
+
// currently under consideration, sorted in ascending order of their respective upper bounds
|
|
84
|
+
let sortedCandidates = [] as (readonly [number, number])[];
|
|
85
|
+
const closeCandidatesBefore = (before: number) => {
|
|
86
|
+
while (sortedCandidates.length > 0 && sortedCandidates[0][0] < before) {
|
|
87
|
+
const end = sortedCandidates[0][0] + 1;
|
|
88
|
+
//remove all candidates that end at the same position
|
|
89
|
+
const removeIndex = sortedCandidates.findIndex(([upper]) => end <= upper);
|
|
90
|
+
if (removeIndex === -1)
|
|
91
|
+
sortedCandidates = [];
|
|
92
|
+
else
|
|
93
|
+
sortedCandidates.splice(0, removeIndex);
|
|
94
|
+
//introduce a new bound that captures all candidates that can have a size of at least `end`
|
|
95
|
+
ascendingBounds.set(end, sortedCandidates.map(([, j]) => j));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
sizeBounds
|
|
100
|
+
.map((b, i) => [b, i] as const)
|
|
101
|
+
.sort(([[lowerLhs]], [[lowerRhs]]) => lowerLhs - lowerRhs)
|
|
102
|
+
.forEach(([[lower, upper], i]) => {
|
|
103
|
+
closeCandidatesBefore(lower);
|
|
104
|
+
const insertIndex = sortedCandidates.findIndex(([u]) => u <= upper);
|
|
105
|
+
if (insertIndex === -1)
|
|
106
|
+
sortedCandidates.push([upper, i]);
|
|
107
|
+
else
|
|
108
|
+
sortedCandidates.splice(insertIndex, 0, [upper, i]);
|
|
109
|
+
|
|
110
|
+
maxOverlap = Math.max(maxOverlap, sortedCandidates.length);
|
|
111
|
+
|
|
112
|
+
ascendingBounds.set(lower, sortedCandidates.map(([, j]) => j));
|
|
113
|
+
});
|
|
114
|
+
closeCandidatesBefore(Infinity);
|
|
115
|
+
|
|
116
|
+
return [ascendingBounds, sizeBounds.length - maxOverlap] as const;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function setMinus(sortedLhs: readonly number[], sortedRhs: readonly number[]) {
|
|
120
|
+
const ret = [] as number[];
|
|
121
|
+
let i = 0;
|
|
122
|
+
for (let j = 0; i < sortedLhs.length && j < sortedRhs.length;) {
|
|
123
|
+
if (sortedLhs[i] < sortedRhs[j])
|
|
124
|
+
ret.push(sortedLhs[i++]);
|
|
125
|
+
else if (sortedLhs[i] > sortedRhs[j])
|
|
126
|
+
++j;
|
|
127
|
+
else {
|
|
128
|
+
++i;
|
|
129
|
+
++j;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (; i < sortedLhs.length; ++i)
|
|
133
|
+
ret.push(sortedLhs[i]);
|
|
134
|
+
|
|
135
|
+
return ret;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function setIntersection(sortedLhs: readonly number[], sortedRhs: readonly number[]) {
|
|
139
|
+
let ret = [] as number[];
|
|
140
|
+
for (let i = 0, j = 0; i < sortedLhs.length && j < sortedRhs.length;) {
|
|
141
|
+
if (sortedLhs[i] < sortedRhs[j])
|
|
142
|
+
++i;
|
|
143
|
+
else if (sortedLhs[i] > sortedRhs[j])
|
|
144
|
+
++j;
|
|
145
|
+
else {
|
|
146
|
+
ret.push(sortedLhs[i]);
|
|
147
|
+
++i;
|
|
148
|
+
++j;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return ret;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function setUnion(sortedLhs: readonly number[], sortedRhs: readonly number[]) {
|
|
155
|
+
let ret = [] as number[];
|
|
156
|
+
let i = 0, j = 0;
|
|
157
|
+
while (i < sortedLhs.length && j < sortedRhs.length) {
|
|
158
|
+
if (sortedLhs[i] < sortedRhs[j])
|
|
159
|
+
ret.push(sortedLhs[i++]);
|
|
160
|
+
else if (sortedLhs[i] > sortedRhs[j])
|
|
161
|
+
ret.push(sortedRhs[j++]);
|
|
162
|
+
else {
|
|
163
|
+
ret.push(sortedLhs[i]);
|
|
164
|
+
++i;
|
|
165
|
+
++j;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (; i < sortedLhs.length; ++i)
|
|
169
|
+
ret.push(sortedLhs[i]);
|
|
170
|
+
for (; j < sortedRhs.length; ++j)
|
|
171
|
+
ret.push(sortedRhs[j]);
|
|
172
|
+
return ret;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
function isSubset(sortedLhs: readonly number[], sortedRhs: readonly number[]) {
|
|
176
|
+
for (let i = 0, j = 0; i < sortedLhs.length && j < sortedRhs.length;) {
|
|
177
|
+
if (sortedLhs[i] < sortedRhs[j])
|
|
178
|
+
return false;
|
|
179
|
+
else if (sortedLhs[i] > sortedRhs[j])
|
|
180
|
+
++j;
|
|
181
|
+
else {
|
|
182
|
+
++i;
|
|
183
|
+
++j;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
//Generates a greedy divide-and-conquer strategy to determine the layout (or set of layouts) that
|
|
190
|
+
// a given serialized byte array might conform to.
|
|
191
|
+
//It leverages size bounds and known fixed bytes of layouts to quickly eliminate candidates, by
|
|
192
|
+
// (greedily) choosing the discriminator (byte or size) that eliminates the most candidates at
|
|
193
|
+
// each step.
|
|
194
|
+
//Power is a relative measure of the strength of a discriminator given a set of layout candidates.
|
|
195
|
+
// It's in [0, candidate.length - 1] and states how many layouts of that set can _at least_ be
|
|
196
|
+
// eliminated when applying that discriminator.
|
|
197
|
+
//Layout sizes are only tracked in terms of lower and upper bounds. This means that while a layout
|
|
198
|
+
// like an array of e.g. 2 byte uints can actually never have an odd size, the algorithm will
|
|
199
|
+
// simply treat it as having a size bound of [0, Infinity]. This means that the algorithm is
|
|
200
|
+
// "lossy" in the sense that it does not use all the information that it actually has available
|
|
201
|
+
// and will e.g. wrongly conclude that the aforementioned layout cannot be distinguished from a
|
|
202
|
+
// second layout that starts off with a one byte uint followed by an array of 2 byte uints (and
|
|
203
|
+
// would thus always have odd size). I.e. it would wrongly conclude that the power of the size
|
|
204
|
+
// discriminator is 0 when it should be 1.
|
|
205
|
+
//The alternative to accepting this limitation is tracking all possible combinations of offsets,
|
|
206
|
+
// multiples, and their arbitrary composition which would be massively more complicated and
|
|
207
|
+
// also pointless in the general case because we'd have to figure out whether a given size can be
|
|
208
|
+
// expressed as some combination of offsets and array size multiples in which case it's almost
|
|
209
|
+
// certainly computaionally cheaper to simply attempt to deserialize the given given data for the
|
|
210
|
+
// respective layout.
|
|
211
|
+
function generateLayoutDiscriminator<const LA extends readonly Layout[]>(
|
|
212
|
+
layouts: LA
|
|
213
|
+
): [boolean, (encoded: Uint8Array) => readonly number[]] {
|
|
214
|
+
type Uint = number;
|
|
215
|
+
type Size = Uint;
|
|
216
|
+
type BytePos = Uint;
|
|
217
|
+
type ByteVal = Uint; //actually a uint8
|
|
218
|
+
type LayoutIndex = Uint;
|
|
219
|
+
type Candidates = LayoutIndex[];
|
|
220
|
+
type FixedKnownByte = (readonly [ByteVal, LayoutIndex])[];
|
|
221
|
+
|
|
222
|
+
const fixedKnown = layouts.map(_ => [] as FixedBytes);
|
|
223
|
+
const sizeBounds = layouts.map((l, i) => createLayoutMeta(l, 0, fixedKnown[i]));
|
|
224
|
+
|
|
225
|
+
const [ascendingBounds, sizePower] = calcAscendingBoundsAndPower(sizeBounds);
|
|
226
|
+
//we don't check sizePower here and bail early if it is perfect because we prefer perfect byte
|
|
227
|
+
// discriminators over perfect size discriminators due to their faster lookup times (hash map
|
|
228
|
+
// vs binary search (and actually currently it's even implement using linear search)) and
|
|
229
|
+
// more predictable / lower complexity branching behavior.
|
|
230
|
+
|
|
231
|
+
const layoutsWithSize = (size: Size) => {
|
|
232
|
+
for (const [lower, candidates] of ascendingBounds)
|
|
233
|
+
if (size >= lower)
|
|
234
|
+
return candidates;
|
|
235
|
+
|
|
236
|
+
return [];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const fixedKnownBytes: FixedKnownByte[] = Array(
|
|
240
|
+
Math.max(...fixedKnown.map(fkb => fkb[fkb.length][0] + fkb[fkb.length][1].length))
|
|
241
|
+
).fill([]);
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < fixedKnown.length; ++i)
|
|
244
|
+
for (const [offset, serialized] of fixedKnown[i])
|
|
245
|
+
for (let j = 0; j < serialized.length; ++j)
|
|
246
|
+
fixedKnownBytes[offset + j].push([serialized[j], i]);
|
|
247
|
+
|
|
248
|
+
let bestBytes = [];
|
|
249
|
+
for (const [bytePos, fixedKnownByte] of fixedKnownBytes.entries()) {
|
|
250
|
+
//the number of layouts with a given size is an upper bound on the discriminatory power of
|
|
251
|
+
// a byte at a given position: If the encoded data is too short we can automatically
|
|
252
|
+
// exclude all layouts whose minimum size is larger than it, nevermind those who expect
|
|
253
|
+
// a known, fixed value at this position.
|
|
254
|
+
let power = layoutsWithSize(bytePos).length;
|
|
255
|
+
const anyValueLayouts =
|
|
256
|
+
setMinus(layoutsWithSize(bytePos), fixedKnownByte.map(([, layoutIdx]) => layoutIdx));
|
|
257
|
+
const outOfBoundsLayouts = setMinus(layouts.map((_, i) => i), layoutsWithSize(bytePos));
|
|
258
|
+
const distinctValues = new Map<BytePos, Candidates>();
|
|
259
|
+
//the following equation holds (after applying .length to each component):
|
|
260
|
+
//layouts = outOfBoundsLayouts + arbitraryValLayouts + fixedKnownByte
|
|
261
|
+
for (const [byteVal, candidate] of fixedKnownByte) {
|
|
262
|
+
if (!distinctValues.has(byteVal))
|
|
263
|
+
distinctValues.set(byteVal, []);
|
|
264
|
+
|
|
265
|
+
distinctValues.get(byteVal)!.push(candidate);
|
|
266
|
+
}
|
|
267
|
+
for (const layoutsWithValue of distinctValues.values()) {
|
|
268
|
+
//if we find the byte value associated with the this set of layouts, we can eliminate
|
|
269
|
+
// all other layouts that don't have this value at this position and all layouts
|
|
270
|
+
// that are too short or too long to have a value in this position regardless
|
|
271
|
+
const curPower = fixedKnownByte.length - layoutsWithValue.length + outOfBoundsLayouts.length;
|
|
272
|
+
power = Math.min(power, curPower);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (power === 0)
|
|
276
|
+
continue;
|
|
277
|
+
|
|
278
|
+
if (power === layouts.length - 1) {
|
|
279
|
+
//we have a perfect byte discriminator -> bail early
|
|
280
|
+
return [true, (encoded: Uint8Array) => {
|
|
281
|
+
if (encoded.length <= bytePos)
|
|
282
|
+
return outOfBoundsLayouts.length === 1 ? outOfBoundsLayouts : [];
|
|
283
|
+
|
|
284
|
+
const layout = distinctValues.get(encoded[bytePos]);
|
|
285
|
+
if (layout === undefined)
|
|
286
|
+
return [];
|
|
287
|
+
|
|
288
|
+
return layout;
|
|
289
|
+
}];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
bestBytes.push([power, bytePos, outOfBoundsLayouts, distinctValues, anyValueLayouts] as const);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
//if we get here, we know we don't have a perfect byte discriminator so we now check wether we
|
|
296
|
+
// we have a perfect size discriminator and bail early if so
|
|
297
|
+
if (sizePower === layouts.length - 1)
|
|
298
|
+
return [true, (encoded: Uint8Array) => layoutsWithSize(encoded.length)];
|
|
299
|
+
|
|
300
|
+
//sort in descending order of power
|
|
301
|
+
bestBytes.sort(([lhsPower], [rhsPower]) => rhsPower - lhsPower);
|
|
302
|
+
type BestBytes = typeof bestBytes;
|
|
303
|
+
type Strategy = [BytePos, Candidates, Map<number, Candidates>] | "size" | "indistinguishable";
|
|
304
|
+
|
|
305
|
+
let distinguishable = true;
|
|
306
|
+
let firstStrategy: Strategy | undefined;
|
|
307
|
+
const strategies = new Map<Candidates, Strategy>();
|
|
308
|
+
const candidatesBySize = new Map<Size, Candidates[]>();
|
|
309
|
+
const addStrategy = (candidates: Candidates, strategy: Strategy) => {
|
|
310
|
+
if (firstStrategy === undefined) {
|
|
311
|
+
firstStrategy = strategy;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
strategies.set(candidates, strategy);
|
|
316
|
+
if (!candidatesBySize.has(candidates.length))
|
|
317
|
+
candidatesBySize.set(candidates.length, []);
|
|
318
|
+
candidatesBySize.get(candidates.length)!.push(candidates);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const recursivelyBuildStrategy = (
|
|
322
|
+
candidates: Candidates,
|
|
323
|
+
bestBytes: BestBytes,
|
|
324
|
+
) => {
|
|
325
|
+
if (candidates.length <= 1 || strategies.has(candidates))
|
|
326
|
+
return;
|
|
327
|
+
|
|
328
|
+
let sizePower = 0;
|
|
329
|
+
const narrowedBounds = new Map<Size, Candidates>();
|
|
330
|
+
for (const candidate of candidates) {
|
|
331
|
+
const lower = sizeBounds[candidate][0];
|
|
332
|
+
const overlap = setIntersection(ascendingBounds.get(lower)!, candidates);
|
|
333
|
+
narrowedBounds.set(lower, overlap)
|
|
334
|
+
sizePower = Math.max(sizePower, overlap.length);
|
|
335
|
+
}
|
|
336
|
+
sizePower = candidates.length - sizePower;
|
|
337
|
+
|
|
338
|
+
const narrowedBestBytes = [] as BestBytes;
|
|
339
|
+
for (const [power, bytePos, outOfBoundsLayouts, distinctValues, anyValueLayouts] of bestBytes) {
|
|
340
|
+
const narrowedDistinctValues = new Map<ByteVal, Candidates>();
|
|
341
|
+
let fixedKnownCount = 0;
|
|
342
|
+
for (const [byteVal, layoutsWithValue] of distinctValues) {
|
|
343
|
+
const lwv = setIntersection(layoutsWithValue, candidates);
|
|
344
|
+
if (lwv.length > 0) {
|
|
345
|
+
narrowedDistinctValues.set(byteVal, lwv);
|
|
346
|
+
fixedKnownCount += lwv.length;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const narrowedOutOfBoundsLayouts = setIntersection(outOfBoundsLayouts, candidates);
|
|
350
|
+
|
|
351
|
+
let narrowedPower = power;
|
|
352
|
+
for (const [, layoutsWithValue] of narrowedDistinctValues) {
|
|
353
|
+
const curPower =
|
|
354
|
+
fixedKnownCount - layoutsWithValue.length + narrowedOutOfBoundsLayouts.length;
|
|
355
|
+
narrowedPower = Math.min(narrowedPower, curPower);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (narrowedPower === 0)
|
|
359
|
+
continue;
|
|
360
|
+
|
|
361
|
+
if (narrowedPower === candidates.length - 1) {
|
|
362
|
+
addStrategy(candidates, [bytePos, narrowedOutOfBoundsLayouts, narrowedDistinctValues]);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
narrowedBestBytes.push([
|
|
367
|
+
narrowedPower,
|
|
368
|
+
bytePos,
|
|
369
|
+
narrowedOutOfBoundsLayouts,
|
|
370
|
+
narrowedDistinctValues,
|
|
371
|
+
setIntersection(anyValueLayouts, candidates)
|
|
372
|
+
] as const);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (sizePower === candidates.length - 1) {
|
|
376
|
+
addStrategy(candidates, "size");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
narrowedBestBytes.sort(([lhsPower], [rhsPower]) => rhsPower - lhsPower);
|
|
381
|
+
|
|
382
|
+
if (narrowedBestBytes.length > 0 && narrowedBestBytes[0][0] >= sizePower) {
|
|
383
|
+
const [, bytePos, narrowedOutOfBoundsLayouts, narrowedDistinctValues, anyValueLayouts] =
|
|
384
|
+
narrowedBestBytes[0];
|
|
385
|
+
addStrategy(candidates, [bytePos, narrowedOutOfBoundsLayouts, narrowedDistinctValues]);
|
|
386
|
+
recursivelyBuildStrategy(narrowedOutOfBoundsLayouts, narrowedBestBytes);
|
|
387
|
+
for (const cand of narrowedDistinctValues.values())
|
|
388
|
+
recursivelyBuildStrategy(setUnion(cand, anyValueLayouts), narrowedBestBytes.slice(1));
|
|
389
|
+
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (sizePower > 0) {
|
|
394
|
+
addStrategy(candidates, "size");
|
|
395
|
+
for (const cands of narrowedBounds.values())
|
|
396
|
+
recursivelyBuildStrategy(cands, narrowedBestBytes);
|
|
397
|
+
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
addStrategy(candidates, "indistinguishable");
|
|
402
|
+
distinguishable = false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
recursivelyBuildStrategy(layouts.map((_, i) => i), bestBytes);
|
|
406
|
+
|
|
407
|
+
const findSmallestSuperSetStrategy = (candidates: Candidates) => {
|
|
408
|
+
for (let size = candidates.length + 1; size < layouts.length - 2; ++size)
|
|
409
|
+
for (const larger of candidatesBySize.get(size) ?? [])
|
|
410
|
+
if (isSubset(candidates, larger))
|
|
411
|
+
return strategies.get(larger)!;
|
|
412
|
+
|
|
413
|
+
throw new Error("Implementation error in layout discrimination algorithm");
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
return [distinguishable, (encoded: Uint8Array) => {
|
|
417
|
+
let candidates = layouts.map((_, i) => i);
|
|
418
|
+
let strategy = firstStrategy!;
|
|
419
|
+
while (strategy !== "indistinguishable") {
|
|
420
|
+
switch (strategy) {
|
|
421
|
+
case "size": {
|
|
422
|
+
candidates = setIntersection(candidates, layoutsWithSize(encoded.length));
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
default: {
|
|
426
|
+
const [bytePos, outOfBoundsLayouts, distinctValues] = strategy;
|
|
427
|
+
if (encoded.length <= bytePos)
|
|
428
|
+
candidates = setIntersection(candidates, outOfBoundsLayouts);
|
|
429
|
+
else {
|
|
430
|
+
const byteVal = encoded[bytePos];
|
|
431
|
+
for (const [val, cands] of distinctValues)
|
|
432
|
+
if (val !== byteVal)
|
|
433
|
+
candidates = setMinus(candidates, cands);
|
|
434
|
+
|
|
435
|
+
candidates = setMinus(candidates, outOfBoundsLayouts);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (candidates.length <= 1)
|
|
441
|
+
return candidates;
|
|
442
|
+
|
|
443
|
+
strategy = strategies.get(candidates) ?? findSmallestSuperSetStrategy(candidates);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return candidates;
|
|
447
|
+
}];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function layoutDiscriminator<const LA extends readonly Layout[]>(
|
|
451
|
+
layouts: LA,
|
|
452
|
+
mustBeDistinguishable = true
|
|
453
|
+
) {
|
|
454
|
+
const [distinguishable, discriminator] = generateLayoutDiscriminator(layouts);
|
|
455
|
+
if (!distinguishable && mustBeDistinguishable)
|
|
456
|
+
throw new Error("Cannot uniquely distinguished the given layouts");
|
|
457
|
+
|
|
458
|
+
return !mustBeDistinguishable
|
|
459
|
+
? discriminator
|
|
460
|
+
: (encoded: Uint8Array) => {
|
|
461
|
+
const layout = discriminator(encoded);
|
|
462
|
+
return (layout.length === 0) ? null : layout[0];
|
|
463
|
+
};
|
|
464
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Layout,
|
|
3
|
+
LayoutItem,
|
|
4
|
+
ObjectLayoutItem,
|
|
5
|
+
ArrayLayoutItem,
|
|
6
|
+
LayoutToType,
|
|
7
|
+
PrimitiveType,
|
|
8
|
+
FixedConversion,
|
|
9
|
+
isPrimitiveType,
|
|
10
|
+
} from "./layout";
|
|
11
|
+
|
|
12
|
+
type FilterFixedItem<I extends LayoutItem> =
|
|
13
|
+
I extends { custom: PrimitiveType | FixedConversion<PrimitiveType, any> }
|
|
14
|
+
? I
|
|
15
|
+
: I extends ObjectLayoutItem | ArrayLayoutItem
|
|
16
|
+
? FixedItemsOfLayout<I["layout"]> extends readonly LayoutItem[]
|
|
17
|
+
? [FixedItemsOfLayout<I["layout"]>[number]] extends [never]
|
|
18
|
+
? never
|
|
19
|
+
: { readonly [K in keyof I]: K extends "layout" ? FixedItemsOfLayout<I["layout"]> : I[K] }
|
|
20
|
+
: never
|
|
21
|
+
: never;
|
|
22
|
+
|
|
23
|
+
export type FixedItemsOfLayout<L extends Layout> =
|
|
24
|
+
L extends readonly [infer H extends LayoutItem, ...infer T]
|
|
25
|
+
? [FilterFixedItem<H>] extends [never]
|
|
26
|
+
? T extends Layout
|
|
27
|
+
? FixedItemsOfLayout<T>
|
|
28
|
+
: readonly []
|
|
29
|
+
: T extends Layout
|
|
30
|
+
? readonly [FilterFixedItem<H>, ...FixedItemsOfLayout<T>]
|
|
31
|
+
: readonly [FilterFixedItem<H>]
|
|
32
|
+
: readonly [];
|
|
33
|
+
|
|
34
|
+
export const fixedItemsOfLayout = <L extends Layout>(layout: L): FixedItemsOfLayout<L> =>
|
|
35
|
+
layout.reduce(
|
|
36
|
+
(acc: any, item: any) => {
|
|
37
|
+
if (item["custom"] !== undefined && (
|
|
38
|
+
isPrimitiveType(item["custom"]) || isPrimitiveType(item["custom"].from)
|
|
39
|
+
))
|
|
40
|
+
return [...acc, item ];
|
|
41
|
+
if (item.binary === "array" || item.binary === "object") {
|
|
42
|
+
const fixedItems = fixedItemsOfLayout(item.layout);
|
|
43
|
+
if (fixedItems.length > 0)
|
|
44
|
+
return [...acc, { ...item, layout: fixedItems }];
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
},
|
|
48
|
+
[] as any
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
type FilterDynamicItem<I extends LayoutItem> =
|
|
52
|
+
I extends { custom: PrimitiveType | FixedConversion<PrimitiveType, any> }
|
|
53
|
+
? never
|
|
54
|
+
: I extends ObjectLayoutItem | ArrayLayoutItem
|
|
55
|
+
? DynamicItemsOfLayout<I["layout"]> extends readonly LayoutItem[]
|
|
56
|
+
? [DynamicItemsOfLayout<I["layout"]>[number]] extends [never]
|
|
57
|
+
? never
|
|
58
|
+
: { readonly [K in keyof I]: K extends "layout" ? DynamicItemsOfLayout<I["layout"]> : I[K] }
|
|
59
|
+
: never
|
|
60
|
+
: I;
|
|
61
|
+
|
|
62
|
+
export type DynamicItemsOfLayout<L extends Layout> =
|
|
63
|
+
L extends readonly [infer H extends LayoutItem, ...infer T]
|
|
64
|
+
? [FilterDynamicItem<H>] extends [never]
|
|
65
|
+
? T extends Layout
|
|
66
|
+
? DynamicItemsOfLayout<T>
|
|
67
|
+
: readonly []
|
|
68
|
+
: T extends Layout
|
|
69
|
+
? readonly [FilterDynamicItem<H>, ...DynamicItemsOfLayout<T>]
|
|
70
|
+
: readonly [FilterDynamicItem<H>]
|
|
71
|
+
: readonly [];
|
|
72
|
+
|
|
73
|
+
export const dynamicItemsOfLayout = <L extends Layout>(layout: L): DynamicItemsOfLayout<L> =>
|
|
74
|
+
layout.reduce(
|
|
75
|
+
(acc: any, item: any) => {
|
|
76
|
+
if (item["custom"] === undefined || !(
|
|
77
|
+
isPrimitiveType(item["custom"]) || isPrimitiveType(item["custom"].from)
|
|
78
|
+
))
|
|
79
|
+
return [...acc, item ];
|
|
80
|
+
if (item.binary === "array" || item.binary === "object") {
|
|
81
|
+
const dynamicItems = dynamicItemsOfLayout(item.layout);
|
|
82
|
+
if (dynamicItems.length > 0)
|
|
83
|
+
return [...acc, { ...item, layout: dynamicItems }];
|
|
84
|
+
}
|
|
85
|
+
return acc;
|
|
86
|
+
},
|
|
87
|
+
[] as any
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
export const addFixedValues = <L extends Layout>(
|
|
91
|
+
layout: L,
|
|
92
|
+
dynamicValues: LayoutToType<DynamicItemsOfLayout<L>>,
|
|
93
|
+
): LayoutToType<L> => {
|
|
94
|
+
const ret = {} as any;
|
|
95
|
+
for (const item of layout) {
|
|
96
|
+
if (item.binary === "object") {
|
|
97
|
+
const subDynamicValues = (
|
|
98
|
+
(item.name in dynamicValues)
|
|
99
|
+
? dynamicValues[item.name as keyof typeof dynamicValues]
|
|
100
|
+
: {}
|
|
101
|
+
) as LayoutToType<DynamicItemsOfLayout<typeof item.layout>>;
|
|
102
|
+
ret[item.name] = addFixedValues(item.layout, subDynamicValues);
|
|
103
|
+
}
|
|
104
|
+
else if (item.binary === "array") {
|
|
105
|
+
const subDynamicValues = (
|
|
106
|
+
(item.name in dynamicValues)
|
|
107
|
+
? dynamicValues[item.name as keyof typeof dynamicValues]
|
|
108
|
+
: []
|
|
109
|
+
) as readonly LayoutToType<DynamicItemsOfLayout<typeof item.layout>>[];
|
|
110
|
+
ret[item.name] = subDynamicValues.map(element => addFixedValues(item.layout, element));
|
|
111
|
+
}
|
|
112
|
+
else if (item.custom !== undefined &&
|
|
113
|
+
(isPrimitiveType(item.custom) || isPrimitiveType((item.custom as {from: any}).from))
|
|
114
|
+
) {
|
|
115
|
+
if (!(item as {omit?: boolean})?.omit)
|
|
116
|
+
ret[item.name] = isPrimitiveType(item.custom) ? item.custom : item.custom.to;
|
|
117
|
+
}
|
|
118
|
+
else
|
|
119
|
+
ret[item.name] = dynamicValues[item.name as keyof typeof dynamicValues];
|
|
120
|
+
}
|
|
121
|
+
return ret as LayoutToType<L>;
|
|
122
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Layout,
|
|
3
|
+
LayoutItem,
|
|
4
|
+
UintLayoutItem,
|
|
5
|
+
BytesLayoutItem,
|
|
6
|
+
FixedPrimitiveBytesLayoutItem,
|
|
7
|
+
FixedValueBytesLayoutItem,
|
|
8
|
+
FixedSizeBytesLayoutItem,
|
|
9
|
+
LengthPrefixedBytesLayoutItem,
|
|
10
|
+
ArrayLayoutItem,
|
|
11
|
+
ObjectLayoutItem,
|
|
12
|
+
LayoutToType,
|
|
13
|
+
LayoutItemToType,
|
|
14
|
+
FixedConversion,
|
|
15
|
+
CustomConversion,
|
|
16
|
+
} from "./layout";
|
|
17
|
+
|
|
18
|
+
export { serializeLayout } from "./serialize";
|
|
19
|
+
export { deserializeLayout } from "./deserialize";
|
|
20
|
+
export {
|
|
21
|
+
FixedItemsOfLayout,
|
|
22
|
+
DynamicItemsOfLayout,
|
|
23
|
+
fixedItemsOfLayout,
|
|
24
|
+
dynamicItemsOfLayout,
|
|
25
|
+
addFixedValues,
|
|
26
|
+
} from "./fixedDynamic";
|