Cleaner interface for encoding/decoding. Moved encode/decode parameters into a struct.
This commit is contained in:
@@ -2,6 +2,15 @@ import ethUtil = require('ethereumjs-util');
|
||||
import CommunicationChatBubbleOutline from 'material-ui/SvgIcon';
|
||||
var _ = require('lodash');
|
||||
|
||||
export interface DecodingRules {
|
||||
structsAsObjects: boolean;
|
||||
}
|
||||
|
||||
export interface EncodingRules {
|
||||
optimize?: boolean;
|
||||
annotate?: boolean;
|
||||
}
|
||||
|
||||
export abstract class CalldataBlock {
|
||||
private name: string;
|
||||
private signature: string;
|
||||
@@ -237,11 +246,13 @@ class Queue<T> {
|
||||
|
||||
export class Calldata {
|
||||
private selector: string;
|
||||
private rules: EncodingRules;
|
||||
private sizeInBytes: number;
|
||||
private root: MemberCalldataBlock | undefined;
|
||||
|
||||
constructor() {
|
||||
this.selector = '0x';
|
||||
constructor(rules: EncodingRules) {
|
||||
this.selector = '';
|
||||
this.rules = rules;
|
||||
this.sizeInBytes = 0;
|
||||
this.root = undefined;
|
||||
}
|
||||
@@ -272,28 +283,6 @@ export class Calldata {
|
||||
return blockQueue;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// Basic optimize method that prunes duplicate branches of the tree
|
||||
// Notes:
|
||||
// 1. Pruning is at the calldata block level, so it is independent of type
|
||||
// 2.
|
||||
private optimize(blocks: CalldataBlock[]) {
|
||||
// Build hash table of blocks
|
||||
const blockLookupTable: { [key: string]: string } = {};
|
||||
_.each(blocks, (block: CalldataBlock) => {
|
||||
if (blocks instanceof DependentCalldataBlock === false) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const leavesHash = block.hashLeaves();
|
||||
if (leavesHash in blockLookupTable) {
|
||||
|
||||
}
|
||||
})
|
||||
}*/
|
||||
|
||||
private generateAnnotatedHexString(): string {
|
||||
let hexValue = `${this.selector}`;
|
||||
if (this.root === undefined) {
|
||||
@@ -413,12 +402,12 @@ export class Calldata {
|
||||
console.log('*'.repeat(100), ' FINISHED OPTIMIZING ', '*'.repeat(100));
|
||||
}
|
||||
|
||||
public toHexString(optimize: boolean = false, annotate: boolean = false): string {
|
||||
public toHexString(): string {
|
||||
if (this.root === undefined) {
|
||||
throw new Error('expected root');
|
||||
}
|
||||
|
||||
if (optimize) this.optimize();
|
||||
if (this.rules.optimize) this.optimize();
|
||||
|
||||
const offsetQueue = this.createQueue(this.root);
|
||||
let block: CalldataBlock | undefined;
|
||||
@@ -428,7 +417,7 @@ export class Calldata {
|
||||
offset += block.getSizeInBytes();
|
||||
}
|
||||
|
||||
const hexValue = annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString();
|
||||
const hexValue = this.rules.annotate ? this.generateAnnotatedHexString() : this.generateCondensedHexString();
|
||||
return hexValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata";
|
||||
import { MethodAbi, DataItem } from 'ethereum-types';
|
||||
import { DecodingRules, EncodingRules } from './calldata';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import ethUtil = require('ethereumjs-util');
|
||||
var _ = require('lodash');
|
||||
|
||||
export interface GenerateValueRules {
|
||||
structsAsObjects: boolean;
|
||||
}
|
||||
|
||||
export abstract class DataType {
|
||||
private static DEFAULT_ENCODING_RULES = { optimize: false, annotate: false } as EncodingRules;
|
||||
private static DEFAULT_DECODING_RULES = { structsAsObjects: false } as DecodingRules;
|
||||
|
||||
private dataItem: DataItem;
|
||||
|
||||
constructor(dataItem: DataItem) {
|
||||
@@ -19,9 +19,25 @@ export abstract class DataType {
|
||||
return this.dataItem;
|
||||
}
|
||||
|
||||
public encode(value: any, rules?: EncodingRules, selector?: string): string {
|
||||
const rules_ = rules ? rules : DataType.DEFAULT_ENCODING_RULES;
|
||||
const calldata = new Calldata(rules_);
|
||||
if (selector) calldata.setSelector(selector);
|
||||
const block = this.generateCalldataBlock(value);
|
||||
calldata.setRoot(block as MemberCalldataBlock); // @TODO CHANGE
|
||||
const calldataHex = calldata.toHexString();
|
||||
return calldataHex;
|
||||
}
|
||||
|
||||
public decode(calldata: string, rules?: DecodingRules): any {
|
||||
const rawCalldata = new RawCalldata(calldata);
|
||||
const rules_ = rules ? rules : DataType.DEFAULT_DECODING_RULES;
|
||||
const value = this.generateValue(rawCalldata, rules_);
|
||||
return value;
|
||||
}
|
||||
|
||||
public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock;
|
||||
public abstract generateValue(calldata: RawCalldata, rules: GenerateValueRules): any;
|
||||
public abstract encode(value: any, calldata: Calldata): void;
|
||||
public abstract generateValue(calldata: RawCalldata, rules: DecodingRules): any;
|
||||
public abstract getSignature(): string;
|
||||
public abstract isStatic(): boolean;
|
||||
}
|
||||
@@ -44,12 +60,7 @@ export abstract class PayloadDataType extends DataType {
|
||||
return block;
|
||||
}
|
||||
|
||||
public encode(value: any, calldata: Calldata): void {
|
||||
const block = this.generateCalldataBlock(value);
|
||||
// calldata.setRoot(block);
|
||||
}
|
||||
|
||||
public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any {
|
||||
public generateValue(calldata: RawCalldata, rules: DecodingRules): any {
|
||||
const value = this.decodeValue(calldata);
|
||||
return value;
|
||||
}
|
||||
@@ -86,12 +97,7 @@ export abstract class DependentDataType extends DataType {
|
||||
return block;
|
||||
}
|
||||
|
||||
public encode(value: any, calldata: Calldata = new Calldata()): void {
|
||||
const block = this.generateCalldataBlock(value);
|
||||
//calldata.setRoot(block);
|
||||
}
|
||||
|
||||
public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any {
|
||||
public generateValue(calldata: RawCalldata, rules: DecodingRules): any {
|
||||
const destinationOffsetBuf = calldata.popWord();
|
||||
const currentOffset = calldata.getOffset();
|
||||
const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16);
|
||||
@@ -237,12 +243,7 @@ export abstract class MemberDataType extends DataType {
|
||||
return block;
|
||||
}
|
||||
|
||||
public encode(value: any, calldata: Calldata = new Calldata()): void {
|
||||
const block = this.generateCalldataBlock(value);
|
||||
calldata.setRoot(block);
|
||||
}
|
||||
|
||||
public generateValue(calldata: RawCalldata, rules: GenerateValueRules): any[] | object {
|
||||
public generateValue(calldata: RawCalldata, rules: DecodingRules): any[] | object {
|
||||
let members = this.members;
|
||||
if (this.isArray && this.arrayLength === undefined) {
|
||||
const arrayLengthBuf = calldata.popWord();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { GenerateValueRules, DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type';
|
||||
import { DataType, DataTypeFactory, DataTypeFactoryImpl, PayloadDataType, DependentDataType, MemberDataType } from './data_type';
|
||||
|
||||
import { DecodingRules, EncodingRules } from './calldata';
|
||||
|
||||
import { MethodAbi, DataItem } from 'ethereum-types';
|
||||
|
||||
@@ -13,7 +15,7 @@ var _ = require('lodash');
|
||||
export interface DataTypeStaticInterface {
|
||||
matchGrammar: (type: string) => boolean;
|
||||
encodeValue: (value: any) => Buffer;
|
||||
// decodeValue: (value: Buffer) => [any, Buffer];
|
||||
decodeValue: (rawCalldata: RawCalldata) => any;
|
||||
}
|
||||
|
||||
export class Address extends PayloadDataType {
|
||||
@@ -489,21 +491,16 @@ export class Method extends MemberDataType {
|
||||
return selector;
|
||||
}
|
||||
|
||||
public encode(value: any[] | object, calldata = new Calldata(), annotate: boolean = false, optimize: boolean = false): string {
|
||||
calldata.setSelector(this.methodSelector);
|
||||
super.encode(value, calldata);
|
||||
return calldata.toHexString(optimize, annotate);
|
||||
public encode(value: any, rules?: EncodingRules): string {
|
||||
const calldata = super.encode(value, rules, this.selector);
|
||||
return calldata;
|
||||
}
|
||||
|
||||
public decode(calldata: string, decodeStructsAsObjects: boolean = false): any[] | object {
|
||||
const calldata_ = new RawCalldata(calldata);
|
||||
if (this.selector !== calldata_.getSelector()) {
|
||||
throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${calldata_.getSelector()}'`);
|
||||
public decode(calldata: string, rules?: DecodingRules): any[] | object {
|
||||
if (!calldata.startsWith(this.selector)) {
|
||||
throw new Error(`Tried to decode calldata, but it was missing the function selector. Expected '${this.selector}'.`);
|
||||
}
|
||||
let rules: GenerateValueRules = {
|
||||
structsAsObjects: decodeStructsAsObjects
|
||||
};
|
||||
const value = super.generateValue(calldata_, rules);
|
||||
const value = super.decode(calldata, rules);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const expect = chai.expect;
|
||||
describe.only('ABI Encoder', () => {
|
||||
describe.only('ABI Tests at Method Level', () => {
|
||||
|
||||
it('Optimizer #1', async () => {
|
||||
it('Should reuse duplicated strings in string array', async () => {
|
||||
const method = new AbiEncoder.Method(AbiSamples.stringAbi);
|
||||
const strings = [
|
||||
"Test String",
|
||||
@@ -37,7 +37,7 @@ describe.only('ABI Encoder', () => {
|
||||
];
|
||||
const args = [strings];
|
||||
|
||||
const optimizedCalldata = method.encode(args, new Calldata(), true, true);
|
||||
const optimizedCalldata = method.encode(args, { optimize: true, annotate: true });
|
||||
console.log(optimizedCalldata);
|
||||
});
|
||||
|
||||
@@ -53,10 +53,10 @@ describe.only('ABI Encoder', () => {
|
||||
const args = [stringArray, string];
|
||||
|
||||
|
||||
const TEST = method.encode(args, new Calldata(), true, true);
|
||||
const TEST = method.encode(args, { optimize: true, annotate: true });
|
||||
console.log(TEST);
|
||||
|
||||
const optimizedCalldata = method.encode(args, new Calldata(), false, true);
|
||||
const optimizedCalldata = method.encode(args, { optimize: true });
|
||||
|
||||
console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`);
|
||||
const decodedArgs = method.decode(optimizedCalldata);
|
||||
@@ -79,10 +79,10 @@ describe.only('ABI Encoder', () => {
|
||||
const args = [uint8Array, uintTupleArray];
|
||||
|
||||
|
||||
const TEST = method.encode(args, new Calldata(), true, true);
|
||||
const TEST = method.encode(args, { optimize: true, annotate: true });
|
||||
console.log('*'.repeat(50), ' ENCODED DATA ', TEST);
|
||||
|
||||
const optimizedCalldata = method.encode(args, new Calldata(), false, true);
|
||||
const optimizedCalldata = method.encode(args, { optimize: true });
|
||||
|
||||
console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`);
|
||||
const decodedArgs = method.decode(optimizedCalldata);
|
||||
@@ -92,7 +92,7 @@ describe.only('ABI Encoder', () => {
|
||||
expect(decodedArgsJson).to.be.equal(argsJson);
|
||||
});
|
||||
|
||||
it.only('Optimizer #4 (Expect no optimization)', async () => {
|
||||
it('Optimizer #4 (Expect no optimization)', async () => {
|
||||
const method = new AbiEncoder.Method(AbiSamples.optimizerAbi4);
|
||||
const uint8Array = [
|
||||
new BigNumber(100),
|
||||
@@ -104,10 +104,10 @@ describe.only('ABI Encoder', () => {
|
||||
const args = [uint8Array, uintTupleArray];
|
||||
|
||||
|
||||
const TEST = method.encode(args, new Calldata(), true, true);
|
||||
const TEST = method.encode(args, { optimize: true, annotate: true });
|
||||
console.log('*'.repeat(50), ' ENCODED DATA ', TEST);
|
||||
|
||||
const optimizedCalldata = method.encode(args, new Calldata(), false, true);
|
||||
const optimizedCalldata = method.encode(args, { optimize: true });
|
||||
|
||||
console.log(`OPTIMIZED CALLDATA == '${optimizedCalldata}'`);
|
||||
const decodedArgs = method.decode(optimizedCalldata);
|
||||
@@ -179,12 +179,9 @@ describe.only('ABI Encoder', () => {
|
||||
someArrayOfTuplesWithDynamicTypes: someArrayOfTuplesWithDynamicTypes
|
||||
};
|
||||
|
||||
const calldata = method.encode(args, new Calldata(), true);
|
||||
const calldata = method.encode(args);
|
||||
console.log(calldata);
|
||||
|
||||
|
||||
throw new Error(`done`);
|
||||
|
||||
console.log('*'.repeat(40));
|
||||
console.log(JSON.stringify(args));
|
||||
console.log(method.getSignature());
|
||||
@@ -195,7 +192,7 @@ describe.only('ABI Encoder', () => {
|
||||
|
||||
// Test decoding
|
||||
const expectedDecodedValueJson = JSON.stringify(args);
|
||||
const decodedValue = method.decode(calldata, true);
|
||||
const decodedValue = method.decode(calldata, { structsAsObjects: true });
|
||||
const decodedValueJson = JSON.stringify(decodedValue);
|
||||
console.log(`DECODED`, '*'.repeat(200), '\n', decodedValueJson);
|
||||
expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);
|
||||
|
||||
Reference in New Issue
Block a user