Cleaner interface for encoding/decoding. Moved encode/decode parameters into a struct.

This commit is contained in:
Greg Hysen
2018-11-14 13:51:08 -08:00
parent 93e967c3b3
commit 2d2255e9af
4 changed files with 63 additions and 79 deletions

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);