refactored implementation done but not tested
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user