-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
enum.ts
71 lines (62 loc) · 2.53 KB
/
enum.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import { concat } from '@ethersproject/bytes';
import { toHex, toNumber } from '@fuel-ts/math';
import type { RequireExactlyOne } from 'type-fest';
import type { TypesOfCoder } from './abstract-coder';
import Coder from './abstract-coder';
import U64Coder from './u64';
type InputValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
[P in keyof TCoders]: TypesOfCoder<TCoders[P]>['Input'];
}>;
type DecodedValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
[P in keyof TCoders]: TypesOfCoder<TCoders[P]>['Decoded'];
}>;
export default class EnumCoder<TCoders extends Record<string, Coder>> extends Coder<
InputValueOf<TCoders>,
DecodedValueOf<TCoders>
> {
name: string;
coders: TCoders;
#caseIndexCoder: U64Coder;
#encodedValueSize: number;
constructor(name: string, coders: TCoders) {
const caseIndexCoder = new U64Coder();
const encodedValueSize = Object.values(coders).reduce(
(max, coder) => Math.max(max, coder.encodedLength),
0
);
super('enum', `enum ${name}`, caseIndexCoder.encodedLength + encodedValueSize);
this.name = name;
this.coders = coders;
this.#caseIndexCoder = caseIndexCoder;
this.#encodedValueSize = encodedValueSize;
}
encode(value: InputValueOf<TCoders>): Uint8Array {
const [caseKey, ...empty] = Object.keys(value);
if (!caseKey) {
throw new Error('A field for the case must be provided');
}
if (empty.length !== 0) {
throw new Error('Only one field must be provided');
}
const valueCoder = this.coders[caseKey];
const caseIndex = Object.keys(this.coders).indexOf(caseKey);
const encodedValue = valueCoder.encode(value[caseKey]);
const padding = new Uint8Array(this.#encodedValueSize - valueCoder.encodedLength);
return concat([this.#caseIndexCoder.encode(toHex(caseIndex)), padding, encodedValue]);
}
decode(data: Uint8Array, offset: number): [DecodedValueOf<TCoders>, number] {
let newOffset = offset;
let decoded;
[decoded, newOffset] = new U64Coder().decode(data, newOffset);
const caseIndex = toNumber(decoded);
const caseKey = Object.keys(this.coders)[caseIndex];
if (!caseKey) {
throw new Error(`Invalid caseIndex "${caseIndex}". Valid cases: ${Object.keys(this.coders)}`);
}
const valueCoder = this.coders[caseKey];
const padding = this.#encodedValueSize - valueCoder.encodedLength;
newOffset += padding;
[decoded, newOffset] = valueCoder.decode(data, newOffset);
return [{ [caseKey]: decoded } as DecodedValueOf<TCoders>, newOffset];
}
}