aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-trace
diff options
context:
space:
mode:
authorLeonid Logvinov <logvinov.leon@gmail.com>2019-01-10 21:35:13 +0800
committerGitHub <noreply@github.com>2019-01-10 21:35:13 +0800
commit6c22594882c94146519ec6e3b24d558127bd092c (patch)
tree522c5ee89e4af0948b8e645237678f46ec8d3d5c /packages/sol-trace
parent686f27a96f0cd749f6315d7edd2bb56cf1819245 (diff)
parentb8e3829fdbd1f516686618562172cb45fbb63bde (diff)
downloaddexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.tar
dexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.tar.gz
dexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.tar.bz2
dexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.tar.lz
dexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.tar.xz
dexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.tar.zst
dexon-sol-tools-6c22594882c94146519ec6e3b24d558127bd092c.zip
Merge pull request #1492 from 0xProject/feature/sol-cov-sol-profiler-sol-trace-divorce
Refactor out sol-cov, sol-profiler and sol-trace into their separate packages
Diffstat (limited to 'packages/sol-trace')
-rw-r--r--packages/sol-trace/.npmignore6
-rw-r--r--packages/sol-trace/CHANGELOG.json11
-rw-r--r--packages/sol-trace/README.md75
-rw-r--r--packages/sol-trace/package.json52
-rw-r--r--packages/sol-trace/src/globals.d.ts7
-rw-r--r--packages/sol-trace/src/index.ts24
-rw-r--r--packages/sol-trace/src/revert_trace_subprovider.ts167
-rw-r--r--packages/sol-trace/tsconfig.json8
-rw-r--r--packages/sol-trace/tslint.json3
-rw-r--r--packages/sol-trace/typedoc-tsconfig.json7
10 files changed, 360 insertions, 0 deletions
diff --git a/packages/sol-trace/.npmignore b/packages/sol-trace/.npmignore
new file mode 100644
index 000000000..037786e46
--- /dev/null
+++ b/packages/sol-trace/.npmignore
@@ -0,0 +1,6 @@
+.*
+yarn-error.log
+/src/
+/scripts/
+tsconfig.json
+/lib/src/monorepo_scripts/
diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json
new file mode 100644
index 000000000..223400eae
--- /dev/null
+++ b/packages/sol-trace/CHANGELOG.json
@@ -0,0 +1,11 @@
+[
+ {
+ "version": "1.0.0",
+ "changes": [
+ {
+ "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils",
+ "pr": 1492
+ }
+ ]
+ }
+]
diff --git a/packages/sol-trace/README.md b/packages/sol-trace/README.md
new file mode 100644
index 000000000..86ca2cbd6
--- /dev/null
+++ b/packages/sol-trace/README.md
@@ -0,0 +1,75 @@
+## @0x/sol-trace
+
+Prints a stack trace when a revert is encountered.
+
+### Read the [Documentation](https://0xproject.com/docs/sol-trace).
+
+## Installation
+
+```bash
+yarn add @0x/sol-trace
+```
+
+**Import**
+
+```javascript
+import { RevertTraceSubprovider } from '@0x/sol-trace';
+```
+
+or
+
+```javascript
+var RevertTraceSubprovider = require('@0x/sol-trace').RevertTraceSubprovider;
+```
+
+## Contributing
+
+We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
+
+Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
+
+### Install dependencies
+
+If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
+
+```bash
+yarn config set workspaces-experimental true
+```
+
+Then install dependencies
+
+```bash
+yarn install
+```
+
+### Build
+
+To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
+
+```bash
+PKG=@0x/sol-trace yarn build
+```
+
+Or continuously rebuild on change:
+
+```bash
+PKG=@0x/sol-trace yarn watch
+```
+
+### Clean
+
+```bash
+yarn clean
+```
+
+### Lint
+
+```bash
+yarn lint
+```
+
+### Run Tests
+
+```bash
+yarn test
+```
diff --git a/packages/sol-trace/package.json b/packages/sol-trace/package.json
new file mode 100644
index 000000000..3013be6f9
--- /dev/null
+++ b/packages/sol-trace/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@0x/sol-trace",
+ "version": "1.0.0",
+ "engines": {
+ "node": ">=6.12"
+ },
+ "description": "Prints stack trace on Solidity revert",
+ "main": "lib/src/index.js",
+ "types": "lib/src/index.d.ts",
+ "scripts": {
+ "build": "tsc -b",
+ "build:ci": "yarn build",
+ "lint": "tslint --format stylish --project .",
+ "clean": "shx rm -rf lib src/artifacts generated_docs",
+ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
+ },
+ "config": {
+ "postpublish": {
+ "assets": []
+ }
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/0xProject/0x-monorepo.git"
+ },
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/0xProject/0x-monorepo/issues"
+ },
+ "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-trace/README.md",
+ "dependencies": {
+ "@0x/subproviders": "^2.1.8",
+ "@0x/typescript-typings": "^3.0.6",
+ "@0x/sol-tracing-utils": "^2.1.16",
+ "ethereum-types": "^1.1.4",
+ "ethereumjs-util": "^5.1.1",
+ "lodash": "^4.17.5",
+ "loglevel": "^1.6.1"
+ },
+ "devDependencies": {
+ "@0x/tslint-config": "^2.0.0",
+ "@types/loglevel": "^1.5.3",
+ "@types/node": "*",
+ "npm-run-all": "^4.1.2",
+ "shx": "^0.2.2",
+ "tslint": "5.11.0",
+ "typescript": "3.0.1"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/sol-trace/src/globals.d.ts b/packages/sol-trace/src/globals.d.ts
new file mode 100644
index 000000000..e799b3529
--- /dev/null
+++ b/packages/sol-trace/src/globals.d.ts
@@ -0,0 +1,7 @@
+// tslint:disable:completed-docs
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
diff --git a/packages/sol-trace/src/index.ts b/packages/sol-trace/src/index.ts
new file mode 100644
index 000000000..120c0d0a9
--- /dev/null
+++ b/packages/sol-trace/src/index.ts
@@ -0,0 +1,24 @@
+export {
+ AbstractArtifactAdapter,
+ TruffleArtifactAdapter,
+ SolCompilerArtifactAdapter,
+ ContractData,
+} from '@0x/sol-tracing-utils';
+
+export { RevertTraceSubprovider } from './revert_trace_subprovider';
+
+export {
+ JSONRPCRequestPayload,
+ Provider,
+ JSONRPCErrorCallback,
+ JSONRPCResponsePayload,
+ JSONRPCResponseError,
+} from 'ethereum-types';
+
+export {
+ JSONRPCRequestPayloadWithMethod,
+ NextCallback,
+ ErrorCallback,
+ OnNextCompleted,
+ Callback,
+} from '@0x/subproviders';
diff --git a/packages/sol-trace/src/revert_trace_subprovider.ts b/packages/sol-trace/src/revert_trace_subprovider.ts
new file mode 100644
index 000000000..31067a402
--- /dev/null
+++ b/packages/sol-trace/src/revert_trace_subprovider.ts
@@ -0,0 +1,167 @@
+import {
+ AbstractArtifactAdapter,
+ constants,
+ ContractData,
+ EvmCallStack,
+ getRevertTrace,
+ getSourceRangeSnippet,
+ parseSourceMap,
+ SourceRange,
+ SourceSnippet,
+ TraceCollectionSubprovider,
+ utils,
+} from '@0x/sol-tracing-utils';
+import { stripHexPrefix } from 'ethereumjs-util';
+import * as _ from 'lodash';
+import { getLogger, levels, Logger } from 'loglevel';
+
+/**
+ * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
+ * It is used to report call stack traces whenever a revert occurs.
+ */
+export class RevertTraceSubprovider extends TraceCollectionSubprovider {
+ // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise
+ private _contractsData!: ContractData[];
+ private readonly _artifactAdapter: AbstractArtifactAdapter;
+ private readonly _logger: Logger;
+
+ /**
+ * Instantiates a RevertTraceSubprovider instance
+ * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.)
+ * @param defaultFromAddress default from address to use when sending transactions
+ * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them
+ */
+ constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) {
+ const traceCollectionSubproviderConfig = {
+ shouldCollectTransactionTraces: true,
+ shouldCollectGasEstimateTraces: true,
+ shouldCollectCallTraces: true,
+ };
+ super(defaultFromAddress, traceCollectionSubproviderConfig);
+ this._artifactAdapter = artifactAdapter;
+ this._logger = getLogger('sol-trace');
+ this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR);
+ }
+ // tslint:disable-next-line:no-unused-variable
+ 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 evmCallStack = getRevertTrace(trace.structLogs, address);
+ if (evmCallStack.length > 0) {
+ // if getRevertTrace returns a call stack it means there was a
+ // revert.
+ await this._printStackTraceAsync(evmCallStack);
+ }
+ }
+ private async _printStackTraceAsync(evmCallStack: EvmCallStack): Promise<void> {
+ const sourceSnippets: SourceSnippet[] = [];
+ if (_.isUndefined(this._contractsData)) {
+ this._contractsData = await this._artifactAdapter.collectContractsDataAsync();
+ }
+ for (const evmCallStackEntry of evmCallStack) {
+ const isContractCreation = evmCallStackEntry.address === constants.NEW_CONTRACT;
+ if (isContractCreation) {
+ this._logger.error('Contract creation not supported');
+ continue;
+ }
+ const bytecode = await this._web3Wrapper.getContractCodeAsync(evmCallStackEntry.address);
+ const contractData = utils.getContractDataIfExists(this._contractsData, bytecode);
+ if (_.isUndefined(contractData)) {
+ const errMsg = isContractCreation
+ ? `Unknown contract creation transaction`
+ : `Transaction to an unknown address: ${evmCallStackEntry.address}`;
+ this._logger.warn(errMsg);
+ continue;
+ }
+ const bytecodeHex = stripHexPrefix(bytecode);
+ const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime;
+
+ const pcToSourceRange = parseSourceMap(
+ contractData.sourceCodes,
+ sourceMap,
+ bytecodeHex,
+ contractData.sources,
+ );
+ // tslint:disable-next-line:no-unnecessary-initializer
+ let sourceRange: SourceRange | undefined = undefined;
+ let pc = evmCallStackEntry.structLog.pc;
+ // Sometimes there is not a mapping for this pc (e.g. if the revert
+ // actually happens in assembly). In that case, we want to keep
+ // searching backwards by decrementing the pc until we find a
+ // mapped source range.
+ while (_.isUndefined(sourceRange) && pc > 0) {
+ sourceRange = pcToSourceRange[pc];
+ pc -= 1;
+ }
+ if (_.isUndefined(sourceRange)) {
+ this._logger.warn(
+ `could not find matching sourceRange for structLog: ${JSON.stringify(
+ _.omit(evmCallStackEntry.structLog, 'stack'),
+ )}`,
+ );
+ continue;
+ }
+
+ const fileIndex = contractData.sources.indexOf(sourceRange.fileName);
+ const sourceSnippet = getSourceRangeSnippet(sourceRange, contractData.sourceCodes[fileIndex]);
+ if (sourceSnippet !== null) {
+ sourceSnippets.push(sourceSnippet);
+ }
+ }
+ const filteredSnippets = filterSnippets(sourceSnippets);
+ if (filteredSnippets.length > 0) {
+ this._logger.error('\n\nStack trace for REVERT:\n');
+ _.forEach(_.reverse(filteredSnippets), snippet => {
+ const traceString = getStackTraceString(snippet);
+ this._logger.error(traceString);
+ });
+ this._logger.error('\n');
+ } else {
+ this._logger.error('REVERT detected but could not determine stack trace');
+ }
+ }
+}
+
+// removes duplicates and if statements
+function filterSnippets(sourceSnippets: SourceSnippet[]): SourceSnippet[] {
+ if (sourceSnippets.length === 0) {
+ return [];
+ }
+ const results: SourceSnippet[] = [sourceSnippets[0]];
+ let prev = sourceSnippets[0];
+ for (const sourceSnippet of sourceSnippets) {
+ if (sourceSnippet.type === 'IfStatement') {
+ continue;
+ } else if (sourceSnippet.source === prev.source) {
+ prev = sourceSnippet;
+ continue;
+ }
+ results.push(sourceSnippet);
+ prev = sourceSnippet;
+ }
+ return results;
+}
+
+function getStackTraceString(sourceSnippet: SourceSnippet): string {
+ let result = `${sourceSnippet.fileName}:${sourceSnippet.range.start.line}:${sourceSnippet.range.start.column}`;
+ const snippetString = getSourceSnippetString(sourceSnippet);
+ if (snippetString !== '') {
+ result += `:\n ${snippetString}`;
+ }
+ return result;
+}
+
+function getSourceSnippetString(sourceSnippet: SourceSnippet): string {
+ switch (sourceSnippet.type) {
+ case 'ContractDefinition':
+ return `contract ${sourceSnippet.name}`;
+ case 'FunctionDefinition':
+ return `function ${sourceSnippet.name}`;
+ default:
+ return `${sourceSnippet.source}`;
+ }
+}
diff --git a/packages/sol-trace/tsconfig.json b/packages/sol-trace/tsconfig.json
new file mode 100644
index 000000000..233008d61
--- /dev/null
+++ b/packages/sol-trace/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "."
+ },
+ "include": ["./src/**/*"]
+}
diff --git a/packages/sol-trace/tslint.json b/packages/sol-trace/tslint.json
new file mode 100644
index 000000000..dd9053357
--- /dev/null
+++ b/packages/sol-trace/tslint.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@0x/tslint-config"]
+}
diff --git a/packages/sol-trace/typedoc-tsconfig.json b/packages/sol-trace/typedoc-tsconfig.json
new file mode 100644
index 000000000..a4c669cb6
--- /dev/null
+++ b/packages/sol-trace/typedoc-tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../typedoc-tsconfig",
+ "compilerOptions": {
+ "outDir": "lib"
+ },
+ "include": ["./src/**/*"]
+}