refactored implementation done but not tested

This commit is contained in:
Greg Hysen
2018-11-09 18:10:58 -08:00
parent 78498b7019
commit 2802aed79c
3 changed files with 167 additions and 88 deletions

View File

@@ -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<T> {
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<CalldataBlock>();
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();
}

View File

@@ -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 {

View File

@@ -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;
}