From 2d2255e9af42f6441ee620c4aa74bd1981dfe03a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 14 Nov 2018 13:51:08 -0800 Subject: Cleaner interface for encoding/decoding. Moved encode/decode parameters into a struct. --- packages/order-utils/test/abi/calldata.ts | 43 ++++++++-------------- packages/order-utils/test/abi/data_type.ts | 49 +++++++++++++------------ packages/order-utils/test/abi/evm_data_types.ts | 25 ++++++------- packages/order-utils/test/abi_encoder_test.ts | 25 ++++++------- 4 files changed, 63 insertions(+), 79 deletions(-) diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts index 754d8f823..7eb4e0026 100644 --- a/packages/order-utils/test/abi/calldata.ts +++ b/packages/order-utils/test/abi/calldata.ts @@ -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 { 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; } diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts index 25f792c7a..6ad0b2736 100644 --- a/packages/order-utils/test/abi/data_type.ts +++ b/packages/order-utils/test/abi/data_type.ts @@ -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(); diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts index a24046664..9504b1a10 100644 --- a/packages/order-utils/test/abi/evm_data_types.ts +++ b/packages/order-utils/test/abi/evm_data_types.ts @@ -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; } diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts index 78e28015d..52b6bc067 100644 --- a/packages/order-utils/test/abi_encoder_test.ts +++ b/packages/order-utils/test/abi_encoder_test.ts @@ -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); -- cgit v1.2.3