Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Sol-profiler ganache #1647

Merged
merged 8 commits into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions contracts/test-utils/src/web3_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 10 additions & 1 deletion packages/sol-profiler/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"version": "3.1.0",
"changes": [
{
"note": "Add support for Ganache",
"pr": 1647
}
]
},
{
"version": "3.0.0",
"changes": [
Expand Down Expand Up @@ -103,4 +112,4 @@
}
]
}
]
]
2 changes: 1 addition & 1 deletion packages/sol-profiler/src/cost_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion packages/sol-profiler/src/profiler_subprovider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 6 additions & 7 deletions packages/sol-tracing-utils/src/revert_trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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({
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 5 additions & 6 deletions packages/sol-tracing-utils/src/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 4 additions & 2 deletions packages/sol-tracing-utils/src/trace_info_subprovider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 });
},
Expand All @@ -74,6 +75,7 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
disableStorage: true,
});
}
trace.structLogs = utils.normalizeStructLogs(trace.structLogs);
const traceInfo = {
trace,
address,
Expand Down
82 changes: 57 additions & 25 deletions packages/sol-tracing-utils/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
let normalizedStructLogs = structLogs;
// HACK(leo): Geth traces sometimes returns those gas costs incorrectly as very big numbers so we manually fix them.
LogvinovLeon marked this conversation as resolved.
Show resolved Hide resolved
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, idx: number) => {
LogvinovLeon marked this conversation as resolved.
Show resolved Hide resolved
if (structLog.op === OpCode.Call) {
const HEX_BASE = 16;
const callAddress = parseInt(structLog.stack[0], HEX_BASE);
LogvinovLeon marked this conversation as resolved.
Show resolved Hide resolved
const MAX_REASONABLE_PRECOMPILE_ADDRESS = 100;
if (callAddress < MAX_REASONABLE_PRECOMPILE_ADDRESS) {
const nextStructLog = normalizedStructLogs[idx + 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,
depth: structLog.depth - 1,
gasCost: 0,
};
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;
} 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 structLogs;
return normalizedStructLogs;
},
getRange(sourceCode: string, range: SingleFileSourceRange): string {
const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line);
Expand Down