diff options
author | Alex Browne <stephenalexbrowne@gmail.com> | 2018-07-27 05:11:03 +0800 |
---|---|---|
committer | Alex Browne <stephenalexbrowne@gmail.com> | 2018-08-09 05:27:30 +0800 |
commit | 6a5965d73bb542634631d7af76c150795d899744 (patch) | |
tree | aba6597779094b9738c0ad94fb2694a18d22cdc2 /packages/utils/src | |
parent | 19cda0eb036b6876964d8074aea1295142d25027 (diff) | |
download | dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.tar dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.tar.gz dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.tar.bz2 dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.tar.lz dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.tar.xz dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.tar.zst dexon-0x-contracts-6a5965d73bb542634631d7af76c150795d899744.zip |
Add strictArgumentEncodingCheck to BaseContract and use it in contract templates
Diffstat (limited to 'packages/utils/src')
-rw-r--r-- | packages/utils/src/abi_utils.ts | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 421dd405c..16aa72afd 100644 --- a/packages/utils/src/abi_utils.ts +++ b/packages/utils/src/abi_utils.ts @@ -1,7 +1,160 @@ import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; +import { BigNumber } from './configured_bignumber'; + +export type EthersParamName = null | string | EthersNestedParamName; + +export interface EthersNestedParamName { + name: string | null; + names: EthersParamName[]; +} + +// Note(albrow): This function is unexported in ethers.js. Copying it here for +// now. +// Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30 +function parseEthersParams(params: DataItem[]): { names: EthersParamName[]; types: string[] } { + const names: EthersParamName[] = []; + const types: string[] = []; + + params.forEach((param: DataItem) => { + if (param.components != null) { + let suffix = ''; + const arrayBracket = param.type.indexOf('['); + if (arrayBracket >= 0) { suffix = param.type.substring(arrayBracket); } + + const result = parseEthersParams(param.components); + names.push({ name: (param.name || null), names: result.names }); + types.push('tuple(' + result.types.join(',') + ')' + suffix); + } else { + names.push(param.name || null); + types.push(param.type); + } + }); + + return { + names, + types, + }; +} + +// returns true if x is equal to y and false otherwise. Performs some minimal +// type conversion and data massaging for x and y, depending on type. name and +// type should typically be derived from parseEthersParams. +function isAbiDataEqual(name: EthersParamName, type: string, x: any, y: any): boolean { + if (_.isUndefined(x) && _.isUndefined(y)) { + return true; + } else if (_.isUndefined(x) && !_.isUndefined(y)) { + return false; + } else if (!_.isUndefined(x) && _.isUndefined(y)) { + return false; + } + if (_.endsWith(type, '[]')) { + // For array types, we iterate through the elements and check each one + // individually. Strangely, name does not need to be changed in this + // case. + if (x.length !== y.length) { + return false; + } + const newType = _.trimEnd(type, '[]'); + for (let i = 0; i < x.length; i++) { + if (!isAbiDataEqual(name, newType, x[i], y[i])) { + return false; + } + } + return true; + } + if (_.startsWith(type, 'tuple(')) { + if (_.isString(name)) { + throw new Error('Internal error: type was tuple but names was a string'); + } else if (_.isNull(name)) { + throw new Error('Internal error: type was tuple but names was a null'); + } + // For tuples, we iterate through the underlying values and check each + // one individually. + const types = splitTupleTypes(type); + if (types.length !== name.names.length) { + throw new Error(`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`); + } + for (let i = 0; i < types.length; i++) { + // For tuples, name is an object with a names property that is an + // array. As an example, for orders, name looks like: + // + // { + // name: 'orders', + // names: [ + // 'makerAddress', + // // ... + // 'takerAssetData' + // ] + // } + // + const nestedName = _.isString(name.names[i]) ? name.names[i] as string : (name.names[i] as EthersNestedParamName).name as string; + if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) { + return false; + } + } + return true; + } else if (type === 'address' || type === 'bytes') { + // HACK(albrow): ethers.js sometimes changes the case of addresses/bytes + // when decoding/encoding. To account for that, we convert to lowercase + // before comparing. + return _.isEqual(_.toLower(x), _.toLower(y)); + } else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) { + return new BigNumber(x).eq(new BigNumber(y)); + } + return _.isEqual(x, y); +} + +// splitTupleTypes splits a tuple type string (of the form `tuple(X)` where X is +// any other type or list of types) into its component types. It works with +// nested tuples, so, e.g., `tuple(tuple(uint256,address),bytes32)` will yield: +// `['tuple(uint256,address)', 'bytes32']`. It expects exactly one tuple type as +// an argument (not an array). +function splitTupleTypes(type: string): string[] { + if (_.endsWith(type, '[]')) { + throw new Error('Internal error: array types are not supported'); + } else if (!_.startsWith(type, 'tuple(')) { + throw new Error('Internal error: expected tuple type but got non-tuple type: ' + type); + } + // Trim the outtermost tuple(). + const trimmedType = type.substring('tuple('.length, type.length - 1); + const types: string[] = []; + let currToken = ''; + let parenCount = 0; + // Tokenize the type string while keeping track of parentheses. + for (const char of trimmedType) { + switch (char) { + case '(': + parenCount += 1; + currToken += char; + break; + case ')': + parenCount -= 1; + currToken += char; + break; + case ',': + if (parenCount === 0) { + types.push(currToken); + currToken = ''; + break; + } else { + currToken += char; + break; + } + default: + currToken += char; + break; + } + } + types.push(currToken); + return types; +} + export const abiUtils = { + parseEthersParams, + isAbiDataEqual, + splitTupleTypes, parseFunctionParam(param: DataItem): string { if (param.type === 'tuple') { // Parse out tuple types into {type_1, type_2, ..., type_N} |