From 96bcc7e33281a3a7831e92dad65de5303ac39523 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 6 Nov 2018 16:33:25 -0800 Subject: Going towards first calldata impl --- packages/order-utils/test/abi_encoder_test.ts | 265 ++++++++++++++++++++++++-- 1 file changed, 250 insertions(+), 15 deletions(-) (limited to 'packages') diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 0366ddfcc..7e12dfee6 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -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; + + 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}`; + } - class Word {} + 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(); }); }); -- cgit