aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/utils/src')
-rw-r--r--packages/utils/src/abi_encoder/calldata.ts520
-rw-r--r--packages/utils/src/abi_encoder/data_type.ts322
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types.ts550
-rw-r--r--packages/utils/src/abi_encoder/index.ts2
-rw-r--r--packages/utils/src/index.ts1
5 files changed, 1395 insertions, 0 deletions
diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts
new file mode 100644
index 000000000..32278e5c5
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata.ts
@@ -0,0 +1,520 @@
+import ethUtil = require('ethereumjs-util');
+import CommunicationChatBubbleOutline from 'material-ui/SvgIcon';
+var _ = require('lodash');
+
+export interface DecodingRules {
+ structsAsObjects: boolean;
+}
+
+export interface EncodingRules {
+ optimize?: boolean;
+ annotate?: boolean;
+}
+
+export abstract class CalldataBlock {
+ private name: string;
+ private signature: string;
+ private offsetInBytes: number;
+ private headerSizeInBytes: number;
+ private bodySizeInBytes: number;
+ private relocatable: boolean;
+ private parentName: string;
+
+ constructor(name: string, signature: string, parentName: string, /*offsetInBytes: number,*/ headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) {
+ this.name = name;
+ this.signature = signature;
+ this.parentName = parentName;
+ this.offsetInBytes = 0;
+ this.headerSizeInBytes = headerSizeInBytes;
+ this.bodySizeInBytes = bodySizeInBytes;
+ this.relocatable = relocatable;
+ }
+
+ protected setHeaderSize(headerSizeInBytes: number) {
+ this.headerSizeInBytes = headerSizeInBytes;
+ }
+
+ protected setBodySize(bodySizeInBytes: number) {
+ this.bodySizeInBytes = bodySizeInBytes;
+ }
+
+ protected setName(name: string) {
+ this.name = name;
+ }
+
+ public getName(): string {
+ return this.name;
+ }
+
+ public getParentName(): string {
+ return this.parentName;
+ }
+
+ public getSignature(): string {
+ return this.signature;
+ }
+
+ public isRelocatable(): boolean {
+ return this.relocatable;
+ }
+
+ public getHeaderSizeInBytes(): number {
+ return this.headerSizeInBytes;
+ }
+
+ public getBodySizeInBytes(): number {
+ return this.bodySizeInBytes;
+ }
+
+ public getSizeInBytes(): number {
+ return this.headerSizeInBytes + this.bodySizeInBytes;
+ }
+
+ public getOffsetInBytes(): number {
+ return this.offsetInBytes;
+ }
+
+ public setOffset(offsetInBytes: number) {
+ 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;
+}
+
+export class PayloadCalldataBlock extends CalldataBlock {
+ private payload: Buffer;
+
+ constructor(name: string, signature: string, parentName: string, /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer) {
+ const headerSizeInBytes = 0;
+ const bodySizeInBytes = payload.byteLength;
+ super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable);
+ this.payload = payload;
+ }
+
+ public toBuffer(): Buffer {
+ return this.payload;
+ }
+
+ public getRawData(): Buffer {
+ return this.payload;
+ }
+}
+
+export class DependentCalldataBlock extends CalldataBlock {
+ public static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
+ public static RAW_DATA_START = new Buffer('<');
+ public static RAW_DATA_END = new Buffer('>');
+ private parent: CalldataBlock;
+ private dependency: CalldataBlock;
+ private aliasFor: CalldataBlock | undefined;
+
+ constructor(name: string, signature: string, parentName: string, relocatable: boolean, dependency: CalldataBlock, parent: CalldataBlock) {
+ const headerSizeInBytes = 0;
+ const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES;
+ super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes, relocatable);
+ this.parent = parent;
+ this.dependency = dependency;
+ this.aliasFor = undefined;
+ }
+
+ public toBuffer(): Buffer {
+ const destinationOffset = (this.aliasFor !== undefined) ? this.aliasFor.getOffsetInBytes() : this.dependency.getOffsetInBytes();
+ const parentOffset = this.parent.getOffsetInBytes();
+ const parentHeaderSize = this.parent.getHeaderSizeInBytes();
+ const pointer: number = destinationOffset - (parentOffset + parentHeaderSize);
+ const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(16)}`);
+ const evmWordWidthInBytes = 32;
+ const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes);
+ return pointerBufPadded;
+ }
+
+ public getDependency(): CalldataBlock {
+ return this.dependency;
+ }
+
+ public setAlias(block: CalldataBlock) {
+ 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(DependentCalldataBlock.RAW_DATA_START);
+ rawDataComponents.push(dependencyRawData);
+ rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END);
+ const rawData = Buffer.concat(rawDataComponents);
+ return rawData;
+ }
+}
+
+export class MemberCalldataBlock extends CalldataBlock {
+ private static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
+ private header: Buffer | undefined;
+ private members: CalldataBlock[];
+ private contiguous: boolean;
+
+ constructor(name: string, signature: string, parentName: string, relocatable: boolean, contiguous: boolean) {
+ super(name, signature, parentName, 0, 0, relocatable);
+ this.members = [];
+ this.header = undefined;
+ this.contiguous = contiguous;
+ }
+
+ public getRawData(): Buffer {
+ const rawDataComponents: Buffer[] = [];
+ if (this.header !== undefined) {
+ 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[]) {
+ let bodySizeInBytes = 0;
+ _.each(members, (member: CalldataBlock) => {
+ bodySizeInBytes += member.getSizeInBytes();
+ });
+ this.members = members;
+ this.setBodySize(0);
+ }
+
+ public isContiguous(): boolean {
+ return true;
+ }
+
+ public setHeader(header: Buffer) {
+ this.setHeaderSize(header.byteLength);
+ this.header = header;
+ }
+
+ public toBuffer(): Buffer {
+ if (this.header !== undefined) return this.header;
+ return new Buffer('');
+ }
+
+ public getMembers(): CalldataBlock[] {
+ return this.members;
+ }
+}
+
+class Queue<T> {
+ private store: T[] = [];
+ push(val: T) {
+ this.store.push(val);
+ }
+ pushFront(val: T) {
+ this.store.unshift(val);
+ }
+ pop(): T | undefined {
+ return this.store.shift();
+ }
+ popBack(): T | undefined {
+ if (this.store.length === 0) return undefined;
+ const backElement = this.store.splice(-1, 1)[0];
+ return backElement;
+ }
+ merge(q: Queue<T>) {
+ this.store = this.store.concat(q.store);
+ }
+ mergeFront(q: Queue<T>) {
+ this.store = q.store.concat(this.store);
+ }
+ getStore(): T[] {
+ return this.store;
+ }
+ peek(): T | undefined {
+ return this.store.length >= 0 ? this.store[0] : undefined;
+ }
+}
+
+export class Calldata {
+ private selector: string;
+ private rules: EncodingRules;
+ private sizeInBytes: number;
+ private root: MemberCalldataBlock | undefined;
+
+ constructor(rules: EncodingRules) {
+ this.selector = '';
+ this.rules = rules;
+ this.sizeInBytes = 0;
+ this.root = undefined;
+ }
+
+ private createQueue(memberBlock: MemberCalldataBlock): Queue<CalldataBlock> {
+ const blockQueue = new Queue<CalldataBlock>();
+ _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => {
+ if (member instanceof MemberCalldataBlock) {
+ blockQueue.mergeFront(this.createQueue(member));
+ } else {
+ blockQueue.pushFront(member);
+ }
+ });
+
+ // Children
+ _.each(memberBlock.getMembers(), (member: CalldataBlock) => {
+ if (member instanceof DependentCalldataBlock && member.getAlias() === undefined) {
+ let dependency = member.getDependency();
+ if (dependency instanceof MemberCalldataBlock) {
+ blockQueue.merge(this.createQueue(dependency));
+ } else {
+ blockQueue.push(dependency);
+ }
+ }
+ });
+
+ blockQueue.pushFront(memberBlock);
+ return blockQueue;
+ }
+
+ private generateAnnotatedHexString(): string {
+ let hexValue = `${this.selector}`;
+ if (this.root === undefined) {
+ throw new Error('expected root');
+ }
+
+ const valueQueue = this.createQueue(this.root);
+
+ let block: CalldataBlock | undefined;
+ let offset = 0;
+ const functionBlock = valueQueue.peek();
+ let functionName: string = functionBlock === undefined ? '' : functionBlock.getName();
+ while ((block = valueQueue.pop()) !== undefined) {
+ // Set f
+
+ // Process each block 1 word at a time
+ const size = block.getSizeInBytes();
+ const name = block.getName();
+ const parentName = block.getParentName();
+
+ //const ancestrialNamesOffset = name.startsWith('ptr<') ? 4 : 0;
+ //const parentOffset = name.lastIndexOf(parentName);
+ const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, '');//.replace(`${parentName}[`, '[');
+ const signature = block.getSignature();
+
+ // Current offset
+ let offsetStr = '';
+
+ // If this block is empty then it's a newline
+ let value = '';
+ let nameStr = '';
+ let line = '';
+ if (size === 0) {
+ offsetStr = ' '.repeat(10);
+ value = ' '.repeat(74);
+ nameStr = `### ${prettyName.padEnd(80)}`;
+ line = `\n${offsetStr}${value}${nameStr}`;
+ } else {
+ offsetStr = `0x${offset.toString(16)}`.padEnd(10, ' ');
+ value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(0, 32))).padEnd(74);
+ if (block instanceof MemberCalldataBlock) {
+ nameStr = `### ${prettyName.padEnd(80)}`;
+ line = `\n${offsetStr}${value}${nameStr}`;
+ } else {
+ nameStr = ` ${prettyName.padEnd(80)}`;
+ line = `${offsetStr}${value}${nameStr}`;
+ }
+ }
+
+ for (let j = 32; j < size; j += 32) {
+ offsetStr = `0x${(offset + j).toString(16)}`.padEnd(10, ' ');
+ value = ethUtil.stripHexPrefix(ethUtil.bufferToHex(block.toBuffer().slice(j, j + 32))).padEnd(74);
+ nameStr = ' '.repeat(40);
+
+ line = `${line}\n${offsetStr}${value}${nameStr}`;
+ }
+
+ // Append to hex value
+ hexValue = `${hexValue}\n${line}`;
+ offset += size;
+ }
+
+ return hexValue;
+ }
+
+ private generateCondensedHexString(): string {
+ let selectorBuffer = ethUtil.toBuffer(this.selector);
+ if (this.root === undefined) {
+ throw new Error('expected root');
+ }
+
+ const valueQueue = this.createQueue(this.root);
+ const valueBufs: Buffer[] = [selectorBuffer];
+ let block: CalldataBlock | undefined;
+ while ((block = valueQueue.pop()) !== undefined) {
+ valueBufs.push(block.toBuffer());
+ }
+
+ const combinedBuffers = Buffer.concat(valueBufs);
+ const hexValue = ethUtil.bufferToHex(combinedBuffers);
+ return hexValue;
+ }
+
+ public optimize() {
+ if (this.root === undefined) {
+ throw new Error('expected root');
+ }
+
+ const blocksByHash: { [key: string]: CalldataBlock } = {};
+
+ // 1. Create a queue of subtrees by hash
+ // Note that they are ordered the same as
+ const subtreeQueue = this.createQueue(this.root);
+ let block: CalldataBlock | undefined;
+ while ((block = subtreeQueue.popBack()) !== undefined) {
+ if (block instanceof DependentCalldataBlock) {
+ const blockHashBuf = block.getDependency().computeHash();
+ const blockHash = ethUtil.bufferToHex(blockHashBuf);
+ if (blockHash in blocksByHash) {
+ const blockWithSameHash = blocksByHash[blockHash];
+ if (blockWithSameHash !== block.getDependency()) {
+ block.setAlias(blockWithSameHash);
+ }
+ }
+ continue;
+ }
+
+ const blockHashBuf = block.computeHash();
+ const blockHash = ethUtil.bufferToHex(blockHashBuf);
+ if (blockHash in blocksByHash === false) {
+ blocksByHash[blockHash] = block;
+ }
+ }
+ }
+
+ public toHexString(): string {
+ if (this.root === undefined) {
+ throw new Error('expected root');
+ }
+
+ if (this.rules.optimize) this.optimize();
+
+ const offsetQueue = this.createQueue(this.root);
+ let block: CalldataBlock | undefined;
+ let offset = 0;
+ while ((block = offsetQueue.pop()) !== undefined) {
+ block.setOffset(offset);
+ offset += block.getSizeInBytes();
+ }
+
+ const hexValue = this.rules.annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString();
+ return hexValue;
+ }
+
+ public getSelectorHex(): string {
+ return this.selector;
+ }
+
+ public getSizeInBytes(): number {
+ return this.sizeInBytes;
+ }
+
+ public toAnnotatedString(): string {
+ return "";
+ }
+
+ public setRoot(block: MemberCalldataBlock) {
+ this.root = block;
+ this.sizeInBytes += block.getSizeInBytes();
+ }
+
+ public setSelector(selector: string) {
+ // Ensure we have a 0x prefix
+ if (selector.startsWith('0x')) {
+ this.selector = selector;
+ } else {
+ this.selector = `$0x${selector}`;
+ }
+
+ // The selector must be 10 characters: '0x' followed by 4 bytes (two hex chars per byte)
+ if (this.selector.length !== 10) {
+ throw new Error(`Invalid selector '${this.selector}'`);
+ }
+ this.sizeInBytes += 8;
+ }
+}
+
+export class RawCalldata {
+ private value: Buffer;
+ private offset: number; // tracks current offset into raw calldata; used for parsing
+ private selector: string;
+ private scopes: Queue<number>;
+
+ constructor(value: string | Buffer) {
+ if (typeof value === 'string' && !value.startsWith('0x')) {
+ throw new Error(`Expected raw calldata to start with '0x'`);
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ this.selector = ethUtil.bufferToHex(valueBuf.slice(0, 4));
+ this.value = valueBuf.slice(4); // disregard selector
+ this.offset = 0;
+ this.scopes = new Queue<number>();
+ this.scopes.push(0);
+ }
+
+ 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) {
+ this.offset = offsetInBytes;
+ }
+
+ public startScope() {
+ this.scopes.pushFront(this.offset);
+ }
+
+ public endScope() {
+ this.scopes.pop();
+ }
+
+ public getOffset(): number {
+ return this.offset;
+ }
+
+ public toAbsoluteOffset(relativeOffset: number) {
+ const scopeOffset = this.scopes.peek();
+ if (scopeOffset === undefined) {
+ throw new Error(`Tried to access undefined scope.`);
+ }
+ const absoluteOffset = relativeOffset + scopeOffset;
+ return absoluteOffset;
+ }
+
+ public getSelector(): string {
+ return this.selector;
+ }
+} \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts
new file mode 100644
index 000000000..3b4028abd
--- /dev/null
+++ b/packages/utils/src/abi_encoder/data_type.ts
@@ -0,0 +1,322 @@
+import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata";
+import { MethodAbi, DataItem } from 'ethereum-types';
+import { DecodingRules, EncodingRules } from './calldata';
+import { BigNumber } from '../configured_bignumber';
+import ethUtil = require('ethereumjs-util');
+var _ = require('lodash');
+
+export interface DataTypeFactory {
+ create: (dataItem: DataItem, parentDataType: DataType) => DataType;
+ mapDataItemToDataType: (dataItem: DataItem) => DataType;
+}
+
+export abstract class DataType {
+ private static DEFAULT_ENCODING_RULES = { optimize: false, annotate: false } as EncodingRules;
+ private static DEFAULT_DECODING_RULES = { structsAsObjects: false } as DecodingRules;
+
+ private dataItem: DataItem;
+ private 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_ = rules ? rules : DataType.DEFAULT_ENCODING_RULES;
+ const calldata = new Calldata(rules_);
+ if (selector) calldata.setSelector(selector);
+ const block = this.generateCalldataBlock(value);
+ calldata.setRoot(block as MemberCalldataBlock); // @TODO CHANGE
+ const calldataHex = calldata.toHexString();
+ return calldataHex;
+ }
+
+ public decode(calldata: string, rules?: DecodingRules): any {
+ const rawCalldata = new RawCalldata(calldata);
+ const rules_ = rules ? rules : DataType.DEFAULT_DECODING_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;
+}
+
+export abstract class PayloadDataType extends DataType {
+ protected hasConstantSize: boolean;
+
+ public constructor(dataItem: DataItem, factory: DataTypeFactory, hasConstantSize: boolean) {
+ super(dataItem, factory);
+ this.hasConstantSize = hasConstantSize;
+ }
+
+ public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock {
+ const encodedValue = this.encodeValue(value);
+ const name = this.getDataItem().name;
+ const signature = this.getSignature();
+ const parentName = parentBlock === undefined ? '' : parentBlock.getName();
+ const relocatable = false;
+ const block = new PayloadCalldataBlock(name, signature, parentName, /*offsetInBytes,*/ relocatable, encodedValue);
+ return block;
+ }
+
+ public generateValue(calldata: RawCalldata, rules: DecodingRules): any {
+ const value = this.decodeValue(calldata);
+ return value;
+ }
+
+ public isStatic(): boolean {
+ // If a payload has a constant size then it's static
+ return this.hasConstantSize;
+ }
+
+ public abstract encodeValue(value: any): Buffer;
+ public abstract decodeValue(calldata: RawCalldata): any;
+}
+
+export abstract class DependentDataType extends DataType {
+ protected dependency: DataType;
+ protected parent: DataType;
+
+ public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) {
+ super(dataItem, factory);
+ this.dependency = dependency;
+ this.parent = parent;
+ }
+
+ public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock {
+ if (parentBlock === undefined) {
+ throw new Error(`DependentDataType requires a parent block to generate its block`);
+ }
+ const dependencyBlock = this.dependency.generateCalldataBlock(value, parentBlock);
+ const name = this.getDataItem().name;
+ const signature = this.getSignature();
+ const parentName = parentBlock === undefined ? '' : parentBlock.getName();
+ const relocatable = false;
+ const block = new DependentCalldataBlock(name, signature, parentName, relocatable, dependencyBlock, parentBlock);
+ return block;
+ }
+
+ public generateValue(calldata: RawCalldata, rules: DecodingRules): any {
+ const destinationOffsetBuf = calldata.popWord();
+ const currentOffset = calldata.getOffset();
+ const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16);
+ const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative);
+ calldata.setOffset(destinationOffsetAbsolute);
+ const value = this.dependency.generateValue(calldata, rules);
+ calldata.setOffset(currentOffset);
+ return value;
+ }
+
+ public isStatic(): boolean {
+ return true;
+ }
+}
+
+export interface MemberMap {
+ [key: string]: number;
+}
+
+export abstract class MemberDataType extends DataType {
+ private memberMap: MemberMap;
+ private members: DataType[];
+ private isArray: boolean;
+ protected arrayLength: number | undefined;
+ protected arrayElementType: string | undefined;
+
+
+ public constructor(dataItem: DataItem, factory: DataTypeFactory, isArray: boolean = false, arrayLength?: number, arrayElementType?: string) {
+ super(dataItem, factory);
+ this.memberMap = {};
+ this.members = [];
+ this.isArray = isArray;
+ this.arrayLength = arrayLength;
+ this.arrayElementType = arrayElementType;
+ if (isArray && arrayLength !== undefined) {
+ [this.members, this.memberMap] = this.createMembersWithLength(dataItem, arrayLength);
+ } else if (!isArray) {
+ [this.members, this.memberMap] = this.createMembersWithKeys(dataItem);
+ }
+ }
+
+ private createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] {
+ // Sanity check
+ if (dataItem.components === undefined) {
+ throw new Error(`Expected components`);
+ }
+
+ let members: DataType[] = [];
+ let memberMap: MemberMap = {};
+ _.each(dataItem.components, (memberItem: DataItem) => {
+ const childDataItem = {
+ type: memberItem.type,
+ name: `${dataItem.name}.${memberItem.name}`,
+ } as DataItem;
+ const components = memberItem.components;
+ if (components !== undefined) {
+ childDataItem.components = components;
+ }
+ const child = this.getFactory().create(childDataItem, this);
+ memberMap[memberItem.name] = members.length;
+ members.push(child);
+ });
+
+ return [members, memberMap];
+ }
+
+ private createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] {
+ let members: DataType[] = [];
+ let memberMap: MemberMap = {};
+ const range = _.range(length);
+ _.each(range, (idx: number) => {
+ const childDataItem = {
+ type: this.arrayElementType,
+ name: `${dataItem.name}[${idx.toString(10)}]`,
+ } as DataItem;
+ const components = dataItem.components;
+ if (components !== undefined) {
+ childDataItem.components = components;
+ }
+ const child = this.getFactory().create(childDataItem, this);
+ memberMap[idx.toString(10)] = members.length;
+ members.push(child);
+ });
+
+ return [members, memberMap];
+ }
+
+ protected generateCalldataBlockFromArray(value: any[], parentBlock?: CalldataBlock): MemberCalldataBlock {
+ // Sanity check length
+ if (this.arrayLength !== undefined && value.length !== this.arrayLength) {
+ throw new Error(
+ `Expected array of ${JSON.stringify(
+ this.arrayLength,
+ )} elements, but got array of length ${JSON.stringify(value.length)}`,
+ );
+ }
+
+ const parentName = parentBlock === undefined ? '' : parentBlock.getName();
+ const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), parentName, this.isStatic(), false);
+
+ let members = this.members;
+ if (this.isArray && this.arrayLength === undefined) {
+ [members,] = this.createMembersWithLength(this.getDataItem(), value.length);
+
+ const lenBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.length.toString(16)}`), 32);
+ methodBlock.setHeader(lenBuf);
+ }
+
+ const memberBlocks: CalldataBlock[] = [];
+ _.each(members, (member: DataType, idx: number) => {
+ const block = member.generateCalldataBlock(value[idx], methodBlock);
+ memberBlocks.push(block);
+ });
+ methodBlock.setMembers(memberBlocks);
+ return methodBlock;
+ }
+
+ protected generateCalldataBlockFromObject(obj: object, parentBlock?: CalldataBlock): MemberCalldataBlock {
+ const parentName = parentBlock === undefined ? '' : parentBlock.getName();
+ const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), parentName, this.isStatic(), false);
+ const memberBlocks: CalldataBlock[] = [];
+ let childMap = _.cloneDeep(this.memberMap);
+ _.forOwn(obj, (value: any, key: string) => {
+ if (key in childMap === false) {
+ throw new Error(`Could not assign tuple to object: unrecognized key '${key}' in object ${this.getDataItem().name}`);
+ }
+ const block = this.members[this.memberMap[key]].generateCalldataBlock(value, methodBlock);
+ memberBlocks.push(block);
+ delete childMap[key];
+ });
+
+ if (Object.keys(childMap).length !== 0) {
+ throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`);
+ }
+
+ methodBlock.setMembers(memberBlocks);
+ return methodBlock;
+ }
+
+ public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock {
+ 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;
+ if (this.isArray && this.arrayLength === undefined) {
+ const arrayLengthBuf = calldata.popWord();
+ const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf);
+ const hexBase = 16;
+ const arrayLength = new BigNumber(arrayLengthHex, hexBase);
+
+ [members,] = this.createMembersWithLength(this.getDataItem(), arrayLength.toNumber());
+ }
+
+ calldata.startScope();
+ let value: any[] | object;
+ if (rules.structsAsObjects && !this.isArray) {
+ value = {};
+ _.each(this.memberMap, (idx: number, key: string) => {
+ const member = this.members[idx];
+ let memberValue = member.generateValue(calldata, rules);
+ (value as { [key: string]: any })[key] = memberValue;
+ });
+ } else {
+ value = [];
+ _.each(members, (member: DataType, idx: number) => {
+ let memberValue = member.generateValue(calldata, rules);
+ (value as any[]).push(memberValue);
+ });
+ }
+ calldata.endScope();
+ return value;
+ }
+
+ 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;
+ }
+
+ public isStatic(): boolean {
+ /* For Tuple:
+ const isStaticTuple = this.children.length === 0;
+ return isStaticTuple; // @TODO: True in every case or only when dynamic data?
+
+ For Array:
+ if isLengthDefined = false then this is false
+
+ Otherwise if the first element is a Pointer then false
+ */
+
+ if (this.isArray && this.arrayLength === undefined) {
+ return false;
+ }
+
+ // Search for dependent members
+ const dependentMember = _.find(this.members, (member: DataType) => {
+ return (member instanceof DependentDataType);
+ });
+ const isStatic = (dependentMember === undefined); // static if we couldn't find a dependent member
+ return isStatic;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/evm_data_types.ts b/packages/utils/src/abi_encoder/evm_data_types.ts
new file mode 100644
index 000000000..2973596fe
--- /dev/null
+++ b/packages/utils/src/abi_encoder/evm_data_types.ts
@@ -0,0 +1,550 @@
+import { DataType, DataTypeFactory, PayloadDataType, DependentDataType, MemberDataType } from './data_type';
+
+import { DecodingRules, EncodingRules } from './calldata';
+
+import { MethodAbi, DataItem } from 'ethereum-types';
+
+import ethUtil = require('ethereumjs-util');
+
+import { Calldata, RawCalldata } from './calldata';
+
+import { BigNumber } from '../configured_bignumber';
+
+var _ = require('lodash');
+
+export interface DataTypeStaticInterface {
+ matchGrammar: (type: string) => boolean;
+ encodeValue: (value: any) => Buffer;
+ decodeValue: (rawCalldata: RawCalldata) => any;
+}
+
+export class Address extends PayloadDataType {
+ private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance(), Address.SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!Address.matchGrammar(dataItem.type)) {
+ throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`);
+ }
+ }
+
+ public getSignature(): string {
+ return 'address';
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'address';
+ }
+
+ public encodeValue(value: boolean): Buffer {
+ const evmWordWidth = 32;
+ const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth);
+ return encodedValueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ const paddedValueBuf = calldata.popWord();
+ const valueBuf = paddedValueBuf.slice(12);
+ const value = ethUtil.bufferToHex(valueBuf);
+ return value;
+ }
+}
+
+export class Bool extends PayloadDataType {
+ private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance(), Bool.SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!Bool.matchGrammar(dataItem.type)) {
+ throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`);
+ }
+ }
+
+ public getSignature(): string {
+ return 'bool';
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'bool';
+ }
+
+ public encodeValue(value: boolean): Buffer {
+ const evmWordWidth = 32;
+ const encodedValue = value === true ? '0x1' : '0x0';
+ const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth);
+ return encodedValueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): boolean {
+ const valueBuf = calldata.popWord();
+ const valueHex = ethUtil.bufferToHex(valueBuf);
+ const valueNumber = new BigNumber(valueHex, 16);
+ let value: boolean = (valueNumber.equals(0)) ? false : true;
+ if (!(valueNumber.equals(0) || valueNumber.equals(1))) {
+ throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`);
+ }
+ return value;
+ }
+}
+
+abstract class Number extends PayloadDataType {
+ private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+ static MAX_WIDTH: number = 256;
+ static DEFAULT_WIDTH: number = Number.MAX_WIDTH;
+ width: number = Number.DEFAULT_WIDTH;
+
+ constructor(dataItem: DataItem, matcher: RegExp) {
+ super(dataItem, EvmDataTypeFactory.getInstance(), Number.SIZE_KNOWN_AT_COMPILE_TIME);
+ const matches = matcher.exec(dataItem.type);
+ if (matches === null) {
+ throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`);
+ }
+ if (matches !== null && matches.length === 2 && matches[1] !== undefined) {
+ this.width = parseInt(matches[1]);
+ } else {
+ this.width = 256;
+ }
+ }
+
+ public encodeValue(value: BigNumber): Buffer {
+ if (value.greaterThan(this.getMaxValue())) {
+ throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`;
+ } else if (value.lessThan(this.getMinValue())) {
+ throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`;
+ }
+
+ const hexBase = 16;
+ const evmWordWidth = 32;
+ let valueBuf: Buffer;
+ if (value.greaterThanOrEqualTo(0)) {
+ valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth);
+ } else {
+ // BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves.
+ // Step 1/3: Convert value to positive binary string
+ const binBase = 2;
+ const valueBin = value.times(-1).toString(binBase);
+
+ // Step 2/3: Invert binary value
+ const bitsInEvmWord = 256;
+ let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length);
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, binBase);
+
+ // Step 3/3: Add 1 to inverted value
+ // The result is the two's-complement represent of the input value.
+ const negativeValue = invertedValue.plus(1);
+
+ // Convert the negated value to a hex string
+ valueBuf = ethUtil.setLengthLeft(
+ ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`),
+ evmWordWidth,
+ );
+ }
+
+ return valueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): BigNumber {
+ const paddedValueBuf = calldata.popWord();
+ const paddedValueHex = ethUtil.bufferToHex(paddedValueBuf);
+ let value = new BigNumber(paddedValueHex, 16);
+ if (this instanceof Int) {
+ // Check if we're negative
+ const binBase = 2;
+ const paddedValueBin = value.toString(binBase);
+ const valueBin = paddedValueBin.slice(paddedValueBin.length - this.width);
+ if (valueBin[0].startsWith('1')) {
+ // Negative
+ // Step 1/3: Invert binary value
+ let invertedValueBin = '';
+ _.each(valueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, binBase);
+
+ // Step 2/3: Add 1 to inverted value
+ // The result is the two's-complement represent of the input value.
+ const positiveValue = invertedValue.plus(1);
+
+ // Step 3/3: Invert positive value
+ const negativeValue = positiveValue.times(-1);
+ value = negativeValue;
+ }
+ }
+
+ return value;
+ }
+
+ public abstract getMaxValue(): BigNumber;
+ public abstract getMinValue(): BigNumber;
+}
+
+export class Int extends Number {
+ static 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}$',
+ );
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, Int.matcher);
+ }
+
+ public getMaxValue(): BigNumber {
+ return new BigNumber(2).toPower(this.width - 1).sub(1);
+ }
+
+ public getMinValue(): BigNumber {
+ return new BigNumber(2).toPower(this.width - 1).times(-1);
+ }
+
+ public getSignature(): string {
+ return `int${this.width}`;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class UInt extends Number {
+ static 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}$',
+ );
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, UInt.matcher);
+ }
+
+ public getMaxValue(): BigNumber {
+ return new BigNumber(2).toPower(this.width).sub(1);
+ }
+
+ public getMinValue(): BigNumber {
+ return new BigNumber(0);
+ }
+
+ public getSignature(): string {
+ return `uint${this.width}`;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class Byte extends PayloadDataType {
+ private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
+ static 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))$',
+ );
+
+ static DEFAULT_WIDTH = 1;
+ width: number = Byte.DEFAULT_WIDTH;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance(), Byte.SIZE_KNOWN_AT_COMPILE_TIME);
+ const matches = Byte.matcher.exec(dataItem.type);
+ if (!Byte.matchGrammar(dataItem.type)) {
+ throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`);
+ }
+ if (matches !== null && matches.length === 3 && matches[2] !== undefined) {
+ this.width = parseInt(matches[2]);
+ } else {
+ this.width = Byte.DEFAULT_WIDTH;
+ }
+ }
+
+ public getSignature(): string {
+ // Note that `byte` reduces to `bytes1`
+ return `bytes${this.width}`;
+ }
+
+ public encodeValue(value: string | Buffer): Buffer {
+ // Convert value into a buffer and do bounds checking
+ 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()}`,
+ );
+ } else if (value.length % 2 !== 0) {
+ throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
+ }
+
+ // Store value as hex
+ const evmWordWidth = 32;
+ const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth);
+ return paddedValue;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ const paddedValueBuf = calldata.popWord();
+ const valueBuf = paddedValueBuf.slice(0, this.width);
+ const value = ethUtil.bufferToHex(valueBuf);
+ return value;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class Bytes extends PayloadDataType {
+ private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false;
+ static UNDEFINED_LENGTH = new BigNumber(-1);
+ length: BigNumber = Bytes.UNDEFINED_LENGTH;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance(), Bytes.SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!Bytes.matchGrammar(dataItem.type)) {
+ throw new Error(`Tried to instantiate Bytes with bad input: ${dataItem}`);
+ }
+ }
+
+ public encodeValue(value: string | Buffer): Buffer {
+ if (typeof value === 'string' && !value.startsWith('0x')) {
+ throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`);
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ if (value.length % 2 !== 0) {
+ throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
+ }
+
+ const wordsForValue = Math.ceil(valueBuf.byteLength / 32);
+ const paddedBytesForValue = wordsForValue * 32;
+ const paddedValueBuf = ethUtil.setLengthRight(valueBuf, paddedBytesForValue);
+ const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32);
+ const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]);
+ return encodedValueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ const lengthBuf = calldata.popWord();
+ const lengthHex = ethUtil.bufferToHex(lengthBuf);
+ const length = parseInt(lengthHex, 16);
+ const wordsForValue = Math.ceil(length / 32);
+ const paddedValueBuf = calldata.popWords(wordsForValue);
+ const valueBuf = paddedValueBuf.slice(0, length);
+ const decodedValue = ethUtil.bufferToHex(valueBuf);
+ return decodedValue;
+ }
+
+ public getSignature(): string {
+ return 'bytes';
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'bytes';
+ }
+}
+
+export class SolString extends PayloadDataType {
+ private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false;
+ constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance(), SolString.SIZE_KNOWN_AT_COMPILE_TIME);
+ if (!SolString.matchGrammar(dataItem.type)) {
+ throw new Error(`Tried to instantiate String with bad input: ${dataItem}`);
+ }
+ }
+
+ public encodeValue(value: string): Buffer {
+ const wordsForValue = Math.ceil(value.length / 32);
+ const paddedBytesForValue = wordsForValue * 32;
+ const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
+ const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32);
+ const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]);
+ return encodedValueBuf;
+ }
+
+ public decodeValue(calldata: RawCalldata): string {
+ const lengthBuf = calldata.popWord();
+ const lengthHex = ethUtil.bufferToHex(lengthBuf);
+ const length = parseInt(lengthHex, 16);
+ const wordsForValue = Math.ceil(length / 32);
+ const paddedValueBuf = calldata.popWords(wordsForValue);
+ const valueBuf = paddedValueBuf.slice(0, length);
+ const value = valueBuf.toString('ascii');
+ return value;
+ }
+
+ public getSignature(): string {
+ return 'string';
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'string';
+ }
+}
+
+export class Pointer extends DependentDataType {
+
+ constructor(destDataType: DataType, parentDataType: DataType) {
+ const destDataItem = destDataType.getDataItem();
+ const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem;
+ super(dataItem, EvmDataTypeFactory.getInstance(), destDataType, parentDataType);
+ }
+
+ public getSignature(): string {
+ return this.dependency.getSignature();
+ }
+}
+
+export class Tuple extends MemberDataType {
+ private tupleSignature: string;
+
+ constructor(dataItem: DataItem) {
+ super(dataItem, EvmDataTypeFactory.getInstance());
+ if (!Tuple.matchGrammar(dataItem.type)) {
+ throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`);
+ }
+ this.tupleSignature = this.computeSignatureOfMembers();
+ }
+
+ public getSignature(): string {
+ return this.tupleSignature;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return type === 'tuple';
+ }
+}
+
+export class SolArray extends MemberDataType {
+ static matcher = RegExp('^(.+)\\[([0-9]*)\\]$');
+ private arraySignature: string;
+ private elementType: string;
+
+ constructor(dataItem: DataItem) {
+ // Sanity check
+ const matches = SolArray.matcher.exec(dataItem.type);
+ if (matches === null || matches.length !== 3) {
+ throw new Error(`Could not parse array: ${dataItem.type}`);
+ } else if (matches[1] === undefined) {
+ throw new Error(`Could not parse array type: ${dataItem.type}`);
+ } else if (matches[2] === undefined) {
+ throw new Error(`Could not parse array length: ${dataItem.type}`);
+ }
+
+ const isArray = true;
+ const arrayElementType = matches[1];
+ const arrayLength = (matches[2] === '') ? undefined : parseInt(matches[2], 10);
+ super(dataItem, EvmDataTypeFactory.getInstance(), isArray, arrayLength, arrayElementType);
+ this.elementType = arrayElementType;
+ this.arraySignature = this.computeSignature();
+ }
+
+ private computeSignature(): string {
+ let dataItem = {
+ type: this.elementType,
+ name: 'N/A',
+ } as DataItem;
+ const components = this.getDataItem().components;
+ if (components !== undefined) {
+ dataItem.components = components;
+ }
+ const elementDataType = this.getFactory().mapDataItemToDataType(dataItem);
+ const type = elementDataType.getSignature();
+ if (this.arrayLength === undefined) {
+ return `${type}[]`;
+ } else {
+ return `${type}[${this.arrayLength}]`;
+ }
+ }
+
+ public getSignature(): string {
+ return this.arraySignature;
+ }
+
+ public static matchGrammar(type: string): boolean {
+ return this.matcher.test(type);
+ }
+}
+
+export class Method extends MemberDataType {
+ private methodSignature: string;
+ private methodSelector: string;
+
+ // TMP
+ public selector: string;
+
+ constructor(abi: MethodAbi) {
+ super({ type: 'method', name: abi.name, components: abi.inputs }, EvmDataTypeFactory.getInstance());
+ this.methodSignature = this.computeSignature();
+ this.selector = this.methodSelector = this.computeSelector();
+
+ }
+
+ 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(0, 4)));
+ return selector;
+ }
+
+ public encode(value: any, rules?: EncodingRules): string {
+ const calldata = super.encode(value, rules, this.selector);
+ return calldata;
+ }
+
+ public decode(calldata: string, rules?: DecodingRules): any[] | object {
+ if (!calldata.startsWith(this.selector)) {
+ throw new Error(`Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`);
+ }
+ const value = super.decode(calldata, rules);
+ return value;
+ }
+
+ public getSignature(): string {
+ return this.methodSignature;
+ }
+
+ public getSelector(): string {
+ return this.methodSelector;
+ }
+}
+
+export class EvmDataTypeFactory implements DataTypeFactory {
+ private static instance: DataTypeFactory;
+
+ private constructor() { }
+
+ public static getInstance(): DataTypeFactory {
+ if (!EvmDataTypeFactory.instance) {
+ EvmDataTypeFactory.instance = new EvmDataTypeFactory();
+ }
+ return EvmDataTypeFactory.instance;
+ }
+
+ public mapDataItemToDataType(dataItem: DataItem): DataType {
+ if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
+ if (Address.matchGrammar(dataItem.type)) return new Address(dataItem);
+ if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem);
+ if (Int.matchGrammar(dataItem.type)) return new Int(dataItem);
+ if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem);
+ if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem);
+ if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem);
+ if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem);
+ if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem);
+ //if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem);
+ //if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem);
+
+ throw new Error(`Unrecognized data type: '${dataItem.type}'`);
+ }
+
+ public create(dataItem: DataItem, parentDataType: DataType): DataType {
+ const dataType = this.mapDataItemToDataType(dataItem);
+ if (dataType.isStatic()) {
+ return dataType;
+ }
+
+ const pointer = new Pointer(dataType, parentDataType);
+ return pointer;
+ }
+} \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts
new file mode 100644
index 000000000..991edb8c5
--- /dev/null
+++ b/packages/utils/src/abi_encoder/index.ts
@@ -0,0 +1,2 @@
+export { EncodingRules, DecodingRules } from './calldata';
+export * from './evm_data_types'; \ No newline at end of file
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 0723e5788..c44530bbc 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 * from './abi_encoder';