@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.
@@ -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";