aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/utils/src/abi_encoder/calldata.ts531
-rw-r--r--packages/utils/src/abi_encoder/calldata/calldata.ts224
-rw-r--r--packages/utils/src/abi_encoder/calldata/calldata_block.ts77
-rw-r--r--packages/utils/src/abi_encoder/calldata/calldata_blocks.ts3
-rw-r--r--packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts59
-rw-r--r--packages/utils/src/abi_encoder/calldata/index.ts6
-rw-r--r--packages/utils/src/abi_encoder/calldata/member_calldata_block.ts48
-rw-r--r--packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts20
-rw-r--r--packages/utils/src/abi_encoder/calldata/raw_calldata.ts82
-rw-r--r--packages/utils/src/abi_encoder/data_type.ts4
-rw-r--r--packages/utils/src/abi_encoder/evm_data_types/method.ts3
-rw-r--r--packages/utils/src/abi_encoder/index.ts2
-rw-r--r--packages/utils/src/abi_encoder/utils/queue.ts39
-rw-r--r--packages/utils/src/abi_encoder/utils/rules.ts8
14 files changed, 571 insertions, 535 deletions
diff --git a/packages/utils/src/abi_encoder/calldata.ts b/packages/utils/src/abi_encoder/calldata.ts
deleted file mode 100644
index d108ef0a7..000000000
--- a/packages/utils/src/abi_encoder/calldata.ts
+++ /dev/null
@@ -1,531 +0,0 @@
-import * as ethUtil from 'ethereumjs-util';
-import * as _ from 'lodash';
-
-import * as Constants from './constants';
-
-export interface DecodingRules {
- structsAsObjects: boolean;
-}
-
-export interface EncodingRules {
- optimize?: boolean;
- annotate?: boolean;
-}
-
-export abstract class CalldataBlock {
- private readonly _signature: string;
- private readonly _parentName: string;
- private _name: string;
- private _offsetInBytes: number;
- private _headerSizeInBytes: number;
- private _bodySizeInBytes: number;
-
- constructor(
- name: string,
- signature: string,
- parentName: string,
- headerSizeInBytes: number,
- bodySizeInBytes: number,
- ) {
- this._name = name;
- this._signature = signature;
- this._parentName = parentName;
- this._offsetInBytes = 0;
- this._headerSizeInBytes = headerSizeInBytes;
- this._bodySizeInBytes = bodySizeInBytes;
- }
-
- protected _setHeaderSize(headerSizeInBytes: number): void {
- this._headerSizeInBytes = headerSizeInBytes;
- }
-
- protected _setBodySize(bodySizeInBytes: number): void {
- this._bodySizeInBytes = bodySizeInBytes;
- }
-
- protected _setName(name: string): void {
- this._name = name;
- }
-
- public getName(): string {
- return this._name;
- }
-
- public getParentName(): string {
- return this._parentName;
- }
-
- public getSignature(): string {
- return this._signature;
- }
- public getHeaderSizeInBytes(): number {
- return this._headerSizeInBytes;
- }
-
- public getBodySizeInBytes(): number {
- return this._bodySizeInBytes;
- }
-
- public getSizeInBytes(): number {
- return this.getHeaderSizeInBytes() + this.getBodySizeInBytes();
- }
-
- public getOffsetInBytes(): number {
- return this._offsetInBytes;
- }
-
- public setOffset(offsetInBytes: number): void {
- this._offsetInBytes = offsetInBytes;
- }
-
- public computeHash(): Buffer {
- const rawData = this.getRawData();
- const hash = ethUtil.sha3(rawData);
- return hash;
- }
-
- public abstract toBuffer(): Buffer;
- public abstract getRawData(): Buffer;
-}
-
-export class PayloadCalldataBlock extends CalldataBlock {
- private readonly _payload: Buffer;
-
- constructor(name: string, signature: string, parentName: string, payload: Buffer) {
- const headerSizeInBytes = 0;
- const bodySizeInBytes = payload.byteLength;
- super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes);
- this._payload = payload;
- }
-
- public toBuffer(): Buffer {
- return this._payload;
- }
-
- public getRawData(): Buffer {
- return this._payload;
- }
-}
-
-export class DependentCalldataBlock extends CalldataBlock {
- public static readonly RAW_DATA_START = new Buffer('<');
- public static readonly RAW_DATA_END = new Buffer('>');
- private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
- private static readonly _EMPTY_HEADER_SIZE = 0;
- private readonly _parent: CalldataBlock;
- private readonly _dependency: CalldataBlock;
- private _aliasFor: CalldataBlock | undefined;
-
- constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) {
- const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE;
- const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES;
- super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes);
- this._parent = parent;
- this._dependency = dependency;
- this._aliasFor = undefined;
- }
-
- public toBuffer(): Buffer {
- const destinationOffset =
- this._aliasFor !== undefined ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes();
- const parentOffset = this._parent.getOffsetInBytes();
- const parentHeaderSize = this._parent.getHeaderSizeInBytes();
- const pointer: number = destinationOffset - (parentOffset + parentHeaderSize);
- const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(Constants.HEX_BASE)}`);
- const evmWordWidthInBytes = 32;
- const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes);
- return pointerBufPadded;
- }
-
- public getDependency(): CalldataBlock {
- return this._dependency;
- }
-
- public setAlias(block: CalldataBlock): void {
- this._aliasFor = block;
- this._setName(`${this.getName()} (alias for ${block.getName()})`);
- }
-
- public getAlias(): CalldataBlock | undefined {
- return this._aliasFor;
- }
-
- public getRawData(): Buffer {
- const dependencyRawData = this._dependency.getRawData();
- const rawDataComponents: Buffer[] = [];
- rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START);
- rawDataComponents.push(dependencyRawData);
- rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END);
- const rawData = Buffer.concat(rawDataComponents);
- return rawData;
- }
-}
-
-export class MemberCalldataBlock extends CalldataBlock {
- private _header: Buffer | undefined;
- private _members: CalldataBlock[];
-
- constructor(name: string, signature: string, parentName: string) {
- super(name, signature, parentName, 0, 0);
- this._members = [];
- this._header = undefined;
- }
-
- public getRawData(): Buffer {
- const rawDataComponents: Buffer[] = [];
- if (this._header !== undefined) {
- rawDataComponents.push(this._header);
- }
- _.each(this._members, (member: CalldataBlock) => {
- const memberBuffer = member.getRawData();
- rawDataComponents.push(memberBuffer);
- });
-
- const rawData = Buffer.concat(rawDataComponents);
- return rawData;
- }
-
- public setMembers(members: CalldataBlock[]): void {
- this._members = members;
- }
-
- public setHeader(header: Buffer): void {
- this._setHeaderSize(header.byteLength);
- this._header = header;
- }
-
- public toBuffer(): Buffer {
- if (this._header !== undefined) {
- return this._header;
- }
- return new Buffer('');
- }
-
- public getMembers(): CalldataBlock[] {
- return this._members;
- }
-}
-
-class Queue<T> {
- private _store: T[] = [];
- public push(val: T): void {
- this._store.push(val);
- }
- public pushFront(val: T): void {
- this._store.unshift(val);
- }
- public pop(): T | undefined {
- return this._store.shift();
- }
- public popBack(): T | undefined {
- if (this._store.length === 0) {
- return undefined;
- }
- const backElement = this._store.splice(-1, 1)[0];
- return backElement;
- }
- public merge(q: Queue<T>): void {
- this._store = this._store.concat(q._store);
- }
- public mergeFront(q: Queue<T>): void {
- this._store = q._store.concat(this._store);
- }
- public getStore(): T[] {
- return this._store;
- }
- public peek(): T | undefined {
- return this._store.length >= 0 ? this._store[0] : undefined;
- }
-}
-
-export class Calldata {
- private readonly _rules: EncodingRules;
- private _selector: string;
- private _sizeInBytes: number;
- private _root: CalldataBlock | undefined;
-
- private static _createQueue(block: CalldataBlock): Queue<CalldataBlock> {
- const blockQueue = new Queue<CalldataBlock>();
-
- // Base Case
- if (!(block instanceof MemberCalldataBlock)) {
- blockQueue.push(block);
- return blockQueue;
- }
-
- // This is a Member Block
- const memberBlock = block;
- _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => {
- if (member instanceof MemberCalldataBlock) {
- blockQueue.mergeFront(Calldata._createQueue(member));
- } else {
- blockQueue.pushFront(member);
- }
- });
-
- // Children
- _.each(memberBlock.getMembers(), (member: CalldataBlock) => {
- if (member instanceof DependentCalldataBlock && member.getAlias() === undefined) {
- const dependency = member.getDependency();
- if (dependency instanceof MemberCalldataBlock) {
- blockQueue.merge(Calldata._createQueue(dependency));
- } else {
- blockQueue.push(dependency);
- }
- }
- });
-
- blockQueue.pushFront(memberBlock);
- return blockQueue;
- }
-
- public constructor(rules: EncodingRules) {
- this._rules = rules;
- this._selector = '';
- this._sizeInBytes = 0;
- this._root = undefined;
- }
-
- public optimize(): void {
- if (this._root === undefined) {
- throw new Error('expected root');
- }
-
- const blocksByHash: { [key: string]: CalldataBlock } = {};
-
- // 1. Create a queue of subtrees by hash
- // Note that they are ordered the same as
- const subtreeQueue = Calldata._createQueue(this._root);
- let block: CalldataBlock | undefined;
- for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) {
- if (block instanceof DependentCalldataBlock) {
- const dependencyBlockHashBuf = block.getDependency().computeHash();
- const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf);
- if (dependencyBlockHash in blocksByHash) {
- const blockWithSameHash = blocksByHash[dependencyBlockHash];
- if (blockWithSameHash !== block.getDependency()) {
- block.setAlias(blockWithSameHash);
- }
- }
- continue;
- }
-
- const blockHashBuf = block.computeHash();
- const blockHash = ethUtil.bufferToHex(blockHashBuf);
- if (!(blockHash in blocksByHash)) {
- blocksByHash[blockHash] = block;
- }
- }
- }
-
- public toHexString(): string {
- if (this._root === undefined) {
- throw new Error('expected root');
- }
-
- if (this._rules.optimize) {
- this.optimize();
- }
-
- const offsetQueue = Calldata._createQueue(this._root);
- let block: CalldataBlock | undefined;
- let offset = 0;
- for (block = offsetQueue.pop(); block !== undefined; block = offsetQueue.pop()) {
- block.setOffset(offset);
- offset += block.getSizeInBytes();
- }
-
- const hexValue = this._rules.annotate ? this._generateAnnotatedHexString() : this._generateCondensedHexString();
- return hexValue;
- }
-
- public getSelectorHex(): string {
- return this._selector;
- }
-
- public getSizeInBytes(): number {
- return this._sizeInBytes;
- }
-
- public setRoot(block: CalldataBlock): void {
- this._root = block;
- this._sizeInBytes += block.getSizeInBytes();
- }
-
- public setSelector(selector: string): void {
- this._selector = selector.startsWith('0x') ? selector : `$0x${selector}`;
- if (this._selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) {
- throw new Error(`Invalid selector '${this._selector}'`);
- }
- this._sizeInBytes += Constants.HEX_SELECTOR_LENGTH_IN_BYTES; // @TODO: Used to be += 8. Bad?
- }
-
- private _generateAnnotatedHexString(): string {
- let hexValue = `${this._selector}`;
- if (this._root === undefined) {
- throw new Error('expected root');
- }
-
- const valueQueue = Calldata._createQueue(this._root);
-
- let block: CalldataBlock | undefined;
- let offset = 0;
- const functionBlock = valueQueue.peek();
- const functionName: string = functionBlock === undefined ? '' : functionBlock.getName();
- for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) {
- // Process each block 1 word at a time
- const size = block.getSizeInBytes();
- const name = block.getName();
- const parentName = block.getParentName();
- const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, '');
-
- // Current offset
- let offsetStr = '';
-
- // If this block is empty then it's a newline
- const offsetPadding = 10;
- const valuePadding = 74;
- const namePadding = 80;
- const evmWordStartIndex = 0;
- const emptySize = 0;
- let value = '';
- let nameStr = '';
- let line = '';
- if (size === emptySize) {
- offsetStr = ' '.repeat(offsetPadding);
- value = ' '.repeat(valuePadding);
- nameStr = `### ${prettyName.padEnd(namePadding)}`;
- line = `\n${offsetStr}${value}${nameStr}`;
- } else {
- offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding);
- value = ethUtil
- .stripHexPrefix(
- ethUtil.bufferToHex(
- block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES),
- ),
- )
- .padEnd(valuePadding);
- if (block instanceof MemberCalldataBlock) {
- nameStr = `### ${prettyName.padEnd(namePadding)}`;
- line = `\n${offsetStr}${value}${nameStr}`;
- } else {
- nameStr = ` ${prettyName.padEnd(namePadding)}`;
- line = `${offsetStr}${value}${nameStr}`;
- }
- }
-
- for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) {
- offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding);
- value = ethUtil
- .stripHexPrefix(
- ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)),
- )
- .padEnd(valuePadding);
- nameStr = ' '.repeat(namePadding);
- line = `${line}\n${offsetStr}${value}${nameStr}`;
- }
-
- // Append to hex value
- hexValue = `${hexValue}\n${line}`;
- offset += size;
- }
-
- return hexValue;
- }
-
- private _generateCondensedHexString(): string {
- const selectorBuffer = ethUtil.toBuffer(this._selector);
- if (this._root === undefined) {
- throw new Error('expected root');
- }
-
- const valueQueue = Calldata._createQueue(this._root);
- const valueBufs: Buffer[] = [selectorBuffer];
- let block: CalldataBlock | undefined;
- for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) {
- valueBufs.push(block.toBuffer());
- }
-
- const combinedBuffers = Buffer.concat(valueBufs);
- const hexValue = ethUtil.bufferToHex(combinedBuffers);
- return hexValue;
- }
-}
-
-export class RawCalldata {
- private static readonly _INITIAL_OFFSET = 0;
- private readonly _value: Buffer;
- private readonly _selector: string;
- private readonly _scopes: Queue<number>;
- private _offset: number; // tracks current offset into raw calldata; used for parsing
-
- constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) {
- if (typeof value === 'string' && !value.startsWith('0x')) {
- throw new Error(`Expected raw calldata to start with '0x'`);
- }
- const valueBuf = ethUtil.toBuffer(value);
- if (hasSelectorPrefix) {
- this._selector = ethUtil.bufferToHex(
- valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES),
- );
- this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector
- } else {
- this._selector = '0x';
- this._value = valueBuf;
- }
-
- this._scopes = new Queue<number>();
- this._scopes.push(RawCalldata._INITIAL_OFFSET);
- this._offset = RawCalldata._INITIAL_OFFSET;
- }
-
- 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): void {
- this._offset = offsetInBytes;
- }
-
- public startScope(): void {
- this._scopes.pushFront(this._offset);
- }
-
- public endScope(): void {
- this._scopes.pop();
- }
-
- public getOffset(): number {
- return this._offset;
- }
-
- public toAbsoluteOffset(relativeOffset: number): 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;
- }
-}
diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts
new file mode 100644
index 000000000..5ac4c1fe7
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/calldata.ts
@@ -0,0 +1,224 @@
+
+import * as ethUtil from 'ethereumjs-util';
+import * as _ from 'lodash';
+
+import * as Constants from '../constants';
+import { Queue } from '../utils/queue';
+import { EncodingRules } from '../utils/rules';
+
+import { CalldataBlock } from './calldata_block';
+import * as CalldataBlocks from './calldata_blocks';
+
+export class Calldata {
+ private readonly _rules: EncodingRules;
+ private _selector: string;
+ private _sizeInBytes: number;
+ private _root: CalldataBlock | undefined;
+
+ private static _createQueue(block: CalldataBlock): Queue<CalldataBlock> {
+ const blockQueue = new Queue<CalldataBlock>();
+
+ // Base Case
+ if (!(block instanceof CalldataBlocks.MemberCalldataBlock)) {
+ blockQueue.push(block);
+ return blockQueue;
+ }
+
+ // This is a Member Block
+ const memberBlock = block;
+ _.eachRight(memberBlock.getMembers(), (member: CalldataBlock) => {
+ if (member instanceof CalldataBlocks.MemberCalldataBlock) {
+ blockQueue.mergeFront(Calldata._createQueue(member));
+ } else {
+ blockQueue.pushFront(member);
+ }
+ });
+
+ // Children
+ _.each(memberBlock.getMembers(), (member: CalldataBlock) => {
+ if (member instanceof CalldataBlocks.DependentCalldataBlock && member.getAlias() === undefined) {
+ const dependency = member.getDependency();
+ if (dependency instanceof CalldataBlocks.MemberCalldataBlock) {
+ blockQueue.merge(Calldata._createQueue(dependency));
+ } else {
+ blockQueue.push(dependency);
+ }
+ }
+ });
+
+ blockQueue.pushFront(memberBlock);
+ return blockQueue;
+ }
+
+ public constructor(rules: EncodingRules) {
+ this._rules = rules;
+ this._selector = '';
+ this._sizeInBytes = 0;
+ this._root = undefined;
+ }
+
+ public optimize(): void {
+ if (this._root === undefined) {
+ throw new Error('expected root');
+ }
+
+ const blocksByHash: { [key: string]: CalldataBlock } = {};
+
+ // 1. Create a queue of subtrees by hash
+ // Note that they are ordered the same as
+ const subtreeQueue = Calldata._createQueue(this._root);
+ let block: CalldataBlock | undefined;
+ for (block = subtreeQueue.popBack(); block !== undefined; block = subtreeQueue.popBack()) {
+ if (block instanceof CalldataBlocks.DependentCalldataBlock) {
+ const dependencyBlockHashBuf = block.getDependency().computeHash();
+ const dependencyBlockHash = ethUtil.bufferToHex(dependencyBlockHashBuf);
+ if (dependencyBlockHash in blocksByHash) {
+ const blockWithSameHash = blocksByHash[dependencyBlockHash];
+ if (blockWithSameHash !== block.getDependency()) {
+ block.setAlias(blockWithSameHash);
+ }
+ }
+ continue;
+ }
+
+ const blockHashBuf = block.computeHash();
+ const blockHash = ethUtil.bufferToHex(blockHashBuf);
+ if (!(blockHash in blocksByHash)) {
+ blocksByHash[blockHash] = block;
+ }
+ }
+ }
+
+ public toHexString(): string {
+ if (this._root === undefined) {
+ throw new Error('expected root');
+ }
+
+ if (this._rules.optimize) {
+ this.optimize();
+ }
+
+ const offsetQueue = Calldata._createQueue(this._root);
+ let block: CalldataBlock | undefined;
+ let offset = 0;
+ for (block = offsetQueue.pop(); block !== undefined; block = offsetQueue.pop()) {
+ block.setOffset(offset);
+ offset += block.getSizeInBytes();
+ }
+
+ const hexValue = this._rules.annotate ? this._generateAnnotatedHexString() : this._generateCondensedHexString();
+ return hexValue;
+ }
+
+ public getSelectorHex(): string {
+ return this._selector;
+ }
+
+ public getSizeInBytes(): number {
+ return this._sizeInBytes;
+ }
+
+ public setRoot(block: CalldataBlock): void {
+ this._root = block;
+ this._sizeInBytes += block.getSizeInBytes();
+ }
+
+ public setSelector(selector: string): void {
+ this._selector = selector.startsWith('0x') ? selector : `$0x${selector}`;
+ if (this._selector.length !== Constants.HEX_SELECTOR_LENGTH_IN_CHARS) {
+ throw new Error(`Invalid selector '${this._selector}'`);
+ }
+ this._sizeInBytes += Constants.HEX_SELECTOR_LENGTH_IN_BYTES; // @TODO: Used to be += 8. Bad?
+ }
+
+ private _generateAnnotatedHexString(): string {
+ let hexValue = `${this._selector}`;
+ if (this._root === undefined) {
+ throw new Error('expected root');
+ }
+
+ const valueQueue = Calldata._createQueue(this._root);
+
+ let block: CalldataBlock | undefined;
+ let offset = 0;
+ const functionBlock = valueQueue.peek();
+ const functionName: string = functionBlock === undefined ? '' : functionBlock.getName();
+ for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) {
+ // Process each block 1 word at a time
+ const size = block.getSizeInBytes();
+ const name = block.getName();
+ const parentName = block.getParentName();
+ const prettyName = name.replace(`${parentName}.`, '').replace(`${functionName}.`, '');
+
+ // Current offset
+ let offsetStr = '';
+
+ // If this block is empty then it's a newline
+ const offsetPadding = 10;
+ const valuePadding = 74;
+ const namePadding = 80;
+ const evmWordStartIndex = 0;
+ const emptySize = 0;
+ let value = '';
+ let nameStr = '';
+ let line = '';
+ if (size === emptySize) {
+ offsetStr = ' '.repeat(offsetPadding);
+ value = ' '.repeat(valuePadding);
+ nameStr = `### ${prettyName.padEnd(namePadding)}`;
+ line = `\n${offsetStr}${value}${nameStr}`;
+ } else {
+ offsetStr = `0x${offset.toString(Constants.HEX_BASE)}`.padEnd(offsetPadding);
+ value = ethUtil
+ .stripHexPrefix(
+ ethUtil.bufferToHex(
+ block.toBuffer().slice(evmWordStartIndex, Constants.EVM_WORD_WIDTH_IN_BYTES),
+ ),
+ )
+ .padEnd(valuePadding);
+ if (block instanceof CalldataBlocks.MemberCalldataBlock) {
+ nameStr = `### ${prettyName.padEnd(namePadding)}`;
+ line = `\n${offsetStr}${value}${nameStr}`;
+ } else {
+ nameStr = ` ${prettyName.padEnd(namePadding)}`;
+ line = `${offsetStr}${value}${nameStr}`;
+ }
+ }
+
+ for (let j = Constants.EVM_WORD_WIDTH_IN_BYTES; j < size; j += Constants.EVM_WORD_WIDTH_IN_BYTES) {
+ offsetStr = `0x${(offset + j).toString(Constants.HEX_BASE)}`.padEnd(offsetPadding);
+ value = ethUtil
+ .stripHexPrefix(
+ ethUtil.bufferToHex(block.toBuffer().slice(j, j + Constants.EVM_WORD_WIDTH_IN_BYTES)),
+ )
+ .padEnd(valuePadding);
+ nameStr = ' '.repeat(namePadding);
+ line = `${line}\n${offsetStr}${value}${nameStr}`;
+ }
+
+ // Append to hex value
+ hexValue = `${hexValue}\n${line}`;
+ offset += size;
+ }
+
+ return hexValue;
+ }
+
+ private _generateCondensedHexString(): string {
+ const selectorBuffer = ethUtil.toBuffer(this._selector);
+ if (this._root === undefined) {
+ throw new Error('expected root');
+ }
+
+ const valueQueue = Calldata._createQueue(this._root);
+ const valueBufs: Buffer[] = [selectorBuffer];
+ let block: CalldataBlock | undefined;
+ for (block = valueQueue.pop(); block !== undefined; block = valueQueue.pop()) {
+ valueBufs.push(block.toBuffer());
+ }
+
+ const combinedBuffers = Buffer.concat(valueBufs);
+ const hexValue = ethUtil.bufferToHex(combinedBuffers);
+ return hexValue;
+ }
+}
diff --git a/packages/utils/src/abi_encoder/calldata/calldata_block.ts b/packages/utils/src/abi_encoder/calldata/calldata_block.ts
new file mode 100644
index 000000000..35bd994e5
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/calldata_block.ts
@@ -0,0 +1,77 @@
+import * as ethUtil from 'ethereumjs-util';
+
+export abstract class CalldataBlock {
+ private readonly _signature: string;
+ private readonly _parentName: string;
+ private _name: string;
+ private _offsetInBytes: number;
+ private _headerSizeInBytes: number;
+ private _bodySizeInBytes: number;
+
+ constructor(
+ name: string,
+ signature: string,
+ parentName: string,
+ headerSizeInBytes: number,
+ bodySizeInBytes: number,
+ ) {
+ this._name = name;
+ this._signature = signature;
+ this._parentName = parentName;
+ this._offsetInBytes = 0;
+ this._headerSizeInBytes = headerSizeInBytes;
+ this._bodySizeInBytes = bodySizeInBytes;
+ }
+
+ protected _setHeaderSize(headerSizeInBytes: number): void {
+ this._headerSizeInBytes = headerSizeInBytes;
+ }
+
+ protected _setBodySize(bodySizeInBytes: number): void {
+ this._bodySizeInBytes = bodySizeInBytes;
+ }
+
+ protected _setName(name: string): void {
+ this._name = name;
+ }
+
+ public getName(): string {
+ return this._name;
+ }
+
+ public getParentName(): string {
+ return this._parentName;
+ }
+
+ public getSignature(): string {
+ return this._signature;
+ }
+ public getHeaderSizeInBytes(): number {
+ return this._headerSizeInBytes;
+ }
+
+ public getBodySizeInBytes(): number {
+ return this._bodySizeInBytes;
+ }
+
+ public getSizeInBytes(): number {
+ return this.getHeaderSizeInBytes() + this.getBodySizeInBytes();
+ }
+
+ public getOffsetInBytes(): number {
+ return this._offsetInBytes;
+ }
+
+ public setOffset(offsetInBytes: number): void {
+ this._offsetInBytes = offsetInBytes;
+ }
+
+ public computeHash(): Buffer {
+ const rawData = this.getRawData();
+ const hash = ethUtil.sha3(rawData);
+ return hash;
+ }
+
+ public abstract toBuffer(): Buffer;
+ public abstract getRawData(): Buffer;
+}
diff --git a/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts
new file mode 100644
index 000000000..37660ef21
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/calldata_blocks.ts
@@ -0,0 +1,3 @@
+export * from './dependent_calldata_block';
+export * from './member_calldata_block';
+export * from './payload_calldata_block'; \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts
new file mode 100644
index 000000000..d6870ec0b
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/dependent_calldata_block.ts
@@ -0,0 +1,59 @@
+import * as ethUtil from 'ethereumjs-util';
+
+import * as Constants from '../constants';
+
+import { CalldataBlock } from './calldata_block';
+
+export class DependentCalldataBlock extends CalldataBlock {
+ public static readonly RAW_DATA_START = new Buffer('<');
+ public static readonly RAW_DATA_END = new Buffer('>');
+ private static readonly _DEPENDENT_PAYLOAD_SIZE_IN_BYTES = 32;
+ private static readonly _EMPTY_HEADER_SIZE = 0;
+ private readonly _parent: CalldataBlock;
+ private readonly _dependency: CalldataBlock;
+ private _aliasFor: CalldataBlock | undefined;
+
+ constructor(name: string, signature: string, parentName: string, dependency: CalldataBlock, parent: CalldataBlock) {
+ const headerSizeInBytes = DependentCalldataBlock._EMPTY_HEADER_SIZE;
+ const bodySizeInBytes = DependentCalldataBlock._DEPENDENT_PAYLOAD_SIZE_IN_BYTES;
+ super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes);
+ this._parent = parent;
+ this._dependency = dependency;
+ this._aliasFor = undefined;
+ }
+
+ public toBuffer(): Buffer {
+ const destinationOffset =
+ this._aliasFor !== undefined ? this._aliasFor.getOffsetInBytes() : this._dependency.getOffsetInBytes();
+ const parentOffset = this._parent.getOffsetInBytes();
+ const parentHeaderSize = this._parent.getHeaderSizeInBytes();
+ const pointer: number = destinationOffset - (parentOffset + parentHeaderSize);
+ const pointerBuf = ethUtil.toBuffer(`0x${pointer.toString(Constants.HEX_BASE)}`);
+ const evmWordWidthInBytes = 32;
+ const pointerBufPadded = ethUtil.setLengthLeft(pointerBuf, evmWordWidthInBytes);
+ return pointerBufPadded;
+ }
+
+ public getDependency(): CalldataBlock {
+ return this._dependency;
+ }
+
+ public setAlias(block: CalldataBlock): void {
+ this._aliasFor = block;
+ this._setName(`${this.getName()} (alias for ${block.getName()})`);
+ }
+
+ public getAlias(): CalldataBlock | undefined {
+ return this._aliasFor;
+ }
+
+ public getRawData(): Buffer {
+ const dependencyRawData = this._dependency.getRawData();
+ const rawDataComponents: Buffer[] = [];
+ rawDataComponents.push(DependentCalldataBlock.RAW_DATA_START);
+ rawDataComponents.push(dependencyRawData);
+ rawDataComponents.push(DependentCalldataBlock.RAW_DATA_END);
+ const rawData = Buffer.concat(rawDataComponents);
+ return rawData;
+ }
+} \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/calldata/index.ts b/packages/utils/src/abi_encoder/calldata/index.ts
new file mode 100644
index 000000000..0e1f57a2d
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/index.ts
@@ -0,0 +1,6 @@
+export * from './calldata_block';
+export * from './dependent_calldata_block';
+export * from './payload_calldata_block';
+export * from './member_calldata_block';
+export * from './calldata';
+export * from './raw_calldata'; \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts
new file mode 100644
index 000000000..e9bcb2f34
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/member_calldata_block.ts
@@ -0,0 +1,48 @@
+import * as _ from 'lodash';
+
+import { CalldataBlock } from './calldata_block';
+
+export class MemberCalldataBlock extends CalldataBlock {
+ private _header: Buffer | undefined;
+ private _members: CalldataBlock[];
+
+ constructor(name: string, signature: string, parentName: string) {
+ super(name, signature, parentName, 0, 0);
+ this._members = [];
+ this._header = undefined;
+ }
+
+ public getRawData(): Buffer {
+ const rawDataComponents: Buffer[] = [];
+ if (this._header !== undefined) {
+ rawDataComponents.push(this._header);
+ }
+ _.each(this._members, (member: CalldataBlock) => {
+ const memberBuffer = member.getRawData();
+ rawDataComponents.push(memberBuffer);
+ });
+
+ const rawData = Buffer.concat(rawDataComponents);
+ return rawData;
+ }
+
+ public setMembers(members: CalldataBlock[]): void {
+ this._members = members;
+ }
+
+ public setHeader(header: Buffer): void {
+ this._setHeaderSize(header.byteLength);
+ this._header = header;
+ }
+
+ public toBuffer(): Buffer {
+ if (this._header !== undefined) {
+ return this._header;
+ }
+ return new Buffer('');
+ }
+
+ public getMembers(): CalldataBlock[] {
+ return this._members;
+ }
+} \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts
new file mode 100644
index 000000000..81a88bc19
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/payload_calldata_block.ts
@@ -0,0 +1,20 @@
+import { CalldataBlock } from './calldata_block';
+
+export class PayloadCalldataBlock extends CalldataBlock {
+ private readonly _payload: Buffer;
+
+ constructor(name: string, signature: string, parentName: string, payload: Buffer) {
+ const headerSizeInBytes = 0;
+ const bodySizeInBytes = payload.byteLength;
+ super(name, signature, parentName, headerSizeInBytes, bodySizeInBytes);
+ this._payload = payload;
+ }
+
+ public toBuffer(): Buffer {
+ return this._payload;
+ }
+
+ public getRawData(): Buffer {
+ return this._payload;
+ }
+} \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/calldata/raw_calldata.ts b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts
new file mode 100644
index 000000000..8b946ee4b
--- /dev/null
+++ b/packages/utils/src/abi_encoder/calldata/raw_calldata.ts
@@ -0,0 +1,82 @@
+import * as ethUtil from 'ethereumjs-util';
+
+import * as Constants from '../constants';
+import { Queue } from '../utils/queue';
+
+export class RawCalldata {
+ private static readonly _INITIAL_OFFSET = 0;
+ private readonly _value: Buffer;
+ private readonly _selector: string;
+ private readonly _scopes: Queue<number>;
+ private _offset: number; // tracks current offset into raw calldata; used for parsing
+
+ constructor(value: string | Buffer, hasSelectorPrefix: boolean = true) {
+ if (typeof value === 'string' && !value.startsWith('0x')) {
+ throw new Error(`Expected raw calldata to start with '0x'`);
+ }
+ const valueBuf = ethUtil.toBuffer(value);
+ if (hasSelectorPrefix) {
+ this._selector = ethUtil.bufferToHex(
+ valueBuf.slice(Constants.HEX_SELECTOR_BYTE_OFFSET_IN_CALLDATA, Constants.HEX_SELECTOR_LENGTH_IN_BYTES),
+ );
+ this._value = valueBuf.slice(Constants.HEX_SELECTOR_LENGTH_IN_BYTES); // disregard selector
+ } else {
+ this._selector = '0x';
+ this._value = valueBuf;
+ }
+
+ this._scopes = new Queue<number>();
+ this._scopes.push(RawCalldata._INITIAL_OFFSET);
+ this._offset = RawCalldata._INITIAL_OFFSET;
+ }
+
+ 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): void {
+ this._offset = offsetInBytes;
+ }
+
+ public startScope(): void {
+ this._scopes.pushFront(this._offset);
+ }
+
+ public endScope(): void {
+ this._scopes.pop();
+ }
+
+ public getOffset(): number {
+ return this._offset;
+ }
+
+ public toAbsoluteOffset(relativeOffset: number): 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/utils/src/abi_encoder/data_type.ts b/packages/utils/src/abi_encoder/data_type.ts
index 9890619e5..0fbb9165a 100644
--- a/packages/utils/src/abi_encoder/data_type.ts
+++ b/packages/utils/src/abi_encoder/data_type.ts
@@ -9,14 +9,14 @@ import * as Constants from './constants';
import {
Calldata,
CalldataBlock,
- DecodingRules,
DependentCalldataBlock,
- EncodingRules,
MemberCalldataBlock,
PayloadCalldataBlock,
RawCalldata,
} from './calldata';
+import { DecodingRules, EncodingRules } from './utils/rules';
+
export interface DataTypeFactory {
create: (dataItem: DataItem, parentDataType?: DataType) => DataType;
mapDataItemToDataType: (dataItem: DataItem) => DataType;
diff --git a/packages/utils/src/abi_encoder/evm_data_types/method.ts b/packages/utils/src/abi_encoder/evm_data_types/method.ts
index e8e717bf1..c4e6ee93a 100644
--- a/packages/utils/src/abi_encoder/evm_data_types/method.ts
+++ b/packages/utils/src/abi_encoder/evm_data_types/method.ts
@@ -2,9 +2,10 @@ import { DataItem, MethodAbi } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
-import { DecodingRules, EncodingRules, RawCalldata } from '../calldata';
+import { RawCalldata } from '../calldata';
import * as Constants from '../constants';
import { DataType, DataTypeFactory, MemberDataType } from '../data_type';
+import { DecodingRules, EncodingRules } from '../utils/rules';
import { StaticBytes } from './static_bytes';
import { Tuple } from './tuple';
diff --git a/packages/utils/src/abi_encoder/index.ts b/packages/utils/src/abi_encoder/index.ts
index a62569fab..ea037b40a 100644
--- a/packages/utils/src/abi_encoder/index.ts
+++ b/packages/utils/src/abi_encoder/index.ts
@@ -1,2 +1,2 @@
-export { EncodingRules, DecodingRules } from './calldata';
+export { EncodingRules, DecodingRules } from './utils/rules';
export * from './evm_data_type_factory';
diff --git a/packages/utils/src/abi_encoder/utils/queue.ts b/packages/utils/src/abi_encoder/utils/queue.ts
new file mode 100644
index 000000000..2e27afd45
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/queue.ts
@@ -0,0 +1,39 @@
+export class Queue<T> {
+ private _store: T[] = [];
+
+ public push(val: T): void {
+ this._store.push(val);
+ }
+
+ public pushFront(val: T): void {
+ this._store.unshift(val);
+ }
+
+ public pop(): T | undefined {
+ return this._store.shift();
+ }
+
+ public popBack(): T | undefined {
+ if (this._store.length === 0) {
+ return undefined;
+ }
+ const backElement = this._store.splice(-1, 1)[0];
+ return backElement;
+ }
+
+ public merge(q: Queue<T>): void {
+ this._store = this._store.concat(q._store);
+ }
+
+ public mergeFront(q: Queue<T>): void {
+ this._store = q._store.concat(this._store);
+ }
+
+ public getStore(): T[] {
+ return this._store;
+ }
+
+ public peek(): T | undefined {
+ return this._store.length >= 0 ? this._store[0] : undefined;
+ }
+} \ No newline at end of file
diff --git a/packages/utils/src/abi_encoder/utils/rules.ts b/packages/utils/src/abi_encoder/utils/rules.ts
new file mode 100644
index 000000000..cf97ef6da
--- /dev/null
+++ b/packages/utils/src/abi_encoder/utils/rules.ts
@@ -0,0 +1,8 @@
+export interface DecodingRules {
+ structsAsObjects: boolean;
+}
+
+export interface EncodingRules {
+ optimize?: boolean;
+ annotate?: boolean;
+} \ No newline at end of file