UNPKG

9.26 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.abiUtils = void 0;
4const ethereum_types_1 = require("ethereum-types");
5const _ = require("lodash");
6const configured_bignumber_1 = require("./configured_bignumber");
7// Note(albrow): This function is unexported in ethers.js. Copying it here for
8// now.
9// Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30
10function parseEthersParams(params) {
11 const names = [];
12 const types = [];
13 params.forEach((param) => {
14 if (param.components != null) {
15 let suffix = '';
16 const arrayBracket = param.type.indexOf('[');
17 if (arrayBracket >= 0) {
18 suffix = param.type.substring(arrayBracket);
19 }
20 const result = parseEthersParams(param.components);
21 names.push({ name: param.name || null, names: result.names });
22 types.push(`tuple(${result.types.join(',')})${suffix}`);
23 }
24 else {
25 names.push(param.name || null);
26 types.push(param.type);
27 }
28 });
29 return {
30 names,
31 types,
32 };
33}
34// returns true if x is equal to y and false otherwise. Performs some minimal
35// type conversion and data massaging for x and y, depending on type. name and
36// type should typically be derived from parseEthersParams.
37function isAbiDataEqual(name, type, x, y) {
38 if (x === undefined && y === undefined) {
39 return true;
40 }
41 else if (x === undefined && y !== undefined) {
42 return false;
43 }
44 else if (x !== undefined && y === undefined) {
45 return false;
46 }
47 if (_.endsWith(type, '[]')) {
48 // For array types, we iterate through the elements and check each one
49 // individually. Strangely, name does not need to be changed in this
50 // case.
51 if (x.length !== y.length) {
52 return false;
53 }
54 const newType = _.trimEnd(type, '[]');
55 for (let i = 0; i < x.length; i++) {
56 if (!isAbiDataEqual(name, newType, x[i], y[i])) {
57 return false;
58 }
59 }
60 return true;
61 }
62 if (_.startsWith(type, 'tuple(')) {
63 if (_.isString(name)) {
64 throw new Error('Internal error: type was tuple but names was a string');
65 }
66 else if (name === null) {
67 throw new Error('Internal error: type was tuple but names was null');
68 }
69 // For tuples, we iterate through the underlying values and check each
70 // one individually.
71 const types = splitTupleTypes(type);
72 if (types.length !== name.names.length) {
73 throw new Error(`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`);
74 }
75 for (let i = 0; i < types.length; i++) {
76 // For tuples, name is an object with a names property that is an
77 // array. As an example, for orders, name looks like:
78 //
79 // {
80 // name: 'orders',
81 // names: [
82 // 'makerAddress',
83 // // ...
84 // 'takerAssetData'
85 // ]
86 // }
87 //
88 const nestedName = _.isString(name.names[i])
89 ? name.names[i]
90 : name.names[i].name;
91 if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) {
92 return false;
93 }
94 }
95 return true;
96 }
97 else if (type === 'address' || type === 'bytes') {
98 // HACK(albrow): ethers.js returns the checksummed address even when
99 // initially passed in a non-checksummed address. To account for that,
100 // we convert to lowercase before comparing.
101 return _.isEqual(_.toLower(x), _.toLower(y));
102 }
103 else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) {
104 return new configured_bignumber_1.BigNumber(x).eq(new configured_bignumber_1.BigNumber(y));
105 }
106 return _.isEqual(x, y);
107}
108// splitTupleTypes splits a tuple type string (of the form `tuple(X)` where X is
109// any other type or list of types) into its component types. It works with
110// nested tuples, so, e.g., `tuple(tuple(uint256,address),bytes32)` will yield:
111// `['tuple(uint256,address)', 'bytes32']`. It expects exactly one tuple type as
112// an argument (not an array).
113function splitTupleTypes(type) {
114 if (_.endsWith(type, '[]')) {
115 throw new Error('Internal error: array types are not supported');
116 }
117 else if (!_.startsWith(type, 'tuple(')) {
118 throw new Error(`Internal error: expected tuple type but got non-tuple type: ${type}`);
119 }
120 // Trim the outtermost tuple().
121 const trimmedType = type.substring('tuple('.length, type.length - 1);
122 const types = [];
123 let currToken = '';
124 let parenCount = 0;
125 // Tokenize the type string while keeping track of parentheses.
126 for (const char of trimmedType) {
127 switch (char) {
128 case '(':
129 parenCount += 1;
130 currToken += char;
131 break;
132 case ')':
133 parenCount -= 1;
134 currToken += char;
135 break;
136 case ',':
137 if (parenCount === 0) {
138 types.push(currToken);
139 currToken = '';
140 break;
141 }
142 else {
143 currToken += char;
144 break;
145 }
146 default:
147 currToken += char;
148 break;
149 }
150 }
151 types.push(currToken);
152 return types;
153}
154exports.abiUtils = {
155 parseEthersParams,
156 isAbiDataEqual,
157 splitTupleTypes,
158 parseFunctionParam(param) {
159 if (param.type === 'tuple') {
160 // Parse out tuple types into {type_1, type_2, ..., type_N}
161 const tupleComponents = param.components;
162 const paramString = _.map(tupleComponents, component => exports.abiUtils.parseFunctionParam(component));
163 const tupleParamString = `{${paramString}}`;
164 return tupleParamString;
165 }
166 return param.type;
167 },
168 getFunctionSignature(methodAbi) {
169 const functionName = methodAbi.name;
170 const parameterTypeList = _.map(methodAbi.inputs, (param) => exports.abiUtils.parseFunctionParam(param));
171 const functionSignature = `${functionName}(${parameterTypeList})`;
172 return functionSignature;
173 },
174 /**
175 * Solidity supports function overloading whereas TypeScript does not.
176 * See: https://solidity.readthedocs.io/en/v0.4.21/contracts.html?highlight=overload#function-overloading
177 * In order to support overloaded functions, we suffix overloaded function names with an index.
178 * This index should be deterministic, regardless of function ordering within the smart contract. To do so,
179 * we assign indexes based on the alphabetical order of function signatures.
180 *
181 * E.g
182 * ['f(uint)', 'f(uint,byte32)']
183 * Should always be renamed to:
184 * ['f1(uint)', 'f2(uint,byte32)']
185 * Regardless of the order in which these these overloaded functions are declared within the contract ABI.
186 */
187 renameOverloadedMethods(inputContractAbi) {
188 const contractAbi = _.cloneDeep(inputContractAbi);
189 const methodAbis = contractAbi.filter((abi) => abi.type === ethereum_types_1.AbiType.Function);
190 // Sort method Abis into alphabetical order, by function signature
191 const methodAbisOrdered = _.sortBy(methodAbis, [
192 (methodAbi) => {
193 const functionSignature = exports.abiUtils.getFunctionSignature(methodAbi);
194 return functionSignature;
195 },
196 ]);
197 // Group method Abis by name (overloaded methods will be grouped together, in alphabetical order)
198 const methodAbisByName = {};
199 _.each(methodAbisOrdered, methodAbi => {
200 (methodAbisByName[methodAbi.name] || (methodAbisByName[methodAbi.name] = [])).push(methodAbi);
201 });
202 // Rename overloaded methods to overloadedMethodName1, overloadedMethodName2, ...
203 _.each(methodAbisByName, methodAbisWithSameName => {
204 _.each(methodAbisWithSameName, (methodAbi, i) => {
205 if (methodAbisWithSameName.length > 1) {
206 const overloadedMethodId = i + 1;
207 const sanitizedMethodName = `${methodAbi.name}${overloadedMethodId}`;
208 const indexOfExistingAbiWithSanitizedMethodNameIfExists = _.findIndex(methodAbis, currentMethodAbi => currentMethodAbi.name === sanitizedMethodName);
209 if (indexOfExistingAbiWithSanitizedMethodNameIfExists >= 0) {
210 const methodName = methodAbi.name;
211 throw new Error(`Failed to rename overloaded method '${methodName}' to '${sanitizedMethodName}'. A method with this name already exists.`);
212 }
213 methodAbi.name = sanitizedMethodName;
214 }
215 });
216 });
217 return contractAbi;
218 },
219};
220//# sourceMappingURL=abi_utils.js.map
\No newline at end of file