aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-tracing-utils
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2019-01-15 22:13:24 +0800
committerFabio Berger <me@fabioberger.com>2019-01-15 22:13:24 +0800
commit7c37d10d1f93791db75b1622efa775a361965c49 (patch)
tree7017104bea27120661f71701552214dc5efba909 /packages/sol-tracing-utils
parentaffd6170540034c31593a4f36ce1dbacb91b6b3e (diff)
parent18084588ea9fa724d6e32c9a49c79d49f189ba7c (diff)
downloaddexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.tar
dexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.tar.gz
dexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.tar.bz2
dexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.tar.lz
dexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.tar.xz
dexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.tar.zst
dexon-sol-tools-7c37d10d1f93791db75b1622efa775a361965c49.zip
Merge branch 'development' into feature/monorepo-scripts/publishToDockerHub
* development: (87 commits) Update packages/sol-tracing-utils/src/trace_collection_subprovider.ts Make mapping namings direct Remove unused tslint disable Revert "Remove logAsyncErrors hack" Remove logAsyncErrors hack Refactor logAsyncErrors to follow our conventions Export Sources and SourceCodes out of tracing utils Replace console.log with logUtils.log (#1515) strict decoding of return values using generics makerAssetFillAmount -> takerAssetFillAmount Ran prettier Linter Fix build after rebase Style cleanup for Compressed Calldata in Contract Wrappers PR Use simpler `_.find` to locate fillOrderBai Updated dutch auction wrapper Added back abi-gen-wrappers Renamed signatureParser.ts to signature_parser.ts Renamed decode rule `structsAsObjects` to `shouldConvertStructsToObjects` circle build failed. New commit to resubmit job. ...
Diffstat (limited to 'packages/sol-tracing-utils')
-rw-r--r--packages/sol-tracing-utils/CHANGELOG.json28
-rw-r--r--packages/sol-tracing-utils/CHANGELOG.md4
-rw-r--r--packages/sol-tracing-utils/package.json18
-rw-r--r--packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts13
-rw-r--r--packages/sol-tracing-utils/src/ast_visitor.ts45
-rw-r--r--packages/sol-tracing-utils/src/collect_coverage_entries.ts14
-rw-r--r--packages/sol-tracing-utils/src/get_source_range_snippet.ts8
-rw-r--r--packages/sol-tracing-utils/src/index.ts4
-rw-r--r--packages/sol-tracing-utils/src/source_maps.ts35
-rw-r--r--packages/sol-tracing-utils/src/trace.ts22
-rw-r--r--packages/sol-tracing-utils/src/trace_collection_subprovider.ts24
-rw-r--r--packages/sol-tracing-utils/src/trace_collector.ts8
-rw-r--r--packages/sol-tracing-utils/src/trace_info_subprovider.ts55
-rw-r--r--packages/sol-tracing-utils/src/types.ts13
-rw-r--r--packages/sol-tracing-utils/src/utils.ts6
-rw-r--r--packages/sol-tracing-utils/test/source_maps_test.ts6
-rw-r--r--packages/sol-tracing-utils/test/trace_test.ts4
17 files changed, 224 insertions, 83 deletions
diff --git a/packages/sol-tracing-utils/CHANGELOG.json b/packages/sol-tracing-utils/CHANGELOG.json
index 9c0b73169..5992016b1 100644
--- a/packages/sol-tracing-utils/CHANGELOG.json
+++ b/packages/sol-tracing-utils/CHANGELOG.json
@@ -1,12 +1,38 @@
[
{
+ "version": "4.0.0",
+ "changes": [
+ {
+ "note": "Fix a bug with incorrect parsing of `sourceMaps` due to sources being in an array instead of a map",
+ "pr": 1498
+ },
+ {
+ "note": "Change the types of `ContractData.sources` and `ContractData.sourceCodes` to be objects instead of arrays",
+ "pr": 1498
+ },
+ {
+ "note": "Use custom JS tracer to speed up tracing on clients that support it (e.g., Geth)",
+ "pr": 1498
+ },
+ {
+ "note": "Log errors encountered in `TraceCollectionSubprovider`",
+ "pr": 1498
+ },
+ {
+ "note": "Add support for assembly statements",
+ "pr": 1498
+ }
+ ]
+ },
+ {
"version": "3.0.0",
"changes": [
{
"note": "Move out specific tools and leave just the shared parts of the codebase",
"pr": 1492
}
- ]
+ ],
+ "timestamp": 1547225310
},
{
"timestamp": 1547040760,
diff --git a/packages/sol-tracing-utils/CHANGELOG.md b/packages/sol-tracing-utils/CHANGELOG.md
index c2bc3cd01..da998f905 100644
--- a/packages/sol-tracing-utils/CHANGELOG.md
+++ b/packages/sol-tracing-utils/CHANGELOG.md
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
+## v3.0.0 - _January 11, 2019_
+
+ * Move out specific tools and leave just the shared parts of the codebase (#1492)
+
## v2.1.17 - _January 9, 2019_
* Dependencies updated
diff --git a/packages/sol-tracing-utils/package.json b/packages/sol-tracing-utils/package.json
index 9486ab541..5021342fa 100644
--- a/packages/sol-tracing-utils/package.json
+++ b/packages/sol-tracing-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@0x/sol-tracing-utils",
- "version": "2.1.17",
+ "version": "3.0.0",
"engines": {
"node": ">=6.12"
},
@@ -42,14 +42,14 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-tracing-utils/README.md",
"dependencies": {
- "@0x/dev-utils": "^1.0.22",
- "@0x/sol-compiler": "^2.0.0",
- "@0x/subproviders": "^2.1.9",
- "@0x/typescript-typings": "^3.0.6",
- "@0x/utils": "^2.1.1",
- "@0x/web3-wrapper": "^3.2.2",
+ "@0x/dev-utils": "^1.0.23",
+ "@0x/sol-compiler": "^2.0.1",
+ "@0x/subproviders": "^2.1.10",
+ "@0x/typescript-typings": "^3.0.7",
+ "@0x/utils": "^3.0.0",
+ "@0x/web3-wrapper": "^3.2.3",
"@types/solidity-parser-antlr": "^0.2.0",
- "ethereum-types": "^1.1.4",
+ "ethereum-types": "^1.1.5",
"ethereumjs-util": "^5.1.1",
"glob": "^7.1.2",
"istanbul": "^0.4.5",
@@ -61,7 +61,7 @@
"solidity-parser-antlr": "^0.2.12"
},
"devDependencies": {
- "@0x/tslint-config": "^2.0.0",
+ "@0x/tslint-config": "^2.0.1",
"@types/istanbul": "^0.4.30",
"@types/loglevel": "^1.5.3",
"@types/mkdirp": "^0.5.1",
diff --git a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts
index 57391abbe..d52587f2c 100644
--- a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts
+++ b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts
@@ -5,7 +5,7 @@ import * as glob from 'glob';
import * as _ from 'lodash';
import * as path from 'path';
-import { ContractData } from '../types';
+import { ContractData, SourceCodes, Sources } from '../types';
import { AbstractArtifactAdapter } from './abstract_artifact_adapter';
@@ -43,9 +43,14 @@ export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter {
logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`);
continue;
}
- let sources = _.keys(artifact.sources);
- sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath));
- const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString());
+ const sources: Sources = {};
+ const sourceCodes: SourceCodes = {};
+ _.map(artifact.sources, (value: { id: number }, relativeFilePath: string) => {
+ const filePath = path.resolve(this._sourcesPath, relativeFilePath);
+ const fileContent = fs.readFileSync(filePath).toString();
+ sources[value.id] = filePath;
+ sourceCodes[value.id] = fileContent;
+ });
const contractData = {
sourceCodes,
sources,
diff --git a/packages/sol-tracing-utils/src/ast_visitor.ts b/packages/sol-tracing-utils/src/ast_visitor.ts
index e55cdf6ec..1ac9cd1de 100644
--- a/packages/sol-tracing-utils/src/ast_visitor.ts
+++ b/packages/sol-tracing-utils/src/ast_visitor.ts
@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import * as Parser from 'solidity-parser-antlr';
-import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types';
+import { BranchMap, FnMap, OffsetToLocation, SingleFileSourceRange, StatementMap } from './types';
export interface CoverageEntriesDescription {
fnMap: FnMap;
@@ -22,13 +22,13 @@ export class ASTVisitor {
private readonly _branchMap: BranchMap = {};
private readonly _modifiersStatementIds: number[] = [];
private readonly _statementMap: StatementMap = {};
- private readonly _locationByOffset: LocationByOffset;
+ private readonly _offsetToLocation: OffsetToLocation;
private readonly _ignoreRangesBeginningAt: number[];
// keep track of contract/function ranges that are to be ignored
// so we can also ignore any children nodes within the contract/function
private readonly _ignoreRangesWithin: Array<[number, number]> = [];
- constructor(locationByOffset: LocationByOffset, ignoreRangesBeginningAt: number[] = []) {
- this._locationByOffset = locationByOffset;
+ constructor(offsetToLocation: OffsetToLocation, ignoreRangesBeginningAt: number[] = []) {
+ this._offsetToLocation = offsetToLocation;
this._ignoreRangesBeginningAt = ignoreRangesBeginningAt;
}
public getCollectedCoverageEntries(): CoverageEntriesDescription {
@@ -94,6 +94,39 @@ export class ASTVisitor {
public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void {
this._visitStatement(ast);
}
+ public AssemblyLocalDefinition(ast: Parser.AssemblyLocalDefinition): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyCall(ast: Parser.AssemblyCall): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyIf(ast: Parser.AssemblyIf): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyBlock(ast: Parser.AssemblyBlock): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyExpression(ast: Parser.AssemblyExpression): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyAssignment(ast: Parser.AssemblyAssignment): void {
+ this._visitStatement(ast);
+ }
+ public LabelDefinition(ast: Parser.LabelDefinition): void {
+ this._visitStatement(ast);
+ }
+ public AssemblySwitch(ast: Parser.AssemblySwitch): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyFunctionDefinition(ast: Parser.AssemblyFunctionDefinition): void {
+ this._visitStatement(ast);
+ }
+ public AssemblyFor(ast: Parser.AssemblyFor): void {
+ this._visitStatement(ast);
+ }
+ public SubAssembly(ast: Parser.SubAssembly): void {
+ this._visitStatement(ast);
+ }
public BinaryOperation(ast: Parser.BinaryOperation): void {
const BRANCHING_BIN_OPS = ['&&', '||'];
if (_.includes(BRANCHING_BIN_OPS, ast.operator)) {
@@ -136,8 +169,8 @@ export class ASTVisitor {
}
private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange {
const astRange = ast.range as [number, number];
- const start = this._locationByOffset[astRange[0]];
- const end = this._locationByOffset[astRange[1] + 1];
+ const start = this._offsetToLocation[astRange[0]];
+ const end = this._offsetToLocation[astRange[1] + 1];
const range = {
start,
end,
diff --git a/packages/sol-tracing-utils/src/collect_coverage_entries.ts b/packages/sol-tracing-utils/src/collect_coverage_entries.ts
index bdbcd613e..9e3591d74 100644
--- a/packages/sol-tracing-utils/src/collect_coverage_entries.ts
+++ b/packages/sol-tracing-utils/src/collect_coverage_entries.ts
@@ -3,24 +3,24 @@ import * as _ from 'lodash';
import * as parser from 'solidity-parser-antlr';
import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor';
-import { getLocationByOffset } from './source_maps';
+import { getOffsetToLocation } from './source_maps';
const IGNORE_RE = /\/\*\s*solcov\s+ignore\s+next\s*\*\/\s*/gm;
// Parsing source code for each transaction/code is slow and therefore we cache it
-const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {};
+const sourceHashToCoverageEntries: { [sourceHash: string]: CoverageEntriesDescription } = {};
export const collectCoverageEntries = (contractSource: string) => {
const sourceHash = ethUtil.sha3(contractSource).toString('hex');
- if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) {
+ if (_.isUndefined(sourceHashToCoverageEntries[sourceHash]) && !_.isUndefined(contractSource)) {
const ast = parser.parse(contractSource, { range: true });
- const locationByOffset = getLocationByOffset(contractSource);
+ const offsetToLocation = getOffsetToLocation(contractSource);
const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource);
- const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt);
+ const visitor = new ASTVisitor(offsetToLocation, ignoreRangesBegingingAt);
parser.visit(ast, visitor);
- coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries();
+ sourceHashToCoverageEntries[sourceHash] = visitor.getCollectedCoverageEntries();
}
- const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash];
+ const coverageEntriesDescription = sourceHashToCoverageEntries[sourceHash];
return coverageEntriesDescription;
};
diff --git a/packages/sol-tracing-utils/src/get_source_range_snippet.ts b/packages/sol-tracing-utils/src/get_source_range_snippet.ts
index f578675d3..7aef00fee 100644
--- a/packages/sol-tracing-utils/src/get_source_range_snippet.ts
+++ b/packages/sol-tracing-utils/src/get_source_range_snippet.ts
@@ -13,7 +13,7 @@ interface ASTInfo {
}
// Parsing source code for each transaction/code is slow and therefore we cache it
-const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {};
+const hashToParsedSource: { [sourceHash: string]: Parser.ASTNode } = {};
/**
* Gets the source range snippet by source range to be used by revert trace.
@@ -22,10 +22,10 @@ const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {};
*/
export function getSourceRangeSnippet(sourceRange: SourceRange, sourceCode: string): SourceSnippet | null {
const sourceHash = ethUtil.sha3(sourceCode).toString('hex');
- if (_.isUndefined(parsedSourceByHash[sourceHash])) {
- parsedSourceByHash[sourceHash] = Parser.parse(sourceCode, { loc: true });
+ if (_.isUndefined(hashToParsedSource[sourceHash])) {
+ hashToParsedSource[sourceHash] = Parser.parse(sourceCode, { loc: true });
}
- const astNode = parsedSourceByHash[sourceHash];
+ const astNode = hashToParsedSource[sourceHash];
const visitor = new ASTInfoVisitor();
Parser.visit(astNode, visitor);
const astInfo = visitor.getASTInfoForRange(sourceRange);
diff --git a/packages/sol-tracing-utils/src/index.ts b/packages/sol-tracing-utils/src/index.ts
index 413e5305e..fdf024ae0 100644
--- a/packages/sol-tracing-utils/src/index.ts
+++ b/packages/sol-tracing-utils/src/index.ts
@@ -22,11 +22,13 @@ export {
BranchMap,
EvmCallStackEntry,
FnMap,
- LocationByOffset,
+ OffsetToLocation,
StatementMap,
TraceInfoBase,
TraceInfoExistingContract,
TraceInfoNewContract,
+ Sources,
+ SourceCodes,
} from './types';
export { collectCoverageEntries } from './collect_coverage_entries';
export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector';
diff --git a/packages/sol-tracing-utils/src/source_maps.ts b/packages/sol-tracing-utils/src/source_maps.ts
index af0fb4035..8c17652d9 100644
--- a/packages/sol-tracing-utils/src/source_maps.ts
+++ b/packages/sol-tracing-utils/src/source_maps.ts
@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import { getPcToInstructionIndexMapping } from './instructions';
-import { LocationByOffset, SourceRange } from './types';
+import { OffsetToLocation, SourceCodes, SourceRange, Sources } from './types';
const RADIX = 10;
@@ -15,38 +15,41 @@ export interface SourceLocation {
* Receives a string with newlines and returns a map of byte offset to LineColumn
* @param str A string to process
*/
-export function getLocationByOffset(str: string): LocationByOffset {
- const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } };
+export function getOffsetToLocation(str: string): OffsetToLocation {
+ const offsetToLocation: OffsetToLocation = { 0: { line: 1, column: 0 } };
let currentOffset = 0;
for (const char of str.split('')) {
- const location = locationByOffset[currentOffset];
+ const location = offsetToLocation[currentOffset];
const isNewline = char === '\n';
- locationByOffset[currentOffset + 1] = {
+ offsetToLocation[currentOffset + 1] = {
line: location.line + (isNewline ? 1 : 0),
column: isNewline ? 0 : location.column + 1,
};
currentOffset++;
}
- return locationByOffset;
+ return offsetToLocation;
}
/**
* Parses a sourcemap string.
* The solidity sourcemap format is documented here: https://github.com/ethereum/solidity/blob/develop/docs/miscellaneous.rst#source-mappings
- * @param sourceCodes sources contents
+ * @param indexToSourceCode index to source code
* @param srcMap source map string
* @param bytecodeHex contract bytecode
- * @param sources sources file names
+ * @param indexToSource index to source file path
*/
export function parseSourceMap(
- sourceCodes: string[],
+ sourceCodes: SourceCodes,
srcMap: string,
bytecodeHex: string,
- sources: string[],
+ sources: Sources,
): { [programCounter: number]: SourceRange } {
const bytecode = Uint8Array.from(Buffer.from(bytecodeHex, 'hex'));
const pcToInstructionIndex: { [programCounter: number]: number } = getPcToInstructionIndexMapping(bytecode);
- const locationByOffsetByFileIndex = _.map(sourceCodes, s => (_.isUndefined(s) ? {} : getLocationByOffset(s)));
+ const fileIndexToOffsetToLocation: { [fileIndex: number]: OffsetToLocation } = {};
+ _.map(sourceCodes, (sourceCode: string, fileIndex: number) => {
+ fileIndexToOffsetToLocation[fileIndex] = _.isUndefined(sourceCode) ? {} : getOffsetToLocation(sourceCode);
+ });
const entries = srcMap.split(';');
let lastParsedEntry: SourceLocation = {} as any;
const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {};
@@ -66,14 +69,18 @@ export function parseSourceMap(
length,
fileIndex,
};
- if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) {
+ if (parsedEntry.fileIndex !== -1 && !_.isUndefined(fileIndexToOffsetToLocation[parsedEntry.fileIndex])) {
+ const offsetToLocation = fileIndexToOffsetToLocation[parsedEntry.fileIndex];
const sourceRange = {
location: {
- start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset],
- end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length],
+ start: offsetToLocation[parsedEntry.offset],
+ end: offsetToLocation[parsedEntry.offset + parsedEntry.length],
},
fileName: sources[parsedEntry.fileIndex],
};
+ if (sourceRange.location.start === undefined || sourceRange.location.end === undefined) {
+ throw new Error(`Error while processing sourcemap: location out of range in ${sourceRange.fileName}`);
+ }
instructionIndexToSourceRange[i] = sourceRange;
} else {
// Some assembly code generated by Solidity can't be mapped back to a line of source code.
diff --git a/packages/sol-tracing-utils/src/trace.ts b/packages/sol-tracing-utils/src/trace.ts
index 770080af3..973452b24 100644
--- a/packages/sol-tracing-utils/src/trace.ts
+++ b/packages/sol-tracing-utils/src/trace.ts
@@ -4,21 +4,21 @@ import * as _ from 'lodash';
import { utils } from './utils';
-export interface TraceByContractAddress {
+export interface ContractAddressToTraces {
[contractAddress: string]: StructLog[];
}
/**
- * Converts linear stack trace to `TraceByContractAddress`.
+ * Converts linear stack trace to `ContractAddressToTraces`.
* @param structLogs stack trace
* @param startAddress initial context address
*/
-export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress {
- const traceByContractAddress: TraceByContractAddress = {};
+export function getContractAddressToTraces(structLogs: StructLog[], startAddress: string): ContractAddressToTraces {
+ const contractAddressToTraces: ContractAddressToTraces = {};
let currentTraceSegment = [];
const addressStack = [startAddress];
if (_.isEmpty(structLogs)) {
- return traceByContractAddress;
+ return contractAddressToTraces;
}
const normalizedStructLogs = utils.normalizeStructLogs(structLogs);
// tslint:disable-next-line:prefer-for-of
@@ -45,14 +45,14 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
const nextStructLog = normalizedStructLogs[i + 1];
if (nextStructLog.depth !== structLog.depth) {
addressStack.push(newAddress);
- traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
+ contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
currentTraceSegment,
);
currentTraceSegment = [];
}
} else if (utils.isEndOpcode(structLog.op)) {
const currentAddress = addressStack.pop() as string;
- traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
+ contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
currentTraceSegment,
);
currentTraceSegment = [];
@@ -71,7 +71,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
logUtils.warn(
"Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace",
);
- return traceByContractAddress;
+ return contractAddressToTraces;
} else {
if (structLog !== _.last(normalizedStructLogs)) {
const nextStructLog = normalizedStructLogs[i + 1];
@@ -79,7 +79,7 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
continue;
} else if (nextStructLog.depth === structLog.depth - 1) {
const currentAddress = addressStack.pop() as string;
- traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
+ contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
currentTraceSegment,
);
currentTraceSegment = [];
@@ -94,11 +94,11 @@ export function getTracesByContractAddress(structLogs: StructLog[], startAddress
}
if (currentTraceSegment.length !== 0) {
const currentAddress = addressStack.pop() as string;
- traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat(
+ contractAddressToTraces[currentAddress] = (contractAddressToTraces[currentAddress] || []).concat(
currentTraceSegment,
);
currentTraceSegment = [];
logUtils.warn('Malformed trace. Current trace segment non empty at the end');
}
- return traceByContractAddress;
+ return contractAddressToTraces;
}
diff --git a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts
index 25e38768d..323e1523c 100644
--- a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts
+++ b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts
@@ -1,5 +1,6 @@
import { BlockchainLifecycle } from '@0x/dev-utils';
import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders';
+import { logUtils } from '@0x/utils';
import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper';
import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types';
import * as _ from 'lodash';
@@ -20,6 +21,23 @@ export interface TraceCollectionSubproviderConfig {
shouldCollectGasEstimateTraces: boolean;
}
+type AsyncFunc = (...args: any[]) => Promise<void>;
+
+// HACK: This wrapper outputs errors to console even if the promise gets ignored
+// we need this because web3-provider-engine does not handle promises in
+// the after function of next(after).
+function logAsyncErrors(fn: AsyncFunc): AsyncFunc {
+ async function wrappedAsync(...args: any[]): Promise<void> {
+ try {
+ await fn(...args);
+ } catch (err) {
+ logUtils.log(err);
+ throw err;
+ }
+ }
+ return wrappedAsync;
+}
+
// Because there is no notion of a call trace in the Ethereum rpc - we collect them in a rather non-obvious/hacky way.
// On each call - we create a snapshot, execute the call as a transaction, get the trace, revert the snapshot.
// That allows us to avoid influencing test behaviour.
@@ -74,7 +92,7 @@ export abstract class TraceCollectionSubprovider extends Subprovider {
next();
} else {
const txData = payload.params[0];
- next(this._onTransactionSentAsync.bind(this, txData));
+ next(logAsyncErrors(this._onTransactionSentAsync.bind(this, txData)));
}
return;
@@ -83,7 +101,7 @@ export abstract class TraceCollectionSubprovider extends Subprovider {
next();
} else {
const callData = payload.params[0];
- next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData));
+ next(logAsyncErrors(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)));
}
return;
@@ -92,7 +110,7 @@ export abstract class TraceCollectionSubprovider extends Subprovider {
next();
} else {
const estimateGasData = payload.params[0];
- next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData));
+ next(logAsyncErrors(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)));
}
return;
diff --git a/packages/sol-tracing-utils/src/trace_collector.ts b/packages/sol-tracing-utils/src/trace_collector.ts
index 943e208cf..f5dde8762 100644
--- a/packages/sol-tracing-utils/src/trace_collector.ts
+++ b/packages/sol-tracing-utils/src/trace_collector.ts
@@ -56,7 +56,7 @@ export class TraceCollector {
this._singleFileSubtraceHandler = singleFileSubtraceHandler;
}
public async writeOutputAsync(): Promise<void> {
- const finalCoverage = this._collector.getFinalCoverage();
+ const finalCoverage: Coverage = this._collector.getFinalCoverage();
const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t');
await mkdirpAsync('coverage');
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
@@ -80,14 +80,14 @@ export class TraceCollector {
const bytecodeHex = stripHexPrefix(bytecode);
const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime;
const pcToSourceRange = parseSourceMap(contractData.sourceCodes, sourceMap, bytecodeHex, contractData.sources);
- for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
+ _.map(contractData.sources, (_sourcePath: string, fileIndex: string) => {
const singleFileCoverageForTrace = this._singleFileSubtraceHandler(
contractData,
traceInfo.subtrace,
pcToSourceRange,
- fileIndex,
+ _.parseInt(fileIndex),
);
this._collector.add(singleFileCoverageForTrace);
- }
+ });
}
}
diff --git a/packages/sol-tracing-utils/src/trace_info_subprovider.ts b/packages/sol-tracing-utils/src/trace_info_subprovider.ts
index 635a68f58..b75fc7bf7 100644
--- a/packages/sol-tracing-utils/src/trace_info_subprovider.ts
+++ b/packages/sol-tracing-utils/src/trace_info_subprovider.ts
@@ -1,7 +1,8 @@
+import { NodeType } from '@0x/web3-wrapper';
import * as _ from 'lodash';
import { constants } from './constants';
-import { getTracesByContractAddress } from './trace';
+import { getContractAddressToTraces } from './trace';
import { TraceCollectionSubprovider } from './trace_collection_subprovider';
import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types';
@@ -12,18 +13,50 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void>;
protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0);
- const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, {
- disableMemory: true,
- disableStack: false,
- disableStorage: true,
- });
- const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
- const subcallAddresses = _.keys(tracesByContractAddress);
+ const nodeType = await this._web3Wrapper.getNodeTypeAsync();
+ let trace;
+ if (nodeType === NodeType.Geth) {
+ // For very large traces we use a custom tracer that outputs a format compatible with a
+ // regular trace. We only need the 2nd item on the stack when the instruction is a call.
+ // By not including other stack values, we drastically limit the amount of data to be collected.
+ // There are no good docs about how to write those tracers, but you can find some example ones here:
+ // https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracers
+ const tracer = `
+ {
+ data: [],
+ step: function(log) {
+ const op = log.op.toString();
+ const opn = 0 | log.op.toNumber();
+ const pc = 0 | log.getPC();
+ const depth = 0 | log.getDepth();
+ const gasCost = 0 | log.getCost();
+ const gas = 0 | log.getGas();
+ const isCall = opn == 0xf1 || opn == 0xf2 || opn == 0xf4 || opn == 0xf5;
+ const stack = isCall ? ['0x'+log.stack.peek(1).toString(16), null] : null;
+ this.data.push({ pc, gasCost, depth, op, stack, gas });
+ },
+ fault: function() { },
+ result: function() { return {structLogs: this.data}; }
+ }
+ `;
+ trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { tracer, timeout: '600s' });
+ } else {
+ /**
+ * Ganache doesn't support custom tracers yet.
+ */
+ trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, {
+ disableMemory: true,
+ disableStack: false,
+ disableStorage: true,
+ });
+ }
+ const contractAddressToTraces = getContractAddressToTraces(trace.structLogs, address);
+ const subcallAddresses = _.keys(contractAddressToTraces);
if (address === constants.NEW_CONTRACT) {
for (const subcallAddress of subcallAddresses) {
let traceInfo: TraceInfoNewContract | TraceInfoExistingContract;
if (subcallAddress === 'NEW_CONTRACT') {
- const traceForThatSubcall = tracesByContractAddress[subcallAddress];
+ const traceForThatSubcall = contractAddressToTraces[subcallAddress];
traceInfo = {
subtrace: traceForThatSubcall,
txHash,
@@ -32,7 +65,7 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
};
} else {
const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
- const traceForThatSubcall = tracesByContractAddress[subcallAddress];
+ const traceForThatSubcall = contractAddressToTraces[subcallAddress];
traceInfo = {
subtrace: traceForThatSubcall,
txHash,
@@ -45,7 +78,7 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
} else {
for (const subcallAddress of subcallAddresses) {
const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
- const traceForThatSubcall = tracesByContractAddress[subcallAddress];
+ const traceForThatSubcall = contractAddressToTraces[subcallAddress];
const traceInfo: TraceInfoExistingContract = {
subtrace: traceForThatSubcall,
txHash,
diff --git a/packages/sol-tracing-utils/src/types.ts b/packages/sol-tracing-utils/src/types.ts
index 54ade0400..27568ae03 100644
--- a/packages/sol-tracing-utils/src/types.ts
+++ b/packages/sol-tracing-utils/src/types.ts
@@ -16,7 +16,7 @@ export interface SingleFileSourceRange {
end: LineColumn;
}
-export interface LocationByOffset {
+export interface OffsetToLocation {
[offset: number]: LineColumn;
}
@@ -76,13 +76,20 @@ export interface Coverage {
};
}
+export interface SourceCodes {
+ [sourceId: number]: string;
+}
+export interface Sources {
+ [sourceId: number]: string;
+}
+
export interface ContractData {
bytecode: string;
sourceMap: string;
runtimeBytecode: string;
sourceMapRuntime: string;
- sourceCodes: string[];
- sources: string[];
+ sourceCodes: SourceCodes;
+ sources: Sources;
}
// Part of the trace executed within the same context
diff --git a/packages/sol-tracing-utils/src/utils.ts b/packages/sol-tracing-utils/src/utils.ts
index d8bc65e73..644321f32 100644
--- a/packages/sol-tracing-utils/src/utils.ts
+++ b/packages/sol-tracing-utils/src/utils.ts
@@ -23,6 +23,12 @@ export const utils = {
utils.compareLineColumn(childRange.end, parentRange.end) <= 0
);
},
+ isRangeEqual(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean {
+ return (
+ utils.compareLineColumn(parentRange.start, childRange.start) === 0 &&
+ utils.compareLineColumn(childRange.end, parentRange.end) === 0
+ );
+ },
bytecodeToBytecodeRegex(bytecode: string): string {
const bytecodeRegex = bytecode
// Library linking placeholder: __ConvertLib____________________________
diff --git a/packages/sol-tracing-utils/test/source_maps_test.ts b/packages/sol-tracing-utils/test/source_maps_test.ts
index 5820bedd7..330a6a3e1 100644
--- a/packages/sol-tracing-utils/test/source_maps_test.ts
+++ b/packages/sol-tracing-utils/test/source_maps_test.ts
@@ -4,7 +4,7 @@ import * as _ from 'lodash';
import 'mocha';
import * as path from 'path';
-import { getLocationByOffset, parseSourceMap } from '../src/source_maps';
+import { getOffsetToLocation, parseSourceMap } from '../src/source_maps';
const expect = chai.expect;
@@ -15,7 +15,7 @@ const simplestContract = fs.readFileSync(simplestContractFileName).toString();
describe('source maps', () => {
describe('#getLocationByOffset', () => {
it('correctly computes location by offset', () => {
- const locationByOffset = getLocationByOffset(simplestContract);
+ const offsetToLocation = getOffsetToLocation(simplestContract);
const expectedLocationByOffset = {
'0': { line: 1, column: 0 },
'1': { line: 1, column: 1 },
@@ -41,7 +41,7 @@ describe('source maps', () => {
'21': { line: 2, column: 1 },
'22': { line: 3, column: 0 },
};
- expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset);
+ expect(offsetToLocation).to.be.deep.equal(expectedLocationByOffset);
});
});
describe('#parseSourceMap', () => {
diff --git a/packages/sol-tracing-utils/test/trace_test.ts b/packages/sol-tracing-utils/test/trace_test.ts
index 7a034362c..c9ed93e63 100644
--- a/packages/sol-tracing-utils/test/trace_test.ts
+++ b/packages/sol-tracing-utils/test/trace_test.ts
@@ -3,7 +3,7 @@ import { OpCode, StructLog } from 'ethereum-types';
import * as _ from 'lodash';
import 'mocha';
-import { getTracesByContractAddress } from '../src/trace';
+import { getContractAddressToTraces } from '../src/trace';
const expect = chai.expect;
@@ -44,7 +44,7 @@ describe('Trace', () => {
];
const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog));
const startAddress = '0x0000000000000000000000000000000000000001';
- const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress);
+ const traceByContractAddress = getContractAddressToTraces(fullTrace, startAddress);
const expectedTraceByContractAddress = {
[startAddress]: [fullTrace[0], fullTrace[2]],
[delegateCallAddress]: [fullTrace[1]],