diff options
author | Hsuan Lee <hsuan@cobinhood.com> | 2019-01-19 18:42:04 +0800 |
---|---|---|
committer | Hsuan Lee <hsuan@cobinhood.com> | 2019-01-19 18:42:04 +0800 |
commit | 7ae38906926dc09bc10670c361af0d2bf0050426 (patch) | |
tree | 5fb10ae366b987db09e4ddb4bc3ba0f75404ad08 /packages/utils/src/abi_encoder/calldata/calldata.ts | |
parent | b5fd3c72a08aaa6957917d74c333387a16edf66b (diff) | |
download | dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.tar dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.tar.gz dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.tar.bz2 dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.tar.lz dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.tar.xz dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.tar.zst dexon-sol-tools-7ae38906926dc09bc10670c361af0d2bf0050426.zip |
Update dependency packages
Diffstat (limited to 'packages/utils/src/abi_encoder/calldata/calldata.ts')
-rw-r--r-- | packages/utils/src/abi_encoder/calldata/calldata.ts | 245 |
1 files changed, 0 insertions, 245 deletions
diff --git a/packages/utils/src/abi_encoder/calldata/calldata.ts b/packages/utils/src/abi_encoder/calldata/calldata.ts deleted file mode 100644 index b08fb71ce..000000000 --- a/packages/utils/src/abi_encoder/calldata/calldata.ts +++ /dev/null @@ -1,245 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { constants } from '../utils/constants'; -import { EncodingRules } from '../utils/rules'; - -import { PointerCalldataBlock } from './blocks/pointer'; -import { SetCalldataBlock } from './blocks/set'; -import { CalldataBlock } from './calldata_block'; -import { CalldataIterator, ReverseCalldataIterator } from './iterator'; - -export class Calldata { - private readonly _rules: EncodingRules; - private _selector: string; - private _root: CalldataBlock | undefined; - - public constructor(rules: EncodingRules) { - this._rules = rules; - this._selector = ''; - this._root = undefined; - } - /** - * Sets the root calldata block. This block usually corresponds to a Method. - */ - public setRoot(block: CalldataBlock): void { - this._root = block; - } - /** - * Sets the selector to be prepended onto the calldata. - * If the root block was created by a Method then a selector will likely be set. - */ - public setSelector(selector: string): void { - if (!_.startsWith(selector, '0x')) { - throw new Error(`Expected selector to be hex. Missing prefix '0x'`); - } else if (selector.length !== constants.HEX_SELECTOR_LENGTH_IN_CHARS) { - throw new Error(`Invalid selector '${selector}'`); - } - this._selector = selector; - } - /** - * Iterates through the calldata blocks, starting from the root block, to construct calldata as a hex string. - * If the `optimize` flag is set then this calldata will be condensed, to save gas. - * If the `annotate` flag is set then this will return human-readable calldata. - * If the `annotate` flag is *not* set then this will return EVM-compatible calldata. - */ - public toString(): string { - // Sanity check: root block must be set - if (_.isUndefined(this._root)) { - throw new Error('expected root'); - } - // Optimize, if flag set - if (this._rules.shouldOptimize) { - this._optimize(); - } - // Set offsets - const iterator = new CalldataIterator(this._root); - let offset = 0; - for (const block of iterator) { - block.setOffset(offset); - offset += block.getSizeInBytes(); - } - // Generate hex string - const hexString = this._rules.shouldAnnotate - ? this._toHumanReadableCallData() - : this._toEvmCompatibeCallDataHex(); - return hexString; - } - /** - * There are three types of calldata blocks: Blob, Set and Pointer. - * Scenarios arise where distinct pointers resolve to identical values. - * We optimize by keeping only one such instance of the identical value, and redirecting all pointers here. - * We keep the last such duplicate value because pointers can only be positive (they cannot point backwards). - * - * Example #1: - * function f(string[], string[]) - * f(["foo", "bar", "blitz"], ["foo", "bar", "blitz"]) - * The array ["foo", "bar", "blitz"] will only be included in the calldata once. - * - * Example #2: - * function f(string[], string) - * f(["foo", "bar", "blitz"], "foo") - * The string "foo" will only be included in the calldata once. - * - * Example #3: - * function f((string, uint, bytes), string, uint, bytes) - * f(("foo", 5, "0x05"), "foo", 5, "0x05") - * The string "foo" and bytes "0x05" will only be included in the calldata once. - * The duplicate `uint 5` values cannot be optimized out because they are static values (no pointer points to them). - * - * @TODO #1: - * This optimization strategy handles blocks that are exact duplicates of one another. - * But what if some block is a combination of two other blocks? Or a subset of another block? - * This optimization problem is not much different from the current implemetation. - * Instead of tracking "observed" hashes, at each node we would simply do pattern-matching on the calldata. - * This strategy would be applied after assigning offsets to the tree, rather than before (as in this strategy). - * Note that one consequence of this strategy is pointers may resolve to offsets that are not word-aligned. - * This shouldn't be a problem but further investigation should be done. - * - * @TODO #2: - * To be done as a follow-up to @TODO #1. - * Since we optimize from the bottom-up, we could be affecting the outcome of a later potential optimization. - * For example, what if by removing one duplicate value we miss out on optimizing another block higher in the tree. - * To handle this case, at each node we can store a candidate optimization in a priority queue (sorted by calldata size). - * At the end of traversing the tree, the candidate at the front of the queue will be the most optimal output. - * - */ - private _optimize(): void { - // Step 1/1 Create a reverse iterator (starts from the end of the calldata to the beginning) - if (_.isUndefined(this._root)) { - throw new Error('expected root'); - } - const iterator = new ReverseCalldataIterator(this._root); - // Step 2/2 Iterate over each block, keeping track of which blocks have been seen and pruning redundant blocks. - const blocksByHash: { [key: string]: CalldataBlock } = {}; - for (const block of iterator) { - // If a block is a pointer and its value has already been observed, then update - // the pointer to resolve to the existing value. - if (block instanceof PointerCalldataBlock) { - 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; - } - // This block has not been seen. Record its hash. - const blockHashBuf = block.computeHash(); - const blockHash = ethUtil.bufferToHex(blockHashBuf); - if (!(blockHash in blocksByHash)) { - blocksByHash[blockHash] = block; - } - } - } - private _toEvmCompatibeCallDataHex(): string { - // Sanity check: must have a root block. - if (_.isUndefined(this._root)) { - throw new Error('expected root'); - } - // Construct an array of buffers (one buffer for each block). - const selectorBuffer = ethUtil.toBuffer(this._selector); - const valueBufs: Buffer[] = [selectorBuffer]; - const iterator = new CalldataIterator(this._root); - for (const block of iterator) { - valueBufs.push(block.toBuffer()); - } - // Create hex from buffer array. - const combinedBuffers = Buffer.concat(valueBufs); - const hexValue = ethUtil.bufferToHex(combinedBuffers); - return hexValue; - } - /** - * Returns human-readable calldata. - * - * Example: - * simpleFunction(string[], string[]) - * strings = ["Hello", "World"] - * simpleFunction(strings, strings) - * - * Output: - * 0xbb4f12e3 - * ### simpleFunction - * 0x0 0000000000000000000000000000000000000000000000000000000000000040 ptr<array1> (alias for array2) - * 0x20 0000000000000000000000000000000000000000000000000000000000000040 ptr<array2> - * - * 0x40 0000000000000000000000000000000000000000000000000000000000000002 ### array2 - * 0x60 0000000000000000000000000000000000000000000000000000000000000040 ptr<array2[0]> - * 0x80 0000000000000000000000000000000000000000000000000000000000000080 ptr<array2[1]> - * 0xa0 0000000000000000000000000000000000000000000000000000000000000005 array2[0] - * 0xc0 48656c6c6f000000000000000000000000000000000000000000000000000000 - * 0xe0 0000000000000000000000000000000000000000000000000000000000000005 array2[1] - * 0x100 576f726c64000000000000000000000000000000000000000000000000000000 - */ - private _toHumanReadableCallData(): string { - // Sanity check: must have a root block. - if (_.isUndefined(this._root)) { - throw new Error('expected root'); - } - // Constants for constructing annotated string - const offsetPadding = 10; - const valuePadding = 74; - const namePadding = 80; - const evmWordStartIndex = 0; - const emptySize = 0; - // Construct annotated calldata - let hexValue = `${this._selector}`; - let offset = 0; - const functionName: string = this._root.getName(); - const iterator = new CalldataIterator(this._root); - for (const block of iterator) { - // 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}.`, ''); - // Resulting line will be <offsetStr><valueStr><nameStr> - let offsetStr = ''; - let valueStr = ''; - let nameStr = ''; - let lineStr = ''; - if (size === emptySize) { - // This is a Set block with no header. - // For example, a tuple or an array with a defined length. - offsetStr = ' '.repeat(offsetPadding); - valueStr = ' '.repeat(valuePadding); - nameStr = `### ${prettyName.padEnd(namePadding)}`; - lineStr = `\n${offsetStr}${valueStr}${nameStr}`; - } else { - // This block has at least one word of value. - offsetStr = `0x${offset.toString(constants.HEX_BASE)}`.padEnd(offsetPadding); - valueStr = ethUtil - .stripHexPrefix( - ethUtil.bufferToHex( - block.toBuffer().slice(evmWordStartIndex, constants.EVM_WORD_WIDTH_IN_BYTES), - ), - ) - .padEnd(valuePadding); - if (block instanceof SetCalldataBlock) { - nameStr = `### ${prettyName.padEnd(namePadding)}`; - lineStr = `\n${offsetStr}${valueStr}${nameStr}`; - } else { - nameStr = ` ${prettyName.padEnd(namePadding)}`; - lineStr = `${offsetStr}${valueStr}${nameStr}`; - } - } - // This block has a value that is more than 1 word. - 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); - valueStr = ethUtil - .stripHexPrefix( - ethUtil.bufferToHex(block.toBuffer().slice(j, j + constants.EVM_WORD_WIDTH_IN_BYTES)), - ) - .padEnd(valuePadding); - nameStr = ' '.repeat(namePadding); - lineStr = `${lineStr}\n${offsetStr}${valueStr}${nameStr}`; - } - // Append to hex value - hexValue = `${hexValue}\n${lineStr}`; - offset += size; - } - return hexValue; - } -} |