aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Hysen <greg.hysen@gmail.com>2018-11-13 09:10:32 +0800
committerGreg Hysen <greg.hysen@gmail.com>2018-11-29 08:38:10 +0800
commit9b1f56c9687d3fffb2f59fa98d619ead90c1b910 (patch)
treece8f939d16e15972c8eabe11a678776ee83ae68c
parent13d456fda1da19c8227acb2bf23106bcbdaf6f12 (diff)
downloaddexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.tar
dexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.tar.gz
dexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.tar.bz2
dexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.tar.lz
dexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.tar.xz
dexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.tar.zst
dexon-sol-tools-9b1f56c9687d3fffb2f59fa98d619ead90c1b910.zip
Decoding works for some basic typs
-rw-r--r--packages/order-utils/test/abi/calldata.ts102
-rw-r--r--packages/order-utils/test/abi/data_type.ts43
-rw-r--r--packages/order-utils/test/abi/evm_data_types.ts103
-rw-r--r--packages/order-utils/test/abi_encoder_test.ts40
4 files changed, 274 insertions, 14 deletions
diff --git a/packages/order-utils/test/abi/calldata.ts b/packages/order-utils/test/abi/calldata.ts
index 94f6a5571..0445f68ec 100644
--- a/packages/order-utils/test/abi/calldata.ts
+++ b/packages/order-utils/test/abi/calldata.ts
@@ -164,6 +164,12 @@ class Queue<T> {
mergeFront(q: Queue<T>) {
this.store = q.store.concat(this.store);
}
+ getStore(): T[] {
+ return this.store;
+ }
+ peek(): T | undefined {
+ return this.store.length >= 0 ? this.store[0] : undefined;
+ }
}
export class Calldata {
@@ -203,7 +209,29 @@ export class Calldata {
return blockQueue;
}
- public toHexString(): string {
+ /*
+
+ // 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) {
+
+ }
+ })
+ }*/
+
+ public toHexString(optimize: boolean = false): string {
let selectorBuffer = ethUtil.toBuffer(this.selector);
if (this.root === undefined) {
throw new Error('expected root');
@@ -223,6 +251,8 @@ export class Calldata {
valueBufs.push(block.toBuffer());
}
+ // if (optimize) this.optimize(valueQueue.getStore());
+
const combinedBuffers = Buffer.concat(valueBufs);
const hexValue = ethUtil.bufferToHex(combinedBuffers);
return hexValue;
@@ -259,4 +289,74 @@ export class Calldata {
}
this.sizeInBytes += 8;
}
+}
+
+export class RawCalldata {
+ private value: Buffer;
+ private offset: number; // tracks current offset into raw calldata; used for parsing
+ private selector: string;
+ private scopes: Queue<number>;
+
+ constructor(value: string | Buffer) {
+ if (typeof value === 'string' && !value.startsWith('0x')) {
+ throw new Error(`Expected raw calldata to start with '0x'`);
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ this.selector = ethUtil.bufferToHex(valueBuf.slice(0, 4));
+ this.value = valueBuf.slice(4); // disregard selector
+ this.offset = 0;
+ this.scopes = new Queue<number>();
+ this.scopes.push(0);
+ }
+
+ public popBytes(lengthInBytes: number): Buffer {
+ const value = this.value.slice(this.offset, this.offset + lengthInBytes);
+ this.setOffset(this.offset + lengthInBytes);
+ return value;
+ }
+
+ public popWord(): Buffer {
+ const wordInBytes = 32;
+ return this.popBytes(wordInBytes);
+ }
+
+ public popWords(length: number): Buffer {
+ const wordInBytes = 32;
+ return this.popBytes(length * wordInBytes);
+ }
+
+ public readBytes(from: number, to: number): Buffer {
+ const value = this.value.slice(from, to);
+ return value;
+ }
+
+ public setOffset(offsetInBytes: number) {
+ this.offset = offsetInBytes;
+ console.log('0'.repeat(100), this.offset);
+ }
+
+ public startScope() {
+ this.scopes.pushFront(this.offset);
+ }
+
+ public endScope() {
+ this.scopes.pop();
+ }
+
+ public getOffset(): number {
+ return this.offset;
+ }
+
+ public toAbsoluteOffset(relativeOffset: number) {
+ const scopeOffset = this.scopes.peek();
+ if (scopeOffset === undefined) {
+ throw new Error(`Tried to access undefined scope.`);
+ }
+ const absoluteOffset = relativeOffset + scopeOffset;
+ return absoluteOffset;
+ }
+
+ public getSelector(): string {
+ return this.selector;
+ }
} \ No newline at end of file
diff --git a/packages/order-utils/test/abi/data_type.ts b/packages/order-utils/test/abi/data_type.ts
index 532407f52..af170f7e6 100644
--- a/packages/order-utils/test/abi/data_type.ts
+++ b/packages/order-utils/test/abi/data_type.ts
@@ -1,4 +1,4 @@
-import { Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata";
+import { RawCalldata, Calldata, CalldataBlock, PayloadCalldataBlock, DependentCalldataBlock, MemberCalldataBlock } from "./calldata";
import { MethodAbi, DataItem } from 'ethereum-types';
import { BigNumber } from '@0x/utils';
import ethUtil = require('ethereumjs-util');
@@ -18,6 +18,7 @@ export abstract class DataType {
}
public abstract generateCalldataBlock(value: any, parentBlock?: CalldataBlock): CalldataBlock;
+ public abstract generateValue(calldata: RawCalldata): any;
public abstract encode(value: any, calldata: Calldata): void;
public abstract getSignature(): string;
public abstract isStatic(): boolean;
@@ -46,12 +47,18 @@ export abstract class PayloadDataType extends DataType {
// calldata.setRoot(block);
}
+ public generateValue(calldata: RawCalldata): any {
+ const value = this.decodeValue(calldata);
+ return value;
+ }
+
public isStatic(): boolean {
// If a payload has a constant size then it's static
return this.hasConstantSize;
}
public abstract encodeValue(value: any): Buffer;
+ public abstract decodeValue(calldata: RawCalldata): any;
}
export abstract class DependentDataType extends DataType {
@@ -81,6 +88,17 @@ export abstract class DependentDataType extends DataType {
//calldata.setRoot(block);
}
+ public generateValue(calldata: RawCalldata): any {
+ const destinationOffsetBuf = calldata.popWord();
+ const currentOffset = calldata.getOffset();
+ const destinationOffsetRelative = parseInt(ethUtil.bufferToHex(destinationOffsetBuf), 16);
+ const destinationOffsetAbsolute = calldata.toAbsoluteOffset(destinationOffsetRelative);
+ calldata.setOffset(destinationOffsetAbsolute);
+ const value = this.dependency.generateValue(calldata);
+ calldata.setOffset(currentOffset);
+ return value;
+ }
+
public isStatic(): boolean {
return true;
}
@@ -179,7 +197,6 @@ export abstract class MemberDataType extends DataType {
methodBlock.setHeader(lenBuf);
}
-
const memberBlocks: CalldataBlock[] = [];
_.each(members, (member: DataType, idx: number) => {
const block = member.generateCalldataBlock(value[idx], methodBlock);
@@ -220,6 +237,28 @@ export abstract class MemberDataType extends DataType {
calldata.setRoot(block);
}
+ public generateValue(calldata: RawCalldata): any[] {
+ let members = this.members;
+ if (this.isArray && this.arrayLength === undefined) {
+ const arrayLengthBuf = calldata.popWord();
+ const arrayLengthHex = ethUtil.bufferToHex(arrayLengthBuf);
+ const hexBase = 16;
+ const arrayLength = new BigNumber(arrayLengthHex, hexBase);
+
+ [members,] = this.createMembersWithLength(this.getDataItem(), arrayLength.toNumber());
+ }
+
+ calldata.startScope();
+ const decodedValue: any[] = [];
+ _.each(members, (member: DataType, idx: number) => {
+ let memberValue = member.generateValue(calldata);
+ decodedValue.push(memberValue);
+ });
+ calldata.endScope();
+
+ return decodedValue;
+ }
+
protected computeSignatureOfMembers(): string {
// Compute signature of members
let signature = `(`;
diff --git a/packages/order-utils/test/abi/evm_data_types.ts b/packages/order-utils/test/abi/evm_data_types.ts
index dfa541e80..a05c29b28 100644
--- a/packages/order-utils/test/abi/evm_data_types.ts
+++ b/packages/order-utils/test/abi/evm_data_types.ts
@@ -4,7 +4,7 @@ import { MethodAbi, DataItem } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
-import { Calldata } from './calldata';
+import { Calldata, RawCalldata } from './calldata';
import { BigNumber } from '@0x/utils';
@@ -13,6 +13,7 @@ var _ = require('lodash');
export interface DataTypeStaticInterface {
matchGrammar: (type: string) => boolean;
encodeValue: (value: any) => Buffer;
+ // decodeValue: (value: Buffer) => [any, Buffer];
}
export class Address extends PayloadDataType {
@@ -38,6 +39,13 @@ export class Address extends PayloadDataType {
const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(value), evmWordWidth);
return encodedValueBuf;
}
+
+ public decodeValue(calldata: RawCalldata): string {
+ const paddedValueBuf = calldata.popWord();
+ const valueBuf = paddedValueBuf.slice(12);
+ const value = ethUtil.bufferToHex(valueBuf);
+ return value;
+ }
}
export class Bool extends PayloadDataType {
@@ -64,6 +72,17 @@ export class Bool extends PayloadDataType {
const encodedValueBuf = ethUtil.setLengthLeft(ethUtil.toBuffer(encodedValue), evmWordWidth);
return encodedValueBuf;
}
+
+ public decodeValue(calldata: RawCalldata): boolean {
+ const valueBuf = calldata.popWord();
+ const valueHex = ethUtil.bufferToHex(valueBuf);
+ const valueNumber = new BigNumber(valueHex, 16);
+ let value: boolean = (valueNumber.equals(0)) ? false : true;
+ if (!(valueNumber.equals(0) || valueNumber.equals(1))) {
+ throw new Error(`Failed to decode boolean. Expected 0x0 or 0x1, got ${valueHex}`);
+ }
+ return value;
+ }
}
abstract class Number extends PayloadDataType {
@@ -126,6 +145,37 @@ abstract class Number extends PayloadDataType {
return valueBuf;
}
+ public decodeValue(calldata: RawCalldata): BigNumber {
+ const decodedValueBuf = calldata.popWord();
+ const decodedValueHex = ethUtil.bufferToHex(decodedValueBuf);
+ let decodedValue = new BigNumber(decodedValueHex, 16);
+ if (this instanceof Int) {
+ // Check if we're negative
+ const binBase = 2;
+ const decodedValueBin = decodedValue.toString(binBase);
+ if (decodedValueBin[0] === '1') {
+ // Negative
+ // Step 1/3: Invert binary value
+ const bitsInEvmWord = 256;
+ let invertedValueBin = '1'.repeat(bitsInEvmWord - decodedValueBin.length);
+ _.each(decodedValueBin, (bit: string) => {
+ invertedValueBin += bit === '1' ? '0' : '1';
+ });
+ const invertedValue = new BigNumber(invertedValueBin, binBase);
+
+ // Step 2/3: Add 1 to inverted value
+ // The result is the two's-complement represent of the input value.
+ const positiveDecodedValue = invertedValue.plus(binBase);
+
+ // Step 3/3: Invert positive value
+ const negativeDecodedValue = positiveDecodedValue.times(-1);
+ decodedValue = negativeDecodedValue;
+ }
+ }
+
+ return decodedValue;
+ }
+
public abstract getMaxValue(): BigNumber;
public abstract getMinValue(): BigNumber;
}
@@ -228,6 +278,13 @@ export class Byte extends PayloadDataType {
return paddedValue;
}
+ public decodeValue(calldata: RawCalldata): string {
+ const paddedValueBuf = calldata.popWord();
+ const valueBuf = paddedValueBuf.slice(0, this.width);
+ const value = ethUtil.bufferToHex(valueBuf);
+ return value;
+ }
+
public static matchGrammar(type: string): boolean {
return this.matcher.test(type);
}
@@ -262,6 +319,17 @@ export class Bytes extends PayloadDataType {
return encodedValueBuf;
}
+ public decodeValue(calldata: RawCalldata): string {
+ const lengthBuf = calldata.popWord();
+ const lengthHex = ethUtil.bufferToHex(lengthBuf);
+ const length = parseInt(lengthHex, 16);
+ const wordsForValue = Math.ceil(length / 32);
+ const paddedValueBuf = calldata.popWords(wordsForValue);
+ const valueBuf = paddedValueBuf.slice(0, length);
+ const decodedValue = ethUtil.bufferToHex(valueBuf);
+ return decodedValue;
+ }
+
public getSignature(): string {
return 'bytes';
}
@@ -289,6 +357,18 @@ export class SolString extends PayloadDataType {
return encodedValueBuf;
}
+ public decodeValue(calldata: RawCalldata): string {
+ const lengthBuf = calldata.popWord();
+ const lengthHex = ethUtil.bufferToHex(lengthBuf);
+ const length = parseInt(lengthHex, 16);
+ const wordsForValue = Math.ceil(length / 32);
+ const paddedValueBuf = calldata.popWords(wordsForValue);
+ const valueBuf = paddedValueBuf.slice(0, length);
+ console.log('LENGTH UPINYA === ', length);
+ const value = valueBuf.toString('ascii');
+ return value;
+ }
+
public getSignature(): string {
return 'string';
}
@@ -415,6 +495,27 @@ export class Method extends MemberDataType {
return calldata.toHexString();
}
+ /*
+ protected decodeValue(value: Buffer): any[] {
+ const selectorBuf = value.slice(4);
+ const selectorHex = ethUtil.bufferToHex(selectorBuf);
+ if (this.selector !== selectorHex) {
+ throw new Error(`Tried to decode calldata with mismatched selector. Expected '${this.selector}', got '${selectorHex}'`);
+ }
+ const remainingValue = value.slice(9);
+ const decodedValue = super.decodeValue(remainingValue);
+ return decodedValue;
+ }*/
+
+ public decode(calldata: string): any[] {
+ 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()}'`);
+ }
+ const value = super.generateValue(calldata_);
+ return value;
+ }
+
public getSignature(): string {
return this.methodSignature;
}
diff --git a/packages/order-utils/test/abi_encoder_test.ts b/packages/order-utils/test/abi_encoder_test.ts
index 0db5f4281..e8ebd7b98 100644
--- a/packages/order-utils/test/abi_encoder_test.ts
+++ b/packages/order-utils/test/abi_encoder_test.ts
@@ -310,6 +310,12 @@ describe.only('ABI Encoder', () => {
const expectedCalldata =
'0xf68ade72000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000036';
expect(calldata).to.be.equal(expectedCalldata);
+
+ // Test decoding
+ const expectedDecodedValueJson = JSON.stringify(args);
+ const decodedValue = method.decode(calldata);
+ const decodedValueJson = JSON.stringify(decodedValue);
+ expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);
});
@@ -327,38 +333,52 @@ describe.only('ABI Encoder', () => {
const expectedCalldata =
'0x7ac2bd96af000000000000000000000000000000000000000000000000000000000000000001020304050607080911121314151617181920212223242526272829303132000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000047616161616161616161616161616161616161616161616161616161616161616161616161616161611114f3245678384756473829384756774488993384576688990020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414d79206669727374206e616d65206973204772656720616e64206d79206c617374206e616d6520697320487973656e2c207768617420646f207961206b6e6f772100000000000000000000000000000000000000000000000000000000000000';
expect(calldata).to.be.equal(expectedCalldata);
- });
- it('Yessir', async () => {
- const method = new AbiEncoder.Method(AbiSamples.simpleAbi);
- const calldata = method.encode([new BigNumber(5), 'five']);
- console.log(calldata);
- expect(true).to.be.true();
+ // Test decoding
+ const expectedDecodedValueJson = JSON.stringify(args);
+ const decodedValue = method.decode(calldata);
+ const decodedValueJson = JSON.stringify(decodedValue);
+ expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);
});
- it('Array ABI', async () => {
+ it.only('Array ABI', async () => {
const method = new AbiEncoder.Method(AbiSamples.stringAbi);
console.log(method);
- const calldata = method.encode([['five', 'six', 'seven']]);
+ const args = [['five', 'six', 'seven']];
+ const calldata = method.encode(args);
console.log(method.getSignature());
console.log(method.selector);
+ /*
console.log(calldata);
const expectedCalldata =
'0x13e751a900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000046669766500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000373697800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005736576656e000000000000000000000000000000000000000000000000000000';
- expect(calldata).to.be.equal(expectedCalldata);
+ expect(calldata).to.be.equal(expectedCalldata);*/
+
+ // Test decoding
+ const expectedDecodedValueJson = JSON.stringify(args);
+ const decodedValue = method.decode(calldata);
+ const decodedValueJson = JSON.stringify(decodedValue);
+ expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);
});
it('Static Tuple', async () => {
// This is dynamic because it has dynamic members
const method = new AbiEncoder.Method(AbiSamples.staticTupleAbi);
- const calldata = method.encode([[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]]);
+ const args = [[new BigNumber(5), new BigNumber(10), new BigNumber(15), false]];
+ const calldata = method.encode(args);
console.log(method.getSignature());
console.log(method.selector);
console.log(calldata);
const expectedCalldata = '0xa9125e150000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000f0000000000000000000000000000000000000000000000000000000000000000';
expect(calldata).to.be.equal(expectedCalldata);
+
+ // Test decoding
+ const expectedDecodedValueJson = JSON.stringify(args);
+ const decodedValue = method.decode(calldata);
+ const decodedValueJson = JSON.stringify(decodedValue);
+ expect(decodedValueJson).to.be.equal(expectedDecodedValueJson);
});
it('Dynamic Tuple (Array input)', async () => {