-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
enum.ts
69 lines (61 loc) · 2.5 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
import { concat } from '@ethersproject/bytes';
import type { RequireExactlyOne } from 'type-fest';
import type { TypesOfCoder } from './abstract-coder';
import Coder from './abstract-coder';
import NumberCoder from './number';
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: NumberCoder<'u64'>;
#encodedValueSize: number;
constructor(name: string, coders: TCoders) {
const caseIndexCoder = new NumberCoder('u64');
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(caseIndex), padding, encodedValue]);
}
decode(data: Uint8Array, offset: number): [DecodedValueOf<TCoders>, number] {
let newOffset = offset;
let decoded;
[decoded, newOffset] = new NumberCoder('u64').decode(data, newOffset);
const caseIndex = decoded;
const caseKey = Object.keys(this.coders)[Number(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];
}
}