-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
interface.ts
152 lines (125 loc) · 4.69 KB
/
interface.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
141
142
143
144
145
146
147
148
149
150
151
152
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { BytesLike } from '@ethersproject/bytes';
import { arrayify, concat, hexlify } from '@ethersproject/bytes';
import { Logger } from '@ethersproject/logger';
import { sha256 } from '@ethersproject/sha2';
import { toUtf8Bytes } from '@ethersproject/strings';
import AbiCoder from './abi-coder';
import type { InputValue } from './coders/abstract-coder';
import BooleanCoder from './coders/boolean';
import type { Fragment } from './fragments/fragment';
import FunctionFragment from './fragments/function-fragment';
import type { JsonAbi, JsonAbiFragment } from './json-abi';
import { isReferenceType } from './json-abi';
import { filterEmptyParams } from './utilities';
const logger = new Logger(process.env.BUILD_VERSION || '~');
const coerceFragments = (value: ReadonlyArray<JsonAbiFragment>): Array<Fragment> => {
const fragments: Array<Fragment> = [];
value.forEach((v) => {
if (v.type === 'function') {
fragments.push(FunctionFragment.fromObject(v));
}
});
return fragments;
};
export default class Interface {
readonly fragments: Array<Fragment>;
readonly functions: { [name: string]: FunctionFragment };
readonly abiCoder: AbiCoder;
constructor(jsonAbi: JsonAbi) {
this.fragments = coerceFragments(jsonAbi);
this.abiCoder = new AbiCoder();
this.functions = {};
this.fragments.forEach((fragment) => {
let bucket: { [name: string]: Fragment } = {};
switch (fragment.type) {
case 'function':
bucket = this.functions;
break;
default:
return;
}
const signature = fragment.getInputsSighash();
if (bucket[signature]) {
logger.warn(`duplicate definition - ${signature}`);
return;
}
bucket[signature] = fragment;
});
}
static getSighash(fragment: FunctionFragment | string): Uint8Array {
const bytes =
typeof fragment === 'string'
? toUtf8Bytes(fragment)
: toUtf8Bytes(fragment.getInputsSighash());
return concat([new Uint8Array(4), arrayify(sha256(bytes)).slice(0, 4)]);
}
getFunction(nameOrSignatureOrSighash: string): FunctionFragment {
if (this.functions[nameOrSignatureOrSighash]) {
return this.functions[nameOrSignatureOrSighash];
}
const functionFragment = Object.values(this.functions).find(
(fragment: Fragment) =>
hexlify(Interface.getSighash(fragment)) === nameOrSignatureOrSighash ||
fragment.name === nameOrSignatureOrSighash
);
if (functionFragment) {
return functionFragment;
}
return logger.throwArgumentError(
`function ${nameOrSignatureOrSighash} not found.`,
'data',
functionFragment
);
}
// Decode the data for a function call (e.g. tx.data)
decodeFunctionData(functionFragment: FunctionFragment | string, data: BytesLike): any {
const fragment =
typeof functionFragment === 'string' ? this.getFunction(functionFragment) : functionFragment;
const bytes = arrayify(data);
if (hexlify(bytes.slice(0, 8)) !== hexlify(Interface.getSighash(fragment))) {
logger.throwArgumentError(
`data signature does not match function ${fragment.name}.`,
'data',
hexlify(bytes)
);
}
return this.abiCoder.decode(fragment.inputs, bytes.slice(16));
}
encodeFunctionData(
functionFragment: FunctionFragment | string,
values: Array<InputValue>
): Uint8Array {
const fragment =
typeof functionFragment === 'string' ? this.getFunction(functionFragment) : functionFragment;
if (!fragment) {
throw new Error('Fragment not found');
}
const selector = Interface.getSighash(fragment);
const inputs = filterEmptyParams(fragment.inputs);
if (inputs.length === 0) {
return selector;
}
const isRef = inputs.length > 1 || isReferenceType(inputs[0].type);
const args = this.abiCoder.encode(inputs, values);
return concat([selector, new BooleanCoder().encode(isRef), args]);
}
// Decode the result of a function call
decodeFunctionResult(functionFragment: FunctionFragment | string, data: BytesLike): any {
const fragment =
typeof functionFragment === 'string' ? this.getFunction(functionFragment) : functionFragment;
const bytes = arrayify(data);
return this.abiCoder.decode(fragment.outputs, bytes);
}
encodeFunctionResult(
functionFragment: FunctionFragment | string,
values: Array<InputValue>
): Uint8Array {
const fragment =
typeof functionFragment === 'string' ? this.getFunction(functionFragment) : functionFragment;
if (!fragment) {
throw new Error('Fragment not found');
}
return this.abiCoder.encode(fragment.outputs, values);
}
}