diff options
Diffstat (limited to 'packages/utils/src')
17 files changed, 247 insertions, 41 deletions
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts index 13cc87e2a..f23324721 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts @@ -51,8 +51,26 @@ export abstract class DataType { return value; } + public decodeAsArray(returndata: string, rules?: DecodingRules): any[] { + const value = this.decode(returndata, rules); + const valuesAsArray = _.isObject(value) ? _.values(value) : [value]; + return valuesAsArray; + } + + public getSignature(isDetailed?: boolean): string { + if (_.isEmpty(this._dataItem.name) || !isDetailed) { + return this.getSignatureType(); + } + const name = this.getDataItem().name; + const lastIndexOfScopeDelimiter = name.lastIndexOf('.'); + const isScopedName = !_.isUndefined(lastIndexOfScopeDelimiter) && lastIndexOfScopeDelimiter > 0; + const shortName = isScopedName ? name.substr((lastIndexOfScopeDelimiter as number) + 1) : name; + const detailedSignature = `${shortName} ${this.getSignatureType()}`; + return detailedSignature; + } + public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; public abstract generateValue(calldata: RawCalldata, rules: DecodingRules): any; - public abstract getSignature(): string; + public abstract getSignatureType(): string; public abstract isStatic(): boolean; } diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts index 00059a4b6..2c6c4b0f6 100644 --- a/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts +++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts @@ -1,3 +1,4 @@ +import { ObjectMap } from '@0x/types'; import { DataItem } from 'ethereum-types'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; @@ -134,31 +135,26 @@ export abstract class AbstractSetDataType extends DataType { const block = new SetCalldataBlock(this.getDataItem().name, this.getSignature(), parentName); // Create blocks for members of set. const memberCalldataBlocks: CalldataBlock[] = []; - const childMap = _.cloneDeep(this._memberIndexByName); - _.forOwn(obj, (value: any, key: string) => { - if (!(key in childMap)) { + _.forEach(this._memberIndexByName, (memberIndex: number, memberName: string) => { + if (!(memberName in obj)) { throw new Error( - `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`, + `Could not assign tuple to object: missing key '${memberName}' in object ${JSON.stringify(obj)}`, ); } - const memberBlock = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, block); + const memberValue: any = (obj as ObjectMap<any>)[memberName]; + const memberBlock = this._members[memberIndex].generateCalldataBlock(memberValue, block); memberCalldataBlocks.push(memberBlock); - delete childMap[key]; }); - // Sanity check that all members have been included. - if (Object.keys(childMap).length !== 0) { - throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); - } // Associate member blocks with Set block. block.setMembers(memberCalldataBlocks); return block; } - protected _computeSignatureOfMembers(): string { + protected _computeSignatureOfMembers(isDetailed?: boolean): string { // Compute signature of members let signature = `(`; _.each(this._members, (member: DataType, i: number) => { - signature += member.getSignature(); + signature += member.getSignature(isDetailed); if (i < this._members.length - 1) { signature += ','; } @@ -179,17 +175,27 @@ export abstract class AbstractSetDataType extends DataType { // Create one member for each component of `dataItem` const members: DataType[] = []; const memberIndexByName: MemberIndexByName = {}; + const memberNames: string[] = []; _.each(dataItem.components, (memberItem: DataItem) => { + // If a component with `name` already exists then + // rename to `name_nameIdx` to avoid naming conflicts. + let memberName = memberItem.name; + let nameIdx = 0; + while (_.includes(memberNames, memberName) || _.isEmpty(memberName)) { + nameIdx++; + memberName = `${memberItem.name}_${nameIdx}`; + } + memberNames.push(memberName); const childDataItem: DataItem = { type: memberItem.type, - name: `${dataItem.name}.${memberItem.name}`, + name: `${dataItem.name}.${memberName}`, }; const components = memberItem.components; if (!_.isUndefined(components)) { childDataItem.components = components; } const child = this.getFactory().create(childDataItem, this); - memberIndexByName[memberItem.name] = members.length; + memberIndexByName[memberName] = members.length; members.push(child); }); return [members, memberIndexByName]; diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts index 4cc124e0a..268649148 100644 --- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts +++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts @@ -2,6 +2,8 @@ import { DataItem, MethodAbi } from 'ethereum-types'; import * as _ from 'lodash'; +import { generateDataItemsFromSignature } from './utils/signature_parser'; + import { DataType } from './abstract_data_types/data_type'; import { DataTypeFactory } from './abstract_data_types/interfaces'; import { AddressDataType } from './evm_data_types/address'; @@ -129,4 +131,35 @@ export class EvmDataTypeFactory implements DataTypeFactory { private constructor() {} } + +/** + * Convenience function for creating a DataType from different inputs. + * @param input A single or set of DataItem or a DataType signature. + * A signature in the form of '<type>' is interpreted as a `DataItem` + * For example, 'string' is interpreted as {type: 'string'} + * A signature in the form '(<type1>, <type2>, ..., <typen>)' is interpreted as `DataItem[]` + * For eaxmple, '(string, uint256)' is interpreted as [{type: 'string'}, {type: 'uint256'}] + * @return DataType corresponding to input. + */ +export function create(input: DataItem | DataItem[] | string): DataType { + // Handle different types of input + const isSignature = typeof input === 'string'; + const isTupleSignature = isSignature && (input as string).startsWith('('); + const shouldParseAsTuple = isTupleSignature || _.isArray(input); + // Create input `dataItem` + let dataItem: DataItem; + if (shouldParseAsTuple) { + const dataItems = isSignature ? generateDataItemsFromSignature(input as string) : (input as DataItem[]); + dataItem = { + name: '', + type: 'tuple', + components: dataItems, + }; + } else { + dataItem = isSignature ? generateDataItemsFromSignature(input as string)[0] : (input as DataItem); + } + // Create data type + const dataType = EvmDataTypeFactory.getInstance().create(dataItem); + return dataType; +} /* tslint:enable no-construct */ diff --git a/packages/utils/src/abi_encoder/evm_data_types/address.ts b/packages/utils/src/abi_encoder/evm_data_types/address.ts index 2e3a206c6..2278830eb 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/address.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts @@ -39,10 +39,11 @@ export class AddressDataType extends AbstractBlobDataType { const valueBufPadded = calldata.popWord(); const valueBuf = valueBufPadded.slice(AddressDataType._DECODED_ADDRESS_OFFSET_IN_BYTES); const value = ethUtil.bufferToHex(valueBuf); - return value; + const valueLowercase = _.toLower(value); + return valueLowercase; } - public getSignature(): string { + public getSignatureType(): string { return SolidityTypes.Address; } /* tslint:enable prefer-function-over-method */ diff --git a/packages/utils/src/abi_encoder/evm_data_types/array.ts b/packages/utils/src/abi_encoder/evm_data_types/array.ts index 7595cb667..d9607f47e 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/array.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts @@ -7,7 +7,6 @@ import { constants } from '../utils/constants'; export class ArrayDataType extends AbstractSetDataType { private static readonly _MATCHER = RegExp('^(.+)\\[([0-9]*)\\]$'); - private readonly _arraySignature: string; private readonly _elementType: string; public static matchType(type: string): boolean { @@ -35,25 +34,36 @@ export class ArrayDataType extends AbstractSetDataType { super(dataItem, dataTypeFactory, isArray, arrayLength, arrayElementType); // Set array properties this._elementType = arrayElementType; - this._arraySignature = this._computeSignature(); } - public getSignature(): string { - return this._arraySignature; + public getSignatureType(): string { + return this._computeSignature(false); } - private _computeSignature(): string { + public getSignature(isDetailed?: boolean): string { + if (_.isEmpty(this.getDataItem().name) || !isDetailed) { + return this.getSignatureType(); + } + const name = this.getDataItem().name; + const lastIndexOfScopeDelimiter = name.lastIndexOf('.'); + const isScopedName = !_.isUndefined(lastIndexOfScopeDelimiter) && lastIndexOfScopeDelimiter > 0; + const shortName = isScopedName ? name.substr((lastIndexOfScopeDelimiter as number) + 1) : name; + const detailedSignature = `${shortName} ${this._computeSignature(isDetailed)}`; + return detailedSignature; + } + + private _computeSignature(isDetailed?: boolean): string { // Compute signature for a single array element const elementDataItem: DataItem = { type: this._elementType, - name: 'N/A', + name: '', }; const elementComponents = this.getDataItem().components; if (!_.isUndefined(elementComponents)) { elementDataItem.components = elementComponents; } const elementDataType = this.getFactory().create(elementDataItem); - const elementSignature = elementDataType.getSignature(); + const elementSignature = elementDataType.getSignature(isDetailed); // Construct signature for array of type `element` if (_.isUndefined(this._arrayLength)) { return `${elementSignature}[]`; diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts index d713d5a94..7f91f34e6 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/bool.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts @@ -46,7 +46,7 @@ export class BoolDataType extends AbstractBlobDataType { return value; } - public getSignature(): string { + public getSignatureType(): string { return SolidityTypes.Bool; } /* tslint:enable prefer-function-over-method */ diff --git a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts index 5277efd6c..fa38b63c0 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts @@ -65,7 +65,7 @@ export class DynamicBytesDataType extends AbstractBlobDataType { return value; } - public getSignature(): string { + public getSignatureType(): string { return SolidityTypes.Bytes; } /* tslint:enable prefer-function-over-method */ diff --git a/packages/utils/src/abi_encoder/evm_data_types/int.ts b/packages/utils/src/abi_encoder/evm_data_types/int.ts index f1dcf5ea1..8d98e195b 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/int.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts @@ -47,13 +47,17 @@ export class IntDataType extends AbstractBlobDataType { return encodedValue; } - public decodeValue(calldata: RawCalldata): BigNumber { + public decodeValue(calldata: RawCalldata): BigNumber | number { const valueBuf = calldata.popWord(); const value = EncoderMath.safeDecodeNumericValue(valueBuf, this._minValue, this._maxValue); + const numberOfBytesInUint8 = 8; + if (this._width === numberOfBytesInUint8) { + return value.toNumber(); + } return value; } - public getSignature(): string { + public getSignatureType(): string { return `${SolidityTypes.Int}${this._width}`; } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts index b1cd1377f..c852a0fdf 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/method.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts @@ -44,7 +44,20 @@ export class MethodDataType extends AbstractSetDataType { return returnValues; } - public getSignature(): string { + public strictDecodeReturnValue<T>(returndata: string, rules?: DecodingRules): T { + const returnValues = this._returnDataType.decode(returndata, rules); + const returnValuesAsArray: any = _.isObject(returnValues) ? _.values(returnValues) : [returnValues]; + switch (returnValuesAsArray.length) { + case 0: + return undefined as any; + case 1: + return returnValuesAsArray[0]; + default: + return returnValuesAsArray; + } + } + + public getSignatureType(): string { return this._methodSignature; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts index 389e75927..250db7c64 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts @@ -11,7 +11,11 @@ export class PointerDataType extends AbstractPointerDataType { super(dataItem, dataTypeFactory, destDataType, parentDataType); } - public getSignature(): string { - return this._destination.getSignature(); + public getSignatureType(): string { + return this._destination.getSignature(false); + } + + public getSignature(isDetailed?: boolean): string { + return this._destination.getSignature(isDetailed); } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts index 2e371c505..cbf1957d7 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts @@ -36,7 +36,7 @@ export class StaticBytesDataType extends AbstractBlobDataType { this._width = StaticBytesDataType._decodeWidthFromType(dataItem.type); } - public getSignature(): string { + public getSignatureType(): string { // Note that `byte` reduces to `bytes1` return `${SolidityTypes.Bytes}${this._width}`; } diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts index 91a72ad3f..97ac46442 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/string.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts @@ -52,7 +52,7 @@ export class StringDataType extends AbstractBlobDataType { return value; } - public getSignature(): string { + public getSignatureType(): string { return SolidityTypes.String; } /* tslint:enable prefer-function-over-method */ diff --git a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts index 31593c882..5000c85e8 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/tuple.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts @@ -1,11 +1,10 @@ import { DataItem, SolidityTypes } from 'ethereum-types'; +import * as _ from 'lodash'; import { DataTypeFactory } from '../abstract_data_types/interfaces'; import { AbstractSetDataType } from '../abstract_data_types/types/set'; export class TupleDataType extends AbstractSetDataType { - private readonly _signature: string; - public static matchType(type: string): boolean { return type === SolidityTypes.Tuple; } @@ -15,10 +14,21 @@ export class TupleDataType extends AbstractSetDataType { if (!TupleDataType.matchType(dataItem.type)) { throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`); } - this._signature = this._computeSignatureOfMembers(); } - public getSignature(): string { - return this._signature; + public getSignatureType(): string { + return this._computeSignatureOfMembers(false); + } + + public getSignature(isDetailed?: boolean): string { + if (_.isEmpty(this.getDataItem().name) || !isDetailed) { + return this.getSignatureType(); + } + const name = this.getDataItem().name; + const lastIndexOfScopeDelimiter = name.lastIndexOf('.'); + const isScopedName = !_.isUndefined(lastIndexOfScopeDelimiter) && lastIndexOfScopeDelimiter > 0; + const shortName = isScopedName ? name.substr((lastIndexOfScopeDelimiter as number) + 1) : name; + const detailedSignature = `${shortName} ${this._computeSignatureOfMembers(isDetailed)}`; + return detailedSignature; } } diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts index 5180f0cf3..8e382e8dc 100644 --- a/packages/utils/src/abi_encoder/evm_data_types/uint.ts +++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts @@ -46,13 +46,17 @@ export class UIntDataType extends AbstractBlobDataType { return encodedValue; } - public decodeValue(calldata: RawCalldata): BigNumber { + public decodeValue(calldata: RawCalldata): BigNumber | number { const valueBuf = calldata.popWord(); const value = EncoderMath.safeDecodeNumericValue(valueBuf, UIntDataType._MIN_VALUE, this._maxValue); + const numberOfBytesInUint8 = 8; + if (this._width === numberOfBytesInUint8) { + return value.toNumber(); + } return value; } - public getSignature(): string { + public getSignatureType(): string { return `${SolidityTypes.Uint}${this._width}`; } } diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts index baf844ac6..cfacfe075 100644 --- a/packages/utils/src/abi_encoder/index.ts +++ b/packages/utils/src/abi_encoder/index.ts @@ -11,4 +11,6 @@ export { String, Tuple, UInt, + create, } from './evm_data_type_factory'; +export { DataType } from './abstract_data_types/data_type'; diff --git a/packages/utils/src/abi_encoder/utils/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts index 36de2dd4f..fc586f295 100644 --- a/packages/utils/src/abi_encoder/utils/constants.ts +++ b/packages/utils/src/abi_encoder/utils/constants.ts @@ -11,7 +11,7 @@ export const constants = { HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA: 0, // Disable no-object-literal-type-assertion so we can enforce cast /* tslint:disable no-object-literal-type-assertion */ - DEFAULT_DECODING_RULES: { shouldConvertStructsToObjects: false } as DecodingRules, + DEFAULT_DECODING_RULES: { shouldConvertStructsToObjects: true } as DecodingRules, DEFAULT_ENCODING_RULES: { shouldOptimize: true, shouldAnnotate: false } as EncodingRules, /* tslint:enable no-object-literal-type-assertion */ }; diff --git a/packages/utils/src/abi_encoder/utils/signature_parser.ts b/packages/utils/src/abi_encoder/utils/signature_parser.ts new file mode 100644 index 000000000..315784cea --- /dev/null +++ b/packages/utils/src/abi_encoder/utils/signature_parser.ts @@ -0,0 +1,101 @@ +import { DataItem } from 'ethereum-types'; +import * as _ from 'lodash'; + +/** + * Returns an array of DataItem's corresponding to the input signature. + * A signature can be in two forms: '<DataItem.type>' or '(<DataItem1.type>, <DataItem2.type>, ...) + * An example of the first form would be 'address' or 'uint256' + * An example of the second form would be '(address, uint256)' + * Signatures can also include a name field, for example: 'foo address' or '(foo address, bar uint256)' + * @param signature of input DataItems + * @return DataItems derived from input signature + */ +export function generateDataItemsFromSignature(signature: string): DataItem[] { + let trimmedSignature = signature; + if (signature.startsWith('(')) { + if (!signature.endsWith(')')) { + throw new Error(`Failed to generate data item. Must end with ')'`); + } + trimmedSignature = signature.substr(1, signature.length - 2); + } + trimmedSignature += ','; + let isCurrTokenArray = false; + let currTokenArrayModifier = ''; + let isParsingArrayModifier = false; + let currToken = ''; + let parenCount = 0; + let currTokenName = ''; + const dataItems: DataItem[] = []; + for (const char of trimmedSignature) { + // Tokenize the type string while keeping track of parentheses. + switch (char) { + case '(': + parenCount += 1; + currToken += char; + break; + case ')': + parenCount -= 1; + currToken += char; + break; + case '[': + if (parenCount === 0) { + isParsingArrayModifier = true; + isCurrTokenArray = true; + currTokenArrayModifier += '['; + } else { + currToken += char; + } + break; + case ']': + if (parenCount === 0) { + isParsingArrayModifier = false; + currTokenArrayModifier += ']'; + } else { + currToken += char; + } + break; + case ' ': + if (parenCount === 0) { + currTokenName = currToken; + currToken = ''; + } else { + currToken += char; + } + break; + case ',': + if (parenCount === 0) { + // Generate new DataItem from token + const components = currToken.startsWith('(') ? generateDataItemsFromSignature(currToken) : []; + const isTuple = !_.isEmpty(components); + const dataItem: DataItem = { name: currTokenName, type: '' }; + if (isTuple) { + dataItem.type = 'tuple'; + dataItem.components = components; + } else { + dataItem.type = currToken; + } + if (isCurrTokenArray) { + dataItem.type += currTokenArrayModifier; + } + dataItems.push(dataItem); + // reset token state + currTokenName = ''; + currToken = ''; + isCurrTokenArray = false; + currTokenArrayModifier = ''; + break; + } else { + currToken += char; + break; + } + default: + if (isParsingArrayModifier) { + currTokenArrayModifier += char; + } else { + currToken += char; + } + break; + } + } + return dataItems; +} |