Initial port -- wont compile yet
This commit is contained in:
1
packages/order-utils/test/abi/abi_encoder.ts
Normal file
1
packages/order-utils/test/abi/abi_encoder.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './calldata_block';
|
||||
177
packages/order-utils/test/abi/calldata.ts
Normal file
177
packages/order-utils/test/abi/calldata.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
var _ = require('lodash');
|
||||
|
||||
export abstract class CalldataBlock {
|
||||
private name: string;
|
||||
private signature: string;
|
||||
private offsetInBytes: number;
|
||||
private headerSizeInBytes: number;
|
||||
private bodySizeInBytes: number;
|
||||
private 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.headerSizeInBytes = headerSizeInBytes;
|
||||
this.bodySizeInBytes = bodySizeInBytes;
|
||||
this.relocatable = relocatable;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
public isRelocatable(): boolean {
|
||||
return this.relocatable;
|
||||
}
|
||||
|
||||
public getHeaderSizeInBytes(): number {
|
||||
return this.headerSizeInBytes;
|
||||
}
|
||||
|
||||
public getBodySizeInBytes(): number {
|
||||
return this.bodySizeInBytes;
|
||||
}
|
||||
|
||||
public getSizeInBytes(): number {
|
||||
return this.headerSizeInBytes + this.bodySizeInBytes;
|
||||
}
|
||||
|
||||
public getOffsetInBytes(): number {
|
||||
return this.offsetInBytes;
|
||||
}
|
||||
|
||||
public abstract toHexString(): string;
|
||||
}
|
||||
|
||||
export class PayloadCalldataBlock extends CalldataBlock {
|
||||
private 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);
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public toHexString(): string {
|
||||
const payloadHex = ethUtil.bufferToHex(this.payload);
|
||||
return payloadHex;
|
||||
}
|
||||
}
|
||||
|
||||
export class DependentCalldataBlock extends CalldataBlock {
|
||||
public static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
|
||||
private parent: CalldataBlock;
|
||||
private 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);
|
||||
this.parent = parent;
|
||||
this.dependency = dependency;
|
||||
}
|
||||
|
||||
public toHexString(): string {
|
||||
const dependencyOffset = this.dependency.getOffsetInBytes();
|
||||
const parentOffset = this.parent.getOffsetInBytes();
|
||||
const parentHeaderSize = this.parent.getHeaderSizeInBytes();
|
||||
const pointer = dependencyOffset - parentOffset + parentHeaderSize;
|
||||
const pointerBuf = new Buffer(`0x${pointer.toString(16)}`);
|
||||
const evmWordWidthInBytes = 32;
|
||||
const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes);
|
||||
const pointerHex = ethUtil.bufferToHex(pointerBufPadded);
|
||||
return pointerHex;
|
||||
}
|
||||
}
|
||||
|
||||
export class MemberCalldataBlock extends CalldataBlock {
|
||||
private static DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
|
||||
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;
|
||||
let bodySizeInBytes = 0;
|
||||
_.each(members, (member: Memblock) => {
|
||||
bodySizeInBytes += member.getSizeInBytes();
|
||||
});
|
||||
|
||||
super(name, signature, offsetInBytes, headerSizeInBytes, bodySizeInBytes, relocatable);
|
||||
this.members = members;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export class Calldata {
|
||||
private selector: string;
|
||||
private sizeInBytes: number;
|
||||
private blocks: CalldataBlock[];
|
||||
|
||||
constructor() {
|
||||
this.selector = '0x';
|
||||
this.sizeInBytes = 0;
|
||||
this.blocks = [];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public getSelectorHex(): string {
|
||||
return this.selector;
|
||||
}
|
||||
|
||||
public getSizeInBytes(): number {
|
||||
return this.sizeInBytes;
|
||||
}
|
||||
|
||||
public toAnnotatedString(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
public pushBlock(block: CalldataBlock) {
|
||||
this.blocks.push(block);
|
||||
this.sizeInBytes += block.getSizeInBytes();
|
||||
}
|
||||
|
||||
public setSelector(selector: string) {
|
||||
// Ensure we have a 0x prefix
|
||||
if (selector.startsWith('0x')) {
|
||||
this.selector = selector;
|
||||
} else {
|
||||
this.selector = `$0x${selector}`;
|
||||
}
|
||||
|
||||
// The selector must be 10 characters: '0x' followed by 4 bytes (two hex chars per byte)
|
||||
if (this.selector.length !== 10) {
|
||||
throw new Error(`Invalid selector '${this.selector}'`);
|
||||
}
|
||||
this.sizeInBytes += 8;
|
||||
}
|
||||
}
|
||||
4
packages/order-utils/test/abi/config.ts
Normal file
4
packages/order-utils/test/abi/config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { DataTypeFactory } from './data_type';
|
||||
import { EvmDataTypeFactoryImpl } from './evm_data_types';
|
||||
|
||||
DataTypeFactory.setImpl(new EvmDataTypeFactoryImpl());
|
||||
278
packages/order-utils/test/abi/data_type.ts
Normal file
278
packages/order-utils/test/abi/data_type.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock } from "./calldata";
|
||||
import { MethodAbi, DataItem } from 'ethereum-types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
var _ = require('lodash');
|
||||
|
||||
|
||||
|
||||
export abstract class DataType {
|
||||
private dataItem: DataItem;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
this.dataItem = dataItem;
|
||||
}
|
||||
|
||||
public getDataItem(): DataItem {
|
||||
return this.dataItem;
|
||||
}
|
||||
|
||||
protected abstract createCalldataBlock(): CalldataBlock;
|
||||
public abstract encode(value: any, calldata: Calldata): void;
|
||||
public abstract getSignature(): string;
|
||||
public abstract isStatic(): boolean;
|
||||
}
|
||||
|
||||
export abstract class PayloadDataType extends DataType {
|
||||
protected hasConstantSize: boolean;
|
||||
|
||||
public constructor(dataItem: DataItem, hasConstantSize: boolean) {
|
||||
super(dataItem);
|
||||
this.hasConstantSize = hasConstantSize;
|
||||
}
|
||||
|
||||
protected generateCalldataBlock(payload: Buffer, calldata: Calldata): void {
|
||||
const name = this.getDataItem().name;
|
||||
const signature = this.getSignature();
|
||||
const offsetInBytes = calldata.getSizeInBytes();
|
||||
const relocatable = false;
|
||||
const block = new PayloadCalldataBlock(name, signature, offsetInBytes, relocatable, payload);
|
||||
calldata.pushBlock(block);
|
||||
}
|
||||
|
||||
public isStatic(): boolean {
|
||||
// If a payload has a constant size then it's static
|
||||
return this.hasConstantSize;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class DependentDataType extends DataType {
|
||||
protected dependency: DataType;
|
||||
protected parent: DataType;
|
||||
|
||||
public constructor(dataItem: DataItem, dependency: DataType, parent: DataType) {
|
||||
super(dataItem);
|
||||
this.dependency = dependency;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
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 offsetInBytes = calldata.reserveSpace(DependentCalldataBlock.DEPENDENT_PAYLOAD_SIZE_IN_BYTES);
|
||||
this.dependency.encode(value, calldata);
|
||||
this.generateCalldataBlock(offsetInBytes, calldata);
|
||||
}
|
||||
|
||||
public isStatic(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MemberMap {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
export abstract class MemberDataType extends DataType {
|
||||
private memberMap: MemberMap;
|
||||
private members: DataType[];
|
||||
private isArray: boolean;
|
||||
protected arrayLength: number | undefined;
|
||||
|
||||
|
||||
public constructor(dataItem: DataItem, isArray: boolean = false, arrayLength?: number) {
|
||||
super(dataItem);
|
||||
|
||||
this.memberMap = {};
|
||||
this.members = [];
|
||||
this.isArray = isArray;
|
||||
this.arrayLength = arrayLength;
|
||||
if (isArray && arrayLength !== undefined) {
|
||||
[this.members, this.memberMap] = MemberDataType.createMembersWithLength(arrayLength);
|
||||
} else if (!isArray) {
|
||||
[this.members, this.memberMap] = MemberDataType.createMembersWithKeys(dataItem);
|
||||
}
|
||||
}
|
||||
|
||||
private static createMembersWithKeys(dataItem: DataItem): [DataType[], MemberMap] {
|
||||
// Sanity check
|
||||
if (dataItem.components === undefined) {
|
||||
throw new Error(`Expected components`);
|
||||
}
|
||||
|
||||
let members: DataType[] = [];
|
||||
let memberMap: MemberMap = {};
|
||||
_.each(dataItem.components, (dataItem: DataItem) => {
|
||||
const childDataItem = {
|
||||
type: dataItem.type,
|
||||
name: `${dataItem.name}.${dataItem.name}`,
|
||||
} as DataItem;
|
||||
const child = DataTypeFactory.create(childDataItem, this);
|
||||
members.push(child);
|
||||
memberMap[dataItem.name] = members.length;
|
||||
});
|
||||
|
||||
return [members, memberMap];
|
||||
}
|
||||
|
||||
private static createMembersWithLength(dataItem: DataItem, length: number): [DataType[], MemberMap] {
|
||||
let members: DataType[] = [];
|
||||
let memberMap: MemberMap = {};
|
||||
const range = _.range(length);
|
||||
_.each(range, (idx: number) => {
|
||||
const childDataItem = {
|
||||
type: this.type,
|
||||
name: `${dataItem.name}[${idx.toString(10)}]`,
|
||||
} as DataItem;
|
||||
const components = dataItem.components;
|
||||
if (components !== undefined) {
|
||||
childDataItem.components = components;
|
||||
}
|
||||
const child = DataTypeFactory.create(childDataItem, this);
|
||||
members.push(child);
|
||||
memberMap[idx.toString(10)] = members.length;
|
||||
});
|
||||
|
||||
return [members, memberMap];
|
||||
}
|
||||
|
||||
protected encodeFromArray(value: any[], calldata: Calldata) {
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
|
||||
protected encodeFromObject(obj: object, calldata: Calldata) {
|
||||
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);
|
||||
delete childMap[key];
|
||||
});
|
||||
|
||||
if (Object.keys(childMap).length !== 0) {
|
||||
throw new Error(`Could not assign tuple to object: missing keys ${Object.keys(childMap)}`);
|
||||
}
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected computeSignatureOfMembers(): string {
|
||||
// Compute signature of members
|
||||
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 {
|
||||
/* For Tuple:
|
||||
const isStaticTuple = this.children.length === 0;
|
||||
return isStaticTuple; // @TODO: True in every case or only when dynamic data?
|
||||
|
||||
For Array:
|
||||
if isLengthDefined = false then this is false
|
||||
|
||||
Otherwise if the first element is a Pointer then false
|
||||
*/
|
||||
|
||||
if (this.isArray && this.arrayLength === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Search for dependent members
|
||||
const dependentMember = _.find(this.members, (member: DataType) => {
|
||||
return (member instanceof DependentDataType);
|
||||
});
|
||||
const isStatic = (dependentMember === undefined); // static if we couldn't find a dependent member
|
||||
return isStatic;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DataTypeFactoryImpl {
|
||||
create: (dataItem: DataItem, parentDataType: DataType) => DataType;
|
||||
mapDataItemToDataType: (dataItem: DataItem) => DataType;
|
||||
}
|
||||
|
||||
export class DataTypeFactory {
|
||||
private static instance: DataTypeFactory;
|
||||
private provider: DataTypeFactoryImpl | undefined;
|
||||
|
||||
private constructor() { }
|
||||
|
||||
private static getInstance(): DataTypeFactory {
|
||||
if (!DataTypeFactory.instance) {
|
||||
DataTypeFactory.instance = new DataTypeFactory();
|
||||
}
|
||||
return DataTypeFactory.instance;
|
||||
}
|
||||
|
||||
public static setImpl(provider: DataTypeFactoryImpl) {
|
||||
const instance = DataTypeFactory.getInstance();
|
||||
if (instance.provider !== undefined) {
|
||||
throw new Error(`Tried to set implementation more than once`);
|
||||
}
|
||||
DataTypeFactory.getInstance().provider = provider;
|
||||
}
|
||||
|
||||
public static create(dataItem: DataItem, parentDataType: DataType): DataType {
|
||||
const instance = DataTypeFactory.getInstance();
|
||||
if (instance.provider === undefined) {
|
||||
throw new Error(`Tried to create before implementation is set`);
|
||||
}
|
||||
return instance.provider.create(dataItem, parentDataType);
|
||||
}
|
||||
|
||||
public static mapDataItemToDataType(dataItem: DataItem): DataType {
|
||||
const instance = DataTypeFactory.getInstance();
|
||||
if (instance.provider === undefined) {
|
||||
throw new Error(`Tried to create before implementation is set`);
|
||||
}
|
||||
return instance.provider.mapDataItemToDataType(dataItem);
|
||||
}
|
||||
}
|
||||
439
packages/order-utils/test/abi/evm_data_types.ts
Normal file
439
packages/order-utils/test/abi/evm_data_types.ts
Normal file
@@ -0,0 +1,439 @@
|
||||
import { DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type';
|
||||
|
||||
import { MethodAbi, DataItem } from 'ethereum-types';
|
||||
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export interface DataTypeStaticInterface {
|
||||
matchGrammar: (type: string) => boolean;
|
||||
encodeValue: (value: any) => Buffer;
|
||||
}
|
||||
|
||||
export class Address extends PayloadDataType {
|
||||
private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem, Address.SIZE_KNOWN_AT_COMPILE_TIME);
|
||||
if (!Address.matchGrammar(dataItem.type)) {
|
||||
throw new Error(`Tried to instantiate Address with bad input: ${dataItem}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return 'address';
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'address';
|
||||
}
|
||||
|
||||
public static encodeValue(value: boolean): Buffer {
|
||||
const evmWordWidth = 32;
|
||||
const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth);
|
||||
return encodedValueBuf;
|
||||
}
|
||||
}
|
||||
|
||||
export class Bool extends PayloadDataType {
|
||||
private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem, Bool.SIZE_KNOWN_AT_COMPILE_TIME);
|
||||
if (!Bool.matchGrammar(dataItem.type)) {
|
||||
throw new Error(`Tried to instantiate Bool with bad input: ${dataItem}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return 'bool';
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'bool';
|
||||
}
|
||||
|
||||
public static encodeValue(value: boolean): Buffer {
|
||||
const evmWordWidth = 32;
|
||||
const encodedValue = value === true ? '0x1' : '0x0';
|
||||
const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth);
|
||||
return encodedValueBuf;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Number extends PayloadDataType {
|
||||
private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
|
||||
static MAX_WIDTH: number = 256;
|
||||
static DEFAULT_WIDTH: number = Number.MAX_WIDTH;
|
||||
width: number = Number.DEFAULT_WIDTH;
|
||||
|
||||
constructor(dataItem: DataItem, matcher: RegExp) {
|
||||
super(dataItem, Number.SIZE_KNOWN_AT_COMPILE_TIME);
|
||||
const matches = matcher.exec(dataItem.type);
|
||||
if (matches === null) {
|
||||
throw new Error(`Tried to instantiate Number with bad input: ${dataItem}`);
|
||||
}
|
||||
if (matches !== null && matches.length === 2 && matches[1] !== undefined) {
|
||||
this.width = parseInt(matches[1]);
|
||||
} else {
|
||||
this.width = 256;
|
||||
}
|
||||
}
|
||||
|
||||
public static 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())) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
return valueBuf;
|
||||
}
|
||||
|
||||
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 PayloadDataType {
|
||||
private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = true;
|
||||
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, Byte.SIZE_KNOWN_AT_COMPILE_TIME);
|
||||
const matches = Byte.matcher.exec(dataItem.type);
|
||||
if (!Byte.matchGrammar(dataItem.type)) {
|
||||
throw new Error(`Tried to instantiate Byte with bad input: ${dataItem}`);
|
||||
}
|
||||
if (matches !== null && matches.length === 3 && matches[2] !== undefined) {
|
||||
this.width = parseInt(matches[2]);
|
||||
} else {
|
||||
this.width = Byte.DEFAULT_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
// Note that `byte` reduces to `bytes1`
|
||||
return `bytes${this.width}`;
|
||||
}
|
||||
|
||||
public static encodeValue(value: string | Buffer): 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);
|
||||
return paddedValue;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return this.matcher.test(type);
|
||||
}
|
||||
}
|
||||
|
||||
export class Bytes extends PayloadDataType {
|
||||
private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false;
|
||||
static UNDEFINED_LENGTH = new BigNumber(-1);
|
||||
length: BigNumber = Bytes.UNDEFINED_LENGTH;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem, Bytes.SIZE_KNOWN_AT_COMPILE_TIME);
|
||||
if (!Bytes.matchGrammar(dataItem.type)) {
|
||||
throw new Error(`Tried to instantiate Bytes with bad input: ${dataItem}`);
|
||||
}
|
||||
}
|
||||
|
||||
public encodeValue(value: string | Buffer): 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(valueBuf, paddedBytesForValue);
|
||||
const paddedLengthBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(valueBuf.byteLength), 32);
|
||||
const encodedValueBuf = Buffer.concat([paddedLengthBuf, paddedValueBuf]);
|
||||
return encodedValueBuf;
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return 'bytes';
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'bytes';
|
||||
}
|
||||
}
|
||||
|
||||
export class SolString extends PayloadDataType {
|
||||
private static SIZE_KNOWN_AT_COMPILE_TIME: boolean = false;
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem, SolString.SIZE_KNOWN_AT_COMPILE_TIME);
|
||||
if (!SolString.matchGrammar(dataItem.type)) {
|
||||
throw new Error(`Tried to instantiate String with bad input: ${dataItem}`);
|
||||
}
|
||||
}
|
||||
|
||||
public static encodeValue(value: string): Buffer {
|
||||
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]);
|
||||
return encodedValueBuf;
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
export class Pointer extends DependentDataType {
|
||||
|
||||
constructor(destDataType: DataType, parentDataType: DataType) {
|
||||
const destDataItem = destDataType.getDataItem();
|
||||
const dataItem = { name: `ptr<${destDataItem.name}>`, type: `ptr<${destDataItem.type}>` } as DataItem;
|
||||
super(dataItem, destDataType, parentDataType);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return this.dependency.getSignature();
|
||||
}
|
||||
}
|
||||
|
||||
export class Tuple extends MemberDataType {
|
||||
private tupleSignature: string;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem);
|
||||
if (!Tuple.matchGrammar(dataItem.type)) {
|
||||
throw new Error(`Tried to instantiate Tuple with bad input: ${dataItem}`);
|
||||
}
|
||||
this.tupleSignature = this.computeSignatureOfMembers();
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return this.tupleSignature;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'tuple';
|
||||
}
|
||||
}
|
||||
|
||||
export class SolArray extends MemberDataType {
|
||||
static matcher = RegExp('^(.+)\\[([0-9]*)\\]$');
|
||||
private arraySignature: string;
|
||||
private elementType: string;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
// Sanity check
|
||||
const matches = SolArray.matcher.exec(dataItem.type);
|
||||
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}`);
|
||||
}
|
||||
|
||||
const isArray = true;
|
||||
const arrayLength = (matches[2] === '') ? undefined : parseInt(matches[2], 10);
|
||||
super(dataItem, isArray, arrayLength);
|
||||
this.elementType = matches[1];
|
||||
this.arraySignature = this.computeSignature();
|
||||
}
|
||||
|
||||
private computeSignature(): string {
|
||||
let dataItem = {
|
||||
type: this.elementType,
|
||||
name: 'N/A',
|
||||
} as DataItem;
|
||||
const components = this.getDataItem().components;
|
||||
if (components !== undefined) {
|
||||
dataItem.components = components;
|
||||
}
|
||||
const elementDataType = DataTypeFactory.mapDataItemToDataType(dataItem);
|
||||
const type = elementDataType.getSignature();
|
||||
if (this.arrayLength === undefined) {
|
||||
return `${type}[]`;
|
||||
} else {
|
||||
return `${type}[${this.arrayLength}]`;
|
||||
}
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return this.arraySignature;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return this.matcher.test(type);
|
||||
}
|
||||
}
|
||||
|
||||
export class Method extends MemberDataType {
|
||||
private methodSignature: string;
|
||||
private methodSelector: string;
|
||||
|
||||
constructor(abi: MethodAbi) {
|
||||
super({ type: 'method', name: abi.name });
|
||||
this.methodSignature = this.computeSignature();
|
||||
this.methodSelector = this.computeSelector();
|
||||
}
|
||||
|
||||
private computeSignature(): string {
|
||||
const memberSignature = this.computeSignatureOfMembers();
|
||||
const methodSignature = `${this.getDataItem().name}${memberSignature}`;
|
||||
return methodSignature;
|
||||
}
|
||||
|
||||
private computeSelector(): string {
|
||||
const signature = this.computeSignature();
|
||||
const selector = ethUtil.bufferToHex(ethUtil.toBuffer(ethUtil.sha3(signature).slice(0, 4)));
|
||||
return selector;
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return this.methodSignature;
|
||||
}
|
||||
|
||||
public getSelector(): string {
|
||||
return this.methodSelector;
|
||||
}
|
||||
}
|
||||
|
||||
export class EvmDataTypeFactoryImpl implements DataTypeFactoryImpl {
|
||||
|
||||
public 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 (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 create(dataItem: DataItem, parentDataType: DataType): DataType {
|
||||
const dataType = this.mapDataItemToDataType(dataItem);
|
||||
if (dataType.isStatic()) {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
const pointer = new Pointer(dataType, parentDataType);
|
||||
return pointer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user