aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/utils/src')
-rw-r--r--packages/utils/src/abi_decoder.ts33
-rw-r--r--packages/utils/src/abi_utils.ts15
-rw-r--r--packages/utils/src/index.ts1
-rw-r--r--packages/utils/src/monorepo_scripts/postpublish.ts8
-rw-r--r--packages/utils/src/sign_typed_data_utils.ts82
5 files changed, 119 insertions, 20 deletions
diff --git a/packages/utils/src/abi_decoder.ts b/packages/utils/src/abi_decoder.ts
index cc05321ab..2da46db35 100644
--- a/packages/utils/src/abi_decoder.ts
+++ b/packages/utils/src/abi_decoder.ts
@@ -15,12 +15,25 @@ import * as _ from 'lodash';
import { addressUtils } from './address_utils';
import { BigNumber } from './configured_bignumber';
+/**
+ * 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 } } = {};
+ /**
+ * Instantiate an AbiDecoder
+ * @param abiArrays An array of contract ABI's
+ * @return AbiDecoder instance
+ */
constructor(abiArrays: AbiDefinition[][]) {
_.forEach(abiArrays, this.addABI.bind(this));
}
- // This method can only decode logs from the 0x & ERC20 smart contracts
+ /**
+ * Attempt to decode a log given the ABI's the AbiDecoder knows about.
+ * @param log The log to attempt to decode
+ * @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 numIndexedArgs = log.topics.length - 1;
@@ -28,13 +41,13 @@ export class AbiDecoder {
return log;
}
const event = this._methodIds[methodId][numIndexedArgs];
- const ethersInterface = new ethers.Interface([event]);
+ const ethersInterface = new ethers.utils.Interface([event]);
const decodedParams: DecodedLogArgs = {};
let topicsIndex = 1;
let decodedData: any[];
try {
- decodedData = ethersInterface.events[event.name].parse(log.data);
+ decodedData = ethersInterface.events[event.name].decode(log.data);
} catch (error) {
if (error.code === ethers.errors.INVALID_ARGUMENT) {
// Because we index events by Method ID, and Method IDs are derived from the method
@@ -75,18 +88,24 @@ export class AbiDecoder {
};
}
}
+ /**
+ * Add additional ABI definitions to the AbiDecoder
+ * @param abiArray An array of ABI definitions to add to the AbiDecoder
+ */
public addABI(abiArray: AbiDefinition[]): void {
if (_.isUndefined(abiArray)) {
return;
}
- const ethersInterface = new ethers.Interface(abiArray);
+ const ethersInterface = new ethers.utils.Interface(abiArray);
_.map(abiArray, (abi: AbiDefinition) => {
if (abi.type === AbiType.Event) {
- const topic = ethersInterface.events[abi.name].topics[0];
- const numIndexedArgs = _.reduce(abi.inputs, (sum, input) => (input.indexed ? sum + 1 : sum), 0);
+ // 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]: abi,
+ [numIndexedArgs]: eventAbi,
};
}
});
diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts
index c9b70966c..598ea5fcc 100644
--- a/packages/utils/src/abi_utils.ts
+++ b/packages/utils/src/abi_utils.ts
@@ -1,14 +1,19 @@
import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types';
-import * as ethers from 'ethers';
import * as _ from 'lodash';
import { BigNumber } from './configured_bignumber';
+type ParamName = null | string | NestedParamName;
+interface NestedParamName {
+ name: string | null;
+ names: ParamName[];
+}
+
// Note(albrow): This function is unexported in ethers.js. Copying it here for
// now.
// Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30
-function parseEthersParams(params: DataItem[]): { names: ethers.ParamName[]; types: string[] } {
- const names: ethers.ParamName[] = [];
+function parseEthersParams(params: DataItem[]): { names: ParamName[]; types: string[] } {
+ const names: ParamName[] = [];
const types: string[] = [];
params.forEach((param: DataItem) => {
@@ -37,7 +42,7 @@ function parseEthersParams(params: DataItem[]): { names: ethers.ParamName[]; typ
// returns true if x is equal to y and false otherwise. Performs some minimal
// type conversion and data massaging for x and y, depending on type. name and
// type should typically be derived from parseEthersParams.
-function isAbiDataEqual(name: ethers.ParamName, type: string, x: any, y: any): boolean {
+function isAbiDataEqual(name: ParamName, type: string, x: any, y: any): boolean {
if (_.isUndefined(x) && _.isUndefined(y)) {
return true;
} else if (_.isUndefined(x) && !_.isUndefined(y)) {
@@ -89,7 +94,7 @@ function isAbiDataEqual(name: ethers.ParamName, type: string, x: any, y: any): b
//
const nestedName = _.isString(name.names[i])
? (name.names[i] as string)
- : ((name.names[i] as ethers.NestedParamName).name as string);
+ : ((name.names[i] as NestedParamName).name as string);
if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) {
return false;
}
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 9d01e5bc5..0723e5788 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -9,3 +9,4 @@ export { abiUtils } from './abi_utils';
export { NULL_BYTES } from './constants';
export { errorUtils } from './error_utils';
export { fetchAsync } from './fetch_async';
+export { signTypedDataUtils } from './sign_typed_data_utils';
diff --git a/packages/utils/src/monorepo_scripts/postpublish.ts b/packages/utils/src/monorepo_scripts/postpublish.ts
deleted file mode 100644
index dcb99d0f7..000000000
--- a/packages/utils/src/monorepo_scripts/postpublish.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { postpublishUtils } from '@0xproject/monorepo-scripts';
-
-import * as packageJSON from '../package.json';
-import * as tsConfigJSON from '../tsconfig.json';
-
-const cwd = `${__dirname}/..`;
-// tslint:disable-next-line:no-floating-promises
-postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd);
diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts
new file mode 100644
index 000000000..6963b9084
--- /dev/null
+++ b/packages/utils/src/sign_typed_data_utils.ts
@@ -0,0 +1,82 @@
+import * as ethUtil from 'ethereumjs-util';
+import * as ethers from 'ethers';
+import * as _ from 'lodash';
+
+import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0x/types';
+
+export const signTypedDataUtils = {
+ /**
+ * Generates the EIP712 Typed Data hash for signing
+ * @param typedData An object that conforms to the EIP712TypedData interface
+ * @return A Buffer containing the hash of the typed data.
+ */
+ generateTypedDataHash(typedData: EIP712TypedData): Buffer {
+ return ethUtil.sha3(
+ Buffer.concat([
+ Buffer.from('1901', 'hex'),
+ signTypedDataUtils._structHash('EIP712Domain', typedData.domain, typedData.types),
+ signTypedDataUtils._structHash(typedData.primaryType, typedData.message, typedData.types),
+ ]),
+ );
+ },
+ _findDependencies(primaryType: string, types: EIP712Types, found: string[] = []): string[] {
+ if (found.includes(primaryType) || types[primaryType] === undefined) {
+ return found;
+ }
+ found.push(primaryType);
+ for (const field of types[primaryType]) {
+ for (const dep of signTypedDataUtils._findDependencies(field.type, types, found)) {
+ if (!found.includes(dep)) {
+ found.push(dep);
+ }
+ }
+ }
+ return found;
+ },
+ _encodeType(primaryType: string, types: EIP712Types): string {
+ let deps = signTypedDataUtils._findDependencies(primaryType, types);
+ deps = deps.filter(d => d !== primaryType);
+ deps = [primaryType].concat(deps.sort());
+ let result = '';
+ for (const dep of deps) {
+ result += `${dep}(${types[dep].map(({ name, type }) => `${type} ${name}`).join(',')})`;
+ }
+ return result;
+ },
+ _encodeData(primaryType: string, data: EIP712Object, types: EIP712Types): string {
+ const encodedTypes = ['bytes32'];
+ const encodedValues: Array<Buffer | EIP712ObjectValue> = [signTypedDataUtils._typeHash(primaryType, types)];
+ for (const field of types[primaryType]) {
+ const value = data[field.name];
+ if (field.type === 'string' || field.type === 'bytes') {
+ const hashValue = ethUtil.sha3(value as string);
+ encodedTypes.push('bytes32');
+ encodedValues.push(hashValue);
+ } else if (types[field.type] !== undefined) {
+ encodedTypes.push('bytes32');
+ const hashValue = ethUtil.sha3(
+ // tslint:disable-next-line:no-unnecessary-type-assertion
+ signTypedDataUtils._encodeData(field.type, value as EIP712Object, types),
+ );
+ encodedValues.push(hashValue);
+ } else if (field.type.lastIndexOf(']') === field.type.length - 1) {
+ throw new Error('Arrays currently unimplemented in encodeData');
+ } else {
+ encodedTypes.push(field.type);
+ const normalizedValue = signTypedDataUtils._normalizeValue(field.type, value);
+ encodedValues.push(normalizedValue);
+ }
+ }
+ return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues);
+ },
+ _normalizeValue(type: string, value: any): EIP712ObjectValue {
+ const normalizedValue = type === 'uint256' && _.isObject(value) && value.isBigNumber ? value.toString() : value;
+ return normalizedValue;
+ },
+ _typeHash(primaryType: string, types: EIP712Types): Buffer {
+ return ethUtil.sha3(signTypedDataUtils._encodeType(primaryType, types));
+ },
+ _structHash(primaryType: string, data: EIP712Object, types: EIP712Types): Buffer {
+ return ethUtil.sha3(signTypedDataUtils._encodeData(primaryType, data, types));
+ },
+};