Merge pull request #1647 from 0xProject/feature/sol-profiler-ganache
Sol-profiler ganache
This commit is contained in:
commit
7c492071f1
@ -64,12 +64,6 @@ if (isCoverageEnabled) {
|
|||||||
prependSubprovider(provider, coverageSubprovider);
|
prependSubprovider(provider, coverageSubprovider);
|
||||||
}
|
}
|
||||||
if (isProfilerEnabled) {
|
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();
|
const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
|
||||||
logUtils.log(
|
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",
|
"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",
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add support for Ganache",
|
||||||
|
"pr": 1647
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
@ -103,4 +112,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -51,7 +51,7 @@ export const costUtils = {
|
|||||||
const length = parseInt(structLog.stack[2], HEX_BASE);
|
const length = parseInt(structLog.stack[2], HEX_BASE);
|
||||||
return memOffset + length;
|
return memOffset + length;
|
||||||
} else {
|
} else {
|
||||||
return parseInt(structLog.stack[0], HEX_BASE);
|
return parseInt(structLog.stack[structLog.stack.length - 1], HEX_BASE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const highestMemoryLocationAccessed = _.max(memoryLocationsAccessed);
|
const highestMemoryLocationAccessed = _.max(memoryLocationsAccessed);
|
||||||
|
@ -57,7 +57,6 @@ export class ProfilerSubprovider extends TraceInfoSubprovider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logUtils.header(`Profiling data for ${traceInfo.txHash}`);
|
logUtils.header(`Profiling data for ${traceInfo.txHash}`);
|
||||||
traceInfo.trace.structLogs = utils.normalizeStructLogs(traceInfo.trace.structLogs);
|
|
||||||
const callDataCost = costUtils.reportCallDataCost(traceInfo);
|
const callDataCost = costUtils.reportCallDataCost(traceInfo);
|
||||||
const memoryCost = costUtils.reportMemoryCost(traceInfo);
|
const memoryCost = costUtils.reportMemoryCost(traceInfo);
|
||||||
const opcodesCost = costUtils.reportOpcodesCost(traceInfo);
|
const opcodesCost = costUtils.reportOpcodesCost(traceInfo);
|
||||||
|
@ -17,10 +17,9 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
|
|||||||
if (_.isEmpty(structLogs)) {
|
if (_.isEmpty(structLogs)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const normalizedStructLogs = utils.normalizeStructLogs(structLogs);
|
|
||||||
// tslint:disable-next-line:prefer-for-of
|
// tslint:disable-next-line:prefer-for-of
|
||||||
for (let i = 0; i < normalizedStructLogs.length; i++) {
|
for (let i = 0; i < structLogs.length; i++) {
|
||||||
const structLog = normalizedStructLogs[i];
|
const structLog = structLogs[i];
|
||||||
if (structLog.depth !== addressStack.length - 1) {
|
if (structLog.depth !== addressStack.length - 1) {
|
||||||
throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
|
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
|
// 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
|
// 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.
|
// 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) {
|
if (nextStructLog.depth !== structLog.depth) {
|
||||||
addressStack.push(newAddress);
|
addressStack.push(newAddress);
|
||||||
evmCallStack.push({
|
evmCallStack.push({
|
||||||
@ -48,7 +47,7 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
|
|||||||
}
|
}
|
||||||
} else if (utils.isEndOpcode(structLog.op) && structLog.op !== OpCode.Revert) {
|
} 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).
|
// 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) {
|
if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) {
|
||||||
evmCallStack.pop();
|
evmCallStack.pop();
|
||||||
addressStack.pop();
|
addressStack.pop();
|
||||||
@ -76,8 +75,8 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
|
|||||||
);
|
);
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
if (structLog !== _.last(normalizedStructLogs)) {
|
if (structLog !== _.last(structLogs)) {
|
||||||
const nextStructLog = normalizedStructLogs[i + 1];
|
const nextStructLog = structLogs[i + 1];
|
||||||
if (nextStructLog.depth === structLog.depth) {
|
if (nextStructLog.depth === structLog.depth) {
|
||||||
continue;
|
continue;
|
||||||
} else if (nextStructLog.depth === structLog.depth - 1) {
|
} else if (nextStructLog.depth === structLog.depth - 1) {
|
||||||
|
@ -20,10 +20,9 @@ export function getContractAddressToTraces(structLogs: StructLog[], startAddress
|
|||||||
if (_.isEmpty(structLogs)) {
|
if (_.isEmpty(structLogs)) {
|
||||||
return contractAddressToTraces;
|
return contractAddressToTraces;
|
||||||
}
|
}
|
||||||
const normalizedStructLogs = utils.normalizeStructLogs(structLogs);
|
|
||||||
// tslint:disable-next-line:prefer-for-of
|
// tslint:disable-next-line:prefer-for-of
|
||||||
for (let i = 0; i < normalizedStructLogs.length; i++) {
|
for (let i = 0; i < structLogs.length; i++) {
|
||||||
const structLog = normalizedStructLogs[i];
|
const structLog = structLogs[i];
|
||||||
if (structLog.depth !== addressStack.length - 1) {
|
if (structLog.depth !== addressStack.length - 1) {
|
||||||
throw new Error("Malformed trace. Trace depth doesn't match call stack depth");
|
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
|
// 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
|
// 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.
|
// 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) {
|
if (nextStructLog.depth !== structLog.depth) {
|
||||||
addressStack.push(newAddress);
|
addressStack.push(newAddress);
|
||||||
contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
|
contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
|
||||||
@ -73,8 +72,8 @@ export function getContractAddressToTraces(structLogs: StructLog[], startAddress
|
|||||||
);
|
);
|
||||||
return contractAddressToTraces;
|
return contractAddressToTraces;
|
||||||
} else {
|
} else {
|
||||||
if (structLog !== _.last(normalizedStructLogs)) {
|
if (structLog !== _.last(structLogs)) {
|
||||||
const nextStructLog = normalizedStructLogs[i + 1];
|
const nextStructLog = structLogs[i + 1];
|
||||||
if (nextStructLog.depth === structLog.depth) {
|
if (nextStructLog.depth === structLog.depth) {
|
||||||
continue;
|
continue;
|
||||||
} else if (nextStructLog.depth === structLog.depth - 1) {
|
} else if (nextStructLog.depth === structLog.depth - 1) {
|
||||||
|
@ -5,6 +5,7 @@ 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 { SubTraceInfo, SubTraceInfoExistingContract, SubTraceInfoNewContract, TraceInfo } from './types';
|
import { SubTraceInfo, SubTraceInfoExistingContract, SubTraceInfoNewContract, TraceInfo } from './types';
|
||||||
|
import { utils } from './utils';
|
||||||
|
|
||||||
// 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
|
||||||
@ -51,11 +52,11 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
|||||||
const isCallDataAccess = opn == 0x37;
|
const isCallDataAccess = opn == 0x37;
|
||||||
var stack;
|
var stack;
|
||||||
if (isCall) {
|
if (isCall) {
|
||||||
stack = ['0x'+log.stack.peek(1).toString(16), null];
|
stack = [null, '0x'+log.stack.peek(1).toString(16)];
|
||||||
} else if (isMemoryAccess) {
|
} else if (isMemoryAccess) {
|
||||||
stack = ['0x'+log.stack.peek(0).toString(16)];
|
stack = ['0x'+log.stack.peek(0).toString(16)];
|
||||||
} else if (isCallDataAccess) {
|
} 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 });
|
this.data.push({ pc, gasCost, depth, op, stack, gas });
|
||||||
},
|
},
|
||||||
@ -74,6 +75,7 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
|||||||
disableStorage: true,
|
disableStorage: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
trace.structLogs = utils.normalizeStructLogs(trace.structLogs);
|
||||||
const traceInfo = {
|
const traceInfo = {
|
||||||
trace,
|
trace,
|
||||||
address,
|
address,
|
||||||
|
@ -85,34 +85,66 @@ export const utils = {
|
|||||||
if (_.isEmpty(structLogs)) {
|
if (_.isEmpty(structLogs)) {
|
||||||
return structLogs;
|
return structLogs;
|
||||||
}
|
}
|
||||||
|
const reduceDepthBy1 = (structLog: StructLog) => ({
|
||||||
|
...structLog,
|
||||||
|
depth: structLog.depth - 1,
|
||||||
|
});
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} 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) {
|
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: StructLog, idx: number) => {
|
normalizedStructLogs = _.map(structLogs, reduceDepthBy1);
|
||||||
const newStructLog = {
|
normalizedStructLogs = _.map(structLogs, normalizeCallCost);
|
||||||
...structLog,
|
normalizedStructLogs = _.map(structLogs, normalizeStaticCallCost);
|
||||||
depth: structLog.depth - 1,
|
} else {
|
||||||
};
|
// Ganache shifts opcodes gas costs so we need to unshift them
|
||||||
if (newStructLog.op === OpCode.StaticCall) {
|
normalizedStructLogs = _.map(structLogs, shiftGasCosts1Left);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
return structLogs;
|
return normalizedStructLogs;
|
||||||
},
|
},
|
||||||
getRange(sourceCode: string, range: SingleFileSourceRange): string {
|
getRange(sourceCode: string, range: SingleFileSourceRange): string {
|
||||||
const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line);
|
const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user