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_test.ts | 1336 +------------------------ 1 file changed, 45 insertions(+), 1291 deletions(-) (limited to 'packages/order-utils/test/abi_encoder_test.ts') 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); -- cgit v1.2.3