Going towards first calldata impl
This commit is contained in:
@@ -11,13 +11,14 @@ import { chaiSetup } from './utils/chai_setup';
|
||||
import { MethodAbi, DataItem } from 'ethereum-types';
|
||||
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { assert } from '@0x/order-utils/src/assert';
|
||||
|
||||
const simpleAbi = {
|
||||
constant: false,
|
||||
inputs: [
|
||||
{
|
||||
name: 'greg',
|
||||
type: 'uint208',
|
||||
type: 'uint256',
|
||||
},
|
||||
{
|
||||
name: 'gregStr',
|
||||
@@ -116,17 +117,138 @@ chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
namespace AbiEncoder {
|
||||
class Memory {}
|
||||
class Word {
|
||||
private value: string;
|
||||
|
||||
class Word {}
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
|
||||
enum CalldataSection {
|
||||
NONE,
|
||||
PARAMS,
|
||||
DATA,
|
||||
}
|
||||
|
||||
class Memblock {
|
||||
private dataType: DataType;
|
||||
private location: { calldataSection: CalldataSection; offset: BigNumber };
|
||||
|
||||
constructor(dataType: DataType) {
|
||||
this.dataType = dataType;
|
||||
this.location = {
|
||||
calldataSection: CalldataSection.NONE,
|
||||
offset: new BigNumber(0),
|
||||
};
|
||||
}
|
||||
|
||||
public getSize(): BigNumber {
|
||||
return new BigNumber(ethUtil.toBuffer(this.dataType.getHexValue()).byteLength);
|
||||
}
|
||||
|
||||
public assignLocation(calldataSection: CalldataSection, offset: BigNumber) {
|
||||
this.location.calldataSection = calldataSection;
|
||||
this.location.offset = offset;
|
||||
}
|
||||
|
||||
public get(): string {
|
||||
return ethUtil.stripHexPrefix(this.dataType.getHexValue());
|
||||
}
|
||||
}
|
||||
|
||||
interface BindList {
|
||||
[key: string]: Memblock;
|
||||
}
|
||||
|
||||
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;
|
||||
console.log(this.selector);
|
||||
this.params = [];
|
||||
this.data = [];
|
||||
const evmWordSize = 32;
|
||||
this.dataOffset = new BigNumber(nParams).times(evmWordSize);
|
||||
this.currentDataOffset = this.dataOffset;
|
||||
this.currentParamOffset = new BigNumber(0);
|
||||
this.bindList = {};
|
||||
}
|
||||
|
||||
public bind(dataType: DataType, section: CalldataSection = CalldataSection.DATA) {
|
||||
if (dataType.getId() in this.bindList) {
|
||||
throw `Rebind`;
|
||||
}
|
||||
const memblock = new Memblock(dataType);
|
||||
switch (section) {
|
||||
case CalldataSection.PARAMS:
|
||||
this.params.push(memblock);
|
||||
memblock.assignLocation(section, this.currentParamOffset);
|
||||
this.currentParamOffset = this.currentParamOffset.plus(memblock.getSize());
|
||||
break;
|
||||
|
||||
case CalldataSection.DATA:
|
||||
this.data.push(memblock);
|
||||
memblock.assignLocation(section, this.currentDataOffset);
|
||||
this.currentDataOffset = this.currentDataOffset.plus(memblock.getSize());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw `Unrecognized calldata section: ${section}`;
|
||||
}
|
||||
|
||||
this.bindList[dataType.getId()] = memblock;
|
||||
}
|
||||
|
||||
public getHexValue(): string {
|
||||
let hexValue = `0x${this.selector}`;
|
||||
_.each(this.params, (memblock: Memblock) => {
|
||||
hexValue += memblock.get();
|
||||
});
|
||||
_.each(this.data, (memblock: Memblock) => {
|
||||
hexValue += memblock.get();
|
||||
});
|
||||
|
||||
return hexValue;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class DataType {
|
||||
private dataItem: DataItem;
|
||||
private hexValue: string;
|
||||
private memblock: Memblock | undefined;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
this.dataItem = dataItem;
|
||||
this.hexValue = '0x';
|
||||
this.memblock = undefined;
|
||||
}
|
||||
|
||||
protected assignHexValue(hexValue: string) {
|
||||
@@ -141,13 +263,25 @@ namespace AbiEncoder {
|
||||
return this.dataItem;
|
||||
}
|
||||
|
||||
public rbind(memblock: Memblock) {
|
||||
this.memblock = memblock;
|
||||
}
|
||||
|
||||
public bind(calldata: Calldata) {
|
||||
if (this.memblock !== undefined) return; // already binded
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.dataItem.name;
|
||||
}
|
||||
|
||||
public abstract assignValue(value: any): void;
|
||||
public abstract getSignature(): string;
|
||||
public abstract encodeToCalldata(calldata: Calldata): void;
|
||||
|
||||
// abstract match(type: string): Bool;
|
||||
}
|
||||
|
||||
class Calldata {}
|
||||
|
||||
export abstract class StaticDataType extends DataType {
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem);
|
||||
@@ -163,7 +297,7 @@ namespace AbiEncoder {
|
||||
export class Address extends StaticDataType {
|
||||
constructor(dataItem: DataItem) {
|
||||
super(dataItem);
|
||||
expect(Tuple.matchGrammar(dataItem.type)).to.be.true();
|
||||
expect(Address.matchGrammar(dataItem.type)).to.be.true();
|
||||
}
|
||||
|
||||
public assignValue(value: string) {
|
||||
@@ -171,6 +305,14 @@ namespace AbiEncoder {
|
||||
this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'address';
|
||||
}
|
||||
@@ -187,6 +329,14 @@ namespace AbiEncoder {
|
||||
//this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'bool';
|
||||
}
|
||||
@@ -209,11 +359,19 @@ namespace AbiEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public assignValue(value: string) {
|
||||
//const hexValue = ethUtil.bufferToHex(new Buffer(value));
|
||||
//this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return this.matcher.test(type);
|
||||
}
|
||||
@@ -259,6 +417,14 @@ namespace AbiEncoder {
|
||||
this.assignHexValue(encodedValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return `uint${this.width}`;
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return this.matcher.test(type);
|
||||
}
|
||||
@@ -286,6 +452,14 @@ namespace AbiEncoder {
|
||||
//this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return this.matcher.test(type);
|
||||
}
|
||||
@@ -302,6 +476,14 @@ namespace AbiEncoder {
|
||||
//this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'tuple';
|
||||
}
|
||||
@@ -321,6 +503,14 @@ namespace AbiEncoder {
|
||||
//this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'bytes';
|
||||
}
|
||||
@@ -345,9 +535,17 @@ namespace AbiEncoder {
|
||||
//this.assignHexValue(hexValue);
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return this.matcher.test(type);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
export class SolString extends DynamicDataType {
|
||||
@@ -367,6 +565,12 @@ namespace AbiEncoder {
|
||||
this.assignHexValue(encodedValue);
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {}
|
||||
|
||||
public static matchGrammar(type: string): boolean {
|
||||
return type === 'string';
|
||||
}
|
||||
@@ -399,6 +603,14 @@ namespace AbiEncoder {
|
||||
public getHexValue(): string {
|
||||
return this.destDataType.getHexValue();
|
||||
}
|
||||
|
||||
public getSignature(): string {
|
||||
return this.destDataType.getSignature();
|
||||
}
|
||||
|
||||
public encodeToCalldata(calldata: Calldata): void {
|
||||
throw 2;
|
||||
}
|
||||
}
|
||||
|
||||
export class DataTypeFactory {
|
||||
@@ -437,6 +649,8 @@ namespace AbiEncoder {
|
||||
export class Method {
|
||||
name: string;
|
||||
params: DataType[];
|
||||
signature: string;
|
||||
selector: string;
|
||||
|
||||
constructor(abi: MethodAbi) {
|
||||
// super();
|
||||
@@ -446,20 +660,40 @@ namespace AbiEncoder {
|
||||
_.each(abi.inputs, (input: DataItem) => {
|
||||
this.params.push(DataTypeFactory.create(input));
|
||||
});
|
||||
|
||||
// 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`);
|
||||
}
|
||||
|
||||
encode(args: any[]): string {
|
||||
//const calldata = new Calldata(this.name, this.params.length);
|
||||
let params = this.params;
|
||||
const calldata = new Calldata(this.selector, this.params.length);
|
||||
|
||||
// Write params section
|
||||
const params = this.params;
|
||||
_.each(params, (param: DataType, i: number) => {
|
||||
console.log('param:\n', param, '\n--end--\n');
|
||||
console.log('arg:\n', args[i], '\n--end\n');
|
||||
// Assign value to param
|
||||
param.assignValue(args[i]);
|
||||
console.log(param.getHexValue());
|
||||
//param.encodeToCalldata(calldata);
|
||||
// Binds top-level parameter to the params section of calldata
|
||||
calldata.bind(param, CalldataSection.PARAMS);
|
||||
// Binds parameter's children to the data section of calldata,
|
||||
// while retaining internal pointers
|
||||
param.bind(calldata);
|
||||
});
|
||||
|
||||
return '';
|
||||
return calldata.getHexValue();
|
||||
|
||||
//return calldata.getRaw();
|
||||
}
|
||||
@@ -499,10 +733,11 @@ namespace AbiEncoder {
|
||||
}
|
||||
|
||||
describe.only('ABI Encoder', () => {
|
||||
describe('Just a Greg, Eh', () => {
|
||||
describe.only('Just a Greg, Eh', () => {
|
||||
it('Yessir', async () => {
|
||||
const method = new AbiEncoder.Method(simpleAbi);
|
||||
method.encode([new BigNumber(5), 'five']);
|
||||
const calldata = method.encode([new BigNumber(5), 'five']);
|
||||
console.log(calldata);
|
||||
expect(true).to.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user