Major sol-profiler overhaul
This commit is contained in:
parent
047de370d6
commit
04d8f46ff3
@ -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<void> {
|
||||
await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo);
|
||||
protected async _handleSubTraceInfoAsync(subTraceInfo: SubTraceInfo): Promise<void> {
|
||||
await this._coverageCollector.computeSingleTraceCoverageAsync(subTraceInfo);
|
||||
}
|
||||
/**
|
||||
* 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,
|
||||
"version": "2.0.4",
|
||||
|
@ -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"
|
||||
},
|
||||
|
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 {
|
||||
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<void> {
|
||||
await this._profilerCollector.computeSingleTraceCoverageAsync(subTraceInfo);
|
||||
}
|
||||
// tslint:disable prefer-function-over-method
|
||||
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.
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
|
@ -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<void> {
|
||||
public async getContractDataByTraceInfoIfExistsAsync(
|
||||
address: string,
|
||||
bytecode: string,
|
||||
isContractCreation: boolean,
|
||||
): Promise<ContractData | undefined> {
|
||||
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<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;
|
||||
}
|
||||
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),
|
||||
);
|
||||
|
@ -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<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> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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)}`;
|
||||
},
|
||||
};
|
||||
|
15
yarn.lock
15
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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user