From 100840b74359e00bda90146f87ddeafd62db06e3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 29 Jan 2019 17:18:33 -0800 Subject: Utility function to decode arbitrary 0x calldata --- packages/utils/package.json | 2 +- packages/utils/src/calldata_decoder.ts | 66 ++++++++++++++++++++++++++++ packages/utils/src/index.ts | 1 + packages/utils/test/calldata_decoder_test.ts | 21 +++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 packages/utils/src/calldata_decoder.ts create mode 100644 packages/utils/test/calldata_decoder_test.ts diff --git a/packages/utils/package.json b/packages/utils/package.json index 86fecbc7c..895560961 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -14,7 +14,7 @@ "lint": "tslint --format stylish --project .", "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", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*_test.js' '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" }, diff --git a/packages/utils/src/calldata_decoder.ts b/packages/utils/src/calldata_decoder.ts new file mode 100644 index 000000000..780332514 --- /dev/null +++ b/packages/utils/src/calldata_decoder.ts @@ -0,0 +1,66 @@ +// Decodes any 0x transaction + +import * as ContractArtifacts from '@0x/contract-artifacts'; +import { SimpleContractArtifact } from '@0x/types'; +import { ContractAbi, MethodAbi } from 'ethereum-types'; +import * as _ from 'lodash'; +import { AbiEncoder } from '.'; + +export interface DecodedCalldata { + functionName: string; + functionSignature: string; + functionArguments: any; +} + +interface AbiEncoderBySelector { + [index: string]: AbiEncoder.Method; +} + +export class CalldataDecoder { + private readonly _abiEncoderBySelector: AbiEncoderBySelector = {}; + private static _instance: CalldataDecoder; + + public static getInstance(): CalldataDecoder { + if (!CalldataDecoder._instance) { + CalldataDecoder._instance = new CalldataDecoder(); + } + return CalldataDecoder._instance; + } + + public constructor() { + _.each(ContractArtifacts, (contractArtifactAsJson: any) => { + const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; + const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; + const functionAbis = _.filter(contractAbi, (abiEntry) => {return abiEntry.type === 'function'}) as MethodAbi[]; + _.each(functionAbis, (functionAbi) => { + const abiEncoder = new AbiEncoder.Method(functionAbi); + const functionSelector = abiEncoder.getSelector(); + if (_.has(this._abiEncoderBySelector, functionSelector)) { + return; + } + this._abiEncoderBySelector[functionSelector] = abiEncoder; + }); + }); + } + + public static decode(calldata: string, rules?: AbiEncoder.DecodingRules): DecodedCalldata { + if (!calldata.startsWith('0x') || calldata.length < 10) { + throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); + } + const functionSelector = calldata.substr(0, 10); + const instance = CalldataDecoder.getInstance(); + const abiEncoder = instance._abiEncoderBySelector[functionSelector]; + if (_.isUndefined(abiEncoder)) { + throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); + } + const functionName = abiEncoder.getDataItem().name; + const functionSignature = abiEncoder.getSignatureType(); + const functionArguments = abiEncoder.decode(calldata, rules); + const decodedCalldata = { + functionName, + functionSignature, + functionArguments + } + return decodedCalldata; + } +} \ No newline at end of file diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 082aff6bb..3e5058edc 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -11,3 +11,4 @@ export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); +export { CalldataDecoder } from './calldata_decoder'; diff --git a/packages/utils/test/calldata_decoder_test.ts b/packages/utils/test/calldata_decoder_test.ts new file mode 100644 index 000000000..a3ff02a59 --- /dev/null +++ b/packages/utils/test/calldata_decoder_test.ts @@ -0,0 +1,21 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { CalldataDecoder } from '../src'; +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe.only('CalldataDecoder', () => { + describe.only('decodeCalldata', () => { + it.only('should successfull decode fillOrder calldata', async () => { + //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; + //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; + const calldata = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; + const decodedCalldata = CalldataDecoder.decode(calldata); + console.log(JSON.stringify(decodedCalldata, null, 4)); + expect(5).to.be.equal(5); + }); + }); +}); -- cgit v1.2.3 From 8fc3a6b828e6f3a5f1aacfc62f585834f012ce7a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 30 Jan 2019 10:52:13 -0800 Subject: Expanding search parameters for transaction decoder --- packages/utils/src/calldata_decoder.ts | 93 +++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/calldata_decoder.ts b/packages/utils/src/calldata_decoder.ts index 780332514..0279aa030 100644 --- a/packages/utils/src/calldata_decoder.ts +++ b/packages/utils/src/calldata_decoder.ts @@ -5,18 +5,41 @@ import { SimpleContractArtifact } from '@0x/types'; import { ContractAbi, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; import { AbiEncoder } from '.'; +import { ContractAddresses, getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; export interface DecodedCalldata { functionName: string; functionSignature: string; functionArguments: any; + contractName: string; + deployedAddress?: string; + deployedNeworkId?: string; +} + +interface AbiEncoderBySelectorElement { + abiEncoder: AbiEncoder.Method; + contractName?: string; + contractAddress?: string; +} + +interface AbiEncoderByNeworkId { + [index: string]: AbiEncoderBySelectorElement; } interface AbiEncoderBySelector { - [index: string]: AbiEncoder.Method; + [index: string]: AbiEncoderByNeworkId; +} + +interface DeployedContractInfoByNetwork { + [index: number]: string; +} + +interface DeployedContractInfoByName { + [index: string]: DeployedContractInfoByNetwork; } export class CalldataDecoder { + private readonly _deployedContractInfoByName = {} as DeployedContractInfoByName; private readonly _abiEncoderBySelector: AbiEncoderBySelector = {}; private static _instance: CalldataDecoder; @@ -27,7 +50,15 @@ export class CalldataDecoder { return CalldataDecoder._instance; } - public constructor() { + private constructor() { + // Load addresses by contract name + _.each(NetworkId, (networkId: NetworkId) => { + const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkId); + _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { + this._deployedContractInfoByName[contractName][networkId as number] = contractAddress; + }); + }); + // Load contract artifacts _.each(ContractArtifacts, (contractArtifactAsJson: any) => { const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; @@ -38,21 +69,71 @@ export class CalldataDecoder { if (_.has(this._abiEncoderBySelector, functionSelector)) { return; } - this._abiEncoderBySelector[functionSelector] = abiEncoder; + this._abiEncoderBySelector[functionSelector][conractArtifact.contractName] = {abiEncoder}; }); }); } - public static decode(calldata: string, rules?: AbiEncoder.DecodingRules): DecodedCalldata { + public static registerContractAbi(contractArtifact: SimpleContractArtifact, deployedAddress?: string, deployedNeworkId?: number) { + + } + + private static getFunctionSelector(calldata: string): string { if (!calldata.startsWith('0x') || calldata.length < 10) { throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); } const functionSelector = calldata.substr(0, 10); + return functionSelector; + } + + public static decodeWithContractAddress(calldata: string, contractAddress: string, networkId?: number): DecodedCalldata { + const functionSelector = CalldataDecoder.getFunctionSelector(calldata); const instance = CalldataDecoder.getInstance(); - const abiEncoder = instance._abiEncoderBySelector[functionSelector]; + const contractName = _.findKey(instance._deployedContractInfoByName, (info: DeployedContractInfoByNetwork) => { + return (!_.isUndefined(networkId) && info[networkId] === contractAddress) || (_.isUndefined(networkId) && contractAddress in info); + }); + if (_.isUndefined(contractName)) { + throw new Error(`Could not find contract name: ${contractName}`); + } + const abiEncoder = instance._abiEncoderBySelector[functionSelector][contractName]; if (_.isUndefined(abiEncoder)) { throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); } + + } + + public static decodeWithContractName(calldata: string, contractName: string): DecodedCalldata { + const functionSelector = CalldataDecoder.getFunctionSelector(calldata); + const instance = CalldataDecoder.getInstance(); + const abiEncoder = instance._abiEncoderBySelector[functionSelector][contractName]; + if (_.isUndefined(abiEncoder)) { + throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); + } + } + + public static decodeWithoutContractInfo(calldata: string): DecodedCalldata { + const functionSelector = CalldataDecoder.getFunctionSelector(calldata); + const instance = CalldataDecoder.getInstance(); + const abiEncoder = _.find(instance._abiEncoderBySelector[functionSelector], () => {return true}); + if (_.isUndefined(abiEncoder)) { + throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); + } + return { + functionName: string; + functionSignature: string; + functionArguments: any; + contractName: string; + deployedAddress?: string; + deployedNeworkId?: string; + }; + } + + public static decode(calldata: string, contractName?: string, contractAddress?: string, networkId?: number, rules?: AbiEncoder.DecodingRules): DecodedCalldata { + + + + + /* const functionName = abiEncoder.getDataItem().name; const functionSignature = abiEncoder.getSignatureType(); const functionArguments = abiEncoder.decode(calldata, rules); @@ -61,6 +142,6 @@ export class CalldataDecoder { functionSignature, functionArguments } - return decodedCalldata; + return decodedCalldata;*/ } } \ No newline at end of file -- cgit v1.2.3 From f93cd1bb48c9ae977822a274033f02fab737f64a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 30 Jan 2019 12:28:09 -0800 Subject: Can query decoder by contract address / network id OR contract name --- packages/utils/src/calldata_decoder.ts | 129 ++++++++++++++------------- packages/utils/test/calldata_decoder_test.ts | 2 +- 2 files changed, 68 insertions(+), 63 deletions(-) diff --git a/packages/utils/src/calldata_decoder.ts b/packages/utils/src/calldata_decoder.ts index 0279aa030..bbd67fd2c 100644 --- a/packages/utils/src/calldata_decoder.ts +++ b/packages/utils/src/calldata_decoder.ts @@ -11,15 +11,26 @@ export interface DecodedCalldata { functionName: string; functionSignature: string; functionArguments: any; - contractName: string; - deployedAddress?: string; - deployedNeworkId?: string; } interface AbiEncoderBySelectorElement { abiEncoder: AbiEncoder.Method; contractName?: string; contractAddress?: string; + networkId?: number; +} + +interface TransactionDecoderInfo { + abiEncoder: AbiEncoder.Method; + contractName?: string; + contractAddress?: string; + networkId?: number; +} + +interface TransactionProperties { + contractName?: string; + contractAddress?: string; + networkId?: number; } interface AbiEncoderByNeworkId { @@ -30,17 +41,23 @@ interface AbiEncoderBySelector { [index: string]: AbiEncoderByNeworkId; } -interface DeployedContractInfoByNetwork { - [index: number]: string; +interface DeployedContractInfo { + contractAddress?: string; + networkId?: number; } interface DeployedContractInfoByName { - [index: string]: DeployedContractInfoByNetwork; + [index: string]: DeployedContractInfo[]; +} + +interface TransactionDecodersBySelector { + [index: string]: TransactionDecoderInfo[]; } export class CalldataDecoder { private readonly _deployedContractInfoByName = {} as DeployedContractInfoByName; private readonly _abiEncoderBySelector: AbiEncoderBySelector = {}; + private readonly _txDecoders: TransactionDecodersBySelector = {}; private static _instance: CalldataDecoder; public static getInstance(): CalldataDecoder { @@ -52,15 +69,23 @@ export class CalldataDecoder { private constructor() { // Load addresses by contract name - _.each(NetworkId, (networkId: NetworkId) => { - const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkId); + _.each(NetworkId, (networkId: any) => { + if (typeof networkId !== 'number') return; + const networkIdAsNumber = networkId as number; + const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkIdAsNumber); _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { - this._deployedContractInfoByName[contractName][networkId as number] = contractAddress; + const contractNameLowercase = _.toLower(contractName); + if (_.isUndefined(this._deployedContractInfoByName[contractNameLowercase])) { + this._deployedContractInfoByName[contractNameLowercase] = []; + } + this._deployedContractInfoByName[contractNameLowercase].push({contractAddress, networkId: networkIdAsNumber}); }); }); // Load contract artifacts _.each(ContractArtifacts, (contractArtifactAsJson: any) => { const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; + const contractName = conractArtifact.contractName; + const contractNameLowercase = _.toLower(contractName); const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; const functionAbis = _.filter(contractAbi, (abiEntry) => {return abiEntry.type === 'function'}) as MethodAbi[]; _.each(functionAbis, (functionAbi) => { @@ -69,15 +94,27 @@ export class CalldataDecoder { if (_.has(this._abiEncoderBySelector, functionSelector)) { return; } - this._abiEncoderBySelector[functionSelector][conractArtifact.contractName] = {abiEncoder}; + if (!(functionSelector in this._txDecoders)) this._txDecoders[functionSelector] = []; + // Recored deployed versions of this decoder + _.each(this._deployedContractInfoByName[contractNameLowercase], (deployedContract) => { + this._txDecoders[functionSelector].push({ + abiEncoder, + contractName, + contractAddress: deployedContract.contractAddress, + networkId: deployedContract.networkId, + }); + }); + // If there isn't a deployed version of this contract, record it without address/network id + if (_.isUndefined(this._deployedContractInfoByName[contractNameLowercase])) { + this._txDecoders[functionSelector].push({ + abiEncoder, + contractName, + }); + } }); }); } - public static registerContractAbi(contractArtifact: SimpleContractArtifact, deployedAddress?: string, deployedNeworkId?: number) { - - } - private static getFunctionSelector(calldata: string): string { if (!calldata.startsWith('0x') || calldata.length < 10) { throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); @@ -86,62 +123,30 @@ export class CalldataDecoder { return functionSelector; } - public static decodeWithContractAddress(calldata: string, contractAddress: string, networkId?: number): DecodedCalldata { + public static decode(calldata: string, txProperties_?: TransactionProperties): DecodedCalldata { const functionSelector = CalldataDecoder.getFunctionSelector(calldata); + const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; const instance = CalldataDecoder.getInstance(); - const contractName = _.findKey(instance._deployedContractInfoByName, (info: DeployedContractInfoByNetwork) => { - return (!_.isUndefined(networkId) && info[networkId] === contractAddress) || (_.isUndefined(networkId) && contractAddress in info); - }); - if (_.isUndefined(contractName)) { - throw new Error(`Could not find contract name: ${contractName}`); - } - const abiEncoder = instance._abiEncoderBySelector[functionSelector][contractName]; - if (_.isUndefined(abiEncoder)) { - throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); - } - - } - - public static decodeWithContractName(calldata: string, contractName: string): DecodedCalldata { - const functionSelector = CalldataDecoder.getFunctionSelector(calldata); - const instance = CalldataDecoder.getInstance(); - const abiEncoder = instance._abiEncoderBySelector[functionSelector][contractName]; - if (_.isUndefined(abiEncoder)) { - throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); + const txDecodersByFunctionSelector = instance._txDecoders[functionSelector]; + if (_.isUndefined(txDecodersByFunctionSelector)) { + throw new Error(`No decoder registered for function selector '${functionSelector}'`); } - } - - public static decodeWithoutContractInfo(calldata: string): DecodedCalldata { - const functionSelector = CalldataDecoder.getFunctionSelector(calldata); - const instance = CalldataDecoder.getInstance(); - const abiEncoder = _.find(instance._abiEncoderBySelector[functionSelector], () => {return true}); - if (_.isUndefined(abiEncoder)) { - throw new Error(`Could not find matching abi encoder for selector '${functionSelector}'`); + const txDecoderWithProperties = _.find(txDecodersByFunctionSelector, (txDecoder) => { + return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && + (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && + (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); + }); + if (_.isUndefined(txDecoderWithProperties)) { + throw new Error(`No decoder registered with properties: ${JSON.stringify(txProperties)}.`); } - return { - functionName: string; - functionSignature: string; - functionArguments: any; - contractName: string; - deployedAddress?: string; - deployedNeworkId?: string; - }; - } - - public static decode(calldata: string, contractName?: string, contractAddress?: string, networkId?: number, rules?: AbiEncoder.DecodingRules): DecodedCalldata { - - - - - /* - const functionName = abiEncoder.getDataItem().name; - const functionSignature = abiEncoder.getSignatureType(); - const functionArguments = abiEncoder.decode(calldata, rules); + const functionName = txDecoderWithProperties.abiEncoder.getDataItem().name; + const functionSignature = txDecoderWithProperties.abiEncoder.getSignatureType(); + const functionArguments = txDecoderWithProperties.abiEncoder.decode(calldata); const decodedCalldata = { functionName, functionSignature, functionArguments } - return decodedCalldata;*/ + return decodedCalldata; } } \ No newline at end of file diff --git a/packages/utils/test/calldata_decoder_test.ts b/packages/utils/test/calldata_decoder_test.ts index a3ff02a59..7d5cc9fd9 100644 --- a/packages/utils/test/calldata_decoder_test.ts +++ b/packages/utils/test/calldata_decoder_test.ts @@ -13,7 +13,7 @@ describe.only('CalldataDecoder', () => { //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; const calldata = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; - const decodedCalldata = CalldataDecoder.decode(calldata); + const decodedCalldata = CalldataDecoder.decode(calldata, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); console.log(JSON.stringify(decodedCalldata, null, 4)); expect(5).to.be.equal(5); }); -- cgit v1.2.3 From a9f8e80b1c5e7be3ca2a4552b67ff3bd39aa23c3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 11:24:12 -0800 Subject: Abstractd out ZeroExTransactionDecoder --- packages/utils/src/calldata_decoder.ts | 193 ++++++++++++++------------- packages/utils/src/index.ts | 2 +- packages/utils/test/calldata_decoder_test.ts | 4 +- 3 files changed, 106 insertions(+), 93 deletions(-) diff --git a/packages/utils/src/calldata_decoder.ts b/packages/utils/src/calldata_decoder.ts index bbd67fd2c..36016c7bc 100644 --- a/packages/utils/src/calldata_decoder.ts +++ b/packages/utils/src/calldata_decoder.ts @@ -2,29 +2,27 @@ import * as ContractArtifacts from '@0x/contract-artifacts'; import { SimpleContractArtifact } from '@0x/types'; -import { ContractAbi, MethodAbi } from 'ethereum-types'; +import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; import { AbiEncoder } from '.'; -import { ContractAddresses, getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; +import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; -export interface DecodedCalldata { - functionName: string; +export interface FunctionInfo { functionSignature: string; - functionArguments: any; -} - -interface AbiEncoderBySelectorElement { - abiEncoder: AbiEncoder.Method; contractName?: string; contractAddress?: string; networkId?: number; + abiEncoder?: AbiEncoder.Method; } -interface TransactionDecoderInfo { - abiEncoder: AbiEncoder.Method; - contractName?: string; - contractAddress?: string; - networkId?: number; +interface FunctionInfoBySelector { + [index: string]: FunctionInfo[]; +} + +export interface DecodedCalldata { + functionName: string; + functionSignature: string; + functionArguments: any; } interface TransactionProperties { @@ -33,41 +31,104 @@ interface TransactionProperties { networkId?: number; } -interface AbiEncoderByNeworkId { - [index: string]: AbiEncoderBySelectorElement; -} - -interface AbiEncoderBySelector { - [index: string]: AbiEncoderByNeworkId; -} - interface DeployedContractInfo { - contractAddress?: string; - networkId?: number; + contractAddress: string; + networkId: number; } interface DeployedContractInfoByName { [index: string]: DeployedContractInfo[]; } -interface TransactionDecodersBySelector { - [index: string]: TransactionDecoderInfo[]; +export class TransactionDecoder { + private _functionInfoBySelector: FunctionInfoBySelector = {}; + + private static getFunctionSelector(calldata: string): string { + if (!calldata.startsWith('0x') || calldata.length < 10) { + throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); + } + const functionSelector = calldata.substr(0, 10); + return functionSelector; + } + + public addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { + if (_.isEmpty(abiArray)) { + return; + } + const functionAbis = _.filter(abiArray, abiEntry => { + return abiEntry.type === 'function'; + }) as MethodAbi[]; + _.each(functionAbis, functionAbi => { + const abiEncoder = new AbiEncoder.Method(functionAbi); + const functionSelector = abiEncoder.getSelector(); + if (!(functionSelector in this._functionInfoBySelector)) { + this._functionInfoBySelector[functionSelector] = []; + } + // Recored deployed versions of this decoder + const functionSignature = abiEncoder.getSignature(); + _.each(deploymentInfos, deploymentInfo => { + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + contractAddress: deploymentInfo.contractAddress, + networkId: deploymentInfo.networkId, + }); + }); + // If there isn't a deployed version of this contract, record it without address/network id + if (_.isEmpty(deploymentInfos)) { + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + }); + } + }); + } + + public decode(calldata: string, txProperties_?: TransactionProperties): DecodedCalldata { + const functionSelector = TransactionDecoder.getFunctionSelector(calldata); + const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; + + const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; + if (_.isUndefined(candidateFunctionInfos)) { + throw new Error(`No functions registered for selector '${functionSelector}'`); + } + const functionInfo = _.find(candidateFunctionInfos, (txDecoder) => { + return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && + (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && + (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); + }); + if (_.isUndefined(functionInfo)) { + throw new Error(`No function registered with properties: ${JSON.stringify(txProperties)}.`); + } else if (_.isUndefined(functionInfo.abiEncoder)) { + throw new Error(`Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`); + } + const functionName = functionInfo.abiEncoder.getDataItem().name; + const functionSignature = functionInfo.abiEncoder.getSignatureType(); + const functionArguments = functionInfo.abiEncoder.decode(calldata); + const decodedCalldata = { + functionName, + functionSignature, + functionArguments + } + return decodedCalldata; + } } -export class CalldataDecoder { +export class ZeroExTransactionDecoder extends TransactionDecoder { private readonly _deployedContractInfoByName = {} as DeployedContractInfoByName; - private readonly _abiEncoderBySelector: AbiEncoderBySelector = {}; - private readonly _txDecoders: TransactionDecodersBySelector = {}; - private static _instance: CalldataDecoder; + private static _instance: ZeroExTransactionDecoder; - public static getInstance(): CalldataDecoder { - if (!CalldataDecoder._instance) { - CalldataDecoder._instance = new CalldataDecoder(); + private static getInstance(): ZeroExTransactionDecoder { + if (!ZeroExTransactionDecoder._instance) { + ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); } - return CalldataDecoder._instance; + return ZeroExTransactionDecoder._instance; } private constructor() { + super(); // Load addresses by contract name _.each(NetworkId, (networkId: any) => { if (typeof networkId !== 'number') return; @@ -87,66 +148,18 @@ export class CalldataDecoder { const contractName = conractArtifact.contractName; const contractNameLowercase = _.toLower(contractName); const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; - const functionAbis = _.filter(contractAbi, (abiEntry) => {return abiEntry.type === 'function'}) as MethodAbi[]; - _.each(functionAbis, (functionAbi) => { - const abiEncoder = new AbiEncoder.Method(functionAbi); - const functionSelector = abiEncoder.getSelector(); - if (_.has(this._abiEncoderBySelector, functionSelector)) { - return; - } - if (!(functionSelector in this._txDecoders)) this._txDecoders[functionSelector] = []; - // Recored deployed versions of this decoder - _.each(this._deployedContractInfoByName[contractNameLowercase], (deployedContract) => { - this._txDecoders[functionSelector].push({ - abiEncoder, - contractName, - contractAddress: deployedContract.contractAddress, - networkId: deployedContract.networkId, - }); - }); - // If there isn't a deployed version of this contract, record it without address/network id - if (_.isUndefined(this._deployedContractInfoByName[contractNameLowercase])) { - this._txDecoders[functionSelector].push({ - abiEncoder, - contractName, - }); - } - }); + this.addABI(contractAbi, contractName, this._deployedContractInfoByName[contractNameLowercase]); }); } - private static getFunctionSelector(calldata: string): string { - if (!calldata.startsWith('0x') || calldata.length < 10) { - throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); - } - const functionSelector = calldata.substr(0, 10); - return functionSelector; + public static addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { + const instance = ZeroExTransactionDecoder.getInstance(); + instance.addABI(abiArray, contractName, deploymentInfos); } - public static decode(calldata: string, txProperties_?: TransactionProperties): DecodedCalldata { - const functionSelector = CalldataDecoder.getFunctionSelector(calldata); - const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; - const instance = CalldataDecoder.getInstance(); - const txDecodersByFunctionSelector = instance._txDecoders[functionSelector]; - if (_.isUndefined(txDecodersByFunctionSelector)) { - throw new Error(`No decoder registered for function selector '${functionSelector}'`); - } - const txDecoderWithProperties = _.find(txDecodersByFunctionSelector, (txDecoder) => { - return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && - (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && - (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); - }); - if (_.isUndefined(txDecoderWithProperties)) { - throw new Error(`No decoder registered with properties: ${JSON.stringify(txProperties)}.`); - } - const functionName = txDecoderWithProperties.abiEncoder.getDataItem().name; - const functionSignature = txDecoderWithProperties.abiEncoder.getSignatureType(); - const functionArguments = txDecoderWithProperties.abiEncoder.decode(calldata); - const decodedCalldata = { - functionName, - functionSignature, - functionArguments - } + public static decode(calldata: string, txProperties?: TransactionProperties): DecodedCalldata { + const instance = ZeroExTransactionDecoder.getInstance(); + const decodedCalldata = instance.decode(calldata, txProperties); return decodedCalldata; } -} \ No newline at end of file +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 3e5058edc..de32557fc 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -11,4 +11,4 @@ export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); -export { CalldataDecoder } from './calldata_decoder'; +export { ZeroExTransactionDecoder } from './calldata_decoder'; diff --git a/packages/utils/test/calldata_decoder_test.ts b/packages/utils/test/calldata_decoder_test.ts index 7d5cc9fd9..a7eafed70 100644 --- a/packages/utils/test/calldata_decoder_test.ts +++ b/packages/utils/test/calldata_decoder_test.ts @@ -1,7 +1,7 @@ import * as chai from 'chai'; import 'mocha'; -import { CalldataDecoder } from '../src'; +import { ZeroExTransactionDecoder } from '../src'; import { chaiSetup } from './utils/chai_setup'; chaiSetup.configure(); @@ -13,7 +13,7 @@ describe.only('CalldataDecoder', () => { //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; const calldata = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; - const decodedCalldata = CalldataDecoder.decode(calldata, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + const decodedCalldata = ZeroExTransactionDecoder.decode(calldata, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); console.log(JSON.stringify(decodedCalldata, null, 4)); expect(5).to.be.equal(5); }); -- cgit v1.2.3 From 896874fb99b47cc256489e6f7be348b2140e6036 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 11:40:33 -0800 Subject: separated tx decode classes and types into separate files --- packages/utils/src/calldata_decoder.ts | 165 ----------------------- packages/utils/src/index.ts | 4 +- packages/utils/src/transaction_decoder.ts | 80 +++++++++++ packages/utils/src/types.ts | 34 +++++ packages/utils/src/zeroex_transaction_decoder.ts | 55 ++++++++ packages/utils/test/calldata_decoder_test.ts | 21 --- packages/utils/test/transaction_decoder_test.ts | 20 +++ 7 files changed, 192 insertions(+), 187 deletions(-) delete mode 100644 packages/utils/src/calldata_decoder.ts create mode 100644 packages/utils/src/transaction_decoder.ts create mode 100644 packages/utils/src/types.ts create mode 100644 packages/utils/src/zeroex_transaction_decoder.ts delete mode 100644 packages/utils/test/calldata_decoder_test.ts create mode 100644 packages/utils/test/transaction_decoder_test.ts diff --git a/packages/utils/src/calldata_decoder.ts b/packages/utils/src/calldata_decoder.ts deleted file mode 100644 index 36016c7bc..000000000 --- a/packages/utils/src/calldata_decoder.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Decodes any 0x transaction - -import * as ContractArtifacts from '@0x/contract-artifacts'; -import { SimpleContractArtifact } from '@0x/types'; -import { AbiDefinition, ContractAbi, MethodAbi } from 'ethereum-types'; -import * as _ from 'lodash'; -import { AbiEncoder } from '.'; -import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; - -export interface FunctionInfo { - functionSignature: string; - contractName?: string; - contractAddress?: string; - networkId?: number; - abiEncoder?: AbiEncoder.Method; -} - -interface FunctionInfoBySelector { - [index: string]: FunctionInfo[]; -} - -export interface DecodedCalldata { - functionName: string; - functionSignature: string; - functionArguments: any; -} - -interface TransactionProperties { - contractName?: string; - contractAddress?: string; - networkId?: number; -} - -interface DeployedContractInfo { - contractAddress: string; - networkId: number; -} - -interface DeployedContractInfoByName { - [index: string]: DeployedContractInfo[]; -} - -export class TransactionDecoder { - private _functionInfoBySelector: FunctionInfoBySelector = {}; - - private static getFunctionSelector(calldata: string): string { - if (!calldata.startsWith('0x') || calldata.length < 10) { - throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); - } - const functionSelector = calldata.substr(0, 10); - return functionSelector; - } - - public addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { - if (_.isEmpty(abiArray)) { - return; - } - const functionAbis = _.filter(abiArray, abiEntry => { - return abiEntry.type === 'function'; - }) as MethodAbi[]; - _.each(functionAbis, functionAbi => { - const abiEncoder = new AbiEncoder.Method(functionAbi); - const functionSelector = abiEncoder.getSelector(); - if (!(functionSelector in this._functionInfoBySelector)) { - this._functionInfoBySelector[functionSelector] = []; - } - // Recored deployed versions of this decoder - const functionSignature = abiEncoder.getSignature(); - _.each(deploymentInfos, deploymentInfo => { - this._functionInfoBySelector[functionSelector].push({ - functionSignature, - abiEncoder, - contractName, - contractAddress: deploymentInfo.contractAddress, - networkId: deploymentInfo.networkId, - }); - }); - // If there isn't a deployed version of this contract, record it without address/network id - if (_.isEmpty(deploymentInfos)) { - this._functionInfoBySelector[functionSelector].push({ - functionSignature, - abiEncoder, - contractName, - }); - } - }); - } - - public decode(calldata: string, txProperties_?: TransactionProperties): DecodedCalldata { - const functionSelector = TransactionDecoder.getFunctionSelector(calldata); - const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; - - const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; - if (_.isUndefined(candidateFunctionInfos)) { - throw new Error(`No functions registered for selector '${functionSelector}'`); - } - const functionInfo = _.find(candidateFunctionInfos, (txDecoder) => { - return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && - (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && - (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); - }); - if (_.isUndefined(functionInfo)) { - throw new Error(`No function registered with properties: ${JSON.stringify(txProperties)}.`); - } else if (_.isUndefined(functionInfo.abiEncoder)) { - throw new Error(`Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`); - } - const functionName = functionInfo.abiEncoder.getDataItem().name; - const functionSignature = functionInfo.abiEncoder.getSignatureType(); - const functionArguments = functionInfo.abiEncoder.decode(calldata); - const decodedCalldata = { - functionName, - functionSignature, - functionArguments - } - return decodedCalldata; - } -} - -export class ZeroExTransactionDecoder extends TransactionDecoder { - private readonly _deployedContractInfoByName = {} as DeployedContractInfoByName; - private static _instance: ZeroExTransactionDecoder; - - private static getInstance(): ZeroExTransactionDecoder { - if (!ZeroExTransactionDecoder._instance) { - ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); - } - return ZeroExTransactionDecoder._instance; - } - - private constructor() { - super(); - // Load addresses by contract name - _.each(NetworkId, (networkId: any) => { - if (typeof networkId !== 'number') return; - const networkIdAsNumber = networkId as number; - const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkIdAsNumber); - _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { - const contractNameLowercase = _.toLower(contractName); - if (_.isUndefined(this._deployedContractInfoByName[contractNameLowercase])) { - this._deployedContractInfoByName[contractNameLowercase] = []; - } - this._deployedContractInfoByName[contractNameLowercase].push({contractAddress, networkId: networkIdAsNumber}); - }); - }); - // Load contract artifacts - _.each(ContractArtifacts, (contractArtifactAsJson: any) => { - const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; - const contractName = conractArtifact.contractName; - const contractNameLowercase = _.toLower(contractName); - const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; - this.addABI(contractAbi, contractName, this._deployedContractInfoByName[contractNameLowercase]); - }); - } - - public static addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { - const instance = ZeroExTransactionDecoder.getInstance(); - instance.addABI(abiArray, contractName, deploymentInfos); - } - - public static decode(calldata: string, txProperties?: TransactionProperties): DecodedCalldata { - const instance = ZeroExTransactionDecoder.getInstance(); - const decodedCalldata = instance.decode(calldata, txProperties); - return decodedCalldata; - } -} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index de32557fc..c0f15f2ab 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -11,4 +11,6 @@ export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); -export { ZeroExTransactionDecoder } from './calldata_decoder'; +export * from './types'; +export { TransactionDecoder } from './transaction_decoder'; +export { ZeroExTransactionDecoder } from './zeroex_transaction_decoder'; diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts new file mode 100644 index 000000000..85d92d553 --- /dev/null +++ b/packages/utils/src/transaction_decoder.ts @@ -0,0 +1,80 @@ +import { AbiDefinition, MethodAbi } from 'ethereum-types'; +import * as _ from 'lodash'; +import { AbiEncoder } from '.'; +import { FunctionInfoBySelector, TransactionData, TransactionProperties, DeployedContractInfo } from './types'; + +export class TransactionDecoder { + private _functionInfoBySelector: FunctionInfoBySelector = {}; + + private static getFunctionSelector(calldata: string): string { + if (!calldata.startsWith('0x') || calldata.length < 10) { + throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); + } + const functionSelector = calldata.substr(0, 10); + return functionSelector; + } + + public addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { + if (_.isEmpty(abiArray)) { + return; + } + const functionAbis = _.filter(abiArray, abiEntry => { + return abiEntry.type === 'function'; + }) as MethodAbi[]; + _.each(functionAbis, functionAbi => { + const abiEncoder = new AbiEncoder.Method(functionAbi); + const functionSelector = abiEncoder.getSelector(); + if (!(functionSelector in this._functionInfoBySelector)) { + this._functionInfoBySelector[functionSelector] = []; + } + // Recored deployed versions of this decoder + const functionSignature = abiEncoder.getSignature(); + _.each(deploymentInfos, deploymentInfo => { + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + contractAddress: deploymentInfo.contractAddress, + networkId: deploymentInfo.networkId, + }); + }); + // If there isn't a deployed version of this contract, record it without address/network id + if (_.isEmpty(deploymentInfos)) { + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + }); + } + }); + } + + public decode(calldata: string, txProperties_?: TransactionProperties): TransactionData { + const functionSelector = TransactionDecoder.getFunctionSelector(calldata); + const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; + const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; + if (_.isUndefined(candidateFunctionInfos)) { + throw new Error(`No functions registered for selector '${functionSelector}'`); + } + const functionInfo = _.find(candidateFunctionInfos, (txDecoder) => { + return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && + (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && + (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); + }); + if (_.isUndefined(functionInfo)) { + throw new Error(`No function registered with properties: ${JSON.stringify(txProperties)}.`); + } else if (_.isUndefined(functionInfo.abiEncoder)) { + throw new Error(`Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`); + } + const functionName = functionInfo.abiEncoder.getDataItem().name; + const functionSignature = functionInfo.abiEncoder.getSignatureType(); + const functionArguments = functionInfo.abiEncoder.decode(calldata); + const decodedCalldata = { + functionName, + functionSignature, + functionArguments + } + return decodedCalldata; + } +} + diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts new file mode 100644 index 000000000..2510a9ec2 --- /dev/null +++ b/packages/utils/src/types.ts @@ -0,0 +1,34 @@ +import { AbiEncoder } from '.'; + +export interface FunctionInfo { + functionSignature: string; + contractName?: string; + contractAddress?: string; + networkId?: number; + abiEncoder?: AbiEncoder.Method; +} + +export interface FunctionInfoBySelector { + [index: string]: FunctionInfo[]; +} + +export interface TransactionData { + functionName: string; + functionSignature: string; + functionArguments: any; +} + +export interface TransactionProperties { + contractName?: string; + contractAddress?: string; + networkId?: number; +} + +export interface DeployedContractInfo { + contractAddress: string; + networkId: number; +} + +export interface DeployedContractInfoByName { + [index: string]: DeployedContractInfo[]; +} diff --git a/packages/utils/src/zeroex_transaction_decoder.ts b/packages/utils/src/zeroex_transaction_decoder.ts new file mode 100644 index 000000000..049596770 --- /dev/null +++ b/packages/utils/src/zeroex_transaction_decoder.ts @@ -0,0 +1,55 @@ +import { TransactionDecoder } from './transaction_decoder'; +import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; +import * as ContractArtifacts from '@0x/contract-artifacts'; +import { SimpleContractArtifact } from '@0x/types'; +import { AbiDefinition, ContractAbi } from 'ethereum-types'; +import { TransactionData, DeployedContractInfo, DeployedContractInfoByName, TransactionProperties } from './types'; +import * as _ from 'lodash'; + +export class ZeroExTransactionDecoder extends TransactionDecoder { + private readonly _deployedContractInfoByName = {} as DeployedContractInfoByName; + private static _instance: ZeroExTransactionDecoder; + + private static getInstance(): ZeroExTransactionDecoder { + if (!ZeroExTransactionDecoder._instance) { + ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); + } + return ZeroExTransactionDecoder._instance; + } + + private constructor() { + super(); + // Load addresses by contract name + _.each(NetworkId, (networkId: any) => { + if (typeof networkId !== 'number') return; + const networkIdAsNumber = networkId as number; + const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkIdAsNumber); + _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { + const contractNameLowercase = _.toLower(contractName); + if (_.isUndefined(this._deployedContractInfoByName[contractNameLowercase])) { + this._deployedContractInfoByName[contractNameLowercase] = []; + } + this._deployedContractInfoByName[contractNameLowercase].push({contractAddress, networkId: networkIdAsNumber}); + }); + }); + // Load contract artifacts + _.each(ContractArtifacts, (contractArtifactAsJson: any) => { + const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; + const contractName = conractArtifact.contractName; + const contractNameLowercase = _.toLower(contractName); + const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; + this.addABI(contractAbi, contractName, this._deployedContractInfoByName[contractNameLowercase]); + }); + } + + public static addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { + const instance = ZeroExTransactionDecoder.getInstance(); + instance.addABI(abiArray, contractName, deploymentInfos); + } + + public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { + const instance = ZeroExTransactionDecoder.getInstance(); + const decodedCalldata = instance.decode(calldata, txProperties); + return decodedCalldata; + } +} \ No newline at end of file diff --git a/packages/utils/test/calldata_decoder_test.ts b/packages/utils/test/calldata_decoder_test.ts deleted file mode 100644 index a7eafed70..000000000 --- a/packages/utils/test/calldata_decoder_test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as chai from 'chai'; -import 'mocha'; - -import { ZeroExTransactionDecoder } from '../src'; -import { chaiSetup } from './utils/chai_setup'; - -chaiSetup.configure(); -const expect = chai.expect; - -describe.only('CalldataDecoder', () => { - describe.only('decodeCalldata', () => { - it.only('should successfull decode fillOrder calldata', async () => { - //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; - //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; - const calldata = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; - const decodedCalldata = ZeroExTransactionDecoder.decode(calldata, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(JSON.stringify(decodedCalldata, null, 4)); - expect(5).to.be.equal(5); - }); - }); -}); diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts new file mode 100644 index 000000000..b360876b4 --- /dev/null +++ b/packages/utils/test/transaction_decoder_test.ts @@ -0,0 +1,20 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { ZeroExTransactionDecoder } from '../src'; +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe.only('TransactionDecoder', () => { + describe('decode', () => { + it('should successfull decode fillOrder calldata', async () => { + //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; + //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; + const calldata = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; + const decodedTxData = ZeroExTransactionDecoder.decode(calldata, {contractName: "Dutchauction"});//{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + console.log(decodedTxData); + }); + }); +}); -- cgit v1.2.3 From 436d87da98a2025106bfb42838bd18054f7aba63 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 13:20:18 -0800 Subject: Ran prettier and linter --- packages/utils/src/transaction_decoder.ts | 48 ++++++++++++--------- packages/utils/src/zeroex_transaction_decoder.ts | 55 ++++++++++++++---------- packages/utils/test/transaction_decoder_test.ts | 5 ++- 3 files changed, 62 insertions(+), 46 deletions(-) diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts index 85d92d553..35a889a4e 100644 --- a/packages/utils/src/transaction_decoder.ts +++ b/packages/utils/src/transaction_decoder.ts @@ -1,26 +1,27 @@ import { AbiDefinition, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; + import { AbiEncoder } from '.'; -import { FunctionInfoBySelector, TransactionData, TransactionProperties, DeployedContractInfo } from './types'; +import { DeployedContractInfo, FunctionInfoBySelector, TransactionData, TransactionProperties } from './types'; export class TransactionDecoder { - private _functionInfoBySelector: FunctionInfoBySelector = {}; - - private static getFunctionSelector(calldata: string): string { - if (!calldata.startsWith('0x') || calldata.length < 10) { - throw new Error(`Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`); + private readonly _functionInfoBySelector: FunctionInfoBySelector = {}; + + private static _getFunctionSelector(calldata: string): string { + const functionSelectorLength = 10; + if (!calldata.startsWith('0x') || calldata.length < functionSelectorLength) { + throw new Error( + `Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`, + ); } - const functionSelector = calldata.substr(0, 10); + const functionSelector = calldata.substr(0, functionSelectorLength); return functionSelector; } public addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { - if (_.isEmpty(abiArray)) { - return; - } - const functionAbis = _.filter(abiArray, abiEntry => { + const functionAbis: MethodAbi[] = _.filter(abiArray, abiEntry => { return abiEntry.type === 'function'; - }) as MethodAbi[]; + }); _.each(functionAbis, functionAbi => { const abiEncoder = new AbiEncoder.Method(functionAbi); const functionSelector = abiEncoder.getSelector(); @@ -50,21 +51,27 @@ export class TransactionDecoder { } public decode(calldata: string, txProperties_?: TransactionProperties): TransactionData { - const functionSelector = TransactionDecoder.getFunctionSelector(calldata); + const functionSelector = TransactionDecoder._getFunctionSelector(calldata); const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; if (_.isUndefined(candidateFunctionInfos)) { throw new Error(`No functions registered for selector '${functionSelector}'`); } - const functionInfo = _.find(candidateFunctionInfos, (txDecoder) => { - return (_.isUndefined(txProperties.contractName) || _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && - (_.isUndefined(txProperties.contractAddress) || txDecoder.contractAddress === txProperties.contractAddress) && - (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId); + const functionInfo = _.find(candidateFunctionInfos, txDecoder => { + return ( + (_.isUndefined(txProperties.contractName) || + _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && + (_.isUndefined(txProperties.contractAddress) || + txDecoder.contractAddress === txProperties.contractAddress) && + (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId) + ); }); if (_.isUndefined(functionInfo)) { throw new Error(`No function registered with properties: ${JSON.stringify(txProperties)}.`); } else if (_.isUndefined(functionInfo.abiEncoder)) { - throw new Error(`Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`); + throw new Error( + `Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`, + ); } const functionName = functionInfo.abiEncoder.getDataItem().name; const functionSignature = functionInfo.abiEncoder.getSignatureType(); @@ -72,9 +79,8 @@ export class TransactionDecoder { const decodedCalldata = { functionName, functionSignature, - functionArguments - } + functionArguments, + }; return decodedCalldata; } } - diff --git a/packages/utils/src/zeroex_transaction_decoder.ts b/packages/utils/src/zeroex_transaction_decoder.ts index 049596770..f236257cb 100644 --- a/packages/utils/src/zeroex_transaction_decoder.ts +++ b/packages/utils/src/zeroex_transaction_decoder.ts @@ -1,16 +1,31 @@ -import { TransactionDecoder } from './transaction_decoder'; import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; import * as ContractArtifacts from '@0x/contract-artifacts'; import { SimpleContractArtifact } from '@0x/types'; import { AbiDefinition, ContractAbi } from 'ethereum-types'; -import { TransactionData, DeployedContractInfo, DeployedContractInfoByName, TransactionProperties } from './types'; import * as _ from 'lodash'; +import { TransactionDecoder } from './transaction_decoder'; +import { DeployedContractInfo, DeployedContractInfoByName, TransactionData, TransactionProperties } from './types'; + export class ZeroExTransactionDecoder extends TransactionDecoder { - private readonly _deployedContractInfoByName = {} as DeployedContractInfoByName; private static _instance: ZeroExTransactionDecoder; - private static getInstance(): ZeroExTransactionDecoder { + public static addABI( + abiArray: AbiDefinition[], + contractName: string, + deploymentInfos?: DeployedContractInfo[], + ): void { + const instance = ZeroExTransactionDecoder._getInstance(); + instance.addABI(abiArray, contractName, deploymentInfos); + } + + public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { + const instance = ZeroExTransactionDecoder._getInstance(); + const decodedCalldata = instance.decode(calldata, txProperties); + return decodedCalldata; + } + + private static _getInstance(): ZeroExTransactionDecoder { if (!ZeroExTransactionDecoder._instance) { ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); } @@ -20,16 +35,21 @@ export class ZeroExTransactionDecoder extends TransactionDecoder { private constructor() { super(); // Load addresses by contract name + const deployedContractInfoByName: DeployedContractInfoByName = {}; _.each(NetworkId, (networkId: any) => { - if (typeof networkId !== 'number') return; - const networkIdAsNumber = networkId as number; - const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkIdAsNumber); + if (typeof networkId !== 'number') { + return; + } + const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkId); _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { const contractNameLowercase = _.toLower(contractName); - if (_.isUndefined(this._deployedContractInfoByName[contractNameLowercase])) { - this._deployedContractInfoByName[contractNameLowercase] = []; + if (_.isUndefined(deployedContractInfoByName[contractNameLowercase])) { + deployedContractInfoByName[contractNameLowercase] = []; } - this._deployedContractInfoByName[contractNameLowercase].push({contractAddress, networkId: networkIdAsNumber}); + deployedContractInfoByName[contractNameLowercase].push({ + contractAddress, + networkId, + }); }); }); // Load contract artifacts @@ -38,18 +58,7 @@ export class ZeroExTransactionDecoder extends TransactionDecoder { const contractName = conractArtifact.contractName; const contractNameLowercase = _.toLower(contractName); const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; - this.addABI(contractAbi, contractName, this._deployedContractInfoByName[contractNameLowercase]); + this.addABI(contractAbi, contractName, deployedContractInfoByName[contractNameLowercase]); }); } - - public static addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { - const instance = ZeroExTransactionDecoder.getInstance(); - instance.addABI(abiArray, contractName, deploymentInfos); - } - - public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { - const instance = ZeroExTransactionDecoder.getInstance(); - const decodedCalldata = instance.decode(calldata, txProperties); - return decodedCalldata; - } -} \ No newline at end of file +} diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts index b360876b4..eee712e98 100644 --- a/packages/utils/test/transaction_decoder_test.ts +++ b/packages/utils/test/transaction_decoder_test.ts @@ -12,8 +12,9 @@ describe.only('TransactionDecoder', () => { it('should successfull decode fillOrder calldata', async () => { //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; - const calldata = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; - const decodedTxData = ZeroExTransactionDecoder.decode(calldata, {contractName: "Dutchauction"});//{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + const calldata = + '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; + const decodedTxData = ZeroExTransactionDecoder.decode(calldata, { contractName: 'Dutchauction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); console.log(decodedTxData); }); }); -- cgit v1.2.3 From f7b58e7f64dc5af1e4ac8e7159717c78f0767c85 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 13:38:49 -0800 Subject: moved ZeroExTransactionDecoder into contract wrappers --- .../src/utils/zeroex_transaction_decoder.ts | 63 +++++++++++++++++++++ packages/utils/src/zeroex_transaction_decoder.ts | 64 ---------------------- 2 files changed, 63 insertions(+), 64 deletions(-) create mode 100644 packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts delete mode 100644 packages/utils/src/zeroex_transaction_decoder.ts diff --git a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts new file mode 100644 index 000000000..441424e92 --- /dev/null +++ b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts @@ -0,0 +1,63 @@ +import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; +import * as ContractArtifacts from '@0x/contract-artifacts'; +import { SimpleContractArtifact } from '@0x/types'; +import { AbiDefinition, ContractAbi } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { DeployedContractInfo, DeployedContractInfoByName, TransactionData, TransactionDecoder, TransactionProperties } from '@0x/utils'; + +export class ZeroExTransactionDecoder extends TransactionDecoder { + private static _instance: ZeroExTransactionDecoder; + + public static addABI( + abiArray: AbiDefinition[], + contractName: string, + deploymentInfos?: DeployedContractInfo[], + ): void { + const instance = ZeroExTransactionDecoder._getInstance(); + instance.addABI(abiArray, contractName, deploymentInfos); + } + + public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { + const instance = ZeroExTransactionDecoder._getInstance(); + const decodedCalldata = instance.decode(calldata, txProperties); + return decodedCalldata; + } + + private static _getInstance(): ZeroExTransactionDecoder { + if (!ZeroExTransactionDecoder._instance) { + ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); + } + return ZeroExTransactionDecoder._instance; + } + + private constructor() { + super(); + // Load addresses by contract name + const deployedContractInfoByName: DeployedContractInfoByName = {}; + _.each(NetworkId, (networkId: any) => { + if (typeof networkId !== 'number') { + return; + } + const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkId); + _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { + const contractNameLowercase = _.toLower(contractName); + if (_.isUndefined(deployedContractInfoByName[contractNameLowercase])) { + deployedContractInfoByName[contractNameLowercase] = []; + } + deployedContractInfoByName[contractNameLowercase].push({ + contractAddress, + networkId, + }); + }); + }); + // Load contract artifacts + _.each(ContractArtifacts, (contractArtifactAsJson: any) => { + const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; + const contractName = conractArtifact.contractName; + const contractNameLowercase = _.toLower(contractName); + const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; + this.addABI(contractAbi, contractName, deployedContractInfoByName[contractNameLowercase]); + }); + } +} diff --git a/packages/utils/src/zeroex_transaction_decoder.ts b/packages/utils/src/zeroex_transaction_decoder.ts deleted file mode 100644 index f236257cb..000000000 --- a/packages/utils/src/zeroex_transaction_decoder.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; -import * as ContractArtifacts from '@0x/contract-artifacts'; -import { SimpleContractArtifact } from '@0x/types'; -import { AbiDefinition, ContractAbi } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { TransactionDecoder } from './transaction_decoder'; -import { DeployedContractInfo, DeployedContractInfoByName, TransactionData, TransactionProperties } from './types'; - -export class ZeroExTransactionDecoder extends TransactionDecoder { - private static _instance: ZeroExTransactionDecoder; - - public static addABI( - abiArray: AbiDefinition[], - contractName: string, - deploymentInfos?: DeployedContractInfo[], - ): void { - const instance = ZeroExTransactionDecoder._getInstance(); - instance.addABI(abiArray, contractName, deploymentInfos); - } - - public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { - const instance = ZeroExTransactionDecoder._getInstance(); - const decodedCalldata = instance.decode(calldata, txProperties); - return decodedCalldata; - } - - private static _getInstance(): ZeroExTransactionDecoder { - if (!ZeroExTransactionDecoder._instance) { - ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); - } - return ZeroExTransactionDecoder._instance; - } - - private constructor() { - super(); - // Load addresses by contract name - const deployedContractInfoByName: DeployedContractInfoByName = {}; - _.each(NetworkId, (networkId: any) => { - if (typeof networkId !== 'number') { - return; - } - const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkId); - _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { - const contractNameLowercase = _.toLower(contractName); - if (_.isUndefined(deployedContractInfoByName[contractNameLowercase])) { - deployedContractInfoByName[contractNameLowercase] = []; - } - deployedContractInfoByName[contractNameLowercase].push({ - contractAddress, - networkId, - }); - }); - }); - // Load contract artifacts - _.each(ContractArtifacts, (contractArtifactAsJson: any) => { - const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; - const contractName = conractArtifact.contractName; - const contractNameLowercase = _.toLower(contractName); - const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; - this.addABI(contractAbi, contractName, deployedContractInfoByName[contractNameLowercase]); - }); - } -} -- cgit v1.2.3 From 171618d32b649b976f8ecffa69cff3ef30fc7c7d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 14:07:09 -0800 Subject: Added comments for transaction decoder --- packages/contract-wrappers/src/index.ts | 1 + .../src/utils/zeroex_transaction_decoder.ts | 31 ++++++++++++--- packages/utils/src/transaction_decoder.ts | 46 ++++++++++++++++------ 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 69bbe3c91..269366896 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -37,6 +37,7 @@ export { OrderValidatorWrapper } from './contract_wrappers/order_validator_wrapp export { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper'; export { TransactionEncoder } from './utils/transaction_encoder'; +export { ZeroExTransactionDecoder } from './utils/zeroex_transaction_decoder'; export { ContractWrappersError, diff --git a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts index 441424e92..4a5a5809f 100644 --- a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts +++ b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts @@ -8,29 +8,48 @@ import { DeployedContractInfo, DeployedContractInfoByName, TransactionData, Tran export class ZeroExTransactionDecoder extends TransactionDecoder { private static _instance: ZeroExTransactionDecoder; - + /** + * Adds a set of ABI definitions, after which transaction data targeting these ABI's can be decoded. + * Additional properties can be included to disambiguate similar ABI's. For example, if two functions + * have the same signature but different parameter names, then their ABI definitions can be disambiguated + * by specifying a contract name. + * @param abiDefinitions ABI definitions for a given contract. + * @param contractName Name of contract that encapsulates the ABI definitions (optional). + * @param deploymentInfos A collection of network/address pairs where this contract is deployed (optional). + */ public static addABI( - abiArray: AbiDefinition[], + abiDefinitions: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[], ): void { const instance = ZeroExTransactionDecoder._getInstance(); - instance.addABI(abiArray, contractName, deploymentInfos); + instance.addABI(abiDefinitions, contractName, deploymentInfos); } - + /** + * Decodes transaction data for a known ABI. + * @param txData hex-encoded transaction data. + * @param txProperties Properties about the transaction used to disambiguate similar ABI's (optional). + * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. + */ public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { const instance = ZeroExTransactionDecoder._getInstance(); const decodedCalldata = instance.decode(calldata, txProperties); return decodedCalldata; } - + /** + * Gets instance for singleton. + * @return singleton instance. + */ private static _getInstance(): ZeroExTransactionDecoder { if (!ZeroExTransactionDecoder._instance) { ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); } return ZeroExTransactionDecoder._instance; } - + /** + * Adds all known contract ABI's defined by the @0x/Artifacts package, along with known 0x + * contract addresses. + */ private constructor() { super(); // Load addresses by contract name diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts index 35a889a4e..1ce2ea3b0 100644 --- a/packages/utils/src/transaction_decoder.ts +++ b/packages/utils/src/transaction_decoder.ts @@ -6,29 +6,43 @@ import { DeployedContractInfo, FunctionInfoBySelector, TransactionData, Transact export class TransactionDecoder { private readonly _functionInfoBySelector: FunctionInfoBySelector = {}; - - private static _getFunctionSelector(calldata: string): string { + /** + * Retrieves the function selector from tranasction data. + * @param txData hex-encoded transaction data. + * @return hex-encoded function selector. + */ + private static _getFunctionSelector(txData: string): string { const functionSelectorLength = 10; - if (!calldata.startsWith('0x') || calldata.length < functionSelectorLength) { + if (!txData.startsWith('0x') || txData.length < functionSelectorLength) { throw new Error( - `Malformed calldata. Must include hex prefix '0x' and 4-byte function selector. Got '${calldata}'`, + `Malformed transaction data. Must include a hex prefix '0x' and 4-byte function selector. Got '${txData}'`, ); } const functionSelector = calldata.substr(0, functionSelectorLength); return functionSelector; } - - public addABI(abiArray: AbiDefinition[], contractName: string, deploymentInfos?: DeployedContractInfo[]): void { - const functionAbis: MethodAbi[] = _.filter(abiArray, abiEntry => { + /** + * Adds a set of ABI definitions, after which transaction data targeting these ABI's can be decoded. + * Additional properties can be included to disambiguate similar ABI's. For example, if two functions + * have the same signature but different parameter names, then their ABI definitions can be disambiguated + * by specifying a contract name. + * @param abiDefinitions ABI definitions for a given contract. + * @param contractName Name of contract that encapsulates the ABI definitions (optional). + * @param deploymentInfos A collection of network/address pairs where this contract is deployed (optional). + */ + public addABI(abiDefinitions: AbiDefinition[], contractName?: string, deploymentInfos?: DeployedContractInfo[]): void { + // Disregard definitions that are not functions + const functionAbis: MethodAbi[] = _.filter(abiDefinitions, abiEntry => { return abiEntry.type === 'function'; }); + // Record function ABI's _.each(functionAbis, functionAbi => { const abiEncoder = new AbiEncoder.Method(functionAbi); const functionSelector = abiEncoder.getSelector(); if (!(functionSelector in this._functionInfoBySelector)) { this._functionInfoBySelector[functionSelector] = []; } - // Recored deployed versions of this decoder + // Recored a copy of this ABI for each deployment const functionSignature = abiEncoder.getSignature(); _.each(deploymentInfos, deploymentInfo => { this._functionInfoBySelector[functionSelector].push({ @@ -39,7 +53,7 @@ export class TransactionDecoder { networkId: deploymentInfo.networkId, }); }); - // If there isn't a deployed version of this contract, record it without address/network id + // There is no deployment info for this contract; record it without an address/network id if (_.isEmpty(deploymentInfos)) { this._functionInfoBySelector[functionSelector].push({ functionSignature, @@ -49,9 +63,15 @@ export class TransactionDecoder { } }); } - - public decode(calldata: string, txProperties_?: TransactionProperties): TransactionData { - const functionSelector = TransactionDecoder._getFunctionSelector(calldata); + /** + * Decodes transaction data for a known ABI. + * @param txData hex-encoded transaction data. + * @param txProperties Properties about the transaction used to disambiguate similar ABI's (optional). + * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. + */ + public decode(txData: string, txProperties_?: TransactionProperties): TransactionData { + // Lookup + const functionSelector = TransactionDecoder._getFunctionSelector(txData); const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; if (_.isUndefined(candidateFunctionInfos)) { @@ -75,7 +95,7 @@ export class TransactionDecoder { } const functionName = functionInfo.abiEncoder.getDataItem().name; const functionSignature = functionInfo.abiEncoder.getSignatureType(); - const functionArguments = functionInfo.abiEncoder.decode(calldata); + const functionArguments = functionInfo.abiEncoder.decode(txData); const decodedCalldata = { functionName, functionSignature, -- cgit v1.2.3 From 63f41df3272060bf44924c586fd882595428eff6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 14:07:46 -0800 Subject: started writing tests for zeroex tx decoder --- .../test/zeroex_transaction_decoder_test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts diff --git a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts new file mode 100644 index 000000000..7ac507076 --- /dev/null +++ b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts @@ -0,0 +1,20 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { ZeroExTransactionDecoder } from '../src'; +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe.only('ZeroExTransactionDecoder', () => { + const sampleMatchOrdersTxData = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; + + describe('decode', () => { + it('should successfully decode matchOrders txData', async () => { + const expected + const decodedTxData = ZeroExTransactionDecoder.decode(calldata, { contractName: 'Dutchauction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + console.log(decodedTxData); + }); + }); +}); -- cgit v1.2.3 From 5a231fb0575a00dfcf1237ec4e733cbeb96e984d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 31 Jan 2019 17:55:23 -0800 Subject: Prep for txData decoder tests --- .../src/utils/transaction_encoder.ts | 17 +++++ .../test/zeroex_transaction_decoder_test.ts | 88 +++++++++++++++++++++- packages/utils/src/address_utils.ts | 25 +++++- packages/utils/src/index.ts | 1 - packages/utils/src/transaction_decoder.ts | 6 +- packages/utils/test/transaction_decoder_test.ts | 13 +--- 6 files changed, 130 insertions(+), 20 deletions(-) diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts index 307487a9b..5faa593a5 100644 --- a/packages/contract-wrappers/src/utils/transaction_encoder.ts +++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts @@ -241,6 +241,23 @@ export class TransactionEncoder { ); return abiEncodedData; } + /** + * Encodes a matchOrders transaction. + * @param leftOrder + * @param rightOrder + * @return Hex encoded abi of the function call. + */ + public matchOrdersTx(leftOrder: SignedOrder, rightOrder: SignedOrder): string { + assert.doesConformToSchema('order', leftOrder, schemas.orderSchema); + assert.doesConformToSchema('order', rightOrder, schemas.orderSchema); + const abiEncodedData = this._getExchangeContract().matchOrders.getABIEncodedTransactionData( + leftOrder, + rightOrder, + leftOrder.signature, + rightOrder.signature, + ); + return abiEncodedData; + } /** * Encodes a preSign transaction. * @param hash Hash to pre-sign diff --git a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts index 7ac507076..e4fbb8b99 100644 --- a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts +++ b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts @@ -1,20 +1,100 @@ +import { assetDataUtils } from '@0x/order-utils'; import * as chai from 'chai'; import 'mocha'; +import * as _ from 'lodash'; +import { addressUtils, BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; -import { ZeroExTransactionDecoder } from '../src'; +import { + constants, + OrderFactory, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { SignedOrder } from '@0x/types'; + +import { TransactionEncoder, ZeroExTransactionDecoder } from '../src'; import { chaiSetup } from './utils/chai_setup'; chaiSetup.configure(); const expect = chai.expect; describe.only('ZeroExTransactionDecoder', () => { + let orderFactory: OrderFactory; + let defaultERC20MakerAssetAddress = addressUtils.generatePseudoRandomAddress(); + let defaultERC20TakerAssetAddress = addressUtils.generatePseudoRandomAddress(); + let signedOrderLeft: SignedOrder; + let signedOrderRifght: SignedOrder; + let matchOrdersTxData: string; const sampleMatchOrdersTxData = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; + before(async () => { + // Create accounts + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + + let makerAddressLeft = addressUtils.generatePseudoRandomAddress(); + let makerAddressRight = addressUtils.generatePseudoRandomAddress(); + const exchangeAddress = addressUtils.generatePseudoRandomAddress(); + const feeRecipientAddress = addressUtils.generatePseudoRandomAddress();; + // Create orders to match + const defaultOrderParamsLeft = { + makerAddress: makerAddressLeft, + exchangeAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + feeRecipientAddress, + }; + const defaultOrderParamsRight = { + makerAddress: makerAddressRight, + exchangeAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + feeRecipientAddress, + }; + + const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; + const orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); + const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; + const orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); + + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: new BigNumber(1), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: new BigNumber(1), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), + }); + + + //matchOrdersTxData = TransactionEncoder.matchOrdersTx(signedOrderLeft, signedOrderRight); + }); + describe('decode', () => { - it('should successfully decode matchOrders txData', async () => { - const expected - const decodedTxData = ZeroExTransactionDecoder.decode(calldata, { contractName: 'Dutchauction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + /* + it('should successfully decode DutchAuction.matchOrders txData', async () => { + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName: 'DutchAuction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); console.log(decodedTxData); }); + it('should successfully decode Exchange.matchOrders txData', async () => { + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName: 'Exchange' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + console.log(decodedTxData); + }); + it('should successfully decode Exchange.matchOrders, using exchange address to identify the exchange contract', async () => { + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + console.log(decodedTxData); + }); + it('should throw if cannot decode txData', async () => { + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + console.log(decodedTxData); + });*/ + }); + + describe('addABI', () => { + /*it('should successfully add a new ABI', async () => { + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName: 'DutchAuction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); + console.log(decodedTxData); + });*/ }); }); diff --git a/packages/utils/src/address_utils.ts b/packages/utils/src/address_utils.ts index 1fc960408..318504c37 100644 --- a/packages/utils/src/address_utils.ts +++ b/packages/utils/src/address_utils.ts @@ -1,10 +1,13 @@ -import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util'; +import { addHexPrefix, stripHexPrefix, sha3 } from 'ethereumjs-util'; import * as jsSHA3 from 'js-sha3'; import * as _ from 'lodash'; +import { BigNumber } from './configured_bignumber'; + const BASIC_ADDRESS_REGEX = /^(0x)?[0-9a-f]{40}$/i; const SAME_CASE_ADDRESS_REGEX = /^(0x)?([0-9a-f]{40}|[0-9A-F]{40})$/; const ADDRESS_LENGTH = 40; +const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export const addressUtils = { isChecksumAddress(address: string): boolean { @@ -43,4 +46,24 @@ export const addressUtils = { padZeros(address: string): string { return addHexPrefix(_.padStart(stripHexPrefix(address), ADDRESS_LENGTH, '0')); }, + /** + * Generates a pseudo-random 256-bit salt. + * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash + * and will not collide with other outstanding orders that are identical in all other parameters. + * @return A pseudo-random 256-bit number that can be used as a salt. + */ + generatePseudoRandomSalt(): BigNumber { + // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. + // Source: https://mikemcl.github.io/bignumber.js/#random + const randomNumber = BigNumber.random(MAX_DIGITS_IN_UNSIGNED_256_INT); + const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1); + const salt = randomNumber.times(factor); + return salt; + }, + generatePseudoRandomAddress(): string { + const randomBigNum = addressUtils.generatePseudoRandomSalt(); + const randomBuff = sha3(randomBigNum.toString()); + const randomAddress = `0x${randomBuff.slice(0, 20).toString('hex')}`; + return randomAddress; + } }; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index c0f15f2ab..6f1c14c83 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -13,4 +13,3 @@ export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); export * from './types'; export { TransactionDecoder } from './transaction_decoder'; -export { ZeroExTransactionDecoder } from './zeroex_transaction_decoder'; diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts index 1ce2ea3b0..2c3b96c72 100644 --- a/packages/utils/src/transaction_decoder.ts +++ b/packages/utils/src/transaction_decoder.ts @@ -18,7 +18,7 @@ export class TransactionDecoder { `Malformed transaction data. Must include a hex prefix '0x' and 4-byte function selector. Got '${txData}'`, ); } - const functionSelector = calldata.substr(0, functionSelectorLength); + const functionSelector = txData.substr(0, functionSelectorLength); return functionSelector; } /** @@ -32,9 +32,9 @@ export class TransactionDecoder { */ public addABI(abiDefinitions: AbiDefinition[], contractName?: string, deploymentInfos?: DeployedContractInfo[]): void { // Disregard definitions that are not functions - const functionAbis: MethodAbi[] = _.filter(abiDefinitions, abiEntry => { + const functionAbis = _.filter(abiDefinitions, abiEntry => { return abiEntry.type === 'function'; - }); + }) as MethodAbi[]; // Record function ABI's _.each(functionAbis, functionAbi => { const abiEncoder = new AbiEncoder.Method(functionAbi); diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts index eee712e98..f214b1733 100644 --- a/packages/utils/test/transaction_decoder_test.ts +++ b/packages/utils/test/transaction_decoder_test.ts @@ -1,21 +1,12 @@ import * as chai from 'chai'; import 'mocha'; -import { ZeroExTransactionDecoder } from '../src'; + import { chaiSetup } from './utils/chai_setup'; chaiSetup.configure(); const expect = chai.expect; describe.only('TransactionDecoder', () => { - describe('decode', () => { - it('should successfull decode fillOrder calldata', async () => { - //const cancelCalldata = '0xd46b02c3000000000000000000000000000000000000000000000000000000000000002000000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a258b39954cef5cb142fd567a46cddb31a6701240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071d75ab9b9204fffc40000000000000000000000000000000000000000000000011c6e19c53d35b66200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c50f2ed000000000000000000000000000000000000000000000000000001689c2bc812000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000'; - //const marketBuycalldata = '0xe5fa431b0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000012309ce5400000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c26348f63f9e008f0dd09a0ce1ed7caf6c1366b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e150a33ffa97a8d22f59c77ae5487b089ef62e90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000001323e717ba3800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ea9bd19a0c4b5533ac98f58db0558a96e15ec5f71d64b6070cea4b5df10b7fb35424035000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000006cb262679c522c4f0834041a6248e8feb35f0337000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000421c750cedbf0eef0914c09b296f08462c363527f454bcf2dfaaf2f772e290d0ee5b0417d8b95837cbe501494195edc2a5a48c664d2ef74a340e40213c05db8767fa03000000000000000000000000000000000000000000000000000000000000'; - const calldata = - '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; - const decodedTxData = ZeroExTransactionDecoder.decode(calldata, { contractName: 'Dutchauction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(decodedTxData); - }); - }); + }); -- cgit v1.2.3 From d9c4c74a56c913e90f3c76566c64950fec86063b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 6 Feb 2019 17:02:47 -0800 Subject: Added tests for ZeroExTransactionDecoder --- .../src/utils/transaction_encoder.ts | 4 +- .../src/utils/zeroex_transaction_decoder.ts | 8 +- .../test/zeroex_transaction_decoder_test.ts | 210 +++++++++++++++------ packages/utils/src/address_utils.ts | 2 +- packages/utils/src/transaction_decoder.ts | 8 +- packages/utils/test/transaction_decoder_test.ts | 5 +- 6 files changed, 167 insertions(+), 70 deletions(-) diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts index 5faa593a5..8bf67ee56 100644 --- a/packages/contract-wrappers/src/utils/transaction_encoder.ts +++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts @@ -243,8 +243,8 @@ export class TransactionEncoder { } /** * Encodes a matchOrders transaction. - * @param leftOrder - * @param rightOrder + * @param leftOrder First order to match. + * @param rightOrder Second order to match. * @return Hex encoded abi of the function call. */ public matchOrdersTx(leftOrder: SignedOrder, rightOrder: SignedOrder): string { diff --git a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts index 4a5a5809f..17f06497c 100644 --- a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts +++ b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts @@ -4,7 +4,13 @@ import { SimpleContractArtifact } from '@0x/types'; import { AbiDefinition, ContractAbi } from 'ethereum-types'; import * as _ from 'lodash'; -import { DeployedContractInfo, DeployedContractInfoByName, TransactionData, TransactionDecoder, TransactionProperties } from '@0x/utils'; +import { + DeployedContractInfo, + DeployedContractInfoByName, + TransactionData, + TransactionDecoder, + TransactionProperties, +} from '@0x/utils'; export class ZeroExTransactionDecoder extends TransactionDecoder { private static _instance: ZeroExTransactionDecoder; diff --git a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts index e4fbb8b99..83c810c3a 100644 --- a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts +++ b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts @@ -1,100 +1,190 @@ +import { constants, OrderFactory } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; +import { SignedOrder } from '@0x/types'; +import { AbiEncoder, addressUtils, BigNumber } from '@0x/utils'; import * as chai from 'chai'; -import 'mocha'; +import { MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; -import { addressUtils, BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; +import 'mocha'; -import { - constants, - OrderFactory, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { SignedOrder } from '@0x/types'; +import { ContractAddresses, ContractWrappers } from '../src'; +import { ZeroExTransactionDecoder } from '../src/utils/zeroex_transaction_decoder'; -import { TransactionEncoder, ZeroExTransactionDecoder } from '../src'; import { chaiSetup } from './utils/chai_setup'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + describe.only('ZeroExTransactionDecoder', () => { - let orderFactory: OrderFactory; - let defaultERC20MakerAssetAddress = addressUtils.generatePseudoRandomAddress(); - let defaultERC20TakerAssetAddress = addressUtils.generatePseudoRandomAddress(); + const defaultERC20MakerAssetAddress = addressUtils.generatePseudoRandomAddress(); + const matchOrdersSignature = + 'matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)'; let signedOrderLeft: SignedOrder; - let signedOrderRifght: SignedOrder; + let signedOrderRight: SignedOrder; + let orderLeft = {}; + let orderRight = {}; let matchOrdersTxData: string; - const sampleMatchOrdersTxData = '0x3c28d861000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000500000000000000000000000000da912ecc847b3d98ca882e396e693e485deed5180000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008bb6a7394e2f000000000000000000000000000000000000000000000000868cab59cce788000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c51035008197e43b4d84439ec534b62670eaaaf4a46f50ff37ff62f6d1c1fbe8b036d3c000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b0000000000000000000000000503f9794d6a6bb0df8fbb19a2b3e2aeab35339ad000000000000000000000000000000000000000000000000000000000000000000000000000000003997d0f55d1daa549e95c240bc6353636f4cf9740000000000000000000000000681e844593a051e2882ec897ecd5444efe19ff20000000000000000000000008124071f810d533ff63de61d0c98db99eeb99d6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000871bcc4c32c9d66800000000000000000000000000000000000000000000000000008a70a4d2d2100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c510350c20e53540c9b2c9207ad9a04e472e2224af211f08efc2f0eec15d7e1cfbf2109000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c8f294b2728c269a9d01a1b58fe7cae2ef7895bd2de48cc3101eb47464d96594340924793fc8325db26a3abd5602605806a82ca77e810494c5ecab58b03449de80300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421c372d6daa8e6ce2c696e51b6e1e33f10fd2b41b403cd88c311a617c3656ea02fe454e51cddf4682751bea9a02ce725cf364d1107f27be427d5157adbdcca2609b03000000000000000000000000000000000000000000000000000000000000'; + let contractAddresses: ContractAddresses; before(async () => { // Create accounts const accounts = await web3Wrapper.getAvailableAddressesAsync(); - - let makerAddressLeft = addressUtils.generatePseudoRandomAddress(); - let makerAddressRight = addressUtils.generatePseudoRandomAddress(); + const [makerAddressLeft, makerAddressRight] = accounts.slice(0, 2); const exchangeAddress = addressUtils.generatePseudoRandomAddress(); - const feeRecipientAddress = addressUtils.generatePseudoRandomAddress();; - // Create orders to match - const defaultOrderParamsLeft = { + const feeRecipientAddress = addressUtils.generatePseudoRandomAddress(); + const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; + const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; + // Create orders to match + orderLeft = { makerAddress: makerAddressLeft, - exchangeAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + makerAssetAmount: new BigNumber(10), + takerAddress: '0x0000000000000000000000000000000000000000', + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetAmount: new BigNumber(1), feeRecipientAddress, + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + senderAddress: '0x0000000000000000000000000000000000000000', + expirationTimeSeconds: new BigNumber(1549498915), + salt: new BigNumber(217), }; - const defaultOrderParamsRight = { + orderRight = { makerAddress: makerAddressRight, - exchangeAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: new BigNumber(1), + takerAddress: '0x0000000000000000000000000000000000000000', takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetAmount: new BigNumber(8), feeRecipientAddress, + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + senderAddress: '0x0000000000000000000000000000000000000000', + expirationTimeSeconds: new BigNumber(1549498915), + salt: new BigNumber(50010), }; - - const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; - const orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); - const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; - const orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); - - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: new BigNumber(1), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), - }); - - - //matchOrdersTxData = TransactionEncoder.matchOrdersTx(signedOrderLeft, signedOrderRight); + const orderFactoryLeft = new OrderFactory(privateKeyLeft, orderLeft); + signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ exchangeAddress }); + const orderFactoryRight = new OrderFactory(privateKeyRight, orderRight); + signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ exchangeAddress }); + // Encode match orders transaction + contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + contractAddresses, + blockPollingIntervalMs: 10, + }; + const contractWrappers = new ContractWrappers(provider, config); + const transactionEncoder = await contractWrappers.exchange.transactionEncoderAsync(); + matchOrdersTxData = transactionEncoder.matchOrdersTx(signedOrderLeft, signedOrderRight); }); describe('decode', () => { - /* it('should successfully decode DutchAuction.matchOrders txData', async () => { - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName: 'DutchAuction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(decodedTxData); + const contractName = 'DutchAuction'; + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName }); + const expectedFunctionName = 'matchOrders'; + const expectedFunctionArguments = { + buyOrder: orderLeft, + sellOrder: orderRight, + buySignature: signedOrderLeft.signature, + sellSignature: signedOrderRight.signature, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); }); - it('should successfully decode Exchange.matchOrders txData', async () => { - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName: 'Exchange' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(decodedTxData); + it('should successfully decode Exchange.matchOrders txData (and distinguish from DutchAuction.matchOrders)', async () => { + const contractName = 'Exchange'; + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName }); + const expectedFunctionName = 'matchOrders'; + const expectedFunctionArguments = { + leftOrder: orderLeft, + rightOrder: orderRight, + leftSignature: signedOrderLeft.signature, + rightSignature: signedOrderRight.signature, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); }); it('should successfully decode Exchange.matchOrders, using exchange address to identify the exchange contract', async () => { - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(decodedTxData); + const contractAddress = contractAddresses.exchange; + const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractAddress }); + const expectedFunctionName = 'matchOrders'; + const expectedFunctionArguments = { + leftOrder: orderLeft, + rightOrder: orderRight, + leftSignature: signedOrderLeft.signature, + rightSignature: signedOrderRight.signature, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); }); it('should throw if cannot decode txData', async () => { - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, {networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(decodedTxData); - });*/ + const contractAddress = contractAddresses.exchange; + const badTxData = '0x01020304'; + expect(() => { + ZeroExTransactionDecoder.decode(badTxData, { contractAddress }); + }).to.throw("No functions registered for selector '0x01020304'"); + }); }); describe('addABI', () => { - /*it('should successfully add a new ABI', async () => { - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName: 'DutchAuction' }); //{networkId: 1, contractAddress: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b'}); - console.log(decodedTxData); - });*/ + it('should successfully add a new ABI', async () => { + // Add new ABI + const abi: MethodAbi = { + name: 'foobar', + type: 'function', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [ + { + name: 'butter', + type: 'string', + }, + ], + constant: false, + payable: false, + stateMutability: 'pure', + }; + const contractName = 'newContract'; + const contractAddress = addressUtils.generatePseudoRandomAddress(); + const networkId = 1; + const contractInfo = [ + { + contractAddress, + networkId, + }, + ]; + ZeroExTransactionDecoder.addABI([abi], contractName, contractInfo); + // Create some tx data + const foobarEncoder = new AbiEncoder.Method(abi); + const foobarSignature = foobarEncoder.getSignature(); + const foobarTxData = foobarEncoder.encode([contractAddress]); + // Decode tx data using contract name + const decodedTxData = ZeroExTransactionDecoder.decode(foobarTxData, { contractName }); + const expectedFunctionName = abi.name; + const expectedFunctionArguments = { + addr: contractAddress, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(foobarSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); + // Decode tx data using contract address + const decodedTxDataDecodedWithAddress = ZeroExTransactionDecoder.decode(foobarTxData, { contractAddress }); + expect(decodedTxDataDecodedWithAddress).to.be.deep.equal(decodedTxData); + }); }); }); diff --git a/packages/utils/src/address_utils.ts b/packages/utils/src/address_utils.ts index 318504c37..b700cd944 100644 --- a/packages/utils/src/address_utils.ts +++ b/packages/utils/src/address_utils.ts @@ -65,5 +65,5 @@ export const addressUtils = { const randomBuff = sha3(randomBigNum.toString()); const randomAddress = `0x${randomBuff.slice(0, 20).toString('hex')}`; return randomAddress; - } + }, }; diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts index 2c3b96c72..dd1b4d19a 100644 --- a/packages/utils/src/transaction_decoder.ts +++ b/packages/utils/src/transaction_decoder.ts @@ -30,7 +30,11 @@ export class TransactionDecoder { * @param contractName Name of contract that encapsulates the ABI definitions (optional). * @param deploymentInfos A collection of network/address pairs where this contract is deployed (optional). */ - public addABI(abiDefinitions: AbiDefinition[], contractName?: string, deploymentInfos?: DeployedContractInfo[]): void { + public addABI( + abiDefinitions: AbiDefinition[], + contractName?: string, + deploymentInfos?: DeployedContractInfo[], + ): void { // Disregard definitions that are not functions const functionAbis = _.filter(abiDefinitions, abiEntry => { return abiEntry.type === 'function'; @@ -70,7 +74,7 @@ export class TransactionDecoder { * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. */ public decode(txData: string, txProperties_?: TransactionProperties): TransactionData { - // Lookup + // Lookup const functionSelector = TransactionDecoder._getFunctionSelector(txData); const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts index f214b1733..bc40f4840 100644 --- a/packages/utils/test/transaction_decoder_test.ts +++ b/packages/utils/test/transaction_decoder_test.ts @@ -1,12 +1,9 @@ import * as chai from 'chai'; import 'mocha'; - import { chaiSetup } from './utils/chai_setup'; chaiSetup.configure(); const expect = chai.expect; -describe.only('TransactionDecoder', () => { - -}); +describe.only('TransactionDecoder', () => {}); -- cgit v1.2.3 From 3d2babd059551fc0e1a571d3cf1cb544ec85f52c Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 6 Feb 2019 17:21:19 -0800 Subject: Tests for transaction decoder --- packages/utils/test/transaction_decoder_test.ts | 54 ++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts index bc40f4840..3845bb134 100644 --- a/packages/utils/test/transaction_decoder_test.ts +++ b/packages/utils/test/transaction_decoder_test.ts @@ -1,9 +1,61 @@ import * as chai from 'chai'; +import { MethodAbi } from 'ethereum-types'; import 'mocha'; import { chaiSetup } from './utils/chai_setup'; +import { AbiEncoder, TransactionDecoder } from '../src'; chaiSetup.configure(); const expect = chai.expect; -describe.only('TransactionDecoder', () => {}); +describe('TransactionDecoder', () => { + it('should successfully add a new ABI and decode tx data for it', async () => { + // Add new ABI + const abi: MethodAbi = { + name: 'foobar', + type: 'function', + inputs: [ + { + name: 'addr', + type: 'address', + }, + ], + outputs: [ + { + name: 'butter', + type: 'string', + }, + ], + constant: false, + payable: false, + stateMutability: 'pure', + }; + const contractName = 'newContract'; + const contractAddress = '0x0001020304050607080900010203040506070809'; + const networkId = 1; + const contractInfo = [ + { + contractAddress, + networkId, + }, + ]; + const transactionDecoder = new TransactionDecoder(); + transactionDecoder.addABI([abi], contractName, contractInfo); + // Create some tx data + const foobarEncoder = new AbiEncoder.Method(abi); + const foobarSignature = foobarEncoder.getSignature(); + const foobarTxData = foobarEncoder.encode([contractAddress]); + // Decode tx data using contract name + const decodedTxData = transactionDecoder.decode(foobarTxData, { contractName }); + const expectedFunctionName = abi.name; + const expectedFunctionArguments = { + addr: contractAddress, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(foobarSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); + // Decode tx data using contract address + const decodedTxDataDecodedWithAddress = transactionDecoder.decode(foobarTxData, { contractAddress }); + expect(decodedTxDataDecodedWithAddress).to.be.deep.equal(decodedTxData); + }); +}); -- cgit v1.2.3 From 831a628379aa444ffe944b4f73913b59cb300d76 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 6 Feb 2019 17:42:54 -0800 Subject: ran linter --- packages/utils/src/address_utils.ts | 5 +++-- packages/utils/src/transaction_decoder.ts | 2 ++ packages/utils/test/transaction_decoder_test.ts | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/address_utils.ts b/packages/utils/src/address_utils.ts index b700cd944..b269c26b4 100644 --- a/packages/utils/src/address_utils.ts +++ b/packages/utils/src/address_utils.ts @@ -1,4 +1,4 @@ -import { addHexPrefix, stripHexPrefix, sha3 } from 'ethereumjs-util'; +import { addHexPrefix, sha3, stripHexPrefix } from 'ethereumjs-util'; import * as jsSHA3 from 'js-sha3'; import * as _ from 'lodash'; @@ -63,7 +63,8 @@ export const addressUtils = { generatePseudoRandomAddress(): string { const randomBigNum = addressUtils.generatePseudoRandomSalt(); const randomBuff = sha3(randomBigNum.toString()); - const randomAddress = `0x${randomBuff.slice(0, 20).toString('hex')}`; + const addressLengthInBytes = 20; + const randomAddress = `0x${randomBuff.slice(0, addressLengthInBytes).toString('hex')}`; return randomAddress; }, }; diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts index dd1b4d19a..9d567286e 100644 --- a/packages/utils/src/transaction_decoder.ts +++ b/packages/utils/src/transaction_decoder.ts @@ -36,9 +36,11 @@ export class TransactionDecoder { deploymentInfos?: DeployedContractInfo[], ): void { // Disregard definitions that are not functions + // tslint:disable no-unnecessary-type-assertion const functionAbis = _.filter(abiDefinitions, abiEntry => { return abiEntry.type === 'function'; }) as MethodAbi[]; + // tslint:enable no-unnecessary-type-assertion // Record function ABI's _.each(functionAbis, functionAbi => { const abiEncoder = new AbiEncoder.Method(functionAbi); diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts index 3845bb134..725ad5032 100644 --- a/packages/utils/test/transaction_decoder_test.ts +++ b/packages/utils/test/transaction_decoder_test.ts @@ -2,9 +2,10 @@ import * as chai from 'chai'; import { MethodAbi } from 'ethereum-types'; import 'mocha'; -import { chaiSetup } from './utils/chai_setup'; import { AbiEncoder, TransactionDecoder } from '../src'; +import { chaiSetup } from './utils/chai_setup'; + chaiSetup.configure(); const expect = chai.expect; -- cgit v1.2.3 From 6bde77bb57e1d0526154cc6b2b3bb0ff70c1d2b0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 6 Feb 2019 17:50:37 -0800 Subject: fix doc generation in contract-wrappers --- packages/contract-wrappers/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 269366896..fd4b9d357 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -72,6 +72,12 @@ export { AssetProxyId, } from '@0x/types'; +export { + DeployedContractInfo, + TransactionData, + TransactionProperties +} from '@0x/utils'; + export { BlockParamLiteral, BlockParam, -- cgit v1.2.3 From 6406126ae356a6dd1102cf603d4ce00333c9fd62 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 6 Feb 2019 23:47:40 -0800 Subject: Merged tx decoder into AbiDecoder in utils and merged zeroex tx decoder into ContractWrappers. --- .../contract-wrappers/src/contract_wrappers.ts | 12 +- packages/contract-wrappers/src/index.ts | 7 - .../src/utils/zeroex_transaction_decoder.ts | 88 ---------- .../test/calldata_decoder_test.ts | 123 +++++++++++++ .../test/zeroex_transaction_decoder_test.ts | 190 --------------------- packages/utils/CHANGELOG.json | 9 + packages/utils/src/abi_decoder.ts | 121 +++++++++++-- packages/utils/src/index.ts | 1 - packages/utils/src/transaction_decoder.ts | 112 ------------ packages/utils/src/types.ts | 15 -- packages/utils/test/abi_decoder_test.ts | 50 ++++++ packages/utils/test/transaction_decoder_test.ts | 62 ------- 12 files changed, 298 insertions(+), 492 deletions(-) delete mode 100644 packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts create mode 100644 packages/contract-wrappers/test/calldata_decoder_test.ts delete mode 100644 packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts delete mode 100644 packages/utils/src/transaction_decoder.ts create mode 100644 packages/utils/test/abi_decoder_test.ts delete mode 100644 packages/utils/test/transaction_decoder_test.ts diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 4e594593e..63de61a47 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -1,4 +1,5 @@ import { + DutchAuction, ERC20Proxy, ERC20Token, ERC721Proxy, @@ -8,6 +9,7 @@ import { OrderValidator, WETH9, } from '@0x/contract-artifacts'; +import { AbiDecoder } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; @@ -87,6 +89,7 @@ export class ContractWrappers { }; this._web3Wrapper = new Web3Wrapper(provider, txDefaults); const artifactsArray = [ + DutchAuction, ERC20Proxy, ERC20Token, ERC721Proxy, @@ -97,7 +100,7 @@ export class ContractWrappers { WETH9, ]; _.forEach(artifactsArray, artifact => { - this._web3Wrapper.abiDecoder.addABI(artifact.compilerOutput.abi); + this._web3Wrapper.abiDecoder.addABI(artifact.compilerOutput.abi, artifact.contractName); }); const blockPollingIntervalMs = _.isUndefined(config.blockPollingIntervalMs) ? constants.DEFAULT_BLOCK_POLLING_INTERVAL @@ -168,4 +171,11 @@ export class ContractWrappers { public getProvider(): Provider { return this._web3Wrapper.getProvider(); } + /** + * Get the provider instance currently used by contract-wrappers + * @return Web3 provider instance + */ + public getAbiDecoder(): AbiDecoder { + return this._web3Wrapper.abiDecoder; + } } diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index fd4b9d357..69bbe3c91 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -37,7 +37,6 @@ export { OrderValidatorWrapper } from './contract_wrappers/order_validator_wrapp export { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper'; export { TransactionEncoder } from './utils/transaction_encoder'; -export { ZeroExTransactionDecoder } from './utils/zeroex_transaction_decoder'; export { ContractWrappersError, @@ -72,12 +71,6 @@ export { AssetProxyId, } from '@0x/types'; -export { - DeployedContractInfo, - TransactionData, - TransactionProperties -} from '@0x/utils'; - export { BlockParamLiteral, BlockParam, diff --git a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts b/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts deleted file mode 100644 index 17f06497c..000000000 --- a/packages/contract-wrappers/src/utils/zeroex_transaction_decoder.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; -import * as ContractArtifacts from '@0x/contract-artifacts'; -import { SimpleContractArtifact } from '@0x/types'; -import { AbiDefinition, ContractAbi } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { - DeployedContractInfo, - DeployedContractInfoByName, - TransactionData, - TransactionDecoder, - TransactionProperties, -} from '@0x/utils'; - -export class ZeroExTransactionDecoder extends TransactionDecoder { - private static _instance: ZeroExTransactionDecoder; - /** - * Adds a set of ABI definitions, after which transaction data targeting these ABI's can be decoded. - * Additional properties can be included to disambiguate similar ABI's. For example, if two functions - * have the same signature but different parameter names, then their ABI definitions can be disambiguated - * by specifying a contract name. - * @param abiDefinitions ABI definitions for a given contract. - * @param contractName Name of contract that encapsulates the ABI definitions (optional). - * @param deploymentInfos A collection of network/address pairs where this contract is deployed (optional). - */ - public static addABI( - abiDefinitions: AbiDefinition[], - contractName: string, - deploymentInfos?: DeployedContractInfo[], - ): void { - const instance = ZeroExTransactionDecoder._getInstance(); - instance.addABI(abiDefinitions, contractName, deploymentInfos); - } - /** - * Decodes transaction data for a known ABI. - * @param txData hex-encoded transaction data. - * @param txProperties Properties about the transaction used to disambiguate similar ABI's (optional). - * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. - */ - public static decode(calldata: string, txProperties?: TransactionProperties): TransactionData { - const instance = ZeroExTransactionDecoder._getInstance(); - const decodedCalldata = instance.decode(calldata, txProperties); - return decodedCalldata; - } - /** - * Gets instance for singleton. - * @return singleton instance. - */ - private static _getInstance(): ZeroExTransactionDecoder { - if (!ZeroExTransactionDecoder._instance) { - ZeroExTransactionDecoder._instance = new ZeroExTransactionDecoder(); - } - return ZeroExTransactionDecoder._instance; - } - /** - * Adds all known contract ABI's defined by the @0x/Artifacts package, along with known 0x - * contract addresses. - */ - private constructor() { - super(); - // Load addresses by contract name - const deployedContractInfoByName: DeployedContractInfoByName = {}; - _.each(NetworkId, (networkId: any) => { - if (typeof networkId !== 'number') { - return; - } - const contractAddressesForNetwork = getContractAddressesForNetworkOrThrow(networkId); - _.each(contractAddressesForNetwork, (contractAddress: string, contractName: string) => { - const contractNameLowercase = _.toLower(contractName); - if (_.isUndefined(deployedContractInfoByName[contractNameLowercase])) { - deployedContractInfoByName[contractNameLowercase] = []; - } - deployedContractInfoByName[contractNameLowercase].push({ - contractAddress, - networkId, - }); - }); - }); - // Load contract artifacts - _.each(ContractArtifacts, (contractArtifactAsJson: any) => { - const conractArtifact = contractArtifactAsJson as SimpleContractArtifact; - const contractName = conractArtifact.contractName; - const contractNameLowercase = _.toLower(contractName); - const contractAbi: ContractAbi = conractArtifact.compilerOutput.abi; - this.addABI(contractAbi, contractName, deployedContractInfoByName[contractNameLowercase]); - }); - } -} diff --git a/packages/contract-wrappers/test/calldata_decoder_test.ts b/packages/contract-wrappers/test/calldata_decoder_test.ts new file mode 100644 index 000000000..d44e13a89 --- /dev/null +++ b/packages/contract-wrappers/test/calldata_decoder_test.ts @@ -0,0 +1,123 @@ +import { constants, OrderFactory } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { SignedOrder } from '@0x/types'; +import { addressUtils, BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { ContractAddresses, ContractWrappers } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('ABI Decoding Calldata', () => { + const defaultERC20MakerAssetAddress = addressUtils.generatePseudoRandomAddress(); + const matchOrdersSignature = + 'matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)'; + let signedOrderLeft: SignedOrder; + let signedOrderRight: SignedOrder; + let orderLeft = {}; + let orderRight = {}; + let matchOrdersTxData: string; + let contractAddresses: ContractAddresses; + let contractWrappers: ContractWrappers; + + before(async () => { + // Create accounts + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const [makerAddressLeft, makerAddressRight] = accounts.slice(0, 2); + const exchangeAddress = addressUtils.generatePseudoRandomAddress(); + const feeRecipientAddress = addressUtils.generatePseudoRandomAddress(); + const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; + const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; + // Create orders to match + orderLeft = { + makerAddress: makerAddressLeft, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: new BigNumber(10), + takerAddress: '0x0000000000000000000000000000000000000000', + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetAmount: new BigNumber(1), + feeRecipientAddress, + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + senderAddress: '0x0000000000000000000000000000000000000000', + expirationTimeSeconds: new BigNumber(1549498915), + salt: new BigNumber(217), + }; + orderRight = { + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: new BigNumber(1), + takerAddress: '0x0000000000000000000000000000000000000000', + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetAmount: new BigNumber(8), + feeRecipientAddress, + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + senderAddress: '0x0000000000000000000000000000000000000000', + expirationTimeSeconds: new BigNumber(1549498915), + salt: new BigNumber(50010), + }; + const orderFactoryLeft = new OrderFactory(privateKeyLeft, orderLeft); + signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ exchangeAddress }); + const orderFactoryRight = new OrderFactory(privateKeyRight, orderRight); + signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ exchangeAddress }); + // Encode match orders transaction + contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + contractAddresses, + blockPollingIntervalMs: 10, + }; + contractWrappers = new ContractWrappers(provider, config); + const transactionEncoder = await contractWrappers.exchange.transactionEncoderAsync(); + matchOrdersTxData = transactionEncoder.matchOrdersTx(signedOrderLeft, signedOrderRight); + }); + + describe('decode', () => { + it('should successfully decode DutchAuction.matchOrders calldata', async () => { + const contractName = 'DutchAuction'; + const decodedTxData = contractWrappers.getAbiDecoder().tryDecodeCalldata(matchOrdersTxData, contractName); + const expectedFunctionName = 'matchOrders'; + const expectedFunctionArguments = { + buyOrder: orderLeft, + sellOrder: orderRight, + buySignature: signedOrderLeft.signature, + sellSignature: signedOrderRight.signature, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); + }); + it('should successfully decode Exchange.matchOrders calldata (and distinguish from DutchAuction.matchOrders)', async () => { + const contractName = 'Exchange'; + const decodedTxData = contractWrappers.getAbiDecoder().tryDecodeCalldata(matchOrdersTxData, contractName); + const expectedFunctionName = 'matchOrders'; + const expectedFunctionArguments = { + leftOrder: orderLeft, + rightOrder: orderRight, + leftSignature: signedOrderLeft.signature, + rightSignature: signedOrderRight.signature, + }; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); + }); + it('should throw if cannot decode calldata', async () => { + const badTxData = '0x01020304'; + expect(() => { + contractWrappers.getAbiDecoder().tryDecodeCalldata(badTxData); + }).to.throw("No functions registered for selector '0x01020304'"); + }); + }); +}); diff --git a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts b/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts deleted file mode 100644 index 83c810c3a..000000000 --- a/packages/contract-wrappers/test/zeroex_transaction_decoder_test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { constants, OrderFactory } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { SignedOrder } from '@0x/types'; -import { AbiEncoder, addressUtils, BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import { MethodAbi } from 'ethereum-types'; -import * as _ from 'lodash'; -import 'mocha'; - -import { ContractAddresses, ContractWrappers } from '../src'; -import { ZeroExTransactionDecoder } from '../src/utils/zeroex_transaction_decoder'; - -import { chaiSetup } from './utils/chai_setup'; -import { migrateOnceAsync } from './utils/migrate'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe.only('ZeroExTransactionDecoder', () => { - const defaultERC20MakerAssetAddress = addressUtils.generatePseudoRandomAddress(); - const matchOrdersSignature = - 'matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)'; - let signedOrderLeft: SignedOrder; - let signedOrderRight: SignedOrder; - let orderLeft = {}; - let orderRight = {}; - let matchOrdersTxData: string; - let contractAddresses: ContractAddresses; - - before(async () => { - // Create accounts - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const [makerAddressLeft, makerAddressRight] = accounts.slice(0, 2); - const exchangeAddress = addressUtils.generatePseudoRandomAddress(); - const feeRecipientAddress = addressUtils.generatePseudoRandomAddress(); - const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; - const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; - // Create orders to match - orderLeft = { - makerAddress: makerAddressLeft, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: new BigNumber(10), - takerAddress: '0x0000000000000000000000000000000000000000', - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - takerAssetAmount: new BigNumber(1), - feeRecipientAddress, - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - senderAddress: '0x0000000000000000000000000000000000000000', - expirationTimeSeconds: new BigNumber(1549498915), - salt: new BigNumber(217), - }; - orderRight = { - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: new BigNumber(1), - takerAddress: '0x0000000000000000000000000000000000000000', - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - takerAssetAmount: new BigNumber(8), - feeRecipientAddress, - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - senderAddress: '0x0000000000000000000000000000000000000000', - expirationTimeSeconds: new BigNumber(1549498915), - salt: new BigNumber(50010), - }; - const orderFactoryLeft = new OrderFactory(privateKeyLeft, orderLeft); - signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ exchangeAddress }); - const orderFactoryRight = new OrderFactory(privateKeyRight, orderRight); - signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ exchangeAddress }); - // Encode match orders transaction - contractAddresses = await migrateOnceAsync(); - await blockchainLifecycle.startAsync(); - const config = { - networkId: constants.TESTRPC_NETWORK_ID, - contractAddresses, - blockPollingIntervalMs: 10, - }; - const contractWrappers = new ContractWrappers(provider, config); - const transactionEncoder = await contractWrappers.exchange.transactionEncoderAsync(); - matchOrdersTxData = transactionEncoder.matchOrdersTx(signedOrderLeft, signedOrderRight); - }); - - describe('decode', () => { - it('should successfully decode DutchAuction.matchOrders txData', async () => { - const contractName = 'DutchAuction'; - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName }); - const expectedFunctionName = 'matchOrders'; - const expectedFunctionArguments = { - buyOrder: orderLeft, - sellOrder: orderRight, - buySignature: signedOrderLeft.signature, - sellSignature: signedOrderRight.signature, - }; - expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); - expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); - expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); - }); - it('should successfully decode Exchange.matchOrders txData (and distinguish from DutchAuction.matchOrders)', async () => { - const contractName = 'Exchange'; - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractName }); - const expectedFunctionName = 'matchOrders'; - const expectedFunctionArguments = { - leftOrder: orderLeft, - rightOrder: orderRight, - leftSignature: signedOrderLeft.signature, - rightSignature: signedOrderRight.signature, - }; - expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); - expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); - expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); - }); - it('should successfully decode Exchange.matchOrders, using exchange address to identify the exchange contract', async () => { - const contractAddress = contractAddresses.exchange; - const decodedTxData = ZeroExTransactionDecoder.decode(matchOrdersTxData, { contractAddress }); - const expectedFunctionName = 'matchOrders'; - const expectedFunctionArguments = { - leftOrder: orderLeft, - rightOrder: orderRight, - leftSignature: signedOrderLeft.signature, - rightSignature: signedOrderRight.signature, - }; - expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); - expect(decodedTxData.functionSignature).to.be.equal(matchOrdersSignature); - expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); - }); - it('should throw if cannot decode txData', async () => { - const contractAddress = contractAddresses.exchange; - const badTxData = '0x01020304'; - expect(() => { - ZeroExTransactionDecoder.decode(badTxData, { contractAddress }); - }).to.throw("No functions registered for selector '0x01020304'"); - }); - }); - - describe('addABI', () => { - it('should successfully add a new ABI', async () => { - // Add new ABI - const abi: MethodAbi = { - name: 'foobar', - type: 'function', - inputs: [ - { - name: 'addr', - type: 'address', - }, - ], - outputs: [ - { - name: 'butter', - type: 'string', - }, - ], - constant: false, - payable: false, - stateMutability: 'pure', - }; - const contractName = 'newContract'; - const contractAddress = addressUtils.generatePseudoRandomAddress(); - const networkId = 1; - const contractInfo = [ - { - contractAddress, - networkId, - }, - ]; - ZeroExTransactionDecoder.addABI([abi], contractName, contractInfo); - // Create some tx data - const foobarEncoder = new AbiEncoder.Method(abi); - const foobarSignature = foobarEncoder.getSignature(); - const foobarTxData = foobarEncoder.encode([contractAddress]); - // Decode tx data using contract name - const decodedTxData = ZeroExTransactionDecoder.decode(foobarTxData, { contractName }); - const expectedFunctionName = abi.name; - const expectedFunctionArguments = { - addr: contractAddress, - }; - expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); - expect(decodedTxData.functionSignature).to.be.equal(foobarSignature); - expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); - // Decode tx data using contract address - const decodedTxDataDecodedWithAddress = ZeroExTransactionDecoder.decode(foobarTxData, { contractAddress }); - expect(decodedTxDataDecodedWithAddress).to.be.deep.equal(decodedTxData); - }); - }); -}); diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 9ce2a4c52..95f61a43c 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.1.0", + "changes": [ + { + "note": "Added method decoding to AbiDecoder", + "pr": 1569 + } + ] + }, { "version": "4.0.4", "changes": [ diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index 28b6418d8..c817ad285 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -6,28 +6,49 @@ import { EventParameter, LogEntry, LogWithDecodedArgs, + MethodAbi, RawLog, SolidityTypes, } from 'ethereum-types'; import * as ethers from 'ethers'; import * as _ from 'lodash'; +import { AbiEncoder } from '.'; import { addressUtils } from './address_utils'; import { BigNumber } from './configured_bignumber'; +import { FunctionInfoBySelector, TransactionData } from './types'; /** * AbiDecoder allows you to decode event logs given a set of supplied contract ABI's. It takes the contract's event * signature from the ABI and attempts to decode the logs using it. */ export class AbiDecoder { - private readonly _methodIds: { [signatureHash: string]: { [numIndexedArgs: number]: EventAbi } } = {}; + private readonly _eventIds: { [signatureHash: string]: { [numIndexedArgs: number]: EventAbi } } = {}; + private readonly _functionInfoBySelector: FunctionInfoBySelector = {}; + /** + * Retrieves the function selector from tranasction data. + * @param calldata hex-encoded transaction data. + * @return hex-encoded function selector. + */ + private static _getFunctionSelector(calldata: string): string { + const functionSelectorLength = 10; + if (!calldata.startsWith('0x') || calldata.length < functionSelectorLength) { + throw new Error( + `Malformed transaction data. Must include a hex prefix '0x' and 4-byte function selector. Got '${calldata}'`, + ); + } + const functionSelector = calldata.substr(0, functionSelectorLength); + return functionSelector; + } /** * Instantiate an AbiDecoder * @param abiArrays An array of contract ABI's * @return AbiDecoder instance */ constructor(abiArrays: AbiDefinition[][]) { - _.forEach(abiArrays, this.addABI.bind(this)); + _.each(abiArrays, (abi) => { + this.addABI(abi); + }); } /** * Attempt to decode a log given the ABI's the AbiDecoder knows about. @@ -37,10 +58,10 @@ export class AbiDecoder { public tryToDecodeLogOrNoop(log: LogEntry): LogWithDecodedArgs | RawLog { const methodId = log.topics[0]; const numIndexedArgs = log.topics.length - 1; - if (_.isUndefined(this._methodIds[methodId]) || _.isUndefined(this._methodIds[methodId][numIndexedArgs])) { + if (_.isUndefined(this._eventIds[methodId]) || _.isUndefined(this._eventIds[methodId][numIndexedArgs])) { return log; } - const event = this._methodIds[methodId][numIndexedArgs]; + const event = this._eventIds[methodId][numIndexedArgs]; const ethersInterface = new ethers.utils.Interface([event]); const decodedParams: DecodedLogArgs = {}; let topicsIndex = 1; @@ -89,25 +110,93 @@ export class AbiDecoder { } } /** - * Add additional ABI definitions to the AbiDecoder - * @param abiArray An array of ABI definitions to add to the AbiDecoder + * Decodes transaction data for a known ABI. + * @param calldata hex-encoded transaction data. + * @param contractName used to disambiguate similar ABI's (optional). + * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. */ - public addABI(abiArray: AbiDefinition[]): void { + public tryDecodeCalldata(calldata: string, contractName?: string): TransactionData { + const functionSelector = AbiDecoder._getFunctionSelector(calldata); + const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; + if (_.isUndefined(candidateFunctionInfos)) { + throw new Error(`No functions registered for selector '${functionSelector}'`); + } + const functionInfo = _.find(candidateFunctionInfos, txDecoder => { + return ( + (_.isUndefined(contractName) || + _.toLower(txDecoder.contractName) === _.toLower(contractName))); + }); + if (_.isUndefined(functionInfo)) { + throw new Error(`No function registered with selector ${functionSelector} and contract name ${contractName}.`); + } else if (_.isUndefined(functionInfo.abiEncoder)) { + throw new Error( + `Function ABI Encoder is not defined, for function registered with selector ${functionSelector} and contract name ${contractName}.`, + ); + } + const functionName = functionInfo.abiEncoder.getDataItem().name; + const functionSignature = functionInfo.abiEncoder.getSignatureType(); + const functionArguments = functionInfo.abiEncoder.decode(calldata); + const decodedCalldata = { + functionName, + functionSignature, + functionArguments, + }; + return decodedCalldata; + } + /** + * Adds a set of ABI definitions, after which transaction data targeting these ABI's can be decoded. + * Additional properties can be included to disambiguate similar ABI's. For example, if two functions + * have the same signature but different parameter names, then their ABI definitions can be disambiguated + * by specifying a contract name. + * @param abiDefinitions ABI definitions for a given contract. + * @param contractName Name of contract that encapsulates the ABI definitions (optional). + */ + public addABI( + abiArray: AbiDefinition[], + contractName?: string + ): void { if (_.isUndefined(abiArray)) { return; } const ethersInterface = new ethers.utils.Interface(abiArray); _.map(abiArray, (abi: AbiDefinition) => { - if (abi.type === AbiType.Event) { - // tslint:disable-next-line:no-unnecessary-type-assertion - const eventAbi = abi as EventAbi; - const topic = ethersInterface.events[eventAbi.name].topic; - const numIndexedArgs = _.reduce(eventAbi.inputs, (sum, input) => (input.indexed ? sum + 1 : sum), 0); - this._methodIds[topic] = { - ...this._methodIds[topic], - [numIndexedArgs]: eventAbi, - }; + switch (abi.type) { + case AbiType.Event: + this._addEventABI(abi as EventAbi, ethersInterface); + break; + + case AbiType.Function: + this._addMethodABI(abi as MethodAbi, contractName); + break; + + default: + // ignore other types + break; } }); } + private _addEventABI(abi: EventAbi, ethersInterface: ethers.utils.Interface): void { + // tslint:disable-next-line:no-unnecessary-type-assertion + const eventAbi = abi as EventAbi; + const topic = ethersInterface.events[eventAbi.name].topic; + const numIndexedArgs = _.reduce(eventAbi.inputs, (sum, input) => (input.indexed ? sum + 1 : sum), 0); + this._eventIds[topic] = { + ...this._eventIds[topic], + [numIndexedArgs]: eventAbi, + }; + } + private _addMethodABI(methodAbi: MethodAbi, contractName?: string): void { + const abiEncoder = new AbiEncoder.Method(methodAbi); + const functionSelector = abiEncoder.getSelector(); + if (!(functionSelector in this._functionInfoBySelector)) { + this._functionInfoBySelector[functionSelector] = []; + } + // Recored a copy of this ABI for each deployment + const functionSignature = abiEncoder.getSignature(); + this._functionInfoBySelector[functionSelector].push({ + functionSignature, + abiEncoder, + contractName, + }); + } } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 6f1c14c83..467129d2b 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -12,4 +12,3 @@ export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); export * from './types'; -export { TransactionDecoder } from './transaction_decoder'; diff --git a/packages/utils/src/transaction_decoder.ts b/packages/utils/src/transaction_decoder.ts deleted file mode 100644 index 9d567286e..000000000 --- a/packages/utils/src/transaction_decoder.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { AbiDefinition, MethodAbi } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { AbiEncoder } from '.'; -import { DeployedContractInfo, FunctionInfoBySelector, TransactionData, TransactionProperties } from './types'; - -export class TransactionDecoder { - private readonly _functionInfoBySelector: FunctionInfoBySelector = {}; - /** - * Retrieves the function selector from tranasction data. - * @param txData hex-encoded transaction data. - * @return hex-encoded function selector. - */ - private static _getFunctionSelector(txData: string): string { - const functionSelectorLength = 10; - if (!txData.startsWith('0x') || txData.length < functionSelectorLength) { - throw new Error( - `Malformed transaction data. Must include a hex prefix '0x' and 4-byte function selector. Got '${txData}'`, - ); - } - const functionSelector = txData.substr(0, functionSelectorLength); - return functionSelector; - } - /** - * Adds a set of ABI definitions, after which transaction data targeting these ABI's can be decoded. - * Additional properties can be included to disambiguate similar ABI's. For example, if two functions - * have the same signature but different parameter names, then their ABI definitions can be disambiguated - * by specifying a contract name. - * @param abiDefinitions ABI definitions for a given contract. - * @param contractName Name of contract that encapsulates the ABI definitions (optional). - * @param deploymentInfos A collection of network/address pairs where this contract is deployed (optional). - */ - public addABI( - abiDefinitions: AbiDefinition[], - contractName?: string, - deploymentInfos?: DeployedContractInfo[], - ): void { - // Disregard definitions that are not functions - // tslint:disable no-unnecessary-type-assertion - const functionAbis = _.filter(abiDefinitions, abiEntry => { - return abiEntry.type === 'function'; - }) as MethodAbi[]; - // tslint:enable no-unnecessary-type-assertion - // Record function ABI's - _.each(functionAbis, functionAbi => { - const abiEncoder = new AbiEncoder.Method(functionAbi); - const functionSelector = abiEncoder.getSelector(); - if (!(functionSelector in this._functionInfoBySelector)) { - this._functionInfoBySelector[functionSelector] = []; - } - // Recored a copy of this ABI for each deployment - const functionSignature = abiEncoder.getSignature(); - _.each(deploymentInfos, deploymentInfo => { - this._functionInfoBySelector[functionSelector].push({ - functionSignature, - abiEncoder, - contractName, - contractAddress: deploymentInfo.contractAddress, - networkId: deploymentInfo.networkId, - }); - }); - // There is no deployment info for this contract; record it without an address/network id - if (_.isEmpty(deploymentInfos)) { - this._functionInfoBySelector[functionSelector].push({ - functionSignature, - abiEncoder, - contractName, - }); - } - }); - } - /** - * Decodes transaction data for a known ABI. - * @param txData hex-encoded transaction data. - * @param txProperties Properties about the transaction used to disambiguate similar ABI's (optional). - * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. - */ - public decode(txData: string, txProperties_?: TransactionProperties): TransactionData { - // Lookup - const functionSelector = TransactionDecoder._getFunctionSelector(txData); - const txProperties = _.isUndefined(txProperties_) ? {} : txProperties_; - const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; - if (_.isUndefined(candidateFunctionInfos)) { - throw new Error(`No functions registered for selector '${functionSelector}'`); - } - const functionInfo = _.find(candidateFunctionInfos, txDecoder => { - return ( - (_.isUndefined(txProperties.contractName) || - _.toLower(txDecoder.contractName) === _.toLower(txProperties.contractName)) && - (_.isUndefined(txProperties.contractAddress) || - txDecoder.contractAddress === txProperties.contractAddress) && - (_.isUndefined(txProperties.networkId) || txDecoder.networkId === txProperties.networkId) - ); - }); - if (_.isUndefined(functionInfo)) { - throw new Error(`No function registered with properties: ${JSON.stringify(txProperties)}.`); - } else if (_.isUndefined(functionInfo.abiEncoder)) { - throw new Error( - `Function ABI Encoder is not defined, for function with properties: ${JSON.stringify(txProperties)}.`, - ); - } - const functionName = functionInfo.abiEncoder.getDataItem().name; - const functionSignature = functionInfo.abiEncoder.getSignatureType(); - const functionArguments = functionInfo.abiEncoder.decode(txData); - const decodedCalldata = { - functionName, - functionSignature, - functionArguments, - }; - return decodedCalldata; - } -} diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 2510a9ec2..cd7a13d53 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -17,18 +17,3 @@ export interface TransactionData { functionSignature: string; functionArguments: any; } - -export interface TransactionProperties { - contractName?: string; - contractAddress?: string; - networkId?: number; -} - -export interface DeployedContractInfo { - contractAddress: string; - networkId: number; -} - -export interface DeployedContractInfoByName { - [index: string]: DeployedContractInfo[]; -} diff --git a/packages/utils/test/abi_decoder_test.ts b/packages/utils/test/abi_decoder_test.ts new file mode 100644 index 000000000..346bfdfd3 --- /dev/null +++ b/packages/utils/test/abi_decoder_test.ts @@ -0,0 +1,50 @@ +import * as chai from 'chai'; +import { MethodAbi } from 'ethereum-types'; +import 'mocha'; + +import { AbiEncoder, AbiDecoder } from '../src'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('AbiDecoder', () => { + it('should successfully add a new ABI and decode calldata for it', async () => { + // Add new ABI + const abi: MethodAbi = { + name: 'foobar', + type: 'function', + inputs: [ + { + name: 'testAddress', + type: 'address', + }, + ], + outputs: [ + { + name: 'butter', + type: 'string', + }, + ], + constant: false, + payable: false, + stateMutability: 'pure', + }; + const contractName = 'newContract'; + const testAddress = '0x0001020304050607080900010203040506070809'; + const abiDecoder = new AbiDecoder([]); + abiDecoder.addABI([abi], contractName); + // Create some tx data + const foobarEncoder = new AbiEncoder.Method(abi); + const foobarSignature = foobarEncoder.getSignature(); + const foobarTxData = foobarEncoder.encode([testAddress]); + // Decode tx data using contract name + const decodedTxData = abiDecoder.tryDecodeCalldata(foobarTxData, contractName); + const expectedFunctionName = abi.name; + const expectedFunctionArguments = {testAddress}; + expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); + expect(decodedTxData.functionSignature).to.be.equal(foobarSignature); + expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); + }); +}); diff --git a/packages/utils/test/transaction_decoder_test.ts b/packages/utils/test/transaction_decoder_test.ts deleted file mode 100644 index 725ad5032..000000000 --- a/packages/utils/test/transaction_decoder_test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as chai from 'chai'; -import { MethodAbi } from 'ethereum-types'; -import 'mocha'; - -import { AbiEncoder, TransactionDecoder } from '../src'; - -import { chaiSetup } from './utils/chai_setup'; - -chaiSetup.configure(); -const expect = chai.expect; - -describe('TransactionDecoder', () => { - it('should successfully add a new ABI and decode tx data for it', async () => { - // Add new ABI - const abi: MethodAbi = { - name: 'foobar', - type: 'function', - inputs: [ - { - name: 'addr', - type: 'address', - }, - ], - outputs: [ - { - name: 'butter', - type: 'string', - }, - ], - constant: false, - payable: false, - stateMutability: 'pure', - }; - const contractName = 'newContract'; - const contractAddress = '0x0001020304050607080900010203040506070809'; - const networkId = 1; - const contractInfo = [ - { - contractAddress, - networkId, - }, - ]; - const transactionDecoder = new TransactionDecoder(); - transactionDecoder.addABI([abi], contractName, contractInfo); - // Create some tx data - const foobarEncoder = new AbiEncoder.Method(abi); - const foobarSignature = foobarEncoder.getSignature(); - const foobarTxData = foobarEncoder.encode([contractAddress]); - // Decode tx data using contract name - const decodedTxData = transactionDecoder.decode(foobarTxData, { contractName }); - const expectedFunctionName = abi.name; - const expectedFunctionArguments = { - addr: contractAddress, - }; - expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); - expect(decodedTxData.functionSignature).to.be.equal(foobarSignature); - expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); - // Decode tx data using contract address - const decodedTxDataDecodedWithAddress = transactionDecoder.decode(foobarTxData, { contractAddress }); - expect(decodedTxDataDecodedWithAddress).to.be.deep.equal(decodedTxData); - }); -}); -- cgit v1.2.3 From c72e3667b8366f8b125091d704a67c1de91985ff Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 6 Feb 2019 23:59:25 -0800 Subject: ran prettier and linter --- packages/contract-wrappers/src/contract_wrappers.ts | 2 +- packages/utils/src/abi_decoder.ts | 21 +++++++++++---------- packages/utils/test/abi_decoder_test.ts | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index 63de61a47..bd9a377ed 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -171,7 +171,7 @@ export class ContractWrappers { public getProvider(): Provider { return this._web3Wrapper.getProvider(); } - /** + /** * Get the provider instance currently used by contract-wrappers * @return Web3 provider instance */ diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index c817ad285..e8a222229 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -46,7 +46,7 @@ export class AbiDecoder { * @return AbiDecoder instance */ constructor(abiArrays: AbiDefinition[][]) { - _.each(abiArrays, (abi) => { + _.each(abiArrays, abi => { this.addABI(abi); }); } @@ -122,12 +122,12 @@ export class AbiDecoder { throw new Error(`No functions registered for selector '${functionSelector}'`); } const functionInfo = _.find(candidateFunctionInfos, txDecoder => { - return ( - (_.isUndefined(contractName) || - _.toLower(txDecoder.contractName) === _.toLower(contractName))); + return _.isUndefined(contractName) || _.toLower(txDecoder.contractName) === _.toLower(contractName); }); if (_.isUndefined(functionInfo)) { - throw new Error(`No function registered with selector ${functionSelector} and contract name ${contractName}.`); + throw new Error( + `No function registered with selector ${functionSelector} and contract name ${contractName}.`, + ); } else if (_.isUndefined(functionInfo.abiEncoder)) { throw new Error( `Function ABI Encoder is not defined, for function registered with selector ${functionSelector} and contract name ${contractName}.`, @@ -151,10 +151,7 @@ export class AbiDecoder { * @param abiDefinitions ABI definitions for a given contract. * @param contractName Name of contract that encapsulates the ABI definitions (optional). */ - public addABI( - abiArray: AbiDefinition[], - contractName?: string - ): void { + public addABI(abiArray: AbiDefinition[], contractName?: string): void { if (_.isUndefined(abiArray)) { return; } @@ -162,16 +159,20 @@ export class AbiDecoder { _.map(abiArray, (abi: AbiDefinition) => { switch (abi.type) { case AbiType.Event: + // tslint:disable no-unnecessary-type-assertion this._addEventABI(abi as EventAbi, ethersInterface); + // tslint:enable no-unnecessary-type-assertion break; case AbiType.Function: + // tslint:disable no-unnecessary-type-assertion this._addMethodABI(abi as MethodAbi, contractName); + // tslint:enable no-unnecessary-type-assertion break; default: // ignore other types - break; + break; } }); } diff --git a/packages/utils/test/abi_decoder_test.ts b/packages/utils/test/abi_decoder_test.ts index 346bfdfd3..601434614 100644 --- a/packages/utils/test/abi_decoder_test.ts +++ b/packages/utils/test/abi_decoder_test.ts @@ -2,7 +2,7 @@ import * as chai from 'chai'; import { MethodAbi } from 'ethereum-types'; import 'mocha'; -import { AbiEncoder, AbiDecoder } from '../src'; +import { AbiDecoder, AbiEncoder } from '../src'; import { chaiSetup } from './utils/chai_setup'; @@ -42,7 +42,7 @@ describe('AbiDecoder', () => { // Decode tx data using contract name const decodedTxData = abiDecoder.tryDecodeCalldata(foobarTxData, contractName); const expectedFunctionName = abi.name; - const expectedFunctionArguments = {testAddress}; + const expectedFunctionArguments = { testAddress }; expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); expect(decodedTxData.functionSignature).to.be.equal(foobarSignature); expect(decodedTxData.functionArguments).to.be.deep.equal(expectedFunctionArguments); -- cgit v1.2.3 From 93c128ee21465249a3d9c6549d1765a79f071200 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 7 Feb 2019 01:09:56 -0800 Subject: Added exports for doc generation --- packages/0x.js/src/index.ts | 2 +- packages/contract-wrappers/src/index.ts | 4 ++++ packages/web3-wrapper/src/index.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index 006e4cf29..41a440185 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -68,7 +68,7 @@ export { MetamaskSubprovider, } from '@0x/subproviders'; -export { AbiDecoder } from '@0x/utils'; +export { AbiDecoder, TransactionData } from '@0x/utils'; export { BigNumber } from '@0x/utils'; diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 69bbe3c91..75faac9fb 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -38,6 +38,8 @@ export { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper'; export { TransactionEncoder } from './utils/transaction_encoder'; +export { AbiDecoder, TransactionData } from '@0x/utils'; + export { ContractWrappersError, ForwarderWrapperError, @@ -83,6 +85,8 @@ export { JSONRPCResponseError, AbiDefinition, LogWithDecodedArgs, + LogEntry, + RawLog, FunctionAbi, EventAbi, EventParameter, diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index 4d20ba9be..2a66e01b5 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -1,7 +1,7 @@ export { Web3Wrapper } from './web3_wrapper'; export { marshaller } from './marshaller'; -export { AbiDecoder } from '@0x/utils'; +export { AbiDecoder, TransactionData } from '@0x/utils'; export { BlockParam, -- cgit v1.2.3 From 56e7e7d6444d96d48bcea655281d801f202b5f5e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 7 Feb 2019 15:41:14 -0800 Subject: updated exports for contract wrappers --- packages/contract-wrappers/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 75faac9fb..5eaf6118f 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -86,6 +86,9 @@ export { AbiDefinition, LogWithDecodedArgs, LogEntry, + DecodedLogEntry, + DecodedLogEntryEvent, + LogEntryEvent, RawLog, FunctionAbi, EventAbi, -- cgit v1.2.3 From 6d0dc47157f7cc17f1f3da7a1960456e4ae9bede Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 7 Feb 2019 15:43:54 -0800 Subject: updated changelog for contract wrappers --- packages/contract-wrappers/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 73c8e6070..6e0d1ca4b 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "7.1.0", + "changes": [ + { + "note": "Added calldata decoding to ContractWrappers", + "pr": 1569 + } + ] + }, { "version": "7.0.2", "changes": [ -- cgit v1.2.3 From 500b4940a320c842394c996f15495c1cdc7b3547 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 8 Feb 2019 14:46:39 -0800 Subject: Improvements and conventions in utils package + abi decoder --- packages/0x.js/src/index.ts | 12 ++++- .../contract-wrappers/src/contract_wrappers.ts | 4 +- packages/contract-wrappers/src/index.ts | 2 +- .../src/utils/transaction_encoder.ts | 4 +- .../test/calldata_decoder_test.ts | 18 +++++--- packages/utils/src/abi_decoder.ts | 52 +++++++++++----------- packages/utils/src/address_utils.ts | 26 +---------- packages/utils/src/types.ts | 4 +- packages/utils/test/abi_decoder_test.ts | 2 +- packages/web3-wrapper/src/index.ts | 2 +- 10 files changed, 57 insertions(+), 69 deletions(-) diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts index 41a440185..082b09727 100644 --- a/packages/0x.js/src/index.ts +++ b/packages/0x.js/src/index.ts @@ -1,6 +1,12 @@ export { ContractAddresses } from '@0x/contract-addresses'; -export { assetDataUtils, signatureUtils, generatePseudoRandomSalt, orderHashUtils } from '@0x/order-utils'; +export { + assetDataUtils, + signatureUtils, + generatePseudoRandomSalt, + orderHashUtils, + transactionHashUtils, +} from '@0x/order-utils'; export { ContractWrappers, @@ -68,7 +74,7 @@ export { MetamaskSubprovider, } from '@0x/subproviders'; -export { AbiDecoder, TransactionData } from '@0x/utils'; +export { AbiDecoder, DecodedCalldata } from '@0x/utils'; export { BigNumber } from '@0x/utils'; @@ -92,6 +98,8 @@ export { OrderRelevantState, Stats, DutchAuctionDetails, + ZeroExTransaction, + SignedZeroExTransaction, } from '@0x/types'; export { diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts index bd9a377ed..f43dc5d26 100644 --- a/packages/contract-wrappers/src/contract_wrappers.ts +++ b/packages/contract-wrappers/src/contract_wrappers.ts @@ -172,8 +172,8 @@ export class ContractWrappers { return this._web3Wrapper.getProvider(); } /** - * Get the provider instance currently used by contract-wrappers - * @return Web3 provider instance + * Get the abi decoder instance currently used by contract-wrappers + * @return AbiDecoder instance */ public getAbiDecoder(): AbiDecoder { return this._web3Wrapper.abiDecoder; diff --git a/packages/contract-wrappers/src/index.ts b/packages/contract-wrappers/src/index.ts index 5eaf6118f..5fc400edf 100644 --- a/packages/contract-wrappers/src/index.ts +++ b/packages/contract-wrappers/src/index.ts @@ -38,7 +38,7 @@ export { DutchAuctionWrapper } from './contract_wrappers/dutch_auction_wrapper'; export { TransactionEncoder } from './utils/transaction_encoder'; -export { AbiDecoder, TransactionData } from '@0x/utils'; +export { AbiDecoder, DecodedCalldata } from '@0x/utils'; export { ContractWrappersError, diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts index 8bf67ee56..0832ee73a 100644 --- a/packages/contract-wrappers/src/utils/transaction_encoder.ts +++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts @@ -248,8 +248,8 @@ export class TransactionEncoder { * @return Hex encoded abi of the function call. */ public matchOrdersTx(leftOrder: SignedOrder, rightOrder: SignedOrder): string { - assert.doesConformToSchema('order', leftOrder, schemas.orderSchema); - assert.doesConformToSchema('order', rightOrder, schemas.orderSchema); + assert.doesConformToSchema('leftOrder', leftOrder, schemas.orderSchema); + assert.doesConformToSchema('rightOrder', rightOrder, schemas.orderSchema); const abiEncodedData = this._getExchangeContract().matchOrders.getABIEncodedTransactionData( leftOrder, rightOrder, diff --git a/packages/contract-wrappers/test/calldata_decoder_test.ts b/packages/contract-wrappers/test/calldata_decoder_test.ts index d44e13a89..ba1539ef5 100644 --- a/packages/contract-wrappers/test/calldata_decoder_test.ts +++ b/packages/contract-wrappers/test/calldata_decoder_test.ts @@ -33,12 +33,12 @@ describe('ABI Decoding Calldata', () => { before(async () => { // Create accounts const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const [makerAddressLeft, makerAddressRight] = accounts.slice(0, 2); + const [makerAddressLeft, makerAddressRight] = accounts; + const [privateKeyLeft, privateKeyRight] = constants.TESTRPC_PRIVATE_KEYS; const exchangeAddress = addressUtils.generatePseudoRandomAddress(); const feeRecipientAddress = addressUtils.generatePseudoRandomAddress(); - const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; - const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; - // Create orders to match + // Create orders to match. + // Values are arbitrary, with the exception of maker addresses (generated above). orderLeft = { makerAddress: makerAddressLeft, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), @@ -87,7 +87,9 @@ describe('ABI Decoding Calldata', () => { describe('decode', () => { it('should successfully decode DutchAuction.matchOrders calldata', async () => { const contractName = 'DutchAuction'; - const decodedTxData = contractWrappers.getAbiDecoder().tryDecodeCalldata(matchOrdersTxData, contractName); + const decodedTxData = contractWrappers + .getAbiDecoder() + .decodeCalldataOrThrow(matchOrdersTxData, contractName); const expectedFunctionName = 'matchOrders'; const expectedFunctionArguments = { buyOrder: orderLeft, @@ -101,7 +103,9 @@ describe('ABI Decoding Calldata', () => { }); it('should successfully decode Exchange.matchOrders calldata (and distinguish from DutchAuction.matchOrders)', async () => { const contractName = 'Exchange'; - const decodedTxData = contractWrappers.getAbiDecoder().tryDecodeCalldata(matchOrdersTxData, contractName); + const decodedTxData = contractWrappers + .getAbiDecoder() + .decodeCalldataOrThrow(matchOrdersTxData, contractName); const expectedFunctionName = 'matchOrders'; const expectedFunctionArguments = { leftOrder: orderLeft, @@ -116,7 +120,7 @@ describe('ABI Decoding Calldata', () => { it('should throw if cannot decode calldata', async () => { const badTxData = '0x01020304'; expect(() => { - contractWrappers.getAbiDecoder().tryDecodeCalldata(badTxData); + contractWrappers.getAbiDecoder().decodeCalldataOrThrow(badTxData); }).to.throw("No functions registered for selector '0x01020304'"); }); }); diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts index e8a222229..b764e45b8 100644 --- a/packages/utils/src/abi_decoder.ts +++ b/packages/utils/src/abi_decoder.ts @@ -16,7 +16,7 @@ import * as _ from 'lodash'; import { AbiEncoder } from '.'; import { addressUtils } from './address_utils'; import { BigNumber } from './configured_bignumber'; -import { FunctionInfoBySelector, TransactionData } from './types'; +import { DecodedCalldata, SelectorToFunctionInfo } from './types'; /** * AbiDecoder allows you to decode event logs given a set of supplied contract ABI's. It takes the contract's event @@ -24,17 +24,17 @@ import { FunctionInfoBySelector, TransactionData } from './types'; */ export class AbiDecoder { private readonly _eventIds: { [signatureHash: string]: { [numIndexedArgs: number]: EventAbi } } = {}; - private readonly _functionInfoBySelector: FunctionInfoBySelector = {}; + private readonly _selectorToFunctionInfo: SelectorToFunctionInfo = {}; /** - * Retrieves the function selector from tranasction data. - * @param calldata hex-encoded transaction data. + * Retrieves the function selector from calldata. + * @param calldata hex-encoded calldata. * @return hex-encoded function selector. */ private static _getFunctionSelector(calldata: string): string { const functionSelectorLength = 10; if (!calldata.startsWith('0x') || calldata.length < functionSelectorLength) { throw new Error( - `Malformed transaction data. Must include a hex prefix '0x' and 4-byte function selector. Got '${calldata}'`, + `Malformed calldata. Must include a hex prefix '0x' and 4-byte function selector. Got '${calldata}'`, ); } const functionSelector = calldata.substr(0, functionSelectorLength); @@ -56,12 +56,12 @@ export class AbiDecoder { * @return The decoded log if the requisite ABI was available. Otherwise the log unaltered. */ public tryToDecodeLogOrNoop(log: LogEntry): LogWithDecodedArgs | RawLog { - const methodId = log.topics[0]; + const eventId = log.topics[0]; const numIndexedArgs = log.topics.length - 1; - if (_.isUndefined(this._eventIds[methodId]) || _.isUndefined(this._eventIds[methodId][numIndexedArgs])) { + if (_.isUndefined(this._eventIds[eventId]) || _.isUndefined(this._eventIds[eventId][numIndexedArgs])) { return log; } - const event = this._eventIds[methodId][numIndexedArgs]; + const event = this._eventIds[eventId][numIndexedArgs]; const ethersInterface = new ethers.utils.Interface([event]); const decodedParams: DecodedLogArgs = {}; let topicsIndex = 1; @@ -110,19 +110,21 @@ export class AbiDecoder { } } /** - * Decodes transaction data for a known ABI. - * @param calldata hex-encoded transaction data. + * Decodes calldata for a known ABI. + * @param calldata hex-encoded calldata. * @param contractName used to disambiguate similar ABI's (optional). - * @return Decoded transaction data. Includes: function name and signature, along with the decoded arguments. + * @return Decoded calldata. Includes: function name and signature, along with the decoded arguments. */ - public tryDecodeCalldata(calldata: string, contractName?: string): TransactionData { + public decodeCalldataOrThrow(calldata: string, contractName?: string): DecodedCalldata { const functionSelector = AbiDecoder._getFunctionSelector(calldata); - const candidateFunctionInfos = this._functionInfoBySelector[functionSelector]; + const candidateFunctionInfos = this._selectorToFunctionInfo[functionSelector]; if (_.isUndefined(candidateFunctionInfos)) { throw new Error(`No functions registered for selector '${functionSelector}'`); } - const functionInfo = _.find(candidateFunctionInfos, txDecoder => { - return _.isUndefined(contractName) || _.toLower(txDecoder.contractName) === _.toLower(contractName); + const functionInfo = _.find(candidateFunctionInfos, candidateFunctionInfo => { + return ( + _.isUndefined(contractName) || _.toLower(contractName) === _.toLower(candidateFunctionInfo.contractName) + ); }); if (_.isUndefined(functionInfo)) { throw new Error( @@ -144,12 +146,14 @@ export class AbiDecoder { return decodedCalldata; } /** - * Adds a set of ABI definitions, after which transaction data targeting these ABI's can be decoded. + * Adds a set of ABI definitions, after which calldata and logs targeting these ABI's can be decoded. * Additional properties can be included to disambiguate similar ABI's. For example, if two functions * have the same signature but different parameter names, then their ABI definitions can be disambiguated * by specifying a contract name. * @param abiDefinitions ABI definitions for a given contract. * @param contractName Name of contract that encapsulates the ABI definitions (optional). + * This can be used when decoding calldata to disambiguate methods with + * the same signature but different parameter names. */ public addABI(abiArray: AbiDefinition[], contractName?: string): void { if (_.isUndefined(abiArray)) { @@ -159,15 +163,13 @@ export class AbiDecoder { _.map(abiArray, (abi: AbiDefinition) => { switch (abi.type) { case AbiType.Event: - // tslint:disable no-unnecessary-type-assertion + // tslint:disable-next-line:no-unnecessary-type-assertion this._addEventABI(abi as EventAbi, ethersInterface); - // tslint:enable no-unnecessary-type-assertion break; case AbiType.Function: - // tslint:disable no-unnecessary-type-assertion + // tslint:disable-next-line:no-unnecessary-type-assertion this._addMethodABI(abi as MethodAbi, contractName); - // tslint:enable no-unnecessary-type-assertion break; default: @@ -176,9 +178,7 @@ export class AbiDecoder { } }); } - private _addEventABI(abi: EventAbi, ethersInterface: ethers.utils.Interface): void { - // tslint:disable-next-line:no-unnecessary-type-assertion - const eventAbi = abi as EventAbi; + private _addEventABI(eventAbi: EventAbi, ethersInterface: ethers.utils.Interface): void { const topic = ethersInterface.events[eventAbi.name].topic; const numIndexedArgs = _.reduce(eventAbi.inputs, (sum, input) => (input.indexed ? sum + 1 : sum), 0); this._eventIds[topic] = { @@ -189,12 +189,12 @@ export class AbiDecoder { private _addMethodABI(methodAbi: MethodAbi, contractName?: string): void { const abiEncoder = new AbiEncoder.Method(methodAbi); const functionSelector = abiEncoder.getSelector(); - if (!(functionSelector in this._functionInfoBySelector)) { - this._functionInfoBySelector[functionSelector] = []; + if (!(functionSelector in this._selectorToFunctionInfo)) { + this._selectorToFunctionInfo[functionSelector] = []; } // Recored a copy of this ABI for each deployment const functionSignature = abiEncoder.getSignature(); - this._functionInfoBySelector[functionSelector].push({ + this._selectorToFunctionInfo[functionSelector].push({ functionSignature, abiEncoder, contractName, diff --git a/packages/utils/src/address_utils.ts b/packages/utils/src/address_utils.ts index b269c26b4..1fc960408 100644 --- a/packages/utils/src/address_utils.ts +++ b/packages/utils/src/address_utils.ts @@ -1,13 +1,10 @@ -import { addHexPrefix, sha3, stripHexPrefix } from 'ethereumjs-util'; +import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util'; import * as jsSHA3 from 'js-sha3'; import * as _ from 'lodash'; -import { BigNumber } from './configured_bignumber'; - const BASIC_ADDRESS_REGEX = /^(0x)?[0-9a-f]{40}$/i; const SAME_CASE_ADDRESS_REGEX = /^(0x)?([0-9a-f]{40}|[0-9A-F]{40})$/; const ADDRESS_LENGTH = 40; -const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export const addressUtils = { isChecksumAddress(address: string): boolean { @@ -46,25 +43,4 @@ export const addressUtils = { padZeros(address: string): string { return addHexPrefix(_.padStart(stripHexPrefix(address), ADDRESS_LENGTH, '0')); }, - /** - * Generates a pseudo-random 256-bit salt. - * The salt can be included in a 0x order, ensuring that the order generates a unique orderHash - * and will not collide with other outstanding orders that are identical in all other parameters. - * @return A pseudo-random 256-bit number that can be used as a salt. - */ - generatePseudoRandomSalt(): BigNumber { - // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. - // Source: https://mikemcl.github.io/bignumber.js/#random - const randomNumber = BigNumber.random(MAX_DIGITS_IN_UNSIGNED_256_INT); - const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1); - const salt = randomNumber.times(factor); - return salt; - }, - generatePseudoRandomAddress(): string { - const randomBigNum = addressUtils.generatePseudoRandomSalt(); - const randomBuff = sha3(randomBigNum.toString()); - const addressLengthInBytes = 20; - const randomAddress = `0x${randomBuff.slice(0, addressLengthInBytes).toString('hex')}`; - return randomAddress; - }, }; diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index cd7a13d53..32e11efa2 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -8,11 +8,11 @@ export interface FunctionInfo { abiEncoder?: AbiEncoder.Method; } -export interface FunctionInfoBySelector { +export interface SelectorToFunctionInfo { [index: string]: FunctionInfo[]; } -export interface TransactionData { +export interface DecodedCalldata { functionName: string; functionSignature: string; functionArguments: any; diff --git a/packages/utils/test/abi_decoder_test.ts b/packages/utils/test/abi_decoder_test.ts index 601434614..81fed1060 100644 --- a/packages/utils/test/abi_decoder_test.ts +++ b/packages/utils/test/abi_decoder_test.ts @@ -40,7 +40,7 @@ describe('AbiDecoder', () => { const foobarSignature = foobarEncoder.getSignature(); const foobarTxData = foobarEncoder.encode([testAddress]); // Decode tx data using contract name - const decodedTxData = abiDecoder.tryDecodeCalldata(foobarTxData, contractName); + const decodedTxData = abiDecoder.decodeCalldataOrThrow(foobarTxData, contractName); const expectedFunctionName = abi.name; const expectedFunctionArguments = { testAddress }; expect(decodedTxData.functionName).to.be.equal(expectedFunctionName); diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts index 2a66e01b5..a63408455 100644 --- a/packages/web3-wrapper/src/index.ts +++ b/packages/web3-wrapper/src/index.ts @@ -1,7 +1,7 @@ export { Web3Wrapper } from './web3_wrapper'; export { marshaller } from './marshaller'; -export { AbiDecoder, TransactionData } from '@0x/utils'; +export { AbiDecoder, DecodedCalldata } from '@0x/utils'; export { BlockParam, -- cgit v1.2.3 From a9aaae7f97b2684107d158db0c1530749e096b3e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 8 Feb 2019 16:04:45 -0800 Subject: Deduped random value generation from salt generation --- packages/order-utils/src/salt.ts | 10 ++-------- packages/utils/src/address_utils.ts | 11 ++++++++++- packages/utils/src/index.ts | 1 + packages/utils/src/random.ts | 16 ++++++++++++++++ 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 packages/utils/src/random.ts diff --git a/packages/order-utils/src/salt.ts b/packages/order-utils/src/salt.ts index 95df66c99..a7cc4aea0 100644 --- a/packages/order-utils/src/salt.ts +++ b/packages/order-utils/src/salt.ts @@ -1,6 +1,4 @@ -import { BigNumber } from '@0x/utils'; - -const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; +import { BigNumber, generatePseudoRandom256BitNumber } from '@0x/utils'; /** * Generates a pseudo-random 256-bit salt. @@ -9,10 +7,6 @@ const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; * @return A pseudo-random 256-bit number that can be used as a salt. */ export function generatePseudoRandomSalt(): BigNumber { - // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. - // Source: https://mikemcl.github.io/bignumber.js/#random - const randomNumber = BigNumber.random(MAX_DIGITS_IN_UNSIGNED_256_INT); - const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1); - const salt = randomNumber.times(factor).integerValue(); + const salt = generatePseudoRandom256BitNumber(); return salt; } diff --git a/packages/utils/src/address_utils.ts b/packages/utils/src/address_utils.ts index 1fc960408..361e35cd8 100644 --- a/packages/utils/src/address_utils.ts +++ b/packages/utils/src/address_utils.ts @@ -1,7 +1,9 @@ -import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util'; +import { addHexPrefix, sha3, stripHexPrefix } from 'ethereumjs-util'; import * as jsSHA3 from 'js-sha3'; import * as _ from 'lodash'; +import { generatePseudoRandom256BitNumber } from './random'; + const BASIC_ADDRESS_REGEX = /^(0x)?[0-9a-f]{40}$/i; const SAME_CASE_ADDRESS_REGEX = /^(0x)?([0-9a-f]{40}|[0-9A-F]{40})$/; const ADDRESS_LENGTH = 40; @@ -43,4 +45,11 @@ export const addressUtils = { padZeros(address: string): string { return addHexPrefix(_.padStart(stripHexPrefix(address), ADDRESS_LENGTH, '0')); }, + generatePseudoRandomAddress(): string { + const randomBigNum = generatePseudoRandom256BitNumber(); + const randomBuff = sha3(randomBigNum.toString()); + const addressLengthInBytes = 20; + const randomAddress = `0x${randomBuff.slice(0, addressLengthInBytes).toString('hex')}`; + return randomAddress; + }, }; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 467129d2b..f9c2693fe 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -12,3 +12,4 @@ export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); export * from './types'; +export { generatePseudoRandom256BitNumber } from './random'; diff --git a/packages/utils/src/random.ts b/packages/utils/src/random.ts new file mode 100644 index 000000000..69243bab8 --- /dev/null +++ b/packages/utils/src/random.ts @@ -0,0 +1,16 @@ +import { BigNumber } from './configured_bignumber'; + +const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; + +/** + * Generates a pseudo-random 256-bit number. + * @return A pseudo-random 256-bit number. + */ +export function generatePseudoRandom256BitNumber(): BigNumber { + // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places. + // Source: https://mikemcl.github.io/bignumber.js/#random + const randomNumber = BigNumber.random(MAX_DIGITS_IN_UNSIGNED_256_INT); + const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1); + const randomNumberScaledTo256Bits = randomNumber.times(factor).integerValue(); + return randomNumberScaledTo256Bits; +} -- cgit v1.2.3 From 233869ed80a1f2836834d20adbc478d2e9b26b54 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 8 Feb 2019 16:33:08 -0800 Subject: updated changelog.json for 0x.js / order-utils / web3-wrapper --- packages/0x.js/CHANGELOG.json | 9 +++++++++ packages/order-utils/CHANGELOG.json | 9 +++++++++ packages/web3-wrapper/CHANGELOG.json | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 69381d7a0..e94d87de1 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "5.0.0", + "changes": [ + { + "note": "Export `transactionHashUtils`, `DecodedCalldata`, `ZeroExTransaction`, and `SignedZeroExTransaction`", + "pr": 1569 + } + ] + }, { "version": "4.0.3", "changes": [ diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 0028ea0c7..8f984538b 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "6.1.0", + "changes": [ + { + "note": "Updated implementation of `generatePseudoRandomSalt` to use generator from @0x/utils", + "pr": 1569 + } + ] + }, { "version": "6.0.1", "changes": [ diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index 49dbe5a64..bf1dedc00 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "5.0.0", + "changes": [ + { + "note": "Export `DecodedCalldata` from @0x/utils", + "pr": 1569 + } + ] + }, { "version": "4.0.2", "changes": [ -- cgit v1.2.3