From 04d8f46ff31695a577cb8b6cd3fd17848c599f1f Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 22 Feb 2019 16:16:03 -0800 Subject: [PATCH] Major sol-profiler overhaul --- .../sol-coverage/src/coverage_subprovider.ts | 6 +- packages/sol-profiler/CHANGELOG.json | 23 ++++- packages/sol-profiler/package.json | 4 +- packages/sol-profiler/src/cost_utils.ts | 94 +++++++++++++++++++ .../sol-profiler/src/profiler_subprovider.ts | 69 +++++++++++++- packages/sol-tracing-utils/package.json | 2 +- .../sol_compiler_artifact_adapter.ts | 2 + packages/sol-tracing-utils/src/index.ts | 9 +- .../sol-tracing-utils/src/trace_collector.ts | 49 ++++++---- .../src/trace_info_subprovider.ts | 54 ++++++++--- packages/sol-tracing-utils/src/types.ts | 20 +++- packages/sol-tracing-utils/src/utils.ts | 34 ++++++- yarn.lock | 15 +-- 13 files changed, 320 insertions(+), 61 deletions(-) create mode 100644 packages/sol-profiler/src/cost_utils.ts diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts index 2d92b25cab..6997b1d78d 100644 --- a/packages/sol-coverage/src/coverage_subprovider.ts +++ b/packages/sol-coverage/src/coverage_subprovider.ts @@ -11,8 +11,8 @@ import { StatementCoverage, StatementDescription, Subtrace, + SubTraceInfo, TraceCollector, - TraceInfo, TraceInfoSubprovider, utils, } from '@0x/sol-tracing-utils'; @@ -39,8 +39,8 @@ export class CoverageSubprovider extends TraceInfoSubprovider { super(defaultFromAddress, traceCollectionSubproviderConfig); this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler); } - protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { - await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo); + protected async _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise { + await this._coverageCollector.computeSingleTraceCoverageAsync(subTraceInfo); } /** * Write the test coverage results to a file in Istanbul format. diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index f9306fc663..f2002d2dce 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -1,4 +1,25 @@ [ + { + "version": "3.0.0", + "changes": [ + { + "note": "Big Sol-profiler overhaul. It now has a bunch of new features", + "pr": "TODO" + }, + { + "note": "Added a CLI interface for reporting non line-based profiling info", + "pr": "TODO" + }, + { + "note": "Add memory consumption analysis", + "pr": "TODO" + }, + { + "note": "Add calldata analysis", + "pr": "TODO" + } + ] + }, { "timestamp": 1549733923, "version": "2.0.4", @@ -81,4 +102,4 @@ } ] } -] +] \ No newline at end of file diff --git a/packages/sol-profiler/package.json b/packages/sol-profiler/package.json index acbff2a3fb..ddb7ac9dba 100644 --- a/packages/sol-profiler/package.json +++ b/packages/sol-profiler/package.json @@ -33,6 +33,8 @@ "@0x/subproviders": "^3.0.3", "@0x/typescript-typings": "^4.0.0", "ethereum-types": "^2.0.0", + "ethereumjs-util": "^5.1.1", + "@0x/utils": "^4.1.0", "lodash": "^4.17.11", "web3-provider-engine": "14.0.6" }, @@ -49,4 +51,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/packages/sol-profiler/src/cost_utils.ts b/packages/sol-profiler/src/cost_utils.ts new file mode 100644 index 0000000000..5ba797157d --- /dev/null +++ b/packages/sol-profiler/src/cost_utils.ts @@ -0,0 +1,94 @@ +import { TraceInfo } from '@0x/sol-tracing-utils'; +import { logUtils } from '@0x/utils'; +import { OpCode } from 'ethereum-types'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; + +const ZERO_BYTE_CALL_DATA_COST = 4; +const NON_ZERO_BYTE_CALL_DATA_COST = 68; +const WORD_SIZE = 32; +const G_MEMORY = 3; +const G_QUAD_COEF = 512; +const HEX_BASE = 16; +const G_COPY = 3; + +export const costUtils = { + reportCallDataCost(traceInfo: TraceInfo): number { + if (_.isUndefined(traceInfo.data)) { + // No call data to report + return 0; + } + const callData = traceInfo.data; + const callDataBuf = Buffer.from(stripHexPrefix(callData), 'hex'); + const { true: zeroBytesCountIfExist, false: nonZeroBytesCountIfExist } = _.countBy( + callDataBuf, + byte => byte === 0, + ); + const zeroBytesCost = (zeroBytesCountIfExist || 0) * ZERO_BYTE_CALL_DATA_COST; + const nonZeroBytesCost = (nonZeroBytesCountIfExist || 0) * NON_ZERO_BYTE_CALL_DATA_COST; + const callDataCost = zeroBytesCost + nonZeroBytesCost; + logUtils.logHeader('Call data breakdown', '-'); + logUtils.table({ + 'call data size (bytes)': callData.length, + callDataCost, + zeroBytesCost, + nonZeroBytesCost, + zeroBytesCountIfExist, + nonZeroBytesCountIfExist, + }); + return callDataCost; + }, + reportMemoryCost(traceInfo: TraceInfo): number { + const structLogs = traceInfo.trace.structLogs; + const MEMORY_OPCODES = [OpCode.MLoad, OpCode.MStore, OpCode.MStore8]; + const CALL_DATA_OPCODES = [OpCode.CallDataCopy]; + const memoryLogs = _.filter(structLogs, structLog => + _.includes([...MEMORY_OPCODES, ...CALL_DATA_OPCODES], structLog.op), + ); + const memoryLocationsAccessed = _.map(memoryLogs, structLog => { + if (_.includes(CALL_DATA_OPCODES, structLog.op)) { + const memOffset = parseInt(structLog.stack[0], HEX_BASE); + const length = parseInt(structLog.stack[2], HEX_BASE); + return memOffset + length; + } else { + return parseInt(structLog.stack[0], HEX_BASE); + } + }); + const highestMemoryLocationAccessed = _.max(memoryLocationsAccessed); + return costUtils._printMemoryCost(highestMemoryLocationAccessed); + }, + reportCopyingCost(traceInfo: TraceInfo): number { + const structLogs = traceInfo.trace.structLogs; + const COPY_OPCODES = [OpCode.CallDataCopy]; + const copyLogs = _.filter(structLogs, structLog => _.includes(COPY_OPCODES, structLog.op)); + const copyCosts = _.map(copyLogs, structLog => { + const length = parseInt(structLog.stack[2], HEX_BASE); + return Math.ceil(length / WORD_SIZE) * G_COPY; + }); + return _.sum(copyCosts); + }, + reportOpcodesCost(traceInfo: TraceInfo): number { + const structLogs = traceInfo.trace.structLogs; + const gasCosts = _.map(structLogs, structLog => structLog.gasCost); + const gasCost = _.sum(gasCosts); + return gasCost; + }, + _printMemoryCost(highestMemoryLocationAccessed?: number): number { + if (_.isUndefined(highestMemoryLocationAccessed)) { + return 0; + } + const memoryWordsUsed = Math.ceil((highestMemoryLocationAccessed + WORD_SIZE) / WORD_SIZE); + const linearMemoryCost = G_MEMORY * memoryWordsUsed; + const quadraticMemoryCost = Math.floor((memoryWordsUsed * memoryWordsUsed) / G_QUAD_COEF); + const memoryCost = linearMemoryCost + quadraticMemoryCost; + logUtils.logHeader('Memory breakdown', '-'); + logUtils.table({ + 'memoryCost = linearMemoryCost + quadraticMemoryCost': memoryCost, + linearMemoryCost, + quadraticMemoryCost, + highestMemoryLocationAccessed, + memoryWordsUsed, + }); + return memoryCost; + }, +}; diff --git a/packages/sol-profiler/src/profiler_subprovider.ts b/packages/sol-profiler/src/profiler_subprovider.ts index 9f195f7689..67dbcb18ac 100644 --- a/packages/sol-profiler/src/profiler_subprovider.ts +++ b/packages/sol-profiler/src/profiler_subprovider.ts @@ -1,5 +1,3 @@ -import * as _ from 'lodash'; - import { AbstractArtifactAdapter, collectCoverageEntries, @@ -8,11 +6,20 @@ import { SingleFileSubtraceHandler, SourceRange, Subtrace, + SubTraceInfo, TraceCollector, TraceInfo, TraceInfoSubprovider, utils, } from '@0x/sol-tracing-utils'; +import { logUtils } from '@0x/utils'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { costUtils } from './cost_utils'; + +const CREATE_COST = 32000; +const DEPLOYED_BYTE_COST = 200; /** * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. @@ -35,8 +42,64 @@ export class ProfilerSubprovider extends TraceInfoSubprovider { super(defaultFromAddress, traceCollectionSubproviderConfig); this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler); } + protected async _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise { + await this._profilerCollector.computeSingleTraceCoverageAsync(subTraceInfo); + } + // tslint:disable prefer-function-over-method protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { - await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo); + const receipt = await this._web3Wrapper.getTransactionReceiptIfExistsAsync( + traceInfo.txHash, + ); + if (_.isUndefined(receipt)) { + return; + } + const BASE_COST = 21000; + if (receipt.gasUsed === BASE_COST) { + // Value transfer + return; + } + logUtils.header(`Profiling data for ${traceInfo.txHash}`); + traceInfo.trace.structLogs = utils.normalizeStructLogs(traceInfo.trace.structLogs); + const callDataCost = costUtils.reportCallDataCost(traceInfo); + const memoryCost = costUtils.reportMemoryCost(traceInfo); + const opcodesCost = costUtils.reportOpcodesCost(traceInfo); + const dataCopyingCost = costUtils.reportCopyingCost(traceInfo); + const newContractCost = CREATE_COST; + const transactionBaseCost = BASE_COST; + let totalCost = callDataCost + opcodesCost + BASE_COST; + logUtils.header('Final breakdown', '-'); + if (!_.isNull(receipt.contractAddress)) { + const code = await this._web3Wrapper.getContractCodeAsync(receipt.contractAddress); + const codeBuff = Buffer.from(stripHexPrefix(code), 'hex'); + const codeLength = codeBuff.length; + const contractSizeCost = codeLength * DEPLOYED_BYTE_COST; + totalCost += contractSizeCost + CREATE_COST; + logUtils.table({ + 'totalCost = callDataCost + opcodesCost + transactionBaseCost + newContractCost + contractSizeCost': totalCost, + callDataCost, + 'opcodesCost (including memoryCost and dataCopyingCost)': opcodesCost, + memoryCost, + dataCopyingCost, + transactionBaseCost, + contractSizeCost, + newContractCost, + }); + } else { + logUtils.table({ + 'totalCost = callDataCost + opcodesCost + transactionBaseCost': totalCost, + callDataCost, + 'opcodesCost (including memoryCost and dataCopyingCost)': opcodesCost, + memoryCost, + dataCopyingCost, + transactionBaseCost, + }); + } + const unknownGas = receipt.gasUsed - totalCost; + if (unknownGas !== 0) { + logUtils.warn( + `Unable to find the cause for ${unknownGas} gas. It's most probably an issue in sol-profiler. Please report on Github.`, + ); + } } /** * Write the test profiler results to a file in Istanbul format. diff --git a/packages/sol-tracing-utils/package.json b/packages/sol-tracing-utils/package.json index 4d22107f32..c436b12f23 100644 --- a/packages/sol-tracing-utils/package.json +++ b/packages/sol-tracing-utils/package.json @@ -87,4 +87,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts index bfd3a504a5..025f332c3e 100644 --- a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts +++ b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts @@ -59,6 +59,8 @@ export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { sourceCodes[value.id] = source.source; }); const contractData = { + abi: artifact.compilerOutput.abi, + name: artifact.contractName, sourceCodes, sources, bytecode: artifact.compilerOutput.evm.bytecode.object, diff --git a/packages/sol-tracing-utils/src/index.ts b/packages/sol-tracing-utils/src/index.ts index fdf024ae02..86fc299fee 100644 --- a/packages/sol-tracing-utils/src/index.ts +++ b/packages/sol-tracing-utils/src/index.ts @@ -12,7 +12,7 @@ export { BranchCoverage, BranchDescription, Subtrace, - TraceInfo, + SubTraceInfo, Coverage, LineColumn, LineCoverage, @@ -24,9 +24,10 @@ export { FnMap, OffsetToLocation, StatementMap, - TraceInfoBase, - TraceInfoExistingContract, - TraceInfoNewContract, + TraceInfo, + SubTraceInfoBase, + SubTraceInfoExistingContract, + SubTraceInfoNewContract, Sources, SourceCodes, } from './types'; diff --git a/packages/sol-tracing-utils/src/trace_collector.ts b/packages/sol-tracing-utils/src/trace_collector.ts index 2a1f15b838..ec9ea34b78 100644 --- a/packages/sol-tracing-utils/src/trace_collector.ts +++ b/packages/sol-tracing-utils/src/trace_collector.ts @@ -15,9 +15,9 @@ import { Coverage, SourceRange, Subtrace, - TraceInfo, - TraceInfoExistingContract, - TraceInfoNewContract, + SubTraceInfo, + SubTraceInfoExistingContract, + SubTraceInfoNewContract, } from './types'; import { utils } from './utils'; @@ -62,32 +62,43 @@ export class TraceCollector { await mkdirpAsync('coverage'); fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); } - public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { + public async getContractDataByTraceInfoIfExistsAsync( + address: string, + bytecode: string, + isContractCreation: boolean, + ): Promise { if (_.isUndefined(this._contractsData)) { this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); } - const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; - const bytecode = isContractCreation - ? (traceInfo as TraceInfoNewContract).bytecode - : (traceInfo as TraceInfoExistingContract).runtimeBytecode; const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); if (_.isUndefined(contractData)) { - const shortenHex = (hex: string) => { - /** - * Length chooses so that both error messages are of the same length - * and it's enough data to figure out which artifact has a problem. - */ - const length = 18; - return `${hex.substr(0, length + 2)}...${hex.substr(hex.length - length, length)}`; - }; + /** + * Length chooses so that both error messages are of the same length + * and it's enough data to figure out which artifact has a problem. + */ + const HEX_LENGTH = 16; const errMsg = isContractCreation ? `Unable to find matching bytecode for contract creation ${chalk.bold( - shortenHex(bytecode), + utils.shortenHex(bytecode, HEX_LENGTH), )}, please check your artifacts. Ignoring...` : `Unable to find matching bytecode for contract address ${chalk.bold( - traceInfo.address, + address, )}, please check your artifacts. Ignoring...`; this._logger.warn(errMsg); + } + return contractData; + } + public async computeSingleTraceCoverageAsync(subTraceInfo: SubTraceInfo): Promise { + const isContractCreation = subTraceInfo.address === constants.NEW_CONTRACT; + const bytecode = isContractCreation + ? (subTraceInfo as SubTraceInfoNewContract).bytecode + : (subTraceInfo as SubTraceInfoExistingContract).runtimeBytecode; + const contractData = await this.getContractDataByTraceInfoIfExistsAsync( + subTraceInfo.address, + bytecode, + isContractCreation, + ); + if (_.isUndefined(contractData)) { return; } const bytecodeHex = stripHexPrefix(bytecode); @@ -96,7 +107,7 @@ export class TraceCollector { _.map(contractData.sources, (_sourcePath: string, fileIndex: string) => { const singleFileCoverageForTrace = this._singleFileSubtraceHandler( contractData, - traceInfo.subtrace, + subTraceInfo.subtrace, pcToSourceRange, _.parseInt(fileIndex), ); diff --git a/packages/sol-tracing-utils/src/trace_info_subprovider.ts b/packages/sol-tracing-utils/src/trace_info_subprovider.ts index de42e1862f..ff147b0fb2 100644 --- a/packages/sol-tracing-utils/src/trace_info_subprovider.ts +++ b/packages/sol-tracing-utils/src/trace_info_subprovider.ts @@ -1,16 +1,21 @@ import { NodeType } from '@0x/web3-wrapper'; +import * as fs from 'fs'; import * as _ from 'lodash'; import { constants } from './constants'; import { getContractAddressToTraces } from './trace'; import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; +import { SubTraceInfo, SubTraceInfoExistingContract, SubTraceInfoNewContract, TraceInfo } from './types'; // TraceInfoSubprovider is extended by subproviders which need to work with one // TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which // is called for each TraceInfo. export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { - protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; + protected abstract _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise; + // tslint:disable prefer-function-over-method + protected _handleTraceInfoAsync(_traceInfo: TraceInfo): Promise { + return Promise.resolve(undefined); + } protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); const nodeType = await this._web3Wrapper.getNodeTypeAsync(); @@ -24,6 +29,13 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { const tracer = ` { data: [], + extractStack: function (stack) { + var extract = []; + for (var i = 0; i < stack.length(); i++) { + extract.push('0x' + stack.peek(i).toString(16)); + } + return extract; + }, step: function(log) { const op = log.op.toString(); const opn = 0 | log.op.toNumber(); @@ -32,7 +44,16 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { const gasCost = 0 | log.getCost(); const gas = 0 | log.getGas(); const isCall = opn == 0xf1 || opn == 0xf2 || opn == 0xf4 || opn == 0xf5 || opn == 0xfa; - const stack = isCall ? ['0x'+log.stack.peek(1).toString(16), null] : null; + const isMemoryAccess = opn == 0x51 || opn == 0x52 || opn == 0x53; + const isCallDataAccess = opn == 0x37; + var stack; + if (isCall) { + stack = ['0x'+log.stack.peek(1).toString(16), null]; + } else if (isMemoryAccess) { + stack = ['0x'+log.stack.peek(0).toString(16)]; + } else if (isCallDataAccess) { + stack = ['0x'+log.stack.peek(2).toString(16), '0x'+log.stack.peek(1).toString(16), '0x'+log.stack.peek(0).toString(16)]; + } this.data.push({ pc, gasCost, depth, op, stack, gas }); }, fault: function() { }, @@ -50,14 +71,23 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { disableStorage: true, }); } + const traceInfo = { + trace, + address, + data, + txHash, + }; + await this._handleTraceInfoAsync(traceInfo); const contractAddressToTraces = getContractAddressToTraces(trace.structLogs, address); const subcallAddresses = _.keys(contractAddressToTraces); if (address === constants.NEW_CONTRACT) { for (const subcallAddress of subcallAddresses) { - let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; + let subTraceInfo: SubTraceInfoNewContract | SubTraceInfoExistingContract; + const traceForThatSubcall = contractAddressToTraces[subcallAddress]; + const subcallDepth = traceForThatSubcall[0].depth; if (subcallAddress === 'NEW_CONTRACT') { - const traceForThatSubcall = contractAddressToTraces[subcallAddress]; - traceInfo = { + subTraceInfo = { + subcallDepth, subtrace: traceForThatSubcall, txHash, address: subcallAddress, @@ -65,27 +95,29 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { }; } else { const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = contractAddressToTraces[subcallAddress]; - traceInfo = { + subTraceInfo = { + subcallDepth, subtrace: traceForThatSubcall, txHash, address: subcallAddress, runtimeBytecode, }; } - await this._handleTraceInfoAsync(traceInfo); + await this._handleSubTraceInfoAsync(subTraceInfo); } } else { for (const subcallAddress of subcallAddresses) { const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); const traceForThatSubcall = contractAddressToTraces[subcallAddress]; - const traceInfo: TraceInfoExistingContract = { + const subcallDepth = traceForThatSubcall[0].depth; + const subTraceInfo: SubTraceInfoExistingContract = { + subcallDepth, subtrace: traceForThatSubcall, txHash, address: subcallAddress, runtimeBytecode, }; - await this._handleTraceInfoAsync(traceInfo); + await this._handleSubTraceInfoAsync(subTraceInfo); } } } diff --git a/packages/sol-tracing-utils/src/types.ts b/packages/sol-tracing-utils/src/types.ts index 97b5e6b370..933bc06077 100644 --- a/packages/sol-tracing-utils/src/types.ts +++ b/packages/sol-tracing-utils/src/types.ts @@ -1,4 +1,4 @@ -import { StructLog } from 'ethereum-types'; +import { ContractAbi, StructLog, TransactionTrace } from 'ethereum-types'; export interface LineColumn { line: number; @@ -83,6 +83,8 @@ export interface Sources { } export interface ContractData { + abi: ContractAbi; + name: string; bytecode: string; sourceMap: string; runtimeBytecode: string; @@ -94,22 +96,30 @@ export interface ContractData { // Part of the trace executed within the same context export type Subtrace = StructLog[]; -export interface TraceInfoBase { +export interface SubTraceInfoBase { subtrace: Subtrace; txHash: string; + subcallDepth: number; } -export interface TraceInfoNewContract extends TraceInfoBase { +export interface SubTraceInfoNewContract extends SubTraceInfoBase { address: 'NEW_CONTRACT'; bytecode: string; } -export interface TraceInfoExistingContract extends TraceInfoBase { +export interface SubTraceInfoExistingContract extends SubTraceInfoBase { address: string; runtimeBytecode: string; } -export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract; +export type SubTraceInfo = SubTraceInfoNewContract | SubTraceInfoExistingContract; + +export interface TraceInfo { + trace: TransactionTrace; + txHash: string; + address: string; + data: string | undefined; +} export enum BlockParamLiteral { Latest = 'latest', diff --git a/packages/sol-tracing-utils/src/utils.ts b/packages/sol-tracing-utils/src/utils.ts index 7dc1844a55..8fc9281a26 100644 --- a/packages/sol-tracing-utils/src/utils.ts +++ b/packages/sol-tracing-utils/src/utils.ts @@ -81,17 +81,44 @@ export const utils = { return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); }, normalizeStructLogs(structLogs: StructLog[]): StructLog[] { + if (_.isEmpty(structLogs)) { + return structLogs; + } if (structLogs[0].depth === 1) { // Geth uses 1-indexed depth counter whilst ganache starts from 0 - const newStructLogs = _.map(structLogs, structLog => { + const newStructLogs = _.map(structLogs, (structLog: StructLog, idx: number) => { const newStructLog = { ...structLog, depth: structLog.depth - 1, }; - if (newStructLog.op === 'STATICCALL') { + if (newStructLog.op === OpCode.StaticCall) { // HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them. newStructLog.gasCost = STATICCALL_GAS_COST; } + // if (newStructLog.op === OpCode.MStore) { + // // HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them. + // newStructLog.gasCost = 3; + // } + // if (newStructLog.op === OpCode.MLoad) { + // // HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them. + // newStructLog.gasCost = 3; + // } + // if (newStructLog.op === OpCode.CallDataCopy) { + // // HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them. + // newStructLog.gasCost = 3; + // } + if (newStructLog.op === 'CALL') { + const HEX_BASE = 16; + const callAddress = parseInt(newStructLog.stack[0], HEX_BASE); + // HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them. + if (callAddress < 50) { + const nextStructLog = structLogs[idx + 1]; + newStructLog.gasCost = structLog.gas - nextStructLog.gas; + } else { + newStructLog.gasCost = 700; + } + // newStructLog.gasCost = 700; + } return newStructLog; }); return newStructLogs; @@ -104,4 +131,7 @@ export const utils = { lines[0] = lines[0].slice(range.start.column); return lines.join('\n'); }, + shortenHex(hex: string, length: number): string { + return `${hex.substr(0, length + 2)}...${hex.substr(hex.length - length, length)}`; + }, }; diff --git a/yarn.lock b/yarn.lock index 3360084f2a..0300e633c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1597,7 +1597,7 @@ version "2.2.48" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" -"@types/node@*", "@types/node@^10.3.2": +"@types/node@*", "@types/node@10.9.4", "@types/node@^10.3.2": version "10.9.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897" @@ -13478,13 +13478,6 @@ react-copy-to-clipboard@^5.0.0: copy-to-clipboard "^3" prop-types "^15.5.8" -react-document-title@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/react-document-title/-/react-document-title-2.0.3.tgz#bbf922a0d71412fc948245e4283b2412df70f2b9" - dependencies: - prop-types "^15.5.6" - react-side-effect "^1.0.2" - react-dom@^16.3.2: version "16.4.2" resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4" @@ -13560,8 +13553,8 @@ react-highlight@0xproject/react-highlight#react-peer-deps: dependencies: highlight.js "^9.11.0" highlightjs-solidity "^1.0.5" - react "^16.4.2" - react-dom "^16.4.2" + react "^16.5.2" + react-dom "^16.5.2" react-hot-loader@^4.3.3: version "4.3.4" @@ -13706,7 +13699,7 @@ react-scrollable-anchor@^0.6.1: jump.js "1.0.1" prop-types "^15.5.10" -react-side-effect@^1.0.2, react-side-effect@^1.1.0: +react-side-effect@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d" dependencies: