aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/devnet/genesis.json2
-rw-r--r--packages/ethereum-types/src/index.ts2
-rw-r--r--packages/sol-coverage/src/coverage_subprovider.ts2
-rw-r--r--packages/sol-trace/src/revert_trace_subprovider.ts3
-rw-r--r--packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts11
-rw-r--r--packages/sol-tracing-utils/src/ast_visitor.ts33
-rw-r--r--packages/sol-tracing-utils/src/source_maps.ts21
-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.ts27
-rw-r--r--packages/sol-tracing-utils/src/types.ts4
-rw-r--r--packages/sol-tracing-utils/src/utils.ts6
12 files changed, 116 insertions, 27 deletions
diff --git a/packages/devnet/genesis.json b/packages/devnet/genesis.json
index 073672dd9..03dc5d623 100644
--- a/packages/devnet/genesis.json
+++ b/packages/devnet/genesis.json
@@ -8,7 +8,7 @@
"eip158Block": 3,
"byzantiumBlock": 4,
"clique": {
- "period": 0,
+ "period": 1,
"epoch": 30000
}
},
diff --git a/packages/ethereum-types/src/index.ts b/packages/ethereum-types/src/index.ts
index 9430fdc98..a8dcfd68a 100644
--- a/packages/ethereum-types/src/index.ts
+++ b/packages/ethereum-types/src/index.ts
@@ -306,6 +306,8 @@ export interface TraceParams {
disableMemory?: boolean;
disableStack?: boolean;
disableStorage?: boolean;
+ tracer?: string;
+ timeout?: string;
}
export type OutputField =
diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts
index e6b546c4a..21d25ed74 100644
--- a/packages/sol-coverage/src/coverage_subprovider.ts
+++ b/packages/sol-coverage/src/coverage_subprovider.ts
@@ -74,7 +74,7 @@ export const coverageHandler: SingleFileSubtraceHandler = (
let sourceRanges = _.map(subtrace, structLog => pcToSourceRange[structLog.pc]);
sourceRanges = _.compact(sourceRanges); // Some PC's don't map to a source range and we just ignore them.
- // By default lodash does a shallow object comparasion. We JSON.stringify them and compare as strings.
+ // By default lodash does a shallow object comparison. We JSON.stringify them and compare as strings.
sourceRanges = _.uniqBy(sourceRanges, s => JSON.stringify(s)); // We don't care if one PC was covered multiple times within a single transaction
sourceRanges = _.filter(sourceRanges, sourceRange => sourceRange.fileName === absoluteFileName);
const branchCoverage: BranchCoverage = {};
diff --git a/packages/sol-trace/src/revert_trace_subprovider.ts b/packages/sol-trace/src/revert_trace_subprovider.ts
index 31067a402..d36cba08b 100644
--- a/packages/sol-trace/src/revert_trace_subprovider.ts
+++ b/packages/sol-trace/src/revert_trace_subprovider.ts
@@ -106,7 +106,8 @@ export class RevertTraceSubprovider extends TraceCollectionSubprovider {
continue;
}
- const fileIndex = contractData.sources.indexOf(sourceRange.fileName);
+ const fileIndexByFileName = _.invert(contractData.sources);
+ const fileIndex = _.parseInt(fileIndexByFileName[sourceRange.fileName]);
const sourceSnippet = getSourceRangeSnippet(sourceRange, contractData.sourceCodes[fileIndex]);
if (sourceSnippet !== null) {
sourceSnippets.push(sourceSnippet);
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..7d85f6c68 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
@@ -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: { [sourceId: number]: string } = {};
+ const sourceCodes: { [sourceId: number]: string } = {};
+ _.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..fe71c974b 100644
--- a/packages/sol-tracing-utils/src/ast_visitor.ts
+++ b/packages/sol-tracing-utils/src/ast_visitor.ts
@@ -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)) {
diff --git a/packages/sol-tracing-utils/src/source_maps.ts b/packages/sol-tracing-utils/src/source_maps.ts
index af0fb4035..c674d32a3 100644
--- a/packages/sol-tracing-utils/src/source_maps.ts
+++ b/packages/sol-tracing-utils/src/source_maps.ts
@@ -33,20 +33,23 @@ export function getLocationByOffset(str: string): LocationByOffset {
/**
* 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 sourceCodes sources contents by index
* @param srcMap source map string
* @param bytecodeHex contract bytecode
- * @param sources sources file names
+ * @param sources sources file names by index
*/
export function parseSourceMap(
- sourceCodes: string[],
+ sourceCodes: { [fileIndex: number]: string },
srcMap: string,
bytecodeHex: string,
- sources: string[],
+ sources: { [fileIndex: number]: string },
): { [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 locationByOffsetByFileIndex: { [fileIndex: number]: LocationByOffset } = {};
+ _.map(sourceCodes, (sourceCode: string, fileIndex: number) => {
+ locationByOffsetByFileIndex[fileIndex] = _.isUndefined(sourceCode) ? {} : getLocationByOffset(sourceCode);
+ });
const entries = srcMap.split(';');
let lastParsedEntry: SourceLocation = {} as any;
const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {};
@@ -67,13 +70,17 @@ export function parseSourceMap(
fileIndex,
};
if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) {
+ const locationByOffset = locationByOffsetByFileIndex[parsedEntry.fileIndex];
const sourceRange = {
location: {
- start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset],
- end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length],
+ start: locationByOffset[parsedEntry.offset],
+ end: locationByOffset[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_collection_subprovider.ts b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts
index 25e38768d..3ae8566f9 100644
--- a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts
+++ b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts
@@ -20,6 +20,24 @@ export interface TraceCollectionSubproviderConfig {
shouldCollectGasEstimateTraces: boolean;
}
+type AsyncFunc = (...args: any[]) => Promise<void>;
+
+// This wrapper outputs errors to console even if the promise gets ignored
+// we need this because web3-provider-engine does not handler promises in
+// the after function of next(after).
+function logErrors(fn: AsyncFunc): AsyncFunc {
+ async function wrappedAsync(...args: any[]): Promise<void> {
+ try {
+ await fn(...args);
+ } catch (e) {
+ // tslint:disable-next-line no-console
+ console.error(e);
+ throw e;
+ }
+ }
+ 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(logErrors(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(logErrors(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(logErrors(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..43853e152 100644
--- a/packages/sol-tracing-utils/src/trace_info_subprovider.ts
+++ b/packages/sol-tracing-utils/src/trace_info_subprovider.ts
@@ -12,11 +12,28 @@ 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,
- });
+ // 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 othe stack values, we severly limit the amount of data to be collectd.
+ 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}; }
+ }
+ `;
+ const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { tracer, timeout: '600s' });
const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address);
const subcallAddresses = _.keys(tracesByContractAddress);
if (address === constants.NEW_CONTRACT) {
diff --git a/packages/sol-tracing-utils/src/types.ts b/packages/sol-tracing-utils/src/types.ts
index 54ade0400..fa10a93d6 100644
--- a/packages/sol-tracing-utils/src/types.ts
+++ b/packages/sol-tracing-utils/src/types.ts
@@ -81,8 +81,8 @@ export interface ContractData {
sourceMap: string;
runtimeBytecode: string;
sourceMapRuntime: string;
- sourceCodes: string[];
- sources: string[];
+ sourceCodes: { [sourceId: number]: string };
+ sources: { [sourceId: number]: string };
}
// 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____________________________