Files
protocol/packages/order-utils/test/abi_encoder.ts
2018-11-28 16:38:10 -08:00

1150 lines
35 KiB
TypeScript

import * as chai from 'chai';
import 'mocha';
import ethUtil = require('ethereumjs-util');
var _ = require('lodash');
import { chaiSetup } from './utils/chai_setup';
import { MethodAbi, DataItem } from 'ethereum-types';
import { BigNumber } from '@0x/utils';
import { bigNumberify } from 'ethers/utils';
chaiSetup.configure();
const expect = chai.expect;
class Word {
private value: string;
constructor(value?: string) {
if (value === undefined) {
this.value = '';
} else {
this.value = value;
}
}
public set(value: string) {
if (value.length !== 64) {
throw `Tried to create word that is not 32 bytes: ${value}`;
}
this.value = value;
}
public get(): string {
return this.value;
}
public getAsHex(): string {
return `0x${this.value}`;
}
}
export enum CalldataSection {
NONE,
PARAMS,
DATA,
}
class Memblock {
private dataType: DataType;
private location: { calldataSection: CalldataSection; sectionOffset: BigNumber; offset: BigNumber };
constructor(dataType: DataType) {
this.dataType = dataType;
this.location = {
calldataSection: CalldataSection.NONE,
sectionOffset: new BigNumber(0),
offset: new BigNumber(0),
};
}
public getSize(): BigNumber {
return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength);
}
public assignLocation(calldataSection: CalldataSection, sectionOffset: BigNumber, offset: BigNumber) {
this.location.calldataSection = calldataSection;
this.location.sectionOffset = sectionOffset;
this.location.offset = offset;
}
public get(): string {
console.log(`Unstripped = '${this.dataType.getHexValue()}' and Stripped = '${ethUtil.stripHexPrefix(this.dataType.getHexValue())}'`);
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;
}
public getName(): string {
return this.dataType.getDataItem().name;
}
}
interface BindList {
[key: string]: Memblock;
}
export class Calldata {
private selector: string;
private params: Memblock[];
private data: Memblock[];
private dataOffset: BigNumber;
private currentDataOffset: BigNumber;
private currentParamOffset: BigNumber;
private bindList: BindList;
constructor(selector: string, nParams: number) {
this.selector = selector;
this.params = [];
this.data = [];
const evmWordSize = 32;
this.dataOffset = new BigNumber(nParams).times(evmWordSize);
this.currentDataOffset = new BigNumber(0);
this.currentParamOffset = new BigNumber(0);
this.bindList = {};
}
public bind(dataType: DataType, section: CalldataSection) {
if (dataType.getId() in this.bindList) {
throw `Rebind on ${dataType.getId()}`;
}
const memblock = new Memblock(dataType);
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());
console.log("CURRENT PARAM OFFSET -------- 0x", this.currentParamOffset.toString(16));
/*
switch (section) {
case CalldataSection.PARAMS:
;
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;
}
public printAnnotated() {
let hexValue = `${this.selector}`;
console.log(hexValue);
let offset = new BigNumber(0);
_.each(this.params, (memblock: Memblock) => {
const offsetStr = `0x${offset.toString(16)}`;
const hexValue = memblock.get();
const annotation = memblock.getName();
console.log(`${offsetStr} ${hexValue} ${annotation}`);
});
_.each(this.data, (memblock: Memblock) => {
const offsetStr = `0x${offset.toString(16)}`;
const hexValue = memblock.get();
const annotation = memblock.getName();
console.log(`${offsetStr} ${hexValue} ${annotation}`);
});
}
}
export abstract class DataType {
private dataItem: DataItem;
private hexValue: string;
protected memblock: Memblock | undefined;
protected children: DataType[];
constructor(dataItem: DataItem) {
this.dataItem = dataItem;
this.hexValue = '0x';
this.memblock = undefined;
this.children = [];
}
protected assignHexValue(hexValue: string) {
this.hexValue = hexValue;
}
public getHexValue(): string {
return this.hexValue;
}
public getDataItem(): DataItem {
return this.dataItem;
}
public rbind(memblock: Memblock) {
this.memblock = memblock;
}
public bind(calldata: Calldata, section: CalldataSection) {
calldata.bind(this, section);
_.each(this.getChildren(), (child: DataType) => {
child.bind(calldata, CalldataSection.DATA);
});
}
public getId(): string {
return this.dataItem.name;
}
public getOffset(): BigNumber {
if (this.memblock === undefined) return new BigNumber(0);
return this.memblock.getOffset();
}
public getAbsoluteOffset(): BigNumber {
if (this.memblock === undefined) return new BigNumber(0);
return this.memblock.getAbsoluteOffset();
}
/*
public getSize(): BigNumber {
if (this.memblock === undefined) return new BigNumber(0);
return this.memblock.getSize();
}
*/
public getChildren(): DataType[] {
return this.children;
}
public getSize(): BigNumber {
return this.getHeaderSize().plus(this.getBodySize());
}
public abstract assignValue(value: any): void;
public abstract getSignature(): string;
public abstract isStatic(): boolean;
public abstract getHeaderSize(): BigNumber;
public abstract getBodySize(): BigNumber;
}
export abstract class StaticDataType extends DataType {
constructor(dataItem: DataItem) {
super(dataItem);
}
}
export abstract class DynamicDataType extends DataType {
constructor(dataItem: DataItem) {
super(dataItem);
}
}
export class Address extends StaticDataType {
constructor(dataItem: DataItem) {
super(dataItem);
expect(Address.matchGrammar(dataItem.type)).to.be.true();
}
public assignValue(value: string) {
const evmWordWidth = 32;
const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth));
this.assignHexValue(hexValue);
}
public getSignature(): string {
return `address`;
}
public isStatic(): boolean {
return true;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
return new BigNumber(32);
}
public static matchGrammar(type: string): boolean {
return type === 'address';
}
}
export class Bool extends StaticDataType {
constructor(dataItem: DataItem) {
super(dataItem);
expect(Bool.matchGrammar(dataItem.type)).to.be.true();
}
public assignValue(value: boolean) {
const evmWordWidth = 32;
const encodedValue = value === true ? '0x1' : '0x0';
const hexValue = ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth));
this.assignHexValue(hexValue);
}
public getSignature(): string {
return 'bool';
}
public isStatic(): boolean {
return true;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
return new BigNumber(32);
}
public static matchGrammar(type: string): boolean {
return type === 'bool';
}
}
abstract class Number extends StaticDataType {
static MAX_WIDTH: number = 256;
static DEFAULT_WIDTH: number = Number.MAX_WIDTH;
width: number = Number.DEFAULT_WIDTH;
constructor(dataItem: DataItem, matcher: RegExp) {
super(dataItem);
const matches = matcher.exec(dataItem.type);
expect(matches).to.be.not.null();
if (matches !== null && matches.length === 2 && matches[1] !== undefined) {
this.width = parseInt(matches[1]);
} else {
this.width = 256;
}
}
public assignValue(value: BigNumber) {
if (value.greaterThan(this.getMaxValue())) {
throw `tried to assign value of ${value}, which exceeds max value of ${this.getMaxValue()}`;
} else if (value.lessThan(this.getMinValue())) {
throw `tried to assign value of ${value}, which exceeds min value of ${this.getMinValue()}`;
}
const hexBase = 16;
const evmWordWidth = 32;
let valueBuf: Buffer;
if (value.greaterThanOrEqualTo(0)) {
valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${value.toString(hexBase)}`), evmWordWidth);
} else {
// BigNumber can't write a negative hex value, so we use twos-complement conversion to do it ourselves.
// Step 1/3: Convert value to positive binary string
const binBase = 2;
const valueBin = value.times(-1).toString(binBase);
// Step 2/3: Invert binary value
const bitsInEvmWord = 256;
let invertedValueBin = '1'.repeat(bitsInEvmWord - valueBin.length);
_.each(valueBin, (bit: string) => {
invertedValueBin += bit === '1' ? '0' : '1';
});
const invertedValue = new BigNumber(invertedValueBin, binBase);
// Step 3/3: Add 1 to inverted value
// The result is the two's-complement represent of the input value.
const negativeValue = invertedValue.plus(1);
// Convert the negated value to a hex string
valueBuf = ethUtil.setLengthLeft(
ethUtil.toBuffer(`0x${negativeValue.toString(hexBase)}`),
evmWordWidth,
);
}
const encodedValue = ethUtil.bufferToHex(valueBuf);
this.assignHexValue(encodedValue);
}
public isStatic(): boolean {
return true;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
return new BigNumber(32);
}
public abstract getMaxValue(): BigNumber;
public abstract getMinValue(): BigNumber;
}
export class Int extends Number {
static matcher = RegExp(
'^int(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
);
constructor(dataItem: DataItem) {
super(dataItem, Int.matcher);
}
public getMaxValue(): BigNumber {
return new BigNumber(2).toPower(this.width - 1).sub(1);
}
public getMinValue(): BigNumber {
return new BigNumber(2).toPower(this.width - 1).times(-1);
}
public getSignature(): string {
return `int${this.width}`;
}
public static matchGrammar(type: string): boolean {
return this.matcher.test(type);
}
}
export class UInt extends Number {
static matcher = RegExp(
'^uint(8|16|24|32|40|48|56|64|72|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256){0,1}$',
);
constructor(dataItem: DataItem) {
super(dataItem, UInt.matcher);
}
public getMaxValue(): BigNumber {
return new BigNumber(2).toPower(this.width).sub(1);
}
public getMinValue(): BigNumber {
return new BigNumber(0);
}
public getSignature(): string {
return `uint${this.width}`;
}
public static matchGrammar(type: string): boolean {
return this.matcher.test(type);
}
}
export class Byte extends StaticDataType {
static matcher = RegExp(
'^(byte|bytes(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32))$',
);
static DEFAULT_WIDTH = 1;
width: number = Byte.DEFAULT_WIDTH;
constructor(dataItem: DataItem) {
super(dataItem);
const matches = Byte.matcher.exec(dataItem.type);
expect(matches).to.be.not.null();
if (matches !== null && matches.length === 3 && matches[2] !== undefined) {
this.width = parseInt(matches[2]);
} else {
this.width = Byte.DEFAULT_WIDTH;
}
}
public assignValue(value: string | Buffer) {
// Convert value into a buffer and do bounds checking
const valueBuf = ethUtil.toBuffer(value);
if (valueBuf.byteLength > this.width) {
throw new Error(
`Tried to assign ${value} (${
valueBuf.byteLength
} bytes), which exceeds max bytes that can be stored in a ${this.getSignature()}`,
);
} else if (value.length % 2 !== 0) {
throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
}
// Store value as hex
const evmWordWidth = 32;
const paddedValue = ethUtil.setLengthRight(valueBuf, evmWordWidth);
const hexValue = ethUtil.bufferToHex(paddedValue);
this.assignHexValue(hexValue);
}
public getSignature(): string {
// Note that `byte` reduces to `bytes1`
return `bytes${this.width}`;
}
public isStatic(): boolean {
return true;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
return new BigNumber(32);
}
public static matchGrammar(type: string): boolean {
return this.matcher.test(type);
}
}
export class Bytes extends DynamicDataType {
static UNDEFINED_LENGTH = new BigNumber(-1);
length: BigNumber = Bytes.UNDEFINED_LENGTH;
constructor(dataItem: DataItem) {
super(dataItem);
expect(Bytes.matchGrammar(dataItem.type)).to.be.true();
}
public assignValue(value: string | Buffer) {
if (typeof value === 'string' && !value.startsWith('0x')) {
throw new Error(`Input value must be hex (prefixed with 0x). Actual value is '${value}'`);
}
const valueBuf = ethUtil.toBuffer(value);
if (value.length % 2 !== 0) {
throw new Error(`Tried to assign ${value}, which is contains a half-byte. Use full bytes only.`);
}
const wordsForValue = Math.ceil(valueBuf.byteLength / 32);
const paddedBytesForValue = wordsForValue * 32;
const paddedValueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32);
const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]);
const encodedValue = ethUtil.bufferToHex(encodedValueBuf);
this.assignHexValue(encodedValue);
}
public getSignature(): string {
return 'bytes';
}
public isStatic(): boolean {
return false;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
const valueBuf = ethUtil.toBuffer(this.getHexValue());
const size = new BigNumber(valueBuf.byteLength);
return size;
}
public static matchGrammar(type: string): boolean {
return type === 'bytes';
}
}
export class SolString extends DynamicDataType {
constructor(dataItem: DataItem) {
super(dataItem);
expect(SolString.matchGrammar(dataItem.type)).to.be.true();
}
public assignValue(value: string) {
const wordsForValue = Math.ceil(value.length / 32);
const paddedBytesForValue = wordsForValue * 32;
const valueBuf = ethUtil.setLengthRight(ethUtil.toBuffer(value), paddedBytesForValue);
const lengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value.length), 32);
const encodedValueBuf = Buffer.concat([lengthBuf, valueBuf]);
const encodedValue = ethUtil.bufferToHex(encodedValueBuf);
this.assignHexValue(encodedValue);
}
public getSignature(): string {
return 'string';
}
public isStatic(): boolean {
return false;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
const valueBuf = ethUtil.toBuffer(this.getHexValue());
const size = new BigNumber(valueBuf.byteLength);
return size;
}
public static matchGrammar(type: string): boolean {
return type === 'string';
}
}
export class SolArray extends DynamicDataType {
static matcher = RegExp('^(.+)\\[([0-9]*)\\]$');
static UNDEFINED_LENGTH = new BigNumber(-1);
length: BigNumber = SolArray.UNDEFINED_LENGTH;
type: string = '[undefined]';
isLengthDefined: boolean;
isStaticArray: boolean; // An array is dynamic if it's lenghth is undefined or if its children are dynamic.
private elements: DataType[];
/*
--- Layout 1: Fixed Length Array with Static Types ---
Elem1, Elem2, ..., ElemN
--- Layout 2: Fixed Length Array with Dynamic Types ---
PtrToArray, ..., Elem1, Elem2, ..., ElemN
--- Layout 3: Dynamic Length Array with Static Types ---
PtrToArray, ..., ArrayLength, Elem1, Elem2, ..., ElemN
--- Layout 4: Dynamic Length Array with Dynamic Types ---
PtrToArray, ..., ArrayLength, PtrToElem1, PtrToElem2, ..., PtrToElemN, ..., Elem1, Elem2, ..., ElemN
*/
constructor(dataItem: DataItem) {
super(dataItem);
const matches = SolArray.matcher.exec(dataItem.type);
expect(matches).to.be.not.null();
console.log(JSON.stringify(matches));
if (matches === null || matches.length !== 3) {
throw new Error(`Could not parse array: ${dataItem.type}`);
} else if (matches[1] === undefined) {
throw new Error(`Could not parse array type: ${dataItem.type}`);
} else if (matches[2] === undefined) {
throw new Error(`Could not parse array length: ${dataItem.type}`);
}
this.elements = [];
// Check if length is undefined
if (matches[2] === '') {
this.type = matches[1];
this.length = SolArray.UNDEFINED_LENGTH;
this.isLengthDefined = false;
this.isStaticArray = false;
return;
}
// Parse out array type/length and construct children
this.isLengthDefined = true;
this.type = matches[1];
this.length = new BigNumber(matches[2], 10);
if (this.length.lessThan(1)) {
throw new Error(`Bad array length: ${JSON.stringify(this.length)}`);
}
this.constructChildren();
// Check if we're static or not
this.isStaticArray = !(this.elements[0] instanceof Pointer); //this.elements[0].isStatic();
//throw new Error(`Am I static? ${this.isStaticArray}`);
}
private constructChildren() {
for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
const childDataItem = {
type: this.type,
name: `${this.getDataItem().name}[${idx.toString(10)}]`,
} as DataItem;
const child = DataTypeFactory.create(childDataItem, this);
this.elements.push(child);
if (child instanceof Pointer) {
const pointsTo = child.getChildren()[0];
console.log(JSON.stringify(pointsTo));
this.children.push(pointsTo); // DataType pointing to
}
}
}
// @TODO: HACKY -- shouldn't really have children for a
/*
public getChildren(): DataType[] {
if (this.isStatic()) {
return [];
} else {
return this.children;
}
}*/
public assignValue(value: any[]) {
// Sanity check length
const valueLength = new BigNumber(value.length);
if (this.length !== SolArray.UNDEFINED_LENGTH && valueLength.equals(this.length) === false) {
throw new Error(
`Expected array of length ${JSON.stringify(this.length)}, but got array of length ${JSON.stringify(
valueLength,
)}`,
);
}
// Assign length if not already set
if (this.length === SolArray.UNDEFINED_LENGTH) {
this.length = valueLength;
this.constructChildren();
}
// Assign values to children
for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
const idxNumber = idx.toNumber();
this.elements[idxNumber].assignValue(value[idxNumber]);
}
}
public getHexValue(): string {
let valueBuf = new Buffer("");
if (this.isLengthDefined === false) {
// Must include the array length
const lengthBufUnpadded = ethUtil.toBuffer(`0x${this.length.toString(16)}`);
const lengthBuf = ethUtil.setLengthLeft(lengthBufUnpadded, 32);
valueBuf = lengthBuf;
}
for (let idx = new BigNumber(0); idx.lessThan(this.length); idx = idx.plus(1)) {
const idxNumber = idx.toNumber();
const childValueHex = this.elements[idxNumber].getHexValue();
const childValueBuf = ethUtil.toBuffer(childValueHex);
valueBuf = Buffer.concat([valueBuf, childValueBuf]);
}
// Convert value buffer to hex
const valueHex = ethUtil.bufferToHex(valueBuf);
return valueHex;
}
public isStatic(): boolean {
return this.isStaticArray;
}
public getHeaderSize(): BigNumber {
let size = new BigNumber(0);
if (!this.isLengthDefined) {
size = new BigNumber(32); // stores length of bytes
}
return size;
}
public getBodySize(): BigNumber {
const evmWordWidth = new BigNumber(32);
const body = this.length.times(evmWordWidth);
return body;
}
public static matchGrammar(type: string): boolean {
return this.matcher.test(type);
}
public getSignature(): string {
let type = this.type;
if (this.type === 'tuple') {
let tupleDataItem = {
type: 'tuple',
name: 'N/A',
} as DataItem;
const tupleComponents = this.getDataItem().components;
if (tupleComponents !== undefined) {
tupleDataItem.components = tupleComponents;
}
const tuple = new Tuple(tupleDataItem);
type = tuple.getSignature();
}
if (this.length.equals(SolArray.UNDEFINED_LENGTH)) {
return `${type}[]`;
}
return `${type}[${this.length}]`;
}
}
export class Tuple extends DynamicDataType {
private length: BigNumber;
private childMap: { [key: string]: number };
private members: DataType[];
constructor(dataItem: DataItem) {
super(dataItem);
expect(Tuple.matchGrammar(dataItem.type)).to.be.true();
this.length = new BigNumber(0);
this.childMap = {};
this.members = [];
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.members.length;
if (child instanceof Pointer) {
this.children.push(child.getChildren()[0]);
}
this.members.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.members[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.members[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 {
let paramBufs: Buffer[] = [];
_.each(this.members, (member: DataType) => {
paramBufs.push(ethUtil.toBuffer(member.getHexValue()));
});
const value = Buffer.concat(paramBufs);
const hexValue = ethUtil.bufferToHex(value);
return hexValue;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
const evmWordWidth = new BigNumber(32);
const size = this.length.times(evmWordWidth);
return size;
}
public getSignature(): string {
// Compute signature
let signature = `(`;
_.each(this.members, (member: DataType, i: number) => {
signature += member.getSignature();
if (i < this.members.length - 1) {
signature += ',';
}
});
signature += ')';
return signature;
}
public isStatic(): boolean {
const isStaticTuple = this.children.length === 0;
return isStaticTuple; // @TODO: True in every case or only when dynamic data?
}
public static matchGrammar(type: string): boolean {
return type === 'tuple';
}
}
/* TODO
class Fixed extends StaticDataType {}
class UFixed extends StaticDataType {}*/
export class Pointer extends StaticDataType {
destDataType: DynamicDataType;
parentDataType: DataType;
constructor(destDataType: DynamicDataType, parentDataType: DataType) {
const destDataItem = destDataType.getDataItem();
const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem;
super(dataItem);
this.destDataType = destDataType;
this.parentDataType = parentDataType;
this.children.push(destDataType);
}
/*
public assignValue(destDataType: DynamicDataType) {
this.destDataType = destDataType;
}*/
public assignValue(value: any) {
this.destDataType.assignValue(value);
}
public getHexValue(): string {
console.log(
'*'.repeat(40),
this.destDataType.getAbsoluteOffset().toString(16),
'^'.repeat(150),
this.parentDataType.getAbsoluteOffset().toString(16),
);
let offset = this.destDataType
.getAbsoluteOffset()
.minus(this.parentDataType.getAbsoluteOffset())
.minus(this.parentDataType.getHeaderSize());
console.log("OFFSET == ", JSON.stringify(offset), " or in hex -- 0x", offset.toString(16));
const hexBase = 16;
const evmWordWidth = 32;
const valueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(`0x${offset.toString(hexBase)}`), evmWordWidth);
const encodedValue = ethUtil.bufferToHex(valueBuf);
return encodedValue;
}
public getSignature(): string {
return this.destDataType.getSignature();
}
public isStatic(): boolean {
return true;
}
public getHeaderSize(): BigNumber {
return new BigNumber(0);
}
public getBodySize(): BigNumber {
return new BigNumber(32);
}
}
export class DataTypeFactory {
public static mapDataItemToDataType(dataItem: DataItem): DataType {
console.log(`Type: ${dataItem.type}`);
if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
if (Address.matchGrammar(dataItem.type)) return new Address(dataItem);
if (Bool.matchGrammar(dataItem.type)) return new Bool(dataItem);
if (Int.matchGrammar(dataItem.type)) return new Int(dataItem);
if (UInt.matchGrammar(dataItem.type)) return new UInt(dataItem);
if (Byte.matchGrammar(dataItem.type)) return new Byte(dataItem);
if (Tuple.matchGrammar(dataItem.type)) return new Tuple(dataItem);
if (SolArray.matchGrammar(dataItem.type)) return new SolArray(dataItem);
if (Bytes.matchGrammar(dataItem.type)) return new Bytes(dataItem);
if (SolString.matchGrammar(dataItem.type)) return new SolString(dataItem);
//if (Fixed.matchGrammar(dataItem.type)) return Fixed(dataItem);
//if (UFixed.matchGrammar(dataItem.type)) return UFixed(dataItem);
throw new Error(`Unrecognized data type: '${dataItem.type}'`);
}
public static create(dataItem: DataItem, parentDataType: DataType): DataType {
const dataType = DataTypeFactory.mapDataItemToDataType(dataItem);
if (dataType.isStatic()) {
return dataType;
} else {
const pointer = new Pointer(dataType, parentDataType);
return pointer;
}
throw new Error(`Unrecognized instance type: '${dataType}'`);
}
}
class Queue<T> {
private store: T[] = [];
push(val: T) {
this.store.push(val);
}
pop(): T | undefined {
return this.store.shift();
}
}
export class Method extends DataType {
name: string;
params: DataType[];
private signature: string;
selector: string;
constructor(abi: MethodAbi) {
super({ type: 'method', name: abi.name });
this.name = abi.name;
this.params = [];
_.each(abi.inputs, (input: DataItem) => {
this.params.push(DataTypeFactory.create(input, this));
});
// Compute signature
this.signature = `${this.name}(`;
_.each(this.params, (param: DataType, i: number) => {
this.signature += param.getSignature();
if (i < this.params.length - 1) {
this.signature += ',';
}
});
this.signature += ')';
// Compute selector
this.selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(this.signature).slice(0, 4)));
console.log(`--SIGNATURE--\n${this.signature}\n---------\n`);
console.log(`--SELECTOR--\n${this.selector}\n---------\n`);
}
public getSignature(): string {
return this.signature;
}
public assignValue(args: any[]) {
_.each(this.params, (param: DataType, i: number) => {
// Assign value to parameter
try {
param.assignValue(args[i]);
} catch (e) {
console.log('Failed to assign to ', param.getDataItem().name);
throw e;
}
if (param instanceof Pointer) {
this.children.push(param.getChildren()[0]);
}
});
}
public getHexValue(): string {
let paramBufs: Buffer[] = [];
_.each(this.params, (param: DataType) => {
paramBufs.push(ethUtil.toBuffer(param.getHexValue()));
});
const value = Buffer.concat(paramBufs);
const hexValue = ethUtil.bufferToHex(value);
return hexValue;
}
public encode(args: any[]): string {
this.assignValue(args);
const calldata = new Calldata(this.selector, this.params.length);
calldata.printAnnotated();
this.bind(calldata, CalldataSection.PARAMS);
return calldata.getHexValue();
}
public isStatic(): boolean {
return true;
}
public getHeaderSize(): BigNumber {
// Exclude selector
return new BigNumber(0);
}
public getBodySize(): BigNumber {
const nParams = new BigNumber(this.params.length);
const evmWordWidth = new BigNumber(32);
const size = nParams.times(evmWordWidth);
return size;
}
/*
encodeOptimized(args: any[]): string {
const calldata = new Memory();
// Assign values
optimizableParams : StaticDataType = [];
_.each(this.params, function(args: any[], i: number, param: DataType) {
param.assignValue(args[i]);
if (param instanceof DynamicDataType) {
}
});
// Find non-parameter leaves
return '';
} */
/*
decode(rawCalldata: string): any[] {
const calldata = new Calldata(this.name, this.params.length);
calldata.assignRaw(rawCalldata);
let args: any[];
let params = this.params;
_.each(params, function(args: any[], i: number, param: DataType) {
param.decodeFromCalldata(calldata);
args.push(param.getValue());
});
return args;
}*/
}