aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src/abi_encoder
diff options
context:
space:
mode:
authorGreg Hysen <greg.hysen@gmail.com>2019-02-07 03:56:49 +0800
committerGreg Hysen <greg.hysen@gmail.com>2019-02-08 07:00:07 +0800
commit4079563f5dca96477bb13935c1cc30ad8224ae4c (patch)
tree7edf192cbeab39a21e115a8d0848868f13552212 /packages/utils/src/abi_encoder
parent3939d516e67bd36a95beac1bca049842bff554e3 (diff)
downloaddexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.tar
dexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.tar.gz
dexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.tar.bz2
dexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.tar.lz
dexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.tar.xz
dexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.tar.zst
dexon-0x-contracts-4079563f5dca96477bb13935c1cc30ad8224ae4c.zip
More robust/simple signature parsing, using a parse tree
Diffstat (limited to 'packages/utils/src/abi_encoder')
-rw-r--r--packages/utils/src/abi_encoder/evm_data_type_factory.ts87
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/method.ts5
-rw-r--r--packages/utils/src/abi_encoder/index.ts1
-rw-r--r--packages/utils/src/abi_encoder/utils/signature_parser.ts151
4 files changed, 146 insertions, 98 deletions
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 268649148..613eb887f 100644
--- a/packages/utils/src/abi_encoder/evm_data_type_factory.ts
+++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts
@@ -2,7 +2,7 @@
import { DataItem, MethodAbi } from 'ethereum-types';
import * as _ from 'lodash';
-import { generateDataItemsFromSignature } from './utils/signature_parser';
+import { generateDataItemFromSignature } from './utils/signature_parser';
import { DataType } from './abstract_data_types/data_type';
import { DataTypeFactory } from './abstract_data_types/interfaces';
@@ -134,32 +134,87 @@ export class EvmDataTypeFactory implements DataTypeFactory {
/**
* 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'}]
+ * @param input A single or set of DataItem or a signature for an EVM data type.
* @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`
+ const dataItem = consolidateDataItemsIntoSingle(input);
+ const dataType = EvmDataTypeFactory.getInstance().create(dataItem);
+ return dataType;
+}
+
+/**
+ * Convenience function to aggregate a single input or a set of inputs into a single DataItem.
+ * An array of data items is grouped into a single tuple.
+ * @param input A single data item; a set of data items; a signature.
+ * @return A single data item corresponding to input.
+ */
+function consolidateDataItemsIntoSingle(input: DataItem | DataItem[] | string): DataItem {
let dataItem: DataItem;
- if (shouldParseAsTuple) {
- const dataItems = isSignature ? generateDataItemsFromSignature(input as string) : (input as DataItem[]);
+ if (_.isArray(input)) {
+ const dataItems = input as DataItem[];
dataItem = {
name: '',
type: 'tuple',
components: dataItems,
};
} else {
- dataItem = isSignature ? generateDataItemsFromSignature(input as string)[0] : (input as DataItem);
+ dataItem = typeof input === 'string' ? generateDataItemFromSignature(input) : (input as DataItem);
}
- // Create data type
- const dataType = EvmDataTypeFactory.getInstance().create(dataItem);
+ return dataItem;
+}
+
+/**
+ * Convenience function for creating a Method encoder from different inputs.
+ * @param methodName name of method.
+ * @param input A single data item; a set of data items; a signature; or an array of signatures (optional).
+ * @param output A single data item; a set of data items; a signature; or an array of signatures (optional).
+ * @return Method corresponding to input.
+ */
+export function createMethod(
+ methodName: string,
+ input?: DataItem | DataItem[] | string | string[],
+ output?: DataItem | DataItem[] | string | string[],
+): Method {
+ const methodInput = _.isUndefined(input) ? [] : consolidateDataItemsIntoArray(input);
+ const methodOutput = _.isUndefined(output) ? [] : consolidateDataItemsIntoArray(output);
+ const methodAbi: MethodAbi = {
+ name: methodName,
+ inputs: methodInput,
+ outputs: methodOutput,
+ type: 'function',
+ // default fields not used by ABI
+ constant: false,
+ payable: false,
+ stateMutability: 'nonpayable',
+ };
+ const dataType = new Method(methodAbi);
return dataType;
}
+
+/**
+ * Convenience function that aggregates a single input or a set of inputs into an array of DataItems.
+ * @param input A single data item; a set of data items; a signature; or an array of signatures.
+ * @return Array of data items corresponding to input.
+ */
+function consolidateDataItemsIntoArray(input: DataItem | DataItem[] | string | string[]): DataItem[] {
+ let dataItems: DataItem[];
+ if (_.isArray(input) && _.isEmpty(input)) {
+ dataItems = [];
+ } else if (_.isArray(input) && typeof input[0] === 'string') {
+ dataItems = [];
+ _.each(input as string[], (signature: string) => {
+ const dataItem = generateDataItemFromSignature(signature);
+ dataItems.push(dataItem);
+ });
+ } else if (_.isArray(input)) {
+ dataItems = input as DataItem[];
+ } else if (typeof input === 'string') {
+ const dataItem = generateDataItemFromSignature(input);
+ dataItems = [dataItem];
+ } else {
+ dataItems = [input as DataItem];
+ }
+ return dataItems;
+}
/* tslint:enable no-construct */
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 c852a0fdf..93746fa00 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/method.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts
@@ -65,6 +65,11 @@ export class MethodDataType extends AbstractSetDataType {
return this._methodSelector;
}
+ public getReturnValueDataItem(): DataItem {
+ const returnValueDataItem = this._returnDataType.getDataItem();
+ return returnValueDataItem;
+ }
+
private _computeSignature(): string {
const memberSignature = this._computeSignatureOfMembers();
const methodSignature = `${this.getDataItem().name}${memberSignature}`;
diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts
index cfacfe075..976bac8e6 100644
--- a/packages/utils/src/abi_encoder/index.ts
+++ b/packages/utils/src/abi_encoder/index.ts
@@ -12,5 +12,6 @@ export {
Tuple,
UInt,
create,
+ createMethod,
} from './evm_data_type_factory';
export { DataType } from './abstract_data_types/data_type';
diff --git a/packages/utils/src/abi_encoder/utils/signature_parser.ts b/packages/utils/src/abi_encoder/utils/signature_parser.ts
index 315784cea..d3996bf8e 100644
--- a/packages/utils/src/abi_encoder/utils/signature_parser.ts
+++ b/packages/utils/src/abi_encoder/utils/signature_parser.ts
@@ -1,101 +1,88 @@
import { DataItem } from 'ethereum-types';
import * as _ from 'lodash';
+interface Node {
+ name: string;
+ value: string;
+ children: Node[];
+ parent?: Node;
+}
+
+function parseNode(node: Node): DataItem {
+ const components: DataItem[] = [];
+ _.each(node.children, (child: Node) => {
+ const component = parseNode(child);
+ components.push(component);
+ });
+ const dataItem: DataItem = {
+ name: node.name,
+ type: node.value,
+ };
+ if (!_.isEmpty(components)) {
+ dataItem.components = components;
+ }
+ return dataItem;
+}
+
/**
- * 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
+ * Returns a DataItem corresponding to the input signature.
+ * A signature can be in two forms: `type` or `(type_1,type_2,...,type_n)`
+ * An example of the first form would be 'address' or 'uint256[]' or 'bytes[5][]'
+ * An example of the second form would be '(address,uint256)' or '(address,uint256)[]'
+ * @param signature of input DataItem.
+ * @return DataItem 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);
+export function generateDataItemFromSignature(signature: string): DataItem {
+ // No data item corresponds to an empty signature
+ if (_.isEmpty(signature)) {
+ throw new Error(`Cannot parse data item from empty signature, ''`);
}
- 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.
+ // Create a parse tree for data item
+ let node: Node = {
+ name: '',
+ value: '',
+ children: [],
+ };
+ for (const char of signature) {
switch (char) {
case '(':
- parenCount += 1;
- currToken += char;
+ const child = {
+ name: '',
+ value: '',
+ children: [],
+ parent: node,
+ };
+ node.value = 'tuple';
+ node.children.push(child);
+ node = child;
break;
+
case ')':
- parenCount -= 1;
- currToken += char;
+ node = node.parent as Node;
break;
- case '[':
- if (parenCount === 0) {
- isParsingArrayModifier = true;
- isCurrTokenArray = true;
- currTokenArrayModifier += '[';
- } else {
- currToken += char;
- }
- break;
- case ']':
- if (parenCount === 0) {
- isParsingArrayModifier = false;
- currTokenArrayModifier += ']';
- } else {
- currToken += char;
- }
+
+ case ',':
+ const sibling = {
+ name: '',
+ value: '',
+ children: [],
+ parent: node.parent,
+ };
+ (node.parent as Node).children.push(sibling);
+ node = sibling;
break;
+
case ' ':
- if (parenCount === 0) {
- currTokenName = currToken;
- currToken = '';
- } else {
- currToken += char;
- }
+ node.name = node.value;
+ node.value = '';
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;
- }
+ node.value += char;
break;
}
}
- return dataItems;
+ // Interpret data item from parse tree
+ const dataItem = parseNode(node);
+ return dataItem;
}