aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils
diff options
context:
space:
mode:
Diffstat (limited to 'packages/utils')
-rw-r--r--packages/utils/CHANGELOG.json46
-rw-r--r--packages/utils/CHANGELOG.md12
-rw-r--r--packages/utils/package.json14
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/data_type.ts58
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts19
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts40
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts54
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/types/set.ts218
-rw-r--r--packages/utils/src/abi_encoder/calldata/blocks/blob.ts20
-rw-r--r--packages/utils/src/abi_encoder/calldata/blocks/pointer.ts61
-rw-r--r--packages/utils/src/abi_encoder/calldata/blocks/set.ts47
-rw-r--r--packages/utils/src/abi_encoder/calldata/calldata.ts245
-rw-r--r--packages/utils/src/abi_encoder/calldata/calldata_block.ts77
-rw-r--r--packages/utils/src/abi_encoder/calldata/iterator.ts114
-rw-r--r--packages/utils/src/abi_encoder/calldata/raw_calldata.ts82
-rw-r--r--packages/utils/src/abi_encoder/evm_data_type_factory.ts132
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/address.ts49
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/array.ts64
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/bool.ts53
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts72
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/int.ts59
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/method.ts72
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/pointer.ts17
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts78
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/string.ts59
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/tuple.ts24
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/uint.ts58
-rw-r--r--packages/utils/src/abi_encoder/index.ts14
-rw-r--r--packages/utils/src/abi_encoder/utils/constants.ts17
-rw-r--r--packages/utils/src/abi_encoder/utils/math.ts111
-rw-r--r--packages/utils/src/abi_encoder/utils/queue.ts39
-rw-r--r--packages/utils/src/abi_encoder/utils/rules.ts8
-rw-r--r--packages/utils/src/abi_utils.ts4
-rw-r--r--packages/utils/src/index.ts1
-rw-r--r--packages/utils/src/log_utils.ts5
-rw-r--r--packages/utils/test/abi_encoder/abi_samples/method_abis.ts780
-rw-r--r--packages/utils/test/abi_encoder/abi_samples/optimizer_abis.ts340
-rw-r--r--packages/utils/test/abi_encoder/abi_samples/return_value_abis.ts99
-rw-r--r--packages/utils/test/abi_encoder/evm_data_types_test.ts1007
-rw-r--r--packages/utils/test/abi_encoder/methods_test.ts366
-rw-r--r--packages/utils/test/abi_encoder/optimizer_test.ts262
-rw-r--r--packages/utils/test/abi_encoder/return_values_test.ts67
-rw-r--r--packages/utils/test/utils/chai_setup.ts13
43 files changed, 4970 insertions, 7 deletions
diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json
index 1ef16e112..eb94da8d6 100644
--- a/packages/utils/CHANGELOG.json
+++ b/packages/utils/CHANGELOG.json
@@ -1,5 +1,51 @@
[
{
+ "version": "2.1.1",
+ "changes": [
+ {
+ "note": "Add `should` prefix to names of properties in EncodingRules and DecodingRules",
+ "pr": 1363
+ }
+ ]
+ },
+ {
+ "version": "2.1.0",
+ "changes": [
+ {
+ "note": "Add `logWithTime` to `logUtils`",
+ "pr": 1461
+ }
+ ]
+ },
+ {
+ "version": "2.0.8",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ],
+ "timestamp": 1544739608
+ },
+ {
+ "version": "2.0.7",
+ "changes": [
+ {
+ "note":
+ "Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development."
+ }
+ ],
+ "timestamp": 1544570656
+ },
+ {
+ "timestamp": 1542821676,
+ "version": "2.0.6",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
"timestamp": 1542208198,
"version": "2.0.5",
"changes": [
diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md
index 9846dd344..c0437392d 100644
--- a/packages/utils/CHANGELOG.md
+++ b/packages/utils/CHANGELOG.md
@@ -5,6 +5,18 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
+## v2.0.8 - _December 13, 2018_
+
+ * Dependencies updated
+
+## v2.0.7 - _December 11, 2018_
+
+ * Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development.
+
+## v2.0.6 - _November 21, 2018_
+
+ * Dependencies updated
+
## v2.0.5 - _November 14, 2018_
* Dependencies updated
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 4b924226f..5ffec049a 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@0x/utils",
- "version": "2.0.5",
+ "version": "2.0.8",
"engines": {
"node": ">=6.12"
},
@@ -28,11 +28,14 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/utils/README.md",
"devDependencies": {
- "@0x/tslint-config": "^1.0.10",
+ "@0x/tslint-config": "^2.0.0",
"@types/detect-node": "2.0.0",
"@types/lodash": "4.14.104",
"@types/mocha": "^2.2.42",
"chai": "^4.0.1",
+ "chai-as-promised": "^7.1.0",
+ "chai-bignumber": "^2.0.1",
+ "dirty-chai": "^2.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^4.1.0",
"npm-run-all": "^4.1.2",
@@ -41,13 +44,14 @@
"typescript": "3.0.1"
},
"dependencies": {
- "@0x/types": "^1.2.1",
- "@0x/typescript-typings": "^3.0.4",
+ "@0x/types": "^1.4.1",
+ "@0x/typescript-typings": "^3.0.6",
"@types/node": "*",
"abortcontroller-polyfill": "^1.1.9",
"bignumber.js": "~4.1.0",
+ "chalk": "^2.4.1",
"detect-node": "2.0.3",
- "ethereum-types": "^1.1.2",
+ "ethereum-types": "^1.1.4",
"ethereumjs-util": "^5.1.1",
"ethers": "~4.0.4",
"isomorphic-fetch": "^2.2.1",
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
new file mode 100644
index 000000000..13cc87e2a
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts
@@ -0,0 +1,58 @@
+import { DataItem } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { Calldata } from '../calldata/calldata';
+import { CalldataBlock } from '../calldata/calldata_block';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+import { DecodingRules, EncodingRules } from '../utils/rules';
+
+import { DataTypeFactory } from './interfaces';
+
+export abstract class DataType {
+ private readonly _dataItem: DataItem;
+ private readonly _factory: DataTypeFactory;
+
+ constructor(dataItem: DataItem, factory: DataTypeFactory) {
+ this._dataItem = dataItem;
+ this._factory = factory;
+ }
+
+ public getDataItem(): DataItem {
+ return this._dataItem;
+ }
+
+ public getFactory(): DataTypeFactory {
+ return this._factory;
+ }
+
+ public encode(value: any, rules?: EncodingRules, selector?: string): string {
+ const rules_ = _.isUndefined(rules) ? constants.DEFAULT_ENCODING_RULES : rules;
+ const calldata = new Calldata(rules_);
+ if (!_.isUndefined(selector)) {
+ calldata.setSelector(selector);
+ }
+ const block = this.generateCalldataBlock(value);
+ calldata.setRoot(block);
+ const encodedCalldata = calldata.toString();
+ return encodedCalldata;
+ }
+
+ public decode(calldata: string, rules?: DecodingRules, selector?: string): any {
+ if (!_.isUndefined(selector) && !_.startsWith(calldata, selector)) {
+ throw new Error(
+ `Tried to decode calldata, but it was missing the function selector. Expected prefix '${selector}'. Got '${calldata}'.`,
+ );
+ }
+ const hasSelector = !_.isUndefined(selector);
+ const rawCalldata = new RawCalldata(calldata, hasSelector);
+ const rules_ = _.isUndefined(rules) ? constants.DEFAULT_DECODING_RULES : rules;
+ const value = this.generateValue(rawCalldata, rules_);
+ return value;
+ }
+
+ public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock;
+ public abstract generateValue(calldata: RawCalldata, rules: DecodingRules): any;
+ public abstract getSignature(): string;
+ public abstract isStatic(): boolean;
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts
new file mode 100644
index 000000000..2f2f60871
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts
@@ -0,0 +1,19 @@
+import { DataItem } from 'ethereum-types';
+
+import { RawCalldata } from '../calldata/raw_calldata';
+
+import { DataType } from './data_type';
+
+export interface DataTypeFactory {
+ create: (dataItem: DataItem, parentDataType?: DataType) => DataType;
+}
+
+export interface DataTypeStaticInterface {
+ matchType: (type: string) => boolean;
+ encodeValue: (value: any) => Buffer;
+ decodeValue: (rawCalldata: RawCalldata) => any;
+}
+
+export interface MemberIndexByName {
+ [key: string]: number;
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts
new file mode 100644
index 000000000..a091e55b9
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/types/blob.ts
@@ -0,0 +1,40 @@
+import { DataItem } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BlobCalldataBlock } from '../../calldata/blocks/blob';
+import { CalldataBlock } from '../../calldata/calldata_block';
+import { RawCalldata } from '../../calldata/raw_calldata';
+import { DecodingRules } from '../../utils/rules';
+
+import { DataType } from '../data_type';
+import { DataTypeFactory } from '../interfaces';
+
+export abstract class AbstractBlobDataType extends DataType {
+ protected _sizeKnownAtCompileTime: boolean;
+
+ public constructor(dataItem: DataItem, factory: DataTypeFactory, sizeKnownAtCompileTime: boolean) {
+ super(dataItem, factory);
+ this._sizeKnownAtCompileTime = sizeKnownAtCompileTime;
+ }
+
+ public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): BlobCalldataBlock {
+ const encodedValue = this.encodeValue(value);
+ const name = this.getDataItem().name;
+ const signature = this.getSignature();
+ const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName();
+ const block = new BlobCalldataBlock(name, signature, parentName, encodedValue);
+ return block;
+ }
+
+ public generateValue(calldata: RawCalldata, rules: DecodingRules): any {
+ const value = this.decodeValue(calldata);
+ return value;
+ }
+
+ public isStatic(): boolean {
+ return this._sizeKnownAtCompileTime;
+ }
+
+ public abstract encodeValue(value: any): Buffer;
+ public abstract decodeValue(calldata: RawCalldata): any;
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts
new file mode 100644
index 000000000..0f3c55280
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/types/pointer.ts
@@ -0,0 +1,54 @@
+import { DataItem } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { PointerCalldataBlock } from '../../calldata/blocks/pointer';
+import { CalldataBlock } from '../../calldata/calldata_block';
+import { RawCalldata } from '../../calldata/raw_calldata';
+import { constants } from '../../utils/constants';
+import { DecodingRules } from '../../utils/rules';
+
+import { DataType } from '../data_type';
+import { DataTypeFactory } from '../interfaces';
+
+export abstract class AbstractPointerDataType extends DataType {
+ protected _destination: DataType;
+ protected _parent: DataType;
+
+ public constructor(dataItem: DataItem, factory: DataTypeFactory, destination: DataType, parent: DataType) {
+ super(dataItem, factory);
+ this._destination = destination;
+ this._parent = parent;
+ }
+
+ public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PointerCalldataBlock {
+ if (_.isUndefined(parentBlock)) {
+ throw new Error(`DependentDataType requires a parent block to generate its block`);
+ }
+ const destinationBlock = this._destination.generateCalldataBlock(value, parentBlock);
+ const name = this.getDataItem().name;
+ const signature = this.getSignature();
+ const parentName = parentBlock.getName();
+ const block = new PointerCalldataBlock(name, signature, parentName, destinationBlock, parentBlock);
+ return block;
+ }
+
+ public generateValue(calldata: RawCalldata, rules: DecodingRules): any {
+ const destinationOffsetBuf = calldata.popWord();
+ const destinationOffsetHex = ethUtil.bufferToHex(destinationOffsetBuf);
+ const destinationOffsetRelative = parseInt(destinationOffsetHex, constants.HEX_BASE);
+ const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative);
+ const currentOffset = calldata.getOffset();
+ calldata.setOffset(destinationOffsetAbsolute);
+ const value = this._destination.generateValue(calldata, rules);
+ calldata.setOffset(currentOffset);
+ return value;
+ }
+
+ // Disable prefer-function-over-method for inherited abstract method.
+ /* tslint:disable prefer-function-over-method */
+ public isStatic(): boolean {
+ return true;
+ }
+ /* tslint:enable prefer-function-over-method */
+}
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
new file mode 100644
index 000000000..00059a4b6
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/types/set.ts
@@ -0,0 +1,218 @@
+import { DataItem } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { BigNumber } from '../../../configured_bignumber';
+import { SetCalldataBlock } from '../../calldata/blocks/set';
+import { CalldataBlock } from '../../calldata/calldata_block';
+import { RawCalldata } from '../../calldata/raw_calldata';
+import { constants } from '../../utils/constants';
+import { DecodingRules } from '../../utils/rules';
+
+import { DataType } from '../data_type';
+import { DataTypeFactory, MemberIndexByName } from '../interfaces';
+
+import { AbstractPointerDataType } from './pointer';
+
+export abstract class AbstractSetDataType extends DataType {
+ protected readonly _arrayLength: number | undefined;
+ protected readonly _arrayElementType: string | undefined;
+ private readonly _memberIndexByName: MemberIndexByName;
+ private readonly _members: DataType[];
+ private readonly _isArray: boolean;
+
+ public constructor(
+ dataItem: DataItem,
+ factory: DataTypeFactory,
+ isArray: boolean = false,
+ arrayLength?: number,
+ arrayElementType?: string,
+ ) {
+ super(dataItem, factory);
+ this._memberIndexByName = {};
+ this._members = [];
+ this._isArray = isArray;
+ this._arrayLength = arrayLength;
+ this._arrayElementType = arrayElementType;
+ if (isArray && !_.isUndefined(arrayLength)) {
+ [this._members, this._memberIndexByName] = this._createMembersWithLength(dataItem, arrayLength);
+ } else if (!isArray) {
+ [this._members, this._memberIndexByName] = this._createMembersWithKeys(dataItem);
+ }
+ }
+
+ public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): SetCalldataBlock {
+ const block =
+ value instanceof Array
+ ? this._generateCalldataBlockFromArray(value, parentBlock)
+ : this._generateCalldataBlockFromObject(value, parentBlock);
+ return block;
+ }
+
+ public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object {
+ let members = this._members;
+ // Case 1: This is an array of undefined length, which means that `this._members` was not
+ // populated in the constructor. So we must construct the set of members now.
+ if (this._isArray && _.isUndefined(this._arrayLength)) {
+ const arrayLengthBuf = calldata.popWord();
+ const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf);
+ const arrayLength = new BigNumber(arrayLengthHex, constants.HEX_BASE);
+ [members] = this._createMembersWithLength(this.getDataItem(), arrayLength.toNumber());
+ }
+ // Create a new scope in the calldata, before descending into the members of this set.
+ calldata.startScope();
+ let value: any[] | object;
+ if (rules.shouldConvertStructsToObjects && !this._isArray) {
+ // Construct an object with values for each member of the set.
+ value = {};
+ _.each(this._memberIndexByName, (idx: number, key: string) => {
+ const member = this._members[idx];
+ const memberValue = member.generateValue(calldata, rules);
+ (value as { [key: string]: any })[key] = memberValue;
+ });
+ } else {
+ // Construct an array with values for each member of the set.
+ value = [];
+ _.each(members, (member: DataType, idx: number) => {
+ const memberValue = member.generateValue(calldata, rules);
+ (value as any[]).push(memberValue);
+ });
+ }
+ // Close this scope and return tetheh value.
+ calldata.endScope();
+ return value;
+ }
+
+ public isStatic(): boolean {
+ // An array with an undefined length is never static.
+ if (this._isArray && _.isUndefined(this._arrayLength)) {
+ return false;
+ }
+ // If any member of the set is a pointer then the set is not static.
+ const dependentMember = _.find(this._members, (member: DataType) => {
+ return member instanceof AbstractPointerDataType;
+ });
+ const isStatic = _.isUndefined(dependentMember);
+ return isStatic;
+ }
+
+ protected _generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): SetCalldataBlock {
+ // Sanity check: if the set has a defined length then `value` must have the same length.
+ if (!_.isUndefined(this._arrayLength) && value.length !== this._arrayLength) {
+ throw new Error(
+ `Expected array of ${JSON.stringify(
+ this._arrayLength,
+ )} elements, but got array of length ${JSON.stringify(value.length)}`,
+ );
+ }
+ // Create a new calldata block for this set.
+ const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName();
+ const block = new SetCalldataBlock(this.getDataItem().name, this.getSignature(), parentName);
+ // If this set has an undefined length then set its header to be the number of elements.
+ let members = this._members;
+ if (this._isArray && _.isUndefined(this._arrayLength)) {
+ [members] = this._createMembersWithLength(this.getDataItem(), value.length);
+ const lenBuf = ethUtil.setLengthLeft(
+ ethUtil.toBuffer(`0x${value.length.toString(constants.HEX_BASE)}`),
+ constants.EVM_WORD_WIDTH_IN_BYTES,
+ );
+ block.setHeader(lenBuf);
+ }
+ // Create blocks for members of set.
+ const memberCalldataBlocks: CalldataBlock[] = [];
+ _.each(members, (member: DataType, idx: number) => {
+ const memberBlock = member.generateCalldataBlock(value[idx], block);
+ memberCalldataBlocks.push(memberBlock);
+ });
+ block.setMembers(memberCalldataBlocks);
+ return block;
+ }
+
+ protected _generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): SetCalldataBlock {
+ // Create a new calldata block for this set.
+ const parentName = _.isUndefined(parentBlock) ? '' : parentBlock.getName();
+ 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)) {
+ throw new Error(
+ `Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`,
+ );
+ }
+ const memberBlock = this._members[this._memberIndexByName[key]].generateCalldataBlock(value, 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 {
+ // Compute signature of members
+ let signature = `(`;
+ _.each(this._members, (member: DataType, i: number) => {
+ signature += member.getSignature();
+ if (i < this._members.length - 1) {
+ signature += ',';
+ }
+ });
+ signature += ')';
+ return signature;
+ }
+
+ private _createMembersWithKeys(dataItem: DataItem): [DataType[], MemberIndexByName] {
+ // Sanity check
+ if (_.isUndefined(dataItem.components)) {
+ throw new Error(
+ `Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${
+ dataItem.name
+ }'.`,
+ );
+ }
+ // Create one member for each component of `dataItem`
+ const members: DataType[] = [];
+ const memberIndexByName: MemberIndexByName = {};
+ _.each(dataItem.components, (memberItem: DataItem) => {
+ const childDataItem: DataItem = {
+ type: memberItem.type,
+ name: `${dataItem.name}.${memberItem.name}`,
+ };
+ const components = memberItem.components;
+ if (!_.isUndefined(components)) {
+ childDataItem.components = components;
+ }
+ const child = this.getFactory().create(childDataItem, this);
+ memberIndexByName[memberItem.name] = members.length;
+ members.push(child);
+ });
+ return [members, memberIndexByName];
+ }
+
+ private _createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberIndexByName] {
+ // Create `length` members, deriving the type from `dataItem`
+ const members: DataType[] = [];
+ const memberIndexByName: MemberIndexByName = {};
+ const range = _.range(length);
+ _.each(range, (idx: number) => {
+ const memberDataItem: DataItem = {
+ type: _.isUndefined(this._arrayElementType) ? '' : this._arrayElementType,
+ name: `${dataItem.name}[${idx.toString(constants.DEC_BASE)}]`,
+ };
+ const components = dataItem.components;
+ if (!_.isUndefined(components)) {
+ memberDataItem.components = components;
+ }
+ const memberType = this.getFactory().create(memberDataItem, this);
+ memberIndexByName[idx.toString(constants.DEC_BASE)] = members.length;
+ members.push(memberType);
+ });
+ return [members, memberIndexByName];
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/blocks/blob.ts b/packages/utils/src/abi_encoder/calldata/blocks/blob.ts
new file mode 100644
index 000000000..219ea6c61
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/blocks/blob.ts
@@ -0,0 +1,20 @@
+import { CalldataBlock } from '../calldata_block';
+
+export class BlobCalldataBlock extends CalldataBlock {
+ private readonly _blob: Buffer;
+
+ constructor(name: string, signature: string, parentName: string, blob: Buffer) {
+ const headerSizeInBytes = 0;
+ const bodySizeInBytes = blob.byteLength;
+ super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes);
+ this._blob = blob;
+ }
+
+ public toBuffer(): Buffer {
+ return this._blob;
+ }
+
+ public getRawData(): Buffer {
+ return this._blob;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts
new file mode 100644
index 000000000..72d6a3173
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/blocks/pointer.ts
@@ -0,0 +1,61 @@
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { constants } from '../../utils/constants';
+
+import { CalldataBlock } from '../calldata_block';
+
+export class PointerCalldataBlock extends CalldataBlock {
+ public static readonly RAW_DATA_START = new Buffer('<');
+ public static readonly RAW_DATA_END = new Buffer('>');
+ private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
+ private static readonly _EMPTY_HEADER_SIZE = 0;
+ private readonly _parent: CalldataBlock;
+ private readonly _dependency: CalldataBlock;
+ private _aliasFor: CalldataBlock | undefined;
+
+ constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) {
+ const headerSizeInBytes = PointerCalldataBlock._EMPTY_HEADER_SIZE;
+ const bodySizeInBytes = PointerCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES;
+ super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes);
+ this._parent = parent;
+ this._dependency = dependency;
+ this._aliasFor = undefined;
+ }
+
+ public toBuffer(): Buffer {
+ const destinationOffset = !_.isUndefined(this._aliasFor)
+ ? this._aliasFor.getOffsetInBytes()
+ : this._dependency.getOffsetInBytes();
+ const parentOffset = this._parent.getOffsetInBytes();
+ const parentHeaderSize = this._parent.getHeaderSizeInBytes();
+ const pointer: number = destinationOffset - (parentOffset + parentHeaderSize);
+ const pointerHex = `0x${pointer.toString(constants.HEX_BASE)}`;
+ const pointerBuf = ethUtil.toBuffer(pointerHex);
+ const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
+ return pointerBufPadded;
+ }
+
+ public getDependency(): CalldataBlock {
+ return this._dependency;
+ }
+
+ public setAlias(block: CalldataBlock): void {
+ this._aliasFor = block;
+ this._setName(`${this.getName()} (alias for ${block.getName()})`);
+ }
+
+ public getAlias(): CalldataBlock | undefined {
+ return this._aliasFor;
+ }
+
+ public getRawData(): Buffer {
+ const dependencyRawData = this._dependency.getRawData();
+ const rawDataComponents: Buffer[] = [];
+ rawDataComponents.push(PointerCalldataBlock.RAW_DATA_START);
+ rawDataComponents.push(dependencyRawData);
+ rawDataComponents.push(PointerCalldataBlock.RAW_DATA_END);
+ const rawData = Buffer.concat(rawDataComponents);
+ return rawData;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/blocks/set.ts b/packages/utils/src/abi_encoder/calldata/blocks/set.ts
new file mode 100644
index 000000000..d1abc4986
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/blocks/set.ts
@@ -0,0 +1,47 @@
+import * as _ from 'lodash';
+
+import { CalldataBlock } from '../calldata_block';
+
+export class SetCalldataBlock extends CalldataBlock {
+ private _header: Buffer | undefined;
+ private _members: CalldataBlock[];
+
+ constructor(name: string, signature: string, parentName: string) {
+ super(name, signature, parentName, 0, 0);
+ this._members = [];
+ this._header = undefined;
+ }
+
+ public getRawData(): Buffer {
+ const rawDataComponents: Buffer[] = [];
+ if (!_.isUndefined(this._header)) {
+ rawDataComponents.push(this._header);
+ }
+ _.each(this._members, (member: CalldataBlock) => {
+ const memberBuffer = member.getRawData();
+ rawDataComponents.push(memberBuffer);
+ });
+ const rawData = Buffer.concat(rawDataComponents);
+ return rawData;
+ }
+
+ public setMembers(members: CalldataBlock[]): void {
+ this._members = members;
+ }
+
+ public setHeader(header: Buffer): void {
+ this._setHeaderSize(header.byteLength);
+ this._header = header;
+ }
+
+ public toBuffer(): Buffer {
+ if (!_.isUndefined(this._header)) {
+ return this._header;
+ }
+ return new Buffer('');
+ }
+
+ public getMembers(): CalldataBlock[] {
+ return this._members;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts
new file mode 100644
index 000000000..b08fb71ce
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/calldata.ts
@@ -0,0 +1,245 @@
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { constants } from '../utils/constants';
+import { EncodingRules } from '../utils/rules';
+
+import { PointerCalldataBlock } from './blocks/pointer';
+import { SetCalldataBlock } from './blocks/set';
+import { CalldataBlock } from './calldata_block';
+import { CalldataIterator, ReverseCalldataIterator } from './iterator';
+
+export class Calldata {
+ private readonly _rules: EncodingRules;
+ private _selector: string;
+ private _root: CalldataBlock | undefined;
+
+ public constructor(rules: EncodingRules) {
+ this._rules = rules;
+ this._selector = '';
+ this._root = undefined;
+ }
+ /**
+ * Sets the root calldata block. This block usually corresponds to a Method.
+ */
+ public setRoot(block: CalldataBlock): void {
+ this._root = block;
+ }
+ /**
+ * Sets the selector to be prepended onto the calldata.
+ * If the root block was created by a Method then a selector will likely be set.
+ */
+ public setSelector(selector: string): void {
+ if (!_.startsWith(selector, '0x')) {
+ throw new Error(`Expected selector to be hex. Missing prefix '0x'`);
+ } else if (selector.length !== constants.HEX_SELECTOR_LENGTH_IN_CHARS) {
+ throw new Error(`Invalid selector '${selector}'`);
+ }
+ this._selector = selector;
+ }
+ /**
+ * Iterates through the calldata blocks, starting from the root block, to construct calldata as a hex string.
+ * If the `optimize` flag is set then this calldata will be condensed, to save gas.
+ * If the `annotate` flag is set then this will return human-readable calldata.
+ * If the `annotate` flag is *not* set then this will return EVM-compatible calldata.
+ */
+ public toString(): string {
+ // Sanity check: root block must be set
+ if (_.isUndefined(this._root)) {
+ throw new Error('expected root');
+ }
+ // Optimize, if flag set
+ if (this._rules.shouldOptimize) {
+ this._optimize();
+ }
+ // Set offsets
+ const iterator = new CalldataIterator(this._root);
+ let offset = 0;
+ for (const block of iterator) {
+ block.setOffset(offset);
+ offset += block.getSizeInBytes();
+ }
+ // Generate hex string
+ const hexString = this._rules.shouldAnnotate
+ ? this._toHumanReadableCallData()
+ : this._toEvmCompatibeCallDataHex();
+ return hexString;
+ }
+ /**
+ * There are three types of calldata blocks: Blob, Set and Pointer.
+ * Scenarios arise where distinct pointers resolve to identical values.
+ * We optimize by keeping only one such instance of the identical value, and redirecting all pointers here.
+ * We keep the last such duplicate value because pointers can only be positive (they cannot point backwards).
+ *
+ * Example #1:
+ * function f(string[], string[])
+ * f(["foo", "bar", "blitz"], ["foo", "bar", "blitz"])
+ * The array ["foo", "bar", "blitz"] will only be included in the calldata once.
+ *
+ * Example #2:
+ * function f(string[], string)
+ * f(["foo", "bar", "blitz"], "foo")
+ * The string "foo" will only be included in the calldata once.
+ *
+ * Example #3:
+ * function f((string, uint, bytes), string, uint, bytes)
+ * f(("foo", 5, "0x05"), "foo", 5, "0x05")
+ * The string "foo" and bytes "0x05" will only be included in the calldata once.
+ * The duplicate `uint 5` values cannot be optimized out because they are static values (no pointer points to them).
+ *
+ * @TODO #1:
+ * This optimization strategy handles blocks that are exact duplicates of one another.
+ * But what if some block is a combination of two other blocks? Or a subset of another block?
+ * This optimization problem is not much different from the current implemetation.
+ * Instead of tracking "observed" hashes, at each node we would simply do pattern-matching on the calldata.
+ * This strategy would be applied after assigning offsets to the tree, rather than before (as in this strategy).
+ * Note that one consequence of this strategy is pointers may resolve to offsets that are not word-aligned.
+ * This shouldn't be a problem but further investigation should be done.
+ *
+ * @TODO #2:
+ * To be done as a follow-up to @TODO #1.
+ * Since we optimize from the bottom-up, we could be affecting the outcome of a later potential optimization.
+ * For example, what if by removing one duplicate value we miss out on optimizing another block higher in the tree.
+ * To handle this case, at each node we can store a candidate optimization in a priority queue (sorted by calldata size).
+ * At the end of traversing the tree, the candidate at the front of the queue will be the most optimal output.
+ *
+ */
+ private _optimize(): void {
+ // Step 1/1 Create a reverse iterator (starts from the end of the calldata to the beginning)
+ if (_.isUndefined(this._root)) {
+ throw new Error('expected root');
+ }
+ const iterator = new ReverseCalldataIterator(this._root);
+ // Step 2/2 Iterate over each block, keeping track of which blocks have been seen and pruning redundant blocks.
+ const blocksByHash: { [key: string]: CalldataBlock } = {};
+ for (const block of iterator) {
+ // If a block is a pointer and its value has already been observed, then update
+ // the pointer to resolve to the existing value.
+ if (block instanceof PointerCalldataBlock) {
+ const dependencyBlockHashBuf = block.getDependency().computeHash();
+ const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf);
+ if (dependencyBlockHash in blocksByHash) {
+ const blockWithSameHash = blocksByHash[dependencyBlockHash];
+ if (blockWithSameHash !== block.getDependency()) {
+ block.setAlias(blockWithSameHash);
+ }
+ }
+ continue;
+ }
+ // This block has not been seen. Record its hash.
+ const blockHashBuf = block.computeHash();
+ const blockHash = ethUtil.bufferToHex(blockHashBuf);
+ if (!(blockHash in blocksByHash)) {
+ blocksByHash[blockHash] = block;
+ }
+ }
+ }
+ private _toEvmCompatibeCallDataHex(): string {
+ // Sanity check: must have a root block.
+ if (_.isUndefined(this._root)) {
+ throw new Error('expected root');
+ }
+ // Construct an array of buffers (one buffer for each block).
+ const selectorBuffer = ethUtil.toBuffer(this._selector);
+ const valueBufs: Buffer[] = [selectorBuffer];
+ const iterator = new CalldataIterator(this._root);
+ for (const block of iterator) {
+ valueBufs.push(block.toBuffer());
+ }
+ // Create hex from buffer array.
+ const combinedBuffers = Buffer.concat(valueBufs);
+ const hexValue = ethUtil.bufferToHex(combinedBuffers);
+ return hexValue;
+ }
+ /**
+ * Returns human-readable calldata.
+ *
+ * Example:
+ * simpleFunction(string[], string[])
+ * strings = ["Hello", "World"]
+ * simpleFunction(strings, strings)
+ *
+ * Output:
+ * 0xbb4f12e3
+ * ### simpleFunction
+ * 0x0 0000000000000000000000000000000000000000000000000000000000000040 ptr<array1> (alias for array2)
+ * 0x20 0000000000000000000000000000000000000000000000000000000000000040 ptr<array2>
+ *
+ * 0x40 0000000000000000000000000000000000000000000000000000000000000002 ### array2
+ * 0x60 0000000000000000000000000000000000000000000000000000000000000040 ptr<array2[0]>
+ * 0x80 0000000000000000000000000000000000000000000000000000000000000080 ptr<array2[1]>
+ * 0xa0 0000000000000000000000000000000000000000000000000000000000000005 array2[0]
+ * 0xc0 48656c6c6f000000000000000000000000000000000000000000000000000000
+ * 0xe0 0000000000000000000000000000000000000000000000000000000000000005 array2[1]
+ * 0x100 576f726c64000000000000000000000000000000000000000000000000000000
+ */
+ private _toHumanReadableCallData(): string {
+ // Sanity check: must have a root block.
+ if (_.isUndefined(this._root)) {
+ throw new Error('expected root');
+ }
+ // Constants for constructing annotated string
+ const offsetPadding = 10;
+ const valuePadding = 74;
+ const namePadding = 80;
+ const evmWordStartIndex = 0;
+ const emptySize = 0;
+ // Construct annotated calldata
+ let hexValue = `${this._selector}`;
+ let offset = 0;
+ const functionName: string = this._root.getName();
+ const iterator = new CalldataIterator(this._root);
+ for (const block of iterator) {
+ // Process each block 1 word at a time
+ const size = block.getSizeInBytes();
+ const name = block.getName();
+ const parentName = block.getParentName();
+ const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, '');
+ // Resulting line will be <offsetStr><valueStr><nameStr>
+ let offsetStr = '';
+ let valueStr = '';
+ let nameStr = '';
+ let lineStr = '';
+ if (size === emptySize) {
+ // This is a Set block with no header.
+ // For example, a tuple or an array with a defined length.
+ offsetStr = ' '.repeat(offsetPadding);
+ valueStr = ' '.repeat(valuePadding);
+ nameStr = `### ${prettyName.padEnd(namePadding)}`;
+ lineStr = `\n${offsetStr}${valueStr}${nameStr}`;
+ } else {
+ // This block has at least one word of value.
+ offsetStr = `0x${offset.toString(constants.HEX_BASE)}`.padEnd(offsetPadding);
+ valueStr = ethUtil
+ .stripHexPrefix(
+ ethUtil.bufferToHex(
+ block.toBuffer().slice(evmWordStartIndex, constants.EVM_WORD_WIDTH_IN_BYTES),
+ ),
+ )
+ .padEnd(valuePadding);
+ if (block instanceof SetCalldataBlock) {
+ nameStr = `### ${prettyName.padEnd(namePadding)}`;
+ lineStr = `\n${offsetStr}${valueStr}${nameStr}`;
+ } else {
+ nameStr = ` ${prettyName.padEnd(namePadding)}`;
+ lineStr = `${offsetStr}${valueStr}${nameStr}`;
+ }
+ }
+ // This block has a value that is more than 1 word.
+ for (let j = constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += constants.EVM_WORD_WIDTH_IN_BYTES) {
+ offsetStr = `0x${(offset + j).toString(constants.HEX_BASE)}`.padEnd(offsetPadding);
+ valueStr = ethUtil
+ .stripHexPrefix(
+ ethUtil.bufferToHex(block.toBuffer().slice(j, j + constants.EVM_WORD_WIDTH_IN_BYTES)),
+ )
+ .padEnd(valuePadding);
+ nameStr = ' '.repeat(namePadding);
+ lineStr = `${lineStr}\n${offsetStr}${valueStr}${nameStr}`;
+ }
+ // Append to hex value
+ hexValue = `${hexValue}\n${lineStr}`;
+ offset += size;
+ }
+ return hexValue;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/calldata_block.ts b/packages/utils/src/abi_encoder/calldata/calldata_block.ts
new file mode 100644
index 000000000..35bd994e5
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/calldata_block.ts
@@ -0,0 +1,77 @@
+import * as ethUtil from 'ethereumjs-util';
+
+export abstract class CalldataBlock {
+ private readonly _signature: string;
+ private readonly _parentName: string;
+ private _name: string;
+ private _offsetInBytes: number;
+ private _headerSizeInBytes: number;
+ private _bodySizeInBytes: number;
+
+ constructor(
+ name: string,
+ signature: string,
+ parentName: string,
+ headerSizeInBytes: number,
+ bodySizeInBytes: number,
+ ) {
+ this._name = name;
+ this._signature = signature;
+ this._parentName = parentName;
+ this._offsetInBytes = 0;
+ this._headerSizeInBytes = headerSizeInBytes;
+ this._bodySizeInBytes = bodySizeInBytes;
+ }
+
+ protected _setHeaderSize(headerSizeInBytes: number): void {
+ this._headerSizeInBytes = headerSizeInBytes;
+ }
+
+ protected _setBodySize(bodySizeInBytes: number): void {
+ this._bodySizeInBytes = bodySizeInBytes;
+ }
+
+ protected _setName(name: string): void {
+ this._name = name;
+ }
+
+ public getName(): string {
+ return this._name;
+ }
+
+ public getParentName(): string {
+ return this._parentName;
+ }
+
+ public getSignature(): string {
+ return this._signature;
+ }
+ public getHeaderSizeInBytes(): number {
+ return this._headerSizeInBytes;
+ }
+
+ public getBodySizeInBytes(): number {
+ return this._bodySizeInBytes;
+ }
+
+ public getSizeInBytes(): number {
+ return this.getHeaderSizeInBytes() + this.getBodySizeInBytes();
+ }
+
+ public getOffsetInBytes(): number {
+ return this._offsetInBytes;
+ }
+
+ public setOffset(offsetInBytes: number): void {
+ this._offsetInBytes = offsetInBytes;
+ }
+
+ public computeHash(): Buffer {
+ const rawData = this.getRawData();
+ const hash = ethUtil.sha3(rawData);
+ return hash;
+ }
+
+ public abstract toBuffer(): Buffer;
+ public abstract getRawData(): Buffer;
+}
diff --git a/packages/utils/src/abi_encoder/calldata/iterator.ts b/packages/utils/src/abi_encoder/calldata/iterator.ts
new file mode 100644
index 000000000..333b32b4f
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/iterator.ts
@@ -0,0 +1,114 @@
+/* tslint:disable max-classes-per-file */
+import * as _ from 'lodash';
+
+import { Queue } from '../utils/queue';
+
+import { BlobCalldataBlock } from './blocks/blob';
+import { PointerCalldataBlock } from './blocks/pointer';
+import { SetCalldataBlock } from './blocks/set';
+import { CalldataBlock } from './calldata_block';
+
+/**
+ * Iterator class for Calldata Blocks. Blocks follows the order
+ * they should be put into calldata that is passed to he EVM.
+ *
+ * Example #1:
+ * Let root = Set {
+ * Blob{} A,
+ * Pointer {
+ * Blob{} a
+ * } B,
+ * Blob{} C
+ * }
+ * It will iterate as follows: [A, B, C, B.a]
+ *
+ * Example #2:
+ * Let root = Set {
+ * Blob{} A,
+ * Pointer {
+ * Blob{} a
+ * Pointer {
+ * Blob{} b
+ * }
+ * } B,
+ * Pointer {
+ * Blob{} c
+ * } C
+ * }
+ * It will iterate as follows: [A, B, C, B.a, B.b, C.c]
+ */
+abstract class BaseIterator implements Iterable<CalldataBlock> {
+ protected readonly _root: CalldataBlock;
+ protected readonly _queue: Queue<CalldataBlock>;
+
+ private static _createQueue(block: CalldataBlock): Queue<CalldataBlock> {
+ const queue = new Queue<CalldataBlock>();
+ // Base case
+ if (!(block instanceof SetCalldataBlock)) {
+ queue.pushBack(block);
+ return queue;
+ }
+ // This is a set; add members
+ const set = block;
+ _.eachRight(set.getMembers(), (member: CalldataBlock) => {
+ queue.mergeFront(BaseIterator._createQueue(member));
+ });
+ // Add children
+ _.each(set.getMembers(), (member: CalldataBlock) => {
+ // Traverse child if it is a unique pointer.
+ // A pointer that is an alias for another pointer is ignored.
+ if (member instanceof PointerCalldataBlock && _.isUndefined(member.getAlias())) {
+ const dependency = member.getDependency();
+ queue.mergeBack(BaseIterator._createQueue(dependency));
+ }
+ });
+ // Put set block at the front of the queue
+ queue.pushFront(set);
+ return queue;
+ }
+
+ public constructor(root: CalldataBlock) {
+ this._root = root;
+ this._queue = BaseIterator._createQueue(root);
+ }
+
+ public [Symbol.iterator](): { next: () => IteratorResult<CalldataBlock> } {
+ return {
+ next: () => {
+ const nextBlock = this.nextBlock();
+ if (!_.isUndefined(nextBlock)) {
+ return {
+ value: nextBlock,
+ done: false,
+ };
+ }
+ return {
+ done: true,
+ value: new BlobCalldataBlock('', '', '', new Buffer('')),
+ };
+ },
+ };
+ }
+
+ public abstract nextBlock(): CalldataBlock | undefined;
+}
+
+export class CalldataIterator extends BaseIterator {
+ public constructor(root: CalldataBlock) {
+ super(root);
+ }
+
+ public nextBlock(): CalldataBlock | undefined {
+ return this._queue.popFront();
+ }
+}
+
+export class ReverseCalldataIterator extends BaseIterator {
+ public constructor(root: CalldataBlock) {
+ super(root);
+ }
+
+ public nextBlock(): CalldataBlock | undefined {
+ return this._queue.popBack();
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts
new file mode 100644
index 000000000..189841989
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts
@@ -0,0 +1,82 @@
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { constants } from '../utils/constants';
+import { Queue } from '../utils/queue';
+
+export class RawCalldata {
+ private static readonly _INITIAL_OFFSET = 0;
+ private readonly _value: Buffer;
+ private readonly _selector: string;
+ private readonly _scopes: Queue<number>;
+ private _offset: number;
+
+ public constructor(value: string | Buffer, hasSelector: boolean = true) {
+ // Sanity check
+ if (typeof value === 'string' && !_.startsWith(value, '0x')) {
+ throw new Error(`Expected raw calldata to start with '0x'`);
+ }
+ // Construct initial values
+ this._value = ethUtil.toBuffer(value);
+ this._selector = '0x';
+ this._scopes = new Queue<number>();
+ this._scopes.pushBack(RawCalldata._INITIAL_OFFSET);
+ this._offset = RawCalldata._INITIAL_OFFSET;
+ // If there's a selector then slice it
+ if (hasSelector) {
+ const selectorBuf = this._value.slice(constants.HEX_SELECTOR_LENGTH_IN_BYTES);
+ this._value = this._value.slice(constants.HEX_SELECTOR_LENGTH_IN_BYTES);
+ this._selector = ethUtil.bufferToHex(selectorBuf);
+ }
+ }
+
+ public popBytes(lengthInBytes: number): Buffer {
+ const value = this._value.slice(this._offset, this._offset + lengthInBytes);
+ this.setOffset(this._offset + lengthInBytes);
+ return value;
+ }
+
+ public popWord(): Buffer {
+ const wordInBytes = 32;
+ return this.popBytes(wordInBytes);
+ }
+
+ public popWords(length: number): Buffer {
+ const wordInBytes = 32;
+ return this.popBytes(length * wordInBytes);
+ }
+
+ public readBytes(from: number, to: number): Buffer {
+ const value = this._value.slice(from, to);
+ return value;
+ }
+
+ public setOffset(offsetInBytes: number): void {
+ this._offset = offsetInBytes;
+ }
+
+ public startScope(): void {
+ this._scopes.pushFront(this._offset);
+ }
+
+ public endScope(): void {
+ this._scopes.popFront();
+ }
+
+ public getOffset(): number {
+ return this._offset;
+ }
+
+ public toAbsoluteOffset(relativeOffset: number): number {
+ const scopeOffset = this._scopes.peekFront();
+ if (_.isUndefined(scopeOffset)) {
+ throw new Error(`Tried to access undefined scope.`);
+ }
+ const absoluteOffset = relativeOffset + scopeOffset;
+ return absoluteOffset;
+ }
+
+ public getSelector(): string {
+ return this._selector;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/evm_data_type_factory.ts b/packages/utils/src/abi_encoder/evm_data_type_factory.ts
new file mode 100644
index 000000000..4cc124e0a
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_type_factory.ts
@@ -0,0 +1,132 @@
+/* tslint:disable max-classes-per-file */
+import { DataItem, MethodAbi } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { DataType } from './abstract_data_types/data_type';
+import { DataTypeFactory } from './abstract_data_types/interfaces';
+import { AddressDataType } from './evm_data_types/address';
+import { ArrayDataType } from './evm_data_types/array';
+import { BoolDataType } from './evm_data_types/bool';
+import { DynamicBytesDataType } from './evm_data_types/dynamic_bytes';
+import { IntDataType } from './evm_data_types/int';
+import { MethodDataType } from './evm_data_types/method';
+import { PointerDataType } from './evm_data_types/pointer';
+import { StaticBytesDataType } from './evm_data_types/static_bytes';
+import { StringDataType } from './evm_data_types/string';
+import { TupleDataType } from './evm_data_types/tuple';
+import { UIntDataType } from './evm_data_types/uint';
+
+export class Address extends AddressDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class Bool extends BoolDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class Int extends IntDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class UInt extends UIntDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class StaticBytes extends StaticBytesDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class DynamicBytes extends DynamicBytesDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class String extends StringDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class Pointer extends PointerDataType {
+ public constructor(destDataType: DataType, parentDataType: DataType) {
+ super(destDataType, parentDataType, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class Tuple extends TupleDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class Array extends ArrayDataType {
+ public constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ }
+}
+
+export class Method extends MethodDataType {
+ public constructor(abi: MethodAbi) {
+ super(abi, EvmDataTypeFactory.getInstance());
+ }
+}
+
+/* tslint:disable no-construct */
+export class EvmDataTypeFactory implements DataTypeFactory {
+ private static _instance: DataTypeFactory;
+
+ public static getInstance(): DataTypeFactory {
+ if (!EvmDataTypeFactory._instance) {
+ EvmDataTypeFactory._instance = new EvmDataTypeFactory();
+ }
+ return EvmDataTypeFactory._instance;
+ }
+
+ /* tslint:disable prefer-function-over-method */
+ public create(dataItem: DataItem, parentDataType?: DataType): DataType {
+ // Create data type
+ let dataType: undefined | DataType;
+ if (Array.matchType(dataItem.type)) {
+ dataType = new Array(dataItem);
+ } else if (Address.matchType(dataItem.type)) {
+ dataType = new Address(dataItem);
+ } else if (Bool.matchType(dataItem.type)) {
+ dataType = new Bool(dataItem);
+ } else if (Int.matchType(dataItem.type)) {
+ dataType = new Int(dataItem);
+ } else if (UInt.matchType(dataItem.type)) {
+ dataType = new UInt(dataItem);
+ } else if (StaticBytes.matchType(dataItem.type)) {
+ dataType = new StaticBytes(dataItem);
+ } else if (Tuple.matchType(dataItem.type)) {
+ dataType = new Tuple(dataItem);
+ } else if (DynamicBytes.matchType(dataItem.type)) {
+ dataType = new DynamicBytes(dataItem);
+ } else if (String.matchType(dataItem.type)) {
+ dataType = new String(dataItem);
+ }
+ // @TODO: DataTypeement Fixed/UFixed types
+ if (_.isUndefined(dataType)) {
+ throw new Error(`Unrecognized data type: '${dataItem.type}'`);
+ } else if (!_.isUndefined(parentDataType) && !dataType.isStatic()) {
+ const pointerToDataType = new Pointer(dataType, parentDataType);
+ return pointerToDataType;
+ }
+ return dataType;
+ }
+ /* tslint:enable prefer-function-over-method */
+
+ private constructor() {}
+}
+/* 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
new file mode 100644
index 000000000..88846b1fa
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/address.ts
@@ -0,0 +1,49 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+
+export class AddressDataType extends AbstractBlobDataType {
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+ private static readonly _ADDRESS_SIZE_IN_BYTES = 20;
+ private static readonly _DECODED_ADDRESS_OFFSET_IN_BYTES = constants.EVM_WORD_WIDTH_IN_BYTES -
+ AddressDataType._ADDRESS_SIZE_IN_BYTES;
+
+ public static matchType(type: string): boolean {
+ return type === SolidityTypes.Address;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, AddressDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!AddressDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`);
+ }
+ }
+
+ // Disable prefer-function-over-method for inherited abstract methods.
+ /* tslint:disable prefer-function-over-method */
+ public encodeValue(value: string): Buffer {
+ if (!ethUtil.isValidAddress(value)) {
+ throw new Error(`Invalid address: '${value}'`);
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ const encodedValueBuf = ethUtil.setLengthLeft(valueBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
+ return encodedValueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ const valueBufPadded = calldata.popWord();
+ const valueBuf = valueBufPadded.slice(AddressDataType._DECODED_ADDRESS_OFFSET_IN_BYTES);
+ const value = ethUtil.bufferToHex(valueBuf);
+ return value;
+ }
+
+ public getSignature(): 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
new file mode 100644
index 000000000..7595cb667
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/array.ts
@@ -0,0 +1,64 @@
+import { DataItem } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractSetDataType } from '../abstract_data_types/types/set';
+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 {
+ return ArrayDataType._MATCHER.test(type);
+ }
+
+ private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined | number] {
+ const matches = ArrayDataType._MATCHER.exec(type);
+ if (_.isNull(matches) || matches.length !== 3) {
+ throw new Error(`Could not parse array: ${type}`);
+ } else if (_.isUndefined(matches[1])) {
+ throw new Error(`Could not parse array type: ${type}`);
+ } else if (_.isUndefined(matches[2])) {
+ throw new Error(`Could not parse array length: ${type}`);
+ }
+ const arrayElementType = matches[1];
+ const arrayLength = _.isEmpty(matches[2]) ? undefined : parseInt(matches[2], constants.DEC_BASE);
+ return [arrayElementType, arrayLength];
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ // Construct parent
+ const isArray = true;
+ const [arrayElementType, arrayLength] = ArrayDataType._decodeElementTypeAndLengthFromType(dataItem.type);
+ super(dataItem, dataTypeFactory, isArray, arrayLength, arrayElementType);
+ // Set array properties
+ this._elementType = arrayElementType;
+ this._arraySignature = this._computeSignature();
+ }
+
+ public getSignature(): string {
+ return this._arraySignature;
+ }
+
+ private _computeSignature(): string {
+ // Compute signature for a single array element
+ const elementDataItem: DataItem = {
+ type: this._elementType,
+ name: 'N/A',
+ };
+ const elementComponents = this.getDataItem().components;
+ if (!_.isUndefined(elementComponents)) {
+ elementDataItem.components = elementComponents;
+ }
+ const elementDataType = this.getFactory().create(elementDataItem);
+ const elementSignature = elementDataType.getSignature();
+ // Construct signature for array of type `element`
+ if (_.isUndefined(this._arrayLength)) {
+ return `${elementSignature}[]`;
+ } else {
+ return `${elementSignature}[${this._arrayLength}]`;
+ }
+ }
+}
diff --git a/packages/utils/src/abi_encoder/evm_data_types/bool.ts b/packages/utils/src/abi_encoder/evm_data_types/bool.ts
new file mode 100644
index 000000000..d713d5a94
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/bool.ts
@@ -0,0 +1,53 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { BigNumber } from '../../configured_bignumber';
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+
+export class BoolDataType extends AbstractBlobDataType {
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+
+ public static matchType(type: string): boolean {
+ return type === SolidityTypes.Bool;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, BoolDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!BoolDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`);
+ }
+ }
+
+ // Disable prefer-function-over-method for inherited abstract methods.
+ /* tslint:disable prefer-function-over-method */
+ public encodeValue(value: boolean): Buffer {
+ const encodedValue = value ? '0x1' : '0x0';
+ const encodedValueBuf = ethUtil.setLengthLeft(
+ ethUtil.toBuffer(encodedValue),
+ constants.EVM_WORD_WIDTH_IN_BYTES,
+ );
+ return encodedValueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): boolean {
+ const valueBuf = calldata.popWord();
+ const valueHex = ethUtil.bufferToHex(valueBuf);
+ const valueNumber = new BigNumber(valueHex, constants.HEX_BASE);
+ if (!(valueNumber.equals(0) || valueNumber.equals(1))) {
+ throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`);
+ }
+ /* tslint:disable boolean-naming */
+ const value: boolean = !valueNumber.equals(0);
+ /* tslint:enable boolean-naming */
+ return value;
+ }
+
+ public getSignature(): 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
new file mode 100644
index 000000000..5277efd6c
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/dynamic_bytes.ts
@@ -0,0 +1,72 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+
+export class DynamicBytesDataType extends AbstractBlobDataType {
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false;
+
+ public static matchType(type: string): boolean {
+ return type === SolidityTypes.Bytes;
+ }
+
+ private static _sanityCheckValue(value: string | Buffer): void {
+ if (typeof value !== 'string') {
+ return;
+ }
+ if (!_.startsWith(value, '0x')) {
+ throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`);
+ } else if (value.length % 2 !== 0) {
+ throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
+ }
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, DynamicBytesDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!DynamicBytesDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate Dynamic Bytes with bad input: ${dataItem}`);
+ }
+ }
+
+ // Disable prefer-function-over-method for inherited abstract methods.
+ /* tslint:disable prefer-function-over-method */
+ public encodeValue(value: string | Buffer): Buffer {
+ // Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
+ // 1/3 Construct the length
+ const valueBuf = ethUtil.toBuffer(value);
+ const wordsToStoreValuePadded = Math.ceil(valueBuf.byteLength / constants.EVM_WORD_WIDTH_IN_BYTES);
+ const bytesToStoreValuePadded = wordsToStoreValuePadded * constants.EVM_WORD_WIDTH_IN_BYTES;
+ const lengthBuf = ethUtil.toBuffer(valueBuf.byteLength);
+ const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
+ // 2/3 Construct the value
+ DynamicBytesDataType._sanityCheckValue(value);
+ const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded);
+ // 3/3 Combine length and value
+ const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]);
+ return encodedValue;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ // Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
+ // 1/2 Decode length
+ const lengthBuf = calldata.popWord();
+ const lengthHex = ethUtil.bufferToHex(lengthBuf);
+ const length = parseInt(lengthHex, constants.HEX_BASE);
+ // 2/2 Decode value
+ const wordsToStoreValuePadded = Math.ceil(length / constants.EVM_WORD_WIDTH_IN_BYTES);
+ const valueBufPadded = calldata.popWords(wordsToStoreValuePadded);
+ const valueBuf = valueBufPadded.slice(0, length);
+ const value = ethUtil.bufferToHex(valueBuf);
+ DynamicBytesDataType._sanityCheckValue(value);
+ return value;
+ }
+
+ public getSignature(): 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
new file mode 100644
index 000000000..f1dcf5ea1
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/int.ts
@@ -0,0 +1,59 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BigNumber } from '../../configured_bignumber';
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+import * as EncoderMath from '../utils/math';
+
+export class IntDataType extends AbstractBlobDataType {
+ private static readonly _MATCHER = RegExp(
+ '^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
+ );
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+ private static readonly _MAX_WIDTH: number = 256;
+ private static readonly _DEFAULT_WIDTH: number = IntDataType._MAX_WIDTH;
+ private readonly _width: number;
+ private readonly _minValue: BigNumber;
+ private readonly _maxValue: BigNumber;
+
+ public static matchType(type: string): boolean {
+ return IntDataType._MATCHER.test(type);
+ }
+
+ private static _decodeWidthFromType(type: string): number {
+ const matches = IntDataType._MATCHER.exec(type);
+ const width =
+ !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1])
+ ? parseInt(matches[1], constants.DEC_BASE)
+ : IntDataType._DEFAULT_WIDTH;
+ return width;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, IntDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!IntDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate Int with bad input: ${dataItem}`);
+ }
+ this._width = IntDataType._decodeWidthFromType(dataItem.type);
+ this._minValue = new BigNumber(2).toPower(this._width - 1).times(-1);
+ this._maxValue = new BigNumber(2).toPower(this._width - 1).sub(1);
+ }
+
+ public encodeValue(value: BigNumber | string | number): Buffer {
+ const encodedValue = EncoderMath.safeEncodeNumericValue(value, this._minValue, this._maxValue);
+ return encodedValue;
+ }
+
+ public decodeValue(calldata: RawCalldata): BigNumber {
+ const valueBuf = calldata.popWord();
+ const value = EncoderMath.safeDecodeNumericValue(valueBuf, this._minValue, this._maxValue);
+ return value;
+ }
+
+ public getSignature(): 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
new file mode 100644
index 000000000..b1cd1377f
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts
@@ -0,0 +1,72 @@
+import { DataItem, MethodAbi } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { DataType } from '../abstract_data_types/data_type';
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractSetDataType } from '../abstract_data_types/types/set';
+import { constants } from '../utils/constants';
+import { DecodingRules, EncodingRules } from '../utils/rules';
+
+import { TupleDataType } from './tuple';
+
+export class MethodDataType extends AbstractSetDataType {
+ private readonly _methodSignature: string;
+ private readonly _methodSelector: string;
+ private readonly _returnDataType: DataType;
+
+ public constructor(abi: MethodAbi, dataTypeFactory: DataTypeFactory) {
+ const methodDataItem = { type: 'method', name: abi.name, components: abi.inputs };
+ super(methodDataItem, dataTypeFactory);
+ this._methodSignature = this._computeSignature();
+ this._methodSelector = this._computeSelector();
+ const returnDataItem: DataItem = { type: 'tuple', name: abi.name, components: abi.outputs };
+ this._returnDataType = new TupleDataType(returnDataItem, this.getFactory());
+ }
+
+ public encode(value: any, rules?: EncodingRules): string {
+ const calldata = super.encode(value, rules, this._methodSelector);
+ return calldata;
+ }
+
+ public decode(calldata: string, rules?: DecodingRules): any[] | object {
+ const value = super.decode(calldata, rules, this._methodSelector);
+ return value;
+ }
+
+ public encodeReturnValues(value: any, rules?: EncodingRules): string {
+ const returnData = this._returnDataType.encode(value, rules);
+ return returnData;
+ }
+
+ public decodeReturnValues(returndata: string, rules?: DecodingRules): any {
+ const returnValues = this._returnDataType.decode(returndata, rules);
+ return returnValues;
+ }
+
+ public getSignature(): string {
+ return this._methodSignature;
+ }
+
+ public getSelector(): string {
+ return this._methodSelector;
+ }
+
+ private _computeSignature(): string {
+ const memberSignature = this._computeSignatureOfMembers();
+ const methodSignature = `${this.getDataItem().name}${memberSignature}`;
+ return methodSignature;
+ }
+
+ private _computeSelector(): string {
+ const signature = this._computeSignature();
+ const selector = ethUtil.bufferToHex(
+ ethUtil.toBuffer(
+ ethUtil
+ .sha3(signature)
+ .slice(constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, constants.HEX_SELECTOR_LENGTH_IN_BYTES),
+ ),
+ );
+ return selector;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/evm_data_types/pointer.ts b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts
new file mode 100644
index 000000000..389e75927
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/pointer.ts
@@ -0,0 +1,17 @@
+import { DataItem } from 'ethereum-types';
+
+import { DataType } from '../abstract_data_types/data_type';
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractPointerDataType } from '../abstract_data_types/types/pointer';
+
+export class PointerDataType extends AbstractPointerDataType {
+ constructor(destDataType: DataType, parentDataType: DataType, dataTypeFactory: DataTypeFactory) {
+ const destDataItem = destDataType.getDataItem();
+ const dataItem: DataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` };
+ super(dataItem, dataTypeFactory, destDataType, parentDataType);
+ }
+
+ public getSignature(): string {
+ return this._destination.getSignature();
+ }
+}
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
new file mode 100644
index 000000000..2e371c505
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/static_bytes.ts
@@ -0,0 +1,78 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+
+export class StaticBytesDataType extends AbstractBlobDataType {
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+ private static readonly _MATCHER = RegExp(
+ '^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$',
+ );
+ private static readonly _DEFAULT_WIDTH = 1;
+ private readonly _width: number;
+
+ public static matchType(type: string): boolean {
+ return StaticBytesDataType._MATCHER.test(type);
+ }
+
+ private static _decodeWidthFromType(type: string): number {
+ const matches = StaticBytesDataType._MATCHER.exec(type);
+ const width =
+ !_.isNull(matches) && matches.length === 3 && !_.isUndefined(matches[2])
+ ? parseInt(matches[2], constants.DEC_BASE)
+ : StaticBytesDataType._DEFAULT_WIDTH;
+ return width;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, StaticBytesDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!StaticBytesDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate Static Bytes with bad input: ${dataItem}`);
+ }
+ this._width = StaticBytesDataType._decodeWidthFromType(dataItem.type);
+ }
+
+ public getSignature(): string {
+ // Note that `byte` reduces to `bytes1`
+ return `${SolidityTypes.Bytes}${this._width}`;
+ }
+
+ public encodeValue(value: string | Buffer): Buffer {
+ // 1/2 Convert value into a buffer and do bounds checking
+ this._sanityCheckValue(value);
+ const valueBuf = ethUtil.toBuffer(value);
+ // 2/2 Store value as hex
+ const valuePadded = ethUtil.setLengthRight(valueBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
+ return valuePadded;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ const valueBufPadded = calldata.popWord();
+ const valueBuf = valueBufPadded.slice(0, this._width);
+ const value = ethUtil.bufferToHex(valueBuf);
+ this._sanityCheckValue(value);
+ return value;
+ }
+
+ private _sanityCheckValue(value: string | Buffer): void {
+ if (typeof value === 'string') {
+ if (!_.startsWith(value, '0x')) {
+ throw new Error(`Tried to encode non-hex value. Value must inlcude '0x' prefix.`);
+ } else if (value.length % 2 !== 0) {
+ throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
+ }
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ if (valueBuf.byteLength > this._width) {
+ throw new Error(
+ `Tried to assign ${value} (${
+ valueBuf.byteLength
+ } bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`,
+ );
+ }
+ }
+}
diff --git a/packages/utils/src/abi_encoder/evm_data_types/string.ts b/packages/utils/src/abi_encoder/evm_data_types/string.ts
new file mode 100644
index 000000000..91a72ad3f
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/string.ts
@@ -0,0 +1,59 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+
+export class StringDataType extends AbstractBlobDataType {
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = false;
+
+ public static matchType(type: string): boolean {
+ return type === SolidityTypes.String;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, StringDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!StringDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate String with bad input: ${dataItem}`);
+ }
+ }
+
+ // Disable prefer-function-over-method for inherited abstract methods.
+ /* tslint:disable prefer-function-over-method */
+ public encodeValue(value: string): Buffer {
+ // Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
+ // 1/3 Construct the length
+ const wordsToStoreValuePadded = Math.ceil(value.length / constants.EVM_WORD_WIDTH_IN_BYTES);
+ const bytesToStoreValuePadded = wordsToStoreValuePadded * constants.EVM_WORD_WIDTH_IN_BYTES;
+ const lengthBuf = ethUtil.toBuffer(value.length);
+ const lengthBufPadded = ethUtil.setLengthLeft(lengthBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
+ // 2/3 Construct the value
+ const valueBuf = new Buffer(value);
+ const valueBufPadded = ethUtil.setLengthRight(valueBuf, bytesToStoreValuePadded);
+ // 3/3 Combine length and value
+ const encodedValue = Buffer.concat([lengthBufPadded, valueBufPadded]);
+ return encodedValue;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ // Encoded value is of the form: <length><value>, with each field padded to be word-aligned.
+ // 1/2 Decode length
+ const lengthBufPadded = calldata.popWord();
+ const lengthHexPadded = ethUtil.bufferToHex(lengthBufPadded);
+ const length = parseInt(lengthHexPadded, constants.HEX_BASE);
+ // 2/2 Decode value
+ const wordsToStoreValuePadded = Math.ceil(length / constants.EVM_WORD_WIDTH_IN_BYTES);
+ const valueBufPadded = calldata.popWords(wordsToStoreValuePadded);
+ const valueBuf = valueBufPadded.slice(0, length);
+ const value = valueBuf.toString('ascii');
+ return value;
+ }
+
+ public getSignature(): 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
new file mode 100644
index 000000000..31593c882
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/tuple.ts
@@ -0,0 +1,24 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+
+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;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory);
+ 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;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/evm_data_types/uint.ts b/packages/utils/src/abi_encoder/evm_data_types/uint.ts
new file mode 100644
index 000000000..5180f0cf3
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types/uint.ts
@@ -0,0 +1,58 @@
+import { DataItem, SolidityTypes } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BigNumber } from '../../configured_bignumber';
+import { DataTypeFactory } from '../abstract_data_types/interfaces';
+import { AbstractBlobDataType } from '../abstract_data_types/types/blob';
+import { RawCalldata } from '../calldata/raw_calldata';
+import { constants } from '../utils/constants';
+import * as EncoderMath from '../utils/math';
+
+export class UIntDataType extends AbstractBlobDataType {
+ private static readonly _MATCHER = RegExp(
+ '^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
+ );
+ private static readonly _SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+ private static readonly _MAX_WIDTH: number = 256;
+ private static readonly _DEFAULT_WIDTH: number = UIntDataType._MAX_WIDTH;
+ private static readonly _MIN_VALUE = new BigNumber(0);
+ private readonly _width: number;
+ private readonly _maxValue: BigNumber;
+
+ public static matchType(type: string): boolean {
+ return UIntDataType._MATCHER.test(type);
+ }
+
+ private static _decodeWidthFromType(type: string): number {
+ const matches = UIntDataType._MATCHER.exec(type);
+ const width =
+ !_.isNull(matches) && matches.length === 2 && !_.isUndefined(matches[1])
+ ? parseInt(matches[1], constants.DEC_BASE)
+ : UIntDataType._DEFAULT_WIDTH;
+ return width;
+ }
+
+ public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
+ super(dataItem, dataTypeFactory, UIntDataType._SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!UIntDataType.matchType(dataItem.type)) {
+ throw new Error(`Tried to instantiate UInt with bad input: ${dataItem}`);
+ }
+ this._width = UIntDataType._decodeWidthFromType(dataItem.type);
+ this._maxValue = new BigNumber(2).toPower(this._width).sub(1);
+ }
+
+ public encodeValue(value: BigNumber | string | number): Buffer {
+ const encodedValue = EncoderMath.safeEncodeNumericValue(value, UIntDataType._MIN_VALUE, this._maxValue);
+ return encodedValue;
+ }
+
+ public decodeValue(calldata: RawCalldata): BigNumber {
+ const valueBuf = calldata.popWord();
+ const value = EncoderMath.safeDecodeNumericValue(valueBuf, UIntDataType._MIN_VALUE, this._maxValue);
+ return value;
+ }
+
+ public getSignature(): string {
+ return `${SolidityTypes.Uint}${this._width}`;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts
new file mode 100644
index 000000000..baf844ac6
--- /dev/null
+++ b/packages/utils/src/abi_encoder/index.ts
@@ -0,0 +1,14 @@
+export { EncodingRules, DecodingRules } from './utils/rules';
+export {
+ Address,
+ Array,
+ Bool,
+ DynamicBytes,
+ Int,
+ Method,
+ Pointer,
+ StaticBytes,
+ String,
+ Tuple,
+ UInt,
+} from './evm_data_type_factory';
diff --git a/packages/utils/src/abi_encoder/utils/constants.ts b/packages/utils/src/abi_encoder/utils/constants.ts
new file mode 100644
index 000000000..36de2dd4f
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/constants.ts
@@ -0,0 +1,17 @@
+import { DecodingRules, EncodingRules } from './rules';
+
+export const constants = {
+ EVM_WORD_WIDTH_IN_BYTES: 32,
+ EVM_WORD_WIDTH_IN_BITS: 256,
+ HEX_BASE: 16,
+ DEC_BASE: 10,
+ BIN_BASE: 2,
+ HEX_SELECTOR_LENGTH_IN_CHARS: 10,
+ HEX_SELECTOR_LENGTH_IN_BYTES: 4,
+ 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_ENCODING_RULES: { shouldOptimize: true, shouldAnnotate: false } as EncodingRules,
+ /* tslint:enable no-object-literal-type-assertion */
+};
diff --git a/packages/utils/src/abi_encoder/utils/math.ts b/packages/utils/src/abi_encoder/utils/math.ts
new file mode 100644
index 000000000..d84983c5b
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/math.ts
@@ -0,0 +1,111 @@
+import BigNumber from 'bignumber.js';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { constants } from '../utils/constants';
+
+function sanityCheckBigNumberRange(
+ value_: BigNumber | string | number,
+ minValue: BigNumber,
+ maxValue: BigNumber,
+): void {
+ const value = new BigNumber(value_, 10);
+ if (value.greaterThan(maxValue)) {
+ throw new Error(`Tried to assign value of ${value}, which exceeds max value of ${maxValue}`);
+ } else if (value.lessThan(minValue)) {
+ throw new Error(`Tried to assign value of ${value}, which exceeds min value of ${minValue}`);
+ }
+}
+function bigNumberToPaddedBuffer(value: BigNumber): Buffer {
+ const valueHex = `0x${value.toString(constants.HEX_BASE)}`;
+ const valueBuf = ethUtil.toBuffer(valueHex);
+ const valueBufPadded = ethUtil.setLengthLeft(valueBuf, constants.EVM_WORD_WIDTH_IN_BYTES);
+ return valueBufPadded;
+}
+/**
+ * Takes a numeric value and returns its ABI-encoded value
+ * @param value_ The value to encode.
+ * @return ABI Encoded value
+ */
+export function encodeNumericValue(value_: BigNumber | string | number): Buffer {
+ const value = new BigNumber(value_, 10);
+ // Case 1/2: value is non-negative
+ if (value.greaterThanOrEqualTo(0)) {
+ const encodedPositiveValue = bigNumberToPaddedBuffer(value);
+ return encodedPositiveValue;
+ }
+ // Case 2/2: Value is negative
+ // Use two's-complement to encode the value
+ // Step 1/3: Convert negative value to positive binary string
+ const valueBin = value.times(-1).toString(constants.BIN_BASE);
+ // Step 2/3: Invert binary value
+ let invertedValueBin = '1'.repeat(constants.EVM_WORD_WIDTH_IN_BITS - valueBin.length);
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, constants.BIN_BASE);
+ // Step 3/3: Add 1 to inverted value
+ const negativeValue = invertedValue.plus(1);
+ const encodedValue = bigNumberToPaddedBuffer(negativeValue);
+ return encodedValue;
+}
+/**
+ * Takes a numeric value and returns its ABI-encoded value.
+ * Performs an additional sanity check, given the min/max allowed value.
+ * @param value_ The value to encode.
+ * @return ABI Encoded value
+ */
+export function safeEncodeNumericValue(
+ value: BigNumber | string | number,
+ minValue: BigNumber,
+ maxValue: BigNumber,
+): Buffer {
+ sanityCheckBigNumberRange(value, minValue, maxValue);
+ const encodedValue = encodeNumericValue(value);
+ return encodedValue;
+}
+/**
+ * Takes an ABI-encoded numeric value and returns its decoded value as a BigNumber.
+ * @param encodedValue The encoded numeric value.
+ * @param minValue The minimum possible decoded value.
+ * @return ABI Decoded value
+ */
+export function decodeNumericValue(encodedValue: Buffer, minValue: BigNumber): BigNumber {
+ const valueHex = ethUtil.bufferToHex(encodedValue);
+ // Case 1/3: value is definitely non-negative because of numeric boundaries
+ const value = new BigNumber(valueHex, constants.HEX_BASE);
+ if (!minValue.lessThan(0)) {
+ return value;
+ }
+ // Case 2/3: value is non-negative because there is no leading 1 (encoded as two's-complement)
+ const valueBin = value.toString(constants.BIN_BASE);
+ const isValueNegative = valueBin.length === constants.EVM_WORD_WIDTH_IN_BITS && _.startsWith(valueBin[0], '1');
+ if (!isValueNegative) {
+ return value;
+ }
+ // Case 3/3: value is negative
+ // Step 1/3: Invert b inary value
+ let invertedValueBin = '';
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, constants.BIN_BASE);
+ // Step 2/3: Add 1 to inverted value
+ // The result is the two's-complement representation of the input value.
+ const positiveValue = invertedValue.plus(1);
+ // Step 3/3: Invert positive value to get the negative value
+ const negativeValue = positiveValue.times(-1);
+ return negativeValue;
+}
+/**
+ * Takes an ABI-encoded numeric value and returns its decoded value as a BigNumber.
+ * Performs an additional sanity check, given the min/max allowed value.
+ * @param encodedValue The encoded numeric value.
+ * @param minValue The minimum possible decoded value.
+ * @return ABI Decoded value
+ */
+export function safeDecodeNumericValue(encodedValue: Buffer, minValue: BigNumber, maxValue: BigNumber): BigNumber {
+ const value = decodeNumericValue(encodedValue, minValue);
+ sanityCheckBigNumberRange(value, minValue, maxValue);
+ return value;
+}
diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts
new file mode 100644
index 000000000..53afb7e11
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/queue.ts
@@ -0,0 +1,39 @@
+export class Queue<T> {
+ private _store: T[] = [];
+
+ public pushBack(val: T): void {
+ this._store.push(val);
+ }
+
+ public pushFront(val: T): void {
+ this._store.unshift(val);
+ }
+
+ public popFront(): T | undefined {
+ return this._store.shift();
+ }
+
+ public popBack(): T | undefined {
+ if (this._store.length === 0) {
+ return undefined;
+ }
+ const backElement = this._store.splice(-1, 1)[0];
+ return backElement;
+ }
+
+ public mergeBack(q: Queue<T>): void {
+ this._store = this._store.concat(q._store);
+ }
+
+ public mergeFront(q: Queue<T>): void {
+ this._store = q._store.concat(this._store);
+ }
+
+ public getStore(): T[] {
+ return this._store;
+ }
+
+ public peekFront(): T | undefined {
+ return this._store.length >= 0 ? this._store[0] : undefined;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/utils/rules.ts b/packages/utils/src/abi_encoder/utils/rules.ts
new file mode 100644
index 000000000..c8d83c3ba
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/rules.ts
@@ -0,0 +1,8 @@
+export interface DecodingRules {
+ shouldConvertStructsToObjects: boolean;
+}
+
+export interface EncodingRules {
+ shouldOptimize?: boolean;
+ shouldAnnotate?: boolean;
+}
diff --git a/packages/utils/src/abi_utils.ts b/packages/utils/src/abi_utils.ts
index 598ea5fcc..3e6fc9665 100644
--- a/packages/utils/src/abi_utils.ts
+++ b/packages/utils/src/abi_utils.ts
@@ -26,7 +26,7 @@ function parseEthersParams(params: DataItem[]): { names: ParamName[]; types: str
const result = parseEthersParams(param.components);
names.push({ name: param.name || null, names: result.names });
- types.push('tuple(' + result.types.join(',') + ')' + suffix);
+ types.push(`tuple(${result.types.join(',')})${suffix}`);
} else {
names.push(param.name || null);
types.push(param.type);
@@ -120,7 +120,7 @@ function splitTupleTypes(type: string): string[] {
if (_.endsWith(type, '[]')) {
throw new Error('Internal error: array types are not supported');
} else if (!_.startsWith(type, 'tuple(')) {
- throw new Error('Internal error: expected tuple type but got non-tuple type: ' + type);
+ throw new Error(`Internal error: expected tuple type but got non-tuple type: ${type}`);
}
// Trim the outtermost tuple().
const trimmedType = type.substring('tuple('.length, type.length - 1);
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 0723e5788..082aff6bb 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -10,3 +10,4 @@ export { NULL_BYTES } from './constants';
export { errorUtils } from './error_utils';
export { fetchAsync } from './fetch_async';
export { signTypedDataUtils } from './sign_typed_data_utils';
+export import AbiEncoder = require('./abi_encoder');
diff --git a/packages/utils/src/log_utils.ts b/packages/utils/src/log_utils.ts
index 87f8479b5..6d9996c67 100644
--- a/packages/utils/src/log_utils.ts
+++ b/packages/utils/src/log_utils.ts
@@ -1,3 +1,5 @@
+import chalk from 'chalk';
+
export const logUtils = {
log(...args: any[]): void {
console.log(...args); // tslint:disable-line:no-console
@@ -5,4 +7,7 @@ export const logUtils = {
warn(...args: any[]): void {
console.warn(...args); // tslint:disable-line:no-console
},
+ logWithTime(arg: string): void {
+ logUtils.log(`[${chalk.gray(new Date().toLocaleTimeString())}] ${arg}`);
+ },
};
diff --git a/packages/utils/test/abi_encoder/abi_samples/method_abis.ts b/packages/utils/test/abi_encoder/abi_samples/method_abis.ts
new file mode 100644
index 000000000..fc552c127
--- /dev/null
+++ b/packages/utils/test/abi_encoder/abi_samples/method_abis.ts
@@ -0,0 +1,780 @@
+/* tslint:disable max-file-line-count */
+import { MethodAbi } from 'ethereum-types';
+
+export const simpleAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'greg',
+ type: 'uint256',
+ },
+ {
+ name: 'gregStr',
+ type: 'string',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const stringAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'greg',
+ type: 'string[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const GAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'a',
+ type: 'uint256',
+ },
+ {
+ name: 'b',
+ type: 'string',
+ },
+ {
+ name: 'e',
+ type: 'bytes',
+ },
+ {
+ name: 'f',
+ type: 'address',
+ },
+ ],
+
+ name: 'f',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const typesWithDefaultWidthsAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someUint',
+ type: 'uint',
+ },
+ {
+ name: 'someInt',
+ type: 'int',
+ },
+ {
+ name: 'someByte',
+ type: 'byte',
+ },
+ {
+ name: 'someUint',
+ type: 'uint[]',
+ },
+ {
+ name: 'someInt',
+ type: 'int[]',
+ },
+ {
+ name: 'someByte',
+ type: 'byte[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const multiDimensionalArraysStaticTypeAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'a',
+ type: 'uint8[][][]',
+ },
+ {
+ name: 'b',
+ type: 'uint8[][][2]',
+ },
+ {
+ name: 'c',
+ type: 'uint8[][2][]',
+ },
+ {
+ name: 'd',
+ type: 'uint8[2][][]',
+ },
+ {
+ name: 'e',
+ type: 'uint8[][2][2]',
+ },
+ {
+ name: 'f',
+ type: 'uint8[2][2][]',
+ },
+ {
+ name: 'g',
+ type: 'uint8[2][][2]',
+ },
+ {
+ name: 'h',
+ type: 'uint8[2][2][2]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const multiDimensionalArraysDynamicTypeAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'a',
+ type: 'string[][][]',
+ },
+ {
+ name: 'b',
+ type: 'string[][][2]',
+ },
+ {
+ name: 'c',
+ type: 'string[][2][]',
+ },
+ {
+ name: 'h',
+ type: 'string[2][2][2]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const dynamicTupleAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ ],
+ name: 'order',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayOfStaticTuplesWithDefinedLengthAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someUint2',
+ type: 'uint256',
+ },
+ ],
+ name: 'order',
+ type: 'tuple[8]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayOfStaticTuplesWithDynamicLengthAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someUint2',
+ type: 'uint256',
+ },
+ ],
+ name: 'order',
+ type: 'tuple[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayOfDynamicTuplesWithDefinedLengthAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ ],
+ name: 'order',
+ type: 'tuple[8]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayOfDynamicTuplesWithUndefinedLengthAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ ],
+ name: 'order',
+ type: 'tuple[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayOfDynamicTuplesAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ ],
+ name: 'order',
+ type: 'tuple[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const multidimensionalArrayOfDynamicTuplesAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ ],
+ name: 'order',
+ type: 'tuple[][2][]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const staticTupleAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'someUint1',
+ type: 'uint256',
+ },
+ {
+ name: 'someUint2',
+ type: 'uint256',
+ },
+ {
+ name: 'someUint3',
+ type: 'uint256',
+ },
+ {
+ name: 'someBool',
+ type: 'bool',
+ },
+ ],
+ name: 'order',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const staticArrayAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'uint8[3]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const staticArrayDynamicMembersAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'string[3]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const dynamicArrayDynamicMembersAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'string[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const dynamicArrayStaticMembersAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'uint8[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const largeFlatAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someUInt256',
+ type: 'uint256',
+ },
+ {
+ name: 'someInt256',
+ type: 'int256',
+ },
+ {
+ name: 'someInt32',
+ type: 'int32',
+ },
+ {
+ name: 'someByte',
+ type: 'byte',
+ },
+ {
+ name: 'someBytes32',
+ type: 'bytes32',
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ {
+ name: 'someBool',
+ type: 'bool',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const largeNestedAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someStaticArray',
+ type: 'uint8[3]',
+ },
+ {
+ name: 'someStaticArrayWithDynamicMembers',
+ type: 'string[2]',
+ },
+ {
+ name: 'someDynamicArrayWithDynamicMembers',
+ type: 'bytes[]',
+ },
+ {
+ name: 'some2DArray',
+ type: 'string[][]',
+ },
+ {
+ name: 'someTuple',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint32',
+ type: 'uint32',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ ],
+ },
+ {
+ name: 'someTupleWithDynamicTypes',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ /*{
+ name: 'someStrArray',
+ type: 'string[]',
+ },*/
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ },
+ {
+ name: 'someArrayOfTuplesWithDynamicTypes',
+ type: 'tuple[]',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ /*{
+ name: 'someStrArray',
+ type: 'string[]',
+ },*/
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const nestedTuples: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'firstTuple',
+ type: 'tuple[1]',
+ components: [
+ {
+ name: 'someUint32',
+ type: 'uint32',
+ },
+ {
+ name: 'nestedTuple',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: 'secondTuple',
+ type: 'tuple[]',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ {
+ name: 'nestedTuple',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint32',
+ type: 'uint32',
+ },
+ {
+ name: 'secondNestedTuple',
+ type: 'tuple',
+ components: [
+ {
+ name: 'someUint',
+ type: 'uint256',
+ },
+ {
+ name: 'someStr',
+ type: 'string',
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someAddress',
+ type: 'address',
+ },
+ ],
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const simpleAbi2: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'someByte',
+ type: 'byte',
+ },
+ {
+ name: 'someBytes32',
+ type: 'bytes32',
+ },
+ {
+ name: 'someBytes',
+ type: 'bytes',
+ },
+ {
+ name: 'someString',
+ type: 'string',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const fillOrderAbi: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'makerAddress',
+ type: 'address',
+ },
+ {
+ name: 'takerAddress',
+ type: 'address',
+ },
+ {
+ name: 'feeRecipientAddress',
+ type: 'address',
+ },
+ {
+ name: 'senderAddress',
+ type: 'address',
+ },
+ {
+ name: 'makerAssetAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'takerAssetAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'makerFee',
+ type: 'uint256',
+ },
+ {
+ name: 'takerFee',
+ type: 'uint256',
+ },
+ {
+ name: 'expirationTimeSeconds',
+ type: 'uint256',
+ },
+ {
+ name: 'salt',
+ type: 'uint256',
+ },
+ {
+ name: 'makerAssetData',
+ type: 'bytes',
+ },
+ {
+ name: 'takerAssetData',
+ type: 'bytes',
+ },
+ ],
+ name: 'order',
+ type: 'tuple',
+ },
+ {
+ name: 'takerAssetFillAmount',
+ type: 'uint256',
+ },
+ {
+ name: 'salt',
+ type: 'uint256',
+ },
+ {
+ name: 'orderSignature',
+ type: 'bytes',
+ },
+ {
+ name: 'takerSignature',
+ type: 'bytes',
+ },
+ ],
+ name: 'fillOrder',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
diff --git a/packages/utils/test/abi_encoder/abi_samples/optimizer_abis.ts b/packages/utils/test/abi_encoder/abi_samples/optimizer_abis.ts
new file mode 100644
index 000000000..7cfd7a118
--- /dev/null
+++ b/packages/utils/test/abi_encoder/abi_samples/optimizer_abis.ts
@@ -0,0 +1,340 @@
+/* tslint:disable max-file-line-count */
+import { MethodAbi } from 'ethereum-types';
+
+export const duplicateDynamicArraysWithStaticElements: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'array1',
+ type: 'uint[]',
+ },
+ {
+ name: 'array2',
+ type: 'uint[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateDynamicArraysWithDynamicElements: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'array1',
+ type: 'string[]',
+ },
+ {
+ name: 'array2',
+ type: 'string[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateStaticArraysWithStaticElements: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'array1',
+ type: 'uint[2]',
+ },
+ {
+ name: 'array2',
+ type: 'uint[2]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateStaticArraysWithDynamicElements: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'array1',
+ type: 'string[2]',
+ },
+ {
+ name: 'array2',
+ type: 'string[2]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateArrayElements: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'array',
+ type: 'string[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateTupleFields: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'field1',
+ type: 'string',
+ },
+ {
+ name: 'field2',
+ type: 'string',
+ },
+ ],
+ name: 'Tuple',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateStrings: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'string1',
+ type: 'string',
+ },
+ {
+ name: 'string2',
+ type: 'string',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateBytes: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'bytes1',
+ type: 'bytes',
+ },
+ {
+ name: 'bytes2',
+ type: 'bytes',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateTuples: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'field1',
+ type: 'string',
+ },
+ {
+ name: 'field2',
+ type: 'uint',
+ },
+ ],
+ name: 'Tuple',
+ type: 'tuple',
+ },
+ {
+ components: [
+ {
+ name: 'field1',
+ type: 'string',
+ },
+ {
+ name: 'field2',
+ type: 'uint',
+ },
+ ],
+ name: 'Tuple',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateArraysNestedInTuples: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ name: 'field',
+ type: 'uint[]',
+ },
+ ],
+ name: 'Tuple1',
+ type: 'tuple',
+ },
+ {
+ components: [
+ {
+ name: 'field',
+ type: 'uint[]',
+ },
+ {
+ name: 'extraField',
+ type: 'string',
+ },
+ ],
+ name: 'Tuple2',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateTuplesNestedInTuples: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ components: [
+ {
+ components: [
+ {
+ name: 'nestedField',
+ type: 'string',
+ },
+ ],
+ name: 'field',
+ type: 'tuple',
+ },
+ ],
+ name: 'Tuple1',
+ type: 'tuple',
+ },
+ {
+ components: [
+ {
+ components: [
+ {
+ name: 'nestedField',
+ type: 'string',
+ },
+ ],
+ name: 'field',
+ type: 'tuple',
+ },
+ {
+ name: 'extraField',
+ type: 'string',
+ },
+ ],
+ name: 'Tuple1',
+ type: 'tuple',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const duplicateTwoDimensionalArrays: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'array1',
+ type: 'string[][]',
+ },
+ {
+ name: 'array2',
+ type: 'string[][]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayElementsDuplicatedAsSeparateParameter: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'stringArray',
+ type: 'string[]',
+ },
+ {
+ name: 'string',
+ type: 'string',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const arrayElementsDuplicatedAsTupleFields: MethodAbi = {
+ constant: false,
+ inputs: [
+ {
+ name: 'uint8Array',
+ type: 'uint8[]',
+ },
+ {
+ components: [
+ {
+ name: 'uint',
+ type: 'uint',
+ },
+ ],
+ name: 'uintTuple',
+ type: 'tuple[]',
+ },
+ ],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
diff --git a/packages/utils/test/abi_encoder/abi_samples/return_value_abis.ts b/packages/utils/test/abi_encoder/abi_samples/return_value_abis.ts
new file mode 100644
index 000000000..ac2124011
--- /dev/null
+++ b/packages/utils/test/abi_encoder/abi_samples/return_value_abis.ts
@@ -0,0 +1,99 @@
+/* tslint:disable max-file-line-count */
+import { MethodAbi } from 'ethereum-types';
+
+export const noReturnValues: MethodAbi = {
+ constant: false,
+ inputs: [],
+ name: 'simpleFunction',
+ outputs: [],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const singleStaticReturnValue: MethodAbi = {
+ constant: false,
+ inputs: [],
+ name: 'simpleFunction',
+ outputs: [
+ {
+ name: 'Bytes4',
+ type: 'bytes4',
+ },
+ ],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const multipleStaticReturnValues: MethodAbi = {
+ constant: false,
+ inputs: [],
+ name: 'simpleFunction',
+ outputs: [
+ {
+ name: 'val1',
+ type: 'bytes4',
+ },
+ {
+ name: 'val2',
+ type: 'bytes4',
+ },
+ ],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const singleDynamicReturnValue: MethodAbi = {
+ constant: false,
+ inputs: [],
+ name: 'simpleFunction',
+ outputs: [
+ {
+ name: 'val',
+ type: 'bytes',
+ },
+ ],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const multipleDynamicReturnValues: MethodAbi = {
+ constant: false,
+ inputs: [],
+ name: 'simpleFunction',
+ outputs: [
+ {
+ name: 'val1',
+ type: 'bytes',
+ },
+ {
+ name: 'val2',
+ type: 'bytes',
+ },
+ ],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
+
+export const mixedStaticAndDynamicReturnValues: MethodAbi = {
+ constant: false,
+ inputs: [],
+ name: 'simpleFunction',
+ outputs: [
+ {
+ name: 'val1',
+ type: 'bytes4',
+ },
+ {
+ name: 'val2',
+ type: 'bytes',
+ },
+ ],
+ payable: false,
+ stateMutability: 'nonpayable',
+ type: 'function',
+};
diff --git a/packages/utils/test/abi_encoder/evm_data_types_test.ts b/packages/utils/test/abi_encoder/evm_data_types_test.ts
new file mode 100644
index 000000000..55d582d10
--- /dev/null
+++ b/packages/utils/test/abi_encoder/evm_data_types_test.ts
@@ -0,0 +1,1007 @@
+/* tslint:disable max-file-line-count */
+import * as chai from 'chai';
+import * as ethUtil from 'ethereumjs-util';
+import 'mocha';
+
+import { AbiEncoder, BigNumber } from '../../src/';
+import { chaiSetup } from '../utils/chai_setup';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('ABI Encoder: EVM Data Type Encoding/Decoding', () => {
+ const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: false }; // optimizer is tested separately.
+ describe('Array', () => {
+ it('Fixed size; Static elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'int[2]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = [new BigNumber(5), new BigNumber(6)];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Dynamic size; Static elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'int[]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = [new BigNumber(5), new BigNumber(6)];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Fixed size; Dynamic elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'string[2]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = ['Hello', 'world'];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Dynamic size; Dynamic elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'string[]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = ['Hello', 'world'];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005776f726c64000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Dynamic Size; Multidimensional; Dynamic Elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'bytes[][]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const array1 = ['0x01020304', '0x05060708', '0x09101112'];
+ const array2 = ['0x10111213', '0x14151617'];
+ const array3 = ['0x18192021'];
+ const args = [array1, array2, array3];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000405060708000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000041011121300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414151617000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Dynamic Size; Multidimensional; Static Elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'bytes4[][]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const array1 = ['0x01020304', '0x05060708', '0x09101112'];
+ const array2 = ['0x10111213', '0x14151617'];
+ const array3 = ['0x18192021'];
+ const args = [array1, array2, array3];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000301020304000000000000000000000000000000000000000000000000000000000506070800000000000000000000000000000000000000000000000000000000091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021011121300000000000000000000000000000000000000000000000000000000141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011819202100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Static Size; Multidimensional; Static Elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'bytes4[3][2]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const array1 = ['0x01020304', '0x05060708', '0x09101112'];
+ const array2 = ['0x10111213', '0x14151617', '0x18192021'];
+ const args = [array1, array2];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x010203040000000000000000000000000000000000000000000000000000000005060708000000000000000000000000000000000000000000000000000000000910111200000000000000000000000000000000000000000000000000000000101112130000000000000000000000000000000000000000000000000000000014151617000000000000000000000000000000000000000000000000000000001819202100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Static Size; Multidimensional; Dynamic Elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'bytes[3][2]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const array1 = ['0x01020304', '0x05060708', '0x09101112'];
+ const array2 = ['0x10111213', '0x14151617', '0x18192021'];
+ const args = [array1, array2];
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000401020304000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004050607080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040910111200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000410111213000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004141516170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041819202100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Static size; Too Few Elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'string[3]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = ['Hello', 'world'];
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw('Expected array of 3 elements, but got array of length 2');
+ });
+ it('Static size; Too Many Elements', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'string[1]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = ['Hello', 'world'];
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw('Expected array of 1 elements, but got array of length 2');
+ });
+ it('Element Type Mismatch', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'testArray', type: 'uint[]' };
+ const dataType = new AbiEncoder.Array(testDataItem);
+ // Construct args to be encoded
+ const args = [new BigNumber(1), 'Bad Argument'];
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ });
+
+ describe('Tuple', () => {
+ it('Static elements only', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = { field_1: new BigNumber(-5), field_2: true };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000001';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Dynamic elements only', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field_1', type: 'string' }, { name: 'field_2', type: 'bytes' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = { field_1: 'Hello, World!', field_2: '0xabcdef0123456789' };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Nested Static Array', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field', type: 'uint[2]' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = { field: [new BigNumber(1), new BigNumber(2)] };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Nested Dynamic Array', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field', type: 'uint[]' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = { field: [new BigNumber(1), new BigNumber(2)] };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Nested Static Multidimensional Array', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field', type: 'bytes4[2][2]' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const array1 = ['0x01020304', '0x05060708'];
+ const array2 = ['0x09101112', '0x13141516'];
+ const args = { field: [array1, array2] };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x0102030400000000000000000000000000000000000000000000000000000000050607080000000000000000000000000000000000000000000000000000000009101112000000000000000000000000000000000000000000000000000000001314151600000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Nested Dynamic Multidimensional Array', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field', type: 'bytes[2][2]' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const array1 = ['0x01020304', '0x05060708'];
+ const array2 = ['0x09101112', '0x13141516'];
+ const args = { field: [array1, array2] };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004010203040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040506070800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004091011120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041314151600000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Static and dynamic elements mixed', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [
+ { name: 'field_1', type: 'int32' },
+ { name: 'field_2', type: 'string' },
+ { name: 'field_3', type: 'bool' },
+ { name: 'field_4', type: 'bytes' },
+ ],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = {
+ field_1: new BigNumber(-5),
+ field_2: 'Hello, World!',
+ field_3: true,
+ field_4: '0xabcdef0123456789',
+ };
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008abcdef0123456789000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodingRules: AbiEncoder.DecodingRules = { shouldConvertStructsToObjects: true };
+ const decodedArgs = dataType.decode(encodedArgs, decodingRules);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Missing Key', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = { field_1: new BigNumber(-5) };
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw('Could not assign tuple to object: missing keys field_2');
+ });
+ it('Bad Key', async () => {
+ // Create DataType object
+ const testDataItem = {
+ name: 'Tuple',
+ type: 'tuple',
+ components: [{ name: 'field_1', type: 'int32' }, { name: 'field_2', type: 'bool' }],
+ };
+ const dataType = new AbiEncoder.Tuple(testDataItem);
+ // Construct args to be encoded
+ const args = { unknown_field: new BigNumber(-5) };
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw("Could not assign tuple to object: unrecognized key 'unknown_field' in object Tuple");
+ });
+ });
+
+ describe('Address', () => {
+ it('Valid Address', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Address', type: 'address' };
+ const dataType = new AbiEncoder.Address(testDataItem);
+ // Construct args to be encoded
+ const args = '0xe41d2489571d322189246dafa5ebde1f4699f498';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Invalid Address - input is not valid hex', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Address', type: 'address' };
+ const dataType = new AbiEncoder.Address(testDataItem);
+ // Construct args to be encoded
+ const args = 'e4';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw(`Invalid address: '${args}'`);
+ });
+ it('Invalid Address - input is not 20 bytes', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Address', type: 'address' };
+ const dataType = new AbiEncoder.Address(testDataItem);
+ // Construct args to be encoded
+ const args = '0xe4';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw(`Invalid address: '${args}'`);
+ });
+ });
+
+ describe('Bool', () => {
+ it('True', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Boolean', type: 'bool' };
+ const dataType = new AbiEncoder.Bool(testDataItem);
+ // Construct args to be encoded
+ const args = true;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('False', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Boolean', type: 'bool' };
+ const dataType = new AbiEncoder.Bool(testDataItem);
+ // Construct args to be encoded
+ const args = false;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ });
+
+ describe('Integer', () => {
+ /* tslint:disable custom-no-magic-numbers */
+ const max256BitInteger = new BigNumber(2).pow(255).minus(1);
+ const min256BitInteger = new BigNumber(2).pow(255).times(-1);
+ const max32BitInteger = new BigNumber(2).pow(31).minus(1);
+ const min32BitInteger = new BigNumber(2).pow(31).times(-1);
+ /* tslint:enable custom-no-magic-numbers */
+
+ it('Int256 - Positive Base Case', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (256)', type: 'int' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = new BigNumber(1);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int256 - Negative Base Case', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (256)', type: 'int' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = new BigNumber(-1);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int256 - Positive Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (256)', type: 'int' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = max256BitInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int256 - Negative Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (256)', type: 'int' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = min256BitInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = `0x8000000000000000000000000000000000000000000000000000000000000000`;
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int256 - Value too large', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (256)', type: 'int' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = max256BitInteger.plus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ it('Int256 - Value too small', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (256)', type: 'int' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = min256BitInteger.minus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ it('Int32 - Positive Base Case', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (32)', type: 'int32' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = new BigNumber(1);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int32 - Negative Base Case', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (32)', type: 'int32' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = new BigNumber(-1);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int32 - Positive Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (32)', type: 'int32' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = max32BitInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x000000000000000000000000000000000000000000000000000000007fffffff';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int32 - Negative Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (32)', type: 'int32' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = min32BitInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000`;
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Int32 - Value too large', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (32)', type: 'int32' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = max32BitInteger.plus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ it('Int32 - Value too small', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Integer (32)', type: 'int32' };
+ const dataType = new AbiEncoder.Int(testDataItem);
+ // Construct args to be encoded
+ const args = min32BitInteger.minus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ });
+
+ describe('Unsigned Integer', () => {
+ /* tslint:disable custom-no-magic-numbers */
+ const max256BitUnsignedInteger = new BigNumber(2).pow(256).minus(1);
+ const min256BitUnsignedInteger = new BigNumber(0);
+ const max32BitUnsignedInteger = new BigNumber(2).pow(32).minus(1);
+ const min32BitUnsignedInteger = new BigNumber(0);
+ /* tslint:enable custom-no-magic-numbers */
+
+ it('UInt256 - Positive Base Case', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = new BigNumber(1);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('UInt256 - Positive Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = max256BitUnsignedInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('UInt256 - Zero Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = min256BitUnsignedInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`;
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('UInt256 - Value too large', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = max256BitUnsignedInteger.plus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ it('UInt256 - Value too small', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (256)', type: 'uint' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = min256BitUnsignedInteger.minus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ it('UInt32 - Positive Base Case', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = new BigNumber(1);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0000000000000000000000000000000000000000000000000000000000000001';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('UInt32 - Positive Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = max32BitUnsignedInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x00000000000000000000000000000000000000000000000000000000ffffffff';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('UInt32 - Zero Value', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = min32BitUnsignedInteger;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = `0x0000000000000000000000000000000000000000000000000000000000000000`;
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('UInt32 - Value too large', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = max32BitUnsignedInteger.plus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ it('UInt32 - Value too small', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Unsigned Integer (32)', type: 'uint32' };
+ const dataType = new AbiEncoder.UInt(testDataItem);
+ // Construct args to be encoded
+ const args = min32BitUnsignedInteger.minus(1);
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw();
+ });
+ });
+
+ describe('Static Bytes', () => {
+ it('Single Byte (byte)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Byte', type: 'byte' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x05';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Single Byte (bytes1)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes1', type: 'bytes1' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x05';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0500000000000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('4 Bytes (bytes4)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes4', type: 'bytes4' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x00010203';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0001020300000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('4 Bytes (bytes4); Encoder must pad input', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes4', type: 'bytes4' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const args = '0x1a18';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x1a18000000000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ const paddedArgs = '0x1a180000';
+ expect(decodedArgs).to.be.deep.equal(paddedArgs);
+ });
+ it('32 Bytes (bytes32)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes32', type: 'bytes32' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x0001020304050607080911121314151617181920212223242526272829303132';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x0001020304050607080911121314151617181920212223242526272829303132';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('32 Bytes (bytes32); Encoder must pad input', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes32', type: 'bytes32' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const args = '0x1a18bf61';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ const paddedArgs = '0x1a18bf6100000000000000000000000000000000000000000000000000000000';
+ expect(decodedArgs).to.be.deep.equal(paddedArgs);
+ });
+ it('Should throw when pass in too many bytes (bytes4)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes4', type: 'bytes4' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x0102030405';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw(
+ 'Tried to assign 0x0102030405 (5 bytes), which exceeds max bytes that can be stored in a bytes4',
+ );
+ });
+ it('Should throw when pass in too many bytes (bytes32)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes32', type: 'bytes32' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x010203040506070809101112131415161718192021222324252627282930313233';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw(
+ 'Tried to assign 0x010203040506070809101112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32',
+ );
+ });
+ it('Should throw when pass in bad hex (no 0x prefix)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes32', type: 'bytes32' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0102030405060708091011121314151617181920212223242526272829303132';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix.");
+ });
+ it('Should throw when pass in bad hex (include a half-byte)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes32', type: 'bytes32' };
+ const dataType = new AbiEncoder.StaticBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x010';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.');
+ });
+ });
+
+ describe('Dynamic Bytes', () => {
+ it('Fits into one EVM word', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' };
+ const dataType = new AbiEncoder.DynamicBytes(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const args = '0x1a18bf61';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Spans multiple EVM words', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' };
+ const dataType = new AbiEncoder.DynamicBytes(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const bytesLength = 40;
+ const args = `0x${'61'.repeat(bytesLength)}`;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Input as Buffer', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Dynamic Bytes', type: 'bytes' };
+ const dataType = new AbiEncoder.DynamicBytes(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const args = '0x1a18bf61';
+ const argsAsBuffer = ethUtil.toBuffer(args);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(argsAsBuffer);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000041a18bf6100000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Should throw when pass in bad hex (no 0x prefix)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes', type: 'bytes' };
+ const dataType = new AbiEncoder.DynamicBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '01';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw("Tried to encode non-hex value. Value must inlcude '0x' prefix.");
+ });
+ it('Should throw when pass in bad hex (include a half-byte)', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'Static Bytes', type: 'bytes' };
+ const dataType = new AbiEncoder.DynamicBytes(testDataItem);
+ // Construct args to be encoded
+ const args = '0x010';
+ // Encode Args and validate result
+ expect(() => {
+ dataType.encode(args, encodingRules);
+ }).to.throw('Tried to assign 0x010, which is contains a half-byte. Use full bytes only.');
+ });
+ });
+
+ describe('String', () => {
+ it('Fits into one EVM word', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'String', type: 'string' };
+ const dataType = new AbiEncoder.String(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const args = 'five';
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Spans multiple EVM words', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'String', type: 'string' };
+ const dataType = new AbiEncoder.String(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const bytesLength = 40;
+ const args = 'a'.repeat(bytesLength);
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('String that begins with 0x prefix', async () => {
+ // Create DataType object
+ const testDataItem = { name: 'String', type: 'string' };
+ const dataType = new AbiEncoder.String(testDataItem);
+ // Construct args to be encoded
+ // Note: There will be padding because this is a bytes32 but we are only passing in 4 bytes.
+ const strLength = 40;
+ const args = `0x${'a'.repeat(strLength)}`;
+ // Encode Args and validate result
+ const encodedArgs = dataType.encode(args, encodingRules);
+ const expectedEncodedArgs =
+ '0x000000000000000000000000000000000000000000000000000000000000002a30786161616161616161616161616161616161616161616161616161616161616161616161616161616100000000000000000000000000000000000000000000';
+ expect(encodedArgs).to.be.equal(expectedEncodedArgs);
+ // Decode Encoded Args and validate result
+ const decodedArgs = dataType.decode(encodedArgs);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ });
+});
diff --git a/packages/utils/test/abi_encoder/methods_test.ts b/packages/utils/test/abi_encoder/methods_test.ts
new file mode 100644
index 000000000..a0525967e
--- /dev/null
+++ b/packages/utils/test/abi_encoder/methods_test.ts
@@ -0,0 +1,366 @@
+import * as chai from 'chai';
+import 'mocha';
+
+import { AbiEncoder, BigNumber } from '../../src/';
+import { chaiSetup } from '../utils/chai_setup';
+
+import * as AbiSamples from './abi_samples/method_abis';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('ABI Encoder: Method Encoding / Decoding', () => {
+ const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: false }; // optimizer is tested separately.
+ it('Types with default widths', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi);
+ const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Array of Static Tuples (Array has defined length)', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi);
+ let value = 0;
+ const arrayOfTuples = [];
+ const arrayOfTuplesLength = 8;
+ for (let i = 0; i < arrayOfTuplesLength; ++i) {
+ arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]);
+ }
+ const args = [arrayOfTuples];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Array of Static Tuples (Array has dynamic length)', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi);
+ let value = 0;
+ const arrayOfTuples = [];
+ const arrayOfTuplesLength = 8;
+ for (let i = 0; i < arrayOfTuplesLength; ++i) {
+ arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]);
+ }
+ const args = [arrayOfTuples];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Array of Dynamic Tuples (Array has defined length)', async () => {
+ // Generate Calldata
+ const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi);
+ let value = 0;
+ const arrayOfTuples = [];
+ const arrayOfTuplesLength = 8;
+ for (let i = 0; i < arrayOfTuplesLength; ++i) {
+ arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]);
+ }
+ const args = [arrayOfTuples];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Array of Dynamic Tuples (Array has dynamic length)', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi);
+ let value = 0;
+ const arrayOfTuples = [];
+ const arrayOfTuplesLength = 8;
+ for (let i = 0; i < arrayOfTuplesLength; ++i) {
+ arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value).toString()]);
+ }
+ const args = [arrayOfTuples];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Multidimensional Arrays / Static Members', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi);
+ // Eight 3-dimensional arrays of uint8[2][2][2]
+ let value = 0;
+ const args = [];
+ const argsLength = 8;
+ for (let i = 0; i < argsLength; ++i) {
+ args.push([
+ [[new BigNumber(++value), new BigNumber(++value)], [new BigNumber(++value), new BigNumber(++value)]],
+ [[new BigNumber(++value), new BigNumber(++value)], [new BigNumber(++value), new BigNumber(++value)]],
+ ]);
+ }
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038';
+ expect(calldata).to.be.equal(expectedCalldata);
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Multidimensional Arrays / Dynamic Members', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi);
+ // Eight 3-dimensional arrays of string[2][2][2]
+ let value = 0;
+ const args = [];
+ const argsLength = 4;
+ for (let i = 0; i < argsLength; ++i) {
+ args.push([
+ [
+ [new BigNumber(++value).toString(), new BigNumber(++value).toString()],
+ [new BigNumber(++value).toString(), new BigNumber(++value).toString()],
+ ],
+ [
+ [new BigNumber(++value).toString(), new BigNumber(++value).toString()],
+ [new BigNumber(++value).toString(), new BigNumber(++value).toString()],
+ ],
+ ]);
+ }
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Fixed Length Array / Dynamic Members', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi);
+ const args = [['Brave', 'New', 'World']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Fixed Length Array / Dynamic Members', async () => {
+ // Generaet calldata
+ const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi);
+ const args = [['Brave', 'New', 'World']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Unfixed Length Array / Dynamic Members ABI', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi);
+ const args = [['Brave', 'New', 'World']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Unfixed Length Array / Static Members ABI', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi);
+ const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Fixed Length Array / Static Members ABI', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi);
+ const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Array ABI', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(AbiSamples.stringAbi);
+ const args = [['five', 'six', 'seven']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Static Tuple', async () => {
+ // Generate calldata
+ // This is dynamic because it has dynamic members
+ const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi);
+ const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Dynamic Tuple (Array input)', async () => {
+ // Generate calldata
+ // This is dynamic because it has dynamic members
+ const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi);
+ const args = [[new BigNumber(5), 'five']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Dynamic Tuple (Object input)', async () => {
+ // Generate Calldata
+ // This is dynamic because it has dynamic members
+ const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi);
+ const args = [[new BigNumber(5), 'five']];
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Large, Flat ABI', async () => {
+ // Construct calldata
+ const method = new AbiEncoder.Method(AbiSamples.largeFlatAbi);
+ const args = [
+ new BigNumber(256745454),
+ new BigNumber(-256745454),
+ new BigNumber(434244),
+ '0x43',
+ '0x0001020304050607080911121314151617181920212223242526272829303132',
+ '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132',
+ 'Little peter piper piped a piping pepper pot',
+ '0xe41d2489571d322189246dafa5ebde1f4699f498',
+ true,
+ ];
+ // Validate calldata
+ const calldata = method.encode(args, encodingRules);
+ const expectedCalldata =
+ '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata);
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+ it('Large, Nested ABI', async () => {
+ // Construct Calldata
+ const method = new AbiEncoder.Method(AbiSamples.largeNestedAbi);
+ const someStaticArray = [new BigNumber(127), new BigNumber(14), new BigNumber(54)];
+ const someStaticArrayWithDynamicMembers = [
+ 'the little piping piper piped a piping pipper papper',
+ 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.',
+ ];
+ const someDynamicArrayWithDynamicMembers = [
+ '0x38745637834987324827439287423897238947239847',
+ '0x7283472398237423984723984729847248927498748974284728947239487498749847874329423743492347329847239842374892374892374892347238947289478947489374289472894738942749823743298742389472389473289472389437249823749823742893472398',
+ '0x283473298473248923749238742398742398472894729843278942374982374892374892743982',
+ ];
+ const some2DArray = [
+ [
+ 'some string',
+ 'some another string',
+ 'there are just too many stringsup in',
+ 'here',
+ 'yall ghonna make me lose my mind',
+ ],
+ [
+ 'the little piping piper piped a piping pipper papper',
+ 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.',
+ ],
+ [],
+ ];
+ const someTuple = {
+ someUint32: new BigNumber(4037824789),
+ someStr:
+ 'the kid knows how to write poems, what can I say -- I guess theres a lot I could say to try to fill this line with a lot of text.',
+ };
+ const someTupleWithDynamicTypes = {
+ someUint: new BigNumber(4024789),
+ someStr: 'akdhjasjkdhasjkldshdjahdkjsahdajksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjk',
+ someBytes: '0x29384723894723843743289742389472398473289472348927489274894738427428947389facdea',
+ someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
+ };
+ const someTupleWithDynamicTypes2 = {
+ someUint: new BigNumber(9024789),
+ someStr: 'ksdhsajkdhsajkdhadjkashdjksadhajkdhsajkdhsadjkakdhjasjkdhasjkldshdjahdkjsahdaj',
+ someBytes: '0x29384723894398473289472348927489272384374328974238947274894738427428947389facde1',
+ someAddress: '0x746dafa5ebde1f4699f4981d3221892e41d24895',
+ };
+ const someTupleWithDynamicTypes3 = {
+ someUint: new BigNumber(1024789),
+ someStr: 'sdhsajkdhsajkdhadjkashdjakdhjasjkdhasjkldshdjahdkjsahdajkksadhajkdhsajkdhsadjk',
+ someBytes: '0x38947238437432829384729742389472398473289472348927489274894738427428947389facdef',
+ someAddress: '0x89571d322189e415ebde1f4699f498d24246dafa',
+ };
+ const someArrayOfTuplesWithDynamicTypes = [someTupleWithDynamicTypes2, someTupleWithDynamicTypes3];
+ const args = {
+ someStaticArray,
+ someStaticArrayWithDynamicMembers,
+ someDynamicArrayWithDynamicMembers,
+ some2DArray,
+ someTuple,
+ someTupleWithDynamicTypes,
+ someArrayOfTuplesWithDynamicTypes,
+ };
+ const calldata = method.encode(args, encodingRules);
+ // Validate calldata
+ const expectedCalldata =
+ '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000';
+ expect(calldata).to.be.equal(expectedCalldata);
+ // Validate decoding
+ const decodedValue = method.decode(calldata, { shouldConvertStructsToObjects: true });
+ expect(decodedValue).to.be.deep.equal(args);
+ });
+});
diff --git a/packages/utils/test/abi_encoder/optimizer_test.ts b/packages/utils/test/abi_encoder/optimizer_test.ts
new file mode 100644
index 000000000..ee0654ec3
--- /dev/null
+++ b/packages/utils/test/abi_encoder/optimizer_test.ts
@@ -0,0 +1,262 @@
+import * as chai from 'chai';
+import 'mocha';
+
+import { AbiEncoder, BigNumber } from '../../src/';
+import { chaiSetup } from '../utils/chai_setup';
+
+import * as OptimizedAbis from './abi_samples/optimizer_abis';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('ABI Encoder: Optimized Method Encoding/Decoding', () => {
+ const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: true };
+ it('Duplicate Dynamic Arrays with Static Elements', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithStaticElements);
+ const array1 = [new BigNumber(100), new BigNumber(150)];
+ const array2 = array1;
+ const args = [array1, array2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x7221063300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Dynamic Arrays with Dynamic Elements', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateDynamicArraysWithDynamicElements);
+ const array1 = ['Hello', 'World'];
+ const array2 = array1;
+ const args = [array1, array2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0xbb4f12e300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Static Arrays with Static Elements (should not optimize)', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithStaticElements);
+ const array1 = [new BigNumber(100), new BigNumber(150)];
+ const array2 = array1;
+ const args = [array1, array2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x7f8130430000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000096';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ const unoptimizedCalldata = method.encode(args);
+ expect(optimizedCalldata).to.be.equal(unoptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Static Arrays with Dynamic Elements', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateStaticArraysWithDynamicElements);
+ const array1 = ['Hello', 'World'];
+ const array2 = array1;
+ const args = [array1, array2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x9fe31f8e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Array Elements (should optimize)', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateArrayElements);
+ const strings = ['Hello', 'World', 'Hello', 'World'];
+ const args = [strings];
+ // Validate calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Tuple Fields', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateTupleFields);
+ const tuple = ['Hello', 'Hello'];
+ const args = [tuple];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x16780a5e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Strings', async () => {
+ // Description:
+ // Two dynamic arrays with the same values.
+ // In the optimized calldata, only one set of elements should be included.
+ // Both arrays should point to this set.
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateStrings);
+ const args = ['Hello', 'Hello'];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x07370bfa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Bytes', async () => {
+ // Description:
+ // Two dynamic arrays with the same values.
+ // In the optimized calldata, only one set of elements should be included.
+ // Both arrays should point to this set.
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateBytes);
+ const value = '0x01020304050607080910111213141516171819202122232425262728293031323334353637383940';
+ const args = [value, value];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x6045e42900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002801020304050607080910111213141516171819202122232425262728293031323334353637383940000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Tuples', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples);
+ const tuple1 = ['Hello, World!', new BigNumber(424234)];
+ const tuple2 = tuple1;
+ const args = [tuple1, tuple2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000006792a000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Fields Across Two Tuples', async () => {
+ // Description:
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuples);
+ const tuple1 = ['Hello, World!', new BigNumber(1)];
+ const tuple2 = [tuple1[0], new BigNumber(2)];
+ const args = [tuple1, tuple2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x564f826d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c642100000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Arrays, Nested in Separate Tuples', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateArraysNestedInTuples);
+ const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200)];
+ const tuple1 = [array];
+ const tuple2 = [array, 'extra argument to prevent exactly matching the tuples'];
+ const args = [tuple1, tuple2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x18970a9e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c80000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Tuples, Nested in Separate Tuples', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateTuplesNestedInTuples);
+ const nestedTuple = ['Hello, World!'];
+ const tuple1 = [nestedTuple];
+ const tuple2 = [nestedTuple, 'extra argument to prevent exactly matching the tuples'];
+ const args = [tuple1, tuple2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x0b4d2e6a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20576f726c6421000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035657874726120617267756d656e7420746f2070726576656e742065786163746c79206d61746368696e6720746865207475706c65730000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Two-Dimensional Arrays', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays);
+ const twoDimArray1 = [['Hello', 'World'], ['Foo', 'Bar', 'Zaa']];
+ const twoDimArray2 = twoDimArray1;
+ const args = [twoDimArray1, twoDimArray2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, { shouldOptimize: false });
+ const expectedOptimizedCalldata =
+ '0x0d28c4f9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000003466f6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426172000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035a61610000000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Duplicate Array, Nested within Separate Two-Dimensional Arrays', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.duplicateTwoDimensionalArrays);
+ const twoDimArray1 = [['Hello', 'World'], ['Foo']];
+ const twoDimArray2 = [['Hello', 'World'], ['Bar']];
+ const args = [twoDimArray1, twoDimArray2];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x0d28c4f900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003466f6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034261720000000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Array Elements Duplicated as Tuple Fields', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsTupleFields);
+ const array = [new BigNumber(100), new BigNumber(150), new BigNumber(200), new BigNumber(225)];
+ const tuple = [[array[0]], [array[1]], [array[2]], [array[3]]];
+ const args = [array, tuple];
+ // Validata calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0x5b5c78fd0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000e1';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+ it('Array Elements Duplicated as Separate Parameter', async () => {
+ // Generate calldata
+ const method = new AbiEncoder.Method(OptimizedAbis.arrayElementsDuplicatedAsSeparateParameter);
+ const array = ['Hello', 'Hello', 'Hello', 'World'];
+ const str = 'Hello';
+ const args = [array, str];
+ // Validate calldata
+ const optimizedCalldata = method.encode(args, encodingRules);
+ const expectedOptimizedCalldata =
+ '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000';
+ expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata);
+ // Validate decoding
+ const decodedArgs = method.decode(optimizedCalldata);
+ expect(decodedArgs).to.be.deep.equal(args);
+ });
+});
diff --git a/packages/utils/test/abi_encoder/return_values_test.ts b/packages/utils/test/abi_encoder/return_values_test.ts
new file mode 100644
index 000000000..104c7f5db
--- /dev/null
+++ b/packages/utils/test/abi_encoder/return_values_test.ts
@@ -0,0 +1,67 @@
+import * as chai from 'chai';
+import 'mocha';
+
+import { AbiEncoder } from '../../src/';
+import { chaiSetup } from '../utils/chai_setup';
+
+import * as ReturnValueAbis from './abi_samples/return_value_abis';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('ABI Encoder: Return Value Encoding/Decoding', () => {
+ const encodingRules: AbiEncoder.EncodingRules = { shouldOptimize: false }; // optimizer is tested separately.
+ it('No Return Value', async () => {
+ // Decode return value
+ const method = new AbiEncoder.Method(ReturnValueAbis.noReturnValues);
+ const returnValue = '0x';
+ const decodedReturnValue = method.decodeReturnValues(returnValue);
+ const expectedDecodedReturnValue: any[] = [];
+ expect(decodedReturnValue).to.be.deep.equal(expectedDecodedReturnValue);
+ });
+ it('Single static return value', async () => {
+ // Generate Return Value
+ const method = new AbiEncoder.Method(ReturnValueAbis.singleStaticReturnValue);
+ const returnValue = ['0x01020304'];
+ const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules);
+ const decodedReturnValue = method.decodeReturnValues(encodedReturnValue);
+ // Validate decoded return value
+ expect(decodedReturnValue).to.be.deep.equal(returnValue);
+ });
+ it('Multiple static return values', async () => {
+ // Generate Return Value
+ const method = new AbiEncoder.Method(ReturnValueAbis.multipleStaticReturnValues);
+ const returnValue = ['0x01020304', '0x05060708'];
+ const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules);
+ const decodedReturnValue = method.decodeReturnValues(encodedReturnValue);
+ // Validate decoded return value
+ expect(decodedReturnValue).to.be.deep.equal(returnValue);
+ });
+ it('Single dynamic return value', async () => {
+ // Generate Return Value
+ const method = new AbiEncoder.Method(ReturnValueAbis.singleDynamicReturnValue);
+ const returnValue = ['0x01020304'];
+ const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules);
+ const decodedReturnValue = method.decodeReturnValues(encodedReturnValue);
+ // Validate decoded return value
+ expect(decodedReturnValue).to.be.deep.equal(returnValue);
+ });
+ it('Multiple dynamic return values', async () => {
+ // Generate Return Value
+ const method = new AbiEncoder.Method(ReturnValueAbis.multipleDynamicReturnValues);
+ const returnValue = ['0x01020304', '0x05060708'];
+ const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules);
+ const decodedReturnValue = method.decodeReturnValues(encodedReturnValue);
+ // Validate decoded return value
+ expect(decodedReturnValue).to.be.deep.equal(returnValue);
+ });
+ it('Mixed static/dynamic return values', async () => {
+ // Generate Return Value
+ const method = new AbiEncoder.Method(ReturnValueAbis.mixedStaticAndDynamicReturnValues);
+ const returnValue = ['0x01020304', '0x05060708'];
+ const encodedReturnValue = method.encodeReturnValues(returnValue, encodingRules);
+ const decodedReturnValue = method.decodeReturnValues(encodedReturnValue);
+ // Validate decoded return value
+ expect(decodedReturnValue).to.be.deep.equal(returnValue);
+ });
+});
diff --git a/packages/utils/test/utils/chai_setup.ts b/packages/utils/test/utils/chai_setup.ts
new file mode 100644
index 000000000..1a8733093
--- /dev/null
+++ b/packages/utils/test/utils/chai_setup.ts
@@ -0,0 +1,13 @@
+import * as chai from 'chai';
+import chaiAsPromised = require('chai-as-promised');
+import ChaiBigNumber = require('chai-bignumber');
+import * as dirtyChai from 'dirty-chai';
+
+export const chaiSetup = {
+ configure(): void {
+ chai.config.includeStack = true;
+ chai.use(ChaiBigNumber());
+ chai.use(dirtyChai);
+ chai.use(chaiAsPromised);
+ },
+};