From 2802aed79cac4e73e64e1f33e328bcd05a4daf8b Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 9 Nov 2018 18:10:58 -0800 Subject: refactored implementation done but not tested --- packages/order-utils/test/abi/calldata.ts | 140 +++++++++++++++++------- packages/order-utils/test/abi/data_type.ts | 98 +++++++++-------- packages/order-utils/test/abi/evm_data_types.ts | 17 ++- 3 files changed, 167 insertions(+), 88 deletions(-) (limited to 'packages/order-utils') diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index dc0fcfb2b..725bdce62 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -9,15 +9,23 @@ export abstract class CalldataBlock { private bodySizeInBytes: number; private relocatable: boolean; - constructor(name: string, signature: string, offsetInBytes: number, headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ headerSizeInBytes: number, bodySizeInBytes: number, relocatable: boolean) { this.name = name; this.signature = signature; - this.offsetInBytes = offsetInBytes; + this.offsetInBytes = 0; this.headerSizeInBytes = headerSizeInBytes; this.bodySizeInBytes = bodySizeInBytes; this.relocatable = relocatable; } + protected setHeaderSize(headerSizeInBytes: number) { + this.headerSizeInBytes = headerSizeInBytes; + } + + protected setBodySize(bodySizeInBytes: number) { + this.bodySizeInBytes = bodySizeInBytes; + } + public getName(): string { return this.name; } @@ -46,22 +54,25 @@ export abstract class CalldataBlock { return this.offsetInBytes; } - public abstract toHexString(): string; + public setOffset(offsetInBytes: number) { + this.offsetInBytes = offsetInBytes; + } + + public abstract toBuffer(): Buffer; } export class PayloadCalldataBlock extends CalldataBlock { private payload: Buffer; - constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, payload: Buffer) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, payload: Buffer) { const headerSizeInBytes = 0; const bodySizeInBytes = payload.byteLength; - super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); this.payload = payload; } - public toHexString(): string { - const payloadHex = ethUtil.bufferToHex(this.payload); - return payloadHex; + public toBuffer(): Buffer { + return this.payload; } } @@ -70,15 +81,15 @@ export class DependentCalldataBlock extends CalldataBlock { private parent: CalldataBlock; private dependency: CalldataBlock; - constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, parent: CalldataBlock, dependency: CalldataBlock) { + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean, parent: CalldataBlock, dependency: CalldataBlock) { const headerSizeInBytes = 0; const bodySizeInBytes = DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES; - super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); + super(name, signature, /*offsetInBytes,*/ headerSizeInBytes, bodySizeInBytes, relocatable); this.parent = parent; this.dependency = dependency; } - public toHexString(): string { + public toBuffer(): Buffer { const dependencyOffset = this.dependency.getOffsetInBytes(); const parentOffset = this.parent.getOffsetInBytes(); const parentHeaderSize = this.parent.getHeaderSizeInBytes(); @@ -86,8 +97,11 @@ export class DependentCalldataBlock extends CalldataBlock { const pointerBuf = new Buffer(`0x${pointer.toString(16)}`); const evmWordWidthInBytes = 32; const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes); - const pointerHex = ethUtil.bufferToHex(pointerBufPadded); - return pointerHex; + return pointerBufPadded; + } + + public getDependency(): CalldataBlock { + return this.dependency; } } @@ -96,51 +110,97 @@ export class MemberCalldataBlock extends CalldataBlock { private header: Buffer | undefined; private members: CalldataBlock[]; - constructor(name: string, signature: string, offsetInBytes: number, relocatable: boolean, members: CalldataBlock[], header?: Buffer) { - const headerSizeInBytes = (header === undefined) ? 0 : header.byteLength; + constructor(name: string, signature: string, /*offsetInBytes: number,*/ relocatable: boolean) { + super(name, signature, /*offsetInBytes,*/ 0, 0, relocatable); + this.members = []; + this.header = undefined; + } + + public setMembers(members: CalldataBlock[]) { let bodySizeInBytes = 0; - _.each(members, (member: Memblock) => { + _.each(members, (member: CalldataBlock) => { bodySizeInBytes += member.getSizeInBytes(); }); - - super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable); this.members = members; + this.setBodySize(bodySizeInBytes); + } + + public setHeader(header: Buffer) { + this.setHeaderSize(header.byteLength); this.header = header; } - public toHexString(): string { - let valueBuffers: Buffer[] = []; - if (this.header !== undefined) valueBuffers.push(this.header); - _.each(this.members, (member: CalldataBlock) => { - const memberHexString = member.toHexString(); - const memberBuf = ethUtil.toBuffer(memberHexString); - valueBuffers.push(memberBuf); - }); - const combinedValueBufs = Buffer.concat(valueBuffers); - const combinedValuesAsHex = ethUtil.bufferToHex(combinedValueBufs); - return combinedValuesAsHex; + public toBuffer(): Buffer { + if (this.header !== undefined) return this.header; + return new Buffer(''); + } + + public getMembers(): CalldataBlock[] { + return this.members; + } +} + +class Queue { + private store: T[] = []; + push(val: T) { + this.store.push(val); + } + pop(): T | undefined { + return this.store.shift(); } } export class Calldata { private selector: string; private sizeInBytes: number; - private blocks: CalldataBlock[]; + private root: CalldataBlock | undefined; constructor() { this.selector = '0x'; this.sizeInBytes = 0; - this.blocks = []; + this.root = undefined; } public toHexString(): string { - let calldataString = `${this.selector}`; - _.each(this.blocks, (block: CalldataBlock) => { - const blockAsHexString = block.toHexString(); - const blockAsHexWithoutPrefix = ethUtil.stripHexPrefix(blockAsHexString); - calldataString += blockAsHexWithoutPrefix; - }); - return calldataString; + let selectorBuffer = ethUtil.toBuffer(this.selector); + if (this.root === undefined) { + throw new Error('expected root'); + } + + const blockQueue = new Queue(); + blockQueue.push(this.root); + + // Assign locations in breadth-first manner + let block: CalldataBlock | undefined; + let offset = 0; + while ((block = blockQueue.pop()) !== undefined) { + block.setOffset(offset); + if (block instanceof DependentCalldataBlock) { + blockQueue.push(block.getDependency()); + } else if (block instanceof MemberCalldataBlock) { + _.each(block.getMembers(), (member: CalldataBlock) => { + blockQueue.push(member); + }); + } + } + + // Fetch values using same technique + const valueBufs: Buffer[] = [selectorBuffer]; + while ((block = blockQueue.pop()) !== undefined) { + valueBufs.push(block.toBuffer()); + + if (block instanceof DependentCalldataBlock) { + blockQueue.push(block.getDependency()); + } else if (block instanceof MemberCalldataBlock) { + _.each(block.getMembers(), (member: CalldataBlock) => { + blockQueue.push(member); + }); + } + } + + const combinedBuffers = Buffer.concat(valueBufs); + const hexValue = ethUtil.bufferToHex(combinedBuffers); + return hexValue; } public getSelectorHex(): string { @@ -155,8 +215,8 @@ export class Calldata { return ""; } - public pushBlock(block: CalldataBlock) { - this.blocks.push(block); + public setRoot(block: CalldataBlock) { + this.root = block; this.sizeInBytes += block.getSizeInBytes(); } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index f74621085..1a4610f7c 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -1,4 +1,4 @@ -import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock } from "./calldata"; +import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata"; import { MethodAbi, DataItem } from 'ethereum-types'; import { BigNumber } from '@0x/utils'; import ethUtil = require('ethereumjs-util'); @@ -17,7 +17,7 @@ export abstract class DataType { return this.dataItem; } - protected abstract createCalldataBlock(): CalldataBlock; + public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock; public abstract encode(value: any, calldata: Calldata): void; public abstract getSignature(): string; public abstract isStatic(): boolean; @@ -31,19 +31,27 @@ export abstract class PayloadDataType extends DataType { this.hasConstantSize = hasConstantSize; } - protected generateCalldataBlock(payload: Buffer, calldata: Calldata): void { + public generateCalldataBlocks(value: any, parentBlock?: CalldataBlock): PayloadCalldataBlock { + const encodedValue = this.encodeValue(value); const name = this.getDataItem().name; const signature = this.getSignature(); - const offsetInBytes = calldata.getSizeInBytes(); + // const offsetInBytes = calldata.getSizeInBytes(); const relocatable = false; - const block = new PayloadCalldataBlock(name, signature, offsetInBytes, relocatable, payload); - calldata.pushBlock(block); + const block = new PayloadCalldataBlock(name, signature, /*offsetInBytes,*/ relocatable, encodedValue); + return block; + } + + public encode(value: any, calldata: Calldata): void { + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); } public isStatic(): boolean { // If a payload has a constant size then it's static return this.hasConstantSize; } + + public abstract encodeValue(value: any): Buffer; } export abstract class DependentDataType extends DataType { @@ -56,20 +64,21 @@ export abstract class DependentDataType extends DataType { this.parent = parent; } - protected generateCalldataBlock(offsetInBytes: number, calldata: Calldata): CalldataBlock { + public generateCalldataBlock(value: any, parentBlock?: CalldataBlock): DependentCalldataBlock { + if (parentBlock === undefined) { + throw new Error(`DependentDataType requires a parent block to generate its block`); + } + const dependencyBlock = this.dependency.generateCalldataBlock(value); const name = this.getDataItem().name; const signature = this.getSignature(); const relocatable = false; - const parentBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const dependencyBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const block = new DependentCalldataBlock(name, signature, offsetInBytes, relocatable, dependencyBlock, parentBlock); - calldata.pushBlock(block); + const block = new DependentCalldataBlock(name, signature, /*offsetInBytes,*/ relocatable, dependencyBlock, parentBlock); + return block; } public encode(value: any, calldata: Calldata = new Calldata()): void { - const offsetInBytes = calldata.reserveSpace(DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES); - this.dependency.encode(value, calldata); - this.generateCalldataBlock(offsetInBytes, calldata); + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); } public isStatic(): boolean { @@ -96,7 +105,7 @@ export abstract class MemberDataType extends DataType { this.isArray = isArray; this.arrayLength = arrayLength; if (isArray && arrayLength !== undefined) { - [this.members, this.memberMap] = MemberDataType.createMembersWithLength(arrayLength); + [this.members, this.memberMap] = MemberDataType.createMembersWithLength(dataItem, arrayLength); } else if (!isArray) { [this.members, this.memberMap] = MemberDataType.createMembersWithKeys(dataItem); } @@ -129,7 +138,7 @@ export abstract class MemberDataType extends DataType { const range = _.range(length); _.each(range, (idx: number) => { const childDataItem = { - type: this.type, + type: dataItem.type, name: `${dataItem.name}[${idx.toString(10)}]`, } as DataItem; const components = dataItem.components; @@ -144,57 +153,60 @@ export abstract class MemberDataType extends DataType { return [members, memberMap]; } - protected encodeFromArray(value: any[], calldata: Calldata) { + protected generateCalldataBlockFromArray(value: any[]): MemberCalldataBlock { // Sanity check length - const valueLength = new BigNumber(value.length); - if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) { + if (this.arrayLength !== undefined && value.length !== this.arrayLength) { throw new Error( `Expected array of ${JSON.stringify( - this.length, - )} elements, but got array of length ${JSON.stringify(valueLength)}`, + this.arrayLength, + )} elements, but got array of length ${JSON.stringify(value.length)}`, ); } - // Assign values to children - for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) { - const idxNumber = idx.toNumber(); - this.members[idxNumber].assignValue(value[idxNumber]); + let members = this.members; + if (this.arrayLength === undefined) { + [members,] = MemberDataType.createMembersWithLength(this.getDataItem(), value.length); } + + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const memberBlocks: CalldataBlock[] = []; + _.each(members, (member: DataType) => { + const block = member.generateCalldataBlock(value, methodBlock); + memberBlocks.push(block); + }); + methodBlock.setMembers(memberBlocks); + return methodBlock; } - protected encodeFromObject(obj: object, calldata: Calldata) { + protected generateCalldataBlockFromObject(obj: object): MemberCalldataBlock { + const methodBlock: MemberCalldataBlock = new MemberCalldataBlock(this.getDataItem().name, this.getSignature(), false); + const memberBlocks: CalldataBlock[] = []; let childMap = _.cloneDeep(this.memberMap); _.forOwn(obj, (value: any, key: string) => { if (key in childMap === false) { throw new Error(`Could not assign tuple to object: unrecognized key '${key}'`); } - this.members[this.childMap[key]].assignValue(value); + const block = this.members[this.memberMap[key]].generateCalldataBlock(value, methodBlock); + memberBlocks.push(block); delete childMap[key]; }); if (Object.keys(childMap).length !== 0) { throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`); } + + methodBlock.setMembers(memberBlocks); + return methodBlock; } - public encode(value: any[] | object, calldata = new Calldata()) { - if (value instanceof Array) { - this.encodeFromArray(value, calldata); - } else if (typeof value === 'object') { - this.encodeFromObject(value, encodeFromObject); - } else { - throw new Error(`Unexpected type for ${value}`); - } + public generateCalldataBlock(value: any[] | object, parentBlock?: CalldataBlock): MemberCalldataBlock { + const block = (value instanceof Array) ? this.generateCalldataBlockFromArray(value) : this.generateCalldataBlockFromObject(value, calldata); + return block; } - protected generateCalldataBlock(offsetInBytes: number, calldata: Calldata): CalldataBlock { - const name = this.getDataItem().name; - const signature = this.getSignature(); - const relocatable = false; - const parentBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const dependencyBlock = calldata.lookupBlockByName(this.parent.getDataItem().name); - const block = new DependentCalldataBlock(name, signature, offsetInBytes, relocatable, dependencyBlock, parentBlock); - calldata.pushBlock(block); + public encode(value: any, calldata: Calldata = new Calldata()): void { + const block = this.generateCalldataBlock(value); + calldata.setRoot(block); } protected computeSignatureOfMembers(): string { diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index 4e42a992a..bdbaf5b3c 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -4,6 +4,8 @@ import { MethodAbi, DataItem } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); +import { Calldata } from './calldata'; + import { BigNumber } from '@0x/utils'; export interface DataTypeStaticInterface { @@ -29,7 +31,7 @@ export class Address extends PayloadDataType { return type === 'address'; } - public static encodeValue(value: boolean): Buffer { + public encodeValue(value: boolean): Buffer { const evmWordWidth = 32; const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth); return encodedValueBuf; @@ -54,7 +56,7 @@ export class Bool extends PayloadDataType { return type === 'bool'; } - public static encodeValue(value: boolean): Buffer { + public encodeValue(value: boolean): Buffer { const evmWordWidth = 32; const encodedValue = value === true ? '0x1' : '0x0'; const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth); @@ -81,7 +83,7 @@ abstract class Number extends PayloadDataType { } } - public static encodeValue(value: BigNumber): Buffer { + public encodeValue(value: BigNumber): Buffer { if (value.greaterThan(this.getMaxValue())) { throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`; } else if (value.lessThan(this.getMinValue())) { @@ -204,7 +206,7 @@ export class Byte extends PayloadDataType { return `bytes${this.width}`; } - public static encodeValue(value: string | Buffer): Buffer { + public encodeValue(value: string | Buffer): Buffer { // Convert value into a buffer and do bounds checking const valueBuf = ethUtil.toBuffer(value); if (valueBuf.byteLength > this.width) { @@ -275,7 +277,7 @@ export class SolString extends PayloadDataType { } } - public static encodeValue(value: string): Buffer { + public encodeValue(value: string): Buffer { const wordsForValue = Math.ceil(value.length / 32); const paddedBytesForValue = wordsForValue * 32; const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue); @@ -398,6 +400,11 @@ export class Method extends MemberDataType { return selector; } + public encode(value: any[] | object, calldata = new Calldata()) { + calldata.setSelector(this.methodSelector); + super.encode(value, calldata); + } + public getSignature(): string { return this.methodSignature; } -- cgit