diff options
author | Fabio Berger <me@fabioberger.com> | 2018-08-14 04:01:32 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-08-14 04:01:32 +0800 |
commit | 9d3c287918389d07f884245bd1bc968955768b6f (patch) | |
tree | 460fded537c7d64154972b7d14332f88554d14c0 /packages/utils | |
parent | c2b5fe3d844d35966c5498326000bd8317fb547c (diff) | |
parent | 15e15f994a1b18cf2e9be151194c826d53a01601 (diff) | |
download | dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.gz dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.bz2 dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.lz dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.xz dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.tar.zst dexon-sol-tools-9d3c287918389d07f884245bd1bc968955768b6f.zip |
Merge branch 'sol-cov-fixes' of github.com:0xProject/0x-monorepo into sol-cov-fixes
* 'sol-cov-fixes' of github.com:0xProject/0x-monorepo: (49 commits)
Add @return comments
Import marshaller directly
Update comment about ethers checksummed address behavior
Add packages/coverage/.gitkeep file
Update CI config and package.json to run @0xproject/utils tests on CI
Update remaining CHANGELOG.json files
Change amir picture
Update CHANGELOG.json for contract-wrappers
Update ethers typings for TypeScript 2.9.2
Update CHANGELOG.json for base-contract
Move some ethers-related types to typescript-typings/ethers
Apply prettier
Add strictArgumentEncodingCheck to BaseContract and use it in contract templates
fix(monorepo-scripts): Fix typo in git tag command
feat(monorepo-scripts): Add confirmation prompt before publishing
fix comments and styling for MixinSignatureValidator
Update TypeScript to version 2.9.2
Use asm for hashEIP712Message, increment free memory pointer after asm hashing functions
Fix comments, styling, and optimize hashOrder
Remove assertion comments
...
Diffstat (limited to 'packages/utils')
-rw-r--r-- | packages/utils/coverage/.gitkeep | 0 | ||||
-rw-r--r-- | packages/utils/package.json | 14 | ||||
-rw-r--r-- | packages/utils/src/abi_utils.ts | 153 | ||||
-rw-r--r-- | packages/utils/test/abi_utils_test.ts | 19 | ||||
-rw-r--r-- | packages/utils/tsconfig.json | 2 |
5 files changed, 184 insertions, 4 deletions
diff --git a/packages/utils/coverage/.gitkeep b/packages/utils/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/packages/utils/coverage/.gitkeep diff --git a/packages/utils/package.json b/packages/utils/package.json index b1a0d8eb9..ee5fc264f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -5,12 +5,17 @@ "node": ">=6.12" }, "description": "0x TS utils", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", "scripts": { "watch_without_deps": "tsc -w", "build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts", "clean": "shx rm -rf lib scripts", + "test": "yarn run_mocha", + "test:circleci": "yarn test:coverage", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", "lint": "tslint --project .", "manual:postpublish": "yarn build; node ./scripts/postpublish.js" }, @@ -27,12 +32,15 @@ "@0xproject/monorepo-scripts": "^1.0.4", "@0xproject/tslint-config": "^1.0.4", "@types/lodash": "4.14.104", + "@types/mocha": "^2.2.42", "copyfiles": "^1.2.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", "tslint": "5.11.0", - "typescript": "2.7.1" + "typescript": "2.9.2", + "chai": "^4.0.1", + "mocha": "^4.0.1" }, "dependencies": { "@0xproject/types": "^1.0.1-rc.3", diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts index 421dd405c..c9b70966c 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 ethers from 'ethers'; import * as _ from 'lodash'; +import { BigNumber } from './configured_bignumber'; + +// 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: ethers.ParamName[]; types: string[] } { + const names: ethers.ParamName[] = []; + 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: ethers.ParamName, 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 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 ethers.NestedParamName).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 returns the checksummed address even when + // initially passed in a non-checksummed address. 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} diff --git a/packages/utils/test/abi_utils_test.ts b/packages/utils/test/abi_utils_test.ts new file mode 100644 index 000000000..0ebee64c4 --- /dev/null +++ b/packages/utils/test/abi_utils_test.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { abiUtils } from '../src'; + +const expect = chai.expect; + +describe('abiUtils', () => { + describe('splitTupleTypes', () => { + it('handles basic types', () => { + const got = abiUtils.splitTupleTypes('tuple(bytes,uint256,address)'); + expect(got).to.deep.equal(['bytes', 'uint256', 'address']); + }); + it('handles nested tuple types', () => { + const got = abiUtils.splitTupleTypes('tuple(tuple(bytes,uint256),address)'); + expect(got).to.deep.equal(['tuple(bytes,uint256)', 'address']); + }); + }); +}); diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index c56d255d5..8b4cd47a2 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "outDir": "lib" }, - "include": ["./src/**/*"] + "include": ["src/**/*", "test/**/*"] } |