Merge pull request #1663 from 0xProject/fix/sol-profiler
Sol profiler improvements and bug fixes
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "3.1.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Bug fixes related to stack parameters parsing",
|
||||
"pr": 1663
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1551299797,
|
||||
"version": "3.1.1",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TraceInfo } from '@0x/sol-tracing-utils';
|
||||
import { constants, TraceInfo } from '@0x/sol-tracing-utils';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { OpCode } from 'ethereum-types';
|
||||
import { stripHexPrefix } from 'ethereumjs-util';
|
||||
@@ -47,11 +47,17 @@ export const costUtils = {
|
||||
);
|
||||
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);
|
||||
const memoryOffsetStackOffset = constants.opCodeToParamToStackOffset[structLog.op as any].memoryOffset;
|
||||
const lengthStackOffset = constants.opCodeToParamToStackOffset[structLog.op as any].length;
|
||||
const memOffset = parseInt(
|
||||
structLog.stack[structLog.stack.length - memoryOffsetStackOffset - 1],
|
||||
HEX_BASE,
|
||||
);
|
||||
const length = parseInt(structLog.stack[structLog.stack.length - lengthStackOffset - 1], HEX_BASE);
|
||||
return memOffset + length;
|
||||
} else {
|
||||
return parseInt(structLog.stack[structLog.stack.length - 1], HEX_BASE);
|
||||
const memoryLocationStackOffset = constants.opCodeToParamToStackOffset[structLog.op].offset;
|
||||
return parseInt(structLog.stack[structLog.stack.length - memoryLocationStackOffset - 1], HEX_BASE);
|
||||
}
|
||||
});
|
||||
const highestMemoryLocationAccessed = _.max(memoryLocationsAccessed);
|
||||
@@ -62,7 +68,8 @@ export const costUtils = {
|
||||
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);
|
||||
const lengthStackOffset = constants.opCodeToParamToStackOffset[structLog.op as any].length;
|
||||
const length = parseInt(structLog.stack[structLog.stack.length - lengthStackOffset - 1], HEX_BASE);
|
||||
return Math.ceil(length / WORD_SIZE) * G_COPY;
|
||||
});
|
||||
return _.sum(copyCosts);
|
||||
|
||||
@@ -65,7 +65,7 @@ export class ProfilerSubprovider extends TraceInfoSubprovider {
|
||||
const transactionBaseCost = BASE_COST;
|
||||
let totalCost = callDataCost + opcodesCost + BASE_COST;
|
||||
logUtils.header('Final breakdown', '-');
|
||||
if (!_.isNull(receipt.contractAddress)) {
|
||||
if (_.isString(receipt.contractAddress)) {
|
||||
const code = await this._web3Wrapper.getContractCodeAsync(receipt.contractAddress);
|
||||
const codeBuff = Buffer.from(stripHexPrefix(code), 'hex');
|
||||
const codeLength = codeBuff.length;
|
||||
@@ -125,17 +125,22 @@ export const profilerHandler: SingleFileSubtraceHandler = (
|
||||
const profilerEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]);
|
||||
const statementToGasConsumed: { [statementId: string]: number } = {};
|
||||
const statementIds = _.keys(profilerEntriesDescription.statementMap);
|
||||
// `interestingStructLogs` are those that map back to source ranges within the current file.
|
||||
// It also doesn't include any that cannot be mapped back
|
||||
// This is a perf optimization reducing the work done in the loop over `statementIds`.
|
||||
// TODO(logvinov): Optimize the loop below.
|
||||
const interestingStructLogs = _.filter(subtrace, structLog => {
|
||||
const sourceRange = pcToSourceRange[structLog.pc];
|
||||
if (_.isUndefined(sourceRange)) {
|
||||
return false;
|
||||
}
|
||||
return sourceRange.fileName === absoluteFileName;
|
||||
});
|
||||
for (const statementId of statementIds) {
|
||||
const statementDescription = profilerEntriesDescription.statementMap[statementId];
|
||||
const totalGasCost = _.sum(
|
||||
_.map(subtrace, structLog => {
|
||||
_.map(interestingStructLogs, structLog => {
|
||||
const sourceRange = pcToSourceRange[structLog.pc];
|
||||
if (_.isUndefined(sourceRange)) {
|
||||
return 0;
|
||||
}
|
||||
if (sourceRange.fileName !== absoluteFileName) {
|
||||
return 0;
|
||||
}
|
||||
if (utils.isRangeInside(sourceRange.location, statementDescription)) {
|
||||
return structLog.gasCost;
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
[
|
||||
{
|
||||
"version": "6.0.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fix a bug when `TruffleArtifactAdapter` wasn't correctly parsing solc config in pre-5.0 versions of Truffle",
|
||||
"pr": 1663
|
||||
},
|
||||
{
|
||||
"note": "Fix a bug when `opCodes` gas costs were incorrect or `NaN`",
|
||||
"pr": 1663
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "6.0.6",
|
||||
"changes": [
|
||||
|
||||
@@ -83,7 +83,7 @@ export class TruffleArtifactAdapter extends AbstractArtifactAdapter {
|
||||
const truffleConfig = this._getTruffleConfig();
|
||||
if (!_.isUndefined(truffleConfig.solc)) {
|
||||
// Truffle < 5.0
|
||||
return (truffleConfig as any).solc.settings;
|
||||
return (truffleConfig as any).solc;
|
||||
} else if (!_.isUndefined((truffleConfig as any).compilers.solc)) {
|
||||
// Truffle >= 5.0
|
||||
return (truffleConfig as any).compilers.solc.settings;
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
import { OpCode } from 'ethereum-types';
|
||||
|
||||
import { OpCodeToGasCost, OpCodeToParamToStackOffset } from './types';
|
||||
|
||||
const opCodeToParamToStackOffset: OpCodeToParamToStackOffset = {
|
||||
[OpCode.Call]: {
|
||||
gas: 0,
|
||||
to: 1,
|
||||
value: 1,
|
||||
},
|
||||
[OpCode.MLoad]: { offset: 0 },
|
||||
[OpCode.MStore]: { offset: 0 },
|
||||
[OpCode.MStore8]: { offset: 0 },
|
||||
[OpCode.CallDataCopy]: { memoryOffset: 0, callDataOffset: 1, length: 2 },
|
||||
};
|
||||
|
||||
const opCodeToGasCost: OpCodeToGasCost = {
|
||||
[OpCode.Call]: 700,
|
||||
[OpCode.StaticCall]: 40,
|
||||
};
|
||||
|
||||
// tslint:disable:number-literal-format
|
||||
export const constants = {
|
||||
NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT',
|
||||
HEX_BASE: 16,
|
||||
PUSH1: 0x60,
|
||||
PUSH2: 0x61,
|
||||
PUSH32: 0x7f,
|
||||
TIMESTAMP: 0x42,
|
||||
opCodeToGasCost,
|
||||
opCodeToParamToStackOffset,
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { OpCode, StructLog } from 'ethereum-types';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { EvmCallStack } from './types';
|
||||
import { utils } from './utils';
|
||||
|
||||
@@ -29,9 +30,8 @@ export function getRevertTrace(structLogs: StructLog[], startAddress: string): E
|
||||
|
||||
if (utils.isCallLike(structLog.op)) {
|
||||
const currentAddress = _.last(addressStack) as string;
|
||||
const jumpAddressOffset = 1;
|
||||
const newAddress = utils.getAddressFromStackEntry(
|
||||
structLog.stack[structLog.stack.length - jumpAddressOffset - 1],
|
||||
structLog.stack[structLog.stack.length - constants.opCodeToParamToStackOffset[OpCode.Call].to - 1],
|
||||
);
|
||||
|
||||
// Sometimes calls don't change the execution context (current address). When we do a transfer to an
|
||||
|
||||
@@ -2,6 +2,7 @@ import { logUtils } from '@0x/utils';
|
||||
import { OpCode, StructLog } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { utils } from './utils';
|
||||
|
||||
export interface ContractAddressToTraces {
|
||||
@@ -33,9 +34,8 @@ export function getContractAddressToTraces(structLogs: StructLog[], startAddress
|
||||
|
||||
if (utils.isCallLike(structLog.op)) {
|
||||
const currentAddress = _.last(addressStack) as string;
|
||||
const jumpAddressOffset = 1;
|
||||
const newAddress = utils.getAddressFromStackEntry(
|
||||
structLog.stack[structLog.stack.length - jumpAddressOffset - 1],
|
||||
structLog.stack[structLog.stack.length - constants.opCodeToParamToStackOffset[OpCode.Call].to - 1],
|
||||
);
|
||||
|
||||
// Sometimes calls don't change the execution context (current address). When we do a transfer to an
|
||||
|
||||
@@ -52,11 +52,11 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
|
||||
const isCallDataAccess = opn == 0x37;
|
||||
var stack;
|
||||
if (isCall) {
|
||||
stack = [null, '0x'+log.stack.peek(1).toString(16)];
|
||||
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(0).toString(16), '0x'+log.stack.peek(1).toString(16), '0x'+log.stack.peek(2).toString(16)];
|
||||
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 });
|
||||
},
|
||||
|
||||
@@ -136,3 +136,13 @@ export interface SourceSnippet {
|
||||
fileName: string;
|
||||
range: SingleFileSourceRange;
|
||||
}
|
||||
|
||||
export interface OpCodeToParamToStackOffset {
|
||||
[opCode: string]: {
|
||||
[param: string]: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OpCodeToGasCost {
|
||||
[opCode: string]: number;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ import { OpCode, StructLog } from 'ethereum-types';
|
||||
import { addHexPrefix } from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { ContractData, LineColumn, SingleFileSourceRange } from './types';
|
||||
|
||||
const STATICCALL_GAS_COST = 40;
|
||||
const CALL_GAS_COST = 700;
|
||||
|
||||
const bytecodeToContractDataIfExists: { [bytecode: string]: ContractData | undefined } = {};
|
||||
|
||||
export const utils = {
|
||||
@@ -95,14 +93,16 @@ export const utils = {
|
||||
structLog.op === OpCode.StaticCall
|
||||
? {
|
||||
...structLog,
|
||||
gasCost: STATICCALL_GAS_COST,
|
||||
gasCost: constants.opCodeToGasCost[structLog.op],
|
||||
}
|
||||
: 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 callAddress = parseInt(
|
||||
structLog.stack[structLog.stack.length - constants.opCodeToParamToStackOffset[OpCode.Call].to - 1],
|
||||
constants.HEX_BASE,
|
||||
);
|
||||
const MAX_REASONABLE_PRECOMPILE_ADDRESS = 100;
|
||||
if (callAddress < MAX_REASONABLE_PRECOMPILE_ADDRESS) {
|
||||
const nextStructLog = normalizedStructLogs[index + 1];
|
||||
@@ -114,7 +114,7 @@ export const utils = {
|
||||
} else {
|
||||
return {
|
||||
...structLog,
|
||||
gasCost: CALL_GAS_COST,
|
||||
gasCost: constants.opCodeToGasCost[structLog.op],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
@@ -138,12 +138,12 @@ export const utils = {
|
||||
};
|
||||
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);
|
||||
normalizedStructLogs = _.map(normalizedStructLogs, reduceDepthBy1);
|
||||
normalizedStructLogs = _.map(normalizedStructLogs, normalizeCallCost);
|
||||
normalizedStructLogs = _.map(normalizedStructLogs, normalizeStaticCallCost);
|
||||
} else {
|
||||
// Ganache shifts opcodes gas costs so we need to unshift them
|
||||
normalizedStructLogs = _.map(structLogs, shiftGasCosts1Left);
|
||||
normalizedStructLogs = _.map(normalizedStructLogs, shiftGasCosts1Left);
|
||||
}
|
||||
return normalizedStructLogs;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user