Major sol-profiler overhaul
This commit is contained in:
parent
047de370d6
commit
04d8f46ff3
@ -11,8 +11,8 @@ import {
|
|||||||
StatementCoverage,
|
StatementCoverage,
|
||||||
StatementDescription,
|
StatementDescription,
|
||||||
Subtrace,
|
Subtrace,
|
||||||
|
SubTraceInfo,
|
||||||
TraceCollector,
|
TraceCollector,
|
||||||
TraceInfo,
|
|
||||||
TraceInfoSubprovider,
|
TraceInfoSubprovider,
|
||||||
utils,
|
utils,
|
||||||
} from '@0x/sol-tracing-utils';
|
} from '@0x/sol-tracing-utils';
|
||||||
@ -39,8 +39,8 @@ export class CoverageSubprovider extends TraceInfoSubprovider {
|
|||||||
super(defaultFromAddress, traceCollectionSubproviderConfig);
|
super(defaultFromAddress, traceCollectionSubproviderConfig);
|
||||||
this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler);
|
this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler);
|
||||||
}
|
}
|
||||||
protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void> {
|
protected async _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise<void> {
|
||||||
await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo);
|
await this._coverageCollector.computeSingleTraceCoverageAsync(subTraceInfo);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Write the test coverage results to a file in Istanbul format.
|
* Write the test coverage results to a file in Istanbul format.
|
||||||
|
@ -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,
|
"timestamp": 1549733923,
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
@ -81,4 +102,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -33,6 +33,8 @@
|
|||||||
"@0x/subproviders": "^3.0.3",
|
"@0x/subproviders": "^3.0.3",
|
||||||
"@0x/typescript-typings": "^4.0.0",
|
"@0x/typescript-typings": "^4.0.0",
|
||||||
"ethereum-types": "^2.0.0",
|
"ethereum-types": "^2.0.0",
|
||||||
|
"ethereumjs-util": "^5.1.1",
|
||||||
|
"@0x/utils": "^4.1.0",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"web3-provider-engine": "14.0.6"
|
"web3-provider-engine": "14.0.6"
|
||||||
},
|
},
|
||||||
@ -49,4 +51,4 @@
|
|||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
94
packages/sol-profiler/src/cost_utils.ts
Normal file
94
packages/sol-profiler/src/cost_utils.ts
Normal file
@ -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;
|
||||||
|
},
|
||||||
|
};
|
@ -1,5 +1,3 @@
|
|||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbstractArtifactAdapter,
|
AbstractArtifactAdapter,
|
||||||
collectCoverageEntries,
|
collectCoverageEntries,
|
||||||
@ -8,11 +6,20 @@ import {
|
|||||||
SingleFileSubtraceHandler,
|
SingleFileSubtraceHandler,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
Subtrace,
|
Subtrace,
|
||||||
|
SubTraceInfo,
|
||||||
TraceCollector,
|
TraceCollector,
|
||||||
TraceInfo,
|
TraceInfo,
|
||||||
TraceInfoSubprovider,
|
TraceInfoSubprovider,
|
||||||
utils,
|
utils,
|
||||||
} from '@0x/sol-tracing-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.
|
* 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);
|
super(defaultFromAddress, traceCollectionSubproviderConfig);
|
||||||
this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler);
|
this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler);
|
||||||
}
|
}
|
||||||
|
protected async _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise<void> {
|
||||||
|
await this._profilerCollector.computeSingleTraceCoverageAsync(subTraceInfo);
|
||||||
|
}
|
||||||
|
// tslint:disable prefer-function-over-method
|
||||||
protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void> {
|
protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void> {
|
||||||
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.
|
* Write the test profiler results to a file in Istanbul format.
|
||||||
|
@ -87,4 +87,4 @@
|
|||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -59,6 +59,8 @@ export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter {
|
|||||||
sourceCodes[value.id] = source.source;
|
sourceCodes[value.id] = source.source;
|
||||||
});
|
});
|
||||||
const contractData = {
|
const contractData = {
|
||||||
|
abi: artifact.compilerOutput.abi,
|
||||||
|
name: artifact.contractName,
|
||||||
sourceCodes,
|
sourceCodes,
|
||||||
sources,
|
sources,
|
||||||
bytecode: artifact.compilerOutput.evm.bytecode.object,
|
bytecode: artifact.compilerOutput.evm.bytecode.object,
|
||||||
|
@ -12,7 +12,7 @@ export {
|
|||||||
BranchCoverage,
|
BranchCoverage,
|
||||||
BranchDescription,
|
BranchDescription,
|
||||||
Subtrace,
|
Subtrace,
|
||||||
TraceInfo,
|
SubTraceInfo,
|
||||||
Coverage,
|
Coverage,
|
||||||
LineColumn,
|
LineColumn,
|
||||||
LineCoverage,
|
LineCoverage,
|
||||||
@ -24,9 +24,10 @@ export {
|
|||||||
FnMap,
|
FnMap,
|
||||||
OffsetToLocation,
|
OffsetToLocation,
|
||||||
StatementMap,
|
StatementMap,
|
||||||
TraceInfoBase,
|
TraceInfo,
|
||||||
TraceInfoExistingContract,
|
SubTraceInfoBase,
|
||||||
TraceInfoNewContract,
|
SubTraceInfoExistingContract,
|
||||||
|
SubTraceInfoNewContract,
|
||||||
Sources,
|
Sources,
|
||||||
SourceCodes,
|
SourceCodes,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
@ -15,9 +15,9 @@ import {
|
|||||||
Coverage,
|
Coverage,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
Subtrace,
|
Subtrace,
|
||||||
TraceInfo,
|
SubTraceInfo,
|
||||||
TraceInfoExistingContract,
|
SubTraceInfoExistingContract,
|
||||||
TraceInfoNewContract,
|
SubTraceInfoNewContract,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { utils } from './utils';
|
import { utils } from './utils';
|
||||||
|
|
||||||
@ -62,32 +62,43 @@ export class TraceCollector {
|
|||||||
await mkdirpAsync('coverage');
|
await mkdirpAsync('coverage');
|
||||||
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
|
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
|
||||||
}
|
}
|
||||||
public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise<void> {
|
public async getContractDataByTraceInfoIfExistsAsync(
|
||||||
|
address: string,
|
||||||
|
bytecode: string,
|
||||||
|
isContractCreation: boolean,
|
||||||
|
): Promise<ContractData | undefined> {
|
||||||
if (_.isUndefined(this._contractsData)) {
|
if (_.isUndefined(this._contractsData)) {
|
||||||
this._contractsData = await this._artifactAdapter.collectContractsDataAsync();
|
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);
|
const contractData = utils.getContractDataIfExists(this._contractsData, bytecode);
|
||||||
if (_.isUndefined(contractData)) {
|
if (_.isUndefined(contractData)) {
|
||||||
const shortenHex = (hex: string) => {
|
/**
|
||||||
/**
|
* Length chooses so that both error messages are of the same 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.
|
||||||
* and it's enough data to figure out which artifact has a problem.
|
*/
|
||||||
*/
|
const HEX_LENGTH = 16;
|
||||||
const length = 18;
|
|
||||||
return `${hex.substr(0, length + 2)}...${hex.substr(hex.length - length, length)}`;
|
|
||||||
};
|
|
||||||
const errMsg = isContractCreation
|
const errMsg = isContractCreation
|
||||||
? `Unable to find matching bytecode for contract creation ${chalk.bold(
|
? `Unable to find matching bytecode for contract creation ${chalk.bold(
|
||||||
shortenHex(bytecode),
|
utils.shortenHex(bytecode, HEX_LENGTH),
|
||||||
)}, please check your artifacts. Ignoring...`
|
)}, please check your artifacts. Ignoring...`
|
||||||
: `Unable to find matching bytecode for contract address ${chalk.bold(
|
: `Unable to find matching bytecode for contract address ${chalk.bold(
|
||||||
traceInfo.address,
|
address,
|
||||||
)}, please check your artifacts. Ignoring...`;
|
)}, please check your artifacts. Ignoring...`;
|
||||||
this._logger.warn(errMsg);
|
this._logger.warn(errMsg);
|
||||||
|
}
|
||||||
|
return contractData;
|
||||||
|
}
|
||||||
|
public async computeSingleTraceCoverageAsync(subTraceInfo: SubTraceInfo): Promise<void> {
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const bytecodeHex = stripHexPrefix(bytecode);
|
const bytecodeHex = stripHexPrefix(bytecode);
|
||||||
@ -96,7 +107,7 @@ export class TraceCollector {
|
|||||||
_.map(contractData.sources, (_sourcePath: string, fileIndex: string) => {
|
_.map(contractData.sources, (_sourcePath: string, fileIndex: string) => {
|
||||||
const singleFileCoverageForTrace = this._singleFileSubtraceHandler(
|
const singleFileCoverageForTrace = this._singleFileSubtraceHandler(
|
||||||
contractData,
|
contractData,
|
||||||
traceInfo.subtrace,
|
subTraceInfo.subtrace,
|
||||||
pcToSourceRange,
|
pcToSourceRange,
|
||||||
_.parseInt(fileIndex),
|
_.parseInt(fileIndex),
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import { NodeType } from '@0x/web3-wrapper';
|
import { NodeType } from '@0x/web3-wrapper';
|
||||||
|
import * as fs from 'fs';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { constants } from './constants';
|
import { constants } from './constants';
|
||||||
import { getContractAddressToTraces } from './trace';
|
import { getContractAddressToTraces } from './trace';
|
||||||
import { TraceCollectionSubprovider } from './trace_collection_subprovider';
|
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
|
// TraceInfoSubprovider is extended by subproviders which need to work with one
|
||||||
// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which
|
// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which
|
||||||
// is called for each TraceInfo.
|
// is called for each TraceInfo.
|
||||||
export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
||||||
protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void>;
|
protected abstract _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise<void>;
|
||||||
|
// tslint:disable prefer-function-over-method
|
||||||
|
protected _handleTraceInfoAsync(_traceInfo: TraceInfo): Promise<void> {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
|
protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
|
||||||
await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0);
|
await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0);
|
||||||
const nodeType = await this._web3Wrapper.getNodeTypeAsync();
|
const nodeType = await this._web3Wrapper.getNodeTypeAsync();
|
||||||
@ -24,6 +29,13 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
|||||||
const tracer = `
|
const tracer = `
|
||||||
{
|
{
|
||||||
data: [],
|
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) {
|
step: function(log) {
|
||||||
const op = log.op.toString();
|
const op = log.op.toString();
|
||||||
const opn = 0 | log.op.toNumber();
|
const opn = 0 | log.op.toNumber();
|
||||||
@ -32,7 +44,16 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
|||||||
const gasCost = 0 | log.getCost();
|
const gasCost = 0 | log.getCost();
|
||||||
const gas = 0 | log.getGas();
|
const gas = 0 | log.getGas();
|
||||||
const isCall = opn == 0xf1 || opn == 0xf2 || opn == 0xf4 || opn == 0xf5 || opn == 0xfa;
|
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 });
|
this.data.push({ pc, gasCost, depth, op, stack, gas });
|
||||||
},
|
},
|
||||||
fault: function() { },
|
fault: function() { },
|
||||||
@ -50,14 +71,23 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
|||||||
disableStorage: true,
|
disableStorage: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const traceInfo = {
|
||||||
|
trace,
|
||||||
|
address,
|
||||||
|
data,
|
||||||
|
txHash,
|
||||||
|
};
|
||||||
|
await this._handleTraceInfoAsync(traceInfo);
|
||||||
const contractAddressToTraces = getContractAddressToTraces(trace.structLogs, address);
|
const contractAddressToTraces = getContractAddressToTraces(trace.structLogs, address);
|
||||||
const subcallAddresses = _.keys(contractAddressToTraces);
|
const subcallAddresses = _.keys(contractAddressToTraces);
|
||||||
if (address === constants.NEW_CONTRACT) {
|
if (address === constants.NEW_CONTRACT) {
|
||||||
for (const subcallAddress of subcallAddresses) {
|
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') {
|
if (subcallAddress === 'NEW_CONTRACT') {
|
||||||
const traceForThatSubcall = contractAddressToTraces[subcallAddress];
|
subTraceInfo = {
|
||||||
traceInfo = {
|
subcallDepth,
|
||||||
subtrace: traceForThatSubcall,
|
subtrace: traceForThatSubcall,
|
||||||
txHash,
|
txHash,
|
||||||
address: subcallAddress,
|
address: subcallAddress,
|
||||||
@ -65,27 +95,29 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
|
const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
|
||||||
const traceForThatSubcall = contractAddressToTraces[subcallAddress];
|
subTraceInfo = {
|
||||||
traceInfo = {
|
subcallDepth,
|
||||||
subtrace: traceForThatSubcall,
|
subtrace: traceForThatSubcall,
|
||||||
txHash,
|
txHash,
|
||||||
address: subcallAddress,
|
address: subcallAddress,
|
||||||
runtimeBytecode,
|
runtimeBytecode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await this._handleTraceInfoAsync(traceInfo);
|
await this._handleSubTraceInfoAsync(subTraceInfo);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const subcallAddress of subcallAddresses) {
|
for (const subcallAddress of subcallAddresses) {
|
||||||
const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
|
const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
|
||||||
const traceForThatSubcall = contractAddressToTraces[subcallAddress];
|
const traceForThatSubcall = contractAddressToTraces[subcallAddress];
|
||||||
const traceInfo: TraceInfoExistingContract = {
|
const subcallDepth = traceForThatSubcall[0].depth;
|
||||||
|
const subTraceInfo: SubTraceInfoExistingContract = {
|
||||||
|
subcallDepth,
|
||||||
subtrace: traceForThatSubcall,
|
subtrace: traceForThatSubcall,
|
||||||
txHash,
|
txHash,
|
||||||
address: subcallAddress,
|
address: subcallAddress,
|
||||||
runtimeBytecode,
|
runtimeBytecode,
|
||||||
};
|
};
|
||||||
await this._handleTraceInfoAsync(traceInfo);
|
await this._handleSubTraceInfoAsync(subTraceInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { StructLog } from 'ethereum-types';
|
import { ContractAbi, StructLog, TransactionTrace } from 'ethereum-types';
|
||||||
|
|
||||||
export interface LineColumn {
|
export interface LineColumn {
|
||||||
line: number;
|
line: number;
|
||||||
@ -83,6 +83,8 @@ export interface Sources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ContractData {
|
export interface ContractData {
|
||||||
|
abi: ContractAbi;
|
||||||
|
name: string;
|
||||||
bytecode: string;
|
bytecode: string;
|
||||||
sourceMap: string;
|
sourceMap: string;
|
||||||
runtimeBytecode: string;
|
runtimeBytecode: string;
|
||||||
@ -94,22 +96,30 @@ export interface ContractData {
|
|||||||
// Part of the trace executed within the same context
|
// Part of the trace executed within the same context
|
||||||
export type Subtrace = StructLog[];
|
export type Subtrace = StructLog[];
|
||||||
|
|
||||||
export interface TraceInfoBase {
|
export interface SubTraceInfoBase {
|
||||||
subtrace: Subtrace;
|
subtrace: Subtrace;
|
||||||
txHash: string;
|
txHash: string;
|
||||||
|
subcallDepth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TraceInfoNewContract extends TraceInfoBase {
|
export interface SubTraceInfoNewContract extends SubTraceInfoBase {
|
||||||
address: 'NEW_CONTRACT';
|
address: 'NEW_CONTRACT';
|
||||||
bytecode: string;
|
bytecode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TraceInfoExistingContract extends TraceInfoBase {
|
export interface SubTraceInfoExistingContract extends SubTraceInfoBase {
|
||||||
address: string;
|
address: string;
|
||||||
runtimeBytecode: 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 {
|
export enum BlockParamLiteral {
|
||||||
Latest = 'latest',
|
Latest = 'latest',
|
||||||
|
@ -81,17 +81,44 @@ export const utils = {
|
|||||||
return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase));
|
return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase));
|
||||||
},
|
},
|
||||||
normalizeStructLogs(structLogs: StructLog[]): StructLog[] {
|
normalizeStructLogs(structLogs: StructLog[]): StructLog[] {
|
||||||
|
if (_.isEmpty(structLogs)) {
|
||||||
|
return structLogs;
|
||||||
|
}
|
||||||
if (structLogs[0].depth === 1) {
|
if (structLogs[0].depth === 1) {
|
||||||
// Geth uses 1-indexed depth counter whilst ganache starts from 0
|
// 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 = {
|
const newStructLog = {
|
||||||
...structLog,
|
...structLog,
|
||||||
depth: structLog.depth - 1,
|
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.
|
// HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them.
|
||||||
newStructLog.gasCost = STATICCALL_GAS_COST;
|
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 newStructLog;
|
||||||
});
|
});
|
||||||
return newStructLogs;
|
return newStructLogs;
|
||||||
@ -104,4 +131,7 @@ export const utils = {
|
|||||||
lines[0] = lines[0].slice(range.start.column);
|
lines[0] = lines[0].slice(range.start.column);
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
},
|
},
|
||||||
|
shortenHex(hex: string, length: number): string {
|
||||||
|
return `${hex.substr(0, length + 2)}...${hex.substr(hex.length - length, length)}`;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
15
yarn.lock
15
yarn.lock
@ -1597,7 +1597,7 @@
|
|||||||
version "2.2.48"
|
version "2.2.48"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab"
|
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"
|
version "10.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897"
|
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"
|
copy-to-clipboard "^3"
|
||||||
prop-types "^15.5.8"
|
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:
|
react-dom@^16.3.2:
|
||||||
version "16.4.2"
|
version "16.4.2"
|
||||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4"
|
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:
|
dependencies:
|
||||||
highlight.js "^9.11.0"
|
highlight.js "^9.11.0"
|
||||||
highlightjs-solidity "^1.0.5"
|
highlightjs-solidity "^1.0.5"
|
||||||
react "^16.4.2"
|
react "^16.5.2"
|
||||||
react-dom "^16.4.2"
|
react-dom "^16.5.2"
|
||||||
|
|
||||||
react-hot-loader@^4.3.3:
|
react-hot-loader@^4.3.3:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
@ -13706,7 +13699,7 @@ react-scrollable-anchor@^0.6.1:
|
|||||||
jump.js "1.0.1"
|
jump.js "1.0.1"
|
||||||
prop-types "^15.5.10"
|
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"
|
version "1.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d"
|
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user