From 637ab1076a4071505ea3d4797767826070a65e16 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 8 Nov 2018 10:31:30 -0800 Subject: ABI Encoding for all combinations of arrays --- packages/order-utils/test/abi_encoder.ts | 1098 ++++++++++++++++++++ packages/order-utils/test/abi_encoder_test.ts | 1336 +------------------------ packages/order-utils/test/abi_samples.ts | 358 +++++++ 3 files changed, 1501 insertions(+), 1291 deletions(-) create mode 100644 packages/order-utils/test/abi_encoder.ts create mode 100644 packages/order-utils/test/abi_samples.ts diff --git a/packages/order-utils/test/abi_encoder.ts b/packages/order-utils/test/abi_encoder.ts new file mode 100644 index 000000000..55b63b701 --- /dev/null +++ b/packages/order-utils/test/abi_encoder.ts @@ -0,0 +1,1098 @@ + +import * as chai from 'chai'; +import 'mocha'; +import ethUtil = require('ethereumjs-util'); + +var _ = require('lodash'); + +import { chaiSetup } from './utils/chai_setup'; + +import { MethodAbi, DataItem } from 'ethereum-types'; + +import { BigNumber } from '@0x/utils'; +import { bigNumberify } from 'ethers/utils'; + +chaiSetup.configure(); +const expect = chai.expect; + + +class Word { + private value: string; + + constructor(value?: string) { + if (value === undefined) { + this.value = ''; + } else { + this.value = value; + } + } + + public set(value: string) { + if (value.length !== 64) { + throw `Tried to create word that is not 32 bytes: ${value}`; + } + + this.value = value; + } + + public get(): string { + return this.value; + } + + public getAsHex(): string { + return `0x${this.value}`; + } +} + +export enum CalldataSection { + NONE, + PARAMS, + DATA, +} + +class Memblock { + private dataType: DataType; + private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber }; + + constructor(dataType: DataType) { + this.dataType = dataType; + this.location = { + calldataSection: CalldataSection.NONE, + sectionOffset: new BigNumber(0), + offset: new BigNumber(0), + }; + } + + public getSize(): BigNumber { + return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); + } + + public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) { + this.location.calldataSection = calldataSection; + this.location.sectionOffset = sectionOffset; + this.location.offset = offset; + } + + public get(): string { + return ethUtil.stripHexPrefix(this.dataType.getHexValue()); + } + + public getOffset(): BigNumber { + return this.location.offset; + } + + public getAbsoluteOffset(): BigNumber { + return this.location.sectionOffset.plus(this.location.offset); + } + + public getSection(): CalldataSection { + return this.location.calldataSection; + } +} + +interface BindList { + [key: string]: Memblock; +} + +export class Calldata { + private selector: string; + private params: Memblock[]; + private data: Memblock[]; + private dataOffset: BigNumber; + private currentDataOffset: BigNumber; + private currentParamOffset: BigNumber; + private bindList: BindList; + + constructor(selector: string, nParams: number) { + this.selector = selector; + this.params = []; + this.data = []; + const evmWordSize = 32; + this.dataOffset = new BigNumber(nParams).times(evmWordSize); + this.currentDataOffset = new BigNumber(0); + this.currentParamOffset = new BigNumber(0); + this.bindList = {}; + } + + public bind(dataType: DataType, section: CalldataSection) { + if (dataType.getId() in this.bindList) { + throw `Rebind on ${dataType.getId()}`; + } + const memblock = new Memblock(dataType); + switch (section) { + case CalldataSection.PARAMS: + this.params.push(memblock); + memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); + + console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); + this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); + break; + + case CalldataSection.DATA: + this.data.push(memblock); + memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); + + console.log( + `Binding ${dataType.getDataItem().name} to DATA at ${memblock + .getAbsoluteOffset() + .toString(16)}`, + ); + this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); + break; + + default: + throw `Unrecognized calldata section: ${section}`; + } + + this.bindList[dataType.getId()] = memblock; + dataType.rbind(memblock); + } + + public getHexValue(): string { + let hexValue = `${this.selector}`; + _.each(this.params, (memblock: Memblock) => { + hexValue += memblock.get(); + }); + _.each(this.data, (memblock: Memblock) => { + hexValue += memblock.get(); + }); + + return hexValue; + } +} + +export abstract class DataType { + private dataItem: DataItem; + private hexValue: string; + protected memblock: Memblock | undefined; + protected children: DataType[]; + + constructor(dataItem: DataItem) { + this.dataItem = dataItem; + this.hexValue = '0x'; + this.memblock = undefined; + this.children = []; + } + + protected assignHexValue(hexValue: string) { + this.hexValue = hexValue; + } + + public getHexValue(): string { + return this.hexValue; + } + + public getDataItem(): DataItem { + return this.dataItem; + } + + public rbind(memblock: Memblock) { + this.memblock = memblock; + } + + public bind(calldata: Calldata, section: CalldataSection) { + calldata.bind(this, section); + _.each(this.getChildren(), (child: DataType) => { + child.bind(calldata, CalldataSection.DATA); + }); + } + + public getId(): string { + return this.dataItem.name; + } + + public getOffset(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getOffset(); + } + + public getAbsoluteOffset(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getAbsoluteOffset(); + } + /* + public getSize(): BigNumber { + if (this.memblock === undefined) return new BigNumber(0); + return this.memblock.getSize(); + } + */ + + public getChildren(): DataType[] { + return this.children; + } + + public getSize(): BigNumber { + return this.getHeaderSize().plus(this.getBodySize()); + } + + public abstract assignValue(value: any): void; + public abstract getSignature(): string; + public abstract isStatic(): boolean; + public abstract getHeaderSize(): BigNumber; + public abstract getBodySize(): BigNumber; +} + +export abstract class StaticDataType extends DataType { + constructor(dataItem: DataItem) { + super(dataItem); + } +} + +export abstract class DynamicDataType extends DataType { + constructor(dataItem: DataItem) { + super(dataItem); + } +} + +export class Address extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Address.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + const evmWordWidth = 32; + const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth)); + this.assignHexValue(hexValue); + } + + public getSignature(): string { + return `address`; + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public static matchGrammar(type: string): boolean { + return type === 'address'; + } +} + +export class Bool extends StaticDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(Bool.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: boolean) { + const evmWordWidth = 32; + const encodedValue = value === true ? '0x1' : '0x0'; + const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth)); + this.assignHexValue(hexValue); + } + + public getSignature(): string { + return 'bool'; + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public static matchGrammar(type: string): boolean { + return type === 'bool'; + } +} + +abstract class Number extends StaticDataType { + static MAX_WIDTH: number = 256; + static DEFAULT_WIDTH: number = Number.MAX_WIDTH; + width: number = Number.DEFAULT_WIDTH; + + constructor(dataItem: DataItem, matcher: RegExp) { + super(dataItem); + const matches = matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 2 && matches[1] !== undefined) { + this.width = parseInt(matches[1]); + } else { + this.width = 256; + } + } + + public assignValue(value: BigNumber) { + 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, + ); + } + + const encodedValue = ethUtil.bufferToHex(valueBuf); + this.assignHexValue(encodedValue); + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + 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 StaticDataType { + 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); + const matches = Byte.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + if (matches !== null && matches.length === 3 && matches[2] !== undefined) { + this.width = parseInt(matches[2]); + } else { + this.width = Byte.DEFAULT_WIDTH; + } + } + + public assignValue(value: string | 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); + const hexValue = ethUtil.bufferToHex(paddedValue); + this.assignHexValue(hexValue); + } + + public getSignature(): string { + // Note that `byte` reduces to `bytes1` + return `bytes${this.width}`; + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } +} + +export class Bytes extends DynamicDataType { + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = Bytes.UNDEFINED_LENGTH; + + constructor(dataItem: DataItem) { + super(dataItem); + expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string | 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(ethUtil.toBuffer(value), paddedBytesForValue); + const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); + const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public getSignature(): string { + return 'bytes'; + } + + public isStatic(): boolean { + return false; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(this.getHexValue().length); + } + + public static matchGrammar(type: string): boolean { + return type === 'bytes'; + } +} + +export class SolString extends DynamicDataType { + constructor(dataItem: DataItem) { + super(dataItem); + expect(SolString.matchGrammar(dataItem.type)).to.be.true(); + } + + public assignValue(value: string) { + 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]); + const encodedValue = ethUtil.bufferToHex(encodedValueBuf); + + this.assignHexValue(encodedValue); + } + + public getSignature(): string { + return 'string'; + } + + public isStatic(): boolean { + return false; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(this.getHexValue().length); + } + + public static matchGrammar(type: string): boolean { + return type === 'string'; + } +} + +export class SolArray extends DynamicDataType { + static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); + static UNDEFINED_LENGTH = new BigNumber(-1); + length: BigNumber = SolArray.UNDEFINED_LENGTH; + type: string = '[undefined]'; + isLengthDefined: boolean; + isStaticArray: boolean; // An array is dynamic if it's lenghth is undefined or if its children are dynamic. + private elements: DataType[]; + + /* + --- Layout 1: Fixed Length Array with Static Types --- + Elem1, Elem2, ..., ElemN + + --- Layout 2: Fixed Length Array with Dynamic Types --- + PtrToArray, ..., Elem1, Elem2, ..., ElemN + + --- Layout 3: Dynamic Length Array with Static Types --- + PtrToArray, ..., ArrayLength, Elem1, Elem2, ..., ElemN + + --- Layout 4: Dynamic Length Array with Dynamic Types --- + PtrToArray, ..., ArrayLength, PtrToElem1, PtrToElem2, ..., PtrToElemN, ..., Elem1, Elem2, ..., ElemN + */ + + constructor(dataItem: DataItem) { + super(dataItem); + const matches = SolArray.matcher.exec(dataItem.type); + expect(matches).to.be.not.null(); + console.log(JSON.stringify(matches)); + 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}`); + } + + this.elements = []; + + // Check if length is undefined + if (matches[2] === '') { + this.type = matches[1]; + this.length = SolArray.UNDEFINED_LENGTH; + this.isLengthDefined = false; + this.isStaticArray = false; + return; + } + + // Parse out array type/length and construct children + this.isLengthDefined = true; + this.type = matches[1]; + this.length = new BigNumber(matches[2], 10); + if (this.length.lessThan(1)) { + throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); + } + this.constructChildren(); + + // Check if we're static or not + this.isStaticArray = !(this.elements[0] instanceof Pointer); //this.elements[0].isStatic(); + //throw new Error(`Am I static? ${this.isStaticArray}`); + } + + private constructChildren() { + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const childDataItem = { + type: this.type, + name: `${this.getDataItem().name}[${idx.toString(10)}]`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem, this); + this.elements.push(child); + if (child instanceof Pointer) { + const pointsTo = child.getChildren()[0]; + console.log(JSON.stringify(pointsTo)); + this.children.push(pointsTo); // DataType pointing to + } + } + } + + // @TODO: HACKY -- shouldn't really have children for a + /* + public getChildren(): DataType[] { + if (this.isStatic()) { + return []; + } else { + return this.children; + } + }*/ + + public assignValue(value: any[]) { + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( + valueLength, + )}`, + ); + } + + // Assign length if not already set + if (this.length === SolArray.UNDEFINED_LENGTH) { + this.length = valueLength; + this.constructChildren(); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.elements[idxNumber].assignValue(value[idxNumber]); + } + } + + public getHexValue(): string { + let valueBuf = new Buffer(""); + + if (this.isLengthDefined === false) { + // Must include the array length + const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); + const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); + valueBuf = lengthBuf; + } + + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + const childValueHex = this.elements[idxNumber].getHexValue(); + const childValueBuf = ethUtil.toBuffer(childValueHex); + valueBuf = Buffer.concat([valueBuf, childValueBuf]); + } + + // Convert value buffer to hex + const valueHex = ethUtil.bufferToHex(valueBuf); + return valueHex; + } + + public isStatic(): boolean { + return this.isStaticArray; + } + + public getHeaderSize(): BigNumber { + let size = new BigNumber(0); + if (!this.isLengthDefined) { + size = new BigNumber(32); // stores length of bytes + } + return size; + } + + public getBodySize(): BigNumber { + const evmWordWidth = new BigNumber(32); + const body = this.length.times(evmWordWidth); + return body; + } + + public static matchGrammar(type: string): boolean { + return this.matcher.test(type); + } + + public getSignature(): string { + let type = this.type; + if (this.type === 'tuple') { + let tupleDataItem = { + type: 'tuple', + name: 'N/A', + } as DataItem; + const tupleComponents = this.getDataItem().components; + if (tupleComponents !== undefined) { + tupleDataItem.components = tupleComponents; + } + const tuple = new Tuple(tupleDataItem); + type = tuple.getSignature(); + } + + if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { + return `${type}[]`; + } + return `${type}[${this.length}]`; + } +} + +export class Tuple extends DynamicDataType { + private length: BigNumber; + private childMap: { [key: string]: number }; + + constructor(dataItem: DataItem) { + super(dataItem); + expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); + this.length = new BigNumber(0); + this.childMap = {}; + if (dataItem.components !== undefined) { + this.constructChildren(dataItem.components); + this.length = new BigNumber(dataItem.components.length); + } else { + throw new Error('Components undefined'); + } + } + + private constructChildren(dataItems: DataItem[]) { + _.each(dataItems, (dataItem: DataItem) => { + const childDataItem = { + type: dataItem.type, + name: `${this.getDataItem().name}.${dataItem.name}`, + } as DataItem; + const child = DataTypeFactory.create(childDataItem, this); + this.childMap[dataItem.name] = this.children.length; + this.children.push(child); + }); + } + + private assignValueFromArray(value: any[]) { + // Sanity check length + const valueLength = new BigNumber(value.length); + if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + throw new Error( + `Expected array of ${JSON.stringify( + this.length, + )} elements, but got array of length ${JSON.stringify(valueLength)}`, + ); + } + + // Assign values to children + for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { + const idxNumber = idx.toNumber(); + this.children[idxNumber].assignValue(value[idxNumber]); + } + } + + private assignValueFromObject(obj: object) { + let childMap = _.cloneDeep(this.childMap); + _.forOwn(obj, (value: any, key: string) => { + if (key in childMap === false) { + throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); + } + this.children[this.childMap[key]].assignValue(value); + delete childMap[key]; + }); + + if (Object.keys(childMap).length !== 0) { + throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); + } + } + + public assignValue(value: any[] | object) { + if (value instanceof Array) { + this.assignValueFromArray(value); + } else if (typeof value === 'object') { + this.assignValueFromObject(value); + } else { + throw new Error(`Unexpected type for ${value}`); + } + } + + public getHexValue(): string { + return '0x'; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + const evmWordWidth = new BigNumber(32); + const size = this.length.times(evmWordWidth); + return size; + } + + public getSignature(): string { + // Compute signature + let signature = `(`; + _.each(this.children, (child: DataType, i: number) => { + signature += child.getSignature(); + if (i < this.children.length - 1) { + signature += ','; + } + }); + signature += ')'; + return signature; + } + + public isStatic(): boolean { + return false; // @TODO: True in every case or only when dynamic data? + } + + public static matchGrammar(type: string): boolean { + return type === 'tuple'; + } +} + +/* TODO +class Fixed extends StaticDataType {} + +class UFixed extends StaticDataType {}*/ + +export class Pointer extends StaticDataType { + destDataType: DynamicDataType; + parentDataType: DataType; + + constructor(destDataType: DynamicDataType, parentDataType: DataType) { + const destDataItem = destDataType.getDataItem(); + const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; + super(dataItem); + this.destDataType = destDataType; + this.parentDataType = parentDataType; + this.children.push(destDataType); + } + + /* + public assignValue(destDataType: DynamicDataType) { + this.destDataType = destDataType; + }*/ + + public assignValue(value: any) { + this.destDataType.assignValue(value); + } + + public getHexValue(): string { + console.log( + '*'.repeat(40), + this.destDataType.getAbsoluteOffset().toString(16), + '^'.repeat(150), + this.parentDataType.getAbsoluteOffset().toString(16), + ); + + let offset = this.destDataType + .getAbsoluteOffset() + .minus(this.parentDataType.getAbsoluteOffset()) + .minus(this.parentDataType.getHeaderSize()); + + console.log("OFFSET == ", JSON.stringify(offset), " or in hex -- 0x", offset.toString(16)); + + const hexBase = 16; + const evmWordWidth = 32; + const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); + const encodedValue = ethUtil.bufferToHex(valueBuf); + return encodedValue; + } + + public getSignature(): string { + return this.destDataType.getSignature(); + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + return new BigNumber(32); + } + +} + +export class DataTypeFactory { + public static mapDataItemToDataType(dataItem: DataItem): DataType { + console.log(`Type: ${dataItem.type}`); + + 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 (SolArray.matchGrammar(dataItem.type)) return new SolArray(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 static create(dataItem: DataItem, parentDataType: DataType): DataType { + const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); + if (dataType.isStatic()) { + return dataType; + } else { + const pointer = new Pointer(dataType, parentDataType); + return pointer; + } + + throw new Error(`Unrecognized instance type: '${dataType}'`); + } +} + +class Queue { + private store: T[] = []; + push(val: T) { + this.store.push(val); + } + pop(): T | undefined { + return this.store.shift(); + } +} + +export class Method extends DataType { + name: string; + params: DataType[]; + private signature: string; + selector: string; + + constructor(abi: MethodAbi) { + super({ type: 'method', name: abi.name }); + this.name = abi.name; + this.params = []; + + _.each(abi.inputs, (input: DataItem) => { + this.params.push(DataTypeFactory.create(input, this)); + }); + + // Compute signature + this.signature = `${this.name}(`; + _.each(this.params, (param: DataType, i: number) => { + this.signature += param.getSignature(); + if (i < this.params.length - 1) { + this.signature += ','; + } + }); + this.signature += ')'; + + // Compute selector + this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4))); + + console.log(`--SIGNATURE--\n${this.signature}\n---------\n`); + console.log(`--SELECTOR--\n${this.selector}\n---------\n`); + } + + public getSignature(): string { + return this.signature; + } + + public assignValue(args: any[]) { + _.each(this.params, (param: DataType, i: number) => { + // Assign value to parameter + try { + param.assignValue(args[i]); + } catch (e) { + console.log('Failed to assign to ', param.getDataItem().name); + throw e; + } + + if (param instanceof Pointer) { + this.children.push(param.getChildren()[0]); + } + }); + } + + public getHexValue(): string { + let value = ""; + _.each(this.params, (param: DataType) => { + value += param.getHexValue(); + }); + + return value; + } + + public encode(args: any[]): string { + this.assignValue(args); + const calldata = new Calldata(this.selector, this.params.length); + this.bind(calldata, CalldataSection.PARAMS); + + return calldata.getHexValue(); + } + + public isStatic(): boolean { + return true; + } + + public getHeaderSize(): BigNumber { + // Exclude selector + return new BigNumber(0); + } + + public getBodySize(): BigNumber { + const nParams = new BigNumber(this.params.length); + const evmWordWidth = new BigNumber(32); + const size = nParams.times(evmWordWidth); + return size; + } + + /* + encodeOptimized(args: any[]): string { + const calldata = new Memory(); + // Assign values + optimizableParams : StaticDataType = []; + _.each(this.params, function(args: any[], i: number, param: DataType) { + param.assignValue(args[i]); + if (param instanceof DynamicDataType) { + + } + }); + + // Find non-parameter leaves + + + return ''; + } */ + + /* + decode(rawCalldata: string): any[] { + const calldata = new Calldata(this.name, this.params.length); + calldata.assignRaw(rawCalldata); + let args: any[]; + let params = this.params; + _.each(params, function(args: any[], i: number, param: DataType) { + param.decodeFromCalldata(calldata); + args.push(param.getValue()); + }); + + return args; + }*/ +} \ No newline at end of file diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index b8f1d39ad..315537226 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -12,1294 +12,18 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; import { assert } from '@0x/order-utils/src/assert'; - -const simpleAbi = { - constant: false, - inputs: [ - { - name: 'greg', - type: 'uint256', - }, - { - name: 'gregStr', - type: 'string', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const stringAbi = { - constant: false, - inputs: [ - { - name: 'greg', - type: 'string[]', - }, - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const tupleAbi = { - 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; - -const staticArrayAbi = { - constant: false, - inputs: [ - { - name: 'someStaticArray', - type: 'uint8[3]', - } - ], - name: 'simpleFunction', - outputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'function', -} as MethodAbi; - -const crazyAbi = { - 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: '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; - -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; - -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; +import * as AbiEncoder from './abi_encoder'; +import * as AbiSamples from './abi_samples'; chaiSetup.configure(); const expect = chai.expect; -namespace AbiEncoder { - class Word { - private value: string; - - constructor(value?: string) { - if (value === undefined) { - this.value = ''; - } else { - this.value = value; - } - } - - public set(value: string) { - if (value.length !== 64) { - throw `Tried to create word that is not 32 bytes: ${value}`; - } - - this.value = value; - } - - public get(): string { - return this.value; - } - - public getAsHex(): string { - return `0x${this.value}`; - } - } - - export enum CalldataSection { - NONE, - PARAMS, - DATA, - } - - class Memblock { - private dataType: DataType; - private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber }; - - constructor(dataType: DataType) { - this.dataType = dataType; - this.location = { - calldataSection: CalldataSection.NONE, - sectionOffset: new BigNumber(0), - offset: new BigNumber(0), - }; - } - - public getSize(): BigNumber { - return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength); - } - - public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) { - this.location.calldataSection = calldataSection; - this.location.sectionOffset = sectionOffset; - this.location.offset = offset; - } - - public get(): string { - return ethUtil.stripHexPrefix(this.dataType.getHexValue()); - } - - public getOffset(): BigNumber { - return this.location.offset; - } - - public getAbsoluteOffset(): BigNumber { - return this.location.sectionOffset.plus(this.location.offset); - } - - public getSection(): CalldataSection { - return this.location.calldataSection; - } - } - - interface BindList { - [key: string]: Memblock; - } - - export class Calldata { - private selector: string; - private params: Memblock[]; - private data: Memblock[]; - private dataOffset: BigNumber; - private currentDataOffset: BigNumber; - private currentParamOffset: BigNumber; - private bindList: BindList; - - constructor(selector: string, nParams: number) { - this.selector = selector; - console.log(this.selector); - this.params = []; - this.data = []; - const evmWordSize = 32; - this.dataOffset = new BigNumber(nParams).times(evmWordSize); - this.currentDataOffset = new BigNumber(0); - this.currentParamOffset = new BigNumber(0); - this.bindList = {}; - } - - public bind(dataType: DataType, section: CalldataSection) { - if (dataType.getId() in this.bindList) { - throw `Rebind on ${dataType.getId()}`; - } - const memblock = new Memblock(dataType); - switch (section) { - case CalldataSection.PARAMS: - this.params.push(memblock); - memblock.assignLocation(section, new BigNumber(0), this.currentParamOffset); - - console.log(`Binding ${dataType.getDataItem().name} to PARAMS at ${this.currentParamOffset}`); - this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize()); - break; - - case CalldataSection.DATA: - this.data.push(memblock); - memblock.assignLocation(section, this.dataOffset, this.currentDataOffset); - - console.log( - `Binding ${dataType.getDataItem().name} to DATA at ${memblock - .getAbsoluteOffset() - .toString(16)}`, - ); - this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize()); - break; - - default: - throw `Unrecognized calldata section: ${section}`; - } - - this.bindList[dataType.getId()] = memblock; - dataType.rbind(memblock); - } - - public getHexValue(): string { - let hexValue = `${this.selector}`; - _.each(this.params, (memblock: Memblock) => { - hexValue += memblock.get(); - }); - _.each(this.data, (memblock: Memblock) => { - hexValue += memblock.get(); - }); - - return hexValue; - } - } - - export abstract class DataType { - private dataItem: DataItem; - private hexValue: string; - protected memblock: Memblock | undefined; - protected children: DataType[]; - - constructor(dataItem: DataItem) { - this.dataItem = dataItem; - this.hexValue = '0x'; - this.memblock = undefined; - this.children = []; - } - - protected assignHexValue(hexValue: string) { - this.hexValue = hexValue; - } - - public getHexValue(): string { - return this.hexValue; - } - - public getDataItem(): DataItem { - return this.dataItem; - } - - public rbind(memblock: Memblock) { - this.memblock = memblock; - } - - public bind(calldata: Calldata, section: CalldataSection) { - calldata.bind(this, section); - } - - public getId(): string { - return this.dataItem.name; - } - - public getOffset(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getOffset(); - } - - public getAbsoluteOffset(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getAbsoluteOffset(); - } - - public getSize(): BigNumber { - if (this.memblock === undefined) return new BigNumber(0); - return this.memblock.getSize(); - } - - public getChildren(): DataType[] { - return this.children; - } - - public abstract assignValue(value: any): void; - public abstract getSignature(): string; - public abstract isStatic(): boolean; - } - - export abstract class StaticDataType extends DataType { - constructor(dataItem: DataItem) { - super(dataItem); - } - } - - export abstract class DynamicDataType extends DataType { - constructor(dataItem: DataItem) { - super(dataItem); - } - } - - export class Address extends StaticDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(Address.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - const evmWordWidth = 32; - const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth)); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - return `address`; - } - - public isStatic(): boolean { - return true; - } - - public static matchGrammar(type: string): boolean { - return type === 'address'; - } - } - - export class Bool extends StaticDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bool.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: boolean) { - const evmWordWidth = 32; - const encodedValue = value === true ? '0x1' : '0x0'; - const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth)); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - return 'bool'; - } - - public isStatic(): boolean { - return true; - } - - public static matchGrammar(type: string): boolean { - return type === 'bool'; - } - } - - abstract class Number extends StaticDataType { - static MAX_WIDTH: number = 256; - static DEFAULT_WIDTH: number = Number.MAX_WIDTH; - width: number = Number.DEFAULT_WIDTH; - - constructor(dataItem: DataItem, matcher: RegExp) { - super(dataItem); - const matches = matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 2 && matches[1] !== undefined) { - this.width = parseInt(matches[1]); - } else { - this.width = 256; - } - } - - public assignValue(value: BigNumber) { - 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, - ); - } - - const encodedValue = ethUtil.bufferToHex(valueBuf); - this.assignHexValue(encodedValue); - } - - public isStatic(): boolean { - return true; - } - - 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 StaticDataType { - 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); - const matches = Byte.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - if (matches !== null && matches.length === 3 && matches[2] !== undefined) { - this.width = parseInt(matches[2]); - } else { - this.width = Byte.DEFAULT_WIDTH; - } - } - - public assignValue(value: string | 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); - const hexValue = ethUtil.bufferToHex(paddedValue); - this.assignHexValue(hexValue); - } - - public getSignature(): string { - // Note that `byte` reduces to `bytes1` - return `bytes${this.width}`; - } - - public isStatic(): boolean { - return true; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - } - - export class Bytes extends DynamicDataType { - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = Bytes.UNDEFINED_LENGTH; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Bytes.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string | 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(ethUtil.toBuffer(value), paddedBytesForValue); - const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32); - const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'bytes'; - } - - public isStatic(): boolean { - return false; - } - - public static matchGrammar(type: string): boolean { - return type === 'bytes'; - } - } - - export class SolString extends DynamicDataType { - constructor(dataItem: DataItem) { - super(dataItem); - expect(SolString.matchGrammar(dataItem.type)).to.be.true(); - } - - public assignValue(value: string) { - 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]); - const encodedValue = ethUtil.bufferToHex(encodedValueBuf); - - this.assignHexValue(encodedValue); - } - - public getSignature(): string { - return 'string'; - } - - public isStatic(): boolean { - return false; - } - - public static matchGrammar(type: string): boolean { - return type === 'string'; - } - } - - export class SolArray extends DynamicDataType { - static matcher = RegExp('^(.+)\\[([0-9]*)\\]$'); - static UNDEFINED_LENGTH = new BigNumber(-1); - length: BigNumber = SolArray.UNDEFINED_LENGTH; - type: string = '[undefined]'; - isLengthDefined: boolean; - - constructor(dataItem: DataItem) { - super(dataItem); - const matches = SolArray.matcher.exec(dataItem.type); - expect(matches).to.be.not.null(); - console.log(JSON.stringify(matches)); - 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}`); - } - - // Check if length is undefined - if (matches[2] === '') { - this.type = matches[1]; - this.length = SolArray.UNDEFINED_LENGTH; - this.isLengthDefined = false; - return; - } - - // Parse out array type/length and construct children - this.isLengthDefined = true; - this.type = matches[1]; - this.length = new BigNumber(matches[2], 10); - if (this.length.lessThan(0)) { - throw new Error(`Bad array length: ${JSON.stringify(this.length)}`); - } - this.constructChildren(); - } - - private constructChildren() { - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const childDataItem = { - type: this.type, - name: `${this.getDataItem().name}[${idx.toString(10)}]`, - } as DataItem; - const child = DataTypeFactory.create(childDataItem, this); - this.children.push(child); - } - } - - // @TODO: HACKY -- shouldn't really have children for a - public getChildren(): DataType[] { - if (this.isStatic()) { - return []; - } else { - return this.children; - } - } - - public assignValue(value: any[]) { - // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { - throw new Error( - `Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify( - valueLength, - )}`, - ); - } - - // Assign length if not already set - if (this.length === SolArray.UNDEFINED_LENGTH) { - this.length = valueLength; - this.constructChildren(); - } - - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.children[idxNumber].assignValue(value[idxNumber]); - } - } - - private getHexValueDynamicArray(): string { - const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`); - const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32); - let valueBuf = lengthBuf; - - const valueHex = ethUtil.bufferToHex(valueBuf); - return valueHex; - } - - private getHexValueStaticArray(): string { - let valueBuf = new Buffer(""); - - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - const childValueHex = this.children[idxNumber].getHexValue(); - const childValueBuf = ethUtil.toBuffer(childValueHex); - valueBuf = Buffer.concat([valueBuf, childValueBuf]); - - console.log(JSON.stringify(idx)); - } - - // Convert value buffer to hex - const valueHex = ethUtil.bufferToHex(valueBuf); - return valueHex; - } - - public getHexValue(): string { - if (this.isLengthDefined) { - return this.getHexValueStaticArray(); - } else { - return this.getHexValueDynamicArray(); - } - } - - public isStatic(): boolean { - return this.isLengthDefined; - } - - public static matchGrammar(type: string): boolean { - return this.matcher.test(type); - } - - public getSignature(): string { - let type = this.type; - if (this.type === 'tuple') { - let tupleDataItem = { - type: 'tuple', - name: 'N/A', - } as DataItem; - const tupleComponents = this.getDataItem().components; - if (tupleComponents !== undefined) { - tupleDataItem.components = tupleComponents; - } - const tuple = new Tuple(tupleDataItem); - type = tuple.getSignature(); - } - - if (this.length.equals(SolArray.UNDEFINED_LENGTH)) { - return `${type}[]`; - } - return `${type}[${this.length}]`; - } - } - - export class Tuple extends DynamicDataType { - private length: BigNumber; - private childMap: { [key: string]: number }; - - constructor(dataItem: DataItem) { - super(dataItem); - expect(Tuple.matchGrammar(dataItem.type)).to.be.true(); - this.length = new BigNumber(0); - this.childMap = {}; - if (dataItem.components !== undefined) { - this.constructChildren(dataItem.components); - this.length = new BigNumber(dataItem.components.length); - } else { - throw new Error('Components undefined'); - } - } - - private constructChildren(dataItems: DataItem[]) { - _.each(dataItems, (dataItem: DataItem) => { - const childDataItem = { - type: dataItem.type, - name: `${this.getDataItem().name}.${dataItem.name}`, - } as DataItem; - const child = DataTypeFactory.create(childDataItem, this); - this.childMap[dataItem.name] = this.children.length; - this.children.push(child); - }); - } - - private assignValueFromArray(value: any[]) { - // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { - throw new Error( - `Expected array of ${JSON.stringify( - this.length, - )} elements, but got array of length ${JSON.stringify(valueLength)}`, - ); - } - - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.children[idxNumber].assignValue(value[idxNumber]); - } - } - - private assignValueFromObject(obj: object) { - let childMap = _.cloneDeep(this.childMap); - _.forOwn(obj, (value: any, key: string) => { - if (key in childMap === false) { - throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); - } - this.children[this.childMap[key]].assignValue(value); - delete childMap[key]; - }); - - if (Object.keys(childMap).length !== 0) { - throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); - } - } - - public assignValue(value: any[] | object) { - if (value instanceof Array) { - this.assignValueFromArray(value); - } else if (typeof value === 'object') { - this.assignValueFromObject(value); - } else { - throw new Error(`Unexpected type for ${value}`); - } - } - - public getHexValue(): string { - return '0x'; - } - - public getSignature(): string { - // Compute signature - let signature = `(`; - _.each(this.children, (child: DataType, i: number) => { - signature += child.getSignature(); - if (i < this.children.length - 1) { - signature += ','; - } - }); - signature += ')'; - return signature; - } - - public isStatic(): boolean { - return false; // @TODO: True in every case or only when dynamic data? - } - - public static matchGrammar(type: string): boolean { - return type === 'tuple'; - } - } - - /* TODO - class Fixed extends StaticDataType {} - - class UFixed extends StaticDataType {}*/ - - export class Pointer extends StaticDataType { - destDataType: DynamicDataType; - parentDataType: DataType; - - constructor(destDataType: DynamicDataType, parentDataType: DataType) { - const destDataItem = destDataType.getDataItem(); - const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem; - super(dataItem); - this.destDataType = destDataType; - this.parentDataType = parentDataType; - this.children.push(destDataType); - } - - /* - public assignValue(destDataType: DynamicDataType) { - this.destDataType = destDataType; - }*/ - - public assignValue(value: any) { - this.destDataType.assignValue(value); - } - - public getHexValue(): string { - console.log( - '*'.repeat(40), - this.destDataType.getAbsoluteOffset().toString(16), - '^'.repeat(150), - this.parentDataType.getAbsoluteOffset().toString(16), - ); - - let offset = this.destDataType - .getAbsoluteOffset() - .minus(this.parentDataType.getAbsoluteOffset().plus(this.parentDataType.getSize())); - const hexBase = 16; - const evmWordWidth = 32; - const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth); - const encodedValue = ethUtil.bufferToHex(valueBuf); - return encodedValue; - } - - public getSignature(): string { - return this.destDataType.getSignature(); - } - - public isStatic(): boolean { - return true; - } - - public encodeToCalldata(calldata: Calldata): void { - throw 2; - } - } - - export class DataTypeFactory { - public static mapDataItemToDataType(dataItem: DataItem): DataType { - console.log(`Type: ${dataItem.type}`); - - 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 (SolArray.matchGrammar(dataItem.type)) return new SolArray(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 static create(dataItem: DataItem, parentDataType: DataType): DataType { - const dataType = DataTypeFactory.mapDataItemToDataType(dataItem); - if (dataType.isStatic()) { - return dataType; - } else { - const pointer = new Pointer(dataType, parentDataType); - return pointer; - } - - throw new Error(`Unrecognized instance type: '${dataType}'`); - } - } - - class Queue { - private store: T[] = []; - push(val: T) { - this.store.push(val); - } - pop(): T | undefined { - return this.store.shift(); - } - } - - export class Method extends DataType { - name: string; - params: DataType[]; - private signature: string; - selector: string; - - constructor(abi: MethodAbi) { - super({ type: 'method', name: abi.name }); - this.name = abi.name; - this.params = []; - - _.each(abi.inputs, (input: DataItem) => { - this.params.push(DataTypeFactory.create(input, this)); - }); - - // Compute signature - this.signature = `${this.name}(`; - _.each(this.params, (param: DataType, i: number) => { - this.signature += param.getSignature(); - if (i < this.params.length - 1) { - this.signature += ','; - } - }); - this.signature += ')'; - - // Compute selector - this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4))); - - console.log(`--SIGNATURE--\n${this.signature}\n---------\n`); - console.log(`--SELECTOR--\n${this.selector}\n---------\n`); - } - - public getSignature(): string { - return this.signature; - } - - public assignValue(args: any[]) { - const calldata = new Calldata(this.selector, this.params.length); - const params = this.params; - const paramQueue = new Queue(); - _.each(params, (param: DataType, i: number) => { - try { - param.assignValue(args[i]); - } catch (e) { - console.log('Failed to assign to ', param.getDataItem().name); - throw e; - } - param.bind(calldata, CalldataSection.PARAMS); - _.each(param.getChildren(), (child: DataType) => { - paramQueue.push(child); - }); - }); - - let param: DataType | undefined = undefined; - while ((param = paramQueue.pop()) !== undefined) { - param.bind(calldata, CalldataSection.DATA); - _.each(param.getChildren(), (child: DataType) => { - paramQueue.push(child); - }); - } - - console.log(calldata); - - this.assignHexValue(calldata.getHexValue()); - - //return calldata.getRaw(); - } - - public encode(args: any[]): string { - this.assignValue(args); - return this.getHexValue(); - } - - public isStatic(): boolean { - return true; - } - - /* - encodeOptimized(args: any[]): string { - const calldata = new Memory(); - // Assign values - optimizableParams : StaticDataType = []; - _.each(this.params, function(args: any[], i: number, param: DataType) { - param.assignValue(args[i]); - if (param instanceof DynamicDataType) { - - } - }); - - // Find non-parameter leaves - - - return ''; - } */ - - /* - decode(rawCalldata: string): any[] { - const calldata = new Calldata(this.name, this.params.length); - calldata.assignRaw(rawCalldata); - let args: any[]; - let params = this.params; - _.each(params, function(args: any[], i: number, param: DataType) { - param.decodeFromCalldata(calldata); - args.push(param.getValue()); - }); - - return args; - }*/ - } -} - describe.only('ABI Encoder', () => { describe.only('Just a Greg, Eh', () => { + + it('Crazy ABI', async () => { - const method = new AbiEncoder.Method(crazyAbi); + const method = new AbiEncoder.Method(AbiSamples.crazyAbi); console.log(method.getSignature()); const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)], @@ -1405,20 +129,50 @@ describe.only('ABI Encoder', () => { expect(calldata).to.be.equal(expectedCalldata);*/ }); - it.only('Static Array ABI', async () => { - const method = new AbiEncoder.Method(staticArrayAbi); - const args = [[new BigNumber(127), new BigNumber(14), new BigNumber(54)]]; + it.only('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); + }); + + it.only('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); + }); + + it.only('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); + }); + + + it.only('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); }); + it('Simple ABI 2', async () => { - const method = new AbiEncoder.Method(simpleAbi2); + const method = new AbiEncoder.Method(AbiSamples.simpleAbi2); const args = [ '0xaf', // e (bytes1) @@ -1434,14 +188,14 @@ describe.only('ABI Encoder', () => { }); it('Yessir', async () => { - const method = new AbiEncoder.Method(simpleAbi); + const method = new AbiEncoder.Method(AbiSamples.simpleAbi); const calldata = method.encode([new BigNumber(5), 'five']); console.log(calldata); expect(true).to.be.true(); }); it('Array ABI', async () => { - const method = new AbiEncoder.Method(stringAbi); + const method = new AbiEncoder.Method(AbiSamples.stringAbi); const calldata = method.encode([['five', 'six', 'seven']]); console.log(method.getSignature()); console.log(method.selector); @@ -1453,7 +207,7 @@ describe.only('ABI Encoder', () => { }); it('Object ABI (Array input)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([[new BigNumber(5), 'five']]); console.log(method.getSignature()); console.log(method.selector); @@ -1465,7 +219,7 @@ describe.only('ABI Encoder', () => { }); it('Object ABI (Object input)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five' }]); console.log(method.getSignature()); console.log(method.selector); @@ -1477,7 +231,7 @@ describe.only('ABI Encoder', () => { }); it.skip('Object ABI (Object input - Missing Key)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5) }]); console.log(method.getSignature()); console.log(method.selector); @@ -1491,7 +245,7 @@ describe.only('ABI Encoder', () => { }); it.skip('Object ABI (Object input - Too Many Keys)', async () => { - const method = new AbiEncoder.Method(tupleAbi); + const method = new AbiEncoder.Method(AbiSamples.tupleAbi); const calldata = method.encode([{ someUint: new BigNumber(5), someStr: 'five', unwantedKey: 14 }]); console.log(method.getSignature()); console.log(method.selector); diff --git a/packages/order-utils/test/abi_samples.ts b/packages/order-utils/test/abi_samples.ts new file mode 100644 index 000000000..2e7111504 --- /dev/null +++ b/packages/order-utils/test/abi_samples.ts @@ -0,0 +1,358 @@ +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 tupleAbi = { + 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 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 crazyAbi = { + 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: '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 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; -- cgit