From 5b0d554f7baec54837d795b6568ae5ba8d8a0908 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 15:27:07 -0800 Subject: Moved Abi Encoder into utils package --- packages/utils/src/abi_encoder/calldata.ts | 520 +++++++++++++ packages/utils/src/abi_encoder/data_type.ts | 322 ++++++++ packages/utils/src/abi_encoder/evm_data_types.ts | 550 +++++++++++++ packages/utils/src/abi_encoder/index.ts | 2 + packages/utils/src/index.ts | 1 + packages/utils/test/abi_encoder_test.ts | 935 +++++++++++++++++++++++ packages/utils/test/abi_samples.ts | 814 ++++++++++++++++++++ packages/utils/test/utils/chai_setup.ts | 13 + 8 files changed, 3157 insertions(+) create mode 100644 packages/utils/src/abi_encoder/calldata.ts create mode 100644 packages/utils/src/abi_encoder/data_type.ts create mode 100644 packages/utils/src/abi_encoder/evm_data_types.ts create mode 100644 packages/utils/src/abi_encoder/index.ts create mode 100644 packages/utils/test/abi_encoder_test.ts create mode 100644 packages/utils/test/abi_samples.ts create mode 100644 packages/utils/test/utils/chai_setup.ts (limited to 'packages/utils') 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 { + 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) { + this.store = this.store.concat(q.store); + } + mergeFront(q: Queue) { + 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 { + const blockQueue = new Queue(); + _.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; + + 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(); + 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'; diff --git a/packages/utils/test/abi_encoder_test.ts b/packages/utils/test/abi_encoder_test.ts new file mode 100644 index 000000000..2a8fba450 --- /dev/null +++ b/packages/utils/test/abi_encoder_test.ts @@ -0,0 +1,935 @@ +import * as chai from 'chai'; +import 'mocha'; + +// import { assert } from '@0x/order-utils/src/assert'; + +import { chaiSetup } from './utils/chai_setup'; +import { BigNumber } from '../src/'; +//import * as AbiEncoder from './abi_encoder'; + +import * as AbiEncoder from '../src/abi_encoder'; +import * as AbiSamples from './abi_samples'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe.only('ABI Encoder', () => { + describe.only('Optimizer', () => { + + }); + + describe.only('ABI Tests at Method Level', () => { + + it('Should reuse duplicated strings in string array', async () => { + const method = new AbiEncoder.Method(AbiSamples.stringAbi); + const strings = [ + "Test String", + "Test String 2", + "Test String", + "Test String 2", + ]; + const args = [strings]; + + // Verify optimized calldata is expected + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000'; + //expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + + // Verify args decode properly + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); + }); + + it('Should point array elements to a duplicated value from another parameter', async () => { + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi2); + const stringArray = [ + "Test String", + "Test String", + "Test String", + "Test String 2", + ]; + const string = 'Test String'; + const args = [stringArray, string]; + + // Verify optimized calldata is expected + const optimizedCalldata = method.encode(args, { optimize: true }); + const expectedOptimizedCalldata = '0xe0e0d34900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000d5465737420537472696e67203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5465737420537472696e67000000000000000000000000000000000000000000'; + expect(optimizedCalldata).to.be.equal(expectedOptimizedCalldata); + + // Verify args decode properly + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + expect(decodedArgsJson).to.be.equal(argsJson); + + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true, annotate: true }), '\n', '*'.repeat(100)); + console.log('*'.repeat(100), '\n', method.encode(args, { optimize: true }), '\n', '*'.repeat(100)); + }); + + + it('Optimizer #3 (tuple should point to array)', async () => { + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi3); + const uint8Array = [ + new BigNumber(100), + new BigNumber(150), + new BigNumber(200), + new BigNumber(225), + ]; + const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; + const args = [uint8Array, uintTupleArray]; + + + const TEST = method.encode(args, { optimize: true, annotate: true }); + console.log('*'.repeat(50), ' ENCODED DATA ', TEST); + + const optimizedCalldata = method.encode(args, { optimize: true }); + + console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + console.log(JSON.stringify(decodedArgs)); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + + it('Optimizer #4 (Expect no optimization)', async () => { + const method = new AbiEncoder.Method(AbiSamples.optimizerAbi4); + const uint8Array = [ + new BigNumber(100), + new BigNumber(150), + new BigNumber(200), + new BigNumber(225), + ]; + const uintTupleArray = [[uint8Array[0]], [uint8Array[1]], [uint8Array[2]], [uint8Array[3]]]; + const args = [uint8Array, uintTupleArray]; + + + const TEST = method.encode(args, { optimize: true, annotate: true }); + console.log('*'.repeat(50), ' ENCODED DATA ', TEST); + + const optimizedCalldata = method.encode(args, { optimize: true }); + + console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`); + const decodedArgs = method.decode(optimizedCalldata); + const decodedArgsJson = JSON.stringify(decodedArgs); + const argsJson = JSON.stringify(args); + console.log(JSON.stringify(decodedArgs)); + expect(decodedArgsJson).to.be.equal(argsJson); + }); + + it('Crazy ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.crazyAbi); + console.log(method.getSignature()); + + 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: someStaticArray, + someStaticArrayWithDynamicMembers: someStaticArrayWithDynamicMembers, + someDynamicArrayWithDynamicMembers: someDynamicArrayWithDynamicMembers, + some2DArray: some2DArray, + someTuple: someTuple, + someTupleWithDynamicTypes: someTupleWithDynamicTypes, + someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes + }; + + const calldata = method.encode(args); + console.log(calldata); + + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + console.log(method.getSignature()); + + const expectedCalldata = '0x4b49031c000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000440000000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000009800000000000000000000000000000000000000000000000000000000000000ae0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003d69d500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894723843743289742389472398473289472348927489274894738427428947389facdea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000089b51500000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000746dafa5ebde1f4699f4981d3221892e41d24895000000000000000000000000000000000000000000000000000000000000004e6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002829384723894398473289472348927489272384374328974238947274894738427428947389facde100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fa3150000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000089571d322189e415ebde1f4699f498d24246dafa000000000000000000000000000000000000000000000000000000000000004e73646873616a6b646873616a6b646861646a6b617368646a616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b6b73616468616a6b646873616a6b64687361646a6b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002838947238437432829384729742389472398473289472348927489274894738427428947389facdef000000000000000000000000000000000000000000000000'; + //const expectedCalldata = '0x30e1f844000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000008600000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000163874563783498732482743928742389723894723984700000000000000000000000000000000000000000000000000000000000000000000000000000000006e72834723982374239847239847298472489274987489742847289472394874987498478743294237434923473298472398423748923748923748923472389472894789474893742894728947389427498237432987423894723894732894723894372498237498237428934723980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027283473298473248923749238742398742398472894729843278942374982374892374892743982000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000b736f6d6520737472696e670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013736f6d6520616e6f7468657220737472696e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024746865726520617265206a75737420746f6f206d616e7920737472696e6773757020696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002079616c6c2067686f6e6e61206d616b65206d65206c6f7365206d79206d696e640000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000034746865206c6974746c6520706970696e67207069706572207069706564206120706970696e6720706970706572207061707065720000000000000000000000000000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0ac511500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000081746865206b6964206b6e6f777320686f7720746f20777269746520706f656d732c20776861742063616e204920736179202d2d2049206775657373207468657265732061206c6f74204920636f756c642073617920746f2074727920746f2066696c6c2074686973206c696e6520776974682061206c6f74206f6620746578742e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf5763d5ec63d500600000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000100000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498000000000000000000000000000000000000000000000000000000000000004e616b64686a61736a6b646861736a6b6c647368646a6168646b6a73616864616a6b73646873616a6b646873616a6b646861646a6b617368646a6b73616468616a6b646873616a6b64687361646a6b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f7484848484848484848484848484848484848384757687980943399445858584893209100000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata, { structsAsObjects: true }); + const decodedValueJson = JSON.stringify(decodedValue); + console.log(`DECODED`, '*'.repeat(200), '\n', decodedValueJson); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Crazy ABI #1', async () => { + const method = new AbiEncoder.Method(AbiSamples.crazyAbi1); + + const args = [ + new BigNumber(256745454), + new BigNumber(-256745454), + new BigNumber(434244), + '0x43', + '0x0001020304050607080911121314151617181920212223242526272829303132', + '0x0001020304050607080911121314151617181920212223242526272829303132080911121314151617181920212223242526272829303132', + 'Little peter piper piped a piping pepper pot', + '0xe41d2489571d322189246dafa5ebde1f4699f498', + true + ]; + + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x312d4d42000000000000000000000000000000000000000000000000000000000f4d9feefffffffffffffffffffffffffffffffffffffffffffffffffffffffff0b26012000000000000000000000000000000000000000000000000000000000006a0444300000000000000000000000000000000000000000000000000000000000000000102030405060708091112131415161718192021222324252627282930313200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f4980000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003800010203040506070809111213141516171819202122232425262728293031320809111213141516171819202122232425262728293031320000000000000000000000000000000000000000000000000000000000000000000000000000002c4c6974746c65207065746572207069706572207069706564206120706970696e672070657070657220706f740000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + + it('Types with default widths', async () => { + const method = new AbiEncoder.Method(AbiSamples.typesWithDefaultWidthsAbi); + console.log(method); + const args = [new BigNumber(1), new BigNumber(-1), '0x56', [new BigNumber(1)], [new BigNumber(-1)], ['0x56']]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x09f2b0c30000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000015600000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Array of Static Tuples (Array has defined length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDefinedLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x9eb20969000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Array of Static Tuples (Array has dynamic length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfStaticTuplesWithDynamicLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), new BigNumber(++value)]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x63275d6e00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Array of Dynamic Tuples (Array has defined length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithDefinedLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), (new BigNumber(++value)).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0xdeedb00f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Array of Dynamic Tuples (Array has dynamic length)', async () => { + const method = new AbiEncoder.Method(AbiSamples.arrayOfDynamicTuplesWithUndefinedLengthAbi); + + let value = 0; + const arrayOfTuples = []; + for (let i = 0; i < 8; ++i) { + arrayOfTuples.push([new BigNumber(++value), (new BigNumber(++value)).toString()]); + } + const args = [arrayOfTuples]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x60c847fb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000013400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000138000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023132000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000023136000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Multidimensional Arrays / Static Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysStaticTypeAbi); + + // Eight 3-dimensional arrays of uint8[2][2][2] + let value = 0; + const args = []; + for (let i = 0; i < 8; ++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); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0xc2f47d6f00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000480000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000d400000000000000000000000000000000000000000000000000000000000000e600000000000000000000000000000000000000000000000000000000000000039000000000000000000000000000000000000000000000000000000000000003a000000000000000000000000000000000000000000000000000000000000003b000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000003d000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000130000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001500000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000019000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001d000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000001f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000210000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000230000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000027000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000029000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002b000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002d000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000002f0000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000003100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000003400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000035000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000038'; expect(calldata).to.be.equal(expectedCalldata); + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Multidimensional Arrays / Dynamic Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.multiDimensionalArraysDynamicTypeAbi); + + // Eight 3-dimensional arrays of string[2][2][2] + let value = 0; + const args = []; + for (let i = 0; i < 4; ++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); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(method.getSignature()); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x81534ebd0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000009a00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000137000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023131000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000231320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002313300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023134000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000231370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002313800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023139000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000023231000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000232320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000232370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002323800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002323900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000002333100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023332000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Fixed Lenfgth Array / Dynamic Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [["Brave", "New", "World"]]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Fixed Lenfgth Array / Dynamic Members', async () => { + const method = new AbiEncoder.Method(AbiSamples.staticArrayDynamicMembersAbi); + const args = [["Brave", "New", "World"]]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + const expectedCalldata = + '0x243a6e6e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Unfixed Length Array / Dynamic Members ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayDynamicMembersAbi); + const args = [["Brave", "New", "World"]]; + const calldata = method.encode(args); + console.log(calldata); + console.log('*'.repeat(40)); + console.log(JSON.stringify(args)); + const expectedCalldata = '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000005427261766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034e657700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Unfixed Length Array / Static Members ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.dynamicArrayStaticMembersAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + const expectedCalldata = '0x4fc8a83300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + + it('Fixed Length Array / Static Members ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.staticArrayAbi); + const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + const calldata = method.encode(args); + const expectedCalldata = + '0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + + it('Simple ABI 2', async () => { + const method = new AbiEncoder.Method(AbiSamples.simpleAbi2); + + const args = [ + '0xaf', // e (bytes1) + '0x0001020304050607080911121314151617181920212223242526272829303132', // f (bytes32) + '0x616161616161616161616161616161616161616161616161616161616161616161616161616161611114f324567838475647382938475677448899338457668899002020202020', // g + 'My first name is Greg and my last name is Hysen, what do ya know!', // h + ]; + + const calldata = method.encode(args); + const expectedCalldata = + '0x7ac2bd96af000000000000000000000000000000000000000000000000000000000000000001020304050607080911121314151617181920212223242526272829303132000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Array ABI', async () => { + const method = new AbiEncoder.Method(AbiSamples.stringAbi); + console.log(method); + const args = [['five', 'six', 'seven']]; + const calldata = method.encode(args); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Static Tuple', async () => { + // 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); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Dynamic Tuple (Array input)', async () => { + // 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); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + + // Test decoding + const expectedDecodedValueJson = JSON.stringify(args); + const decodedValue = method.decode(calldata); + const decodedValueJson = JSON.stringify(decodedValue); + expect(decodedValueJson).to.be.equal(expectedDecodedValueJson); + }); + + it('Dynamic Tuple (Object input)', async () => { + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.skip('Nested Tuples', async () => { + // Couldn't get nested tuples to work with Remix + // This is dynamic because it has dynamic members + const method = new AbiEncoder.Method(AbiSamples.nestedTuples); + const firstTuple = { + someUint32: new BigNumber(30472), + nestedTuple: { + someUint: new BigNumber('48384725243211555532'), + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498' + } + }; + const secondTuple = { + someUint: new BigNumber(2984237422), + someStr: 'This string will exceed 256 bits, so it will spill into the next word of memory.', + nestedTuple: { + someUint32: new BigNumber(23), + secondNestedTuple: { + someUint: new BigNumber(234324), + someStr: 'Im also a short string -- oops I just got loooooooooooooooooonger!', + someBytes: '0x23847287fff3472984723498ff23487324987aaa237438911873429472ba', + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498' + } + }, + someBytes: '0x2834y3947289423u489aaaff4783924739847489', + someAddress: '0xe41d2489571d322189246dafa5ebde1f4699afaf', + }; + const thirdTuple = { + 'someUint': new BigNumber(37422), + 'someStr': 'This into the next word of memory. string will exceed 256 bits, so it will spill.', + 'nestedTuple': { + someUint32: new BigNumber(23999222), + 'secondNestedTuple': { + 'someUint': new BigNumber(324), + 'someStr': 'Im also a short st', + 'someBytes': '0x723498ff2348732498723847287fff3472984aaa237438911873429472ba', + 'someAddress': '0x46dafa5ebde1f4699f498e41d2489571d3221892' + } + }, + 'someBytes': '0x947289423u489aaaff472834y383924739847489', + 'someAddress': '0x46dafa5ebde1f46e41d2489571d322189299afaf', + }; + const fourthTuple = { + 'someUint': new BigNumber(222283488822), + 'someStr': 'exceed 256 bits, so it will spill into the. This string will next word of memory.', + 'nestedTuple': { + someUint32: new BigNumber(2300), + 'secondNestedTuple': { + 'someUint': new BigNumber(343224), + 'someStr': 'The alphabet backwards is arguably easier to say if thats the way you learned the first time.', + 'someBytes': '0x87324987aaa23743891187323847287fff3472984723498ff234429472ba', + 'someAddress': '0x71d322189246dafa5ebe41d24895de1f4699f498' + } + }, + 'someBytes': '0x2783924739847488343947289423u489aaaff490', + 'someAddress': '0xebde1d322189246dafa1f4699afafe41d2489575', + }; + const args = [ + [firstTuple], + [secondTuple, thirdTuple, fourthTuple] + ]; + + console.log('*'.repeat(250), method, '*'.repeat(250)); + + + const calldata = method.encode(args); + console.log(method.getSignature()); + console.log(method.selector); + console.log(JSON.stringify(args)); + + console.log(calldata); + const expectedCalldata = '0x'; + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.skip('Object ABI (Object input - Missing Key)', async () => { + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const calldata = method.encode([{ someUint: new BigNumber(5) }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + // @TODO: Figure out how to catch throw + expect(calldata).to.be.equal(expectedCalldata); + }); + + it.skip('Object ABI (Object input - Too Many Keys)', async () => { + const method = new AbiEncoder.Method(AbiSamples.dynamicTupleAbi); + const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]); + console.log(method.getSignature()); + console.log(method.selector); + + console.log(calldata); + const expectedCalldata = + '0x5b998f3500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + // @TODO: Figure out how to catch throw + expect(calldata).to.be.equal(expectedCalldata); + }); + }); + /* + describe('Array', () => { + it('sample', async () => { + const testDataItem = { name: 'testArray', type: 'int[2]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + }); + + it('sample undefined size', async () => { + const testDataItem = { name: 'testArray', type: 'int[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue([new BigNumber(5), new BigNumber(6)]); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + }); + + it('sample dynamic types', async () => { + const testDataItem = { name: 'testArray', type: 'string[]' }; + const dataType = new AbiEncoder.SolArray(testDataItem); + console.log(JSON.stringify(dataType, null, 4)); + console.log('*'.repeat(60)); + dataType.assignValue(['five', 'six', 'seven']); + console.log(JSON.stringify(dataType, null, 4)); + const hexValue = dataType.getHexValue(); + console.log('*'.repeat(60)); + console.log(hexValue); + const calldata = new AbiEncoder.Calldata('0x01020304', 1); + dataType.bind(calldata, AbiEncoder.CalldataSection.PARAMS); + console.log('*'.repeat(60)); + console.log(calldata.getHexValue()); + }); + }); + + describe('Address', () => { + const testAddressDataItem = { name: 'testAddress', type: 'address' }; + it('Valid Address', async () => { + const addressDataType = new AbiEncoder.Address(testAddressDataItem); + addressDataType.assignValue('0xe41d2489571d322189246dafa5ebde1f4699f498'); + const expectedAbiEncodedAddress = '0x000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498'; + + console.log(addressDataType.getHexValue()); + console.log(expectedAbiEncodedAddress); + expect(addressDataType.getHexValue()).to.be.equal(expectedAbiEncodedAddress); + }); + }); + + describe('Bool', () => { + const testBoolDataItem = { name: 'testBool', type: 'bool' }; + it('True', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(true); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + + it('False', async () => { + const boolDataType = new AbiEncoder.Bool(testBoolDataItem); + boolDataType.assignValue(false); + const expectedAbiEncodedBool = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(boolDataType.getHexValue()).to.be.equal(expectedAbiEncodedBool); + }); + }); + + describe('Integer', () => { + const testIntDataItem = { name: 'testInt', type: 'int' }; + it('Positive - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Positive', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative - Base case', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-1)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + it('Negative', async () => { + const intDataType = new AbiEncoder.Int(testIntDataItem); + intDataType.assignValue(new BigNumber(-437829473)); + const expectedAbiEncodedInt = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffe5e7409f'; + expect(intDataType.getHexValue()).to.be.equal(expectedAbiEncodedInt); + }); + + // TODO: Add bounds tests + tests for different widths + }); + + describe('Unsigned Integer', () => { + const testIntDataItem = { name: 'testUInt', type: 'uint' }; + it('Lower Bound', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(0)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000000'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Base Case', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(1)); + const expectedAbiEncodedUInt = '0x0000000000000000000000000000000000000000000000000000000000000001'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + it('Random value', async () => { + const uintDataType = new AbiEncoder.UInt(testIntDataItem); + uintDataType.assignValue(new BigNumber(437829473)); + const expectedAbiEncodedUInt = '0x000000000000000000000000000000000000000000000000000000001a18bf61'; + expect(uintDataType.getHexValue()).to.be.equal(expectedAbiEncodedUInt); + }); + + // TODO: Add bounds tests + tests for different widths + }); + + describe('Static Bytes', () => { + it('Byte (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Byte (no padding)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'byte' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect(byteDataType.assignValue('0x5')).to.throw(); + }); + + it('Bytes1', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes1' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x05'); + const expectedAbiEncodedByte = '0x0500000000000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (padded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x0001020304050607080911121314151617181920212223242526272829303132'); + const expectedAbiEncodedByte = '0x0001020304050607080911121314151617181920212223242526272829303132'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it('Bytes32 (unpadded)', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + byteDataType.assignValue('0x1a18bf61'); + const expectedAbiEncodedByte = '0x1a18bf6100000000000000000000000000000000000000000000000000000000'; + expect(byteDataType.getHexValue()).to.be.equal(expectedAbiEncodedByte); + }); + + it.skip('Bytes32 - Too long', async () => { + const testByteDataItem = { name: 'testStaticBytes', type: 'bytes32' }; + const byteDataType = new AbiEncoder.Byte(testByteDataItem); + + // @TODO: This does not catch the Error + expect( + byteDataType.assignValue('0x000102030405060708091112131415161718192021222324252627282930313233'), + ).to.throw( + `Tried to assign 0x000102030405060708091112131415161718192021222324252627282930313233 (33 bytes), which exceeds max bytes that can be stored in a bytes32`, + ); + }); + }); + + describe('Bytes (Dynamic)', () => { + const testBytesDataItem = { name: 'testBytes', type: 'bytes' }; + it('Less than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + bytesDataType.assignValue('0x010203'); + const expectedAbiEncodedBytes = + '0x00000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000'; + + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + it('Greater than 32 bytes', async () => { + const bytesDataType = new AbiEncoder.Bytes(testBytesDataItem); + const testValue = '0x' + '61'.repeat(40); + bytesDataType.assignValue(testValue); + const expectedAbiEncodedBytes = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(bytesDataType.getHexValue()).to.be.equal(expectedAbiEncodedBytes); + }); + + // @TODO: Add test for throw on half-byte + // @TODO: Test with no 0x prefix + // @TODO: Test with Buffer as input + }); + + describe('String', () => { + const testStringDataItem = { name: 'testString', type: 'string' }; + it('Less than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + stringDataType.assignValue('five'); + const expectedAbiEncodedString = + '0x00000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000'; + + console.log(stringDataType.getHexValue()); + console.log(expectedAbiEncodedString); + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + + it('Greater than 32 bytes', async () => { + const stringDataType = new AbiEncoder.SolString(testStringDataItem); + const testValue = 'a'.repeat(40); + stringDataType.assignValue(testValue); + const expectedAbiEncodedString = + '0x000000000000000000000000000000000000000000000000000000000000002861616161616161616161616161616161616161616161616161616161616161616161616161616161000000000000000000000000000000000000000000000000'; + expect(stringDataType.getHexValue()).to.be.equal(expectedAbiEncodedString); + }); + });*/ +}); diff --git a/packages/utils/test/abi_samples.ts b/packages/utils/test/abi_samples.ts new file mode 100644 index 000000000..5e8268f1a --- /dev/null +++ b/packages/utils/test/abi_samples.ts @@ -0,0 +1,814 @@ +import { MethodAbi } from 'ethereum-types'; + +export const simpleAbi = { + constant: false, + inputs: [ + { + name: 'greg', + type: 'uint256', + }, + { + name: 'gregStr', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const stringAbi = { + constant: false, + inputs: [ + { + name: 'greg', + type: 'string[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const optimizerAbi2 = { + constant: false, + inputs: [ + { + name: 'stringArray', + type: 'string[]', + }, + { + name: 'string', + type: 'string', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const optimizerAbi3 = { + 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', +} as MethodAbi; + +export const optimizerAbi4 = { + constant: false, + inputs: [ + { + name: 'uint8Array', + type: 'uint8[4]', + }, + { + components: [ + { + name: 'uint', + type: 'uint', + }, + ], + name: 'uintTuple', + type: 'tuple[]', + }, + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const typesWithDefaultWidthsAbi = { + 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', +} as MethodAbi; + +export const multiDimensionalArraysStaticTypeAbi = { + 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', +} as MethodAbi; + +export const multiDimensionalArraysDynamicTypeAbi = { + 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', +} as MethodAbi; + +export const dynamicTupleAbi = { + 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', +} as MethodAbi; + +export const arrayOfStaticTuplesWithDefinedLengthAbi = { + 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', +} as MethodAbi; + +export const arrayOfStaticTuplesWithDynamicLengthAbi = { + 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', +} as MethodAbi; + +export const arrayOfDynamicTuplesWithDefinedLengthAbi = { + 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', +} as MethodAbi; + +export const arrayOfDynamicTuplesWithUndefinedLengthAbi = { + 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', +} as MethodAbi; + +export const arrayOfDynamicTuplesAbi = { + 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', +} as MethodAbi; + +export const multidimensionalArrayOfDynamicTuplesAbi = { + 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', +} as MethodAbi; + +export const staticTupleAbi = { + 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', +} as MethodAbi; + +export const staticArrayAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'uint8[3]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const staticArrayDynamicMembersAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'string[3]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const dynamicArrayDynamicMembersAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'string[]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const dynamicArrayStaticMembersAbi = { + constant: false, + inputs: [ + { + name: 'someStaticArray', + type: 'uint8[]', + } + ], + name: 'simpleFunction', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', +} as MethodAbi; + +export const crazyAbi1 = { + 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', +} as MethodAbi; + +export const crazyAbi = { + 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', +} as MethodAbi; + +export const nestedTuples = { + 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', +} as MethodAbi; + +export const simpleAbi2 = { + 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', +} as MethodAbi; + +export const fillOrderAbi = { + 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', +} as MethodAbi; 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); + }, +}; -- cgit v1.2.3