blob: dd9d47def846707f4dde915d81226d1d62362cc9 (
plain) (
tree)
|
|
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import * as Constants from '../utils/constants';
import { EncodingRules } from '../utils/rules';
import * as CalldataBlocks from './blocks';
import { CalldataBlock } from './calldata_block';
import { CalldataIterator, ReverseCalldataIterator } from './iterator';
export class Calldata {
private readonly _rules: EncodingRules;
private _selector: string;
private _sizeInBytes: number;
private _root: CalldataBlock | undefined;
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 iterator = new ReverseCalldataIterator(this._root);
let block: CalldataBlock | undefined;
while (block = iterator.next()) {
if (block instanceof CalldataBlocks.Pointer) {
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 iterator = new CalldataIterator(this._root);
let block: CalldataBlock | undefined;
let offset = 0;
while (block = iterator.next()) {
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 iterator = new CalldataIterator(this._root);
let block: CalldataBlock | undefined;
let offset = 0;
const functionName: string = this._root.getName();
while (block = iterator.next()) {
// 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.Set) {
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 iterator = new CalldataIterator(this._root);
const valueBufs: Buffer[] = [selectorBuffer];
let block: CalldataBlock | undefined;
while (block = iterator.next()) {
valueBufs.push(block.toBuffer());
}
const combinedBuffers = Buffer.concat(valueBufs);
const hexValue = ethUtil.bufferToHex(combinedBuffers);
return hexValue;
}
}
|