Merge pull request #1647 from 0xProject/feature/sol-profiler-ganache

Sol-profiler ganache
This commit is contained in:
Leonid Logvinov 2019-02-26 13:53:55 -08:00 committed by GitHub
commit 7c492071f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 49 deletions

View File

@ -64,12 +64,6 @@ if (isCoverageEnabled) {
prependSubprovider(provider, coverageSubprovider);
}
if (isProfilerEnabled) {
if (testProvider === ProviderType.Ganache) {
logUtils.warn(
"Gas costs in Ganache traces are incorrect and we don't recommend using it for profiling. Please switch to Geth",
);
process.exit(1);
}
const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
logUtils.log(
"By default profilerSubprovider is stopped so that you don't get noise from setup code. Don't forget to start it before the code you want to profile and stop it afterwards",

View File

@ -1,4 +1,13 @@
[
{
"version": "3.1.0",
"changes": [
{
"note": "Add support for Ganache",
"pr": 1647
}
]
},
{
"version": "3.0.0",
"changes": [

View File

@ -51,7 +51,7 @@ export const costUtils = {
const length = parseInt(structLog.stack[2], HEX_BASE);
return memOffset + length;
} else {
return parseInt(structLog.stack[0], HEX_BASE);
return parseInt(structLog.stack[structLog.stack.length - 1], HEX_BASE);
}
});
const highestMemoryLocationAccessed = _.max(memoryLocationsAccessed);

View File

@ -57,7 +57,6 @@ export class ProfilerSubprovider extends TraceInfoSubprovider {
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);

View File

@ -17,10 +17,9 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
if (_.isEmpty(structLogs)) {
return [];
}
const normalizedStructLogs = utils.normalizeStructLogs(structLogs);
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < normalizedStructLogs.length; i++) {
const structLog = normalizedStructLogs[i];
for (let i = 0; i < structLogs.length; i++) {
const structLog = structLogs[i];
if (structLog.depth !== addressStack.length - 1) {
throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
}
@ -38,7 +37,7 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
// Sometimes calls don't change the execution context (current address). When we do a transfer to an
// externally owned account - it does the call and immediately returns because there is no fallback
// function. We manually check if the call depth had changed to handle that case.
const nextStructLog = normalizedStructLogs[i + 1];
const nextStructLog = structLogs[i + 1];
if (nextStructLog.depth !== structLog.depth) {
addressStack.push(newAddress);
evmCallStack.push({
@ -48,7 +47,7 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
}
} else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) {
// Just like with calls, sometimes returns/stops don't change the execution context (current address).
const nextStructLog = normalizedStructLogs[i + 1];
const nextStructLog = structLogs[i + 1];
if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) {
evmCallStack.pop();
addressStack.pop();
@ -76,8 +75,8 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
);
return [];
} else {
if (structLog !== _.last(normalizedStructLogs)) {
const nextStructLog = normalizedStructLogs[i + 1];
if (structLog !== _.last(structLogs)) {
const nextStructLog = structLogs[i + 1];
if (nextStructLog.depth === structLog.depth) {
continue;
} else if (nextStructLog.depth === structLog.depth - 1) {

View File

@ -20,10 +20,9 @@ export function getContractAddressToTraces(structLogs: StructLog[], startAddress
if (_.isEmpty(structLogs)) {
return contractAddressToTraces;
}
const normalizedStructLogs = utils.normalizeStructLogs(structLogs);
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < normalizedStructLogs.length; i++) {
const structLog = normalizedStructLogs[i];
for (let i = 0; i < structLogs.length; i++) {
const structLog = structLogs[i];
if (structLog.depth !== addressStack.length - 1) {
throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
}
@ -42,7 +41,7 @@ export function getContractAddressToTraces(structLogs: StructLog[], startAddress
// Sometimes calls don't change the execution context (current address). When we do a transfer to an
// externally owned account - it does the call and immediately returns because there is no fallback
// function. We manually check if the call depth had changed to handle that case.
const nextStructLog = normalizedStructLogs[i + 1];
const nextStructLog = structLogs[i + 1];
if (nextStructLog.depth !== structLog.depth) {
addressStack.push(newAddress);
contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
@ -73,8 +72,8 @@ export function getContractAddressToTraces(structLogs: StructLog[], startAddress
);
return contractAddressToTraces;
} else {
if (structLog !== _.last(normalizedStructLogs)) {
const nextStructLog = normalizedStructLogs[i + 1];
if (structLog !== _.last(structLogs)) {
const nextStructLog = structLogs[i + 1];
if (nextStructLog.depth === structLog.depth) {
continue;
} else if (nextStructLog.depth === structLog.depth - 1) {

View File

@ -5,6 +5,7 @@ import { constants } from './constants';
import { getContractAddressToTraces } from './trace';
import { TraceCollectionSubprovider } from './trace_collection_subprovider';
import { SubTraceInfo, SubTraceInfoExistingContract, SubTraceInfoNewContract, TraceInfo } from './types';
import { utils } from './utils';
// TraceInfoSubprovider is extended by subproviders which need to work with one
// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which
@ -51,11 +52,11 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
const isCallDataAccess = opn == 0x37;
var stack;
if (isCall) {
stack = ['0x'+log.stack.peek(1).toString(16), null];
stack = [null, '0x'+log.stack.peek(1).toString(16)];
} 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)];
stack = ['0x'+log.stack.peek(0).toString(16), '0x'+log.stack.peek(1).toString(16), '0x'+log.stack.peek(2).toString(16)];
}
this.data.push({ pc, gasCost, depth, op, stack, gas });
},
@ -74,6 +75,7 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
disableStorage: true,
});
}
trace.structLogs = utils.normalizeStructLogs(trace.structLogs);
const traceInfo = {
trace,
address,

View File

@ -85,34 +85,66 @@ export const utils = {
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: StructLog, idx: number) => {
const newStructLog = {
const reduceDepthBy1 = (structLog: StructLog) => ({
...structLog,
depth: structLog.depth - 1,
};
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 === 'CALL') {
const HEX_BASE = 16;
const callAddress = parseInt(newStructLog.stack[0], HEX_BASE);
const MAX_REASONABLE_PRECOMPILE_ADDRESS = 100;
// HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them.
if (callAddress < MAX_REASONABLE_PRECOMPILE_ADDRESS) {
const nextStructLog = structLogs[idx + 1];
newStructLog.gasCost = structLog.gas - nextStructLog.gas;
} else {
newStructLog.gasCost = CALL_GAS_COST;
}
}
return newStructLog;
});
return newStructLogs;
let normalizedStructLogs = structLogs;
// HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them.
const normalizeStaticCallCost = (structLog: StructLog) => (
(structLog.op === OpCode.StaticCall) ? {
...structLog,
gasCost: STATICCALL_GAS_COST,
} : structLog
);
// HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them.
const normalizeCallCost = (structLog: StructLog, index: number) => {
if (structLog.op === OpCode.Call) {
const HEX_BASE = 16;
const callAddress = parseInt(structLog.stack[0], HEX_BASE);
const MAX_REASONABLE_PRECOMPILE_ADDRESS = 100;
if (callAddress < MAX_REASONABLE_PRECOMPILE_ADDRESS) {
const nextStructLog = normalizedStructLogs[index + 1];
const gasCost = structLog.gas - nextStructLog.gas;
return {
...structLog,
gasCost,
};
} else {
return {
...structLog,
gasCost: CALL_GAS_COST,
};
}
return structLogs;
} else {
return structLog;
}
};
const shiftGasCosts1Left = (structLog: StructLog, idx: number) => {
if (idx === structLogs.length - 1) {
return {
...structLog,
gasCost: 0,
};
} else {
const nextStructLog = structLogs[idx + 1];
const gasCost = nextStructLog.gasCost;
return {
...structLog,
gasCost,
};
}
};
if (structLogs[0].depth === 1) {
// Geth uses 1-indexed depth counter whilst ganache starts from 0
normalizedStructLogs = _.map(structLogs, reduceDepthBy1);
normalizedStructLogs = _.map(structLogs, normalizeCallCost);
normalizedStructLogs = _.map(structLogs, normalizeStaticCallCost);
} else {
// Ganache shifts opcodes gas costs so we need to unshift them
normalizedStructLogs = _.map(structLogs, shiftGasCosts1Left);
}
return normalizedStructLogs;
},
getRange(sourceCode: string, range: SingleFileSourceRange): string {
const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line);