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