aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabio B <kandinsky454@protonmail.ch>2019-02-09 22:29:20 +0800
committerGitHub <noreply@github.com>2019-02-09 22:29:20 +0800
commit2a772b71f16179af16d549b847ae8343ee65e913 (patch)
tree97fa73654ad9b394050b134d91b4b5ed6ed552d1
parent629a8d632801c4861824b7d0423792d167b8564d (diff)
parent233869ed80a1f2836834d20adbc478d2e9b26b54 (diff)
downloaddexon-0x-contracts-2a772b71f16179af16d549b847ae8343ee65e913.tar.gz
dexon-0x-contracts-2a772b71f16179af16d549b847ae8343ee65e913.tar.zst
dexon-0x-contracts-2a772b71f16179af16d549b847ae8343ee65e913.zip
Merge pull request #1569 from 0xProject/feature/utils/decodeArbitrary0xCalldata
0x Transaction Data Decoder
-rw-r--r--packages/0x.js/CHANGELOG.json9
-rw-r--r--packages/0x.js/src/index.ts12
-rw-r--r--packages/contract-wrappers/CHANGELOG.json9
-rw-r--r--packages/contract-wrappers/src/contract_wrappers.ts12
-rw-r--r--packages/contract-wrappers/src/index.ts7
-rw-r--r--packages/contract-wrappers/src/utils/transaction_encoder.ts17
-rw-r--r--packages/contract-wrappers/test/calldata_decoder_test.ts127
-rw-r--r--packages/order-utils/CHANGELOG.json9
-rw-r--r--packages/order-utils/src/salt.ts10
-rw-r--r--packages/utils/CHANGELOG.json9
-rw-r--r--packages/utils/package.json2
-rw-r--r--packages/utils/src/abi_decoder.ts124
-rw-r--r--packages/utils/src/address_utils.ts11
-rw-r--r--packages/utils/src/index.ts2
-rw-r--r--packages/utils/src/random.ts16
-rw-r--r--packages/utils/src/types.ts19
-rw-r--r--packages/utils/test/abi_decoder_test.ts50
-rw-r--r--packages/web3-wrapper/CHANGELOG.json9
-rw-r--r--packages/web3-wrapper/src/index.ts2
19 files changed, 425 insertions, 31 deletions
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,5 +1,14 @@
[
{
+ "version": "5.0.0",
+ "changes": [
+ {
+ "note": "Export `transactionHashUtils`, `DecodedCalldata`, `ZeroExTransaction`, and `SignedZeroExTransaction`",
+ "pr": 1569
+ }
+ ]
+ },
+ {
"version": "4.0.3",
"changes": [
{
diff --git a/packages/0x.js/src/index.ts b/packages/0x.js/src/index.ts
index 006e4cf29..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 } 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/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,5 +1,14 @@
[
{
+ "version": "7.1.0",
+ "changes": [
+ {
+ "note": "Added calldata decoding to ContractWrappers",
+ "pr": 1569
+ }
+ ]
+ },
+ {
"version": "7.0.2",
"changes": [
{
diff --git a/packages/contract-wrappers/src/contract_wrappers.ts b/packages/contract-wrappers/src/contract_wrappers.ts
index 4e594593e..f43dc5d26 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 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 69bbe3c91..5fc400edf 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, DecodedCalldata } from '@0x/utils';
+
export {
ContractWrappersError,
ForwarderWrapperError,
@@ -83,6 +85,11 @@ export {
JSONRPCResponseError,
AbiDefinition,
LogWithDecodedArgs,
+ LogEntry,
+ DecodedLogEntry,
+ DecodedLogEntryEvent,
+ LogEntryEvent,
+ RawLog,
FunctionAbi,
EventAbi,
EventParameter,
diff --git a/packages/contract-wrappers/src/utils/transaction_encoder.ts b/packages/contract-wrappers/src/utils/transaction_encoder.ts
index 307487a9b..0832ee73a 100644
--- a/packages/contract-wrappers/src/utils/transaction_encoder.ts
+++ b/packages/contract-wrappers/src/utils/transaction_encoder.ts
@@ -242,6 +242,23 @@ export class TransactionEncoder {
return abiEncodedData;
}
/**
+ * Encodes a matchOrders transaction.
+ * @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 {
+ assert.doesConformToSchema('leftOrder', leftOrder, schemas.orderSchema);
+ assert.doesConformToSchema('rightOrder', 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
* @param signerAddress Address that should have signed the given hash.
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..ba1539ef5
--- /dev/null
+++ b/packages/contract-wrappers/test/calldata_decoder_test.ts
@@ -0,0 +1,127 @@
+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;
+ const [privateKeyLeft, privateKeyRight] = constants.TESTRPC_PRIVATE_KEYS;
+ const exchangeAddress = addressUtils.generatePseudoRandomAddress();
+ const feeRecipientAddress = addressUtils.generatePseudoRandomAddress();
+ // Create orders to match.
+ // Values are arbitrary, with the exception of maker addresses (generated above).
+ 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()
+ .decodeCalldataOrThrow(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()
+ .decodeCalldataOrThrow(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().decodeCalldataOrThrow(badTxData);
+ }).to.throw("No functions registered for selector '0x01020304'");
+ });
+ });
+});
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,5 +1,14 @@
[
{
+ "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/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/CHANGELOG.json b/packages/utils/CHANGELOG.json
index 9ce2a4c52..95f61a43c 100644
--- a/packages/utils/CHANGELOG.json
+++ b/packages/utils/CHANGELOG.json
@@ -1,5 +1,14 @@
[
{
+ "version": "4.1.0",
+ "changes": [
+ {
+ "note": "Added method decoding to AbiDecoder",
+ "pr": 1569
+ }
+ ]
+ },
+ {
"version": "4.0.4",
"changes": [
{
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/abi_decoder.ts b/packages/utils/src/abi_decoder.ts
index 28b6418d8..b764e45b8 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 { 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
* 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 _selectorToFunctionInfo: SelectorToFunctionInfo = {};
+ /**
+ * 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 calldata. 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.
@@ -35,12 +56,12 @@ export class AbiDecoder {
* @return The decoded log if the requisite ABI was available. Otherwise the log unaltered.
*/
public tryToDecodeLogOrNoop<ArgsType extends DecodedLogArgs>(log: LogEntry): LogWithDecodedArgs<ArgsType> | RawLog {
- const methodId = log.topics[0];
+ const eventId = log.topics[0];
const numIndexedArgs = log.topics.length - 1;
- if (_.isUndefined(this._methodIds[methodId]) || _.isUndefined(this._methodIds[methodId][numIndexedArgs])) {
+ if (_.isUndefined(this._eventIds[eventId]) || _.isUndefined(this._eventIds[eventId][numIndexedArgs])) {
return log;
}
- const event = this._methodIds[methodId][numIndexedArgs];
+ const event = this._eventIds[eventId][numIndexedArgs];
const ethersInterface = new ethers.utils.Interface([event]);
const decodedParams: DecodedLogArgs = {};
let topicsIndex = 1;
@@ -89,25 +110,94 @@ export class AbiDecoder {
}
}
/**
- * Add additional ABI definitions to the AbiDecoder
- * @param abiArray An array of ABI definitions to add to the AbiDecoder
+ * Decodes calldata for a known ABI.
+ * @param calldata hex-encoded calldata.
+ * @param contractName used to disambiguate similar ABI's (optional).
+ * @return Decoded calldata. Includes: function name and signature, along with the decoded arguments.
*/
- public addABI(abiArray: AbiDefinition[]): void {
+ public decodeCalldataOrThrow(calldata: string, contractName?: string): DecodedCalldata {
+ const functionSelector = AbiDecoder._getFunctionSelector(calldata);
+ const candidateFunctionInfos = this._selectorToFunctionInfo[functionSelector];
+ if (_.isUndefined(candidateFunctionInfos)) {
+ throw new Error(`No functions registered for selector '${functionSelector}'`);
+ }
+ const functionInfo = _.find(candidateFunctionInfos, candidateFunctionInfo => {
+ return (
+ _.isUndefined(contractName) || _.toLower(contractName) === _.toLower(candidateFunctionInfo.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 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)) {
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:
+ // tslint:disable-next-line:no-unnecessary-type-assertion
+ this._addEventABI(abi as EventAbi, ethersInterface);
+ break;
+
+ case AbiType.Function:
+ // tslint:disable-next-line:no-unnecessary-type-assertion
+ this._addMethodABI(abi as MethodAbi, contractName);
+ break;
+
+ default:
+ // ignore other types
+ break;
}
});
}
+ 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] = {
+ ...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._selectorToFunctionInfo)) {
+ this._selectorToFunctionInfo[functionSelector] = [];
+ }
+ // Recored a copy of this ABI for each deployment
+ const functionSignature = abiEncoder.getSignature();
+ this._selectorToFunctionInfo[functionSelector].push({
+ functionSignature,
+ abiEncoder,
+ contractName,
+ });
+ }
}
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 082aff6bb..f9c2693fe 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -11,3 +11,5 @@ export { errorUtils } from './error_utils';
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;
+}
diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts
new file mode 100644
index 000000000..32e11efa2
--- /dev/null
+++ b/packages/utils/src/types.ts
@@ -0,0 +1,19 @@
+import { AbiEncoder } from '.';
+
+export interface FunctionInfo {
+ functionSignature: string;
+ contractName?: string;
+ contractAddress?: string;
+ networkId?: number;
+ abiEncoder?: AbiEncoder.Method;
+}
+
+export interface SelectorToFunctionInfo {
+ [index: string]: FunctionInfo[];
+}
+
+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
new file mode 100644
index 000000000..81fed1060
--- /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 { AbiDecoder, AbiEncoder } 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.decodeCalldataOrThrow(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/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,5 +1,14 @@
[
{
+ "version": "5.0.0",
+ "changes": [
+ {
+ "note": "Export `DecodedCalldata` from @0x/utils",
+ "pr": 1569
+ }
+ ]
+ },
+ {
"version": "4.0.2",
"changes": [
{
diff --git a/packages/web3-wrapper/src/index.ts b/packages/web3-wrapper/src/index.ts
index 4d20ba9be..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 } from '@0x/utils';
+export { AbiDecoder, DecodedCalldata } from '@0x/utils';
export {
BlockParam,