-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
abi-coder.ts
140 lines (120 loc) · 4.91 KB
/
abi-coder.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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
import type { BytesLike } from '@ethersproject/bytes';
import { arrayify } from '@ethersproject/bytes';
import { Logger } from '@ethersproject/logger';
import type { DecodedValue, InputValue } from './coders/abstract-coder';
import type Coder from './coders/abstract-coder';
import ArrayCoder from './coders/array';
import B256Coder from './coders/b256';
import BooleanCoder from './coders/boolean';
import ByteCoder from './coders/byte';
import EnumCoder from './coders/enum';
import NumberCoder from './coders/number';
import StringCoder from './coders/string';
import StructCoder from './coders/struct';
import TupleCoder from './coders/tuple';
import U64Coder from './coders/u64';
import type { JsonAbiFragmentType } from './json-abi';
import { filterEmptyParams } from './utilities';
export const stringRegEx = /str\[(?<length>[0-9]+)\]/;
export const arrayRegEx = /\[(?<item>[\w\s\\[\]]+);\s*(?<length>[0-9]+)\]/;
export const structRegEx = /^struct (?<name>\w+)$/;
export const enumRegEx = /^enum (?<name>\w+)$/;
export const tupleRegEx = /^\((?<items>.*)\)$/;
export const genericRegEx = /^generic (?<name>\w+)$/;
const logger = new Logger(process.env.BUILD_VERSION || '~');
export default class AbiCoder {
constructor() {
logger.checkNew(new.target, AbiCoder);
}
getCoder(param: JsonAbiFragmentType): Coder {
switch (param.type) {
case 'u8':
case 'u16':
case 'u32':
return new NumberCoder(param.type);
case 'u64':
return new U64Coder();
case 'bool':
return new BooleanCoder();
case 'byte':
return new ByteCoder();
case 'b256':
return new B256Coder();
default:
}
const arrayMatch = arrayRegEx.exec(param.type)?.groups;
if (arrayMatch) {
const length = parseInt(arrayMatch.length, 10);
const itemComponent = param.components?.[0];
if (!itemComponent) {
throw new Error('Expected array type to have an item component');
}
const itemCoder = this.getCoder(itemComponent);
return new ArrayCoder(itemCoder, length);
}
const stringMatch = stringRegEx.exec(param.type)?.groups;
if (stringMatch) {
const length = parseInt(stringMatch.length, 10);
return new StringCoder(length);
}
const structMatch = structRegEx.exec(param.type)?.groups;
if (structMatch && Array.isArray(param.components)) {
const coders = param.components.reduce((obj, component) => {
// eslint-disable-next-line no-param-reassign
obj[component.name] = this.getCoder(component);
return obj;
}, {});
return new StructCoder(structMatch.name, coders);
}
const enumMatch = enumRegEx.exec(param.type)?.groups;
if (enumMatch && Array.isArray(param.components)) {
const coders = param.components.reduce((obj, component) => {
// eslint-disable-next-line no-param-reassign
obj[component.name] = this.getCoder(component);
return obj;
}, {});
return new EnumCoder(enumMatch.name, coders);
}
const tupleMatch = tupleRegEx.exec(param.type)?.groups;
if (tupleMatch && Array.isArray(param.components)) {
const coders = param.components.map((component) => this.getCoder(component));
return new TupleCoder(coders);
}
return logger.throwArgumentError('Invalid type', 'type', param.type);
}
encode(types: ReadonlyArray<JsonAbiFragmentType>, values: InputValue[]): Uint8Array {
const nonEmptyTypes = filterEmptyParams(types);
if (Array.isArray(values) && nonEmptyTypes.length !== values.length) {
logger.throwError('Types/values length mismatch', Logger.errors.INVALID_ARGUMENT, {
count: { types: nonEmptyTypes.length, values: values.length },
value: { types, values },
});
}
const coders = nonEmptyTypes.map((type) => this.getCoder(type));
const coder = new TupleCoder(coders);
return coder.encode(values);
}
decode(types: ReadonlyArray<JsonAbiFragmentType>, data: BytesLike): DecodedValue[] | undefined {
const bytes = arrayify(data);
const nonEmptyTypes = filterEmptyParams(types);
const assertParamsMatch = (newOffset: number) => {
if (newOffset !== bytes.length) {
logger.throwError('Types/values length mismatch', Logger.errors.INVALID_ARGUMENT, {
count: { types: nonEmptyTypes.length, values: bytes.length },
value: { types: nonEmptyTypes, bytes },
});
}
};
if (types.length === 0 || nonEmptyTypes.length === 0) {
// The VM is current return 0x0000000000000000, but we should treat it as undefined / void
assertParamsMatch(bytes.length ? 8 : 0);
return undefined;
}
const coders = nonEmptyTypes.map((type) => this.getCoder(type));
const coder = new TupleCoder(coders);
const [decoded, newOffset] = coder.decode(bytes, 0);
assertParamsMatch(newOffset);
return decoded as DecodedValue[];
}
}