aboutsummaryrefslogtreecommitdiffstats
path: root/packages/utils/src/abi_encoder
diff options
context:
space:
mode:
Diffstat (limited to 'packages/utils/src/abi_encoder')
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/data_type.ts51
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts50
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/index.ts5
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts16
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts230
-rw-r--r--packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts38
6 files changed, 390 insertions, 0 deletions
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts
new file mode 100644
index 000000000..a6adeb23b
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/data_type.ts
@@ -0,0 +1,51 @@
+import { DataItem } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { Calldata, CalldataBlock, RawCalldata } from '../calldata';
+import { DecodingRules, EncodingRules } from '../utils/rules';
+
+import { DataTypeFactory } from './interfaces';
+
+export abstract class DataType {
+ private static readonly _DEFAULT_ENCODING_RULES: EncodingRules = { optimize: false, annotate: false };
+ private static readonly _DEFAULT_DECODING_RULES: DecodingRules = { structsAsObjects: false };
+ 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_ = rules ? rules : DataType._DEFAULT_ENCODING_RULES;
+ const calldata = new Calldata(rules_);
+ if (selector) {
+ calldata.setSelector(selector);
+ }
+ const block = this.generateCalldataBlock(value);
+ calldata.setRoot(block); // @TODO CHANGE
+ const calldataHex = calldata.toHexString();
+ return calldataHex;
+ }
+
+ public decode(calldata: string, rules?: DecodingRules, hasSelector: boolean = false): any {
+ const rawCalldata = new RawCalldata(calldata, hasSelector);
+ 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;
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts
new file mode 100644
index 000000000..e9f390b22
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/dependent_data_type.ts
@@ -0,0 +1,50 @@
+import { DataItem } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { CalldataBlock, DependentCalldataBlock, RawCalldata } from '../calldata';
+import * as Constants from '../constants';
+import { DecodingRules } from '../utils/rules';
+
+import { DataType } from './data_type';
+import { DataTypeFactory } from './interfaces';
+
+export abstract class DependentDataType extends DataType {
+ protected _dependency: DataType;
+ protected _parent: DataType;
+ private readonly _isStatic: boolean;
+
+ public constructor(dataItem: DataItem, factory: DataTypeFactory, dependency: DataType, parent: DataType) {
+ super(dataItem, factory);
+ this._dependency = dependency;
+ this._parent = parent;
+ this._isStatic = true;
+ }
+
+ 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 block = new DependentCalldataBlock(name, signature, parentName, 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), Constants.HEX_BASE);
+ const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative);
+ calldata.setOffset(destinationOffsetAbsolute);
+ const value = this._dependency.generateValue(calldata, rules);
+ calldata.setOffset(currentOffset);
+ return value;
+ }
+
+ public isStatic(): boolean {
+ return this._isStatic;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/index.ts b/packages/utils/src/abi_encoder/abstract_data_types/index.ts
new file mode 100644
index 000000000..9ad568134
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/index.ts
@@ -0,0 +1,5 @@
+export * from './interfaces';
+export * from './data_type';
+export * from './dependent_data_type';
+export * from './member_data_type';
+export * from './payload_data_type';
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..2ae92659c
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/interfaces.ts
@@ -0,0 +1,16 @@
+import { DataItem } from 'ethereum-types';
+
+import { RawCalldata } from '../calldata';
+
+import { DataType } from './data_type';
+
+export interface DataTypeFactory {
+ create: (dataItem: DataItem, parentDataType?: DataType) => DataType;
+ mapDataItemToDataType: (dataItem: DataItem) => DataType;
+}
+
+export interface DataTypeStaticInterface {
+ matchType: (type: string) => boolean;
+ encodeValue: (value: any) => Buffer;
+ decodeValue: (rawCalldata: RawCalldata) => any;
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts
new file mode 100644
index 000000000..f44a6dd30
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/member_data_type.ts
@@ -0,0 +1,230 @@
+import { DataItem } from 'ethereum-types';
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import { BigNumber } from '../../configured_bignumber';
+import { CalldataBlock, MemberCalldataBlock, RawCalldata } from '../calldata';
+import * as Constants from '../constants';
+import { DecodingRules } from '../utils/rules';
+
+import { DataType } from './data_type';
+import { DependentDataType } from './dependent_data_type';
+import { DataTypeFactory } from './interfaces';
+
+interface MemberMap {
+ [key: string]: number;
+}
+
+export abstract class MemberDataType extends DataType {
+ protected readonly _arrayLength: number | undefined;
+ protected readonly _arrayElementType: string | undefined;
+ private readonly _memberMap: MemberMap;
+ 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._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);
+ }
+ }
+
+ 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];
+ const memberValue = member.generateValue(calldata, rules);
+ (value as { [key: string]: any })[key] = memberValue;
+ });
+ } else {
+ value = [];
+ _.each(members, (member: DataType, idx: number) => {
+ const memberValue = member.generateValue(calldata, rules);
+ (value as any[]).push(memberValue);
+ });
+ }
+ calldata.endScope();
+ return value;
+ }
+
+ 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;
+ }
+
+ 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,
+ );
+
+ 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(Constants.HEX_BASE)}`),
+ Constants.EVM_WORD_WIDTH_IN_BYTES,
+ );
+ 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,
+ );
+ const memberBlocks: CalldataBlock[] = [];
+ const childMap = _.cloneDeep(this._memberMap);
+ _.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 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;
+ }
+
+ 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[], MemberMap] {
+ // Sanity check
+ if (dataItem.components === undefined) {
+ throw new Error(`Expected components`);
+ }
+
+ const members: DataType[] = [];
+ const memberMap: MemberMap = {};
+ _.each(dataItem.components, (memberItem: DataItem) => {
+ const childDataItem: DataItem = {
+ type: memberItem.type,
+ name: `${dataItem.name}.${memberItem.name}`,
+ };
+ 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] {
+ const members: DataType[] = [];
+ const memberMap: MemberMap = {};
+ const range = _.range(length);
+ _.each(range, (idx: number) => {
+ const childDataItem: DataItem = {
+ type: this._arrayElementType ? this._arrayElementType : '',
+ name: `${dataItem.name}[${idx.toString(Constants.DEC_BASE)}]`,
+ };
+ const components = dataItem.components;
+ if (components !== undefined) {
+ childDataItem.components = components;
+ }
+ const child = this.getFactory().create(childDataItem, this);
+ memberMap[idx.toString(Constants.DEC_BASE)] = members.length;
+ members.push(child);
+ });
+
+ return [members, memberMap];
+ }
+}
diff --git a/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts b/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts
new file mode 100644
index 000000000..767e64f51
--- /dev/null
+++ b/packages/utils/src/abi_encoder/abstract_data_types/payload_data_type.ts
@@ -0,0 +1,38 @@
+import { DataItem } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { CalldataBlock, PayloadCalldataBlock, RawCalldata } from '../calldata';
+import { DecodingRules } from '../utils/rules';
+
+import { DataType } from './data_type';
+import { DataTypeFactory } from './interfaces';
+
+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 block = new PayloadCalldataBlock(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._hasConstantSize;
+ }
+
+ public abstract encodeValue(value: any): Buffer;
+ public abstract decodeValue(calldata: RawCalldata): any;
+}