From 2c974b5f3ffa0e9736000273e39cdeee4a251b94 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 8 Jan 2019 12:23:33 +0100 Subject: Refactor out sol-cov, sol-profiler and sol-trace into their separate packages --- .circleci/config.yml | 8 +- .github/autolabeler.yml | 5 +- .gitignore | 2 +- .prettierignore | 2 +- CODEOWNERS | 5 +- README.md | 4 +- contracts/TESTING.md | 2 +- contracts/examples/package.json | 1 - contracts/extensions/package.json | 1 - contracts/libs/package.json | 1 - contracts/multisig/package.json | 1 - contracts/protocol/package.json | 1 - contracts/test-utils/package.json | 4 +- contracts/test-utils/src/coverage.ts | 2 +- contracts/test-utils/src/profiler.ts | 2 +- contracts/test-utils/src/revert_trace.ts | 2 +- contracts/tokens/package.json | 1 - contracts/utils/package.json | 1 - package.json | 5 +- packages/metacoin/package.json | 3 +- packages/metacoin/test/utils/coverage.ts | 2 +- packages/metacoin/test/utils/profiler.ts | 2 +- packages/sol-cov/.npmignore | 6 - packages/sol-cov/CHANGELOG.json | 389 ------------------- packages/sol-cov/CHANGELOG.md | 158 -------- packages/sol-cov/README.md | 75 ---- packages/sol-cov/compiler.json | 18 - packages/sol-cov/coverage/.gitkeep | 0 packages/sol-cov/package.json | 87 ----- .../artifact_adapters/abstract_artifact_adapter.ts | 5 - .../sol_compiler_artifact_adapter.ts | 61 --- .../artifact_adapters/truffle_artifact_adapter.ts | 88 ----- packages/sol-cov/src/ast_visitor.ts | 168 --------- packages/sol-cov/src/collect_coverage_entries.ts | 41 -- packages/sol-cov/src/constants.ts | 8 - packages/sol-cov/src/coverage_subprovider.ts | 146 -------- packages/sol-cov/src/get_source_range_snippet.ts | 180 --------- packages/sol-cov/src/globals.d.ts | 7 - packages/sol-cov/src/index.ts | 25 -- packages/sol-cov/src/instructions.ts | 23 -- packages/sol-cov/src/profiler_subprovider.ts | 91 ----- packages/sol-cov/src/revert_trace.ts | 90 ----- packages/sol-cov/src/revert_trace_subprovider.ts | 163 -------- packages/sol-cov/src/source_maps.ts | 81 ---- packages/sol-cov/src/trace.ts | 99 ----- .../sol-cov/src/trace_collection_subprovider.ts | 188 ---------- packages/sol-cov/src/trace_collector.ts | 93 ----- packages/sol-cov/src/trace_info_subprovider.ts | 59 --- packages/sol-cov/src/types.ts | 126 ------- packages/sol-cov/src/utils.ts | 87 ----- .../sol-cov/test/collect_coverage_entries_test.ts | 155 -------- .../fixtures/contracts/AllSolidityFeatures.sol | 413 --------------------- .../test/fixtures/contracts/SimpleStorage.sol | 11 - .../sol-cov/test/fixtures/contracts/Simplest.sol | 2 - .../test/fixtures/contracts/SolcovIgnore.sol | 22 -- packages/sol-cov/test/instructions_test.ts | 19 - .../test/sol_compiler_artifact_adapter_test.ts | 29 -- packages/sol-cov/test/source_maps_test.ts | 71 ---- packages/sol-cov/test/trace_test.ts | 55 --- packages/sol-cov/test/utils_test.ts | 53 --- packages/sol-cov/tsconfig.json | 8 - packages/sol-cov/tslint.json | 6 - packages/sol-cov/typedoc-tsconfig.json | 7 - packages/sol-coverage/.npmignore | 6 + packages/sol-coverage/CHANGELOG.json | 12 + packages/sol-coverage/README.md | 75 ++++ packages/sol-coverage/package.json | 52 +++ packages/sol-coverage/src/coverage_subprovider.ts | 146 ++++++++ packages/sol-coverage/src/globals.d.ts | 7 + packages/sol-coverage/src/index.ts | 23 ++ packages/sol-coverage/tsconfig.json | 8 + packages/sol-coverage/tslint.json | 3 + packages/sol-coverage/typedoc-tsconfig.json | 7 + packages/sol-profiler/.npmignore | 6 + packages/sol-profiler/CHANGELOG.json | 12 + packages/sol-profiler/README.md | 75 ++++ packages/sol-profiler/package.json | 50 +++ packages/sol-profiler/src/globals.d.ts | 7 + packages/sol-profiler/src/index.ts | 25 ++ packages/sol-profiler/src/profiler_subprovider.ts | 98 +++++ packages/sol-profiler/tsconfig.json | 8 + packages/sol-profiler/tslint.json | 3 + packages/sol-profiler/typedoc-tsconfig.json | 7 + packages/sol-trace-based-tools-common/.npmignore | 6 + .../sol-trace-based-tools-common/CHANGELOG.json | 398 ++++++++++++++++++++ packages/sol-trace-based-tools-common/CHANGELOG.md | 158 ++++++++ packages/sol-trace-based-tools-common/README.md | 61 +++ .../sol-trace-based-tools-common/compiler.json | 18 + .../sol-trace-based-tools-common/coverage/.gitkeep | 0 packages/sol-trace-based-tools-common/package.json | 87 +++++ .../artifact_adapters/abstract_artifact_adapter.ts | 5 + .../sol_compiler_artifact_adapter.ts | 61 +++ .../artifact_adapters/truffle_artifact_adapter.ts | 88 +++++ .../src/ast_visitor.ts | 168 +++++++++ .../src/collect_coverage_entries.ts | 41 ++ .../sol-trace-based-tools-common/src/constants.ts | 8 + .../src/get_source_range_snippet.ts | 185 +++++++++ .../sol-trace-based-tools-common/src/globals.d.ts | 7 + packages/sol-trace-based-tools-common/src/index.ts | 39 ++ .../src/instructions.ts | 23 ++ .../src/revert_trace.ts | 95 +++++ .../src/source_maps.ts | 91 +++++ packages/sol-trace-based-tools-common/src/trace.ts | 104 ++++++ .../src/trace_collection_subprovider.ts | 188 ++++++++++ .../src/trace_collector.ts | 93 +++++ .../src/trace_info_subprovider.ts | 59 +++ packages/sol-trace-based-tools-common/src/types.ts | 126 +++++++ packages/sol-trace-based-tools-common/src/utils.ts | 87 +++++ .../test/collect_coverage_entries_test.ts | 155 ++++++++ .../fixtures/contracts/AllSolidityFeatures.sol | 413 +++++++++++++++++++++ .../test/fixtures/contracts/SimpleStorage.sol | 11 + .../test/fixtures/contracts/Simplest.sol | 2 + .../test/fixtures/contracts/SolcovIgnore.sol | 22 ++ .../test/instructions_test.ts | 19 + .../test/sol_compiler_artifact_adapter_test.ts | 29 ++ .../test/source_maps_test.ts | 71 ++++ .../test/trace_test.ts | 55 +++ .../test/utils_test.ts | 53 +++ .../sol-trace-based-tools-common/tsconfig.json | 8 + packages/sol-trace-based-tools-common/tslint.json | 3 + packages/sol-trace/.npmignore | 6 + packages/sol-trace/CHANGELOG.json | 12 + packages/sol-trace/README.md | 75 ++++ packages/sol-trace/package.json | 52 +++ packages/sol-trace/src/globals.d.ts | 7 + packages/sol-trace/src/index.ts | 24 ++ packages/sol-trace/src/revert_trace_subprovider.ts | 167 +++++++++ packages/sol-trace/tsconfig.json | 8 + packages/sol-trace/tslint.json | 3 + packages/sol-trace/typedoc-tsconfig.json | 7 + packages/sra-spec/src/md/introduction.md | 4 +- packages/website/md/docs/sol_cov/1/installation.md | 17 - packages/website/md/docs/sol_cov/1/introduction.md | 1 - packages/website/md/docs/sol_cov/1/usage.md | 62 ---- packages/website/md/docs/sol_cov/2/installation.md | 17 - packages/website/md/docs/sol_cov/2/introduction.md | 1 - packages/website/md/docs/sol_cov/2/usage.md | 62 ---- .../website/md/docs/sol_coverage/installation.md | 17 + .../website/md/docs/sol_coverage/introduction.md | 1 + packages/website/md/docs/sol_coverage/usage.md | 62 ++++ .../website/md/docs/sol_profiler/installation.md | 17 + .../website/md/docs/sol_profiler/introduction.md | 1 + packages/website/md/docs/sol_profiler/usage.md | 62 ++++ packages/website/md/docs/sol_trace/installation.md | 17 + packages/website/md/docs/sol_trace/introduction.md | 1 + packages/website/md/docs/sol_trace/usage.md | 62 ++++ .../ts/containers/asset_buyer_documentation.ts | 38 +- .../website/ts/containers/connect_documentation.ts | 38 +- .../containers/contract_wrappers_documentation.ts | 38 +- .../ts/containers/ethereum_types_documentation.ts | 36 +- .../ts/containers/json_schemas_documentation.ts | 38 +- .../ts/containers/migrations_documentation.ts | 2 +- .../ts/containers/order_utils_documentation.ts | 38 +- .../ts/containers/order_watcher_documentation.ts | 38 +- .../ts/containers/smart_contracts_documentation.ts | 36 +- .../ts/containers/sol_compiler_documentation.ts | 38 +- .../website/ts/containers/sol_cov_documentation.ts | 77 ---- .../ts/containers/sol_coverage_documentation.ts | 43 +++ .../ts/containers/sol_profiler_documentation.ts | 43 +++ .../ts/containers/sol_trace_documentation.ts | 43 +++ .../ts/containers/subproviders_documentation.ts | 38 +- .../ts/containers/web3_wrapper_documentation.ts | 38 +- .../ts/containers/zero_ex_js_documentation.ts | 38 +- packages/website/ts/index.tsx | 23 +- .../website/ts/pages/documentation/doc_page.tsx | 4 +- .../website/ts/pages/documentation/docs_home.tsx | 22 +- packages/website/ts/types.ts | 8 +- .../website/ts/utils/documentation_container.ts | 35 ++ python-packages/sra_client/README.md | 2 +- tsconfig.json | 5 +- yarn.lock | 21 +- 171 files changed, 4594 insertions(+), 4088 deletions(-) delete mode 100644 packages/sol-cov/.npmignore delete mode 100644 packages/sol-cov/CHANGELOG.json delete mode 100644 packages/sol-cov/CHANGELOG.md delete mode 100644 packages/sol-cov/README.md delete mode 100644 packages/sol-cov/compiler.json delete mode 100644 packages/sol-cov/coverage/.gitkeep delete mode 100644 packages/sol-cov/package.json delete mode 100644 packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts delete mode 100644 packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts delete mode 100644 packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts delete mode 100644 packages/sol-cov/src/ast_visitor.ts delete mode 100644 packages/sol-cov/src/collect_coverage_entries.ts delete mode 100644 packages/sol-cov/src/constants.ts delete mode 100644 packages/sol-cov/src/coverage_subprovider.ts delete mode 100644 packages/sol-cov/src/get_source_range_snippet.ts delete mode 100644 packages/sol-cov/src/globals.d.ts delete mode 100644 packages/sol-cov/src/index.ts delete mode 100644 packages/sol-cov/src/instructions.ts delete mode 100644 packages/sol-cov/src/profiler_subprovider.ts delete mode 100644 packages/sol-cov/src/revert_trace.ts delete mode 100644 packages/sol-cov/src/revert_trace_subprovider.ts delete mode 100644 packages/sol-cov/src/source_maps.ts delete mode 100644 packages/sol-cov/src/trace.ts delete mode 100644 packages/sol-cov/src/trace_collection_subprovider.ts delete mode 100644 packages/sol-cov/src/trace_collector.ts delete mode 100644 packages/sol-cov/src/trace_info_subprovider.ts delete mode 100644 packages/sol-cov/src/types.ts delete mode 100644 packages/sol-cov/src/utils.ts delete mode 100644 packages/sol-cov/test/collect_coverage_entries_test.ts delete mode 100644 packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol delete mode 100644 packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol delete mode 100644 packages/sol-cov/test/fixtures/contracts/Simplest.sol delete mode 100644 packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol delete mode 100644 packages/sol-cov/test/instructions_test.ts delete mode 100644 packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts delete mode 100644 packages/sol-cov/test/source_maps_test.ts delete mode 100644 packages/sol-cov/test/trace_test.ts delete mode 100644 packages/sol-cov/test/utils_test.ts delete mode 100644 packages/sol-cov/tsconfig.json delete mode 100644 packages/sol-cov/tslint.json delete mode 100644 packages/sol-cov/typedoc-tsconfig.json create mode 100644 packages/sol-coverage/.npmignore create mode 100644 packages/sol-coverage/CHANGELOG.json create mode 100644 packages/sol-coverage/README.md create mode 100644 packages/sol-coverage/package.json create mode 100644 packages/sol-coverage/src/coverage_subprovider.ts create mode 100644 packages/sol-coverage/src/globals.d.ts create mode 100644 packages/sol-coverage/src/index.ts create mode 100644 packages/sol-coverage/tsconfig.json create mode 100644 packages/sol-coverage/tslint.json create mode 100644 packages/sol-coverage/typedoc-tsconfig.json create mode 100644 packages/sol-profiler/.npmignore create mode 100644 packages/sol-profiler/CHANGELOG.json create mode 100644 packages/sol-profiler/README.md create mode 100644 packages/sol-profiler/package.json create mode 100644 packages/sol-profiler/src/globals.d.ts create mode 100644 packages/sol-profiler/src/index.ts create mode 100644 packages/sol-profiler/src/profiler_subprovider.ts create mode 100644 packages/sol-profiler/tsconfig.json create mode 100644 packages/sol-profiler/tslint.json create mode 100644 packages/sol-profiler/typedoc-tsconfig.json create mode 100644 packages/sol-trace-based-tools-common/.npmignore create mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.json create mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.md create mode 100644 packages/sol-trace-based-tools-common/README.md create mode 100644 packages/sol-trace-based-tools-common/compiler.json create mode 100644 packages/sol-trace-based-tools-common/coverage/.gitkeep create mode 100644 packages/sol-trace-based-tools-common/package.json create mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts create mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts create mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts create mode 100644 packages/sol-trace-based-tools-common/src/ast_visitor.ts create mode 100644 packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts create mode 100644 packages/sol-trace-based-tools-common/src/constants.ts create mode 100644 packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts create mode 100644 packages/sol-trace-based-tools-common/src/globals.d.ts create mode 100644 packages/sol-trace-based-tools-common/src/index.ts create mode 100644 packages/sol-trace-based-tools-common/src/instructions.ts create mode 100644 packages/sol-trace-based-tools-common/src/revert_trace.ts create mode 100644 packages/sol-trace-based-tools-common/src/source_maps.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace_collector.ts create mode 100644 packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts create mode 100644 packages/sol-trace-based-tools-common/src/types.ts create mode 100644 packages/sol-trace-based-tools-common/src/utils.ts create mode 100644 packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol create mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol create mode 100644 packages/sol-trace-based-tools-common/test/instructions_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/source_maps_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/trace_test.ts create mode 100644 packages/sol-trace-based-tools-common/test/utils_test.ts create mode 100644 packages/sol-trace-based-tools-common/tsconfig.json create mode 100644 packages/sol-trace-based-tools-common/tslint.json create mode 100644 packages/sol-trace/.npmignore create mode 100644 packages/sol-trace/CHANGELOG.json create mode 100644 packages/sol-trace/README.md create mode 100644 packages/sol-trace/package.json create mode 100644 packages/sol-trace/src/globals.d.ts create mode 100644 packages/sol-trace/src/index.ts create mode 100644 packages/sol-trace/src/revert_trace_subprovider.ts create mode 100644 packages/sol-trace/tsconfig.json create mode 100644 packages/sol-trace/tslint.json create mode 100644 packages/sol-trace/typedoc-tsconfig.json delete mode 100644 packages/website/md/docs/sol_cov/1/installation.md delete mode 100644 packages/website/md/docs/sol_cov/1/introduction.md delete mode 100644 packages/website/md/docs/sol_cov/1/usage.md delete mode 100644 packages/website/md/docs/sol_cov/2/installation.md delete mode 100644 packages/website/md/docs/sol_cov/2/introduction.md delete mode 100644 packages/website/md/docs/sol_cov/2/usage.md create mode 100644 packages/website/md/docs/sol_coverage/installation.md create mode 100644 packages/website/md/docs/sol_coverage/introduction.md create mode 100644 packages/website/md/docs/sol_coverage/usage.md create mode 100644 packages/website/md/docs/sol_profiler/installation.md create mode 100644 packages/website/md/docs/sol_profiler/introduction.md create mode 100644 packages/website/md/docs/sol_profiler/usage.md create mode 100644 packages/website/md/docs/sol_trace/installation.md create mode 100644 packages/website/md/docs/sol_trace/introduction.md create mode 100644 packages/website/md/docs/sol_trace/usage.md delete mode 100644 packages/website/ts/containers/sol_cov_documentation.ts create mode 100644 packages/website/ts/containers/sol_coverage_documentation.ts create mode 100644 packages/website/ts/containers/sol_profiler_documentation.ts create mode 100644 packages/website/ts/containers/sol_trace_documentation.ts create mode 100644 packages/website/ts/utils/documentation_container.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 68d8041a2..3e5a2809c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -118,7 +118,7 @@ jobs: - run: yarn wsrun test:circleci @0x/order-utils - run: yarn wsrun test:circleci @0x/order-watcher - run: yarn wsrun test:circleci @0x/sol-compiler - - run: yarn wsrun test:circleci @0x/sol-cov + - run: yarn wsrun test:circleci @0x/sol-trace-based-tools-common - run: yarn wsrun test:circleci @0x/sol-doc - run: yarn wsrun test:circleci @0x/subproviders - run: yarn wsrun test:circleci @0x/web3-wrapper @@ -169,9 +169,9 @@ jobs: paths: - ~/repo/packages/sol-compiler/coverage/lcov.info - save_cache: - key: coverage-sol-cov-{{ .Environment.CIRCLE_SHA1 }} + key: coverage-sol-trace-based-tools-common-{{ .Environment.CIRCLE_SHA1 }} paths: - - ~/repo/packages/sol-cov/coverage/lcov.info + - ~/repo/packages/sol-trace-based-tools-common/coverage/lcov.info - save_cache: key: coverage-sol-doc-{{ .Environment.CIRCLE_SHA1 }} paths: @@ -342,7 +342,7 @@ jobs: - coverage-sol-compiler-{{ .Environment.CIRCLE_SHA1 }} - restore_cache: keys: - - coverage-sol-cov-{{ .Environment.CIRCLE_SHA1 }} + - coverage-sol-trace-based-tools-common-{{ .Environment.CIRCLE_SHA1 }} - restore_cache: keys: - coverage-sol-doc-{{ .Environment.CIRCLE_SHA1 }} diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml index 8284293d5..ecdc0e89b 100644 --- a/.github/autolabeler.yml +++ b/.github/autolabeler.yml @@ -13,7 +13,10 @@ instant: ['packages/instant'] abi-gen-templates: ['packages/abi-gen-templates'] abi-gen: ['packages/abi-gen'] website: ['packages/website'] -sol-cov: ['packages/sol-cov'] +sol-coverage: ['packages/sol-coverage'] +sol-profiler: ['packages/sol-profiler'] +sol-trace: ['packages/sol-trace'] +sol-trace-based-tools-common: ['packages/sol-trace-based-tools-common'] utils: ['packages/utils'] tslint-config: ['packages/tslint-config'] asset-buyer: ['packages/asset-buyer'] diff --git a/.gitignore b/.gitignore index 1d4410e25..c2100ea62 100644 --- a/.gitignore +++ b/.gitignore @@ -91,7 +91,7 @@ contracts/interfaces/generated-artifacts/ contracts/tokens/generated-artifacts/ contracts/examples/generated-artifacts/ contracts/extensions/generated-artifacts/ -packages/sol-cov/test/fixtures/artifacts/ +packages/sol-trace-based-tools-common/test/fixtures/artifacts/ packages/metacoin/artifacts/ # generated contract wrappers diff --git a/.prettierignore b/.prettierignore index 7f8662b0a..2467f8711 100644 --- a/.prettierignore +++ b/.prettierignore @@ -26,4 +26,4 @@ lib /packages/sra-spec/public/ package.json scripts/postpublish_utils.js -packages/sol-cov/test/fixtures/artifacts +packages/sol-coverage/test/fixtures/artifacts diff --git a/CODEOWNERS b/CODEOWNERS index ca98ec19b..f57da320f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -24,7 +24,10 @@ packages/metacoin/ @LogvinovLeon packages/monorepo-scripts/ @fabioberger packages/order-utils/ @fabioberger @LogvinovLeon packages/sol-compiler/ @LogvinovLeon -packages/sol-cov/ @LogvinovLeon +packages/sol-coverage/ @LogvinovLeon +packages/sol-profiler/ @LogvinovLeon +packages/sol-trace/ @LogvinovLeon +packages/sol-trace-based-tools-common/ @LogvinovLeon packages/sol-resolver/ @LogvinovLeon packages/subproviders/ @fabioberger @dekz packages/verdaccio/ @albrow diff --git a/README.md b/README.md index fd96c2b86..386673a28 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,9 @@ Visit our [developer portal](https://0xproject.com/docs/order-utils) for a compr | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`@0x/web3-wrapper`](/packages/web3-wrapper) | [![npm](https://img.shields.io/npm/v/@0x/web3-wrapper.svg)](https://www.npmjs.com/package/@0x/web3-wrapper) | An Ethereum JSON RPC client | | [`@0x/sol-compiler`](/packages/sol-compiler) | [![npm](https://img.shields.io/npm/v/@0x/sol-compiler.svg)](https://www.npmjs.com/package/@0x/sol-compiler) | A wrapper around solc-js that adds smart re-compilation, ability to compile an entire project, Solidity version specific compilation, standard input description support and much more. | -| [`@0x/sol-cov`](/packages/sol-cov) | [![npm](https://img.shields.io/npm/v/@0x/sol-cov.svg)](https://www.npmjs.com/package/@0x/sol-cov) | A solidity test coverage tool | +| [`@0x/sol-coverage`](/packages/sol-coverage) | [![npm](https://img.shields.io/npm/v/@0x/sol-coverage.svg)](https://www.npmjs.com/package/@0x/sol-coverage) | A solidity test coverage tool | +| [`@0x/sol-profiler`](/packages/sol-profiler) | [![npm](https://img.shields.io/npm/v/@0x/sol-profiler.svg)](https://www.npmjs.com/package/@0x/sol-profiler) | A solidity gas cost profiler | +| [`@0x/sol-trace`](/packages/sol-trace) | [![npm](https://img.shields.io/npm/v/@0x/sol-trace.svg)](https://www.npmjs.com/package/@0x/sol-trace) | A solidity stack trace tool | | [`@0x/sol-resolver`](/packages/sol-resolver) | [![npm](https://img.shields.io/npm/v/@0x/sol-resolver.svg)](https://www.npmjs.com/package/@0x/sol-resolver) | Import resolver for smart contracts dependencies | | [`@0x/subproviders`](/packages/subproviders) | [![npm](https://img.shields.io/npm/v/@0x/subproviders.svg)](https://www.npmjs.com/package/@0x/subproviders) | Web3 provider middlewares (e.g. LedgerSubprovider) | | [`@0x/sol-doc`](/packages/sol-doc) | [![npm](https://img.shields.io/npm/v/@0x/sol-doc.svg)](https://www.npmjs.com/package/@0x/sol-doc) | Solidity documentation generator | diff --git a/contracts/TESTING.md b/contracts/TESTING.md index 750b3c62c..3e6888c04 100644 --- a/contracts/TESTING.md +++ b/contracts/TESTING.md @@ -20,7 +20,7 @@ TEST_PROVIDER=geth yarn test ## Code coverage -In order to see the Solidity code coverage output generated by `@0x/sol-cov`, run: +In order to see the Solidity code coverage output generated by `@0x/sol-coverage`, run: ``` yarn test:coverage diff --git a/contracts/examples/package.json b/contracts/examples/package.json index 37b73f98c..68286ec4f 100644 --- a/contracts/examples/package.json +++ b/contracts/examples/package.json @@ -37,7 +37,6 @@ "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json index aee995645..6dda2ccf3 100644 --- a/contracts/extensions/package.json +++ b/contracts/extensions/package.json @@ -49,7 +49,6 @@ "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/contracts/libs/package.json b/contracts/libs/package.json index ce7b97502..71955e74d 100644 --- a/contracts/libs/package.json +++ b/contracts/libs/package.json @@ -48,7 +48,6 @@ "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/contracts/multisig/package.json b/contracts/multisig/package.json index 2d7b4aa05..6ad04c838 100644 --- a/contracts/multisig/package.json +++ b/contracts/multisig/package.json @@ -48,7 +48,6 @@ "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/contracts/protocol/package.json b/contracts/protocol/package.json index 891838e54..987a1e589 100644 --- a/contracts/protocol/package.json +++ b/contracts/protocol/package.json @@ -47,7 +47,6 @@ "@0x/abi-gen": "^1.0.19", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/contracts/test-utils/package.json b/contracts/test-utils/package.json index 18ec8f6a8..acfcc3d10 100644 --- a/contracts/test-utils/package.json +++ b/contracts/test-utils/package.json @@ -44,7 +44,9 @@ "@0x/dev-utils": "^1.0.21", "@0x/order-utils": "^3.0.7", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", + "@0x/sol-coverage": "^1.0.0", + "@0x/sol-profiler": "^1.0.0", + "@0x/sol-trace": "^1.0.0", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@0x/types": "^1.4.1", diff --git a/contracts/test-utils/src/coverage.ts b/contracts/test-utils/src/coverage.ts index 5becfa1b6..f82288eec 100644 --- a/contracts/test-utils/src/coverage.ts +++ b/contracts/test-utils/src/coverage.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-coverage'; import * as _ from 'lodash'; let coverageSubprovider: CoverageSubprovider; diff --git a/contracts/test-utils/src/profiler.ts b/contracts/test-utils/src/profiler.ts index 2c7c1d66c..7f51c384f 100644 --- a/contracts/test-utils/src/profiler.ts +++ b/contracts/test-utils/src/profiler.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-profiler'; import * as _ from 'lodash'; let profilerSubprovider: ProfilerSubprovider; diff --git a/contracts/test-utils/src/revert_trace.ts b/contracts/test-utils/src/revert_trace.ts index 3f74fd28b..3a5778859 100644 --- a/contracts/test-utils/src/revert_trace.ts +++ b/contracts/test-utils/src/revert_trace.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { RevertTraceSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { RevertTraceSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-trace'; import * as _ from 'lodash'; let revertTraceSubprovider: RevertTraceSubprovider; diff --git a/contracts/tokens/package.json b/contracts/tokens/package.json index 79afc4820..8b8329cf3 100644 --- a/contracts/tokens/package.json +++ b/contracts/tokens/package.json @@ -48,7 +48,6 @@ "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/contracts/utils/package.json b/contracts/utils/package.json index cf94af0f6..4dcf8f826 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -48,7 +48,6 @@ "@0x/contracts-test-utils": "^1.0.2", "@0x/dev-utils": "^1.0.21", "@0x/sol-compiler": "^1.1.16", - "@0x/sol-cov": "^2.1.16", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", diff --git a/package.json b/package.json index 04fbb5e24..9395dbaa2 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ }, "config": { "mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic", - "packagesWithDocPages": "0x.js connect json-schemas subproviders web3-wrapper contract-wrappers order-utils order-watcher sol-compiler sol-cov ethereum-types asset-buyer migrations" + "packagesWithDocPages": "0x.js connect json-schemas subproviders web3-wrapper contract-wrappers order-utils order-watcher sol-compiler sol-coverage sol-profiler sol-trace ethereum-types asset-buyer migrations" }, "bundlewatch": { "files": [ @@ -62,6 +62,9 @@ "repoBranchBase": "development" } }, + "resolutions": { + "graceful-fs": "4.1.15" + }, "devDependencies": { "@0x-lerna-fork/lerna": "3.0.0-beta.26", "@0xproject/npm-cli-login": "^0.0.11", diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 0e9fa4920..33fc16a3e 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -32,7 +32,8 @@ "@0x/abi-gen": "^1.0.19", "@0x/abi-gen-templates": "^1.0.1", "@0x/base-contract": "^3.0.10", - "@0x/sol-cov": "^2.1.16", + "@0x/sol-coverage": "^1.0.0", + "@0x/sol-profiler": "^1.0.0", "@0x/subproviders": "^2.1.8", "@0x/tslint-config": "^2.0.0", "@0x/types": "^1.4.1", diff --git a/packages/metacoin/test/utils/coverage.ts b/packages/metacoin/test/utils/coverage.ts index 31275a163..1a06d8c3c 100644 --- a/packages/metacoin/test/utils/coverage.ts +++ b/packages/metacoin/test/utils/coverage.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-coverage'; import * as _ from 'lodash'; import { config } from './config'; diff --git a/packages/metacoin/test/utils/profiler.ts b/packages/metacoin/test/utils/profiler.ts index e7c373d20..6e6fc309f 100644 --- a/packages/metacoin/test/utils/profiler.ts +++ b/packages/metacoin/test/utils/profiler.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0x/dev-utils'; -import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-cov'; +import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0x/sol-profiler'; import * as _ from 'lodash'; import { config } from './config'; diff --git a/packages/sol-cov/.npmignore b/packages/sol-cov/.npmignore deleted file mode 100644 index 037786e46..000000000 --- a/packages/sol-cov/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -.* -yarn-error.log -/src/ -/scripts/ -tsconfig.json -/lib/src/monorepo_scripts/ diff --git a/packages/sol-cov/CHANGELOG.json b/packages/sol-cov/CHANGELOG.json deleted file mode 100644 index b7973c135..000000000 --- a/packages/sol-cov/CHANGELOG.json +++ /dev/null @@ -1,389 +0,0 @@ -[ - { - "version": "2.1.16", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544739608 - }, - { - "version": "2.1.15", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544570656 - }, - { - "timestamp": 1543401373, - "version": "2.1.14", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542821676, - "version": "2.1.13", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542208198, - "version": "2.1.12", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542134075, - "version": "2.1.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542028948, - "version": "2.1.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.9", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1541740904 - }, - { - "version": "2.1.8", - "changes": [ - { - "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", - "pr": 1105 - } - ], - "timestamp": 1539871071 - }, - { - "version": "2.1.7", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1538693146 - }, - { - "timestamp": 1538157789, - "version": "2.1.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537907159, - "version": "2.1.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537875740, - "version": "2.1.4", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537541580, - "version": "2.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1536142250, - "version": "2.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1535377027, - "version": "2.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.0", - "changes": [ - { - "note": - "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", - "pr": 924 - } - ], - "timestamp": 1535133899 - }, - { - "version": "2.0.0", - "changes": [ - { - "note": - "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", - "pr": 938 - }, - { - "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", - "pr": 938 - }, - { - "note": - "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", - "pr": 938 - }, - { - "note": - "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", - "pr": 938 - } - ], - "timestamp": 1534210131 - }, - { - "version": "1.0.3", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1532619515 - }, - { - "timestamp": 1532605697, - "version": "1.0.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532357734, - "version": "1.0.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532043000, - "version": "1.0.0", - "changes": [ - { - "note": - "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", - "pr": 589 - }, - { - "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", - "pr": 589 - }, - { - "note": "Properly parse multi-level traces", - "pr": 589 - }, - { - "note": "Add support for solidity libraries", - "pr": 589 - }, - { - "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", - "pr": 675 - }, - { - "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", - "pr": 675 - }, - { - "note": "Fixed a bug when the tool crashed on empty traces", - "pr": 675 - }, - { - "note": "Use `BlockchainLifecycle` to support reverts on Geth", - "pr": 675 - }, - { - "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", - "pr": 675 - }, - { - "note": "Collect traces from `estimate_gas` calls", - "pr": 675 - }, - { - "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", - "pr": 675 - }, - { - "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", - "pr": 675 - }, - { - "note": "Skip interface artifacts with a warning instead of failing", - "pr": 675 - }, - { - "note": "Fix `solcVersion` regex in parameter validation", - "pr": 690 - }, - { - "note": - "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", - "pr": 690 - }, - { - "note": "HUGE perf improvements", - "pr": 690 - }, - { - "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", - "pr": 705 - }, - { - "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", - "pr": 725 - } - ] - }, - { - "timestamp": 1531919263, - "version": "0.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1531149657, - "version": "0.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1529397769, - "version": "0.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "0.1.0", - "changes": [ - { - "note": "Incorrect publish that was unpublished" - } - ], - "timestamp": 1527810075 - }, - { - "timestamp": 1527009134, - "version": "0.0.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525477860, - "version": "0.0.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525428773, - "version": "0.0.9", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1524044013, - "version": "0.0.8", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1523462196, - "version": "0.0.7", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522673609, - "version": "0.0.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522658513, - "version": "0.0.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - } -] diff --git a/packages/sol-cov/CHANGELOG.md b/packages/sol-cov/CHANGELOG.md deleted file mode 100644 index 879ef9c95..000000000 --- a/packages/sol-cov/CHANGELOG.md +++ /dev/null @@ -1,158 +0,0 @@ - - -CHANGELOG - -## v2.1.16 - _December 13, 2018_ - - * Dependencies updated - -## v2.1.15 - _December 11, 2018_ - - * Dependencies updated - -## v2.1.14 - _November 28, 2018_ - - * Dependencies updated - -## v2.1.13 - _November 21, 2018_ - - * Dependencies updated - -## v2.1.12 - _November 14, 2018_ - - * Dependencies updated - -## v2.1.11 - _November 13, 2018_ - - * Dependencies updated - -## v2.1.10 - _November 12, 2018_ - - * Dependencies updated - -## v2.1.9 - _November 9, 2018_ - - * Dependencies updated - -## v2.1.8 - _October 18, 2018_ - - * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) - -## v2.1.7 - _October 4, 2018_ - - * Dependencies updated - -## v2.1.6 - _September 28, 2018_ - - * Dependencies updated - -## v2.1.5 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.4 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.3 - _September 21, 2018_ - - * Dependencies updated - -## v2.1.2 - _September 5, 2018_ - - * Dependencies updated - -## v2.1.1 - _August 27, 2018_ - - * Dependencies updated - -## v2.1.0 - _August 24, 2018_ - - * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) - -## v2.0.0 - _August 14, 2018_ - - * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) - * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) - * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) - * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) - -## v1.0.3 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.2 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.1 - _July 23, 2018_ - - * Dependencies updated - -## v1.0.0 - _July 19, 2018_ - - * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) - * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) - * Properly parse multi-level traces (#589) - * Add support for solidity libraries (#589) - * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) - * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) - * Fixed a bug when the tool crashed on empty traces (#675) - * Use `BlockchainLifecycle` to support reverts on Geth (#675) - * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) - * Collect traces from `estimate_gas` calls (#675) - * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) - * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) - * Skip interface artifacts with a warning instead of failing (#675) - * Fix `solcVersion` regex in parameter validation (#690) - * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) - * HUGE perf improvements (#690) - * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) - * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) - -## v0.1.3 - _July 18, 2018_ - - * Dependencies updated - -## v0.1.2 - _July 9, 2018_ - - * Dependencies updated - -## v0.1.1 - _June 19, 2018_ - - * Dependencies updated - -## v0.1.0 - _May 31, 2018_ - - * Incorrect publish that was unpublished - -## v0.0.11 - _May 22, 2018_ - - * Dependencies updated - -## v0.0.10 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.9 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.8 - _April 18, 2018_ - - * Dependencies updated - -## v0.0.7 - _April 11, 2018_ - - * Dependencies updated - -## v0.0.6 - _April 2, 2018_ - - * Dependencies updated - -## v0.0.5 - _April 2, 2018_ - - * Dependencies updated diff --git a/packages/sol-cov/README.md b/packages/sol-cov/README.md deleted file mode 100644 index 31d73dc63..000000000 --- a/packages/sol-cov/README.md +++ /dev/null @@ -1,75 +0,0 @@ -## @0x/sol-cov - -A Solidity code coverage tool. - -### Read the [Documentation](https://0xproject.com/docs/sol-cov). - -## Installation - -```bash -yarn add @0x/sol-cov -``` - -**Import** - -```javascript -import { CoverageSubprovider } from '@0x/sol-cov'; -``` - -or - -```javascript -var CoverageSubprovider = require('@0x/sol-cov').CoverageSubprovider; -``` - -## 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-cov yarn build -``` - -Or continuously rebuild on change: - -```bash -PKG=@0x/sol-cov yarn watch -``` - -### Clean - -```bash -yarn clean -``` - -### Lint - -```bash -yarn lint -``` - -### Run Tests - -```bash -yarn test -``` diff --git a/packages/sol-cov/compiler.json b/packages/sol-cov/compiler.json deleted file mode 100644 index a6a0c6d3a..000000000 --- a/packages/sol-cov/compiler.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "contracts": ["SimpleStorage"], - "contractsDir": "test/fixtures/contracts", - "artifactsDir": "test/fixtures/artifacts", - "compilerSettings": { - "outputSelection": { - "*": { - "*": [ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap" - ] - } - } - } -} diff --git a/packages/sol-cov/coverage/.gitkeep b/packages/sol-cov/coverage/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json deleted file mode 100644 index 3ade51c80..000000000 --- a/packages/sol-cov/package.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "name": "@0x/sol-cov", - "version": "2.1.16", - "engines": { - "node": ">=6.12" - }, - "description": "Generate coverage reports for Solidity code", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "build": "yarn pre_build && tsc -b", - "build:ci": "yarn build", - "pre_build": "run-s copy_test_fixtures", - "lint": "tslint --format stylish --project .", - "test": "run-s compile_test run_mocha", - "rebuild_and_test": "run-s clean build test", - "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", - "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "test:circleci": "yarn test:coverage", - "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", - "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", - "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", - "compile_test": "sol-compiler compile", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" - }, - "config": { - "postpublish": { - "assets": [], - "docOmitExports": [ - "ProfilerSubprovider", - "RevertTraceSubprovider" - ] - } - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x.js.git" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x.js/issues" - }, - "homepage": "https://github.com/0xProject/0x.js/packages/sol-cov/README.md", - "dependencies": { - "@0x/dev-utils": "^1.0.21", - "@0x/sol-compiler": "^1.1.16", - "@0x/subproviders": "^2.1.8", - "@0x/typescript-typings": "^3.0.6", - "@0x/utils": "^2.0.8", - "@0x/web3-wrapper": "^3.2.1", - "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.4", - "ethereumjs-util": "^5.1.1", - "glob": "^7.1.2", - "istanbul": "^0.4.5", - "lodash": "^4.17.5", - "loglevel": "^1.6.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.2", - "semaphore-async-await": "^1.5.1", - "solidity-parser-antlr": "^0.2.12" - }, - "devDependencies": { - "@0x/tslint-config": "^2.0.0", - "@types/istanbul": "^0.4.30", - "@types/loglevel": "^1.5.3", - "@types/mkdirp": "^0.5.1", - "@types/mocha": "^2.2.42", - "@types/node": "*", - "@types/rimraf": "^2.0.2", - "chai": "^4.0.1", - "copyfiles": "^2.0.0", - "dirty-chai": "^2.0.1", - "make-promises-safe": "^1.1.0", - "mocha": "^4.1.0", - "npm-run-all": "^4.1.2", - "nyc": "^11.0.1", - "shx": "^0.2.2", - "sinon": "^4.0.0", - "tslint": "5.11.0", - "typedoc": "0.13.0", - "typescript": "3.0.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts deleted file mode 100644 index fcc6562ad..000000000 --- a/packages/sol-cov/src/artifact_adapters/abstract_artifact_adapter.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContractData } from '../types'; - -export abstract class AbstractArtifactAdapter { - public abstract async collectContractsDataAsync(): Promise; -} diff --git a/packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts deleted file mode 100644 index 57391abbe..000000000 --- a/packages/sol-cov/src/artifact_adapters/sol_compiler_artifact_adapter.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { CompilerOptions, ContractArtifact } from 'ethereum-types'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as _ from 'lodash'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; - -const CONFIG_FILE = 'compiler.json'; - -export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { - private readonly _artifactsPath: string; - private readonly _sourcesPath: string; - /** - * Instantiates a SolCompilerArtifactAdapter - * @param artifactsPath Path to your artifacts directory - * @param sourcesPath Path to your contract sources directory - */ - constructor(artifactsPath?: string, sourcesPath?: string) { - super(); - const config: CompilerOptions = fs.existsSync(CONFIG_FILE) - ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) - : {}; - if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { - throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); - } - this._artifactsPath = (artifactsPath || config.artifactsDir) as string; - if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { - throw new Error(`contractsDir not found in ${CONFIG_FILE}`); - } - this._sourcesPath = (sourcesPath || config.contractsDir) as string; - } - public async collectContractsDataAsync(): Promise { - const artifactsGlob = `${this._artifactsPath}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - const contractsData: ContractData[] = []; - for (const artifactFileName of artifactFileNames) { - const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - if (_.isUndefined(artifact.compilerOutput.evm)) { - 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 contractData = { - sourceCodes, - sources, - bytecode: artifact.compilerOutput.evm.bytecode.object, - sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, - runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, - sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, - }; - contractsData.push(contractData); - } - return contractsData; - } -} diff --git a/packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts deleted file mode 100644 index f064911d3..000000000 --- a/packages/sol-cov/src/artifact_adapters/truffle_artifact_adapter.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Compiler, CompilerOptions } from '@0x/sol-compiler'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; -import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; - -const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; - -interface TruffleConfig { - solc?: any; - contracts_build_directory?: string; -} - -export class TruffleArtifactAdapter extends AbstractArtifactAdapter { - private readonly _solcVersion: string; - private readonly _projectRoot: string; - /** - * Instantiates a TruffleArtifactAdapter - * @param projectRoot Path to the truffle project's root directory - * @param solcVersion Solidity version with which to compile all the contracts - */ - constructor(projectRoot: string, solcVersion: string) { - super(); - this._solcVersion = solcVersion; - this._projectRoot = projectRoot; - } - public async collectContractsDataAsync(): Promise { - const artifactsDir = '.0x-artifacts'; - const contractsDir = path.join(this._projectRoot, 'contracts'); - const truffleConfig = this._getTruffleConfig(); - const solcConfig = truffleConfig.solc || {}; - const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; - this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); - const compilerOptions: CompilerOptions = { - contractsDir, - artifactsDir, - compilerSettings: { - ...solcConfig, - outputSelection: { - ['*']: { - ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], - }, - }, - }, - contracts: '*', - solcVersion: this._solcVersion, - }; - const compiler = new Compiler(compilerOptions); - await compiler.compileAsync(); - const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); - const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); - return contractsDataFrom0xArtifacts; - } - private _getTruffleConfig(): TruffleConfig { - const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); - const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); - if (fs.existsSync(truffleConfigFileShort)) { - const truffleConfig = require(truffleConfigFileShort); - return truffleConfig; - } else if (fs.existsSync(truffleConfigFileLong)) { - const truffleConfig = require(truffleConfigFileLong); - return truffleConfig; - } else { - throw new Error( - `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, - ); - } - } - private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { - const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - for (const artifactFileName of artifactFileNames) { - const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - const compilerVersion = artifact.compiler.version; - if (!compilerVersion.startsWith(this._solcVersion)) { - throw new Error( - `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ - this._solcVersion - } making it impossible for sol-cov to process traces`, - ); - } - } - } -} diff --git a/packages/sol-cov/src/ast_visitor.ts b/packages/sol-cov/src/ast_visitor.ts deleted file mode 100644 index e55cdf6ec..000000000 --- a/packages/sol-cov/src/ast_visitor.ts +++ /dev/null @@ -1,168 +0,0 @@ -import * as _ from 'lodash'; -import * as Parser from 'solidity-parser-antlr'; - -import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types'; - -export interface CoverageEntriesDescription { - fnMap: FnMap; - branchMap: BranchMap; - statementMap: StatementMap; - modifiersStatementIds: number[]; -} - -enum BranchType { - If = 'if', - ConditionalExpression = 'cond-expr', - BinaryExpression = 'binary-expr', -} - -export class ASTVisitor { - private _entryId = 0; - private readonly _fnMap: FnMap = {}; - private readonly _branchMap: BranchMap = {}; - private readonly _modifiersStatementIds: number[] = []; - private readonly _statementMap: StatementMap = {}; - private readonly _locationByOffset: LocationByOffset; - 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; - this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; - } - public getCollectedCoverageEntries(): CoverageEntriesDescription { - const coverageEntriesDescription = { - fnMap: this._fnMap, - branchMap: this._branchMap, - statementMap: this._statementMap, - modifiersStatementIds: this._modifiersStatementIds, - }; - return coverageEntriesDescription; - } - public IfStatement(ast: Parser.IfStatement): void { - this._visitStatement(ast); - this._visitBinaryBranch(ast, ast.trueBody, ast.falseBody || ast, BranchType.If); - } - public FunctionDefinition(ast: Parser.FunctionDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ContractDefinition(ast: Parser.ContractDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - } - } - public ModifierDefinition(ast: Parser.ModifierDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ForStatement(ast: Parser.ForStatement): void { - this._visitStatement(ast); - } - public ReturnStatement(ast: Parser.ReturnStatement): void { - this._visitStatement(ast); - } - public BreakStatement(ast: Parser.BreakStatement): void { - this._visitStatement(ast); - } - public ContinueStatement(ast: Parser.ContinueStatement): void { - this._visitStatement(ast); - } - public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { - this._visitStatement(ast); - } - public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { - this._visitStatement(ast); - } - public Statement(ast: Parser.Statement): void { - this._visitStatement(ast); - } - public WhileStatement(ast: Parser.WhileStatement): void { - this._visitStatement(ast); - } - public SimpleStatement(ast: Parser.SimpleStatement): void { - this._visitStatement(ast); - } - public ThrowStatement(ast: Parser.ThrowStatement): void { - this._visitStatement(ast); - } - public DoWhileStatement(ast: Parser.DoWhileStatement): void { - this._visitStatement(ast); - } - public ExpressionStatement(ast: Parser.ExpressionStatement): void { - this._visitStatement(ast.expression); - } - public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { - this._visitStatement(ast); - } - public BinaryOperation(ast: Parser.BinaryOperation): void { - const BRANCHING_BIN_OPS = ['&&', '||']; - if (_.includes(BRANCHING_BIN_OPS, ast.operator)) { - this._visitBinaryBranch(ast, ast.left, ast.right, BranchType.BinaryExpression); - } - } - public Conditional(ast: Parser.Conditional): void { - this._visitBinaryBranch(ast, ast.trueExpression, ast.falseExpression, BranchType.ConditionalExpression); - } - public ModifierInvocation(ast: Parser.ModifierInvocation): void { - const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; - if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._modifiersStatementIds.push(this._entryId); - this._visitStatement(ast); - } - } - private _visitBinaryBranch( - ast: Parser.ASTNode, - left: Parser.ASTNode, - right: Parser.ASTNode, - type: BranchType, - ): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._branchMap[this._entryId++] = { - line: this._getExpressionRange(ast).start.line, - type, - locations: [this._getExpressionRange(left), this._getExpressionRange(right)], - }; - } - private _visitStatement(ast: Parser.ASTNode): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._statementMap[this._entryId++] = this._getExpressionRange(ast); - } - 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 range = { - start, - end, - }; - return range; - } - private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { - const [astStart, astEnd] = ast.range as [number, number]; - const isRangeIgnored = _.some( - this._ignoreRangesWithin, - ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, - ); - return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - return; - } - const loc = this._getExpressionRange(ast); - this._fnMap[this._entryId++] = { - name: ast.name, - line: loc.start.line, - loc, - }; - this._visitStatement(ast); - } -} diff --git a/packages/sol-cov/src/collect_coverage_entries.ts b/packages/sol-cov/src/collect_coverage_entries.ts deleted file mode 100644 index bdbcd613e..000000000 --- a/packages/sol-cov/src/collect_coverage_entries.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as parser from 'solidity-parser-antlr'; - -import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; -import { getLocationByOffset } 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 } = {}; - -export const collectCoverageEntries = (contractSource: string) => { - const sourceHash = ethUtil.sha3(contractSource).toString('hex'); - if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { - const ast = parser.parse(contractSource, { range: true }); - const locationByOffset = getLocationByOffset(contractSource); - const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); - const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); - parser.visit(ast, visitor); - coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); - } - const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; - return coverageEntriesDescription; -}; - -// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" -function gatherRangesToIgnore(contractSource: string): number[] { - const ignoreRangesStart = []; - - let match; - do { - match = IGNORE_RE.exec(contractSource); - if (match) { - const matchLen = match[0].length; - ignoreRangesStart.push(match.index + matchLen); - } - } while (match); - - return ignoreRangesStart; -} diff --git a/packages/sol-cov/src/constants.ts b/packages/sol-cov/src/constants.ts deleted file mode 100644 index 34d62b537..000000000 --- a/packages/sol-cov/src/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -// tslint:disable:number-literal-format -export const constants = { - NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', - PUSH1: 0x60, - PUSH2: 0x61, - PUSH32: 0x7f, - TIMESTAMP: 0x42, -}; diff --git a/packages/sol-cov/src/coverage_subprovider.ts b/packages/sol-cov/src/coverage_subprovider.ts deleted file mode 100644 index 9667e891c..000000000 --- a/packages/sol-cov/src/coverage_subprovider.ts +++ /dev/null @@ -1,146 +0,0 @@ -import * as _ from 'lodash'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { collectCoverageEntries } from './collect_coverage_entries'; -import { SingleFileSubtraceHandler, TraceCollector } from './trace_collector'; -import { TraceInfoSubprovider } from './trace_info_subprovider'; -import { - BranchCoverage, - ContractData, - Coverage, - FunctionCoverage, - FunctionDescription, - SourceRange, - StatementCoverage, - StatementDescription, - Subtrace, - TraceInfo, -} from './types'; -import { utils } from './utils'; - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It's used to compute your code coverage while running solidity tests. - */ -export class CoverageSubprovider extends TraceInfoSubprovider { - private readonly _coverageCollector: TraceCollector; - /** - * Instantiates a CoverageSubprovider 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._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler); - } - protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { - await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo); - } - /** - * Write the test coverage results to a file in Istanbul format. - */ - public async writeCoverageAsync(): Promise { - await this._coverageCollector.writeOutputAsync(); - } -} - -/** - * Computed partial coverage for a single file & subtrace. - * @param contractData Contract metadata (source, srcMap, bytecode) - * @param subtrace A subset of a transcation/call trace that was executed within that contract - * @param pcToSourceRange A mapping from program counters to source ranges - * @param fileIndex Index of a file to compute coverage for - * @return Partial istanbul coverage for that file & subtrace - */ -export const coverageHandler: SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -): Coverage => { - const absoluteFileName = contractData.sources[fileIndex]; - const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); - - // if the source wasn't provided for the fileIndex, we can't cover the file - if (_.isUndefined(coverageEntriesDescription)) { - return {}; - } - - 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. - 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 = {}; - const branchIds = _.keys(coverageEntriesDescription.branchMap); - for (const branchId of branchIds) { - const branchDescription = coverageEntriesDescription.branchMap[branchId]; - const isBranchCoveredByBranchIndex = _.map(branchDescription.locations, location => { - const isBranchCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, location)); - const timesBranchCovered = Number(isBranchCovered); - return timesBranchCovered; - }); - branchCoverage[branchId] = isBranchCoveredByBranchIndex; - } - const statementCoverage: StatementCoverage = {}; - const statementIds = _.keys(coverageEntriesDescription.statementMap); - for (const statementId of statementIds) { - const statementDescription = coverageEntriesDescription.statementMap[statementId]; - const isStatementCovered = _.some(sourceRanges, range => - utils.isRangeInside(range.location, statementDescription), - ); - const timesStatementCovered = Number(isStatementCovered); - statementCoverage[statementId] = timesStatementCovered; - } - const functionCoverage: FunctionCoverage = {}; - const functionIds = _.keys(coverageEntriesDescription.fnMap); - for (const fnId of functionIds) { - const functionDescription = coverageEntriesDescription.fnMap[fnId]; - const isFunctionCovered = _.some(sourceRanges, range => - utils.isRangeInside(range.location, functionDescription.loc), - ); - const timesFunctionCovered = Number(isFunctionCovered); - functionCoverage[fnId] = timesFunctionCovered; - } - // HACK: Solidity doesn't emit any opcodes that map back to modifiers with no args, that's why we map back to the - // function range and check if there is any covered statement within that range. - for (const modifierStatementId of coverageEntriesDescription.modifiersStatementIds) { - if (statementCoverage[modifierStatementId]) { - // Already detected as covered - continue; - } - const modifierDescription = coverageEntriesDescription.statementMap[modifierStatementId]; - const enclosingFunction = _.find(coverageEntriesDescription.fnMap, functionDescription => - utils.isRangeInside(modifierDescription, functionDescription.loc), - ) as FunctionDescription; - const isModifierCovered = _.some( - coverageEntriesDescription.statementMap, - (statementDescription: StatementDescription, statementId: number) => { - const isInsideTheModifierEnclosingFunction = utils.isRangeInside( - statementDescription, - enclosingFunction.loc, - ); - const isCovered = statementCoverage[statementId]; - return isInsideTheModifierEnclosingFunction && isCovered; - }, - ); - const timesModifierCovered = Number(isModifierCovered); - statementCoverage[modifierStatementId] = timesModifierCovered; - } - const partialCoverage = { - [absoluteFileName]: { - ...coverageEntriesDescription, - path: absoluteFileName, - f: functionCoverage, - s: statementCoverage, - b: branchCoverage, - }, - }; - return partialCoverage; -}; diff --git a/packages/sol-cov/src/get_source_range_snippet.ts b/packages/sol-cov/src/get_source_range_snippet.ts deleted file mode 100644 index bea17beae..000000000 --- a/packages/sol-cov/src/get_source_range_snippet.ts +++ /dev/null @@ -1,180 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as Parser from 'solidity-parser-antlr'; - -import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; -import { utils } from './utils'; - -interface ASTInfo { - type: string; - node: Parser.ASTNode; - name: string | null; - range?: SingleFileSourceRange; -} - -// Parsing source code for each transaction/code is slow and therefore we cache it -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 }); - } - const astNode = parsedSourceByHash[sourceHash]; - const visitor = new ASTInfoVisitor(); - Parser.visit(astNode, visitor); - const astInfo = visitor.getASTInfoForRange(sourceRange); - if (astInfo === null) { - return null; - } - const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); - return { - ...astInfo, - range: astInfo.range as SingleFileSourceRange, - source: sourceCodeInRange, - fileName: sourceRange.fileName, - }; -} - -// A visitor which collects ASTInfo for most nodes in the AST. -class ASTInfoVisitor { - private readonly _astInfos: ASTInfo[] = []; - public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { - // HACK(albrow): Sometimes the source range doesn't exactly match that - // of astInfo. To work around that we try with a +/-1 offset on - // end.column. If nothing matches even with the offset, we return null. - const offset = { - start: { - line: 0, - column: 0, - }, - end: { - line: 0, - column: 0, - }, - }; - let astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column += 1; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column -= 2; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - return null; - } - public ContractDefinition(ast: Parser.ContractDefinition): void { - this._visitContractDefinition(ast); - } - public IfStatement(ast: Parser.IfStatement): void { - this._visitStatement(ast); - } - public FunctionDefinition(ast: Parser.FunctionDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ModifierDefinition(ast: Parser.ModifierDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ForStatement(ast: Parser.ForStatement): void { - this._visitStatement(ast); - } - public ReturnStatement(ast: Parser.ReturnStatement): void { - this._visitStatement(ast); - } - public BreakStatement(ast: Parser.BreakStatement): void { - this._visitStatement(ast); - } - public ContinueStatement(ast: Parser.ContinueStatement): void { - this._visitStatement(ast); - } - public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { - this._visitStatement(ast); - } - public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { - this._visitStatement(ast); - } - public Statement(ast: Parser.Statement): void { - this._visitStatement(ast); - } - public WhileStatement(ast: Parser.WhileStatement): void { - this._visitStatement(ast); - } - public SimpleStatement(ast: Parser.SimpleStatement): void { - this._visitStatement(ast); - } - public ThrowStatement(ast: Parser.ThrowStatement): void { - this._visitStatement(ast); - } - public DoWhileStatement(ast: Parser.DoWhileStatement): void { - this._visitStatement(ast); - } - public ExpressionStatement(ast: Parser.ExpressionStatement): void { - this._visitStatement(ast.expression); - } - public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { - this._visitStatement(ast); - } - public ModifierInvocation(ast: Parser.ModifierInvocation): void { - const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; - if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { - this._visitStatement(ast); - } - } - private _visitStatement(ast: Parser.ASTNode): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: null, - range: ast.loc, - }); - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _visitContractDefinition(ast: Parser.ContractDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { - const offsetSourceRange = { - ...sourceRange, - location: { - start: { - line: sourceRange.location.start.line + offset.start.line, - column: sourceRange.location.start.column + offset.start.column, - }, - end: { - line: sourceRange.location.end.line + offset.end.line, - column: sourceRange.location.end.column + offset.end.column, - }, - }, - }; - for (const astInfo of this._astInfos) { - const astInfoRange = astInfo.range as SingleFileSourceRange; - if ( - astInfoRange.start.column === offsetSourceRange.location.start.column && - astInfoRange.start.line === offsetSourceRange.location.start.line && - astInfoRange.end.column === offsetSourceRange.location.end.column && - astInfoRange.end.line === offsetSourceRange.location.end.line - ) { - return astInfo; - } - } - return null; - } -} diff --git a/packages/sol-cov/src/globals.d.ts b/packages/sol-cov/src/globals.d.ts deleted file mode 100644 index e799b3529..000000000 --- a/packages/sol-cov/src/globals.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// tslint:disable:completed-docs -declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ -} diff --git a/packages/sol-cov/src/index.ts b/packages/sol-cov/src/index.ts deleted file mode 100644 index 348e0fc9b..000000000 --- a/packages/sol-cov/src/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -export { CoverageSubprovider } from './coverage_subprovider'; -export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; -export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; -export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; - -// HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready -export { ProfilerSubprovider } from './profiler_subprovider'; -export { RevertTraceSubprovider } from './revert_trace_subprovider'; - -export { ContractData } from './types'; -export { - JSONRPCRequestPayload, - Provider, - JSONRPCErrorCallback, - JSONRPCResponsePayload, - JSONRPCResponseError, -} from 'ethereum-types'; - -export { - JSONRPCRequestPayloadWithMethod, - NextCallback, - ErrorCallback, - OnNextCompleted, - Callback, -} from '@0x/subproviders'; diff --git a/packages/sol-cov/src/instructions.ts b/packages/sol-cov/src/instructions.ts deleted file mode 100644 index 40987dbe5..000000000 --- a/packages/sol-cov/src/instructions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { constants } from './constants'; - -const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; - -const pushDataLength = (inst: number) => inst - constants.PUSH1 + 1; - -const instructionLength = (inst: number) => (isPush(inst) ? pushDataLength(inst) + 1 : 1); - -export const getPcToInstructionIndexMapping = (bytecode: Uint8Array) => { - const result: { - [programCounter: number]: number; - } = {}; - let byteIndex = 0; - let instructionIndex = 0; - while (byteIndex < bytecode.length) { - const instruction = bytecode[byteIndex]; - const length = instructionLength(instruction); - result[byteIndex] = instructionIndex; - byteIndex += length; - instructionIndex += 1; - } - return result; -}; diff --git a/packages/sol-cov/src/profiler_subprovider.ts b/packages/sol-cov/src/profiler_subprovider.ts deleted file mode 100644 index ae9351f17..000000000 --- a/packages/sol-cov/src/profiler_subprovider.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as _ from 'lodash'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { collectCoverageEntries } from './collect_coverage_entries'; -import { SingleFileSubtraceHandler, TraceCollector } from './trace_collector'; -import { TraceInfoSubprovider } from './trace_info_subprovider'; -import { ContractData, Coverage, SourceRange, Subtrace, TraceInfo } from './types'; -import { utils } from './utils'; - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * ProfilerSubprovider is used to profile Solidity code while running tests. - */ -export class ProfilerSubprovider extends TraceInfoSubprovider { - private readonly _profilerCollector: TraceCollector; - /** - * Instantiates a ProfilerSubprovider 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: false, - shouldCollectCallTraces: false, - }; - super(defaultFromAddress, traceCollectionSubproviderConfig); - this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler); - } - protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { - await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo); - } - /** - * Write the test profiler results to a file in Istanbul format. - */ - public async writeProfilerOutputAsync(): Promise { - await this._profilerCollector.writeOutputAsync(); - } -} - -/** - * Computed partial coverage for a single file & subtrace for the purposes of - * gas profiling. - * @param contractData Contract metadata (source, srcMap, bytecode) - * @param subtrace A subset of a transcation/call trace that was executed within that contract - * @param pcToSourceRange A mapping from program counters to source ranges - * @param fileIndex Index of a file to compute coverage for - * @return Partial istanbul coverage for that file & subtrace - */ -export const profilerHandler: SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -): Coverage => { - const absoluteFileName = contractData.sources[fileIndex]; - const profilerEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); - const gasConsumedByStatement: { [statementId: string]: number } = {}; - const statementIds = _.keys(profilerEntriesDescription.statementMap); - for (const statementId of statementIds) { - const statementDescription = profilerEntriesDescription.statementMap[statementId]; - const totalGasCost = _.sum( - _.map(subtrace, 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 { - return 0; - } - }), - ); - gasConsumedByStatement[statementId] = totalGasCost; - } - const partialProfilerOutput = { - [absoluteFileName]: { - ...profilerEntriesDescription, - path: absoluteFileName, - f: {}, // I's meaningless in profiling context - s: gasConsumedByStatement, - b: {}, // I's meaningless in profiling context - }, - }; - return partialProfilerOutput; -}; diff --git a/packages/sol-cov/src/revert_trace.ts b/packages/sol-cov/src/revert_trace.ts deleted file mode 100644 index d60c6e7d9..000000000 --- a/packages/sol-cov/src/revert_trace.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; - -import * as _ from 'lodash'; - -import { EvmCallStack } from './types'; -import { utils } from './utils'; - -export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { - const evmCallStack: EvmCallStack = []; - const addressStack = [startAddress]; - 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]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - - 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], - ); - - // 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]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - evmCallStack.push({ - address: currentAddress, - structLog, - }); - } - } 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]; - if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { - evmCallStack.pop(); - addressStack.pop(); - } - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. Sol-cov currently doesn't support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Revert) { - evmCallStack.push({ - address: _.last(addressStack) as string, - structLog, - }); - return evmCallStack; - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. Sol-cov currently doesn't support that scenario. We'll just skip that trace", - ); - return []; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - addressStack.pop(); - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (evmCallStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); - } - return []; -} diff --git a/packages/sol-cov/src/revert_trace_subprovider.ts b/packages/sol-cov/src/revert_trace_subprovider.ts deleted file mode 100644 index 6ccf59653..000000000 --- a/packages/sol-cov/src/revert_trace_subprovider.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { stripHexPrefix } from 'ethereumjs-util'; -import * as _ from 'lodash'; -import { getLogger, levels, Logger } from 'loglevel'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { constants } from './constants'; -import { getSourceRangeSnippet } from './get_source_range_snippet'; -import { getRevertTrace } from './revert_trace'; -import { parseSourceMap } from './source_maps'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { ContractData, EvmCallStack, SourceRange, SourceSnippet } from './types'; -import { utils } from './utils'; - -/** - * 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-cov'); - 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 { - 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 { - 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-cov/src/source_maps.ts b/packages/sol-cov/src/source_maps.ts deleted file mode 100644 index 90b21dda1..000000000 --- a/packages/sol-cov/src/source_maps.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as _ from 'lodash'; - -import { getPcToInstructionIndexMapping } from './instructions'; -import { LineColumn, LocationByOffset, SourceRange } from './types'; - -const RADIX = 10; - -export interface SourceLocation { - offset: number; - length: number; - fileIndex: number; -} - -export function getLocationByOffset(str: string): LocationByOffset { - const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } }; - let currentOffset = 0; - for (const char of str.split('')) { - const location = locationByOffset[currentOffset]; - const isNewline = char === '\n'; - locationByOffset[currentOffset + 1] = { - line: location.line + (isNewline ? 1 : 0), - column: isNewline ? 0 : location.column + 1, - }; - currentOffset++; - } - return locationByOffset; -} - -// Parses a sourcemap string -// The solidity sourcemap format is documented here: https://github.com/ethereum/solidity/blob/develop/docs/miscellaneous.rst#source-mappings -export function parseSourceMap( - sourceCodes: string[], - srcMap: string, - bytecodeHex: string, - sources: 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 entries = srcMap.split(';'); - let lastParsedEntry: SourceLocation = {} as any; - const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; - _.each(entries, (entry: string, i: number) => { - // tslint:disable-next-line:no-unused-variable - const [instructionIndexStrIfExists, lengthStrIfExists, fileIndexStrIfExists, jumpTypeStrIfExists] = entry.split( - ':', - ); - const instructionIndexIfExists = parseInt(instructionIndexStrIfExists, RADIX); - const lengthIfExists = parseInt(lengthStrIfExists, RADIX); - const fileIndexIfExists = parseInt(fileIndexStrIfExists, RADIX); - const offset = _.isNaN(instructionIndexIfExists) ? lastParsedEntry.offset : instructionIndexIfExists; - const length = _.isNaN(lengthIfExists) ? lastParsedEntry.length : lengthIfExists; - const fileIndex = _.isNaN(fileIndexIfExists) ? lastParsedEntry.fileIndex : fileIndexIfExists; - const parsedEntry = { - offset, - length, - fileIndex, - }; - if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { - const sourceRange = { - location: { - start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], - end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], - }, - fileName: sources[parsedEntry.fileIndex], - }; - instructionIndexToSourceRange[i] = sourceRange; - } else { - // Some assembly code generated by Solidity can't be mapped back to a line of source code. - // Source: https://github.com/ethereum/solidity/issues/3629 - } - lastParsedEntry = parsedEntry; - }); - const pcsToSourceRange: { [programCounter: number]: SourceRange } = {}; - for (const programCounterKey of _.keys(pcToInstructionIndex)) { - const pc = parseInt(programCounterKey, RADIX); - const instructionIndex: number = pcToInstructionIndex[pc]; - pcsToSourceRange[pc] = instructionIndexToSourceRange[instructionIndex]; - } - return pcsToSourceRange; -} diff --git a/packages/sol-cov/src/trace.ts b/packages/sol-cov/src/trace.ts deleted file mode 100644 index b38dbdce0..000000000 --- a/packages/sol-cov/src/trace.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { utils } from './utils'; - -export interface TraceByContractAddress { - [contractAddress: string]: StructLog[]; -} - -export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { - const traceByContractAddress: TraceByContractAddress = {}; - let currentTraceSegment = []; - const addressStack = [startAddress]; - if (_.isEmpty(structLogs)) { - return traceByContractAddress; - } - const normalizedStructLogs = utils.normalizeStructLogs(structLogs); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < normalizedStructLogs.length; i++) { - const structLog = normalizedStructLogs[i]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - currentTraceSegment.push(structLog); - - 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], - ); - - // 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]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } - } else if (utils.isEndOpcode(structLog.op)) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. Sol-cov currently doesn't support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. Sol-cov currently doesn't support that scenario. We'll just skip that trace", - ); - return traceByContractAddress; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (addressStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end'); - } - if (currentTraceSegment.length !== 0) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - logUtils.warn('Malformed trace. Current trace segment non empty at the end'); - } - return traceByContractAddress; -} diff --git a/packages/sol-cov/src/trace_collection_subprovider.ts b/packages/sol-cov/src/trace_collection_subprovider.ts deleted file mode 100644 index 25e38768d..000000000 --- a/packages/sol-cov/src/trace_collection_subprovider.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; -import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; -import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; -import * as _ from 'lodash'; -import { Lock } from 'semaphore-async-await'; - -import { constants } from './constants'; -import { BlockParamLiteral } from './types'; - -interface MaybeFakeTxData extends TxData { - isFakeTransaction?: boolean; -} - -const BLOCK_GAS_LIMIT = 6000000; - -export interface TraceCollectionSubproviderConfig { - shouldCollectTransactionTraces: boolean; - shouldCollectCallTraces: boolean; - shouldCollectGasEstimateTraces: boolean; -} - -// 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. - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must - * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. - */ -export abstract class TraceCollectionSubprovider extends Subprovider { - protected _web3Wrapper!: Web3Wrapper; - // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise - private readonly _lock = new Lock(); - private readonly _defaultFromAddress: string; - private _isEnabled = true; - private readonly _config: TraceCollectionSubproviderConfig; - /** - * Instantiates a TraceCollectionSubprovider instance - * @param defaultFromAddress default from address to use when sending transactions - */ - constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { - super(); - this._defaultFromAddress = defaultFromAddress; - this._config = config; - } - /** - * Starts trace collection - */ - public start(): void { - this._isEnabled = true; - } - /** - * Stops trace collection - */ - public stop(): void { - this._isEnabled = false; - } - /** - * This method conforms to the web3-provider-engine interface. - * It is called internally by the ProviderEngine when it is this subproviders - * turn to handle a JSON RPC request. - * @param payload JSON RPC payload - * @param next Callback to call if this subprovider decides not to handle the request - * @param _end Callback to call if subprovider handled the request and wants to pass back the request. - */ - // tslint:disable-next-line:prefer-function-over-method async-suffix - public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { - if (this._isEnabled) { - switch (payload.method) { - case 'eth_sendTransaction': - if (!this._config.shouldCollectTransactionTraces) { - next(); - } else { - const txData = payload.params[0]; - next(this._onTransactionSentAsync.bind(this, txData)); - } - return; - - case 'eth_call': - if (!this._config.shouldCollectCallTraces) { - next(); - } else { - const callData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); - } - return; - - case 'eth_estimateGas': - if (!this._config.shouldCollectGasEstimateTraces) { - next(); - } else { - const estimateGasData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); - } - return; - - default: - next(); - return; - } - } else { - next(); - return; - } - } - /** - * Set's the subprovider's engine to the ProviderEngine it is added to. - * This is only called within the ProviderEngine source code, do not call - * directly. - * @param engine The ProviderEngine this subprovider is added to - */ - public setEngine(engine: Provider): void { - super.setEngine(engine); - this._web3Wrapper = new Web3Wrapper(engine); - } - protected abstract async _recordTxTraceAsync( - address: string, - data: string | undefined, - txHash: string, - ): Promise; - private async _onTransactionSentAsync( - txData: MaybeFakeTxData, - err: Error | null, - txHash: string | undefined, - cb: Callback, - ): Promise { - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. Not a call executed as one. - // And we don't want it to be executed within a snapshotting period - await this._lock.acquire(); - } - const NULL_ADDRESS = '0x0'; - if (_.isNull(err)) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); - } else { - const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); - const transactions = latestBlock.transactions; - for (const transaction of transactions) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); - } - } - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. Not a call executed as one. - // And we don't want it to be executed within a snapshotting period - this._lock.release(); - } - cb(); - } - private async _onCallOrGasEstimateExecutedAsync( - callData: Partial, - _err: Error | null, - _callResult: string, - cb: Callback, - ): Promise { - await this._recordCallOrGasEstimateTraceAsync(callData); - cb(); - } - private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { - // We don't want other transactions to be exeucted during snashotting period, that's why we lock the - // transaction execution for all transactions except our fake ones. - await this._lock.acquire(); - const blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); - await blockchainLifecycle.startAsync(); - const fakeTxData = { - gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers - isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked - ...callData, - from: callData.from || this._defaultFromAddress, - }; - try { - const txData = marshaller.unmarshalTxData(fakeTxData); - const txHash = await this._web3Wrapper.sendTransactionAsync(txData); - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - } catch (err) { - // TODO(logvinov) Check that transaction failed and not some other exception - // Even if this transaction failed - we've already recorded it's trace. - _.noop(); - } - await blockchainLifecycle.revertAsync(); - this._lock.release(); - } -} diff --git a/packages/sol-cov/src/trace_collector.ts b/packages/sol-cov/src/trace_collector.ts deleted file mode 100644 index 44cffc238..000000000 --- a/packages/sol-cov/src/trace_collector.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { promisify } from '@0x/utils'; -import { stripHexPrefix } from 'ethereumjs-util'; -import * as fs from 'fs'; -import { Collector } from 'istanbul'; -import * as _ from 'lodash'; -import { getLogger, levels, Logger } from 'loglevel'; -import * as mkdirp from 'mkdirp'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { constants } from './constants'; -import { parseSourceMap } from './source_maps'; -import { - ContractData, - Coverage, - SourceRange, - Subtrace, - TraceInfo, - TraceInfoExistingContract, - TraceInfoNewContract, -} from './types'; -import { utils } from './utils'; - -const mkdirpAsync = promisify(mkdirp); - -export type SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -) => Coverage; - -/** - * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. - */ -export class TraceCollector { - private readonly _artifactAdapter: AbstractArtifactAdapter; - private readonly _logger: Logger; - private _contractsData!: ContractData[]; - private readonly _collector = new Collector(); - private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; - - /** - * Instantiates a TraceCollector instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace - */ - constructor( - artifactAdapter: AbstractArtifactAdapter, - isVerbose: boolean, - singleFileSubtraceHandler: SingleFileSubtraceHandler, - ) { - this._artifactAdapter = artifactAdapter; - this._logger = getLogger('sol-cov'); - this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); - this._singleFileSubtraceHandler = singleFileSubtraceHandler; - } - public async writeOutputAsync(): Promise { - const finalCoverage = this._collector.getFinalCoverage(); - const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); - await mkdirpAsync('coverage'); - fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); - } - public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { - if (_.isUndefined(this._contractsData)) { - this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); - } - const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; - const bytecode = isContractCreation - ? (traceInfo as TraceInfoNewContract).bytecode - : (traceInfo as TraceInfoExistingContract).runtimeBytecode; - const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); - if (_.isUndefined(contractData)) { - const errMsg = isContractCreation - ? `Unknown contract creation transaction` - : `Transaction to an unknown address: ${traceInfo.address}`; - this._logger.warn(errMsg); - return; - } - 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++) { - const singleFileCoverageForTrace = this._singleFileSubtraceHandler( - contractData, - traceInfo.subtrace, - pcToSourceRange, - fileIndex, - ); - this._collector.add(singleFileCoverageForTrace); - } - } -} diff --git a/packages/sol-cov/src/trace_info_subprovider.ts b/packages/sol-cov/src/trace_info_subprovider.ts deleted file mode 100644 index 635a68f58..000000000 --- a/packages/sol-cov/src/trace_info_subprovider.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as _ from 'lodash'; - -import { constants } from './constants'; -import { getTracesByContractAddress } from './trace'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; - -// TraceInfoSubprovider is extended by subproviders which need to work with one -// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which -// is called for each TraceInfo. -export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { - protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; - protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { - 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); - if (address === constants.NEW_CONTRACT) { - for (const subcallAddress of subcallAddresses) { - let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; - if (subcallAddress === 'NEW_CONTRACT') { - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - bytecode: data as string, - }; - } else { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - } - await this._handleTraceInfoAsync(traceInfo); - } - } else { - for (const subcallAddress of subcallAddresses) { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - const traceInfo: TraceInfoExistingContract = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - await this._handleTraceInfoAsync(traceInfo); - } - } - } -} diff --git a/packages/sol-cov/src/types.ts b/packages/sol-cov/src/types.ts deleted file mode 100644 index 54ade0400..000000000 --- a/packages/sol-cov/src/types.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { StructLog } from 'ethereum-types'; -import * as Parser from 'solidity-parser-antlr'; - -export interface LineColumn { - line: number; - column: number; -} - -export interface SourceRange { - location: SingleFileSourceRange; - fileName: string; -} - -export interface SingleFileSourceRange { - start: LineColumn; - end: LineColumn; -} - -export interface LocationByOffset { - [offset: number]: LineColumn; -} - -export interface FunctionDescription { - name: string; - line: number; - loc: SingleFileSourceRange; - skip?: boolean; -} - -export type StatementDescription = SingleFileSourceRange; - -export interface BranchDescription { - line: number; - type: 'if' | 'switch' | 'cond-expr' | 'binary-expr'; - locations: SingleFileSourceRange[]; -} - -export interface FnMap { - [functionId: string]: FunctionDescription; -} - -export interface BranchMap { - [branchId: string]: BranchDescription; -} - -export interface StatementMap { - [statementId: string]: StatementDescription; -} - -export interface LineCoverage { - [lineNo: number]: number; -} - -export interface FunctionCoverage { - [functionId: string]: number; -} - -export interface StatementCoverage { - [statementId: string]: number; -} - -export interface BranchCoverage { - [branchId: string]: number[]; -} - -export interface Coverage { - [fineName: string]: { - l?: LineCoverage; - f: FunctionCoverage; - s: StatementCoverage; - b: BranchCoverage; - fnMap: FnMap; - branchMap: BranchMap; - statementMap: StatementMap; - path: string; - }; -} - -export interface ContractData { - bytecode: string; - sourceMap: string; - runtimeBytecode: string; - sourceMapRuntime: string; - sourceCodes: string[]; - sources: string[]; -} - -// Part of the trace executed within the same context -export type Subtrace = StructLog[]; - -export interface TraceInfoBase { - subtrace: Subtrace; - txHash: string; -} - -export interface TraceInfoNewContract extends TraceInfoBase { - address: 'NEW_CONTRACT'; - bytecode: string; -} - -export interface TraceInfoExistingContract extends TraceInfoBase { - address: string; - runtimeBytecode: string; -} - -export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract; - -export enum BlockParamLiteral { - Latest = 'latest', -} - -export interface EvmCallStackEntry { - structLog: StructLog; - address: string; -} - -export type EvmCallStack = EvmCallStackEntry[]; - -export interface SourceSnippet { - source: string; - fileName: string; - type: string; - node: Parser.ASTNode; - name: string | null; - range: SingleFileSourceRange; -} diff --git a/packages/sol-cov/src/utils.ts b/packages/sol-cov/src/utils.ts deleted file mode 100644 index d8bc65e73..000000000 --- a/packages/sol-cov/src/utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { addressUtils, BigNumber } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import { addHexPrefix } from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { ContractData, LineColumn, SingleFileSourceRange } from './types'; - -// This is the minimum length of valid contract bytecode. The Solidity compiler -// metadata is 86 bytes. If you add the '0x' prefix, we get 88. -const MIN_CONTRACT_BYTECODE_LENGTH = 88; - -export const utils = { - compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { - return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; - }, - removeHexPrefix(hex: string): string { - const hexPrefix = '0x'; - return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; - }, - isRangeInside(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____________________________ - .replace(/_.*_/, '.*') - // Last 86 characters is solidity compiler metadata that's different between compilations - .replace(/.{86}$/, '') - // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance - .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); - // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. - const MAX_REGEX_LENGTH = 32767; - const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); - return truncatedBytecodeRegex; - }, - getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { - if (!bytecode.startsWith('0x')) { - throw new Error(`0x hex prefix missing: ${bytecode}`); - } - const contractData = _.find(contractsData, contractDataCandidate => { - const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); - // If the bytecode is less than the minimum length, we are probably - // dealing with an interface. This isn't what we're looking for. - if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); - if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so - // collisions are practically impossible and it allows us to reuse that code - return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); - }); - return contractData; - }, - isCallLike(op: OpCode): boolean { - return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); - }, - isEndOpcode(op: OpCode): boolean { - return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); - }, - getAddressFromStackEntry(stackEntry: string): string { - const hexBase = 16; - return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); - }, - normalizeStructLogs(structLogs: StructLog[]): StructLog[] { - if (structLogs[0].depth === 1) { - // Geth uses 1-indexed depth counter whilst ganache starts from 0 - const newStructLogs = _.map(structLogs, structLog => ({ - ...structLog, - depth: structLog.depth - 1, - })); - return newStructLogs; - } - return structLogs; - }, - getRange(sourceCode: string, range: SingleFileSourceRange): string { - const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); - lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); - lines[0] = lines[0].slice(range.start.column); - return lines.join('\n'); - }, -}; diff --git a/packages/sol-cov/test/collect_coverage_entries_test.ts b/packages/sol-cov/test/collect_coverage_entries_test.ts deleted file mode 100644 index 7832ec316..000000000 --- a/packages/sol-cov/test/collect_coverage_entries_test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { collectCoverageEntries } from '../src/collect_coverage_entries'; -import { utils } from '../src/utils'; - -const expect = chai.expect; - -describe('Collect coverage entries', () => { - describe('#collectCoverageEntries', () => { - it('correctly collects coverage entries for Simplest contract', () => { - const simplestContractBaseName = 'Simplest.sol'; - const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); - const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simplestContract); - expect(coverageEntries.fnMap).to.be.deep.equal({}); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - expect(coverageEntries.statementMap).to.be.deep.equal({}); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for SimpleStorage contract', () => { - const simpleStorageContractBaseName = 'SimpleStorage.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnIds = _.keys(coverageEntries.fnMap); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); - const setFunction = `function set(uint x) { - storedData = x; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); - const getFunction = `function get() constant returns (uint retVal) { - return storedData; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( - 'storedData = x', - ); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( - 'return storedData;', - ); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for AllSolidityFeatures contract', () => { - const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnDescriptions = _.values(coverageEntries.fnMap); - const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); - const expectedFnNames = [ - 'f', - 'c', - 'test', - 'getChoice', - 'Base', - 'Derived', - 'f', - 'f', - '', - 'g', - 'setData', - 'getData', - 'sendHalf', - 'insert', - 'remove', - 'contains', - 'iterate_start', - 'iterate_valid', - 'iterate_advance', - 'iterate_get', - 'insert', - 'sum', - 'restricted', - 'DualIndex', - 'set', - 'transfer_ownership', - 'lookup', - '', - '', - 'sum', - 'someFunction', - 'fun', - 'at', - 'test', - 'get', - 'returnNumber', - 'alloc', - 'ham', - 'getMyTuple', - 'ham', - 'abstain', - 'foobar', - 'foobar', - 'a', - ]; - expect(fnNames).to.be.deep.equal(expectedFnNames); - - const branchDescriptions = _.values(coverageEntries.branchMap); - const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); - // tslint:disable-next-line:custom-no-magic-numbers - expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); - const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); - expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); - }); - - it('correctly ignores all coverage entries for Ignore contract', () => { - const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; - const solcovIgnoreContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - solcovIgnoreContractBaseName, - ); - const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); - const coverageEntries = collectCoverageEntries(solcovIgnoreContract); - const fnIds = _.keys(coverageEntries.fnMap); - - expect(fnIds.length).to.be.equal(1); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); - const setFunction = `function set(uint x) public { - /* solcov ignore next */ - storedData = x; - }`; - expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( - setFunction, - ); - expect(statementIds.length).to.be.equal(1); - expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); - }); - }); -}); diff --git a/packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol deleted file mode 100644 index 21137347e..000000000 --- a/packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol +++ /dev/null @@ -1,413 +0,0 @@ -// Examples taken from the Solidity documentation online. - -// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions -pragma solidity 0.4.0; -pragma solidity ^0.4.0; - -import "SomeFile.sol"; -import "SomeFile.sol" as SomeOtherFile; -import * as SomeSymbol from "AnotherFile.sol"; -import {symbol1 as alias, symbol2} from "File.sol"; - -interface i { - function f(); -} - -contract c { - function c() - { - val1 = 1 wei; // 1 - val2 = 1 szabo; // 1 * 10 ** 12 - val3 = 1 finney; // 1 * 10 ** 15 - val4 = 1 ether; // 1 * 10 ** 18 - } - uint256 val1; - uint256 val2; - uint256 val3; - uint256 val4; -} - -contract test { - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } - - function test() - { - choices = ActionChoices.GoStraight; - } - function getChoice() returns (uint d) - { - d = uint256(choices); - } - ActionChoices choices; -} - -contract Base { - function Base(uint i) - { - m_i = i; - } - uint public m_i; -} -contract Derived is Base(0) { - function Derived(uint i) Base(i) {} -} - -contract C { - uint248 x; // 31 bytes: slot 0, offset 0 - uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) - uint240 z; // 30 bytes: slot 1, offset 2 bytes - uint8 a; // 1 byte: slot 2, offset 0 bytes - struct S { - uint8 a; // 1 byte, slot +0, offset 0 bytes - uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) - } - S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) - uint8 alpha; // 1 byte, slot 4 (start new slot after struct) - uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) - uint8 gamma; // 1 byte, slot 7 (start new slot after array) -} - -contract test { - function f(uint x, uint y) returns (uint z) { - var c = x + 3; - var b = 7 + (c * (8 - 7)) - x; - return -(-b | 0); - } -} - -contract test { - function f(uint x, uint y) returns (uint z) { - return 10; - } -} - -contract c { - function () returns (uint) { return g(8); } - function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } - function setData(uint pos, uint value) internal { data[pos] = value; } - function getData(uint pos) internal { return data[pos]; } - mapping(uint => uint) data; -} - -contract Sharer { - function sendHalf(address addr) returns (uint balance) { - if (!addr.send(msg.value/2)) - throw; // also reverts the transfer to Sharer - return address(this).balance; - } -} - -/// @dev Models a modifiable and iterable set of uint values. -library IntegerSet -{ - struct data - { - /// Mapping item => index (or zero if not present) - mapping(uint => uint) index; - /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. - uint[] items; - /// Number of stored items. - uint size; - } - function insert(data storage self, uint value) returns (bool alreadyPresent) - { - uint index = self.index[value]; - if (index > 0) - return true; - else - { - if (self.items.length == 0) self.items.length = 1; - index = self.items.length++; - self.items[index] = value; - self.index[value] = index; - self.size++; - return false; - } - } - function remove(data storage self, uint value) returns (bool success) - { - uint index = self.index[value]; - if (index == 0) - return false; - delete self.index[value]; - delete self.items[index]; - self.size --; - } - function contains(data storage self, uint value) returns (bool) - { - return self.index[value] > 0; - } - function iterate_start(data storage self) returns (uint index) - { - return iterate_advance(self, 0); - } - function iterate_valid(data storage self, uint index) returns (bool) - { - return index < self.items.length; - } - function iterate_advance(data storage self, uint index) returns (uint r_index) - { - index++; - while (iterate_valid(self, index) && self.index[self.items[index]] == index) - index++; - return index; - } - function iterate_get(data storage self, uint index) returns (uint value) - { - return self.items[index]; - } -} - -/// How to use it: -contract User -{ - /// Just a struct holding our data. - IntegerSet.data data; - /// Insert something - function insert(uint v) returns (uint size) - { - /// Sends `data` via reference, so IntegerSet can modify it. - IntegerSet.insert(data, v); - /// We can access members of the struct - but we should take care not to mess with them. - return data.size; - } - /// Computes the sum of all stored data. - function sum() returns (uint s) - { - for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) - s += IntegerSet.iterate_get(data, i); - } -} - -// This broke it at one point (namely the modifiers). -contract DualIndex { - mapping(uint => mapping(uint => uint)) data; - address public admin; - - modifier restricted { if (msg.sender == admin) _; } - - function DualIndex() { - admin = msg.sender; - } - - function set(uint key1, uint key2, uint value) restricted { - uint[2][4] memory defaults; // "memory" broke things at one time. - data[key1][key2] = value; - } - - function transfer_ownership(address _admin) restricted { - admin = _admin; - } - - function lookup(uint key1, uint key2) returns(uint) { - return data[key1][key2]; - } -} - -contract A { - -} - -contract B { - -} - -contract C is A, B { - -} - -contract TestPrivate -{ - uint private value; -} - -contract TestInternal -{ - uint internal value; -} - -contract FromSolparse is A, B, TestPrivate, TestInternal { - function() { - uint a = 6 ** 9; - var (x) = 100; - uint y = 2 days; - } -} - -contract CommentedOutFunction { - // FYI: This empty function, as well as the commented - // out function below (bad code) is important to this test. - function() { - - } - - // function something() - // uint x = 10; - // } -} - -library VarHasBrackets { - string constant specialRight = "}"; - //string storage specialLeft = "{"; -} - -library UsingExampleLibrary { - function sum(uint[] storage self) returns (uint s) { - for (uint i = 0; i < self.length; i++) - s += self[i]; - } -} - -contract UsingExampleContract { - using UsingExampleLibrary for uint[]; -} - -contract NewStuff { - uint[] b; - - function someFunction() payable { - string storage a = hex"ab1248fe"; - b[2+2]; - } -} - -// modifier with expression -contract MyContract { - function fun() mymodifier(foo.bar()) {} -} - -library GetCode { - function at(address _addr) returns (bytes o_code) { - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(_addr) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(_addr, add(o_code, 0x20), 0, size) - } - } -} - -contract assemblyLocalBinding { - function test(){ - assembly { - let v := 1 - let x := 0x00 - let y := x - let z := "hello" - } - } -} - -contract assemblyReturn { - uint a = 10; - - function get() constant returns(uint) { - assembly { - mstore(0x40, sload(0)) - byte(0) - address(0) - return(0x40,32) - } - } -} - -contract usesConst { - uint const = 0; -} - -contract memoryArrays { - uint seven = 7; - - function returnNumber(uint number) returns (uint){ - return number; - } - - function alloc() { - uint[] memory a = new uint[](7); - uint[] memory b = new uint[](returnNumber(seven)); - } -} - -contract DeclarativeExpressions { - uint a; - uint b = 7; - uint b2=0; - uint public c; - uint constant public d; - uint public constant e; - uint private constant f = 7; - struct S { uint q;} - - function ham(S storage s1, uint[] storage arr) internal { - uint x; - uint y = 7; - S storage s2 = s1; - uint[] memory stor; - uint[] storage stor2 = arr; - } -} - -contract VariableDeclarationTuple { - function getMyTuple() returns (bool, bool){ - return (true, false); - } - - function ham (){ - var (x, y) = (10, 20); - var (a, b) = getMyTuple(); - var (,c) = (10, 20); - var (d,,) = (10, 20, 30); - var (,e,,f,) = (10, 20, 30, 40, 50); - - var ( - num1, num2, - num3, ,num5 - ) = (10, 20, 30, 40, 50); - } -} - -contract TypeIndexSpacing { - uint [ 7 ] x; - uint [] y; -} - -contract Ballot { - - struct Voter { - uint weight; - bool voted; - } - - function abstain() returns (bool) { - return false; - } - - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); - - Voter you = Voter(1, true); - - Voter me = Voter({ - weight: 2, - voted: abstain() - }); - - Voter airbnb = Voter({ - weight: 2, - voted: true, - }); -} - -contract multilineReturn { - function a() returns (uint x) { - return - 5; - } -} diff --git a/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol deleted file mode 100644 index e4b4ac246..000000000 --- a/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity ^0.4.21; - -contract SimpleStorage { - uint public storedData; - function set(uint x) { - storedData = x; - } - function get() constant returns (uint retVal) { - return storedData; - } -} diff --git a/packages/sol-cov/test/fixtures/contracts/Simplest.sol b/packages/sol-cov/test/fixtures/contracts/Simplest.sol deleted file mode 100644 index d71016e07..000000000 --- a/packages/sol-cov/test/fixtures/contracts/Simplest.sol +++ /dev/null @@ -1,2 +0,0 @@ -contract Simplest { -} diff --git a/packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol deleted file mode 100644 index a7977ffb4..000000000 --- a/packages/sol-cov/test/fixtures/contracts/SolcovIgnore.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.4.21; - -contract SolcovIgnore { - uint public storedData; - - function set(uint x) public { - /* solcov ignore next */ - storedData = x; - } - - /* solcov ignore next */ - function get() constant public returns (uint retVal) { - return storedData; - } -} - -/* solcov ignore next */ -contract Ignore { - function ignored() public returns (bool) { - return false; - } -} diff --git a/packages/sol-cov/test/instructions_test.ts b/packages/sol-cov/test/instructions_test.ts deleted file mode 100644 index 058053cf9..000000000 --- a/packages/sol-cov/test/instructions_test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as chai from 'chai'; -import 'mocha'; - -import { constants } from '../src/constants'; -import { getPcToInstructionIndexMapping } from '../src/instructions'; - -const expect = chai.expect; - -describe('instructions', () => { - describe('#getPcToInstructionIndexMapping', () => { - it('correctly maps pcs to instruction indexed', () => { - // tslint:disable-next-line:custom-no-magic-numbers - const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); - const pcToInstruction = getPcToInstructionIndexMapping(bytecode); - const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; - expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); - }); - }); -}); diff --git a/packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts deleted file mode 100644 index 9c58d2cef..000000000 --- a/packages/sol-cov/test/sol_compiler_artifact_adapter_test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; - -const expect = chai.expect; - -describe('SolCompilerArtifactAdapter', () => { - describe('#collectContractsData', () => { - it('correctly collects contracts data', async () => { - const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); - const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); - const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); - const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); - _.forEach(contractsData, contractData => { - expect(contractData).to.have.keys([ - 'sourceCodes', - 'sources', - 'sourceMap', - 'sourceMapRuntime', - 'bytecode', - 'runtimeBytecode', - ]); - }); - }); - }); -}); diff --git a/packages/sol-cov/test/source_maps_test.ts b/packages/sol-cov/test/source_maps_test.ts deleted file mode 100644 index 5820bedd7..000000000 --- a/packages/sol-cov/test/source_maps_test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; - -const expect = chai.expect; - -const simplestContractBaseName = 'Simplest.sol'; -const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); -const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - -describe('source maps', () => { - describe('#getLocationByOffset', () => { - it('correctly computes location by offset', () => { - const locationByOffset = getLocationByOffset(simplestContract); - const expectedLocationByOffset = { - '0': { line: 1, column: 0 }, - '1': { line: 1, column: 1 }, - '2': { line: 1, column: 2 }, - '3': { line: 1, column: 3 }, - '4': { line: 1, column: 4 }, - '5': { line: 1, column: 5 }, - '6': { line: 1, column: 6 }, - '7': { line: 1, column: 7 }, - '8': { line: 1, column: 8 }, - '9': { line: 1, column: 9 }, - '10': { line: 1, column: 10 }, - '11': { line: 1, column: 11 }, - '12': { line: 1, column: 12 }, - '13': { line: 1, column: 13 }, - '14': { line: 1, column: 14 }, - '15': { line: 1, column: 15 }, - '16': { line: 1, column: 16 }, - '17': { line: 1, column: 17 }, - '18': { line: 1, column: 18 }, - '19': { line: 1, column: 19 }, - '20': { line: 2, column: 0 }, - '21': { line: 2, column: 1 }, - '22': { line: 3, column: 0 }, - }; - expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); - }); - }); - describe('#parseSourceMap', () => { - it('correctly parses the source map', () => { - // This is the source map and bytecode for an empty contract like Example.sol - const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; - const bytecodeHex = - '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; - const sources = [simplestContractBaseName]; - const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); - const expectedSourceRange = { - location: { - start: { line: 1, column: 0 }, - end: { line: 2, column: 1 }, - }, - fileName: simplestContractBaseName, - }; - _.forEach(pcToSourceRange, sourceRange => { - // Solidity source maps are too short and we map some instructions to undefined - // Source: https://github.com/ethereum/solidity/issues/3741 - if (!_.isUndefined(sourceRange)) { - expect(sourceRange).to.be.deep.equal(expectedSourceRange); - } - }); - }); - }); -}); diff --git a/packages/sol-cov/test/trace_test.ts b/packages/sol-cov/test/trace_test.ts deleted file mode 100644 index 7a034362c..000000000 --- a/packages/sol-cov/test/trace_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as chai from 'chai'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; -import 'mocha'; - -import { getTracesByContractAddress } from '../src/trace'; - -const expect = chai.expect; - -const DEFAULT_STRUCT_LOG: StructLog = { - depth: 0, - error: '', - gas: 0, - gasCost: 0, - memory: [], - op: OpCode.Invalid, - pc: 0, - stack: [], - storage: {}, -}; - -function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { - return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; -} - -describe('Trace', () => { - describe('#getTracesByContractAddress', () => { - it('correctly splits trace by contract address', () => { - const delegateCallAddress = '0x0000000000000000000000000000000000000002'; - const trace = [ - { - op: OpCode.DelegateCall, - stack: [delegateCallAddress, '0x'], - depth: 0, - }, - { - op: OpCode.Return, - depth: 1, - }, - { - op: OpCode.Return, - depth: 0, - }, - ]; - const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); - const startAddress = '0x0000000000000000000000000000000000000001'; - const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); - const expectedTraceByContractAddress = { - [startAddress]: [fullTrace[0], fullTrace[2]], - [delegateCallAddress]: [fullTrace[1]], - }; - expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); - }); - }); -}); diff --git a/packages/sol-cov/test/utils_test.ts b/packages/sol-cov/test/utils_test.ts deleted file mode 100644 index 6fc8fcfe1..000000000 --- a/packages/sol-cov/test/utils_test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as chai from 'chai'; -import * as dirtyChai from 'dirty-chai'; -import 'mocha'; - -import { utils } from '../src/utils'; - -chai.use(dirtyChai); -const expect = chai.expect; - -describe('utils', () => { - describe('#compareLineColumn', () => { - it('correctly compares LineColumns', () => { - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); - expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); - }); - }); - - describe('#isRangeInside', () => { - it('returns true if inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, - ), - ).to.be.true(); - }); - it('returns true if the same', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - ), - ).to.be.true(); - }); - it('returns false if not inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, - ), - ).to.be.false(); - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, - ), - ).to.be.false(); - }); - }); -}); diff --git a/packages/sol-cov/tsconfig.json b/packages/sol-cov/tsconfig.json deleted file mode 100644 index 2ee711adc..000000000 --- a/packages/sol-cov/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "rootDir": "." - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/sol-cov/tslint.json b/packages/sol-cov/tslint.json deleted file mode 100644 index 631f46bca..000000000 --- a/packages/sol-cov/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": ["@0x/tslint-config"], - "rules": { - "completed-docs": false - } -} diff --git a/packages/sol-cov/typedoc-tsconfig.json b/packages/sol-cov/typedoc-tsconfig.json deleted file mode 100644 index c9b0af1ae..000000000 --- a/packages/sol-cov/typedoc-tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../typedoc-tsconfig", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/sol-coverage/.npmignore b/packages/sol-coverage/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-coverage/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json new file mode 100644 index 000000000..ca6048d99 --- /dev/null +++ b/packages/sol-coverage/CHANGELOG.json @@ -0,0 +1,12 @@ +[ + { + "version": "1.0.0", + "changes": [ + { + "note": + "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "pr": 1492 + } + ] + } +] diff --git a/packages/sol-coverage/README.md b/packages/sol-coverage/README.md new file mode 100644 index 000000000..f8cc62eb8 --- /dev/null +++ b/packages/sol-coverage/README.md @@ -0,0 +1,75 @@ +## @0x/sol-coverage + +A Solidity code coverage tool. + +### Read the [Documentation](https://0xproject.com/docs/sol-coverage). + +## Installation + +```bash +yarn add @0x/sol-coverage +``` + +**Import** + +```javascript +import { CoverageSubprovider } from '@0x/sol-coverage'; +``` + +or + +```javascript +var CoverageSubprovider = require('@0x/sol-coverage').CoverageSubprovider; +``` + +## 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-coverage yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-coverage yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-coverage/package.json b/packages/sol-coverage/package.json new file mode 100644 index 000000000..b95fa5502 --- /dev/null +++ b/packages/sol-coverage/package.json @@ -0,0 +1,52 @@ +{ + "name": "@0x/sol-coverage", + "version": "1.0.0", + "engines": { + "node": ">=6.12" + }, + "description": "Generate coverage reports for Solidity code", + "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-coverage/README.md", + "dependencies": { + "@0x/subproviders": "^2.1.8", + "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/typescript-typings": "^3.0.6", + "ethereum-types": "^1.1.4", + "lodash": "^4.17.5" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/node": "*", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts new file mode 100644 index 000000000..255fa9481 --- /dev/null +++ b/packages/sol-coverage/src/coverage_subprovider.ts @@ -0,0 +1,146 @@ +import { + AbstractArtifactAdapter, + BranchCoverage, + collectCoverageEntries, + ContractData, + Coverage, + FunctionCoverage, + FunctionDescription, + SingleFileSubtraceHandler, + SourceRange, + StatementCoverage, + StatementDescription, + Subtrace, + TraceCollector, + TraceInfo, + TraceInfoSubprovider, + utils, +} from '@0x/sol-trace-based-tools-common'; +import * as _ from 'lodash'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It's used to compute your code coverage while running solidity tests. + */ +export class CoverageSubprovider extends TraceInfoSubprovider { + private readonly _coverageCollector: TraceCollector; + /** + * Instantiates a CoverageSubprovider 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._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler); + } + protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { + await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo); + } + /** + * Write the test coverage results to a file in Istanbul format. + */ + public async writeCoverageAsync(): Promise { + await this._coverageCollector.writeOutputAsync(); + } +} + +/** + * Computed partial coverage for a single file & subtrace. + * @param contractData Contract metadata (source, srcMap, bytecode) + * @param subtrace A subset of a transcation/call trace that was executed within that contract + * @param pcToSourceRange A mapping from program counters to source ranges + * @param fileIndex Index of a file to compute coverage for + * @return Partial istanbul coverage for that file & subtrace + */ +export const coverageHandler: SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +): Coverage => { + const absoluteFileName = contractData.sources[fileIndex]; + const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); + + // if the source wasn't provided for the fileIndex, we can't cover the file + if (_.isUndefined(coverageEntriesDescription)) { + return {}; + } + + 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. + 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 = {}; + const branchIds = _.keys(coverageEntriesDescription.branchMap); + for (const branchId of branchIds) { + const branchDescription = coverageEntriesDescription.branchMap[branchId]; + const isBranchCoveredByBranchIndex = _.map(branchDescription.locations, location => { + const isBranchCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, location)); + const timesBranchCovered = Number(isBranchCovered); + return timesBranchCovered; + }); + branchCoverage[branchId] = isBranchCoveredByBranchIndex; + } + const statementCoverage: StatementCoverage = {}; + const statementIds = _.keys(coverageEntriesDescription.statementMap); + for (const statementId of statementIds) { + const statementDescription = coverageEntriesDescription.statementMap[statementId]; + const isStatementCovered = _.some(sourceRanges, range => + utils.isRangeInside(range.location, statementDescription), + ); + const timesStatementCovered = Number(isStatementCovered); + statementCoverage[statementId] = timesStatementCovered; + } + const functionCoverage: FunctionCoverage = {}; + const functionIds = _.keys(coverageEntriesDescription.fnMap); + for (const fnId of functionIds) { + const functionDescription = coverageEntriesDescription.fnMap[fnId]; + const isFunctionCovered = _.some(sourceRanges, range => + utils.isRangeInside(range.location, functionDescription.loc), + ); + const timesFunctionCovered = Number(isFunctionCovered); + functionCoverage[fnId] = timesFunctionCovered; + } + // HACK: Solidity doesn't emit any opcodes that map back to modifiers with no args, that's why we map back to the + // function range and check if there is any covered statement within that range. + for (const modifierStatementId of coverageEntriesDescription.modifiersStatementIds) { + if (statementCoverage[modifierStatementId]) { + // Already detected as covered + continue; + } + const modifierDescription = coverageEntriesDescription.statementMap[modifierStatementId]; + const enclosingFunction = _.find(coverageEntriesDescription.fnMap, functionDescription => + utils.isRangeInside(modifierDescription, functionDescription.loc), + ) as FunctionDescription; + const isModifierCovered = _.some( + coverageEntriesDescription.statementMap, + (statementDescription: StatementDescription, statementId: number) => { + const isInsideTheModifierEnclosingFunction = utils.isRangeInside( + statementDescription, + enclosingFunction.loc, + ); + const isCovered = statementCoverage[statementId]; + return isInsideTheModifierEnclosingFunction && isCovered; + }, + ); + const timesModifierCovered = Number(isModifierCovered); + statementCoverage[modifierStatementId] = timesModifierCovered; + } + const partialCoverage = { + [absoluteFileName]: { + ...coverageEntriesDescription, + path: absoluteFileName, + f: functionCoverage, + s: statementCoverage, + b: branchCoverage, + }, + }; + return partialCoverage; +}; diff --git a/packages/sol-coverage/src/globals.d.ts b/packages/sol-coverage/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-coverage/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-coverage/src/index.ts b/packages/sol-coverage/src/index.ts new file mode 100644 index 000000000..dcf97b72e --- /dev/null +++ b/packages/sol-coverage/src/index.ts @@ -0,0 +1,23 @@ +export { CoverageSubprovider } from './coverage_subprovider'; +export { + SolCompilerArtifactAdapter, + TruffleArtifactAdapter, + AbstractArtifactAdapter, + ContractData, +} from '@0x/sol-trace-based-tools-common'; + +export { + JSONRPCRequestPayload, + Provider, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + +export { + JSONRPCRequestPayloadWithMethod, + NextCallback, + ErrorCallback, + OnNextCompleted, + Callback, +} from '@0x/subproviders'; diff --git a/packages/sol-coverage/tsconfig.json b/packages/sol-coverage/tsconfig.json new file mode 100644 index 000000000..233008d61 --- /dev/null +++ b/packages/sol-coverage/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-coverage/tslint.json b/packages/sol-coverage/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-coverage/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/packages/sol-coverage/typedoc-tsconfig.json b/packages/sol-coverage/typedoc-tsconfig.json new file mode 100644 index 000000000..c9b0af1ae --- /dev/null +++ b/packages/sol-coverage/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-profiler/.npmignore b/packages/sol-profiler/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-profiler/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json new file mode 100644 index 000000000..ca6048d99 --- /dev/null +++ b/packages/sol-profiler/CHANGELOG.json @@ -0,0 +1,12 @@ +[ + { + "version": "1.0.0", + "changes": [ + { + "note": + "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "pr": 1492 + } + ] + } +] diff --git a/packages/sol-profiler/README.md b/packages/sol-profiler/README.md new file mode 100644 index 000000000..44fa29e3f --- /dev/null +++ b/packages/sol-profiler/README.md @@ -0,0 +1,75 @@ +## @0x/sol-profiler + +Solidity line-by-line gas profiler. + +### Read the [Documentation](https://0xproject.com/docs/sol-profiler). + +## Installation + +```bash +yarn add @0x/sol-profiler +``` + +**Import** + +```javascript +import { ProfilerSubprovider } from '@0x/sol-profiler'; +``` + +or + +```javascript +var ProfilerSubprovider = require('@0x/sol-profiler').ProfilerSubprovider; +``` + +## 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-profiler yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-profiler yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-profiler/package.json b/packages/sol-profiler/package.json new file mode 100644 index 000000000..03b421b0e --- /dev/null +++ b/packages/sol-profiler/package.json @@ -0,0 +1,50 @@ +{ + "name": "@0x/sol-profiler", + "version": "1.0.0", + "engines": { + "node": ">=6.12" + }, + "description": "Generate profiler reports for Solidity code", + "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-profiler/README.md", + "dependencies": { + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/sol-trace-based-tools-common": "^2.1.16", + "ethereum-types": "^1.1.4", + "lodash": "^4.17.5" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/node": "*", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-profiler/src/globals.d.ts b/packages/sol-profiler/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-profiler/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-profiler/src/index.ts b/packages/sol-profiler/src/index.ts new file mode 100644 index 000000000..b36ed5baa --- /dev/null +++ b/packages/sol-profiler/src/index.ts @@ -0,0 +1,25 @@ +export { + AbstractArtifactAdapter, + SolCompilerArtifactAdapter, + TruffleArtifactAdapter, + ContractData, +} from '@0x/sol-trace-based-tools-common'; + +// HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready +export { ProfilerSubprovider } from './profiler_subprovider'; + +export { + JSONRPCRequestPayload, + Provider, + JSONRPCErrorCallback, + JSONRPCResponsePayload, + JSONRPCResponseError, +} from 'ethereum-types'; + +export { + JSONRPCRequestPayloadWithMethod, + NextCallback, + ErrorCallback, + OnNextCompleted, + Callback, +} from '@0x/subproviders'; diff --git a/packages/sol-profiler/src/profiler_subprovider.ts b/packages/sol-profiler/src/profiler_subprovider.ts new file mode 100644 index 000000000..61dc25b6b --- /dev/null +++ b/packages/sol-profiler/src/profiler_subprovider.ts @@ -0,0 +1,98 @@ +import * as _ from 'lodash'; + +import { + AbstractArtifactAdapter, + collectCoverageEntries, + ContractData, + Coverage, + SingleFileSubtraceHandler, + SourceRange, + Subtrace, + TraceCollector, + TraceInfo, + TraceInfoSubprovider, + utils, +} from '@0x/sol-trace-based-tools-common'; + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * ProfilerSubprovider is used to profile Solidity code while running tests. + */ +export class ProfilerSubprovider extends TraceInfoSubprovider { + private readonly _profilerCollector: TraceCollector; + /** + * Instantiates a ProfilerSubprovider 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: false, + shouldCollectCallTraces: false, + }; + super(defaultFromAddress, traceCollectionSubproviderConfig); + this._profilerCollector = new TraceCollector(artifactAdapter, isVerbose, profilerHandler); + } + protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise { + await this._profilerCollector.computeSingleTraceCoverageAsync(traceInfo); + } + /** + * Write the test profiler results to a file in Istanbul format. + */ + public async writeProfilerOutputAsync(): Promise { + await this._profilerCollector.writeOutputAsync(); + } +} + +/** + * Computed partial coverage for a single file & subtrace for the purposes of + * gas profiling. + * @param contractData Contract metadata (source, srcMap, bytecode) + * @param subtrace A subset of a transcation/call trace that was executed within that contract + * @param pcToSourceRange A mapping from program counters to source ranges + * @param fileIndex Index of a file to compute coverage for + * @return Partial istanbul coverage for that file & subtrace + */ +export const profilerHandler: SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +): Coverage => { + const absoluteFileName = contractData.sources[fileIndex]; + const profilerEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]); + const gasConsumedByStatement: { [statementId: string]: number } = {}; + const statementIds = _.keys(profilerEntriesDescription.statementMap); + for (const statementId of statementIds) { + const statementDescription = profilerEntriesDescription.statementMap[statementId]; + const totalGasCost = _.sum( + _.map(subtrace, 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 { + return 0; + } + }), + ); + gasConsumedByStatement[statementId] = totalGasCost; + } + const partialProfilerOutput = { + [absoluteFileName]: { + ...profilerEntriesDescription, + path: absoluteFileName, + f: {}, // I's meaningless in profiling context + s: gasConsumedByStatement, + b: {}, // I's meaningless in profiling context + }, + }; + return partialProfilerOutput; +}; diff --git a/packages/sol-profiler/tsconfig.json b/packages/sol-profiler/tsconfig.json new file mode 100644 index 000000000..233008d61 --- /dev/null +++ b/packages/sol-profiler/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-profiler/tslint.json b/packages/sol-profiler/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-profiler/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/packages/sol-profiler/typedoc-tsconfig.json b/packages/sol-profiler/typedoc-tsconfig.json new file mode 100644 index 000000000..a4c669cb6 --- /dev/null +++ b/packages/sol-profiler/typedoc-tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/**/*"] +} diff --git a/packages/sol-trace-based-tools-common/.npmignore b/packages/sol-trace-based-tools-common/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-trace-based-tools-common/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.json b/packages/sol-trace-based-tools-common/CHANGELOG.json new file mode 100644 index 000000000..4c56d547d --- /dev/null +++ b/packages/sol-trace-based-tools-common/CHANGELOG.json @@ -0,0 +1,398 @@ +[ + { + "version": "3.0.0", + "changes": [ + { + "note": "Move out specific tools and leave just the common part", + "pr": 1492 + } + ] + }, + { + "version": "2.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { + "timestamp": 1543401373, + "version": "2.1.14", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542821676, + "version": "2.1.13", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542208198, + "version": "2.1.12", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542134075, + "version": "2.1.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542028948, + "version": "2.1.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { + "version": "2.1.8", + "changes": [ + { + "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", + "pr": 1105 + } + ], + "timestamp": 1539871071 + }, + { + "version": "2.1.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1538693146 + }, + { + "timestamp": 1538157789, + "version": "2.1.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537907159, + "version": "2.1.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537875740, + "version": "2.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537541580, + "version": "2.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1536142250, + "version": "2.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1535377027, + "version": "2.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.0", + "changes": [ + { + "note": + "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", + "pr": 924 + } + ], + "timestamp": 1535133899 + }, + { + "version": "2.0.0", + "changes": [ + { + "note": + "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", + "pr": 938 + }, + { + "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", + "pr": 938 + }, + { + "note": + "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", + "pr": 938 + }, + { + "note": + "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", + "pr": 938 + } + ], + "timestamp": 1534210131 + }, + { + "version": "1.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1532619515 + }, + { + "timestamp": 1532605697, + "version": "1.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532357734, + "version": "1.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532043000, + "version": "1.0.0", + "changes": [ + { + "note": + "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", + "pr": 589 + }, + { + "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", + "pr": 589 + }, + { + "note": "Properly parse multi-level traces", + "pr": 589 + }, + { + "note": "Add support for solidity libraries", + "pr": 589 + }, + { + "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", + "pr": 675 + }, + { + "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", + "pr": 675 + }, + { + "note": "Fixed a bug when the tool crashed on empty traces", + "pr": 675 + }, + { + "note": "Use `BlockchainLifecycle` to support reverts on Geth", + "pr": 675 + }, + { + "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", + "pr": 675 + }, + { + "note": "Collect traces from `estimate_gas` calls", + "pr": 675 + }, + { + "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", + "pr": 675 + }, + { + "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", + "pr": 675 + }, + { + "note": "Skip interface artifacts with a warning instead of failing", + "pr": 675 + }, + { + "note": "Fix `solcVersion` regex in parameter validation", + "pr": 690 + }, + { + "note": + "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", + "pr": 690 + }, + { + "note": "HUGE perf improvements", + "pr": 690 + }, + { + "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", + "pr": 705 + }, + { + "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", + "pr": 725 + } + ] + }, + { + "timestamp": 1531919263, + "version": "0.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1531149657, + "version": "0.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1529397769, + "version": "0.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "0.1.0", + "changes": [ + { + "note": "Incorrect publish that was unpublished" + } + ], + "timestamp": 1527810075 + }, + { + "timestamp": 1527009134, + "version": "0.0.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525477860, + "version": "0.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525428773, + "version": "0.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1524044013, + "version": "0.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1523462196, + "version": "0.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522673609, + "version": "0.0.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522658513, + "version": "0.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.md b/packages/sol-trace-based-tools-common/CHANGELOG.md new file mode 100644 index 000000000..879ef9c95 --- /dev/null +++ b/packages/sol-trace-based-tools-common/CHANGELOG.md @@ -0,0 +1,158 @@ + + +CHANGELOG + +## v2.1.16 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.15 - _December 11, 2018_ + + * Dependencies updated + +## v2.1.14 - _November 28, 2018_ + + * Dependencies updated + +## v2.1.13 - _November 21, 2018_ + + * Dependencies updated + +## v2.1.12 - _November 14, 2018_ + + * Dependencies updated + +## v2.1.11 - _November 13, 2018_ + + * Dependencies updated + +## v2.1.10 - _November 12, 2018_ + + * Dependencies updated + +## v2.1.9 - _November 9, 2018_ + + * Dependencies updated + +## v2.1.8 - _October 18, 2018_ + + * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) + +## v2.1.7 - _October 4, 2018_ + + * Dependencies updated + +## v2.1.6 - _September 28, 2018_ + + * Dependencies updated + +## v2.1.5 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.4 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.3 - _September 21, 2018_ + + * Dependencies updated + +## v2.1.2 - _September 5, 2018_ + + * Dependencies updated + +## v2.1.1 - _August 27, 2018_ + + * Dependencies updated + +## v2.1.0 - _August 24, 2018_ + + * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) + +## v2.0.0 - _August 14, 2018_ + + * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) + * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) + * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) + * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) + +## v1.0.3 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.2 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.1 - _July 23, 2018_ + + * Dependencies updated + +## v1.0.0 - _July 19, 2018_ + + * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) + * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) + * Properly parse multi-level traces (#589) + * Add support for solidity libraries (#589) + * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) + * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) + * Fixed a bug when the tool crashed on empty traces (#675) + * Use `BlockchainLifecycle` to support reverts on Geth (#675) + * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) + * Collect traces from `estimate_gas` calls (#675) + * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) + * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) + * Skip interface artifacts with a warning instead of failing (#675) + * Fix `solcVersion` regex in parameter validation (#690) + * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) + * HUGE perf improvements (#690) + * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) + * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) + +## v0.1.3 - _July 18, 2018_ + + * Dependencies updated + +## v0.1.2 - _July 9, 2018_ + + * Dependencies updated + +## v0.1.1 - _June 19, 2018_ + + * Dependencies updated + +## v0.1.0 - _May 31, 2018_ + + * Incorrect publish that was unpublished + +## v0.0.11 - _May 22, 2018_ + + * Dependencies updated + +## v0.0.10 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.9 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.8 - _April 18, 2018_ + + * Dependencies updated + +## v0.0.7 - _April 11, 2018_ + + * Dependencies updated + +## v0.0.6 - _April 2, 2018_ + + * Dependencies updated + +## v0.0.5 - _April 2, 2018_ + + * Dependencies updated diff --git a/packages/sol-trace-based-tools-common/README.md b/packages/sol-trace-based-tools-common/README.md new file mode 100644 index 000000000..b6d75dd59 --- /dev/null +++ b/packages/sol-trace-based-tools-common/README.md @@ -0,0 +1,61 @@ +## @0x/sol-trace-based-tools-common + +Common code for all solidity trace-based tools (sol-coverage, sol-profiler, sol-trace). + +## Installation + +```bash +yarn add @0x/sol-sol-trace-based-tools-common +``` + +## 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-based-tools-common yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-trace-based-tools-common yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-trace-based-tools-common/compiler.json b/packages/sol-trace-based-tools-common/compiler.json new file mode 100644 index 000000000..a6a0c6d3a --- /dev/null +++ b/packages/sol-trace-based-tools-common/compiler.json @@ -0,0 +1,18 @@ +{ + "contracts": ["SimpleStorage"], + "contractsDir": "test/fixtures/contracts", + "artifactsDir": "test/fixtures/artifacts", + "compilerSettings": { + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/packages/sol-trace-based-tools-common/coverage/.gitkeep b/packages/sol-trace-based-tools-common/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/sol-trace-based-tools-common/package.json b/packages/sol-trace-based-tools-common/package.json new file mode 100644 index 000000000..6cfabe6d9 --- /dev/null +++ b/packages/sol-trace-based-tools-common/package.json @@ -0,0 +1,87 @@ +{ + "name": "@0x/sol-trace-based-tools-common", + "version": "2.1.16", + "engines": { + "node": ">=6.12" + }, + "description": "Common part of trace based solidity tools (sol-coverage, sol-trace, sol-profiler)", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s copy_test_fixtures", + "lint": "tslint --format stylish --project .", + "test": "run-s compile_test run_mocha", + "rebuild_and_test": "run-s clean build test", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", + "test:circleci": "yarn test:coverage", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", + "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", + "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", + "compile_test": "sol-compiler compile", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [], + "docOmitExports": [ + "ProfilerSubprovider", + "RevertTraceSubprovider" + ] + } + }, + "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-based-tools-common/README.md", + "dependencies": { + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "@types/solidity-parser-antlr": "^0.2.0", + "ethereum-types": "^1.1.4", + "ethereumjs-util": "^5.1.1", + "glob": "^7.1.2", + "istanbul": "^0.4.5", + "lodash": "^4.17.5", + "loglevel": "^1.6.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.2", + "semaphore-async-await": "^1.5.1", + "solidity-parser-antlr": "^0.2.12" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/istanbul": "^0.4.30", + "@types/loglevel": "^1.5.3", + "@types/mkdirp": "^0.5.1", + "@types/mocha": "^2.2.42", + "@types/node": "*", + "@types/rimraf": "^2.0.2", + "chai": "^4.0.1", + "copyfiles": "^2.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts new file mode 100644 index 000000000..fcc6562ad --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts @@ -0,0 +1,5 @@ +import { ContractData } from '../types'; + +export abstract class AbstractArtifactAdapter { + public abstract async collectContractsDataAsync(): Promise; +} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts new file mode 100644 index 000000000..57391abbe --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts @@ -0,0 +1,61 @@ +import { logUtils } from '@0x/utils'; +import { CompilerOptions, ContractArtifact } from 'ethereum-types'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as _ from 'lodash'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; + +const CONFIG_FILE = 'compiler.json'; + +export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { + private readonly _artifactsPath: string; + private readonly _sourcesPath: string; + /** + * Instantiates a SolCompilerArtifactAdapter + * @param artifactsPath Path to your artifacts directory + * @param sourcesPath Path to your contract sources directory + */ + constructor(artifactsPath?: string, sourcesPath?: string) { + super(); + const config: CompilerOptions = fs.existsSync(CONFIG_FILE) + ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) + : {}; + if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { + throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); + } + this._artifactsPath = (artifactsPath || config.artifactsDir) as string; + if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { + throw new Error(`contractsDir not found in ${CONFIG_FILE}`); + } + this._sourcesPath = (sourcesPath || config.contractsDir) as string; + } + public async collectContractsDataAsync(): Promise { + const artifactsGlob = `${this._artifactsPath}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + const contractsData: ContractData[] = []; + for (const artifactFileName of artifactFileNames) { + const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + if (_.isUndefined(artifact.compilerOutput.evm)) { + 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 contractData = { + sourceCodes, + sources, + bytecode: artifact.compilerOutput.evm.bytecode.object, + sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, + runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, + sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, + }; + contractsData.push(contractData); + } + return contractsData; + } +} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts new file mode 100644 index 000000000..bb2b15153 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts @@ -0,0 +1,88 @@ +import { Compiler, CompilerOptions } from '@0x/sol-compiler'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; +import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; + +const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; + +interface TruffleConfig { + solc?: any; + contracts_build_directory?: string; +} + +export class TruffleArtifactAdapter extends AbstractArtifactAdapter { + private readonly _solcVersion: string; + private readonly _projectRoot: string; + /** + * Instantiates a TruffleArtifactAdapter + * @param projectRoot Path to the truffle project's root directory + * @param solcVersion Solidity version with which to compile all the contracts + */ + constructor(projectRoot: string, solcVersion: string) { + super(); + this._solcVersion = solcVersion; + this._projectRoot = projectRoot; + } + public async collectContractsDataAsync(): Promise { + const artifactsDir = '.0x-artifacts'; + const contractsDir = path.join(this._projectRoot, 'contracts'); + const truffleConfig = this._getTruffleConfig(); + const solcConfig = truffleConfig.solc || {}; + const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; + this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); + const compilerOptions: CompilerOptions = { + contractsDir, + artifactsDir, + compilerSettings: { + ...solcConfig, + outputSelection: { + ['*']: { + ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], + }, + }, + }, + contracts: '*', + solcVersion: this._solcVersion, + }; + const compiler = new Compiler(compilerOptions); + await compiler.compileAsync(); + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); + const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); + return contractsDataFrom0xArtifacts; + } + private _getTruffleConfig(): TruffleConfig { + const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); + const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); + if (fs.existsSync(truffleConfigFileShort)) { + const truffleConfig = require(truffleConfigFileShort); + return truffleConfig; + } else if (fs.existsSync(truffleConfigFileLong)) { + const truffleConfig = require(truffleConfigFileLong); + return truffleConfig; + } else { + throw new Error( + `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, + ); + } + } + private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { + const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + for (const artifactFileName of artifactFileNames) { + const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + const compilerVersion = artifact.compiler.version; + if (!compilerVersion.startsWith(this._solcVersion)) { + throw new Error( + `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ + this._solcVersion + } making it impossible to process traces`, + ); + } + } + } +} diff --git a/packages/sol-trace-based-tools-common/src/ast_visitor.ts b/packages/sol-trace-based-tools-common/src/ast_visitor.ts new file mode 100644 index 000000000..e55cdf6ec --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/ast_visitor.ts @@ -0,0 +1,168 @@ +import * as _ from 'lodash'; +import * as Parser from 'solidity-parser-antlr'; + +import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types'; + +export interface CoverageEntriesDescription { + fnMap: FnMap; + branchMap: BranchMap; + statementMap: StatementMap; + modifiersStatementIds: number[]; +} + +enum BranchType { + If = 'if', + ConditionalExpression = 'cond-expr', + BinaryExpression = 'binary-expr', +} + +export class ASTVisitor { + private _entryId = 0; + private readonly _fnMap: FnMap = {}; + private readonly _branchMap: BranchMap = {}; + private readonly _modifiersStatementIds: number[] = []; + private readonly _statementMap: StatementMap = {}; + private readonly _locationByOffset: LocationByOffset; + 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; + this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; + } + public getCollectedCoverageEntries(): CoverageEntriesDescription { + const coverageEntriesDescription = { + fnMap: this._fnMap, + branchMap: this._branchMap, + statementMap: this._statementMap, + modifiersStatementIds: this._modifiersStatementIds, + }; + return coverageEntriesDescription; + } + public IfStatement(ast: Parser.IfStatement): void { + this._visitStatement(ast); + this._visitBinaryBranch(ast, ast.trueBody, ast.falseBody || ast, BranchType.If); + } + public FunctionDefinition(ast: Parser.FunctionDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ContractDefinition(ast: Parser.ContractDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + } + } + public ModifierDefinition(ast: Parser.ModifierDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ForStatement(ast: Parser.ForStatement): void { + this._visitStatement(ast); + } + public ReturnStatement(ast: Parser.ReturnStatement): void { + this._visitStatement(ast); + } + public BreakStatement(ast: Parser.BreakStatement): void { + this._visitStatement(ast); + } + public ContinueStatement(ast: Parser.ContinueStatement): void { + this._visitStatement(ast); + } + public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { + this._visitStatement(ast); + } + public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { + this._visitStatement(ast); + } + public Statement(ast: Parser.Statement): void { + this._visitStatement(ast); + } + public WhileStatement(ast: Parser.WhileStatement): void { + this._visitStatement(ast); + } + public SimpleStatement(ast: Parser.SimpleStatement): void { + this._visitStatement(ast); + } + public ThrowStatement(ast: Parser.ThrowStatement): void { + this._visitStatement(ast); + } + public DoWhileStatement(ast: Parser.DoWhileStatement): void { + this._visitStatement(ast); + } + public ExpressionStatement(ast: Parser.ExpressionStatement): void { + this._visitStatement(ast.expression); + } + public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { + this._visitStatement(ast); + } + public BinaryOperation(ast: Parser.BinaryOperation): void { + const BRANCHING_BIN_OPS = ['&&', '||']; + if (_.includes(BRANCHING_BIN_OPS, ast.operator)) { + this._visitBinaryBranch(ast, ast.left, ast.right, BranchType.BinaryExpression); + } + } + public Conditional(ast: Parser.Conditional): void { + this._visitBinaryBranch(ast, ast.trueExpression, ast.falseExpression, BranchType.ConditionalExpression); + } + public ModifierInvocation(ast: Parser.ModifierInvocation): void { + const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; + if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._modifiersStatementIds.push(this._entryId); + this._visitStatement(ast); + } + } + private _visitBinaryBranch( + ast: Parser.ASTNode, + left: Parser.ASTNode, + right: Parser.ASTNode, + type: BranchType, + ): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._branchMap[this._entryId++] = { + line: this._getExpressionRange(ast).start.line, + type, + locations: [this._getExpressionRange(left), this._getExpressionRange(right)], + }; + } + private _visitStatement(ast: Parser.ASTNode): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._statementMap[this._entryId++] = this._getExpressionRange(ast); + } + 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 range = { + start, + end, + }; + return range; + } + private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { + const [astStart, astEnd] = ast.range as [number, number]; + const isRangeIgnored = _.some( + this._ignoreRangesWithin, + ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, + ); + return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + return; + } + const loc = this._getExpressionRange(ast); + this._fnMap[this._entryId++] = { + name: ast.name, + line: loc.start.line, + loc, + }; + this._visitStatement(ast); + } +} diff --git a/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts b/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts new file mode 100644 index 000000000..bdbcd613e --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts @@ -0,0 +1,41 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as parser from 'solidity-parser-antlr'; + +import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; +import { getLocationByOffset } 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 } = {}; + +export const collectCoverageEntries = (contractSource: string) => { + const sourceHash = ethUtil.sha3(contractSource).toString('hex'); + if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { + const ast = parser.parse(contractSource, { range: true }); + const locationByOffset = getLocationByOffset(contractSource); + const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); + const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); + parser.visit(ast, visitor); + coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); + } + const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; + return coverageEntriesDescription; +}; + +// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" +function gatherRangesToIgnore(contractSource: string): number[] { + const ignoreRangesStart = []; + + let match; + do { + match = IGNORE_RE.exec(contractSource); + if (match) { + const matchLen = match[0].length; + ignoreRangesStart.push(match.index + matchLen); + } + } while (match); + + return ignoreRangesStart; +} diff --git a/packages/sol-trace-based-tools-common/src/constants.ts b/packages/sol-trace-based-tools-common/src/constants.ts new file mode 100644 index 000000000..34d62b537 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/constants.ts @@ -0,0 +1,8 @@ +// tslint:disable:number-literal-format +export const constants = { + NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', + PUSH1: 0x60, + PUSH2: 0x61, + PUSH32: 0x7f, + TIMESTAMP: 0x42, +}; diff --git a/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts b/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts new file mode 100644 index 000000000..f578675d3 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts @@ -0,0 +1,185 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as Parser from 'solidity-parser-antlr'; + +import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; +import { utils } from './utils'; + +interface ASTInfo { + type: string; + node: Parser.ASTNode; + name: string | null; + range?: SingleFileSourceRange; +} + +// Parsing source code for each transaction/code is slow and therefore we cache it +const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; + +/** + * Gets the source range snippet by source range to be used by revert trace. + * @param sourceRange source range + * @param sourceCode source code + */ +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 }); + } + const astNode = parsedSourceByHash[sourceHash]; + const visitor = new ASTInfoVisitor(); + Parser.visit(astNode, visitor); + const astInfo = visitor.getASTInfoForRange(sourceRange); + if (astInfo === null) { + return null; + } + const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); + return { + ...astInfo, + range: astInfo.range as SingleFileSourceRange, + source: sourceCodeInRange, + fileName: sourceRange.fileName, + }; +} + +// A visitor which collects ASTInfo for most nodes in the AST. +class ASTInfoVisitor { + private readonly _astInfos: ASTInfo[] = []; + public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { + // HACK(albrow): Sometimes the source range doesn't exactly match that + // of astInfo. To work around that we try with a +/-1 offset on + // end.column. If nothing matches even with the offset, we return null. + const offset = { + start: { + line: 0, + column: 0, + }, + end: { + line: 0, + column: 0, + }, + }; + let astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column += 1; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column -= 2; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + return null; + } + public ContractDefinition(ast: Parser.ContractDefinition): void { + this._visitContractDefinition(ast); + } + public IfStatement(ast: Parser.IfStatement): void { + this._visitStatement(ast); + } + public FunctionDefinition(ast: Parser.FunctionDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ModifierDefinition(ast: Parser.ModifierDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ForStatement(ast: Parser.ForStatement): void { + this._visitStatement(ast); + } + public ReturnStatement(ast: Parser.ReturnStatement): void { + this._visitStatement(ast); + } + public BreakStatement(ast: Parser.BreakStatement): void { + this._visitStatement(ast); + } + public ContinueStatement(ast: Parser.ContinueStatement): void { + this._visitStatement(ast); + } + public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { + this._visitStatement(ast); + } + public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { + this._visitStatement(ast); + } + public Statement(ast: Parser.Statement): void { + this._visitStatement(ast); + } + public WhileStatement(ast: Parser.WhileStatement): void { + this._visitStatement(ast); + } + public SimpleStatement(ast: Parser.SimpleStatement): void { + this._visitStatement(ast); + } + public ThrowStatement(ast: Parser.ThrowStatement): void { + this._visitStatement(ast); + } + public DoWhileStatement(ast: Parser.DoWhileStatement): void { + this._visitStatement(ast); + } + public ExpressionStatement(ast: Parser.ExpressionStatement): void { + this._visitStatement(ast.expression); + } + public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { + this._visitStatement(ast); + } + public ModifierInvocation(ast: Parser.ModifierInvocation): void { + const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; + if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + this._visitStatement(ast); + } + } + private _visitStatement(ast: Parser.ASTNode): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: null, + range: ast.loc, + }); + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _visitContractDefinition(ast: Parser.ContractDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { + const offsetSourceRange = { + ...sourceRange, + location: { + start: { + line: sourceRange.location.start.line + offset.start.line, + column: sourceRange.location.start.column + offset.start.column, + }, + end: { + line: sourceRange.location.end.line + offset.end.line, + column: sourceRange.location.end.column + offset.end.column, + }, + }, + }; + for (const astInfo of this._astInfos) { + const astInfoRange = astInfo.range as SingleFileSourceRange; + if ( + astInfoRange.start.column === offsetSourceRange.location.start.column && + astInfoRange.start.line === offsetSourceRange.location.start.line && + astInfoRange.end.column === offsetSourceRange.location.end.column && + astInfoRange.end.line === offsetSourceRange.location.end.line + ) { + return astInfo; + } + } + return null; + } +} diff --git a/packages/sol-trace-based-tools-common/src/globals.d.ts b/packages/sol-trace-based-tools-common/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-trace-based-tools-common/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-based-tools-common/src/index.ts b/packages/sol-trace-based-tools-common/src/index.ts new file mode 100644 index 000000000..413e5305e --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/index.ts @@ -0,0 +1,39 @@ +export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; +export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; +export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; + +export { + ContractData, + EvmCallStack, + SourceRange, + SourceSnippet, + StatementCoverage, + StatementDescription, + BranchCoverage, + BranchDescription, + Subtrace, + TraceInfo, + Coverage, + LineColumn, + LineCoverage, + FunctionCoverage, + FunctionDescription, + SingleFileSourceRange, + BranchMap, + EvmCallStackEntry, + FnMap, + LocationByOffset, + StatementMap, + TraceInfoBase, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +export { collectCoverageEntries } from './collect_coverage_entries'; +export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector'; +export { TraceInfoSubprovider } from './trace_info_subprovider'; +export { utils } from './utils'; +export { constants } from './constants'; +export { parseSourceMap } from './source_maps'; +export { getSourceRangeSnippet } from './get_source_range_snippet'; +export { getRevertTrace } from './revert_trace'; +export { TraceCollectionSubprovider } from './trace_collection_subprovider'; diff --git a/packages/sol-trace-based-tools-common/src/instructions.ts b/packages/sol-trace-based-tools-common/src/instructions.ts new file mode 100644 index 000000000..40987dbe5 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/instructions.ts @@ -0,0 +1,23 @@ +import { constants } from './constants'; + +const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; + +const pushDataLength = (inst: number) => inst - constants.PUSH1 + 1; + +const instructionLength = (inst: number) => (isPush(inst) ? pushDataLength(inst) + 1 : 1); + +export const getPcToInstructionIndexMapping = (bytecode: Uint8Array) => { + const result: { + [programCounter: number]: number; + } = {}; + let byteIndex = 0; + let instructionIndex = 0; + while (byteIndex < bytecode.length) { + const instruction = bytecode[byteIndex]; + const length = instructionLength(instruction); + result[byteIndex] = instructionIndex; + byteIndex += length; + instructionIndex += 1; + } + return result; +}; diff --git a/packages/sol-trace-based-tools-common/src/revert_trace.ts b/packages/sol-trace-based-tools-common/src/revert_trace.ts new file mode 100644 index 000000000..4d474120c --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/revert_trace.ts @@ -0,0 +1,95 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; + +import * as _ from 'lodash'; + +import { EvmCallStack } from './types'; +import { utils } from './utils'; + +/** + * Converts linear trace to a call stack by following calls and returns + * @param structLogs Linear trace + * @param startAddress The address of initial context + */ +export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { + const evmCallStack: EvmCallStack = []; + const addressStack = [startAddress]; + 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]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + + 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], + ); + + // 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]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + evmCallStack.push({ + address: currentAddress, + structLog, + }); + } + } 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]; + if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { + evmCallStack.pop(); + addressStack.pop(); + } + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Revert) { + evmCallStack.push({ + address: _.last(addressStack) as string, + structLog, + }); + return evmCallStack; + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + logUtils.warn( + "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", + ); + return []; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + addressStack.pop(); + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (evmCallStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); + } + return []; +} diff --git a/packages/sol-trace-based-tools-common/src/source_maps.ts b/packages/sol-trace-based-tools-common/src/source_maps.ts new file mode 100644 index 000000000..7191fb712 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/source_maps.ts @@ -0,0 +1,91 @@ +import * as _ from 'lodash'; + +import { getPcToInstructionIndexMapping } from './instructions'; +import { LocationByOffset, SourceRange } from './types'; + +const RADIX = 10; + +export interface SourceLocation { + offset: number; + length: number; + fileIndex: number; +} + +/** + * Receives a string with newlines and returns a hash 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 } }; + let currentOffset = 0; + for (const char of str.split('')) { + const location = locationByOffset[currentOffset]; + const isNewline = char === '\n'; + locationByOffset[currentOffset + 1] = { + line: location.line + (isNewline ? 1 : 0), + column: isNewline ? 0 : location.column + 1, + }; + currentOffset++; + } + return 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 srcMap source map string + * @param bytecodeHex contract bytecode + * @param sources sources file names + */ +export function parseSourceMap( + sourceCodes: string[], + srcMap: string, + bytecodeHex: string, + sources: 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 entries = srcMap.split(';'); + let lastParsedEntry: SourceLocation = {} as any; + const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; + _.each(entries, (entry: string, i: number) => { + // tslint:disable-next-line:no-unused-variable + const [instructionIndexStrIfExists, lengthStrIfExists, fileIndexStrIfExists, jumpTypeStrIfExists] = entry.split( + ':', + ); + const instructionIndexIfExists = parseInt(instructionIndexStrIfExists, RADIX); + const lengthIfExists = parseInt(lengthStrIfExists, RADIX); + const fileIndexIfExists = parseInt(fileIndexStrIfExists, RADIX); + const offset = _.isNaN(instructionIndexIfExists) ? lastParsedEntry.offset : instructionIndexIfExists; + const length = _.isNaN(lengthIfExists) ? lastParsedEntry.length : lengthIfExists; + const fileIndex = _.isNaN(fileIndexIfExists) ? lastParsedEntry.fileIndex : fileIndexIfExists; + const parsedEntry = { + offset, + length, + fileIndex, + }; + if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { + const sourceRange = { + location: { + start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], + end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], + }, + fileName: sources[parsedEntry.fileIndex], + }; + instructionIndexToSourceRange[i] = sourceRange; + } else { + // Some assembly code generated by Solidity can't be mapped back to a line of source code. + // Source: https://github.com/ethereum/solidity/issues/3629 + } + lastParsedEntry = parsedEntry; + }); + const pcsToSourceRange: { [programCounter: number]: SourceRange } = {}; + for (const programCounterKey of _.keys(pcToInstructionIndex)) { + const pc = parseInt(programCounterKey, RADIX); + const instructionIndex: number = pcToInstructionIndex[pc]; + pcsToSourceRange[pc] = instructionIndexToSourceRange[instructionIndex]; + } + return pcsToSourceRange; +} diff --git a/packages/sol-trace-based-tools-common/src/trace.ts b/packages/sol-trace-based-tools-common/src/trace.ts new file mode 100644 index 000000000..770080af3 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace.ts @@ -0,0 +1,104 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { utils } from './utils'; + +export interface TraceByContractAddress { + [contractAddress: string]: StructLog[]; +} + +/** + * Converts linear stack trace to `TraceByContractAddress`. + * @param structLogs stack trace + * @param startAddress initial context address + */ +export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { + const traceByContractAddress: TraceByContractAddress = {}; + let currentTraceSegment = []; + const addressStack = [startAddress]; + if (_.isEmpty(structLogs)) { + return traceByContractAddress; + } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + currentTraceSegment.push(structLog); + + 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], + ); + + // 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]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } + } else if (utils.isEndOpcode(structLog.op)) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + 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; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (addressStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end'); + } + if (currentTraceSegment.length !== 0) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + logUtils.warn('Malformed trace. Current trace segment non empty at the end'); + } + return traceByContractAddress; +} diff --git a/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts new file mode 100644 index 000000000..25e38768d --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts @@ -0,0 +1,188 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; +import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; +import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; +import { Lock } from 'semaphore-async-await'; + +import { constants } from './constants'; +import { BlockParamLiteral } from './types'; + +interface MaybeFakeTxData extends TxData { + isFakeTransaction?: boolean; +} + +const BLOCK_GAS_LIMIT = 6000000; + +export interface TraceCollectionSubproviderConfig { + shouldCollectTransactionTraces: boolean; + shouldCollectCallTraces: boolean; + shouldCollectGasEstimateTraces: boolean; +} + +// 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. + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must + * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. + */ +export abstract class TraceCollectionSubprovider extends Subprovider { + protected _web3Wrapper!: Web3Wrapper; + // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise + private readonly _lock = new Lock(); + private readonly _defaultFromAddress: string; + private _isEnabled = true; + private readonly _config: TraceCollectionSubproviderConfig; + /** + * Instantiates a TraceCollectionSubprovider instance + * @param defaultFromAddress default from address to use when sending transactions + */ + constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { + super(); + this._defaultFromAddress = defaultFromAddress; + this._config = config; + } + /** + * Starts trace collection + */ + public start(): void { + this._isEnabled = true; + } + /** + * Stops trace collection + */ + public stop(): void { + this._isEnabled = false; + } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param _end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method async-suffix + public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { + if (this._isEnabled) { + switch (payload.method) { + case 'eth_sendTransaction': + if (!this._config.shouldCollectTransactionTraces) { + next(); + } else { + const txData = payload.params[0]; + next(this._onTransactionSentAsync.bind(this, txData)); + } + return; + + case 'eth_call': + if (!this._config.shouldCollectCallTraces) { + next(); + } else { + const callData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); + } + return; + + case 'eth_estimateGas': + if (!this._config.shouldCollectGasEstimateTraces) { + next(); + } else { + const estimateGasData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); + } + return; + + default: + next(); + return; + } + } else { + next(); + return; + } + } + /** + * Set's the subprovider's engine to the ProviderEngine it is added to. + * This is only called within the ProviderEngine source code, do not call + * directly. + * @param engine The ProviderEngine this subprovider is added to + */ + public setEngine(engine: Provider): void { + super.setEngine(engine); + this._web3Wrapper = new Web3Wrapper(engine); + } + protected abstract async _recordTxTraceAsync( + address: string, + data: string | undefined, + txHash: string, + ): Promise; + private async _onTransactionSentAsync( + txData: MaybeFakeTxData, + err: Error | null, + txHash: string | undefined, + cb: Callback, + ): Promise { + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. Not a call executed as one. + // And we don't want it to be executed within a snapshotting period + await this._lock.acquire(); + } + const NULL_ADDRESS = '0x0'; + if (_.isNull(err)) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); + } else { + const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); + const transactions = latestBlock.transactions; + for (const transaction of transactions) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); + } + } + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. Not a call executed as one. + // And we don't want it to be executed within a snapshotting period + this._lock.release(); + } + cb(); + } + private async _onCallOrGasEstimateExecutedAsync( + callData: Partial, + _err: Error | null, + _callResult: string, + cb: Callback, + ): Promise { + await this._recordCallOrGasEstimateTraceAsync(callData); + cb(); + } + private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { + // We don't want other transactions to be exeucted during snashotting period, that's why we lock the + // transaction execution for all transactions except our fake ones. + await this._lock.acquire(); + const blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); + await blockchainLifecycle.startAsync(); + const fakeTxData = { + gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers + isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked + ...callData, + from: callData.from || this._defaultFromAddress, + }; + try { + const txData = marshaller.unmarshalTxData(fakeTxData); + const txHash = await this._web3Wrapper.sendTransactionAsync(txData); + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + } catch (err) { + // TODO(logvinov) Check that transaction failed and not some other exception + // Even if this transaction failed - we've already recorded it's trace. + _.noop(); + } + await blockchainLifecycle.revertAsync(); + this._lock.release(); + } +} diff --git a/packages/sol-trace-based-tools-common/src/trace_collector.ts b/packages/sol-trace-based-tools-common/src/trace_collector.ts new file mode 100644 index 000000000..8ba71f4df --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace_collector.ts @@ -0,0 +1,93 @@ +import { promisify } from '@0x/utils'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as fs from 'fs'; +import { Collector } from 'istanbul'; +import * as _ from 'lodash'; +import { getLogger, levels, Logger } from 'loglevel'; +import * as mkdirp from 'mkdirp'; + +import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; +import { constants } from './constants'; +import { parseSourceMap } from './source_maps'; +import { + ContractData, + Coverage, + SourceRange, + Subtrace, + TraceInfo, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +import { utils } from './utils'; + +const mkdirpAsync = promisify(mkdirp); + +export type SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +) => Coverage; + +/** + * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. + */ +export class TraceCollector { + private readonly _artifactAdapter: AbstractArtifactAdapter; + private readonly _logger: Logger; + private _contractsData!: ContractData[]; + private readonly _collector = new Collector(); + private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; + + /** + * Instantiates a TraceCollector instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace + */ + constructor( + artifactAdapter: AbstractArtifactAdapter, + isVerbose: boolean, + singleFileSubtraceHandler: SingleFileSubtraceHandler, + ) { + this._artifactAdapter = artifactAdapter; + this._logger = getLogger('sol-trace-based-tools-common'); + this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); + this._singleFileSubtraceHandler = singleFileSubtraceHandler; + } + public async writeOutputAsync(): Promise { + const finalCoverage = this._collector.getFinalCoverage(); + const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); + await mkdirpAsync('coverage'); + fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); + } + public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { + if (_.isUndefined(this._contractsData)) { + this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); + } + const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; + const bytecode = isContractCreation + ? (traceInfo as TraceInfoNewContract).bytecode + : (traceInfo as TraceInfoExistingContract).runtimeBytecode; + const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); + if (_.isUndefined(contractData)) { + const errMsg = isContractCreation + ? `Unknown contract creation transaction` + : `Transaction to an unknown address: ${traceInfo.address}`; + this._logger.warn(errMsg); + return; + } + 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++) { + const singleFileCoverageForTrace = this._singleFileSubtraceHandler( + contractData, + traceInfo.subtrace, + pcToSourceRange, + fileIndex, + ); + this._collector.add(singleFileCoverageForTrace); + } + } +} diff --git a/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts new file mode 100644 index 000000000..635a68f58 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts @@ -0,0 +1,59 @@ +import * as _ from 'lodash'; + +import { constants } from './constants'; +import { getTracesByContractAddress } from './trace'; +import { TraceCollectionSubprovider } from './trace_collection_subprovider'; +import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; + +// TraceInfoSubprovider is extended by subproviders which need to work with one +// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which +// is called for each TraceInfo. +export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { + protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; + protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { + 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); + if (address === constants.NEW_CONTRACT) { + for (const subcallAddress of subcallAddresses) { + let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; + if (subcallAddress === 'NEW_CONTRACT') { + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + bytecode: data as string, + }; + } else { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + } + await this._handleTraceInfoAsync(traceInfo); + } + } else { + for (const subcallAddress of subcallAddresses) { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + const traceInfo: TraceInfoExistingContract = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + await this._handleTraceInfoAsync(traceInfo); + } + } + } +} diff --git a/packages/sol-trace-based-tools-common/src/types.ts b/packages/sol-trace-based-tools-common/src/types.ts new file mode 100644 index 000000000..54ade0400 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/types.ts @@ -0,0 +1,126 @@ +import { StructLog } from 'ethereum-types'; +import * as Parser from 'solidity-parser-antlr'; + +export interface LineColumn { + line: number; + column: number; +} + +export interface SourceRange { + location: SingleFileSourceRange; + fileName: string; +} + +export interface SingleFileSourceRange { + start: LineColumn; + end: LineColumn; +} + +export interface LocationByOffset { + [offset: number]: LineColumn; +} + +export interface FunctionDescription { + name: string; + line: number; + loc: SingleFileSourceRange; + skip?: boolean; +} + +export type StatementDescription = SingleFileSourceRange; + +export interface BranchDescription { + line: number; + type: 'if' | 'switch' | 'cond-expr' | 'binary-expr'; + locations: SingleFileSourceRange[]; +} + +export interface FnMap { + [functionId: string]: FunctionDescription; +} + +export interface BranchMap { + [branchId: string]: BranchDescription; +} + +export interface StatementMap { + [statementId: string]: StatementDescription; +} + +export interface LineCoverage { + [lineNo: number]: number; +} + +export interface FunctionCoverage { + [functionId: string]: number; +} + +export interface StatementCoverage { + [statementId: string]: number; +} + +export interface BranchCoverage { + [branchId: string]: number[]; +} + +export interface Coverage { + [fineName: string]: { + l?: LineCoverage; + f: FunctionCoverage; + s: StatementCoverage; + b: BranchCoverage; + fnMap: FnMap; + branchMap: BranchMap; + statementMap: StatementMap; + path: string; + }; +} + +export interface ContractData { + bytecode: string; + sourceMap: string; + runtimeBytecode: string; + sourceMapRuntime: string; + sourceCodes: string[]; + sources: string[]; +} + +// Part of the trace executed within the same context +export type Subtrace = StructLog[]; + +export interface TraceInfoBase { + subtrace: Subtrace; + txHash: string; +} + +export interface TraceInfoNewContract extends TraceInfoBase { + address: 'NEW_CONTRACT'; + bytecode: string; +} + +export interface TraceInfoExistingContract extends TraceInfoBase { + address: string; + runtimeBytecode: string; +} + +export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract; + +export enum BlockParamLiteral { + Latest = 'latest', +} + +export interface EvmCallStackEntry { + structLog: StructLog; + address: string; +} + +export type EvmCallStack = EvmCallStackEntry[]; + +export interface SourceSnippet { + source: string; + fileName: string; + type: string; + node: Parser.ASTNode; + name: string | null; + range: SingleFileSourceRange; +} diff --git a/packages/sol-trace-based-tools-common/src/utils.ts b/packages/sol-trace-based-tools-common/src/utils.ts new file mode 100644 index 000000000..d8bc65e73 --- /dev/null +++ b/packages/sol-trace-based-tools-common/src/utils.ts @@ -0,0 +1,87 @@ +import { addressUtils, BigNumber } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import { addHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { ContractData, LineColumn, SingleFileSourceRange } from './types'; + +// This is the minimum length of valid contract bytecode. The Solidity compiler +// metadata is 86 bytes. If you add the '0x' prefix, we get 88. +const MIN_CONTRACT_BYTECODE_LENGTH = 88; + +export const utils = { + compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { + return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; + }, + removeHexPrefix(hex: string): string { + const hexPrefix = '0x'; + return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; + }, + isRangeInside(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____________________________ + .replace(/_.*_/, '.*') + // Last 86 characters is solidity compiler metadata that's different between compilations + .replace(/.{86}$/, '') + // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance + .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); + // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. + const MAX_REGEX_LENGTH = 32767; + const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); + return truncatedBytecodeRegex; + }, + getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { + if (!bytecode.startsWith('0x')) { + throw new Error(`0x hex prefix missing: ${bytecode}`); + } + const contractData = _.find(contractsData, contractDataCandidate => { + const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); + // If the bytecode is less than the minimum length, we are probably + // dealing with an interface. This isn't what we're looking for. + if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); + if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so + // collisions are practically impossible and it allows us to reuse that code + return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); + }); + return contractData; + }, + isCallLike(op: OpCode): boolean { + return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); + }, + isEndOpcode(op: OpCode): boolean { + return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); + }, + getAddressFromStackEntry(stackEntry: string): string { + const hexBase = 16; + return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); + }, + normalizeStructLogs(structLogs: StructLog[]): StructLog[] { + if (structLogs[0].depth === 1) { + // Geth uses 1-indexed depth counter whilst ganache starts from 0 + const newStructLogs = _.map(structLogs, structLog => ({ + ...structLog, + depth: structLog.depth - 1, + })); + return newStructLogs; + } + return structLogs; + }, + getRange(sourceCode: string, range: SingleFileSourceRange): string { + const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); + lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); + lines[0] = lines[0].slice(range.start.column); + return lines.join('\n'); + }, +}; diff --git a/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts b/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts new file mode 100644 index 000000000..7832ec316 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts @@ -0,0 +1,155 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { collectCoverageEntries } from '../src/collect_coverage_entries'; +import { utils } from '../src/utils'; + +const expect = chai.expect; + +describe('Collect coverage entries', () => { + describe('#collectCoverageEntries', () => { + it('correctly collects coverage entries for Simplest contract', () => { + const simplestContractBaseName = 'Simplest.sol'; + const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); + const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simplestContract); + expect(coverageEntries.fnMap).to.be.deep.equal({}); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + expect(coverageEntries.statementMap).to.be.deep.equal({}); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for SimpleStorage contract', () => { + const simpleStorageContractBaseName = 'SimpleStorage.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnIds = _.keys(coverageEntries.fnMap); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); + const setFunction = `function set(uint x) { + storedData = x; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); + const getFunction = `function get() constant returns (uint retVal) { + return storedData; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( + 'storedData = x', + ); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( + 'return storedData;', + ); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for AllSolidityFeatures contract', () => { + const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnDescriptions = _.values(coverageEntries.fnMap); + const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); + const expectedFnNames = [ + 'f', + 'c', + 'test', + 'getChoice', + 'Base', + 'Derived', + 'f', + 'f', + '', + 'g', + 'setData', + 'getData', + 'sendHalf', + 'insert', + 'remove', + 'contains', + 'iterate_start', + 'iterate_valid', + 'iterate_advance', + 'iterate_get', + 'insert', + 'sum', + 'restricted', + 'DualIndex', + 'set', + 'transfer_ownership', + 'lookup', + '', + '', + 'sum', + 'someFunction', + 'fun', + 'at', + 'test', + 'get', + 'returnNumber', + 'alloc', + 'ham', + 'getMyTuple', + 'ham', + 'abstain', + 'foobar', + 'foobar', + 'a', + ]; + expect(fnNames).to.be.deep.equal(expectedFnNames); + + const branchDescriptions = _.values(coverageEntries.branchMap); + const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); + // tslint:disable-next-line:custom-no-magic-numbers + expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); + const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); + expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); + }); + + it('correctly ignores all coverage entries for Ignore contract', () => { + const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; + const solcovIgnoreContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + solcovIgnoreContractBaseName, + ); + const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); + const coverageEntries = collectCoverageEntries(solcovIgnoreContract); + const fnIds = _.keys(coverageEntries.fnMap); + + expect(fnIds.length).to.be.equal(1); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); + const setFunction = `function set(uint x) public { + /* solcov ignore next */ + storedData = x; + }`; + expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( + setFunction, + ); + expect(statementIds.length).to.be.equal(1); + expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol new file mode 100644 index 000000000..21137347e --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol @@ -0,0 +1,413 @@ +// Examples taken from the Solidity documentation online. + +// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions +pragma solidity 0.4.0; +pragma solidity ^0.4.0; + +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias, symbol2} from "File.sol"; + +interface i { + function f(); +} + +contract c { + function c() + { + val1 = 1 wei; // 1 + val2 = 1 szabo; // 1 * 10 ** 12 + val3 = 1 finney; // 1 * 10 ** 15 + val4 = 1 ether; // 1 * 10 ** 18 + } + uint256 val1; + uint256 val2; + uint256 val3; + uint256 val4; +} + +contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + + function test() + { + choices = ActionChoices.GoStraight; + } + function getChoice() returns (uint d) + { + d = uint256(choices); + } + ActionChoices choices; +} + +contract Base { + function Base(uint i) + { + m_i = i; + } + uint public m_i; +} +contract Derived is Base(0) { + function Derived(uint i) Base(i) {} +} + +contract C { + uint248 x; // 31 bytes: slot 0, offset 0 + uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) + uint240 z; // 30 bytes: slot 1, offset 2 bytes + uint8 a; // 1 byte: slot 2, offset 0 bytes + struct S { + uint8 a; // 1 byte, slot +0, offset 0 bytes + uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) + } + S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) + uint8 alpha; // 1 byte, slot 4 (start new slot after struct) + uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) + uint8 gamma; // 1 byte, slot 7 (start new slot after array) +} + +contract test { + function f(uint x, uint y) returns (uint z) { + var c = x + 3; + var b = 7 + (c * (8 - 7)) - x; + return -(-b | 0); + } +} + +contract test { + function f(uint x, uint y) returns (uint z) { + return 10; + } +} + +contract c { + function () returns (uint) { return g(8); } + function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } + function setData(uint pos, uint value) internal { data[pos] = value; } + function getData(uint pos) internal { return data[pos]; } + mapping(uint => uint) data; +} + +contract Sharer { + function sendHalf(address addr) returns (uint balance) { + if (!addr.send(msg.value/2)) + throw; // also reverts the transfer to Sharer + return address(this).balance; + } +} + +/// @dev Models a modifiable and iterable set of uint values. +library IntegerSet +{ + struct data + { + /// Mapping item => index (or zero if not present) + mapping(uint => uint) index; + /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. + uint[] items; + /// Number of stored items. + uint size; + } + function insert(data storage self, uint value) returns (bool alreadyPresent) + { + uint index = self.index[value]; + if (index > 0) + return true; + else + { + if (self.items.length == 0) self.items.length = 1; + index = self.items.length++; + self.items[index] = value; + self.index[value] = index; + self.size++; + return false; + } + } + function remove(data storage self, uint value) returns (bool success) + { + uint index = self.index[value]; + if (index == 0) + return false; + delete self.index[value]; + delete self.items[index]; + self.size --; + } + function contains(data storage self, uint value) returns (bool) + { + return self.index[value] > 0; + } + function iterate_start(data storage self) returns (uint index) + { + return iterate_advance(self, 0); + } + function iterate_valid(data storage self, uint index) returns (bool) + { + return index < self.items.length; + } + function iterate_advance(data storage self, uint index) returns (uint r_index) + { + index++; + while (iterate_valid(self, index) && self.index[self.items[index]] == index) + index++; + return index; + } + function iterate_get(data storage self, uint index) returns (uint value) + { + return self.items[index]; + } +} + +/// How to use it: +contract User +{ + /// Just a struct holding our data. + IntegerSet.data data; + /// Insert something + function insert(uint v) returns (uint size) + { + /// Sends `data` via reference, so IntegerSet can modify it. + IntegerSet.insert(data, v); + /// We can access members of the struct - but we should take care not to mess with them. + return data.size; + } + /// Computes the sum of all stored data. + function sum() returns (uint s) + { + for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) + s += IntegerSet.iterate_get(data, i); + } +} + +// This broke it at one point (namely the modifiers). +contract DualIndex { + mapping(uint => mapping(uint => uint)) data; + address public admin; + + modifier restricted { if (msg.sender == admin) _; } + + function DualIndex() { + admin = msg.sender; + } + + function set(uint key1, uint key2, uint value) restricted { + uint[2][4] memory defaults; // "memory" broke things at one time. + data[key1][key2] = value; + } + + function transfer_ownership(address _admin) restricted { + admin = _admin; + } + + function lookup(uint key1, uint key2) returns(uint) { + return data[key1][key2]; + } +} + +contract A { + +} + +contract B { + +} + +contract C is A, B { + +} + +contract TestPrivate +{ + uint private value; +} + +contract TestInternal +{ + uint internal value; +} + +contract FromSolparse is A, B, TestPrivate, TestInternal { + function() { + uint a = 6 ** 9; + var (x) = 100; + uint y = 2 days; + } +} + +contract CommentedOutFunction { + // FYI: This empty function, as well as the commented + // out function below (bad code) is important to this test. + function() { + + } + + // function something() + // uint x = 10; + // } +} + +library VarHasBrackets { + string constant specialRight = "}"; + //string storage specialLeft = "{"; +} + +library UsingExampleLibrary { + function sum(uint[] storage self) returns (uint s) { + for (uint i = 0; i < self.length; i++) + s += self[i]; + } +} + +contract UsingExampleContract { + using UsingExampleLibrary for uint[]; +} + +contract NewStuff { + uint[] b; + + function someFunction() payable { + string storage a = hex"ab1248fe"; + b[2+2]; + } +} + +// modifier with expression +contract MyContract { + function fun() mymodifier(foo.bar()) {} +} + +library GetCode { + function at(address _addr) returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } +} + +contract assemblyLocalBinding { + function test(){ + assembly { + let v := 1 + let x := 0x00 + let y := x + let z := "hello" + } + } +} + +contract assemblyReturn { + uint a = 10; + + function get() constant returns(uint) { + assembly { + mstore(0x40, sload(0)) + byte(0) + address(0) + return(0x40,32) + } + } +} + +contract usesConst { + uint const = 0; +} + +contract memoryArrays { + uint seven = 7; + + function returnNumber(uint number) returns (uint){ + return number; + } + + function alloc() { + uint[] memory a = new uint[](7); + uint[] memory b = new uint[](returnNumber(seven)); + } +} + +contract DeclarativeExpressions { + uint a; + uint b = 7; + uint b2=0; + uint public c; + uint constant public d; + uint public constant e; + uint private constant f = 7; + struct S { uint q;} + + function ham(S storage s1, uint[] storage arr) internal { + uint x; + uint y = 7; + S storage s2 = s1; + uint[] memory stor; + uint[] storage stor2 = arr; + } +} + +contract VariableDeclarationTuple { + function getMyTuple() returns (bool, bool){ + return (true, false); + } + + function ham (){ + var (x, y) = (10, 20); + var (a, b) = getMyTuple(); + var (,c) = (10, 20); + var (d,,) = (10, 20, 30); + var (,e,,f,) = (10, 20, 30, 40, 50); + + var ( + num1, num2, + num3, ,num5 + ) = (10, 20, 30, 40, 50); + } +} + +contract TypeIndexSpacing { + uint [ 7 ] x; + uint [] y; +} + +contract Ballot { + + struct Voter { + uint weight; + bool voted; + } + + function abstain() returns (bool) { + return false; + } + + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); + + Voter you = Voter(1, true); + + Voter me = Voter({ + weight: 2, + voted: abstain() + }); + + Voter airbnb = Voter({ + weight: 2, + voted: true, + }); +} + +contract multilineReturn { + function a() returns (uint x) { + return + 5; + } +} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol new file mode 100644 index 000000000..e4b4ac246 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.21; + +contract SimpleStorage { + uint public storedData; + function set(uint x) { + storedData = x; + } + function get() constant returns (uint retVal) { + return storedData; + } +} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol new file mode 100644 index 000000000..d71016e07 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol @@ -0,0 +1,2 @@ +contract Simplest { +} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol new file mode 100644 index 000000000..a7977ffb4 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.21; + +contract SolcovIgnore { + uint public storedData; + + function set(uint x) public { + /* solcov ignore next */ + storedData = x; + } + + /* solcov ignore next */ + function get() constant public returns (uint retVal) { + return storedData; + } +} + +/* solcov ignore next */ +contract Ignore { + function ignored() public returns (bool) { + return false; + } +} diff --git a/packages/sol-trace-based-tools-common/test/instructions_test.ts b/packages/sol-trace-based-tools-common/test/instructions_test.ts new file mode 100644 index 000000000..058053cf9 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/instructions_test.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { constants } from '../src/constants'; +import { getPcToInstructionIndexMapping } from '../src/instructions'; + +const expect = chai.expect; + +describe('instructions', () => { + describe('#getPcToInstructionIndexMapping', () => { + it('correctly maps pcs to instruction indexed', () => { + // tslint:disable-next-line:custom-no-magic-numbers + const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); + const pcToInstruction = getPcToInstructionIndexMapping(bytecode); + const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; + expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts new file mode 100644 index 000000000..9c58d2cef --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts @@ -0,0 +1,29 @@ +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; + +const expect = chai.expect; + +describe('SolCompilerArtifactAdapter', () => { + describe('#collectContractsData', () => { + it('correctly collects contracts data', async () => { + const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); + const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); + const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); + const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); + _.forEach(contractsData, contractData => { + expect(contractData).to.have.keys([ + 'sourceCodes', + 'sources', + 'sourceMap', + 'sourceMapRuntime', + 'bytecode', + 'runtimeBytecode', + ]); + }); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/source_maps_test.ts b/packages/sol-trace-based-tools-common/test/source_maps_test.ts new file mode 100644 index 000000000..5820bedd7 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/source_maps_test.ts @@ -0,0 +1,71 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; + +const expect = chai.expect; + +const simplestContractBaseName = 'Simplest.sol'; +const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); +const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + +describe('source maps', () => { + describe('#getLocationByOffset', () => { + it('correctly computes location by offset', () => { + const locationByOffset = getLocationByOffset(simplestContract); + const expectedLocationByOffset = { + '0': { line: 1, column: 0 }, + '1': { line: 1, column: 1 }, + '2': { line: 1, column: 2 }, + '3': { line: 1, column: 3 }, + '4': { line: 1, column: 4 }, + '5': { line: 1, column: 5 }, + '6': { line: 1, column: 6 }, + '7': { line: 1, column: 7 }, + '8': { line: 1, column: 8 }, + '9': { line: 1, column: 9 }, + '10': { line: 1, column: 10 }, + '11': { line: 1, column: 11 }, + '12': { line: 1, column: 12 }, + '13': { line: 1, column: 13 }, + '14': { line: 1, column: 14 }, + '15': { line: 1, column: 15 }, + '16': { line: 1, column: 16 }, + '17': { line: 1, column: 17 }, + '18': { line: 1, column: 18 }, + '19': { line: 1, column: 19 }, + '20': { line: 2, column: 0 }, + '21': { line: 2, column: 1 }, + '22': { line: 3, column: 0 }, + }; + expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); + }); + }); + describe('#parseSourceMap', () => { + it('correctly parses the source map', () => { + // This is the source map and bytecode for an empty contract like Example.sol + const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; + const bytecodeHex = + '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; + const sources = [simplestContractBaseName]; + const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); + const expectedSourceRange = { + location: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 1 }, + }, + fileName: simplestContractBaseName, + }; + _.forEach(pcToSourceRange, sourceRange => { + // Solidity source maps are too short and we map some instructions to undefined + // Source: https://github.com/ethereum/solidity/issues/3741 + if (!_.isUndefined(sourceRange)) { + expect(sourceRange).to.be.deep.equal(expectedSourceRange); + } + }); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/trace_test.ts b/packages/sol-trace-based-tools-common/test/trace_test.ts new file mode 100644 index 000000000..7a034362c --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/trace_test.ts @@ -0,0 +1,55 @@ +import * as chai from 'chai'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; +import 'mocha'; + +import { getTracesByContractAddress } from '../src/trace'; + +const expect = chai.expect; + +const DEFAULT_STRUCT_LOG: StructLog = { + depth: 0, + error: '', + gas: 0, + gasCost: 0, + memory: [], + op: OpCode.Invalid, + pc: 0, + stack: [], + storage: {}, +}; + +function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { + return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; +} + +describe('Trace', () => { + describe('#getTracesByContractAddress', () => { + it('correctly splits trace by contract address', () => { + const delegateCallAddress = '0x0000000000000000000000000000000000000002'; + const trace = [ + { + op: OpCode.DelegateCall, + stack: [delegateCallAddress, '0x'], + depth: 0, + }, + { + op: OpCode.Return, + depth: 1, + }, + { + op: OpCode.Return, + depth: 0, + }, + ]; + const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); + const startAddress = '0x0000000000000000000000000000000000000001'; + const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); + const expectedTraceByContractAddress = { + [startAddress]: [fullTrace[0], fullTrace[2]], + [delegateCallAddress]: [fullTrace[1]], + }; + expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/test/utils_test.ts b/packages/sol-trace-based-tools-common/test/utils_test.ts new file mode 100644 index 000000000..6fc8fcfe1 --- /dev/null +++ b/packages/sol-trace-based-tools-common/test/utils_test.ts @@ -0,0 +1,53 @@ +import * as chai from 'chai'; +import * as dirtyChai from 'dirty-chai'; +import 'mocha'; + +import { utils } from '../src/utils'; + +chai.use(dirtyChai); +const expect = chai.expect; + +describe('utils', () => { + describe('#compareLineColumn', () => { + it('correctly compares LineColumns', () => { + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); + expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); + }); + }); + + describe('#isRangeInside', () => { + it('returns true if inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, + ), + ).to.be.true(); + }); + it('returns true if the same', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + ), + ).to.be.true(); + }); + it('returns false if not inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, + ), + ).to.be.false(); + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + ), + ).to.be.false(); + }); + }); +}); diff --git a/packages/sol-trace-based-tools-common/tsconfig.json b/packages/sol-trace-based-tools-common/tsconfig.json new file mode 100644 index 000000000..2ee711adc --- /dev/null +++ b/packages/sol-trace-based-tools-common/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-trace-based-tools-common/tslint.json b/packages/sol-trace-based-tools-common/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-trace-based-tools-common/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} 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..ca6048d99 --- /dev/null +++ b/packages/sol-trace/CHANGELOG.json @@ -0,0 +1,12 @@ +[ + { + "version": "1.0.0", + "changes": [ + { + "note": + "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "pr": 1492 + } + ] + } +] diff --git a/packages/sol-trace/README.md b/packages/sol-trace/README.md new file mode 100644 index 000000000..dba193426 --- /dev/null +++ b/packages/sol-trace/README.md @@ -0,0 +1,75 @@ +## @0x/sol-trace + +Prints code traces when revert happens. + +### 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..fd80c541e --- /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-trace-based-tools-common": "^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..1f3ca3a99 --- /dev/null +++ b/packages/sol-trace/src/index.ts @@ -0,0 +1,24 @@ +export { + AbstractArtifactAdapter, + TruffleArtifactAdapter, + SolCompilerArtifactAdapter, + ContractData, +} from '@0x/sol-trace-based-tools-common'; + +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..37e62b3f8 --- /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-trace-based-tools-common'; +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 { + 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 { + 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/**/*"] +} diff --git a/packages/sra-spec/src/md/introduction.md b/packages/sra-spec/src/md/introduction.md index 824df8998..ce61abff6 100644 --- a/packages/sra-spec/src/md/introduction.md +++ b/packages/sra-spec/src/md/introduction.md @@ -1,12 +1,12 @@ # Schemas -The [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas). Examples of each payload and response can be found in the library's [test suite](https://github.com/0xProject/0x.js/blob/development/packages/json-schemas/test/schema_test.ts#L1). +The [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas). Examples of each payload and response can be found in the library's [test suite](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/test/schema_test.ts#L1). ```bash npm install @0xproject/json-schemas --save ``` -You can easily validate your API's payloads and responses using the [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas) package: +You can easily validate your API's payloads and responses using the [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas) package: ```js import {SchemaValidator, ValidatorResult, schemas} from '@0xproject/json-schemas'; diff --git a/packages/website/md/docs/sol_cov/1/installation.md b/packages/website/md/docs/sol_cov/1/installation.md deleted file mode 100644 index b9ce25a5f..000000000 --- a/packages/website/md/docs/sol_cov/1/installation.md +++ /dev/null @@ -1,17 +0,0 @@ -**Install** - -```bash -yarn add @0xproject/sol-cov -``` - -**Import** - -```javascript -import { CoverageSubprovider } from '@0xproject/sol-cov'; -``` - -or - -```javascript -var CoverageSubprovider = require('@0xproject/sol-cov').CoverageSubprovider; -``` diff --git a/packages/website/md/docs/sol_cov/1/introduction.md b/packages/website/md/docs/sol_cov/1/introduction.md deleted file mode 100644 index 7064a3554..000000000 --- a/packages/website/md/docs/sol_cov/1/introduction.md +++ /dev/null @@ -1 +0,0 @@ -Welcome to the [@0xproject/sol-cov](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-cov) documentation! Sol-cov is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_cov/1/usage.md b/packages/website/md/docs/sol_cov/1/usage.md deleted file mode 100644 index c2b8404af..000000000 --- a/packages/website/md/docs/sol_cov/1/usage.md +++ /dev/null @@ -1,62 +0,0 @@ -Sol-cov uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `CoverageSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0x.org/wiki#Web3-Provider-Explained) wiki article. - -The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. - -Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. - -In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. - -### Sol-compiler - -If you are generating your artifacts with [@0xproject/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. - -```typescript -import { SolCompilerArtifactsAdapter } from '@0xproject/sol-cov'; -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); -``` - -### Truffle - -If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. - -```typescript -import { TruffleArtifactAdapter } from '@0xproject/sol-cov'; -const contractsPath = 'src/contracts'; -const artifactAdapter = new TruffleArtifactAdapter(contractsDir); -``` - -Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. - -### Other framework/toolset - -You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. -Look at the code of the two adapters above for examples. - -### Usage - -```typescript -import { CoverageSubprovider } from '@0xproject/sol-cov'; -import ProviderEngine = require('web3-provider-engine'); - -const provider = new ProviderEngine(); - -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const networkId = 50; -// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. -const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; -const isVerbose = true; -const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); - -provider.addProvider(coverageSubprovider); -``` - -After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: - -```typescript -await coverageSubprovider.writeCoverageAsync(); -``` - -This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_cov/2/installation.md b/packages/website/md/docs/sol_cov/2/installation.md deleted file mode 100644 index 1d4557cf5..000000000 --- a/packages/website/md/docs/sol_cov/2/installation.md +++ /dev/null @@ -1,17 +0,0 @@ -**Install** - -```bash -yarn add @0x/sol-cov -``` - -**Import** - -```javascript -import { CoverageSubprovider } from '@0x/sol-cov'; -``` - -or - -```javascript -var CoverageSubprovider = require('@0x/sol-cov').CoverageSubprovider; -``` diff --git a/packages/website/md/docs/sol_cov/2/introduction.md b/packages/website/md/docs/sol_cov/2/introduction.md deleted file mode 100644 index ac3256845..000000000 --- a/packages/website/md/docs/sol_cov/2/introduction.md +++ /dev/null @@ -1 +0,0 @@ -Welcome to the [sol-cov](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-cov) documentation! Sol-cov is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_cov/2/usage.md b/packages/website/md/docs/sol_cov/2/usage.md deleted file mode 100644 index 8e33f3bf5..000000000 --- a/packages/website/md/docs/sol_cov/2/usage.md +++ /dev/null @@ -1,62 +0,0 @@ -Sol-cov uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `CoverageSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0x.org/wiki#Web3-Provider-Explained) wiki article. - -The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. - -Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. - -In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. - -### Sol-compiler - -If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. - -```typescript -import { SolCompilerArtifactsAdapter } from '@0x/sol-cov'; -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); -``` - -### Truffle - -If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. - -```typescript -import { TruffleArtifactAdapter } from '@0x/sol-cov'; -const contractsPath = 'src/contracts'; -const artifactAdapter = new TruffleArtifactAdapter(contractsDir); -``` - -Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. - -### Other framework/toolset - -You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. -Look at the code of the two adapters above for examples. - -### Usage - -```typescript -import { CoverageSubprovider } from '@0x/sol-cov'; -import ProviderEngine = require('web3-provider-engine'); - -const provider = new ProviderEngine(); - -const artifactsPath = 'src/artifacts'; -const contractsPath = 'src/contracts'; -const networkId = 50; -// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. -const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; -const isVerbose = true; -const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); - -provider.addProvider(coverageSubprovider); -``` - -After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: - -```typescript -await coverageSubprovider.writeCoverageAsync(); -``` - -This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_coverage/installation.md b/packages/website/md/docs/sol_coverage/installation.md new file mode 100644 index 000000000..c7aaf07e9 --- /dev/null +++ b/packages/website/md/docs/sol_coverage/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0x/sol-coverage +``` + +**Import** + +```javascript +import { CoverageSubprovider } from '@0x/sol-coverage'; +``` + +or + +```javascript +var CoverageSubprovider = require('@0x/sol-coverage').CoverageSubprovider; +``` diff --git a/packages/website/md/docs/sol_coverage/introduction.md b/packages/website/md/docs/sol_coverage/introduction.md new file mode 100644 index 000000000..3214e93a9 --- /dev/null +++ b/packages/website/md/docs/sol_coverage/introduction.md @@ -0,0 +1 @@ +Welcome to the [sol-coverage](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-coverage) documentation! Sol-coverage is a Solidity coverage tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_coverage/usage.md b/packages/website/md/docs/sol_coverage/usage.md new file mode 100644 index 000000000..dd3cdf597 --- /dev/null +++ b/packages/website/md/docs/sol_coverage/usage.md @@ -0,0 +1,62 @@ +Sol-coverage uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `CoverageSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0x.org/wiki#Web3-Provider-Explained) wiki article. + +The CoverageSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. + +Coverage subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. + +In order to use `CoverageSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. + +### Sol-compiler + +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. + +```typescript +import { SolCompilerArtifactsAdapter } from '@0x/sol-coverage'; +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); +``` + +### Truffle + +If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. + +```typescript +import { TruffleArtifactAdapter } from '@0x/sol-coverage'; +const contractsPath = 'src/contracts'; +const artifactAdapter = new TruffleArtifactAdapter(contractsDir); +``` + +Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. + +### Other framework/toolset + +You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. +Look at the code of the two adapters above for examples. + +### Usage + +```typescript +import { CoverageSubprovider } from '@0x/sol-coverage'; +import ProviderEngine = require('web3-provider-engine'); + +const provider = new ProviderEngine(); + +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const networkId = 50; +// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. +const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; +const isVerbose = true; +const coverageSubprovider = new CoverageSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); + +provider.addProvider(coverageSubprovider); +``` + +After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: + +```typescript +await coverageSubprovider.writeCoverageAsync(); +``` + +This will create a `coverage.json` file in a `coverage` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/coverage.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_profiler/installation.md b/packages/website/md/docs/sol_profiler/installation.md new file mode 100644 index 000000000..be9a365f5 --- /dev/null +++ b/packages/website/md/docs/sol_profiler/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0x/sol-profiler +``` + +**Import** + +```javascript +import { ProfilerSubprovider } from '@0x/sol-profiler'; +``` + +or + +```javascript +var ProfilerSubprovider = require('@0x/sol-profiler').ProfilerSubprovider; +``` diff --git a/packages/website/md/docs/sol_profiler/introduction.md b/packages/website/md/docs/sol_profiler/introduction.md new file mode 100644 index 000000000..bd53fb0fe --- /dev/null +++ b/packages/website/md/docs/sol_profiler/introduction.md @@ -0,0 +1 @@ +Welcome to the [sol-profiler](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-profiler) documentation! Sol-profiler is a Solidity profiler tool. diff --git a/packages/website/md/docs/sol_profiler/usage.md b/packages/website/md/docs/sol_profiler/usage.md new file mode 100644 index 000000000..35ea140da --- /dev/null +++ b/packages/website/md/docs/sol_profiler/usage.md @@ -0,0 +1,62 @@ +Sol-profiler uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `ProfilerSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0x.org/wiki#Web3-Provider-Explained) wiki article. + +The ProfilerSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. + +Profiler subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. + +In order to use `ProfilerSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. + +### Sol-compiler + +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. + +```typescript +import { SolCompilerArtifactsAdapter } from '@0x/sol-profiler'; +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); +``` + +### Truffle + +If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. + +```typescript +import { TruffleArtifactAdapter } from '@0x/sol-profiler'; +const contractsPath = 'src/contracts'; +const artifactAdapter = new TruffleArtifactAdapter(contractsDir); +``` + +Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. + +### Other framework/toolset + +You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. +Look at the code of the two adapters above for examples. + +### Usage + +```typescript +import { ProfilerSubprovider } from '@0x/sol-profiler'; +import ProviderEngine = require('web3-provider-engine'); + +const provider = new ProviderEngine(); + +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const networkId = 50; +// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. +const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; +const isVerbose = true; +const profilerSubprovider = new ProfilerSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); + +provider.addProvider(profilerSubprovider); +``` + +After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: + +```typescript +await profilerSubprovider.writeProfilerAsync(); +``` + +This will create a `profiler.json` file in a `profiler` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/profiler.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/md/docs/sol_trace/installation.md b/packages/website/md/docs/sol_trace/installation.md new file mode 100644 index 000000000..2f794b2f8 --- /dev/null +++ b/packages/website/md/docs/sol_trace/installation.md @@ -0,0 +1,17 @@ +**Install** + +```bash +yarn add @0x/sol-trace +``` + +**Import** + +```javascript +import { TraceSubprovider } from '@0x/sol-trace'; +``` + +or + +```javascript +var TraceSubprovider = require('@0x/sol-trace').TraceSubprovider; +``` diff --git a/packages/website/md/docs/sol_trace/introduction.md b/packages/website/md/docs/sol_trace/introduction.md new file mode 100644 index 000000000..21fea764e --- /dev/null +++ b/packages/website/md/docs/sol_trace/introduction.md @@ -0,0 +1 @@ +Welcome to the [sol-trace](https://github.com/0xProject/0x-monorepo/tree/development/packages/sol-trace) documentation! Sol-trace is a Solidity trace tool for your smart contract tests. diff --git a/packages/website/md/docs/sol_trace/usage.md b/packages/website/md/docs/sol_trace/usage.md new file mode 100644 index 000000000..f3aa6fc35 --- /dev/null +++ b/packages/website/md/docs/sol_trace/usage.md @@ -0,0 +1,62 @@ +Sol-trace uses transaction traces in order to figure out which lines of Solidity source code have been covered by your tests. In order for it to gather these traces, you must add the `TraceSubprovider` to the [ProviderEngine](https://github.com/MetaMask/provider-engine) instance you use when running your Solidity tests. If you're unfamiliar with ProviderEngine, please read the [Web3 Provider explained](https://0x.org/wiki#Web3-Provider-Explained) wiki article. + +The TraceSubprovider eavesdrops on the `eth_sendTransaction` and `eth_call` RPC calls and collects traces after each call using `debug_traceTransaction`. `eth_call`'s' don't generate traces - so we take a snapshot, re-submit it as a transaction, get the trace and then revert the snapshot. + +Trace subprovider needs some info about your contracts (`srcMap`, `bytecode`). It gets that info from your project's artifacts. Some frameworks have their own artifact format. Some artifact formats don't actually contain all the neccessary data. + +In order to use `TraceSubprovider` with your favorite framework you need to pass an `artifactsAdapter` to it. + +### Sol-compiler + +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. + +```typescript +import { SolCompilerArtifactsAdapter } from '@0x/sol-trace'; +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const artifactsAdapter = new SolCompilerArtifactsAdapter(artifactsPath, contractsPath); +``` + +### Truffle + +If your project is using [Truffle](https://truffleframework.com/), we've written a `TruffleArtifactsAdapter`for you. + +```typescript +import { TruffleArtifactAdapter } from '@0x/sol-trace'; +const contractsPath = 'src/contracts'; +const artifactAdapter = new TruffleArtifactAdapter(contractsDir); +``` + +Because truffle artifacts don't have all the data we need - we actually will recompile your contracts under the hood. That's why you don't need to pass an `artifactsPath`. + +### Other framework/toolset + +You'll need to write your own artifacts adapter. It should extend `AbstractArtifactsAdapter`. +Look at the code of the two adapters above for examples. + +### Usage + +```typescript +import { TraceSubprovider } from '@0x/sol-trace'; +import ProviderEngine = require('web3-provider-engine'); + +const provider = new ProviderEngine(); + +const artifactsPath = 'src/artifacts'; +const contractsPath = 'src/contracts'; +const networkId = 50; +// Some calls might not have `from` address specified. Nevertheless - transactions need to be submitted from an address with at least some funds. defaultFromAddress is the address that will be used to submit those calls as transactions from. +const defaultFromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631'; +const isVerbose = true; +const traceSubprovider = new TraceSubprovider(artifactsAdapter, defaultFromAddress, isVerbose); + +provider.addProvider(traceSubprovider); +``` + +After your test suite is complete (e.g in the Mocha global `after` hook), you'll need to call: + +```typescript +await traceSubprovider.writeTraceAsync(); +``` + +This will create a `trace.json` file in a `trace` directory. This file has an [Istanbul format](https://github.com/gotwarlost/istanbul/blob/master/trace.json.md) - so you can use it with any of the existing Istanbul reporters. diff --git a/packages/website/ts/containers/asset_buyer_documentation.ts b/packages/website/ts/containers/asset_buyer_documentation.ts index c93b9332d..815176e47 100644 --- a/packages/website/ts/containers/asset_buyer_documentation.ts +++ b/packages/website/ts/containers/asset_buyer_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/asset_buyer/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/asset-buyer', type: SupportedDocJson.TypeDoc, displayName: 'AssetBuyer', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/asset-buyer', markdownMenu: { introduction: [markdownSections.introduction], install: [markdownSections.installation], @@ -40,31 +38,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/connect_documentation.ts b/packages/website/ts/containers/connect_documentation.ts index 0f11e0809..19fd0ee56 100644 --- a/packages/website/ts/containers/connect_documentation.ts +++ b/packages/website/ts/containers/connect_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/connect/1/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/connect', type: SupportedDocJson.TypeDoc, displayName: '0x Connect', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/connect', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -45,31 +43,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/contract_wrappers_documentation.ts b/packages/website/ts/containers/contract_wrappers_documentation.ts index 4c05605e3..dec95ae62 100644 --- a/packages/website/ts/containers/contract_wrappers_documentation.ts +++ b/packages/website/ts/containers/contract_wrappers_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/contract_wrappers/1/introduction'); @@ -24,7 +22,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/contract-wrappers', type: SupportedDocJson.TypeDoc, displayName: 'Contract Wrappers', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/contract-wrappers', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -40,31 +38,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, - translate: state.translate, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/ethereum_types_documentation.ts b/packages/website/ts/containers/ethereum_types_documentation.ts index 9d1df1d1f..9b668d515 100644 --- a/packages/website/ts/containers/ethereum_types_documentation.ts +++ b/packages/website/ts/containers/ethereum_types_documentation.ts @@ -1,12 +1,10 @@ -import { constants as docConstants, DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { constants as docConstants, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/ethereum_types/introduction'); @@ -36,31 +34,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/json_schemas_documentation.ts b/packages/website/ts/containers/json_schemas_documentation.ts index 9c4bb8e26..3862ba878 100644 --- a/packages/website/ts/containers/json_schemas_documentation.ts +++ b/packages/website/ts/containers/json_schemas_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/json_schemas/1/introduction'); @@ -32,7 +30,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/json-schemas', type: SupportedDocJson.TypeDoc, displayName: 'JSON Schemas', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/json-schemas', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], schemas: [markdownSections.schemas], @@ -65,31 +63,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/migrations_documentation.ts b/packages/website/ts/containers/migrations_documentation.ts index 02919e06e..dd5db3ac6 100644 --- a/packages/website/ts/containers/migrations_documentation.ts +++ b/packages/website/ts/containers/migrations_documentation.ts @@ -23,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/migrations', type: SupportedDocJson.TypeDoc, displayName: 'Migrations', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/migrations', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, diff --git a/packages/website/ts/containers/order_utils_documentation.ts b/packages/website/ts/containers/order_utils_documentation.ts index f1d794988..4996c329a 100644 --- a/packages/website/ts/containers/order_utils_documentation.ts +++ b/packages/website/ts/containers/order_utils_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/order_utils/1/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/order-utils', type: SupportedDocJson.TypeDoc, displayName: 'Order utils', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/order-utils', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -41,31 +39,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/order_watcher_documentation.ts b/packages/website/ts/containers/order_watcher_documentation.ts index 683e1fe9f..f3d3bada8 100644 --- a/packages/website/ts/containers/order_watcher_documentation.ts +++ b/packages/website/ts/containers/order_watcher_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/order_watcher/1/introduction'); @@ -25,7 +23,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/order-watcher', type: SupportedDocJson.TypeDoc, displayName: 'Order Watcher', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/order-watcher', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -41,31 +39,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/smart_contracts_documentation.ts b/packages/website/ts/containers/smart_contracts_documentation.ts index 57c98fa3b..7c0de5515 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.ts +++ b/packages/website/ts/containers/smart_contracts_documentation.ts @@ -1,13 +1,11 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import { Networks } from '@0x/react-shared'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths, SmartContractDocSections as Sections } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages, SmartContractDocSections as Sections } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/smart_contracts/1/introduction'); @@ -91,31 +89,7 @@ const docsInfoConfig: DocsInfoConfig = { }, }, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; - docsInfo: DocsInfo; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), - docsInfo, -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/sol_compiler_documentation.ts b/packages/website/ts/containers/sol_compiler_documentation.ts index f3a793e80..e899e9627 100644 --- a/packages/website/ts/containers/sol_compiler_documentation.ts +++ b/packages/website/ts/containers/sol_compiler_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/sol-compiler/1/introduction'); @@ -27,7 +25,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/sol-compiler', type: SupportedDocJson.TypeDoc, displayName: 'Solidity Compiler', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-compiler', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], }, @@ -45,31 +43,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/sol_cov_documentation.ts b/packages/website/ts/containers/sol_cov_documentation.ts deleted file mode 100644 index 8944ec70a..000000000 --- a/packages/website/ts/containers/sol_cov_documentation.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; - -/* tslint:disable:no-var-requires */ -const IntroMarkdown1 = require('md/docs/sol_cov/1/introduction'); -const InstallationMarkdown1 = require('md/docs/sol_cov/1/installation'); -const UsageMarkdown1 = require('md/docs/sol_cov/1/usage'); -const IntroMarkdown2 = require('md/docs/sol_cov/2/introduction'); -const InstallationMarkdown2 = require('md/docs/sol_cov/2/installation'); -const UsageMarkdown2 = require('md/docs/sol_cov/2/usage'); -/* tslint:enable:no-var-requires */ - -const markdownSections = { - introduction: 'introduction', - installation: 'installation', - usage: 'usage', -}; - -const docsInfoConfig: DocsInfoConfig = { - id: DocPackages.SolCov, - packageName: '@0x/sol-cov', - type: SupportedDocJson.TypeDoc, - displayName: 'Sol-cov', - packageUrl: 'https://github.com/0xProject/0x-monorepo', - markdownMenu: { - 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], - }, - sectionNameToMarkdownByVersion: { - '0.0.1': { - [markdownSections.introduction]: IntroMarkdown1, - [markdownSections.installation]: InstallationMarkdown1, - [markdownSections.usage]: UsageMarkdown1, - }, - '2.1.8': { - [markdownSections.introduction]: IntroMarkdown2, - [markdownSections.installation]: InstallationMarkdown2, - [markdownSections.usage]: UsageMarkdown2, - }, - }, - markdownSections, -}; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); - -export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( - DocPageComponent, -); diff --git a/packages/website/ts/containers/sol_coverage_documentation.ts b/packages/website/ts/containers/sol_coverage_documentation.ts new file mode 100644 index 000000000..25d67e2d0 --- /dev/null +++ b/packages/website/ts/containers/sol_coverage_documentation.ts @@ -0,0 +1,43 @@ +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_coverage/introduction'); +const InstallationMarkdown = require('md/docs/sol_coverage/installation'); +const UsageMarkdown = require('md/docs/sol_coverage/usage'); +/* tslint:enable:no-var-requires */ + +const markdownSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolCoverage, + packageName: '@0x/sol-coverage', + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-coverage', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-coverage', + markdownMenu: { + 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], + }, + sectionNameToMarkdownByVersion: { + '1.0.0': { + [markdownSections.introduction]: IntroMarkdown, + [markdownSections.installation]: InstallationMarkdown, + [markdownSections.usage]: UsageMarkdown, + }, + }, + markdownSections, +}; +const mapStateToProps = getMapStateToProps(docsInfoConfig); + +export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/sol_profiler_documentation.ts b/packages/website/ts/containers/sol_profiler_documentation.ts new file mode 100644 index 000000000..14839831e --- /dev/null +++ b/packages/website/ts/containers/sol_profiler_documentation.ts @@ -0,0 +1,43 @@ +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_profiler/introduction'); +const InstallationMarkdown = require('md/docs/sol_profiler/installation'); +const UsageMarkdown = require('md/docs/sol_profiler/usage'); +/* tslint:enable:no-var-requires */ + +const markdownSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolProfiler, + packageName: '@0x/sol-profiler', + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-profiler', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-profiler', + markdownMenu: { + 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], + }, + sectionNameToMarkdownByVersion: { + '1.0.0': { + [markdownSections.introduction]: IntroMarkdown, + [markdownSections.installation]: InstallationMarkdown, + [markdownSections.usage]: UsageMarkdown, + }, + }, + markdownSections, +}; +const mapStateToProps = getMapStateToProps(docsInfoConfig); + +export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/sol_trace_documentation.ts b/packages/website/ts/containers/sol_trace_documentation.ts new file mode 100644 index 000000000..c5286f1ff --- /dev/null +++ b/packages/website/ts/containers/sol_trace_documentation.ts @@ -0,0 +1,43 @@ +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; + +/* tslint:disable:no-var-requires */ +const IntroMarkdown = require('md/docs/sol_trace/introduction'); +const InstallationMarkdown = require('md/docs/sol_trace/installation'); +const UsageMarkdown = require('md/docs/sol_trace/usage'); +/* tslint:enable:no-var-requires */ + +const markdownSections = { + introduction: 'introduction', + installation: 'installation', + usage: 'usage', +}; + +const docsInfoConfig: DocsInfoConfig = { + id: DocPackages.SolTrace, + packageName: '@0x/sol-trace', + type: SupportedDocJson.TypeDoc, + displayName: 'Sol-trace', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/sol-trace', + markdownMenu: { + 'getting-started': [markdownSections.introduction, markdownSections.installation, markdownSections.usage], + }, + sectionNameToMarkdownByVersion: { + '1.0.0': { + [markdownSections.introduction]: IntroMarkdown, + [markdownSections.installation]: InstallationMarkdown, + [markdownSections.usage]: UsageMarkdown, + }, + }, + markdownSections, +}; +const mapStateToProps = getMapStateToProps(docsInfoConfig); + +export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( + DocPageComponent, +); diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts index 0e421777b..c7fb807a6 100644 --- a/packages/website/ts/containers/subproviders_documentation.ts +++ b/packages/website/ts/containers/subproviders_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/subproviders/1/introduction'); @@ -26,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/subproviders', type: SupportedDocJson.TypeDoc, displayName: 'Subproviders', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/subproviders', markdownMenu: { 'getting-started': [docSections.introduction, docSections.installation, docSections.ledgerNodeHid], }, @@ -44,31 +42,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections: docSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/web3_wrapper_documentation.ts b/packages/website/ts/containers/web3_wrapper_documentation.ts index 9c8c34621..ad286da1a 100644 --- a/packages/website/ts/containers/web3_wrapper_documentation.ts +++ b/packages/website/ts/containers/web3_wrapper_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdown1 = require('md/docs/web3_wrapper/1/introduction'); @@ -24,7 +22,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '@0x/web3-wrapper', type: SupportedDocJson.TypeDoc, displayName: 'Web3Wrapper', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/web3-wrapper', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], }, @@ -40,31 +38,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - translate: state.translate, - docsInfo, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/containers/zero_ex_js_documentation.ts b/packages/website/ts/containers/zero_ex_js_documentation.ts index e0ea6e275..5da8d5e52 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.ts +++ b/packages/website/ts/containers/zero_ex_js_documentation.ts @@ -1,12 +1,10 @@ -import { DocsInfo, DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; +import { DocsInfoConfig, SupportedDocJson } from '@0x/react-docs'; import * as React from 'react'; import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; import { DocPage as DocPageComponent, DocPageProps } from 'ts/pages/documentation/doc_page'; -import { Dispatcher } from 'ts/redux/dispatcher'; -import { State } from 'ts/redux/reducer'; -import { DocPackages, ScreenWidths } from 'ts/types'; -import { Translate } from 'ts/utils/translate'; +import { DocPackages } from 'ts/types'; + +import { getMapStateToProps, mapDispatchToProps } from '../utils/documentation_container'; /* tslint:disable:no-var-requires */ const IntroMarkdownV0 = require('md/docs/0xjs/0.0.1/introduction'); @@ -37,7 +35,7 @@ const docsInfoConfig: DocsInfoConfig = { packageName: '0x.js', type: SupportedDocJson.TypeDoc, displayName: '0x.js', - packageUrl: 'https://github.com/0xProject/0x-monorepo', + packageUrl: 'https://github.com/0xProject/0x-monorepo/packages/0x.js', markdownMenu: { 'getting-started': [ markdownSections.introduction, @@ -72,31 +70,7 @@ const docsInfoConfig: DocsInfoConfig = { }, markdownSections, }; -const docsInfo = new DocsInfo(docsInfoConfig); - -interface ConnectedState { - docsVersion: string; - availableDocVersions: string[]; - docsInfo: DocsInfo; - translate: Translate; - screenWidth: ScreenWidths; -} - -interface ConnectedDispatch { - dispatcher: Dispatcher; -} - -const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ - docsVersion: state.docsVersion, - availableDocVersions: state.availableDocVersions, - docsInfo, - translate: state.translate, - screenWidth: state.screenWidth, -}); - -const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ - dispatcher: new Dispatcher(dispatch), -}); +const mapStateToProps = getMapStateToProps(docsInfoConfig); export const Documentation: React.ComponentClass = connect(mapStateToProps, mapDispatchToProps)( DocPageComponent, diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index 45054772c..4ed66c572 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -69,8 +69,14 @@ const LazySolCompilerDocumentation = createLazyComponent('Documentation', async const LazyJSONSchemasDocumentation = createLazyComponent('Documentation', async () => import(/* webpackChunkName: "jsonSchemasDocs" */ 'ts/containers/json_schemas_documentation'), ); -const LazySolCovDocumentation = createLazyComponent('Documentation', async () => - import(/* webpackChunkName: "solCovDocs" */ 'ts/containers/sol_cov_documentation'), +const LazySolCoverageDocumentation = createLazyComponent('Documentation', async () => + import(/* webpackChunkName: "solCoverageDocs" */ 'ts/containers/sol_coverage_documentation'), +); +const LazySolTraceDocumentation = createLazyComponent('Documentation', async () => + import(/* webpackChunkName: "solTraceDocs" */ 'ts/containers/sol_trace_documentation'), +); +const LazySolProfilerDocumentation = createLazyComponent('Documentation', async () => + import(/* webpackChunkName: "solProfilerDocs" */ 'ts/containers/sol_profiler_documentation'), ); const LazySubprovidersDocumentation = createLazyComponent('Documentation', async () => import(/* webpackChunkName: "subproviderDocs" */ 'ts/containers/subproviders_documentation'), @@ -149,7 +155,18 @@ render( path={`${WebsitePaths.SolCompiler}/:version?`} component={LazySolCompilerDocumentation} /> - + + + = { }, { description: - 'A Solidity code coverage tool. Sol-cov uses transaction traces to figure out which lines of your code has been covered by your tests.', + 'A Solidity code coverage tool. Sol-coverage uses transaction traces to figure out which lines of your code has been covered by your tests.', link: { - title: '@0x/sol-cov', - to: WebsitePaths.SolCov, + title: '@0x/sol-coverage', + to: WebsitePaths.SolCoverage, + }, + }, + { + description: + 'A Solidity profiler. Sol-profiler uses transaction traces to figure out line-by-line gas consumption.', + link: { + title: '@0x/sol-profiler', + to: WebsitePaths.SolProfiler, + }, + }, + { + description: + 'A Solidity revert trace tool. Sol-trace prints human-readable revert trace whenever the revert happens.', + link: { + title: '@0x/sol-trace', + to: WebsitePaths.SolTrace, }, }, { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 37bd73063..50114e2d6 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -370,7 +370,9 @@ export enum WebsitePaths { OrderWatcher = '/docs/order-watcher', SolCompiler = '/docs/sol-compiler', JSONSchemas = '/docs/json-schemas', - SolCov = '/docs/sol-cov', + SolCoverage = '/docs/sol-coverage', + SolProfiler = '/docs/sol-profiler', + SolTrace = '/docs/sol-trace', Subproviders = '/docs/subproviders', OrderUtils = '/docs/order-utils', EthereumTypes = '/docs/ethereum-types', @@ -386,7 +388,9 @@ export enum DocPackages { Web3Wrapper = 'WEB3_WRAPPER', SolCompiler = 'SOL_COMPILER', JSONSchemas = 'JSON_SCHEMAS', - SolCov = 'SOL_COV', + SolCoverage = 'SOL_COVERAGE', + SolTrace = 'SOL_TRACE', + SolProfiler = 'SOL_PROFILER', Subproviders = 'SUBPROVIDERS', OrderUtils = 'ORDER_UTILS', EthereumTypes = 'ETHEREUM_TYPES', diff --git a/packages/website/ts/utils/documentation_container.ts b/packages/website/ts/utils/documentation_container.ts new file mode 100644 index 000000000..54e8a2c1a --- /dev/null +++ b/packages/website/ts/utils/documentation_container.ts @@ -0,0 +1,35 @@ +import { DocsInfo, DocsInfoConfig } from '@0x/react-docs'; +import { Dispatch } from 'redux'; +import { DocPageProps } from 'ts/pages/documentation/doc_page'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { ScreenWidths } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; + +export interface ConnectedState { + docsVersion: string; + availableDocVersions: string[]; + docsInfo: DocsInfo; + translate: Translate; + screenWidth: ScreenWidths; +} + +export interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +export const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const getMapStateToProps = (docsInfoConfig: DocsInfoConfig) => { + const docsInfo = new DocsInfo(docsInfoConfig); + const mapStateToProps = (state: State, _ownProps: DocPageProps): ConnectedState => ({ + docsVersion: state.docsVersion, + availableDocVersions: state.availableDocVersions, + translate: state.translate, + docsInfo, + screenWidth: state.screenWidth, + }); + return mapStateToProps; +}; diff --git a/python-packages/sra_client/README.md b/python-packages/sra_client/README.md index ab3939b41..abf0dee42 100644 --- a/python-packages/sra_client/README.md +++ b/python-packages/sra_client/README.md @@ -4,7 +4,7 @@ A Python client for interacting with servers conforming to [the Standard Relayer # Schemas -The [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x.js/tree/development/packages/json-schemas). Examples of each payload and response can be found in the 0x.js library's [test suite](https://github.com/0xProject/0x.js/blob/development/packages/json-schemas/test/schema_test.ts#L1). +The [JSON schemas](http://json-schema.org/) for the API payloads and responses can be found in [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas). Examples of each payload and response can be found in the 0x.js library's [test suite](https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/test/schema_test.ts#L1). # Pagination diff --git a/tsconfig.json b/tsconfig.json index 751115554..1f5da33dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -52,7 +52,10 @@ { "path": "./packages/react-docs" }, { "path": "./packages/react-shared" }, { "path": "./packages/sol-compiler" }, - { "path": "./packages/sol-cov" }, + { "path": "./packages/sol-coverage" }, + { "path": "./packages/sol-profiler" }, + { "path": "./packages/sol-trace" }, + { "path": "./packages/sol-trace-based-tools-common" }, { "path": "./packages/sol-resolver" }, { "path": "./packages/sra-spec" }, { "path": "./packages/subproviders" }, diff --git a/yarn.lock b/yarn.lock index b86969e3b..0a85e8f5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7771,19 +7771,10 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -graceful-fs@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - dependencies: - natives "^1.1.0" - -graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" +graceful-fs@4.1.15, graceful-fs@^3.0.0, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@~1.2.0: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -11124,10 +11115,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -natives@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.3.tgz#44a579be64507ea2d6ed1ca04a9415915cf75558" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" -- cgit v1.2.3 From f05d2906e8d99bd12a07208903450d62ccbb7c80 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 8 Jan 2019 16:00:05 +0100 Subject: Split installation tests in chunks of 10 to not run out of memory --- packages/monorepo-scripts/src/test_installation.ts | 73 ++++++++++++---------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index 5ae13b198..ed9b274e2 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -45,45 +45,50 @@ function logIfDefined(x: any): void { const IS_LOCAL_PUBLISH = process.env.IS_LOCAL_PUBLISH === 'true'; const registry = IS_LOCAL_PUBLISH ? 'http://localhost:4873/' : 'https://registry.npmjs.org/'; const monorepoRootPath = path.join(__dirname, '../../..'); - const packages = utils.getPackages(monorepoRootPath); + // We sort error messages according to package topology so that we can + // them in a more intuitive order. E.g. if package A has an error and + // package B imports it, the tests for both package A and package B will + // fail. But package B only fails because of an error in package A. + // Since the error in package A is the root cause, we log it first. + const packages = utils.getTopologicallySortedPackages(monorepoRootPath); const installablePackages = _.filter( packages, pkg => !pkg.packageJson.private && !_.isUndefined(pkg.packageJson.main) && pkg.packageJson.main.endsWith('.js'), ); - utils.log('Testing packages:'); - _.map(installablePackages, pkg => utils.log(`* ${pkg.packageJson.name}`)); - // Run all package tests asynchronously and push promises into an array so - // we can wait for all of them to resolve. - const promises: Array> = []; - const errors: PackageErr[] = []; - for (const installablePackage of installablePackages) { - const packagePromise = testInstallPackageAsync(monorepoRootPath, registry, installablePackage).catch(error => { - errors.push({ packageName: installablePackage.packageJson.name, error }); - }); - promises.push(packagePromise); - } - await Promise.all(promises); - if (errors.length > 0) { - // We sort error messages according to package topology so that we can - // them in a more intuitive order. E.g. if package A has an error and - // package B imports it, the tests for both package A and package B will - // fail. But package B only fails because of an error in package A. - // Since the error in package A is the root cause, we log it first. - const topologicallySortedPackages = utils.getTopologicallySortedPackages(monorepoRootPath); - const topologicallySortedErrors = _.sortBy(errors, packageErr => - findPackageIndex(topologicallySortedPackages, packageErr.packageName), - ); - _.forEach(topologicallySortedErrors, packageError => { - utils.log(`ERROR in package ${packageError.packageName}:`); - logIfDefined(packageError.error.message); - logIfDefined(packageError.error.stderr); - logIfDefined(packageError.error.stdout); - logIfDefined(packageError.error.stack); - }); - process.exit(1); - } else { - process.exit(0); + const CHUNK_SIZE = 10; + const chunkedInstallablePackages = _.chunk(installablePackages, CHUNK_SIZE); + utils.log(`Testing all packages in ${chunkedInstallablePackages.length} chunks`); + for (const installablePackagesChunk of chunkedInstallablePackages) { + utils.log('Testing packages:'); + _.map(installablePackagesChunk, pkg => utils.log(`* ${pkg.packageJson.name}`)); + // Run all package tests within that chunk asynchronously and push promises into an array so + // we can wait for all of them to resolve. + const promises: Array> = []; + const errors: PackageErr[] = []; + for (const installablePackage of installablePackagesChunk) { + const packagePromise = testInstallPackageAsync(monorepoRootPath, registry, installablePackage).catch( + error => { + errors.push({ packageName: installablePackage.packageJson.name, error }); + }, + ); + promises.push(packagePromise); + } + await Promise.all(promises); + if (errors.length > 0) { + const topologicallySortedErrors = _.sortBy(errors, packageErr => + findPackageIndex(packages, packageErr.packageName), + ); + _.forEach(topologicallySortedErrors, packageError => { + utils.log(`ERROR in package ${packageError.packageName}:`); + logIfDefined(packageError.error.message); + logIfDefined(packageError.error.stderr); + logIfDefined(packageError.error.stdout); + logIfDefined(packageError.error.stack); + }); + process.exit(1); + } } + process.exit(0); })().catch(err => { utils.log(`Unexpected error: ${err.message}`); process.exit(1); -- cgit v1.2.3 From 03248244ff8a7d8f276a86262f1b53e8dc9d5675 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Tue, 8 Jan 2019 16:16:02 +0100 Subject: Bump up the chunk size --- packages/monorepo-scripts/src/test_installation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index ed9b274e2..59647b836 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -55,7 +55,7 @@ function logIfDefined(x: any): void { packages, pkg => !pkg.packageJson.private && !_.isUndefined(pkg.packageJson.main) && pkg.packageJson.main.endsWith('.js'), ); - const CHUNK_SIZE = 10; + const CHUNK_SIZE = 15; const chunkedInstallablePackages = _.chunk(installablePackages, CHUNK_SIZE); utils.log(`Testing all packages in ${chunkedInstallablePackages.length} chunks`); for (const installablePackagesChunk of chunkedInstallablePackages) { -- cgit v1.2.3 From 420333e3c35252639bca7a987029d65ad8322290 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:11:23 +0100 Subject: Update packages/monorepo-scripts/src/test_installation.ts Co-Authored-By: LogvinovLeon --- packages/monorepo-scripts/src/test_installation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monorepo-scripts/src/test_installation.ts b/packages/monorepo-scripts/src/test_installation.ts index 59647b836..822f48967 100644 --- a/packages/monorepo-scripts/src/test_installation.ts +++ b/packages/monorepo-scripts/src/test_installation.ts @@ -45,7 +45,7 @@ function logIfDefined(x: any): void { const IS_LOCAL_PUBLISH = process.env.IS_LOCAL_PUBLISH === 'true'; const registry = IS_LOCAL_PUBLISH ? 'http://localhost:4873/' : 'https://registry.npmjs.org/'; const monorepoRootPath = path.join(__dirname, '../../..'); - // We sort error messages according to package topology so that we can + // We sort error messages according to package topology so that we can see // them in a more intuitive order. E.g. if package A has an error and // package B imports it, the tests for both package A and package B will // fail. But package B only fails because of an error in package A. -- cgit v1.2.3 From 60cdbbccae1f33555edf0b68e8f7af9c8048c513 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:11:43 +0100 Subject: Update packages/sol-coverage/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-coverage/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json index ca6048d99..938fa97a0 100644 --- a/packages/sol-coverage/CHANGELOG.json +++ b/packages/sol-coverage/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", "pr": 1492 } ] -- cgit v1.2.3 From 36504646f93882b43da8918b59dfe40d09bbbfd4 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:11:52 +0100 Subject: Update packages/sol-profiler/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-profiler/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index ca6048d99..938fa97a0 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", "pr": 1492 } ] -- cgit v1.2.3 From 052ed21e9ccc909d981ea61ff29fc845bd976dc0 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:12:08 +0100 Subject: Update packages/sol-trace-based-tools-common/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-trace-based-tools-common/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.json b/packages/sol-trace-based-tools-common/CHANGELOG.json index 4c56d547d..caccb3fff 100644 --- a/packages/sol-trace-based-tools-common/CHANGELOG.json +++ b/packages/sol-trace-based-tools-common/CHANGELOG.json @@ -3,7 +3,7 @@ "version": "3.0.0", "changes": [ { - "note": "Move out specific tools and leave just the common part", + "note": "Move out specific tools and leave just the shared parts of the codebase", "pr": 1492 } ] -- cgit v1.2.3 From d7df402c42f7964bd31a765f5ec92a40179b69d7 Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:15:19 +0100 Subject: Update packages/sol-trace-based-tools-common/src/source_maps.ts Co-Authored-By: LogvinovLeon --- packages/sol-trace-based-tools-common/src/source_maps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-trace-based-tools-common/src/source_maps.ts b/packages/sol-trace-based-tools-common/src/source_maps.ts index 7191fb712..af0fb4035 100644 --- a/packages/sol-trace-based-tools-common/src/source_maps.ts +++ b/packages/sol-trace-based-tools-common/src/source_maps.ts @@ -12,7 +12,7 @@ export interface SourceLocation { } /** - * Receives a string with newlines and returns a hash of byte offset to LineColumn + * 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 { -- cgit v1.2.3 From 03dea585b4e0af9b3272d017e4832d4dd03dbbfe Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:15:26 +0100 Subject: Update packages/sol-trace/CHANGELOG.json Co-Authored-By: LogvinovLeon --- packages/sol-trace/CHANGELOG.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json index ca6048d99..938fa97a0 100644 --- a/packages/sol-trace/CHANGELOG.json +++ b/packages/sol-trace/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", "pr": 1492 } ] -- cgit v1.2.3 From 15c9479ebeca57e7c275cd2e73ca3daad03a412f Mon Sep 17 00:00:00 2001 From: Fabio B Date: Thu, 10 Jan 2019 11:15:34 +0100 Subject: Update packages/sol-trace/README.md Co-Authored-By: LogvinovLeon --- packages/sol-trace/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-trace/README.md b/packages/sol-trace/README.md index dba193426..86ca2cbd6 100644 --- a/packages/sol-trace/README.md +++ b/packages/sol-trace/README.md @@ -1,6 +1,6 @@ ## @0x/sol-trace -Prints code traces when revert happens. +Prints a stack trace when a revert is encountered. ### Read the [Documentation](https://0xproject.com/docs/sol-trace). -- cgit v1.2.3 From 7ae9e79235ed3b7eb110b0a1e88338b3965f44da Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 11:21:05 +0100 Subject: Rename sol-trace-based-tools-common to sol-tracing-utils --- .circleci/config.yml | 8 +- .github/autolabeler.yml | 2 +- .gitignore | 2 +- CODEOWNERS | 2 +- packages/sol-coverage/CHANGELOG.json | 2 +- packages/sol-coverage/package.json | 2 +- packages/sol-coverage/src/coverage_subprovider.ts | 2 +- packages/sol-coverage/src/index.ts | 2 +- packages/sol-profiler/CHANGELOG.json | 2 +- packages/sol-profiler/package.json | 2 +- packages/sol-profiler/src/index.ts | 2 +- packages/sol-profiler/src/profiler_subprovider.ts | 2 +- packages/sol-trace-based-tools-common/.npmignore | 6 - .../sol-trace-based-tools-common/CHANGELOG.json | 398 -------------------- packages/sol-trace-based-tools-common/CHANGELOG.md | 158 -------- packages/sol-trace-based-tools-common/README.md | 61 --- .../sol-trace-based-tools-common/compiler.json | 18 - .../sol-trace-based-tools-common/coverage/.gitkeep | 0 packages/sol-trace-based-tools-common/package.json | 87 ----- .../artifact_adapters/abstract_artifact_adapter.ts | 5 - .../sol_compiler_artifact_adapter.ts | 61 --- .../artifact_adapters/truffle_artifact_adapter.ts | 88 ----- .../src/ast_visitor.ts | 168 --------- .../src/collect_coverage_entries.ts | 41 -- .../sol-trace-based-tools-common/src/constants.ts | 8 - .../src/get_source_range_snippet.ts | 185 --------- .../sol-trace-based-tools-common/src/globals.d.ts | 7 - packages/sol-trace-based-tools-common/src/index.ts | 39 -- .../src/instructions.ts | 23 -- .../src/revert_trace.ts | 95 ----- .../src/source_maps.ts | 91 ----- packages/sol-trace-based-tools-common/src/trace.ts | 104 ------ .../src/trace_collection_subprovider.ts | 188 ---------- .../src/trace_collector.ts | 93 ----- .../src/trace_info_subprovider.ts | 59 --- packages/sol-trace-based-tools-common/src/types.ts | 126 ------- packages/sol-trace-based-tools-common/src/utils.ts | 87 ----- .../test/collect_coverage_entries_test.ts | 155 -------- .../fixtures/contracts/AllSolidityFeatures.sol | 413 --------------------- .../test/fixtures/contracts/SimpleStorage.sol | 11 - .../test/fixtures/contracts/Simplest.sol | 2 - .../test/fixtures/contracts/SolcovIgnore.sol | 22 -- .../test/instructions_test.ts | 19 - .../test/sol_compiler_artifact_adapter_test.ts | 29 -- .../test/source_maps_test.ts | 71 ---- .../test/trace_test.ts | 55 --- .../test/utils_test.ts | 53 --- .../sol-trace-based-tools-common/tsconfig.json | 8 - packages/sol-trace-based-tools-common/tslint.json | 3 - packages/sol-trace/CHANGELOG.json | 2 +- packages/sol-trace/package.json | 2 +- packages/sol-trace/src/index.ts | 2 +- packages/sol-trace/src/revert_trace_subprovider.ts | 2 +- packages/sol-tracing-utils/.npmignore | 6 + packages/sol-tracing-utils/CHANGELOG.json | 398 ++++++++++++++++++++ packages/sol-tracing-utils/CHANGELOG.md | 158 ++++++++ packages/sol-tracing-utils/README.md | 61 +++ packages/sol-tracing-utils/compiler.json | 18 + packages/sol-tracing-utils/package.json | 87 +++++ .../artifact_adapters/abstract_artifact_adapter.ts | 5 + .../sol_compiler_artifact_adapter.ts | 61 +++ .../artifact_adapters/truffle_artifact_adapter.ts | 88 +++++ packages/sol-tracing-utils/src/ast_visitor.ts | 168 +++++++++ .../src/collect_coverage_entries.ts | 41 ++ packages/sol-tracing-utils/src/constants.ts | 8 + .../src/get_source_range_snippet.ts | 185 +++++++++ packages/sol-tracing-utils/src/globals.d.ts | 7 + packages/sol-tracing-utils/src/index.ts | 39 ++ packages/sol-tracing-utils/src/instructions.ts | 23 ++ packages/sol-tracing-utils/src/revert_trace.ts | 95 +++++ packages/sol-tracing-utils/src/source_maps.ts | 91 +++++ packages/sol-tracing-utils/src/trace.ts | 104 ++++++ .../src/trace_collection_subprovider.ts | 188 ++++++++++ packages/sol-tracing-utils/src/trace_collector.ts | 93 +++++ .../src/trace_info_subprovider.ts | 59 +++ packages/sol-tracing-utils/src/types.ts | 126 +++++++ packages/sol-tracing-utils/src/utils.ts | 87 +++++ .../test/collect_coverage_entries_test.ts | 155 ++++++++ .../fixtures/contracts/AllSolidityFeatures.sol | 413 +++++++++++++++++++++ .../test/fixtures/contracts/SimpleStorage.sol | 11 + .../test/fixtures/contracts/Simplest.sol | 2 + .../test/fixtures/contracts/SolcovIgnore.sol | 22 ++ .../sol-tracing-utils/test/instructions_test.ts | 19 + .../test/sol_compiler_artifact_adapter_test.ts | 29 ++ .../sol-tracing-utils/test/source_maps_test.ts | 71 ++++ packages/sol-tracing-utils/test/trace_test.ts | 55 +++ packages/sol-tracing-utils/test/utils_test.ts | 53 +++ packages/sol-tracing-utils/tsconfig.json | 8 + packages/sol-tracing-utils/tslint.json | 3 + tsconfig.json | 2 +- 90 files changed, 3057 insertions(+), 3057 deletions(-) delete mode 100644 packages/sol-trace-based-tools-common/.npmignore delete mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.json delete mode 100644 packages/sol-trace-based-tools-common/CHANGELOG.md delete mode 100644 packages/sol-trace-based-tools-common/README.md delete mode 100644 packages/sol-trace-based-tools-common/compiler.json delete mode 100644 packages/sol-trace-based-tools-common/coverage/.gitkeep delete mode 100644 packages/sol-trace-based-tools-common/package.json delete mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts delete mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts delete mode 100644 packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts delete mode 100644 packages/sol-trace-based-tools-common/src/ast_visitor.ts delete mode 100644 packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts delete mode 100644 packages/sol-trace-based-tools-common/src/constants.ts delete mode 100644 packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts delete mode 100644 packages/sol-trace-based-tools-common/src/globals.d.ts delete mode 100644 packages/sol-trace-based-tools-common/src/index.ts delete mode 100644 packages/sol-trace-based-tools-common/src/instructions.ts delete mode 100644 packages/sol-trace-based-tools-common/src/revert_trace.ts delete mode 100644 packages/sol-trace-based-tools-common/src/source_maps.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace_collector.ts delete mode 100644 packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts delete mode 100644 packages/sol-trace-based-tools-common/src/types.ts delete mode 100644 packages/sol-trace-based-tools-common/src/utils.ts delete mode 100644 packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol delete mode 100644 packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol delete mode 100644 packages/sol-trace-based-tools-common/test/instructions_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/source_maps_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/trace_test.ts delete mode 100644 packages/sol-trace-based-tools-common/test/utils_test.ts delete mode 100644 packages/sol-trace-based-tools-common/tsconfig.json delete mode 100644 packages/sol-trace-based-tools-common/tslint.json create mode 100644 packages/sol-tracing-utils/.npmignore create mode 100644 packages/sol-tracing-utils/CHANGELOG.json create mode 100644 packages/sol-tracing-utils/CHANGELOG.md create mode 100644 packages/sol-tracing-utils/README.md create mode 100644 packages/sol-tracing-utils/compiler.json create mode 100644 packages/sol-tracing-utils/package.json create mode 100644 packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts create mode 100644 packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts create mode 100644 packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts create mode 100644 packages/sol-tracing-utils/src/ast_visitor.ts create mode 100644 packages/sol-tracing-utils/src/collect_coverage_entries.ts create mode 100644 packages/sol-tracing-utils/src/constants.ts create mode 100644 packages/sol-tracing-utils/src/get_source_range_snippet.ts create mode 100644 packages/sol-tracing-utils/src/globals.d.ts create mode 100644 packages/sol-tracing-utils/src/index.ts create mode 100644 packages/sol-tracing-utils/src/instructions.ts create mode 100644 packages/sol-tracing-utils/src/revert_trace.ts create mode 100644 packages/sol-tracing-utils/src/source_maps.ts create mode 100644 packages/sol-tracing-utils/src/trace.ts create mode 100644 packages/sol-tracing-utils/src/trace_collection_subprovider.ts create mode 100644 packages/sol-tracing-utils/src/trace_collector.ts create mode 100644 packages/sol-tracing-utils/src/trace_info_subprovider.ts create mode 100644 packages/sol-tracing-utils/src/types.ts create mode 100644 packages/sol-tracing-utils/src/utils.ts create mode 100644 packages/sol-tracing-utils/test/collect_coverage_entries_test.ts create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol create mode 100644 packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol create mode 100644 packages/sol-tracing-utils/test/instructions_test.ts create mode 100644 packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts create mode 100644 packages/sol-tracing-utils/test/source_maps_test.ts create mode 100644 packages/sol-tracing-utils/test/trace_test.ts create mode 100644 packages/sol-tracing-utils/test/utils_test.ts create mode 100644 packages/sol-tracing-utils/tsconfig.json create mode 100644 packages/sol-tracing-utils/tslint.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e5a2809c..fb1aacdd9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -118,7 +118,7 @@ jobs: - run: yarn wsrun test:circleci @0x/order-utils - run: yarn wsrun test:circleci @0x/order-watcher - run: yarn wsrun test:circleci @0x/sol-compiler - - run: yarn wsrun test:circleci @0x/sol-trace-based-tools-common + - run: yarn wsrun test:circleci @0x/sol-tracing-utils - run: yarn wsrun test:circleci @0x/sol-doc - run: yarn wsrun test:circleci @0x/subproviders - run: yarn wsrun test:circleci @0x/web3-wrapper @@ -169,9 +169,9 @@ jobs: paths: - ~/repo/packages/sol-compiler/coverage/lcov.info - save_cache: - key: coverage-sol-trace-based-tools-common-{{ .Environment.CIRCLE_SHA1 }} + key: coverage-sol-tracing-utils-{{ .Environment.CIRCLE_SHA1 }} paths: - - ~/repo/packages/sol-trace-based-tools-common/coverage/lcov.info + - ~/repo/packages/sol-tracing-utils/coverage/lcov.info - save_cache: key: coverage-sol-doc-{{ .Environment.CIRCLE_SHA1 }} paths: @@ -342,7 +342,7 @@ jobs: - coverage-sol-compiler-{{ .Environment.CIRCLE_SHA1 }} - restore_cache: keys: - - coverage-sol-trace-based-tools-common-{{ .Environment.CIRCLE_SHA1 }} + - coverage-sol-tracing-utils-{{ .Environment.CIRCLE_SHA1 }} - restore_cache: keys: - coverage-sol-doc-{{ .Environment.CIRCLE_SHA1 }} diff --git a/.github/autolabeler.yml b/.github/autolabeler.yml index ecdc0e89b..321ec18a4 100644 --- a/.github/autolabeler.yml +++ b/.github/autolabeler.yml @@ -16,7 +16,7 @@ website: ['packages/website'] sol-coverage: ['packages/sol-coverage'] sol-profiler: ['packages/sol-profiler'] sol-trace: ['packages/sol-trace'] -sol-trace-based-tools-common: ['packages/sol-trace-based-tools-common'] +sol-tracing-utils: ['packages/sol-tracing-utils'] utils: ['packages/utils'] tslint-config: ['packages/tslint-config'] asset-buyer: ['packages/asset-buyer'] diff --git a/.gitignore b/.gitignore index c2100ea62..0210edc4f 100644 --- a/.gitignore +++ b/.gitignore @@ -91,7 +91,7 @@ contracts/interfaces/generated-artifacts/ contracts/tokens/generated-artifacts/ contracts/examples/generated-artifacts/ contracts/extensions/generated-artifacts/ -packages/sol-trace-based-tools-common/test/fixtures/artifacts/ +packages/sol-tracing-utils/test/fixtures/artifacts/ packages/metacoin/artifacts/ # generated contract wrappers diff --git a/CODEOWNERS b/CODEOWNERS index f57da320f..c73a206df 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -27,7 +27,7 @@ packages/sol-compiler/ @LogvinovLeon packages/sol-coverage/ @LogvinovLeon packages/sol-profiler/ @LogvinovLeon packages/sol-trace/ @LogvinovLeon -packages/sol-trace-based-tools-common/ @LogvinovLeon +packages/sol-tracing-utils/ @LogvinovLeon packages/sol-resolver/ @LogvinovLeon packages/subproviders/ @fabioberger @dekz packages/verdaccio/ @albrow diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json index 938fa97a0..c650a4a4b 100644 --- a/packages/sol-coverage/CHANGELOG.json +++ b/packages/sol-coverage/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-coverage/package.json b/packages/sol-coverage/package.json index b95fa5502..7dc764efd 100644 --- a/packages/sol-coverage/package.json +++ b/packages/sol-coverage/package.json @@ -30,7 +30,7 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-coverage/README.md", "dependencies": { "@0x/subproviders": "^2.1.8", - "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/sol-tracing-utils": "^2.1.16", "@0x/typescript-typings": "^3.0.6", "ethereum-types": "^1.1.4", "lodash": "^4.17.5" diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts index 255fa9481..e6b546c4a 100644 --- a/packages/sol-coverage/src/coverage_subprovider.ts +++ b/packages/sol-coverage/src/coverage_subprovider.ts @@ -15,7 +15,7 @@ import { TraceInfo, TraceInfoSubprovider, utils, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; import * as _ from 'lodash'; /** diff --git a/packages/sol-coverage/src/index.ts b/packages/sol-coverage/src/index.ts index dcf97b72e..97b4ecee6 100644 --- a/packages/sol-coverage/src/index.ts +++ b/packages/sol-coverage/src/index.ts @@ -4,7 +4,7 @@ export { TruffleArtifactAdapter, AbstractArtifactAdapter, ContractData, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; export { JSONRPCRequestPayload, diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index 938fa97a0..c650a4a4b 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-profiler/package.json b/packages/sol-profiler/package.json index 03b421b0e..b85c82ed2 100644 --- a/packages/sol-profiler/package.json +++ b/packages/sol-profiler/package.json @@ -31,7 +31,7 @@ "dependencies": { "@0x/subproviders": "^2.1.8", "@0x/typescript-typings": "^3.0.6", - "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/sol-tracing-utils": "^2.1.16", "ethereum-types": "^1.1.4", "lodash": "^4.17.5" }, diff --git a/packages/sol-profiler/src/index.ts b/packages/sol-profiler/src/index.ts index b36ed5baa..5d4806be4 100644 --- a/packages/sol-profiler/src/index.ts +++ b/packages/sol-profiler/src/index.ts @@ -3,7 +3,7 @@ export { SolCompilerArtifactAdapter, TruffleArtifactAdapter, ContractData, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; // HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready export { ProfilerSubprovider } from './profiler_subprovider'; diff --git a/packages/sol-profiler/src/profiler_subprovider.ts b/packages/sol-profiler/src/profiler_subprovider.ts index 61dc25b6b..c3ed13ea5 100644 --- a/packages/sol-profiler/src/profiler_subprovider.ts +++ b/packages/sol-profiler/src/profiler_subprovider.ts @@ -12,7 +12,7 @@ import { TraceInfo, TraceInfoSubprovider, utils, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; /** * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. diff --git a/packages/sol-trace-based-tools-common/.npmignore b/packages/sol-trace-based-tools-common/.npmignore deleted file mode 100644 index 037786e46..000000000 --- a/packages/sol-trace-based-tools-common/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -.* -yarn-error.log -/src/ -/scripts/ -tsconfig.json -/lib/src/monorepo_scripts/ diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.json b/packages/sol-trace-based-tools-common/CHANGELOG.json deleted file mode 100644 index caccb3fff..000000000 --- a/packages/sol-trace-based-tools-common/CHANGELOG.json +++ /dev/null @@ -1,398 +0,0 @@ -[ - { - "version": "3.0.0", - "changes": [ - { - "note": "Move out specific tools and leave just the shared parts of the codebase", - "pr": 1492 - } - ] - }, - { - "version": "2.1.16", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544739608 - }, - { - "version": "2.1.15", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1544570656 - }, - { - "timestamp": 1543401373, - "version": "2.1.14", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542821676, - "version": "2.1.13", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542208198, - "version": "2.1.12", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542134075, - "version": "2.1.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1542028948, - "version": "2.1.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.9", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1541740904 - }, - { - "version": "2.1.8", - "changes": [ - { - "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", - "pr": 1105 - } - ], - "timestamp": 1539871071 - }, - { - "version": "2.1.7", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1538693146 - }, - { - "timestamp": 1538157789, - "version": "2.1.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537907159, - "version": "2.1.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537875740, - "version": "2.1.4", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1537541580, - "version": "2.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1536142250, - "version": "2.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1535377027, - "version": "2.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.1.0", - "changes": [ - { - "note": - "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", - "pr": 924 - } - ], - "timestamp": 1535133899 - }, - { - "version": "2.0.0", - "changes": [ - { - "note": - "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", - "pr": 938 - }, - { - "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", - "pr": 938 - }, - { - "note": - "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", - "pr": 938 - }, - { - "note": - "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", - "pr": 938 - } - ], - "timestamp": 1534210131 - }, - { - "version": "1.0.3", - "changes": [ - { - "note": "Dependencies updated" - } - ], - "timestamp": 1532619515 - }, - { - "timestamp": 1532605697, - "version": "1.0.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532357734, - "version": "1.0.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1532043000, - "version": "1.0.0", - "changes": [ - { - "note": - "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", - "pr": 589 - }, - { - "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", - "pr": 589 - }, - { - "note": "Properly parse multi-level traces", - "pr": 589 - }, - { - "note": "Add support for solidity libraries", - "pr": 589 - }, - { - "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", - "pr": 675 - }, - { - "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", - "pr": 675 - }, - { - "note": "Fixed a bug when the tool crashed on empty traces", - "pr": 675 - }, - { - "note": "Use `BlockchainLifecycle` to support reverts on Geth", - "pr": 675 - }, - { - "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", - "pr": 675 - }, - { - "note": "Collect traces from `estimate_gas` calls", - "pr": 675 - }, - { - "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", - "pr": 675 - }, - { - "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", - "pr": 675 - }, - { - "note": "Skip interface artifacts with a warning instead of failing", - "pr": 675 - }, - { - "note": "Fix `solcVersion` regex in parameter validation", - "pr": 690 - }, - { - "note": - "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", - "pr": 690 - }, - { - "note": "HUGE perf improvements", - "pr": 690 - }, - { - "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", - "pr": 705 - }, - { - "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", - "pr": 725 - } - ] - }, - { - "timestamp": 1531919263, - "version": "0.1.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1531149657, - "version": "0.1.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1529397769, - "version": "0.1.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "0.1.0", - "changes": [ - { - "note": "Incorrect publish that was unpublished" - } - ], - "timestamp": 1527810075 - }, - { - "timestamp": 1527009134, - "version": "0.0.11", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525477860, - "version": "0.0.10", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1525428773, - "version": "0.0.9", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1524044013, - "version": "0.0.8", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1523462196, - "version": "0.0.7", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522673609, - "version": "0.0.6", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1522658513, - "version": "0.0.5", - "changes": [ - { - "note": "Dependencies updated" - } - ] - } -] diff --git a/packages/sol-trace-based-tools-common/CHANGELOG.md b/packages/sol-trace-based-tools-common/CHANGELOG.md deleted file mode 100644 index 879ef9c95..000000000 --- a/packages/sol-trace-based-tools-common/CHANGELOG.md +++ /dev/null @@ -1,158 +0,0 @@ - - -CHANGELOG - -## v2.1.16 - _December 13, 2018_ - - * Dependencies updated - -## v2.1.15 - _December 11, 2018_ - - * Dependencies updated - -## v2.1.14 - _November 28, 2018_ - - * Dependencies updated - -## v2.1.13 - _November 21, 2018_ - - * Dependencies updated - -## v2.1.12 - _November 14, 2018_ - - * Dependencies updated - -## v2.1.11 - _November 13, 2018_ - - * Dependencies updated - -## v2.1.10 - _November 12, 2018_ - - * Dependencies updated - -## v2.1.9 - _November 9, 2018_ - - * Dependencies updated - -## v2.1.8 - _October 18, 2018_ - - * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) - -## v2.1.7 - _October 4, 2018_ - - * Dependencies updated - -## v2.1.6 - _September 28, 2018_ - - * Dependencies updated - -## v2.1.5 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.4 - _September 25, 2018_ - - * Dependencies updated - -## v2.1.3 - _September 21, 2018_ - - * Dependencies updated - -## v2.1.2 - _September 5, 2018_ - - * Dependencies updated - -## v2.1.1 - _August 27, 2018_ - - * Dependencies updated - -## v2.1.0 - _August 24, 2018_ - - * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) - -## v2.0.0 - _August 14, 2018_ - - * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) - * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) - * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) - * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) - -## v1.0.3 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.2 - _July 26, 2018_ - - * Dependencies updated - -## v1.0.1 - _July 23, 2018_ - - * Dependencies updated - -## v1.0.0 - _July 19, 2018_ - - * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) - * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) - * Properly parse multi-level traces (#589) - * Add support for solidity libraries (#589) - * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) - * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) - * Fixed a bug when the tool crashed on empty traces (#675) - * Use `BlockchainLifecycle` to support reverts on Geth (#675) - * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) - * Collect traces from `estimate_gas` calls (#675) - * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) - * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) - * Skip interface artifacts with a warning instead of failing (#675) - * Fix `solcVersion` regex in parameter validation (#690) - * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) - * HUGE perf improvements (#690) - * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) - * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) - -## v0.1.3 - _July 18, 2018_ - - * Dependencies updated - -## v0.1.2 - _July 9, 2018_ - - * Dependencies updated - -## v0.1.1 - _June 19, 2018_ - - * Dependencies updated - -## v0.1.0 - _May 31, 2018_ - - * Incorrect publish that was unpublished - -## v0.0.11 - _May 22, 2018_ - - * Dependencies updated - -## v0.0.10 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.9 - _May 4, 2018_ - - * Dependencies updated - -## v0.0.8 - _April 18, 2018_ - - * Dependencies updated - -## v0.0.7 - _April 11, 2018_ - - * Dependencies updated - -## v0.0.6 - _April 2, 2018_ - - * Dependencies updated - -## v0.0.5 - _April 2, 2018_ - - * Dependencies updated diff --git a/packages/sol-trace-based-tools-common/README.md b/packages/sol-trace-based-tools-common/README.md deleted file mode 100644 index b6d75dd59..000000000 --- a/packages/sol-trace-based-tools-common/README.md +++ /dev/null @@ -1,61 +0,0 @@ -## @0x/sol-trace-based-tools-common - -Common code for all solidity trace-based tools (sol-coverage, sol-profiler, sol-trace). - -## Installation - -```bash -yarn add @0x/sol-sol-trace-based-tools-common -``` - -## 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-based-tools-common yarn build -``` - -Or continuously rebuild on change: - -```bash -PKG=@0x/sol-trace-based-tools-common yarn watch -``` - -### Clean - -```bash -yarn clean -``` - -### Lint - -```bash -yarn lint -``` - -### Run Tests - -```bash -yarn test -``` diff --git a/packages/sol-trace-based-tools-common/compiler.json b/packages/sol-trace-based-tools-common/compiler.json deleted file mode 100644 index a6a0c6d3a..000000000 --- a/packages/sol-trace-based-tools-common/compiler.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "contracts": ["SimpleStorage"], - "contractsDir": "test/fixtures/contracts", - "artifactsDir": "test/fixtures/artifacts", - "compilerSettings": { - "outputSelection": { - "*": { - "*": [ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap" - ] - } - } - } -} diff --git a/packages/sol-trace-based-tools-common/coverage/.gitkeep b/packages/sol-trace-based-tools-common/coverage/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/sol-trace-based-tools-common/package.json b/packages/sol-trace-based-tools-common/package.json deleted file mode 100644 index 6cfabe6d9..000000000 --- a/packages/sol-trace-based-tools-common/package.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "name": "@0x/sol-trace-based-tools-common", - "version": "2.1.16", - "engines": { - "node": ">=6.12" - }, - "description": "Common part of trace based solidity tools (sol-coverage, sol-trace, sol-profiler)", - "main": "lib/src/index.js", - "types": "lib/src/index.d.ts", - "scripts": { - "build": "yarn pre_build && tsc -b", - "build:ci": "yarn build", - "pre_build": "run-s copy_test_fixtures", - "lint": "tslint --format stylish --project .", - "test": "run-s compile_test run_mocha", - "rebuild_and_test": "run-s clean build test", - "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", - "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", - "test:circleci": "yarn test:coverage", - "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", - "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", - "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", - "compile_test": "sol-compiler compile", - "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" - }, - "config": { - "postpublish": { - "assets": [], - "docOmitExports": [ - "ProfilerSubprovider", - "RevertTraceSubprovider" - ] - } - }, - "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-based-tools-common/README.md", - "dependencies": { - "@0x/dev-utils": "^1.0.21", - "@0x/sol-compiler": "^1.1.16", - "@0x/subproviders": "^2.1.8", - "@0x/typescript-typings": "^3.0.6", - "@0x/utils": "^2.0.8", - "@0x/web3-wrapper": "^3.2.1", - "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.4", - "ethereumjs-util": "^5.1.1", - "glob": "^7.1.2", - "istanbul": "^0.4.5", - "lodash": "^4.17.5", - "loglevel": "^1.6.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.2", - "semaphore-async-await": "^1.5.1", - "solidity-parser-antlr": "^0.2.12" - }, - "devDependencies": { - "@0x/tslint-config": "^2.0.0", - "@types/istanbul": "^0.4.30", - "@types/loglevel": "^1.5.3", - "@types/mkdirp": "^0.5.1", - "@types/mocha": "^2.2.42", - "@types/node": "*", - "@types/rimraf": "^2.0.2", - "chai": "^4.0.1", - "copyfiles": "^2.0.0", - "dirty-chai": "^2.0.1", - "make-promises-safe": "^1.1.0", - "mocha": "^4.1.0", - "npm-run-all": "^4.1.2", - "nyc": "^11.0.1", - "shx": "^0.2.2", - "sinon": "^4.0.0", - "tslint": "5.11.0", - "typedoc": "0.13.0", - "typescript": "3.0.1" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts deleted file mode 100644 index fcc6562ad..000000000 --- a/packages/sol-trace-based-tools-common/src/artifact_adapters/abstract_artifact_adapter.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContractData } from '../types'; - -export abstract class AbstractArtifactAdapter { - public abstract async collectContractsDataAsync(): Promise; -} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts deleted file mode 100644 index 57391abbe..000000000 --- a/packages/sol-trace-based-tools-common/src/artifact_adapters/sol_compiler_artifact_adapter.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { CompilerOptions, ContractArtifact } from 'ethereum-types'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as _ from 'lodash'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; - -const CONFIG_FILE = 'compiler.json'; - -export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { - private readonly _artifactsPath: string; - private readonly _sourcesPath: string; - /** - * Instantiates a SolCompilerArtifactAdapter - * @param artifactsPath Path to your artifacts directory - * @param sourcesPath Path to your contract sources directory - */ - constructor(artifactsPath?: string, sourcesPath?: string) { - super(); - const config: CompilerOptions = fs.existsSync(CONFIG_FILE) - ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) - : {}; - if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { - throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); - } - this._artifactsPath = (artifactsPath || config.artifactsDir) as string; - if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { - throw new Error(`contractsDir not found in ${CONFIG_FILE}`); - } - this._sourcesPath = (sourcesPath || config.contractsDir) as string; - } - public async collectContractsDataAsync(): Promise { - const artifactsGlob = `${this._artifactsPath}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - const contractsData: ContractData[] = []; - for (const artifactFileName of artifactFileNames) { - const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - if (_.isUndefined(artifact.compilerOutput.evm)) { - 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 contractData = { - sourceCodes, - sources, - bytecode: artifact.compilerOutput.evm.bytecode.object, - sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, - runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, - sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, - }; - contractsData.push(contractData); - } - return contractsData; - } -} diff --git a/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts deleted file mode 100644 index bb2b15153..000000000 --- a/packages/sol-trace-based-tools-common/src/artifact_adapters/truffle_artifact_adapter.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Compiler, CompilerOptions } from '@0x/sol-compiler'; -import * as fs from 'fs'; -import * as glob from 'glob'; -import * as path from 'path'; - -import { ContractData } from '../types'; - -import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; -import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; - -const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; - -interface TruffleConfig { - solc?: any; - contracts_build_directory?: string; -} - -export class TruffleArtifactAdapter extends AbstractArtifactAdapter { - private readonly _solcVersion: string; - private readonly _projectRoot: string; - /** - * Instantiates a TruffleArtifactAdapter - * @param projectRoot Path to the truffle project's root directory - * @param solcVersion Solidity version with which to compile all the contracts - */ - constructor(projectRoot: string, solcVersion: string) { - super(); - this._solcVersion = solcVersion; - this._projectRoot = projectRoot; - } - public async collectContractsDataAsync(): Promise { - const artifactsDir = '.0x-artifacts'; - const contractsDir = path.join(this._projectRoot, 'contracts'); - const truffleConfig = this._getTruffleConfig(); - const solcConfig = truffleConfig.solc || {}; - const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; - this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); - const compilerOptions: CompilerOptions = { - contractsDir, - artifactsDir, - compilerSettings: { - ...solcConfig, - outputSelection: { - ['*']: { - ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], - }, - }, - }, - contracts: '*', - solcVersion: this._solcVersion, - }; - const compiler = new Compiler(compilerOptions); - await compiler.compileAsync(); - const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); - const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); - return contractsDataFrom0xArtifacts; - } - private _getTruffleConfig(): TruffleConfig { - const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); - const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); - if (fs.existsSync(truffleConfigFileShort)) { - const truffleConfig = require(truffleConfigFileShort); - return truffleConfig; - } else if (fs.existsSync(truffleConfigFileLong)) { - const truffleConfig = require(truffleConfigFileLong); - return truffleConfig; - } else { - throw new Error( - `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, - ); - } - } - private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { - const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; - const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); - for (const artifactFileName of artifactFileNames) { - const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); - const compilerVersion = artifact.compiler.version; - if (!compilerVersion.startsWith(this._solcVersion)) { - throw new Error( - `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ - this._solcVersion - } making it impossible to process traces`, - ); - } - } - } -} diff --git a/packages/sol-trace-based-tools-common/src/ast_visitor.ts b/packages/sol-trace-based-tools-common/src/ast_visitor.ts deleted file mode 100644 index e55cdf6ec..000000000 --- a/packages/sol-trace-based-tools-common/src/ast_visitor.ts +++ /dev/null @@ -1,168 +0,0 @@ -import * as _ from 'lodash'; -import * as Parser from 'solidity-parser-antlr'; - -import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types'; - -export interface CoverageEntriesDescription { - fnMap: FnMap; - branchMap: BranchMap; - statementMap: StatementMap; - modifiersStatementIds: number[]; -} - -enum BranchType { - If = 'if', - ConditionalExpression = 'cond-expr', - BinaryExpression = 'binary-expr', -} - -export class ASTVisitor { - private _entryId = 0; - private readonly _fnMap: FnMap = {}; - private readonly _branchMap: BranchMap = {}; - private readonly _modifiersStatementIds: number[] = []; - private readonly _statementMap: StatementMap = {}; - private readonly _locationByOffset: LocationByOffset; - 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; - this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; - } - public getCollectedCoverageEntries(): CoverageEntriesDescription { - const coverageEntriesDescription = { - fnMap: this._fnMap, - branchMap: this._branchMap, - statementMap: this._statementMap, - modifiersStatementIds: this._modifiersStatementIds, - }; - return coverageEntriesDescription; - } - public IfStatement(ast: Parser.IfStatement): void { - this._visitStatement(ast); - this._visitBinaryBranch(ast, ast.trueBody, ast.falseBody || ast, BranchType.If); - } - public FunctionDefinition(ast: Parser.FunctionDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ContractDefinition(ast: Parser.ContractDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - } - } - public ModifierDefinition(ast: Parser.ModifierDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ForStatement(ast: Parser.ForStatement): void { - this._visitStatement(ast); - } - public ReturnStatement(ast: Parser.ReturnStatement): void { - this._visitStatement(ast); - } - public BreakStatement(ast: Parser.BreakStatement): void { - this._visitStatement(ast); - } - public ContinueStatement(ast: Parser.ContinueStatement): void { - this._visitStatement(ast); - } - public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { - this._visitStatement(ast); - } - public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { - this._visitStatement(ast); - } - public Statement(ast: Parser.Statement): void { - this._visitStatement(ast); - } - public WhileStatement(ast: Parser.WhileStatement): void { - this._visitStatement(ast); - } - public SimpleStatement(ast: Parser.SimpleStatement): void { - this._visitStatement(ast); - } - public ThrowStatement(ast: Parser.ThrowStatement): void { - this._visitStatement(ast); - } - public DoWhileStatement(ast: Parser.DoWhileStatement): void { - this._visitStatement(ast); - } - public ExpressionStatement(ast: Parser.ExpressionStatement): void { - this._visitStatement(ast.expression); - } - public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { - this._visitStatement(ast); - } - public BinaryOperation(ast: Parser.BinaryOperation): void { - const BRANCHING_BIN_OPS = ['&&', '||']; - if (_.includes(BRANCHING_BIN_OPS, ast.operator)) { - this._visitBinaryBranch(ast, ast.left, ast.right, BranchType.BinaryExpression); - } - } - public Conditional(ast: Parser.Conditional): void { - this._visitBinaryBranch(ast, ast.trueExpression, ast.falseExpression, BranchType.ConditionalExpression); - } - public ModifierInvocation(ast: Parser.ModifierInvocation): void { - const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; - if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._modifiersStatementIds.push(this._entryId); - this._visitStatement(ast); - } - } - private _visitBinaryBranch( - ast: Parser.ASTNode, - left: Parser.ASTNode, - right: Parser.ASTNode, - type: BranchType, - ): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._branchMap[this._entryId++] = { - line: this._getExpressionRange(ast).start.line, - type, - locations: [this._getExpressionRange(left), this._getExpressionRange(right)], - }; - } - private _visitStatement(ast: Parser.ASTNode): void { - if (this._shouldIgnoreExpression(ast)) { - return; - } - this._statementMap[this._entryId++] = this._getExpressionRange(ast); - } - 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 range = { - start, - end, - }; - return range; - } - private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { - const [astStart, astEnd] = ast.range as [number, number]; - const isRangeIgnored = _.some( - this._ignoreRangesWithin, - ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, - ); - return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - if (this._shouldIgnoreExpression(ast)) { - this._ignoreRangesWithin.push(ast.range as [number, number]); - return; - } - const loc = this._getExpressionRange(ast); - this._fnMap[this._entryId++] = { - name: ast.name, - line: loc.start.line, - loc, - }; - this._visitStatement(ast); - } -} diff --git a/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts b/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts deleted file mode 100644 index bdbcd613e..000000000 --- a/packages/sol-trace-based-tools-common/src/collect_coverage_entries.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as parser from 'solidity-parser-antlr'; - -import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; -import { getLocationByOffset } 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 } = {}; - -export const collectCoverageEntries = (contractSource: string) => { - const sourceHash = ethUtil.sha3(contractSource).toString('hex'); - if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { - const ast = parser.parse(contractSource, { range: true }); - const locationByOffset = getLocationByOffset(contractSource); - const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); - const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); - parser.visit(ast, visitor); - coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); - } - const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; - return coverageEntriesDescription; -}; - -// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" -function gatherRangesToIgnore(contractSource: string): number[] { - const ignoreRangesStart = []; - - let match; - do { - match = IGNORE_RE.exec(contractSource); - if (match) { - const matchLen = match[0].length; - ignoreRangesStart.push(match.index + matchLen); - } - } while (match); - - return ignoreRangesStart; -} diff --git a/packages/sol-trace-based-tools-common/src/constants.ts b/packages/sol-trace-based-tools-common/src/constants.ts deleted file mode 100644 index 34d62b537..000000000 --- a/packages/sol-trace-based-tools-common/src/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -// tslint:disable:number-literal-format -export const constants = { - NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', - PUSH1: 0x60, - PUSH2: 0x61, - PUSH32: 0x7f, - TIMESTAMP: 0x42, -}; diff --git a/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts b/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts deleted file mode 100644 index f578675d3..000000000 --- a/packages/sol-trace-based-tools-common/src/get_source_range_snippet.ts +++ /dev/null @@ -1,185 +0,0 @@ -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import * as Parser from 'solidity-parser-antlr'; - -import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; -import { utils } from './utils'; - -interface ASTInfo { - type: string; - node: Parser.ASTNode; - name: string | null; - range?: SingleFileSourceRange; -} - -// Parsing source code for each transaction/code is slow and therefore we cache it -const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; - -/** - * Gets the source range snippet by source range to be used by revert trace. - * @param sourceRange source range - * @param sourceCode source code - */ -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 }); - } - const astNode = parsedSourceByHash[sourceHash]; - const visitor = new ASTInfoVisitor(); - Parser.visit(astNode, visitor); - const astInfo = visitor.getASTInfoForRange(sourceRange); - if (astInfo === null) { - return null; - } - const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); - return { - ...astInfo, - range: astInfo.range as SingleFileSourceRange, - source: sourceCodeInRange, - fileName: sourceRange.fileName, - }; -} - -// A visitor which collects ASTInfo for most nodes in the AST. -class ASTInfoVisitor { - private readonly _astInfos: ASTInfo[] = []; - public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { - // HACK(albrow): Sometimes the source range doesn't exactly match that - // of astInfo. To work around that we try with a +/-1 offset on - // end.column. If nothing matches even with the offset, we return null. - const offset = { - start: { - line: 0, - column: 0, - }, - end: { - line: 0, - column: 0, - }, - }; - let astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column += 1; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - offset.end.column -= 2; - astInfo = this._getASTInfoForRange(sourceRange, offset); - if (astInfo !== null) { - return astInfo; - } - return null; - } - public ContractDefinition(ast: Parser.ContractDefinition): void { - this._visitContractDefinition(ast); - } - public IfStatement(ast: Parser.IfStatement): void { - this._visitStatement(ast); - } - public FunctionDefinition(ast: Parser.FunctionDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ModifierDefinition(ast: Parser.ModifierDefinition): void { - this._visitFunctionLikeDefinition(ast); - } - public ForStatement(ast: Parser.ForStatement): void { - this._visitStatement(ast); - } - public ReturnStatement(ast: Parser.ReturnStatement): void { - this._visitStatement(ast); - } - public BreakStatement(ast: Parser.BreakStatement): void { - this._visitStatement(ast); - } - public ContinueStatement(ast: Parser.ContinueStatement): void { - this._visitStatement(ast); - } - public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { - this._visitStatement(ast); - } - public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { - this._visitStatement(ast); - } - public Statement(ast: Parser.Statement): void { - this._visitStatement(ast); - } - public WhileStatement(ast: Parser.WhileStatement): void { - this._visitStatement(ast); - } - public SimpleStatement(ast: Parser.SimpleStatement): void { - this._visitStatement(ast); - } - public ThrowStatement(ast: Parser.ThrowStatement): void { - this._visitStatement(ast); - } - public DoWhileStatement(ast: Parser.DoWhileStatement): void { - this._visitStatement(ast); - } - public ExpressionStatement(ast: Parser.ExpressionStatement): void { - this._visitStatement(ast.expression); - } - public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { - this._visitStatement(ast); - } - public ModifierInvocation(ast: Parser.ModifierInvocation): void { - const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; - if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { - this._visitStatement(ast); - } - } - private _visitStatement(ast: Parser.ASTNode): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: null, - range: ast.loc, - }); - } - private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _visitContractDefinition(ast: Parser.ContractDefinition): void { - this._astInfos.push({ - type: ast.type, - node: ast, - name: ast.name, - range: ast.loc, - }); - } - private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { - const offsetSourceRange = { - ...sourceRange, - location: { - start: { - line: sourceRange.location.start.line + offset.start.line, - column: sourceRange.location.start.column + offset.start.column, - }, - end: { - line: sourceRange.location.end.line + offset.end.line, - column: sourceRange.location.end.column + offset.end.column, - }, - }, - }; - for (const astInfo of this._astInfos) { - const astInfoRange = astInfo.range as SingleFileSourceRange; - if ( - astInfoRange.start.column === offsetSourceRange.location.start.column && - astInfoRange.start.line === offsetSourceRange.location.start.line && - astInfoRange.end.column === offsetSourceRange.location.end.column && - astInfoRange.end.line === offsetSourceRange.location.end.line - ) { - return astInfo; - } - } - return null; - } -} diff --git a/packages/sol-trace-based-tools-common/src/globals.d.ts b/packages/sol-trace-based-tools-common/src/globals.d.ts deleted file mode 100644 index e799b3529..000000000 --- a/packages/sol-trace-based-tools-common/src/globals.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// tslint:disable:completed-docs -declare module '*.json' { - const json: any; - /* tslint:disable */ - export default json; - /* tslint:enable */ -} diff --git a/packages/sol-trace-based-tools-common/src/index.ts b/packages/sol-trace-based-tools-common/src/index.ts deleted file mode 100644 index 413e5305e..000000000 --- a/packages/sol-trace-based-tools-common/src/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; -export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; -export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; - -export { - ContractData, - EvmCallStack, - SourceRange, - SourceSnippet, - StatementCoverage, - StatementDescription, - BranchCoverage, - BranchDescription, - Subtrace, - TraceInfo, - Coverage, - LineColumn, - LineCoverage, - FunctionCoverage, - FunctionDescription, - SingleFileSourceRange, - BranchMap, - EvmCallStackEntry, - FnMap, - LocationByOffset, - StatementMap, - TraceInfoBase, - TraceInfoExistingContract, - TraceInfoNewContract, -} from './types'; -export { collectCoverageEntries } from './collect_coverage_entries'; -export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector'; -export { TraceInfoSubprovider } from './trace_info_subprovider'; -export { utils } from './utils'; -export { constants } from './constants'; -export { parseSourceMap } from './source_maps'; -export { getSourceRangeSnippet } from './get_source_range_snippet'; -export { getRevertTrace } from './revert_trace'; -export { TraceCollectionSubprovider } from './trace_collection_subprovider'; diff --git a/packages/sol-trace-based-tools-common/src/instructions.ts b/packages/sol-trace-based-tools-common/src/instructions.ts deleted file mode 100644 index 40987dbe5..000000000 --- a/packages/sol-trace-based-tools-common/src/instructions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { constants } from './constants'; - -const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; - -const pushDataLength = (inst: number) => inst - constants.PUSH1 + 1; - -const instructionLength = (inst: number) => (isPush(inst) ? pushDataLength(inst) + 1 : 1); - -export const getPcToInstructionIndexMapping = (bytecode: Uint8Array) => { - const result: { - [programCounter: number]: number; - } = {}; - let byteIndex = 0; - let instructionIndex = 0; - while (byteIndex < bytecode.length) { - const instruction = bytecode[byteIndex]; - const length = instructionLength(instruction); - result[byteIndex] = instructionIndex; - byteIndex += length; - instructionIndex += 1; - } - return result; -}; diff --git a/packages/sol-trace-based-tools-common/src/revert_trace.ts b/packages/sol-trace-based-tools-common/src/revert_trace.ts deleted file mode 100644 index 4d474120c..000000000 --- a/packages/sol-trace-based-tools-common/src/revert_trace.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; - -import * as _ from 'lodash'; - -import { EvmCallStack } from './types'; -import { utils } from './utils'; - -/** - * Converts linear trace to a call stack by following calls and returns - * @param structLogs Linear trace - * @param startAddress The address of initial context - */ -export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { - const evmCallStack: EvmCallStack = []; - const addressStack = [startAddress]; - 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]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - - 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], - ); - - // 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]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - evmCallStack.push({ - address: currentAddress, - structLog, - }); - } - } 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]; - if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { - evmCallStack.pop(); - addressStack.pop(); - } - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Revert) { - evmCallStack.push({ - address: _.last(addressStack) as string, - structLog, - }); - return evmCallStack; - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - logUtils.warn( - "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", - ); - return []; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - addressStack.pop(); - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (evmCallStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); - } - return []; -} diff --git a/packages/sol-trace-based-tools-common/src/source_maps.ts b/packages/sol-trace-based-tools-common/src/source_maps.ts deleted file mode 100644 index af0fb4035..000000000 --- a/packages/sol-trace-based-tools-common/src/source_maps.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as _ from 'lodash'; - -import { getPcToInstructionIndexMapping } from './instructions'; -import { LocationByOffset, SourceRange } from './types'; - -const RADIX = 10; - -export interface SourceLocation { - offset: number; - length: number; - fileIndex: number; -} - -/** - * 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 } }; - let currentOffset = 0; - for (const char of str.split('')) { - const location = locationByOffset[currentOffset]; - const isNewline = char === '\n'; - locationByOffset[currentOffset + 1] = { - line: location.line + (isNewline ? 1 : 0), - column: isNewline ? 0 : location.column + 1, - }; - currentOffset++; - } - return 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 srcMap source map string - * @param bytecodeHex contract bytecode - * @param sources sources file names - */ -export function parseSourceMap( - sourceCodes: string[], - srcMap: string, - bytecodeHex: string, - sources: 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 entries = srcMap.split(';'); - let lastParsedEntry: SourceLocation = {} as any; - const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; - _.each(entries, (entry: string, i: number) => { - // tslint:disable-next-line:no-unused-variable - const [instructionIndexStrIfExists, lengthStrIfExists, fileIndexStrIfExists, jumpTypeStrIfExists] = entry.split( - ':', - ); - const instructionIndexIfExists = parseInt(instructionIndexStrIfExists, RADIX); - const lengthIfExists = parseInt(lengthStrIfExists, RADIX); - const fileIndexIfExists = parseInt(fileIndexStrIfExists, RADIX); - const offset = _.isNaN(instructionIndexIfExists) ? lastParsedEntry.offset : instructionIndexIfExists; - const length = _.isNaN(lengthIfExists) ? lastParsedEntry.length : lengthIfExists; - const fileIndex = _.isNaN(fileIndexIfExists) ? lastParsedEntry.fileIndex : fileIndexIfExists; - const parsedEntry = { - offset, - length, - fileIndex, - }; - if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { - const sourceRange = { - location: { - start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], - end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], - }, - fileName: sources[parsedEntry.fileIndex], - }; - instructionIndexToSourceRange[i] = sourceRange; - } else { - // Some assembly code generated by Solidity can't be mapped back to a line of source code. - // Source: https://github.com/ethereum/solidity/issues/3629 - } - lastParsedEntry = parsedEntry; - }); - const pcsToSourceRange: { [programCounter: number]: SourceRange } = {}; - for (const programCounterKey of _.keys(pcToInstructionIndex)) { - const pc = parseInt(programCounterKey, RADIX); - const instructionIndex: number = pcToInstructionIndex[pc]; - pcsToSourceRange[pc] = instructionIndexToSourceRange[instructionIndex]; - } - return pcsToSourceRange; -} diff --git a/packages/sol-trace-based-tools-common/src/trace.ts b/packages/sol-trace-based-tools-common/src/trace.ts deleted file mode 100644 index 770080af3..000000000 --- a/packages/sol-trace-based-tools-common/src/trace.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { logUtils } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { utils } from './utils'; - -export interface TraceByContractAddress { - [contractAddress: string]: StructLog[]; -} - -/** - * Converts linear stack trace to `TraceByContractAddress`. - * @param structLogs stack trace - * @param startAddress initial context address - */ -export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { - const traceByContractAddress: TraceByContractAddress = {}; - let currentTraceSegment = []; - const addressStack = [startAddress]; - if (_.isEmpty(structLogs)) { - return traceByContractAddress; - } - const normalizedStructLogs = utils.normalizeStructLogs(structLogs); - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < normalizedStructLogs.length; i++) { - const structLog = normalizedStructLogs[i]; - if (structLog.depth !== addressStack.length - 1) { - throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); - } - // After that check we have a guarantee that call stack is never empty - // If it would: callStack.length - 1 === structLog.depth === -1 - // That means that we can always safely pop from it - currentTraceSegment.push(structLog); - - 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], - ); - - // 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]; - if (nextStructLog.depth !== structLog.depth) { - addressStack.push(newAddress); - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } - } else if (utils.isEndOpcode(structLog.op)) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - if (structLog.op === OpCode.SelfDestruct) { - // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch - // the bytecode and compute the coverage for the call. If the contract is destroyed with a call - // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. - // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called - // in order to handle this edge-case. - logUtils.warn( - "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", - ); - } - } else if (structLog.op === OpCode.Create) { - // TODO: Extract the new contract address from the stack and handle that scenario - 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; - } else { - if (structLog !== _.last(normalizedStructLogs)) { - const nextStructLog = normalizedStructLogs[i + 1]; - if (nextStructLog.depth === structLog.depth) { - continue; - } else if (nextStructLog.depth === structLog.depth - 1) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - } else { - throw new Error('Malformed trace. Unexpected call depth change'); - } - } - } - } - if (addressStack.length !== 0) { - logUtils.warn('Malformed trace. Call stack non empty at the end'); - } - if (currentTraceSegment.length !== 0) { - const currentAddress = addressStack.pop() as string; - traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( - currentTraceSegment, - ); - currentTraceSegment = []; - logUtils.warn('Malformed trace. Current trace segment non empty at the end'); - } - return traceByContractAddress; -} diff --git a/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts deleted file mode 100644 index 25e38768d..000000000 --- a/packages/sol-trace-based-tools-common/src/trace_collection_subprovider.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; -import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; -import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; -import * as _ from 'lodash'; -import { Lock } from 'semaphore-async-await'; - -import { constants } from './constants'; -import { BlockParamLiteral } from './types'; - -interface MaybeFakeTxData extends TxData { - isFakeTransaction?: boolean; -} - -const BLOCK_GAS_LIMIT = 6000000; - -export interface TraceCollectionSubproviderConfig { - shouldCollectTransactionTraces: boolean; - shouldCollectCallTraces: boolean; - shouldCollectGasEstimateTraces: boolean; -} - -// 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. - -/** - * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. - * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must - * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. - */ -export abstract class TraceCollectionSubprovider extends Subprovider { - protected _web3Wrapper!: Web3Wrapper; - // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise - private readonly _lock = new Lock(); - private readonly _defaultFromAddress: string; - private _isEnabled = true; - private readonly _config: TraceCollectionSubproviderConfig; - /** - * Instantiates a TraceCollectionSubprovider instance - * @param defaultFromAddress default from address to use when sending transactions - */ - constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { - super(); - this._defaultFromAddress = defaultFromAddress; - this._config = config; - } - /** - * Starts trace collection - */ - public start(): void { - this._isEnabled = true; - } - /** - * Stops trace collection - */ - public stop(): void { - this._isEnabled = false; - } - /** - * This method conforms to the web3-provider-engine interface. - * It is called internally by the ProviderEngine when it is this subproviders - * turn to handle a JSON RPC request. - * @param payload JSON RPC payload - * @param next Callback to call if this subprovider decides not to handle the request - * @param _end Callback to call if subprovider handled the request and wants to pass back the request. - */ - // tslint:disable-next-line:prefer-function-over-method async-suffix - public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { - if (this._isEnabled) { - switch (payload.method) { - case 'eth_sendTransaction': - if (!this._config.shouldCollectTransactionTraces) { - next(); - } else { - const txData = payload.params[0]; - next(this._onTransactionSentAsync.bind(this, txData)); - } - return; - - case 'eth_call': - if (!this._config.shouldCollectCallTraces) { - next(); - } else { - const callData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); - } - return; - - case 'eth_estimateGas': - if (!this._config.shouldCollectGasEstimateTraces) { - next(); - } else { - const estimateGasData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); - } - return; - - default: - next(); - return; - } - } else { - next(); - return; - } - } - /** - * Set's the subprovider's engine to the ProviderEngine it is added to. - * This is only called within the ProviderEngine source code, do not call - * directly. - * @param engine The ProviderEngine this subprovider is added to - */ - public setEngine(engine: Provider): void { - super.setEngine(engine); - this._web3Wrapper = new Web3Wrapper(engine); - } - protected abstract async _recordTxTraceAsync( - address: string, - data: string | undefined, - txHash: string, - ): Promise; - private async _onTransactionSentAsync( - txData: MaybeFakeTxData, - err: Error | null, - txHash: string | undefined, - cb: Callback, - ): Promise { - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. Not a call executed as one. - // And we don't want it to be executed within a snapshotting period - await this._lock.acquire(); - } - const NULL_ADDRESS = '0x0'; - if (_.isNull(err)) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); - } else { - const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); - const transactions = latestBlock.transactions; - for (const transaction of transactions) { - const toAddress = - _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; - await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); - } - } - if (!txData.isFakeTransaction) { - // This transaction is a usual transaction. Not a call executed as one. - // And we don't want it to be executed within a snapshotting period - this._lock.release(); - } - cb(); - } - private async _onCallOrGasEstimateExecutedAsync( - callData: Partial, - _err: Error | null, - _callResult: string, - cb: Callback, - ): Promise { - await this._recordCallOrGasEstimateTraceAsync(callData); - cb(); - } - private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { - // We don't want other transactions to be exeucted during snashotting period, that's why we lock the - // transaction execution for all transactions except our fake ones. - await this._lock.acquire(); - const blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); - await blockchainLifecycle.startAsync(); - const fakeTxData = { - gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers - isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked - ...callData, - from: callData.from || this._defaultFromAddress, - }; - try { - const txData = marshaller.unmarshalTxData(fakeTxData); - const txHash = await this._web3Wrapper.sendTransactionAsync(txData); - await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - } catch (err) { - // TODO(logvinov) Check that transaction failed and not some other exception - // Even if this transaction failed - we've already recorded it's trace. - _.noop(); - } - await blockchainLifecycle.revertAsync(); - this._lock.release(); - } -} diff --git a/packages/sol-trace-based-tools-common/src/trace_collector.ts b/packages/sol-trace-based-tools-common/src/trace_collector.ts deleted file mode 100644 index 8ba71f4df..000000000 --- a/packages/sol-trace-based-tools-common/src/trace_collector.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { promisify } from '@0x/utils'; -import { stripHexPrefix } from 'ethereumjs-util'; -import * as fs from 'fs'; -import { Collector } from 'istanbul'; -import * as _ from 'lodash'; -import { getLogger, levels, Logger } from 'loglevel'; -import * as mkdirp from 'mkdirp'; - -import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; -import { constants } from './constants'; -import { parseSourceMap } from './source_maps'; -import { - ContractData, - Coverage, - SourceRange, - Subtrace, - TraceInfo, - TraceInfoExistingContract, - TraceInfoNewContract, -} from './types'; -import { utils } from './utils'; - -const mkdirpAsync = promisify(mkdirp); - -export type SingleFileSubtraceHandler = ( - contractData: ContractData, - subtrace: Subtrace, - pcToSourceRange: { [programCounter: number]: SourceRange }, - fileIndex: number, -) => Coverage; - -/** - * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. - */ -export class TraceCollector { - private readonly _artifactAdapter: AbstractArtifactAdapter; - private readonly _logger: Logger; - private _contractsData!: ContractData[]; - private readonly _collector = new Collector(); - private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; - - /** - * Instantiates a TraceCollector instance - * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) - * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them - * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace - */ - constructor( - artifactAdapter: AbstractArtifactAdapter, - isVerbose: boolean, - singleFileSubtraceHandler: SingleFileSubtraceHandler, - ) { - this._artifactAdapter = artifactAdapter; - this._logger = getLogger('sol-trace-based-tools-common'); - this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); - this._singleFileSubtraceHandler = singleFileSubtraceHandler; - } - public async writeOutputAsync(): Promise { - const finalCoverage = this._collector.getFinalCoverage(); - const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); - await mkdirpAsync('coverage'); - fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); - } - public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { - if (_.isUndefined(this._contractsData)) { - this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); - } - const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; - const bytecode = isContractCreation - ? (traceInfo as TraceInfoNewContract).bytecode - : (traceInfo as TraceInfoExistingContract).runtimeBytecode; - const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); - if (_.isUndefined(contractData)) { - const errMsg = isContractCreation - ? `Unknown contract creation transaction` - : `Transaction to an unknown address: ${traceInfo.address}`; - this._logger.warn(errMsg); - return; - } - 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++) { - const singleFileCoverageForTrace = this._singleFileSubtraceHandler( - contractData, - traceInfo.subtrace, - pcToSourceRange, - fileIndex, - ); - this._collector.add(singleFileCoverageForTrace); - } - } -} diff --git a/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts b/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts deleted file mode 100644 index 635a68f58..000000000 --- a/packages/sol-trace-based-tools-common/src/trace_info_subprovider.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as _ from 'lodash'; - -import { constants } from './constants'; -import { getTracesByContractAddress } from './trace'; -import { TraceCollectionSubprovider } from './trace_collection_subprovider'; -import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; - -// TraceInfoSubprovider is extended by subproviders which need to work with one -// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which -// is called for each TraceInfo. -export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { - protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; - protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { - 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); - if (address === constants.NEW_CONTRACT) { - for (const subcallAddress of subcallAddresses) { - let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; - if (subcallAddress === 'NEW_CONTRACT') { - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - bytecode: data as string, - }; - } else { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - traceInfo = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - } - await this._handleTraceInfoAsync(traceInfo); - } - } else { - for (const subcallAddress of subcallAddresses) { - const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); - const traceForThatSubcall = tracesByContractAddress[subcallAddress]; - const traceInfo: TraceInfoExistingContract = { - subtrace: traceForThatSubcall, - txHash, - address: subcallAddress, - runtimeBytecode, - }; - await this._handleTraceInfoAsync(traceInfo); - } - } - } -} diff --git a/packages/sol-trace-based-tools-common/src/types.ts b/packages/sol-trace-based-tools-common/src/types.ts deleted file mode 100644 index 54ade0400..000000000 --- a/packages/sol-trace-based-tools-common/src/types.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { StructLog } from 'ethereum-types'; -import * as Parser from 'solidity-parser-antlr'; - -export interface LineColumn { - line: number; - column: number; -} - -export interface SourceRange { - location: SingleFileSourceRange; - fileName: string; -} - -export interface SingleFileSourceRange { - start: LineColumn; - end: LineColumn; -} - -export interface LocationByOffset { - [offset: number]: LineColumn; -} - -export interface FunctionDescription { - name: string; - line: number; - loc: SingleFileSourceRange; - skip?: boolean; -} - -export type StatementDescription = SingleFileSourceRange; - -export interface BranchDescription { - line: number; - type: 'if' | 'switch' | 'cond-expr' | 'binary-expr'; - locations: SingleFileSourceRange[]; -} - -export interface FnMap { - [functionId: string]: FunctionDescription; -} - -export interface BranchMap { - [branchId: string]: BranchDescription; -} - -export interface StatementMap { - [statementId: string]: StatementDescription; -} - -export interface LineCoverage { - [lineNo: number]: number; -} - -export interface FunctionCoverage { - [functionId: string]: number; -} - -export interface StatementCoverage { - [statementId: string]: number; -} - -export interface BranchCoverage { - [branchId: string]: number[]; -} - -export interface Coverage { - [fineName: string]: { - l?: LineCoverage; - f: FunctionCoverage; - s: StatementCoverage; - b: BranchCoverage; - fnMap: FnMap; - branchMap: BranchMap; - statementMap: StatementMap; - path: string; - }; -} - -export interface ContractData { - bytecode: string; - sourceMap: string; - runtimeBytecode: string; - sourceMapRuntime: string; - sourceCodes: string[]; - sources: string[]; -} - -// Part of the trace executed within the same context -export type Subtrace = StructLog[]; - -export interface TraceInfoBase { - subtrace: Subtrace; - txHash: string; -} - -export interface TraceInfoNewContract extends TraceInfoBase { - address: 'NEW_CONTRACT'; - bytecode: string; -} - -export interface TraceInfoExistingContract extends TraceInfoBase { - address: string; - runtimeBytecode: string; -} - -export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract; - -export enum BlockParamLiteral { - Latest = 'latest', -} - -export interface EvmCallStackEntry { - structLog: StructLog; - address: string; -} - -export type EvmCallStack = EvmCallStackEntry[]; - -export interface SourceSnippet { - source: string; - fileName: string; - type: string; - node: Parser.ASTNode; - name: string | null; - range: SingleFileSourceRange; -} diff --git a/packages/sol-trace-based-tools-common/src/utils.ts b/packages/sol-trace-based-tools-common/src/utils.ts deleted file mode 100644 index d8bc65e73..000000000 --- a/packages/sol-trace-based-tools-common/src/utils.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { addressUtils, BigNumber } from '@0x/utils'; -import { OpCode, StructLog } from 'ethereum-types'; -import { addHexPrefix } from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { ContractData, LineColumn, SingleFileSourceRange } from './types'; - -// This is the minimum length of valid contract bytecode. The Solidity compiler -// metadata is 86 bytes. If you add the '0x' prefix, we get 88. -const MIN_CONTRACT_BYTECODE_LENGTH = 88; - -export const utils = { - compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { - return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; - }, - removeHexPrefix(hex: string): string { - const hexPrefix = '0x'; - return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; - }, - isRangeInside(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____________________________ - .replace(/_.*_/, '.*') - // Last 86 characters is solidity compiler metadata that's different between compilations - .replace(/.{86}$/, '') - // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance - .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); - // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. - const MAX_REGEX_LENGTH = 32767; - const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); - return truncatedBytecodeRegex; - }, - getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { - if (!bytecode.startsWith('0x')) { - throw new Error(`0x hex prefix missing: ${bytecode}`); - } - const contractData = _.find(contractsData, contractDataCandidate => { - const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); - // If the bytecode is less than the minimum length, we are probably - // dealing with an interface. This isn't what we're looking for. - if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); - if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { - return false; - } - // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so - // collisions are practically impossible and it allows us to reuse that code - return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); - }); - return contractData; - }, - isCallLike(op: OpCode): boolean { - return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); - }, - isEndOpcode(op: OpCode): boolean { - return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); - }, - getAddressFromStackEntry(stackEntry: string): string { - const hexBase = 16; - return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); - }, - normalizeStructLogs(structLogs: StructLog[]): StructLog[] { - if (structLogs[0].depth === 1) { - // Geth uses 1-indexed depth counter whilst ganache starts from 0 - const newStructLogs = _.map(structLogs, structLog => ({ - ...structLog, - depth: structLog.depth - 1, - })); - return newStructLogs; - } - return structLogs; - }, - getRange(sourceCode: string, range: SingleFileSourceRange): string { - const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); - lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); - lines[0] = lines[0].slice(range.start.column); - return lines.join('\n'); - }, -}; diff --git a/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts b/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts deleted file mode 100644 index 7832ec316..000000000 --- a/packages/sol-trace-based-tools-common/test/collect_coverage_entries_test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { collectCoverageEntries } from '../src/collect_coverage_entries'; -import { utils } from '../src/utils'; - -const expect = chai.expect; - -describe('Collect coverage entries', () => { - describe('#collectCoverageEntries', () => { - it('correctly collects coverage entries for Simplest contract', () => { - const simplestContractBaseName = 'Simplest.sol'; - const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); - const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simplestContract); - expect(coverageEntries.fnMap).to.be.deep.equal({}); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - expect(coverageEntries.statementMap).to.be.deep.equal({}); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for SimpleStorage contract', () => { - const simpleStorageContractBaseName = 'SimpleStorage.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnIds = _.keys(coverageEntries.fnMap); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); - const setFunction = `function set(uint x) { - storedData = x; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); - const getFunction = `function get() constant returns (uint retVal) { - return storedData; - }`; - expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( - 'storedData = x', - ); - expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( - 'return storedData;', - ); - expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); - }); - it('correctly collects coverage entries for AllSolidityFeatures contract', () => { - const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; - const simpleStorageContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - simpleStorageContractBaseName, - ); - const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); - const coverageEntries = collectCoverageEntries(simpleStorageContract); - const fnDescriptions = _.values(coverageEntries.fnMap); - const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); - const expectedFnNames = [ - 'f', - 'c', - 'test', - 'getChoice', - 'Base', - 'Derived', - 'f', - 'f', - '', - 'g', - 'setData', - 'getData', - 'sendHalf', - 'insert', - 'remove', - 'contains', - 'iterate_start', - 'iterate_valid', - 'iterate_advance', - 'iterate_get', - 'insert', - 'sum', - 'restricted', - 'DualIndex', - 'set', - 'transfer_ownership', - 'lookup', - '', - '', - 'sum', - 'someFunction', - 'fun', - 'at', - 'test', - 'get', - 'returnNumber', - 'alloc', - 'ham', - 'getMyTuple', - 'ham', - 'abstain', - 'foobar', - 'foobar', - 'a', - ]; - expect(fnNames).to.be.deep.equal(expectedFnNames); - - const branchDescriptions = _.values(coverageEntries.branchMap); - const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); - // tslint:disable-next-line:custom-no-magic-numbers - expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); - const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); - expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); - }); - - it('correctly ignores all coverage entries for Ignore contract', () => { - const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; - const solcovIgnoreContractFileName = path.resolve( - __dirname, - 'fixtures/contracts', - solcovIgnoreContractBaseName, - ); - const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); - const coverageEntries = collectCoverageEntries(solcovIgnoreContract); - const fnIds = _.keys(coverageEntries.fnMap); - - expect(fnIds.length).to.be.equal(1); - expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); - // tslint:disable-next-line:custom-no-magic-numbers - expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); - const setFunction = `function set(uint x) public { - /* solcov ignore next */ - storedData = x; - }`; - expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); - - expect(coverageEntries.branchMap).to.be.deep.equal({}); - const statementIds = _.keys(coverageEntries.statementMap); - expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( - setFunction, - ); - expect(statementIds.length).to.be.equal(1); - expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol deleted file mode 100644 index 21137347e..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/AllSolidityFeatures.sol +++ /dev/null @@ -1,413 +0,0 @@ -// Examples taken from the Solidity documentation online. - -// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions -pragma solidity 0.4.0; -pragma solidity ^0.4.0; - -import "SomeFile.sol"; -import "SomeFile.sol" as SomeOtherFile; -import * as SomeSymbol from "AnotherFile.sol"; -import {symbol1 as alias, symbol2} from "File.sol"; - -interface i { - function f(); -} - -contract c { - function c() - { - val1 = 1 wei; // 1 - val2 = 1 szabo; // 1 * 10 ** 12 - val3 = 1 finney; // 1 * 10 ** 15 - val4 = 1 ether; // 1 * 10 ** 18 - } - uint256 val1; - uint256 val2; - uint256 val3; - uint256 val4; -} - -contract test { - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } - - function test() - { - choices = ActionChoices.GoStraight; - } - function getChoice() returns (uint d) - { - d = uint256(choices); - } - ActionChoices choices; -} - -contract Base { - function Base(uint i) - { - m_i = i; - } - uint public m_i; -} -contract Derived is Base(0) { - function Derived(uint i) Base(i) {} -} - -contract C { - uint248 x; // 31 bytes: slot 0, offset 0 - uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) - uint240 z; // 30 bytes: slot 1, offset 2 bytes - uint8 a; // 1 byte: slot 2, offset 0 bytes - struct S { - uint8 a; // 1 byte, slot +0, offset 0 bytes - uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) - } - S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) - uint8 alpha; // 1 byte, slot 4 (start new slot after struct) - uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) - uint8 gamma; // 1 byte, slot 7 (start new slot after array) -} - -contract test { - function f(uint x, uint y) returns (uint z) { - var c = x + 3; - var b = 7 + (c * (8 - 7)) - x; - return -(-b | 0); - } -} - -contract test { - function f(uint x, uint y) returns (uint z) { - return 10; - } -} - -contract c { - function () returns (uint) { return g(8); } - function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } - function setData(uint pos, uint value) internal { data[pos] = value; } - function getData(uint pos) internal { return data[pos]; } - mapping(uint => uint) data; -} - -contract Sharer { - function sendHalf(address addr) returns (uint balance) { - if (!addr.send(msg.value/2)) - throw; // also reverts the transfer to Sharer - return address(this).balance; - } -} - -/// @dev Models a modifiable and iterable set of uint values. -library IntegerSet -{ - struct data - { - /// Mapping item => index (or zero if not present) - mapping(uint => uint) index; - /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. - uint[] items; - /// Number of stored items. - uint size; - } - function insert(data storage self, uint value) returns (bool alreadyPresent) - { - uint index = self.index[value]; - if (index > 0) - return true; - else - { - if (self.items.length == 0) self.items.length = 1; - index = self.items.length++; - self.items[index] = value; - self.index[value] = index; - self.size++; - return false; - } - } - function remove(data storage self, uint value) returns (bool success) - { - uint index = self.index[value]; - if (index == 0) - return false; - delete self.index[value]; - delete self.items[index]; - self.size --; - } - function contains(data storage self, uint value) returns (bool) - { - return self.index[value] > 0; - } - function iterate_start(data storage self) returns (uint index) - { - return iterate_advance(self, 0); - } - function iterate_valid(data storage self, uint index) returns (bool) - { - return index < self.items.length; - } - function iterate_advance(data storage self, uint index) returns (uint r_index) - { - index++; - while (iterate_valid(self, index) && self.index[self.items[index]] == index) - index++; - return index; - } - function iterate_get(data storage self, uint index) returns (uint value) - { - return self.items[index]; - } -} - -/// How to use it: -contract User -{ - /// Just a struct holding our data. - IntegerSet.data data; - /// Insert something - function insert(uint v) returns (uint size) - { - /// Sends `data` via reference, so IntegerSet can modify it. - IntegerSet.insert(data, v); - /// We can access members of the struct - but we should take care not to mess with them. - return data.size; - } - /// Computes the sum of all stored data. - function sum() returns (uint s) - { - for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) - s += IntegerSet.iterate_get(data, i); - } -} - -// This broke it at one point (namely the modifiers). -contract DualIndex { - mapping(uint => mapping(uint => uint)) data; - address public admin; - - modifier restricted { if (msg.sender == admin) _; } - - function DualIndex() { - admin = msg.sender; - } - - function set(uint key1, uint key2, uint value) restricted { - uint[2][4] memory defaults; // "memory" broke things at one time. - data[key1][key2] = value; - } - - function transfer_ownership(address _admin) restricted { - admin = _admin; - } - - function lookup(uint key1, uint key2) returns(uint) { - return data[key1][key2]; - } -} - -contract A { - -} - -contract B { - -} - -contract C is A, B { - -} - -contract TestPrivate -{ - uint private value; -} - -contract TestInternal -{ - uint internal value; -} - -contract FromSolparse is A, B, TestPrivate, TestInternal { - function() { - uint a = 6 ** 9; - var (x) = 100; - uint y = 2 days; - } -} - -contract CommentedOutFunction { - // FYI: This empty function, as well as the commented - // out function below (bad code) is important to this test. - function() { - - } - - // function something() - // uint x = 10; - // } -} - -library VarHasBrackets { - string constant specialRight = "}"; - //string storage specialLeft = "{"; -} - -library UsingExampleLibrary { - function sum(uint[] storage self) returns (uint s) { - for (uint i = 0; i < self.length; i++) - s += self[i]; - } -} - -contract UsingExampleContract { - using UsingExampleLibrary for uint[]; -} - -contract NewStuff { - uint[] b; - - function someFunction() payable { - string storage a = hex"ab1248fe"; - b[2+2]; - } -} - -// modifier with expression -contract MyContract { - function fun() mymodifier(foo.bar()) {} -} - -library GetCode { - function at(address _addr) returns (bytes o_code) { - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(_addr) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(_addr, add(o_code, 0x20), 0, size) - } - } -} - -contract assemblyLocalBinding { - function test(){ - assembly { - let v := 1 - let x := 0x00 - let y := x - let z := "hello" - } - } -} - -contract assemblyReturn { - uint a = 10; - - function get() constant returns(uint) { - assembly { - mstore(0x40, sload(0)) - byte(0) - address(0) - return(0x40,32) - } - } -} - -contract usesConst { - uint const = 0; -} - -contract memoryArrays { - uint seven = 7; - - function returnNumber(uint number) returns (uint){ - return number; - } - - function alloc() { - uint[] memory a = new uint[](7); - uint[] memory b = new uint[](returnNumber(seven)); - } -} - -contract DeclarativeExpressions { - uint a; - uint b = 7; - uint b2=0; - uint public c; - uint constant public d; - uint public constant e; - uint private constant f = 7; - struct S { uint q;} - - function ham(S storage s1, uint[] storage arr) internal { - uint x; - uint y = 7; - S storage s2 = s1; - uint[] memory stor; - uint[] storage stor2 = arr; - } -} - -contract VariableDeclarationTuple { - function getMyTuple() returns (bool, bool){ - return (true, false); - } - - function ham (){ - var (x, y) = (10, 20); - var (a, b) = getMyTuple(); - var (,c) = (10, 20); - var (d,,) = (10, 20, 30); - var (,e,,f,) = (10, 20, 30, 40, 50); - - var ( - num1, num2, - num3, ,num5 - ) = (10, 20, 30, 40, 50); - } -} - -contract TypeIndexSpacing { - uint [ 7 ] x; - uint [] y; -} - -contract Ballot { - - struct Voter { - uint weight; - bool voted; - } - - function abstain() returns (bool) { - return false; - } - - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} - function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); - - Voter you = Voter(1, true); - - Voter me = Voter({ - weight: 2, - voted: abstain() - }); - - Voter airbnb = Voter({ - weight: 2, - voted: true, - }); -} - -contract multilineReturn { - function a() returns (uint x) { - return - 5; - } -} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol deleted file mode 100644 index e4b4ac246..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SimpleStorage.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity ^0.4.21; - -contract SimpleStorage { - uint public storedData; - function set(uint x) { - storedData = x; - } - function get() constant returns (uint retVal) { - return storedData; - } -} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol deleted file mode 100644 index d71016e07..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/Simplest.sol +++ /dev/null @@ -1,2 +0,0 @@ -contract Simplest { -} diff --git a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol deleted file mode 100644 index a7977ffb4..000000000 --- a/packages/sol-trace-based-tools-common/test/fixtures/contracts/SolcovIgnore.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.4.21; - -contract SolcovIgnore { - uint public storedData; - - function set(uint x) public { - /* solcov ignore next */ - storedData = x; - } - - /* solcov ignore next */ - function get() constant public returns (uint retVal) { - return storedData; - } -} - -/* solcov ignore next */ -contract Ignore { - function ignored() public returns (bool) { - return false; - } -} diff --git a/packages/sol-trace-based-tools-common/test/instructions_test.ts b/packages/sol-trace-based-tools-common/test/instructions_test.ts deleted file mode 100644 index 058053cf9..000000000 --- a/packages/sol-trace-based-tools-common/test/instructions_test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as chai from 'chai'; -import 'mocha'; - -import { constants } from '../src/constants'; -import { getPcToInstructionIndexMapping } from '../src/instructions'; - -const expect = chai.expect; - -describe('instructions', () => { - describe('#getPcToInstructionIndexMapping', () => { - it('correctly maps pcs to instruction indexed', () => { - // tslint:disable-next-line:custom-no-magic-numbers - const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); - const pcToInstruction = getPcToInstructionIndexMapping(bytecode); - const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; - expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts deleted file mode 100644 index 9c58d2cef..000000000 --- a/packages/sol-trace-based-tools-common/test/sol_compiler_artifact_adapter_test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as chai from 'chai'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; - -const expect = chai.expect; - -describe('SolCompilerArtifactAdapter', () => { - describe('#collectContractsData', () => { - it('correctly collects contracts data', async () => { - const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); - const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); - const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); - const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); - _.forEach(contractsData, contractData => { - expect(contractData).to.have.keys([ - 'sourceCodes', - 'sources', - 'sourceMap', - 'sourceMapRuntime', - 'bytecode', - 'runtimeBytecode', - ]); - }); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/source_maps_test.ts b/packages/sol-trace-based-tools-common/test/source_maps_test.ts deleted file mode 100644 index 5820bedd7..000000000 --- a/packages/sol-trace-based-tools-common/test/source_maps_test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as chai from 'chai'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import 'mocha'; -import * as path from 'path'; - -import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; - -const expect = chai.expect; - -const simplestContractBaseName = 'Simplest.sol'; -const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); -const simplestContract = fs.readFileSync(simplestContractFileName).toString(); - -describe('source maps', () => { - describe('#getLocationByOffset', () => { - it('correctly computes location by offset', () => { - const locationByOffset = getLocationByOffset(simplestContract); - const expectedLocationByOffset = { - '0': { line: 1, column: 0 }, - '1': { line: 1, column: 1 }, - '2': { line: 1, column: 2 }, - '3': { line: 1, column: 3 }, - '4': { line: 1, column: 4 }, - '5': { line: 1, column: 5 }, - '6': { line: 1, column: 6 }, - '7': { line: 1, column: 7 }, - '8': { line: 1, column: 8 }, - '9': { line: 1, column: 9 }, - '10': { line: 1, column: 10 }, - '11': { line: 1, column: 11 }, - '12': { line: 1, column: 12 }, - '13': { line: 1, column: 13 }, - '14': { line: 1, column: 14 }, - '15': { line: 1, column: 15 }, - '16': { line: 1, column: 16 }, - '17': { line: 1, column: 17 }, - '18': { line: 1, column: 18 }, - '19': { line: 1, column: 19 }, - '20': { line: 2, column: 0 }, - '21': { line: 2, column: 1 }, - '22': { line: 3, column: 0 }, - }; - expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); - }); - }); - describe('#parseSourceMap', () => { - it('correctly parses the source map', () => { - // This is the source map and bytecode for an empty contract like Example.sol - const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; - const bytecodeHex = - '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; - const sources = [simplestContractBaseName]; - const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); - const expectedSourceRange = { - location: { - start: { line: 1, column: 0 }, - end: { line: 2, column: 1 }, - }, - fileName: simplestContractBaseName, - }; - _.forEach(pcToSourceRange, sourceRange => { - // Solidity source maps are too short and we map some instructions to undefined - // Source: https://github.com/ethereum/solidity/issues/3741 - if (!_.isUndefined(sourceRange)) { - expect(sourceRange).to.be.deep.equal(expectedSourceRange); - } - }); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/trace_test.ts b/packages/sol-trace-based-tools-common/test/trace_test.ts deleted file mode 100644 index 7a034362c..000000000 --- a/packages/sol-trace-based-tools-common/test/trace_test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as chai from 'chai'; -import { OpCode, StructLog } from 'ethereum-types'; -import * as _ from 'lodash'; -import 'mocha'; - -import { getTracesByContractAddress } from '../src/trace'; - -const expect = chai.expect; - -const DEFAULT_STRUCT_LOG: StructLog = { - depth: 0, - error: '', - gas: 0, - gasCost: 0, - memory: [], - op: OpCode.Invalid, - pc: 0, - stack: [], - storage: {}, -}; - -function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { - return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; -} - -describe('Trace', () => { - describe('#getTracesByContractAddress', () => { - it('correctly splits trace by contract address', () => { - const delegateCallAddress = '0x0000000000000000000000000000000000000002'; - const trace = [ - { - op: OpCode.DelegateCall, - stack: [delegateCallAddress, '0x'], - depth: 0, - }, - { - op: OpCode.Return, - depth: 1, - }, - { - op: OpCode.Return, - depth: 0, - }, - ]; - const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); - const startAddress = '0x0000000000000000000000000000000000000001'; - const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); - const expectedTraceByContractAddress = { - [startAddress]: [fullTrace[0], fullTrace[2]], - [delegateCallAddress]: [fullTrace[1]], - }; - expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/test/utils_test.ts b/packages/sol-trace-based-tools-common/test/utils_test.ts deleted file mode 100644 index 6fc8fcfe1..000000000 --- a/packages/sol-trace-based-tools-common/test/utils_test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as chai from 'chai'; -import * as dirtyChai from 'dirty-chai'; -import 'mocha'; - -import { utils } from '../src/utils'; - -chai.use(dirtyChai); -const expect = chai.expect; - -describe('utils', () => { - describe('#compareLineColumn', () => { - it('correctly compares LineColumns', () => { - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); - expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); - expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); - expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); - }); - }); - - describe('#isRangeInside', () => { - it('returns true if inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, - ), - ).to.be.true(); - }); - it('returns true if the same', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - ), - ).to.be.true(); - }); - it('returns false if not inside', () => { - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, - ), - ).to.be.false(); - expect( - utils.isRangeInside( - { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, - { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, - ), - ).to.be.false(); - }); - }); -}); diff --git a/packages/sol-trace-based-tools-common/tsconfig.json b/packages/sol-trace-based-tools-common/tsconfig.json deleted file mode 100644 index 2ee711adc..000000000 --- a/packages/sol-trace-based-tools-common/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "rootDir": "." - }, - "include": ["./src/**/*", "./test/**/*"] -} diff --git a/packages/sol-trace-based-tools-common/tslint.json b/packages/sol-trace-based-tools-common/tslint.json deleted file mode 100644 index dd9053357..000000000 --- a/packages/sol-trace-based-tools-common/tslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["@0x/tslint-config"] -} diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json index 938fa97a0..c650a4a4b 100644 --- a/packages/sol-trace/CHANGELOG.json +++ b/packages/sol-trace/CHANGELOG.json @@ -4,7 +4,7 @@ "changes": [ { "note": - "Initial release as a separate package. For historical entries see @0x/sol-trace-based-tools-common", + "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-trace/package.json b/packages/sol-trace/package.json index fd80c541e..3013be6f9 100644 --- a/packages/sol-trace/package.json +++ b/packages/sol-trace/package.json @@ -31,7 +31,7 @@ "dependencies": { "@0x/subproviders": "^2.1.8", "@0x/typescript-typings": "^3.0.6", - "@0x/sol-trace-based-tools-common": "^2.1.16", + "@0x/sol-tracing-utils": "^2.1.16", "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", diff --git a/packages/sol-trace/src/index.ts b/packages/sol-trace/src/index.ts index 1f3ca3a99..120c0d0a9 100644 --- a/packages/sol-trace/src/index.ts +++ b/packages/sol-trace/src/index.ts @@ -3,7 +3,7 @@ export { TruffleArtifactAdapter, SolCompilerArtifactAdapter, ContractData, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; export { RevertTraceSubprovider } from './revert_trace_subprovider'; diff --git a/packages/sol-trace/src/revert_trace_subprovider.ts b/packages/sol-trace/src/revert_trace_subprovider.ts index 37e62b3f8..31067a402 100644 --- a/packages/sol-trace/src/revert_trace_subprovider.ts +++ b/packages/sol-trace/src/revert_trace_subprovider.ts @@ -10,7 +10,7 @@ import { SourceSnippet, TraceCollectionSubprovider, utils, -} from '@0x/sol-trace-based-tools-common'; +} from '@0x/sol-tracing-utils'; import { stripHexPrefix } from 'ethereumjs-util'; import * as _ from 'lodash'; import { getLogger, levels, Logger } from 'loglevel'; diff --git a/packages/sol-tracing-utils/.npmignore b/packages/sol-tracing-utils/.npmignore new file mode 100644 index 000000000..037786e46 --- /dev/null +++ b/packages/sol-tracing-utils/.npmignore @@ -0,0 +1,6 @@ +.* +yarn-error.log +/src/ +/scripts/ +tsconfig.json +/lib/src/monorepo_scripts/ diff --git a/packages/sol-tracing-utils/CHANGELOG.json b/packages/sol-tracing-utils/CHANGELOG.json new file mode 100644 index 000000000..caccb3fff --- /dev/null +++ b/packages/sol-tracing-utils/CHANGELOG.json @@ -0,0 +1,398 @@ +[ + { + "version": "3.0.0", + "changes": [ + { + "note": "Move out specific tools and leave just the shared parts of the codebase", + "pr": 1492 + } + ] + }, + { + "version": "2.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544739608 + }, + { + "version": "2.1.15", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1544570656 + }, + { + "timestamp": 1543401373, + "version": "2.1.14", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542821676, + "version": "2.1.13", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542208198, + "version": "2.1.12", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542134075, + "version": "2.1.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1542028948, + "version": "2.1.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.9", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1541740904 + }, + { + "version": "2.1.8", + "changes": [ + { + "note": "Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library", + "pr": 1105 + } + ], + "timestamp": 1539871071 + }, + { + "version": "2.1.7", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1538693146 + }, + { + "timestamp": 1538157789, + "version": "2.1.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537907159, + "version": "2.1.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537875740, + "version": "2.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1537541580, + "version": "2.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1536142250, + "version": "2.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1535377027, + "version": "2.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.1.0", + "changes": [ + { + "note": + "Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback`", + "pr": 924 + } + ], + "timestamp": 1535133899 + }, + { + "version": "2.0.0", + "changes": [ + { + "note": + "Fix a bug when eth_call coverage was not computed because of silent schema validation failures", + "pr": 938 + }, + { + "note": "Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings", + "pr": 938 + }, + { + "note": + "Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir`", + "pr": 938 + }, + { + "note": + "Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in", + "pr": 938 + } + ], + "timestamp": 1534210131 + }, + { + "version": "1.0.3", + "changes": [ + { + "note": "Dependencies updated" + } + ], + "timestamp": 1532619515 + }, + { + "timestamp": 1532605697, + "version": "1.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532357734, + "version": "1.0.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1532043000, + "version": "1.0.0", + "changes": [ + { + "note": + "Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter`", + "pr": 589 + }, + { + "note": "Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter`", + "pr": 589 + }, + { + "note": "Properly parse multi-level traces", + "pr": 589 + }, + { + "note": "Add support for solidity libraries", + "pr": 589 + }, + { + "note": "Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters", + "pr": 675 + }, + { + "note": "Fixed a bug caused by Geth debug trace depth being 1indexed", + "pr": 675 + }, + { + "note": "Fixed a bug when the tool crashed on empty traces", + "pr": 675 + }, + { + "note": "Use `BlockchainLifecycle` to support reverts on Geth", + "pr": 675 + }, + { + "note": "Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools", + "pr": 675 + }, + { + "note": "Collect traces from `estimate_gas` calls", + "pr": 675 + }, + { + "note": "Fix a race condition caused by not awaiting the transaction before getting a trace", + "pr": 675 + }, + { + "note": "Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider`", + "pr": 675 + }, + { + "note": "Skip interface artifacts with a warning instead of failing", + "pr": 675 + }, + { + "note": "Fix `solcVersion` regex in parameter validation", + "pr": 690 + }, + { + "note": + "Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there", + "pr": 690 + }, + { + "note": "HUGE perf improvements", + "pr": 690 + }, + { + "note": "Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected", + "pr": 705 + }, + { + "note": "Add source code snippets to stack traces printed by `RevertTraceSubprovider`", + "pr": 725 + } + ] + }, + { + "timestamp": 1531919263, + "version": "0.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1531149657, + "version": "0.1.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1529397769, + "version": "0.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "0.1.0", + "changes": [ + { + "note": "Incorrect publish that was unpublished" + } + ], + "timestamp": 1527810075 + }, + { + "timestamp": 1527009134, + "version": "0.0.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525477860, + "version": "0.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1525428773, + "version": "0.0.9", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1524044013, + "version": "0.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1523462196, + "version": "0.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522673609, + "version": "0.0.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1522658513, + "version": "0.0.5", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/packages/sol-tracing-utils/CHANGELOG.md b/packages/sol-tracing-utils/CHANGELOG.md new file mode 100644 index 000000000..879ef9c95 --- /dev/null +++ b/packages/sol-tracing-utils/CHANGELOG.md @@ -0,0 +1,158 @@ + + +CHANGELOG + +## v2.1.16 - _December 13, 2018_ + + * Dependencies updated + +## v2.1.15 - _December 11, 2018_ + + * Dependencies updated + +## v2.1.14 - _November 28, 2018_ + + * Dependencies updated + +## v2.1.13 - _November 21, 2018_ + + * Dependencies updated + +## v2.1.12 - _November 14, 2018_ + + * Dependencies updated + +## v2.1.11 - _November 13, 2018_ + + * Dependencies updated + +## v2.1.10 - _November 12, 2018_ + + * Dependencies updated + +## v2.1.9 - _November 9, 2018_ + + * Dependencies updated + +## v2.1.8 - _October 18, 2018_ + + * Make @types/solidity-parser-antlr a 'dependency' so it's available to users of the library (#1105) + +## v2.1.7 - _October 4, 2018_ + + * Dependencies updated + +## v2.1.6 - _September 28, 2018_ + + * Dependencies updated + +## v2.1.5 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.4 - _September 25, 2018_ + + * Dependencies updated + +## v2.1.3 - _September 21, 2018_ + + * Dependencies updated + +## v2.1.2 - _September 5, 2018_ + + * Dependencies updated + +## v2.1.1 - _August 27, 2018_ + + * Dependencies updated + +## v2.1.0 - _August 24, 2018_ + + * Export types: `JSONRPCRequestPayload`, `Provider`, `JSONRPCErrorCallback`, `JSONRPCResponsePayload`, `JSONRPCRequestPayloadWithMethod`, `NextCallback`, `ErrorCallback`, `OnNextCompleted` and `Callback` (#924) + +## v2.0.0 - _August 14, 2018_ + + * Fix a bug when eth_call coverage was not computed because of silent schema validation failures (#938) + * Make `TruffleArtifactAdapter` read the `truffle.js` config for `solc` settings (#938) + * Change the first param of `TruffleArtifactAdapter` to be the `projectRoot` instead of `sourcesDir` (#938) + * Throw a helpful error message if truffle artifacts were generated with a different solc version than the one passed in (#938) + +## v1.0.3 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.2 - _July 26, 2018_ + + * Dependencies updated + +## v1.0.1 - _July 23, 2018_ + + * Dependencies updated + +## v1.0.0 - _July 19, 2018_ + + * Add artifact adapter as a parameter for `CoverageSubprovider`. Export `AbstractArtifactAdapter` (#589) + * Implement `SolCompilerArtifactAdapter` and `TruffleArtifactAdapter` (#589) + * Properly parse multi-level traces (#589) + * Add support for solidity libraries (#589) + * Fixed a bug causing `RegExp` to crash if contract code is longer that 32767 characters (#675) + * Fixed a bug caused by Geth debug trace depth being 1indexed (#675) + * Fixed a bug when the tool crashed on empty traces (#675) + * Use `BlockchainLifecycle` to support reverts on Geth (#675) + * Add `ProfilerSubprovider` as a hacky way to profile code using coverage tools (#675) + * Collect traces from `estimate_gas` calls (#675) + * Fix a race condition caused by not awaiting the transaction before getting a trace (#675) + * Add `start`/`stop` functionality to `CoverageSubprovider` and `ProfilerSubprovider` (#675) + * Skip interface artifacts with a warning instead of failing (#675) + * Fix `solcVersion` regex in parameter validation (#690) + * Fix a bug when in `TruffleArtifactsAdapter` causing it to throw if `compiler.json` is not there (#690) + * HUGE perf improvements (#690) + * Create `RevertTraceSubprovider` which prints a stack trace when a `REVERT` is detected (#705) + * Add source code snippets to stack traces printed by `RevertTraceSubprovider` (#725) + +## v0.1.3 - _July 18, 2018_ + + * Dependencies updated + +## v0.1.2 - _July 9, 2018_ + + * Dependencies updated + +## v0.1.1 - _June 19, 2018_ + + * Dependencies updated + +## v0.1.0 - _May 31, 2018_ + + * Incorrect publish that was unpublished + +## v0.0.11 - _May 22, 2018_ + + * Dependencies updated + +## v0.0.10 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.9 - _May 4, 2018_ + + * Dependencies updated + +## v0.0.8 - _April 18, 2018_ + + * Dependencies updated + +## v0.0.7 - _April 11, 2018_ + + * Dependencies updated + +## v0.0.6 - _April 2, 2018_ + + * Dependencies updated + +## v0.0.5 - _April 2, 2018_ + + * Dependencies updated diff --git a/packages/sol-tracing-utils/README.md b/packages/sol-tracing-utils/README.md new file mode 100644 index 000000000..0a4749b8e --- /dev/null +++ b/packages/sol-tracing-utils/README.md @@ -0,0 +1,61 @@ +## @0x/sol-tracing-utils + +Common code for all solidity trace-based tools (sol-coverage, sol-profiler, sol-trace). + +## Installation + +```bash +yarn add @0x/sol-sol-tracing-utils +``` + +## 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-tracing-utils yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/sol-tracing-utils yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-tracing-utils/compiler.json b/packages/sol-tracing-utils/compiler.json new file mode 100644 index 000000000..a6a0c6d3a --- /dev/null +++ b/packages/sol-tracing-utils/compiler.json @@ -0,0 +1,18 @@ +{ + "contracts": ["SimpleStorage"], + "contractsDir": "test/fixtures/contracts", + "artifactsDir": "test/fixtures/artifacts", + "compilerSettings": { + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/packages/sol-tracing-utils/package.json b/packages/sol-tracing-utils/package.json new file mode 100644 index 000000000..f351ccc08 --- /dev/null +++ b/packages/sol-tracing-utils/package.json @@ -0,0 +1,87 @@ +{ + "name": "@0x/sol-tracing-utils", + "version": "2.1.16", + "engines": { + "node": ">=6.12" + }, + "description": "Common part of trace based solidity tools (sol-coverage, sol-trace, sol-profiler)", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s copy_test_fixtures", + "lint": "tslint --format stylish --project .", + "test": "run-s compile_test run_mocha", + "rebuild_and_test": "run-s clean build test", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", + "test:circleci": "yarn test:coverage", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit", + "clean": "shx rm -rf lib test/fixtures/artifacts src/artifacts generated_docs", + "copy_test_fixtures": "copyfiles 'test/fixtures/**/*' ./lib", + "compile_test": "sol-compiler compile", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [], + "docOmitExports": [ + "ProfilerSubprovider", + "RevertTraceSubprovider" + ] + } + }, + "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-tracing-utils/README.md", + "dependencies": { + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "@types/solidity-parser-antlr": "^0.2.0", + "ethereum-types": "^1.1.4", + "ethereumjs-util": "^5.1.1", + "glob": "^7.1.2", + "istanbul": "^0.4.5", + "lodash": "^4.17.5", + "loglevel": "^1.6.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.2", + "semaphore-async-await": "^1.5.1", + "solidity-parser-antlr": "^0.2.12" + }, + "devDependencies": { + "@0x/tslint-config": "^2.0.0", + "@types/istanbul": "^0.4.30", + "@types/loglevel": "^1.5.3", + "@types/mkdirp": "^0.5.1", + "@types/mocha": "^2.2.42", + "@types/node": "*", + "@types/rimraf": "^2.0.2", + "chai": "^4.0.1", + "copyfiles": "^2.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "sinon": "^4.0.0", + "tslint": "5.11.0", + "typedoc": "0.13.0", + "typescript": "3.0.1" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts new file mode 100644 index 000000000..fcc6562ad --- /dev/null +++ b/packages/sol-tracing-utils/src/artifact_adapters/abstract_artifact_adapter.ts @@ -0,0 +1,5 @@ +import { ContractData } from '../types'; + +export abstract class AbstractArtifactAdapter { + public abstract async collectContractsDataAsync(): Promise; +} 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 new file mode 100644 index 000000000..57391abbe --- /dev/null +++ b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts @@ -0,0 +1,61 @@ +import { logUtils } from '@0x/utils'; +import { CompilerOptions, ContractArtifact } from 'ethereum-types'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as _ from 'lodash'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; + +const CONFIG_FILE = 'compiler.json'; + +export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { + private readonly _artifactsPath: string; + private readonly _sourcesPath: string; + /** + * Instantiates a SolCompilerArtifactAdapter + * @param artifactsPath Path to your artifacts directory + * @param sourcesPath Path to your contract sources directory + */ + constructor(artifactsPath?: string, sourcesPath?: string) { + super(); + const config: CompilerOptions = fs.existsSync(CONFIG_FILE) + ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) + : {}; + if (_.isUndefined(artifactsPath) && _.isUndefined(config.artifactsDir)) { + throw new Error(`artifactsDir not found in ${CONFIG_FILE}`); + } + this._artifactsPath = (artifactsPath || config.artifactsDir) as string; + if (_.isUndefined(sourcesPath) && _.isUndefined(config.contractsDir)) { + throw new Error(`contractsDir not found in ${CONFIG_FILE}`); + } + this._sourcesPath = (sourcesPath || config.contractsDir) as string; + } + public async collectContractsDataAsync(): Promise { + const artifactsGlob = `${this._artifactsPath}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + const contractsData: ContractData[] = []; + for (const artifactFileName of artifactFileNames) { + const artifact: ContractArtifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + if (_.isUndefined(artifact.compilerOutput.evm)) { + 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 contractData = { + sourceCodes, + sources, + bytecode: artifact.compilerOutput.evm.bytecode.object, + sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap, + runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object, + sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap, + }; + contractsData.push(contractData); + } + return contractsData; + } +} diff --git a/packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts new file mode 100644 index 000000000..bb2b15153 --- /dev/null +++ b/packages/sol-tracing-utils/src/artifact_adapters/truffle_artifact_adapter.ts @@ -0,0 +1,88 @@ +import { Compiler, CompilerOptions } from '@0x/sol-compiler'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as path from 'path'; + +import { ContractData } from '../types'; + +import { AbstractArtifactAdapter } from './abstract_artifact_adapter'; +import { SolCompilerArtifactAdapter } from './sol_compiler_artifact_adapter'; + +const DEFAULT_TRUFFLE_ARTIFACTS_DIR = './build/contracts'; + +interface TruffleConfig { + solc?: any; + contracts_build_directory?: string; +} + +export class TruffleArtifactAdapter extends AbstractArtifactAdapter { + private readonly _solcVersion: string; + private readonly _projectRoot: string; + /** + * Instantiates a TruffleArtifactAdapter + * @param projectRoot Path to the truffle project's root directory + * @param solcVersion Solidity version with which to compile all the contracts + */ + constructor(projectRoot: string, solcVersion: string) { + super(); + this._solcVersion = solcVersion; + this._projectRoot = projectRoot; + } + public async collectContractsDataAsync(): Promise { + const artifactsDir = '.0x-artifacts'; + const contractsDir = path.join(this._projectRoot, 'contracts'); + const truffleConfig = this._getTruffleConfig(); + const solcConfig = truffleConfig.solc || {}; + const truffleArtifactsDirectory = truffleConfig.contracts_build_directory || DEFAULT_TRUFFLE_ARTIFACTS_DIR; + this._assertSolidityVersionIsCorrect(truffleArtifactsDirectory); + const compilerOptions: CompilerOptions = { + contractsDir, + artifactsDir, + compilerSettings: { + ...solcConfig, + outputSelection: { + ['*']: { + ['*']: ['abi', 'evm.bytecode.object', 'evm.deployedBytecode.object'], + }, + }, + }, + contracts: '*', + solcVersion: this._solcVersion, + }; + const compiler = new Compiler(compilerOptions); + await compiler.compileAsync(); + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); + const contractsDataFrom0xArtifacts = await solCompilerArtifactAdapter.collectContractsDataAsync(); + return contractsDataFrom0xArtifacts; + } + private _getTruffleConfig(): TruffleConfig { + const truffleConfigFileShort = path.resolve(path.join(this._projectRoot, 'truffle.js')); + const truffleConfigFileLong = path.resolve(path.join(this._projectRoot, 'truffle-config.js')); + if (fs.existsSync(truffleConfigFileShort)) { + const truffleConfig = require(truffleConfigFileShort); + return truffleConfig; + } else if (fs.existsSync(truffleConfigFileLong)) { + const truffleConfig = require(truffleConfigFileLong); + return truffleConfig; + } else { + throw new Error( + `Neither ${truffleConfigFileShort} nor ${truffleConfigFileLong} exists. Make sure the project root is correct`, + ); + } + } + private _assertSolidityVersionIsCorrect(truffleArtifactsDirectory: string): void { + const artifactsGlob = `${truffleArtifactsDirectory}/**/*.json`; + const artifactFileNames = glob.sync(artifactsGlob, { absolute: true }); + for (const artifactFileName of artifactFileNames) { + const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString()); + const compilerVersion = artifact.compiler.version; + if (!compilerVersion.startsWith(this._solcVersion)) { + throw new Error( + `${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${ + this._solcVersion + } making it impossible to process traces`, + ); + } + } + } +} diff --git a/packages/sol-tracing-utils/src/ast_visitor.ts b/packages/sol-tracing-utils/src/ast_visitor.ts new file mode 100644 index 000000000..e55cdf6ec --- /dev/null +++ b/packages/sol-tracing-utils/src/ast_visitor.ts @@ -0,0 +1,168 @@ +import * as _ from 'lodash'; +import * as Parser from 'solidity-parser-antlr'; + +import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types'; + +export interface CoverageEntriesDescription { + fnMap: FnMap; + branchMap: BranchMap; + statementMap: StatementMap; + modifiersStatementIds: number[]; +} + +enum BranchType { + If = 'if', + ConditionalExpression = 'cond-expr', + BinaryExpression = 'binary-expr', +} + +export class ASTVisitor { + private _entryId = 0; + private readonly _fnMap: FnMap = {}; + private readonly _branchMap: BranchMap = {}; + private readonly _modifiersStatementIds: number[] = []; + private readonly _statementMap: StatementMap = {}; + private readonly _locationByOffset: LocationByOffset; + 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; + this._ignoreRangesBeginningAt = ignoreRangesBeginningAt; + } + public getCollectedCoverageEntries(): CoverageEntriesDescription { + const coverageEntriesDescription = { + fnMap: this._fnMap, + branchMap: this._branchMap, + statementMap: this._statementMap, + modifiersStatementIds: this._modifiersStatementIds, + }; + return coverageEntriesDescription; + } + public IfStatement(ast: Parser.IfStatement): void { + this._visitStatement(ast); + this._visitBinaryBranch(ast, ast.trueBody, ast.falseBody || ast, BranchType.If); + } + public FunctionDefinition(ast: Parser.FunctionDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ContractDefinition(ast: Parser.ContractDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + } + } + public ModifierDefinition(ast: Parser.ModifierDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ForStatement(ast: Parser.ForStatement): void { + this._visitStatement(ast); + } + public ReturnStatement(ast: Parser.ReturnStatement): void { + this._visitStatement(ast); + } + public BreakStatement(ast: Parser.BreakStatement): void { + this._visitStatement(ast); + } + public ContinueStatement(ast: Parser.ContinueStatement): void { + this._visitStatement(ast); + } + public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { + this._visitStatement(ast); + } + public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { + this._visitStatement(ast); + } + public Statement(ast: Parser.Statement): void { + this._visitStatement(ast); + } + public WhileStatement(ast: Parser.WhileStatement): void { + this._visitStatement(ast); + } + public SimpleStatement(ast: Parser.SimpleStatement): void { + this._visitStatement(ast); + } + public ThrowStatement(ast: Parser.ThrowStatement): void { + this._visitStatement(ast); + } + public DoWhileStatement(ast: Parser.DoWhileStatement): void { + this._visitStatement(ast); + } + public ExpressionStatement(ast: Parser.ExpressionStatement): void { + this._visitStatement(ast.expression); + } + public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { + this._visitStatement(ast); + } + public BinaryOperation(ast: Parser.BinaryOperation): void { + const BRANCHING_BIN_OPS = ['&&', '||']; + if (_.includes(BRANCHING_BIN_OPS, ast.operator)) { + this._visitBinaryBranch(ast, ast.left, ast.right, BranchType.BinaryExpression); + } + } + public Conditional(ast: Parser.Conditional): void { + this._visitBinaryBranch(ast, ast.trueExpression, ast.falseExpression, BranchType.ConditionalExpression); + } + public ModifierInvocation(ast: Parser.ModifierInvocation): void { + const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; + if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._modifiersStatementIds.push(this._entryId); + this._visitStatement(ast); + } + } + private _visitBinaryBranch( + ast: Parser.ASTNode, + left: Parser.ASTNode, + right: Parser.ASTNode, + type: BranchType, + ): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._branchMap[this._entryId++] = { + line: this._getExpressionRange(ast).start.line, + type, + locations: [this._getExpressionRange(left), this._getExpressionRange(right)], + }; + } + private _visitStatement(ast: Parser.ASTNode): void { + if (this._shouldIgnoreExpression(ast)) { + return; + } + this._statementMap[this._entryId++] = this._getExpressionRange(ast); + } + 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 range = { + start, + end, + }; + return range; + } + private _shouldIgnoreExpression(ast: Parser.ASTNode): boolean { + const [astStart, astEnd] = ast.range as [number, number]; + const isRangeIgnored = _.some( + this._ignoreRangesWithin, + ([rangeStart, rangeEnd]: [number, number]) => astStart >= rangeStart && astEnd <= rangeEnd, + ); + return this._ignoreRangesBeginningAt.includes(astStart) || isRangeIgnored; + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + if (this._shouldIgnoreExpression(ast)) { + this._ignoreRangesWithin.push(ast.range as [number, number]); + return; + } + const loc = this._getExpressionRange(ast); + this._fnMap[this._entryId++] = { + name: ast.name, + line: loc.start.line, + loc, + }; + this._visitStatement(ast); + } +} diff --git a/packages/sol-tracing-utils/src/collect_coverage_entries.ts b/packages/sol-tracing-utils/src/collect_coverage_entries.ts new file mode 100644 index 000000000..bdbcd613e --- /dev/null +++ b/packages/sol-tracing-utils/src/collect_coverage_entries.ts @@ -0,0 +1,41 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as parser from 'solidity-parser-antlr'; + +import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor'; +import { getLocationByOffset } 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 } = {}; + +export const collectCoverageEntries = (contractSource: string) => { + const sourceHash = ethUtil.sha3(contractSource).toString('hex'); + if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) { + const ast = parser.parse(contractSource, { range: true }); + const locationByOffset = getLocationByOffset(contractSource); + const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource); + const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt); + parser.visit(ast, visitor); + coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries(); + } + const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash]; + return coverageEntriesDescription; +}; + +// Gather the start index of all code blocks preceeded by "/* solcov ignore next */" +function gatherRangesToIgnore(contractSource: string): number[] { + const ignoreRangesStart = []; + + let match; + do { + match = IGNORE_RE.exec(contractSource); + if (match) { + const matchLen = match[0].length; + ignoreRangesStart.push(match.index + matchLen); + } + } while (match); + + return ignoreRangesStart; +} diff --git a/packages/sol-tracing-utils/src/constants.ts b/packages/sol-tracing-utils/src/constants.ts new file mode 100644 index 000000000..34d62b537 --- /dev/null +++ b/packages/sol-tracing-utils/src/constants.ts @@ -0,0 +1,8 @@ +// tslint:disable:number-literal-format +export const constants = { + NEW_CONTRACT: 'NEW_CONTRACT' as 'NEW_CONTRACT', + PUSH1: 0x60, + PUSH2: 0x61, + PUSH32: 0x7f, + TIMESTAMP: 0x42, +}; diff --git a/packages/sol-tracing-utils/src/get_source_range_snippet.ts b/packages/sol-tracing-utils/src/get_source_range_snippet.ts new file mode 100644 index 000000000..f578675d3 --- /dev/null +++ b/packages/sol-tracing-utils/src/get_source_range_snippet.ts @@ -0,0 +1,185 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; +import * as Parser from 'solidity-parser-antlr'; + +import { SingleFileSourceRange, SourceRange, SourceSnippet } from './types'; +import { utils } from './utils'; + +interface ASTInfo { + type: string; + node: Parser.ASTNode; + name: string | null; + range?: SingleFileSourceRange; +} + +// Parsing source code for each transaction/code is slow and therefore we cache it +const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {}; + +/** + * Gets the source range snippet by source range to be used by revert trace. + * @param sourceRange source range + * @param sourceCode source code + */ +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 }); + } + const astNode = parsedSourceByHash[sourceHash]; + const visitor = new ASTInfoVisitor(); + Parser.visit(astNode, visitor); + const astInfo = visitor.getASTInfoForRange(sourceRange); + if (astInfo === null) { + return null; + } + const sourceCodeInRange = utils.getRange(sourceCode, sourceRange.location); + return { + ...astInfo, + range: astInfo.range as SingleFileSourceRange, + source: sourceCodeInRange, + fileName: sourceRange.fileName, + }; +} + +// A visitor which collects ASTInfo for most nodes in the AST. +class ASTInfoVisitor { + private readonly _astInfos: ASTInfo[] = []; + public getASTInfoForRange(sourceRange: SourceRange): ASTInfo | null { + // HACK(albrow): Sometimes the source range doesn't exactly match that + // of astInfo. To work around that we try with a +/-1 offset on + // end.column. If nothing matches even with the offset, we return null. + const offset = { + start: { + line: 0, + column: 0, + }, + end: { + line: 0, + column: 0, + }, + }; + let astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column += 1; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + offset.end.column -= 2; + astInfo = this._getASTInfoForRange(sourceRange, offset); + if (astInfo !== null) { + return astInfo; + } + return null; + } + public ContractDefinition(ast: Parser.ContractDefinition): void { + this._visitContractDefinition(ast); + } + public IfStatement(ast: Parser.IfStatement): void { + this._visitStatement(ast); + } + public FunctionDefinition(ast: Parser.FunctionDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ModifierDefinition(ast: Parser.ModifierDefinition): void { + this._visitFunctionLikeDefinition(ast); + } + public ForStatement(ast: Parser.ForStatement): void { + this._visitStatement(ast); + } + public ReturnStatement(ast: Parser.ReturnStatement): void { + this._visitStatement(ast); + } + public BreakStatement(ast: Parser.BreakStatement): void { + this._visitStatement(ast); + } + public ContinueStatement(ast: Parser.ContinueStatement): void { + this._visitStatement(ast); + } + public EmitStatement(ast: any /* TODO: Parser.EmitStatement */): void { + this._visitStatement(ast); + } + public VariableDeclarationStatement(ast: Parser.VariableDeclarationStatement): void { + this._visitStatement(ast); + } + public Statement(ast: Parser.Statement): void { + this._visitStatement(ast); + } + public WhileStatement(ast: Parser.WhileStatement): void { + this._visitStatement(ast); + } + public SimpleStatement(ast: Parser.SimpleStatement): void { + this._visitStatement(ast); + } + public ThrowStatement(ast: Parser.ThrowStatement): void { + this._visitStatement(ast); + } + public DoWhileStatement(ast: Parser.DoWhileStatement): void { + this._visitStatement(ast); + } + public ExpressionStatement(ast: Parser.ExpressionStatement): void { + this._visitStatement(ast.expression); + } + public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { + this._visitStatement(ast); + } + public ModifierInvocation(ast: Parser.ModifierInvocation): void { + const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; + if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + this._visitStatement(ast); + } + } + private _visitStatement(ast: Parser.ASTNode): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: null, + range: ast.loc, + }); + } + private _visitFunctionLikeDefinition(ast: Parser.ModifierDefinition | Parser.FunctionDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _visitContractDefinition(ast: Parser.ContractDefinition): void { + this._astInfos.push({ + type: ast.type, + node: ast, + name: ast.name, + range: ast.loc, + }); + } + private _getASTInfoForRange(sourceRange: SourceRange, offset: SingleFileSourceRange): ASTInfo | null { + const offsetSourceRange = { + ...sourceRange, + location: { + start: { + line: sourceRange.location.start.line + offset.start.line, + column: sourceRange.location.start.column + offset.start.column, + }, + end: { + line: sourceRange.location.end.line + offset.end.line, + column: sourceRange.location.end.column + offset.end.column, + }, + }, + }; + for (const astInfo of this._astInfos) { + const astInfoRange = astInfo.range as SingleFileSourceRange; + if ( + astInfoRange.start.column === offsetSourceRange.location.start.column && + astInfoRange.start.line === offsetSourceRange.location.start.line && + astInfoRange.end.column === offsetSourceRange.location.end.column && + astInfoRange.end.line === offsetSourceRange.location.end.line + ) { + return astInfo; + } + } + return null; + } +} diff --git a/packages/sol-tracing-utils/src/globals.d.ts b/packages/sol-tracing-utils/src/globals.d.ts new file mode 100644 index 000000000..e799b3529 --- /dev/null +++ b/packages/sol-tracing-utils/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-tracing-utils/src/index.ts b/packages/sol-tracing-utils/src/index.ts new file mode 100644 index 000000000..413e5305e --- /dev/null +++ b/packages/sol-tracing-utils/src/index.ts @@ -0,0 +1,39 @@ +export { SolCompilerArtifactAdapter } from './artifact_adapters/sol_compiler_artifact_adapter'; +export { TruffleArtifactAdapter } from './artifact_adapters/truffle_artifact_adapter'; +export { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; + +export { + ContractData, + EvmCallStack, + SourceRange, + SourceSnippet, + StatementCoverage, + StatementDescription, + BranchCoverage, + BranchDescription, + Subtrace, + TraceInfo, + Coverage, + LineColumn, + LineCoverage, + FunctionCoverage, + FunctionDescription, + SingleFileSourceRange, + BranchMap, + EvmCallStackEntry, + FnMap, + LocationByOffset, + StatementMap, + TraceInfoBase, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +export { collectCoverageEntries } from './collect_coverage_entries'; +export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector'; +export { TraceInfoSubprovider } from './trace_info_subprovider'; +export { utils } from './utils'; +export { constants } from './constants'; +export { parseSourceMap } from './source_maps'; +export { getSourceRangeSnippet } from './get_source_range_snippet'; +export { getRevertTrace } from './revert_trace'; +export { TraceCollectionSubprovider } from './trace_collection_subprovider'; diff --git a/packages/sol-tracing-utils/src/instructions.ts b/packages/sol-tracing-utils/src/instructions.ts new file mode 100644 index 000000000..40987dbe5 --- /dev/null +++ b/packages/sol-tracing-utils/src/instructions.ts @@ -0,0 +1,23 @@ +import { constants } from './constants'; + +const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32; + +const pushDataLength = (inst: number) => inst - constants.PUSH1 + 1; + +const instructionLength = (inst: number) => (isPush(inst) ? pushDataLength(inst) + 1 : 1); + +export const getPcToInstructionIndexMapping = (bytecode: Uint8Array) => { + const result: { + [programCounter: number]: number; + } = {}; + let byteIndex = 0; + let instructionIndex = 0; + while (byteIndex < bytecode.length) { + const instruction = bytecode[byteIndex]; + const length = instructionLength(instruction); + result[byteIndex] = instructionIndex; + byteIndex += length; + instructionIndex += 1; + } + return result; +}; diff --git a/packages/sol-tracing-utils/src/revert_trace.ts b/packages/sol-tracing-utils/src/revert_trace.ts new file mode 100644 index 000000000..4d474120c --- /dev/null +++ b/packages/sol-tracing-utils/src/revert_trace.ts @@ -0,0 +1,95 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; + +import * as _ from 'lodash'; + +import { EvmCallStack } from './types'; +import { utils } from './utils'; + +/** + * Converts linear trace to a call stack by following calls and returns + * @param structLogs Linear trace + * @param startAddress The address of initial context + */ +export function getRevertTrace(structLogs: StructLog[], startAddress: string): EvmCallStack { + const evmCallStack: EvmCallStack = []; + const addressStack = [startAddress]; + 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]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + + 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], + ); + + // 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]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + evmCallStack.push({ + address: currentAddress, + structLog, + }); + } + } 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]; + if (_.isUndefined(nextStructLog) || nextStructLog.depth !== structLog.depth) { + evmCallStack.pop(); + addressStack.pop(); + } + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Revert) { + evmCallStack.push({ + address: _.last(addressStack) as string, + structLog, + }); + return evmCallStack; + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + logUtils.warn( + "Detected a contract created from within another contract. We currently do not support that scenario. We'll just skip that trace", + ); + return []; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + addressStack.pop(); + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (evmCallStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end. (probably out of gas)'); + } + return []; +} diff --git a/packages/sol-tracing-utils/src/source_maps.ts b/packages/sol-tracing-utils/src/source_maps.ts new file mode 100644 index 000000000..af0fb4035 --- /dev/null +++ b/packages/sol-tracing-utils/src/source_maps.ts @@ -0,0 +1,91 @@ +import * as _ from 'lodash'; + +import { getPcToInstructionIndexMapping } from './instructions'; +import { LocationByOffset, SourceRange } from './types'; + +const RADIX = 10; + +export interface SourceLocation { + offset: number; + length: number; + fileIndex: number; +} + +/** + * 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 } }; + let currentOffset = 0; + for (const char of str.split('')) { + const location = locationByOffset[currentOffset]; + const isNewline = char === '\n'; + locationByOffset[currentOffset + 1] = { + line: location.line + (isNewline ? 1 : 0), + column: isNewline ? 0 : location.column + 1, + }; + currentOffset++; + } + return 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 srcMap source map string + * @param bytecodeHex contract bytecode + * @param sources sources file names + */ +export function parseSourceMap( + sourceCodes: string[], + srcMap: string, + bytecodeHex: string, + sources: 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 entries = srcMap.split(';'); + let lastParsedEntry: SourceLocation = {} as any; + const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; + _.each(entries, (entry: string, i: number) => { + // tslint:disable-next-line:no-unused-variable + const [instructionIndexStrIfExists, lengthStrIfExists, fileIndexStrIfExists, jumpTypeStrIfExists] = entry.split( + ':', + ); + const instructionIndexIfExists = parseInt(instructionIndexStrIfExists, RADIX); + const lengthIfExists = parseInt(lengthStrIfExists, RADIX); + const fileIndexIfExists = parseInt(fileIndexStrIfExists, RADIX); + const offset = _.isNaN(instructionIndexIfExists) ? lastParsedEntry.offset : instructionIndexIfExists; + const length = _.isNaN(lengthIfExists) ? lastParsedEntry.length : lengthIfExists; + const fileIndex = _.isNaN(fileIndexIfExists) ? lastParsedEntry.fileIndex : fileIndexIfExists; + const parsedEntry = { + offset, + length, + fileIndex, + }; + if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { + const sourceRange = { + location: { + start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], + end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], + }, + fileName: sources[parsedEntry.fileIndex], + }; + instructionIndexToSourceRange[i] = sourceRange; + } else { + // Some assembly code generated by Solidity can't be mapped back to a line of source code. + // Source: https://github.com/ethereum/solidity/issues/3629 + } + lastParsedEntry = parsedEntry; + }); + const pcsToSourceRange: { [programCounter: number]: SourceRange } = {}; + for (const programCounterKey of _.keys(pcToInstructionIndex)) { + const pc = parseInt(programCounterKey, RADIX); + const instructionIndex: number = pcToInstructionIndex[pc]; + pcsToSourceRange[pc] = instructionIndexToSourceRange[instructionIndex]; + } + return pcsToSourceRange; +} diff --git a/packages/sol-tracing-utils/src/trace.ts b/packages/sol-tracing-utils/src/trace.ts new file mode 100644 index 000000000..770080af3 --- /dev/null +++ b/packages/sol-tracing-utils/src/trace.ts @@ -0,0 +1,104 @@ +import { logUtils } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { utils } from './utils'; + +export interface TraceByContractAddress { + [contractAddress: string]: StructLog[]; +} + +/** + * Converts linear stack trace to `TraceByContractAddress`. + * @param structLogs stack trace + * @param startAddress initial context address + */ +export function getTracesByContractAddress(structLogs: StructLog[], startAddress: string): TraceByContractAddress { + const traceByContractAddress: TraceByContractAddress = {}; + let currentTraceSegment = []; + const addressStack = [startAddress]; + if (_.isEmpty(structLogs)) { + return traceByContractAddress; + } + const normalizedStructLogs = utils.normalizeStructLogs(structLogs); + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < normalizedStructLogs.length; i++) { + const structLog = normalizedStructLogs[i]; + if (structLog.depth !== addressStack.length - 1) { + throw new Error("Malformed trace. Trace depth doesn't match call stack depth"); + } + // After that check we have a guarantee that call stack is never empty + // If it would: callStack.length - 1 === structLog.depth === -1 + // That means that we can always safely pop from it + currentTraceSegment.push(structLog); + + 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], + ); + + // 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]; + if (nextStructLog.depth !== structLog.depth) { + addressStack.push(newAddress); + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } + } else if (utils.isEndOpcode(structLog.op)) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + if (structLog.op === OpCode.SelfDestruct) { + // After contract execution, we look at all sub-calls to external contracts, and for each one, fetch + // the bytecode and compute the coverage for the call. If the contract is destroyed with a call + // to `selfdestruct`, we are unable to fetch it's bytecode and compute coverage. + // TODO: Refactor this logic to fetch the sub-called contract bytecode before the selfdestruct is called + // in order to handle this edge-case. + logUtils.warn( + "Detected a selfdestruct. We currently do not support that scenario. We'll just skip the trace part for a destructed contract", + ); + } + } else if (structLog.op === OpCode.Create) { + // TODO: Extract the new contract address from the stack and handle that scenario + 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; + } else { + if (structLog !== _.last(normalizedStructLogs)) { + const nextStructLog = normalizedStructLogs[i + 1]; + if (nextStructLog.depth === structLog.depth) { + continue; + } else if (nextStructLog.depth === structLog.depth - 1) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + } else { + throw new Error('Malformed trace. Unexpected call depth change'); + } + } + } + } + if (addressStack.length !== 0) { + logUtils.warn('Malformed trace. Call stack non empty at the end'); + } + if (currentTraceSegment.length !== 0) { + const currentAddress = addressStack.pop() as string; + traceByContractAddress[currentAddress] = (traceByContractAddress[currentAddress] || []).concat( + currentTraceSegment, + ); + currentTraceSegment = []; + logUtils.warn('Malformed trace. Current trace segment non empty at the end'); + } + return traceByContractAddress; +} diff --git a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts new file mode 100644 index 000000000..25e38768d --- /dev/null +++ b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts @@ -0,0 +1,188 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { Callback, ErrorCallback, NextCallback, Subprovider } from '@0x/subproviders'; +import { CallDataRPC, marshaller, Web3Wrapper } from '@0x/web3-wrapper'; +import { JSONRPCRequestPayload, Provider, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; +import { Lock } from 'semaphore-async-await'; + +import { constants } from './constants'; +import { BlockParamLiteral } from './types'; + +interface MaybeFakeTxData extends TxData { + isFakeTransaction?: boolean; +} + +const BLOCK_GAS_LIMIT = 6000000; + +export interface TraceCollectionSubproviderConfig { + shouldCollectTransactionTraces: boolean; + shouldCollectCallTraces: boolean; + shouldCollectGasEstimateTraces: boolean; +} + +// 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. + +/** + * This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface. + * It collects traces of all transactions that were sent and all calls that were executed through JSON RPC. It must + * be extended by implementing the _recordTxTraceAsync method which is called for every transaction. + */ +export abstract class TraceCollectionSubprovider extends Subprovider { + protected _web3Wrapper!: Web3Wrapper; + // Lock is used to not accept normal transactions while doing call/snapshot magic because they'll be reverted later otherwise + private readonly _lock = new Lock(); + private readonly _defaultFromAddress: string; + private _isEnabled = true; + private readonly _config: TraceCollectionSubproviderConfig; + /** + * Instantiates a TraceCollectionSubprovider instance + * @param defaultFromAddress default from address to use when sending transactions + */ + constructor(defaultFromAddress: string, config: TraceCollectionSubproviderConfig) { + super(); + this._defaultFromAddress = defaultFromAddress; + this._config = config; + } + /** + * Starts trace collection + */ + public start(): void { + this._isEnabled = true; + } + /** + * Stops trace collection + */ + public stop(): void { + this._isEnabled = false; + } + /** + * This method conforms to the web3-provider-engine interface. + * It is called internally by the ProviderEngine when it is this subproviders + * turn to handle a JSON RPC request. + * @param payload JSON RPC payload + * @param next Callback to call if this subprovider decides not to handle the request + * @param _end Callback to call if subprovider handled the request and wants to pass back the request. + */ + // tslint:disable-next-line:prefer-function-over-method async-suffix + public async handleRequest(payload: JSONRPCRequestPayload, next: NextCallback, _end: ErrorCallback): Promise { + if (this._isEnabled) { + switch (payload.method) { + case 'eth_sendTransaction': + if (!this._config.shouldCollectTransactionTraces) { + next(); + } else { + const txData = payload.params[0]; + next(this._onTransactionSentAsync.bind(this, txData)); + } + return; + + case 'eth_call': + if (!this._config.shouldCollectCallTraces) { + next(); + } else { + const callData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); + } + return; + + case 'eth_estimateGas': + if (!this._config.shouldCollectGasEstimateTraces) { + next(); + } else { + const estimateGasData = payload.params[0]; + next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); + } + return; + + default: + next(); + return; + } + } else { + next(); + return; + } + } + /** + * Set's the subprovider's engine to the ProviderEngine it is added to. + * This is only called within the ProviderEngine source code, do not call + * directly. + * @param engine The ProviderEngine this subprovider is added to + */ + public setEngine(engine: Provider): void { + super.setEngine(engine); + this._web3Wrapper = new Web3Wrapper(engine); + } + protected abstract async _recordTxTraceAsync( + address: string, + data: string | undefined, + txHash: string, + ): Promise; + private async _onTransactionSentAsync( + txData: MaybeFakeTxData, + err: Error | null, + txHash: string | undefined, + cb: Callback, + ): Promise { + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. Not a call executed as one. + // And we don't want it to be executed within a snapshotting period + await this._lock.acquire(); + } + const NULL_ADDRESS = '0x0'; + if (_.isNull(err)) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, txData.data, txHash as string); + } else { + const latestBlock = await this._web3Wrapper.getBlockWithTransactionDataAsync(BlockParamLiteral.Latest); + const transactions = latestBlock.transactions; + for (const transaction of transactions) { + const toAddress = + _.isUndefined(txData.to) || txData.to === NULL_ADDRESS ? constants.NEW_CONTRACT : txData.to; + await this._recordTxTraceAsync(toAddress, transaction.input, transaction.hash); + } + } + if (!txData.isFakeTransaction) { + // This transaction is a usual transaction. Not a call executed as one. + // And we don't want it to be executed within a snapshotting period + this._lock.release(); + } + cb(); + } + private async _onCallOrGasEstimateExecutedAsync( + callData: Partial, + _err: Error | null, + _callResult: string, + cb: Callback, + ): Promise { + await this._recordCallOrGasEstimateTraceAsync(callData); + cb(); + } + private async _recordCallOrGasEstimateTraceAsync(callData: Partial): Promise { + // We don't want other transactions to be exeucted during snashotting period, that's why we lock the + // transaction execution for all transactions except our fake ones. + await this._lock.acquire(); + const blockchainLifecycle = new BlockchainLifecycle(this._web3Wrapper); + await blockchainLifecycle.startAsync(); + const fakeTxData = { + gas: BLOCK_GAS_LIMIT.toString(16), // tslint:disable-line:custom-no-magic-numbers + isFakeTransaction: true, // This transaction (and only it) is allowed to come through when the lock is locked + ...callData, + from: callData.from || this._defaultFromAddress, + }; + try { + const txData = marshaller.unmarshalTxData(fakeTxData); + const txHash = await this._web3Wrapper.sendTransactionAsync(txData); + await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); + } catch (err) { + // TODO(logvinov) Check that transaction failed and not some other exception + // Even if this transaction failed - we've already recorded it's trace. + _.noop(); + } + await blockchainLifecycle.revertAsync(); + this._lock.release(); + } +} diff --git a/packages/sol-tracing-utils/src/trace_collector.ts b/packages/sol-tracing-utils/src/trace_collector.ts new file mode 100644 index 000000000..943e208cf --- /dev/null +++ b/packages/sol-tracing-utils/src/trace_collector.ts @@ -0,0 +1,93 @@ +import { promisify } from '@0x/utils'; +import { stripHexPrefix } from 'ethereumjs-util'; +import * as fs from 'fs'; +import { Collector } from 'istanbul'; +import * as _ from 'lodash'; +import { getLogger, levels, Logger } from 'loglevel'; +import * as mkdirp from 'mkdirp'; + +import { AbstractArtifactAdapter } from './artifact_adapters/abstract_artifact_adapter'; +import { constants } from './constants'; +import { parseSourceMap } from './source_maps'; +import { + ContractData, + Coverage, + SourceRange, + Subtrace, + TraceInfo, + TraceInfoExistingContract, + TraceInfoNewContract, +} from './types'; +import { utils } from './utils'; + +const mkdirpAsync = promisify(mkdirp); + +export type SingleFileSubtraceHandler = ( + contractData: ContractData, + subtrace: Subtrace, + pcToSourceRange: { [programCounter: number]: SourceRange }, + fileIndex: number, +) => Coverage; + +/** + * TraceCollector is used by CoverageSubprovider to compute code coverage based on collected trace data. + */ +export class TraceCollector { + private readonly _artifactAdapter: AbstractArtifactAdapter; + private readonly _logger: Logger; + private _contractsData!: ContractData[]; + private readonly _collector = new Collector(); + private readonly _singleFileSubtraceHandler: SingleFileSubtraceHandler; + + /** + * Instantiates a TraceCollector instance + * @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.) + * @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them + * @param singleFileSubtraceHandler A handler function for computing partial coverage for a single file & subtrace + */ + constructor( + artifactAdapter: AbstractArtifactAdapter, + isVerbose: boolean, + singleFileSubtraceHandler: SingleFileSubtraceHandler, + ) { + this._artifactAdapter = artifactAdapter; + this._logger = getLogger('sol-tracing-utils'); + this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR); + this._singleFileSubtraceHandler = singleFileSubtraceHandler; + } + public async writeOutputAsync(): Promise { + const finalCoverage = this._collector.getFinalCoverage(); + const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); + await mkdirpAsync('coverage'); + fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); + } + public async computeSingleTraceCoverageAsync(traceInfo: TraceInfo): Promise { + if (_.isUndefined(this._contractsData)) { + this._contractsData = await this._artifactAdapter.collectContractsDataAsync(); + } + const isContractCreation = traceInfo.address === constants.NEW_CONTRACT; + const bytecode = isContractCreation + ? (traceInfo as TraceInfoNewContract).bytecode + : (traceInfo as TraceInfoExistingContract).runtimeBytecode; + const contractData = utils.getContractDataIfExists(this._contractsData, bytecode); + if (_.isUndefined(contractData)) { + const errMsg = isContractCreation + ? `Unknown contract creation transaction` + : `Transaction to an unknown address: ${traceInfo.address}`; + this._logger.warn(errMsg); + return; + } + 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++) { + const singleFileCoverageForTrace = this._singleFileSubtraceHandler( + contractData, + traceInfo.subtrace, + pcToSourceRange, + 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 new file mode 100644 index 000000000..635a68f58 --- /dev/null +++ b/packages/sol-tracing-utils/src/trace_info_subprovider.ts @@ -0,0 +1,59 @@ +import * as _ from 'lodash'; + +import { constants } from './constants'; +import { getTracesByContractAddress } from './trace'; +import { TraceCollectionSubprovider } from './trace_collection_subprovider'; +import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types'; + +// TraceInfoSubprovider is extended by subproviders which need to work with one +// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which +// is called for each TraceInfo. +export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { + protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise; + protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise { + 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); + if (address === constants.NEW_CONTRACT) { + for (const subcallAddress of subcallAddresses) { + let traceInfo: TraceInfoNewContract | TraceInfoExistingContract; + if (subcallAddress === 'NEW_CONTRACT') { + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + bytecode: data as string, + }; + } else { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + traceInfo = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + } + await this._handleTraceInfoAsync(traceInfo); + } + } else { + for (const subcallAddress of subcallAddresses) { + const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress); + const traceForThatSubcall = tracesByContractAddress[subcallAddress]; + const traceInfo: TraceInfoExistingContract = { + subtrace: traceForThatSubcall, + txHash, + address: subcallAddress, + runtimeBytecode, + }; + await this._handleTraceInfoAsync(traceInfo); + } + } + } +} diff --git a/packages/sol-tracing-utils/src/types.ts b/packages/sol-tracing-utils/src/types.ts new file mode 100644 index 000000000..54ade0400 --- /dev/null +++ b/packages/sol-tracing-utils/src/types.ts @@ -0,0 +1,126 @@ +import { StructLog } from 'ethereum-types'; +import * as Parser from 'solidity-parser-antlr'; + +export interface LineColumn { + line: number; + column: number; +} + +export interface SourceRange { + location: SingleFileSourceRange; + fileName: string; +} + +export interface SingleFileSourceRange { + start: LineColumn; + end: LineColumn; +} + +export interface LocationByOffset { + [offset: number]: LineColumn; +} + +export interface FunctionDescription { + name: string; + line: number; + loc: SingleFileSourceRange; + skip?: boolean; +} + +export type StatementDescription = SingleFileSourceRange; + +export interface BranchDescription { + line: number; + type: 'if' | 'switch' | 'cond-expr' | 'binary-expr'; + locations: SingleFileSourceRange[]; +} + +export interface FnMap { + [functionId: string]: FunctionDescription; +} + +export interface BranchMap { + [branchId: string]: BranchDescription; +} + +export interface StatementMap { + [statementId: string]: StatementDescription; +} + +export interface LineCoverage { + [lineNo: number]: number; +} + +export interface FunctionCoverage { + [functionId: string]: number; +} + +export interface StatementCoverage { + [statementId: string]: number; +} + +export interface BranchCoverage { + [branchId: string]: number[]; +} + +export interface Coverage { + [fineName: string]: { + l?: LineCoverage; + f: FunctionCoverage; + s: StatementCoverage; + b: BranchCoverage; + fnMap: FnMap; + branchMap: BranchMap; + statementMap: StatementMap; + path: string; + }; +} + +export interface ContractData { + bytecode: string; + sourceMap: string; + runtimeBytecode: string; + sourceMapRuntime: string; + sourceCodes: string[]; + sources: string[]; +} + +// Part of the trace executed within the same context +export type Subtrace = StructLog[]; + +export interface TraceInfoBase { + subtrace: Subtrace; + txHash: string; +} + +export interface TraceInfoNewContract extends TraceInfoBase { + address: 'NEW_CONTRACT'; + bytecode: string; +} + +export interface TraceInfoExistingContract extends TraceInfoBase { + address: string; + runtimeBytecode: string; +} + +export type TraceInfo = TraceInfoNewContract | TraceInfoExistingContract; + +export enum BlockParamLiteral { + Latest = 'latest', +} + +export interface EvmCallStackEntry { + structLog: StructLog; + address: string; +} + +export type EvmCallStack = EvmCallStackEntry[]; + +export interface SourceSnippet { + source: string; + fileName: string; + type: string; + node: Parser.ASTNode; + name: string | null; + range: SingleFileSourceRange; +} diff --git a/packages/sol-tracing-utils/src/utils.ts b/packages/sol-tracing-utils/src/utils.ts new file mode 100644 index 000000000..d8bc65e73 --- /dev/null +++ b/packages/sol-tracing-utils/src/utils.ts @@ -0,0 +1,87 @@ +import { addressUtils, BigNumber } from '@0x/utils'; +import { OpCode, StructLog } from 'ethereum-types'; +import { addHexPrefix } from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { ContractData, LineColumn, SingleFileSourceRange } from './types'; + +// This is the minimum length of valid contract bytecode. The Solidity compiler +// metadata is 86 bytes. If you add the '0x' prefix, we get 88. +const MIN_CONTRACT_BYTECODE_LENGTH = 88; + +export const utils = { + compareLineColumn(lhs: LineColumn, rhs: LineColumn): number { + return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column; + }, + removeHexPrefix(hex: string): string { + const hexPrefix = '0x'; + return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex; + }, + isRangeInside(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____________________________ + .replace(/_.*_/, '.*') + // Last 86 characters is solidity compiler metadata that's different between compilations + .replace(/.{86}$/, '') + // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance + .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................'); + // HACK: Node regexes can't be longer that 32767 characters. Contracts bytecode can. We just truncate the regexes. It's safe in practice. + const MAX_REGEX_LENGTH = 32767; + const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); + return truncatedBytecodeRegex; + }, + getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined { + if (!bytecode.startsWith('0x')) { + throw new Error(`0x hex prefix missing: ${bytecode}`); + } + const contractData = _.find(contractsData, contractDataCandidate => { + const bytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.bytecode); + // If the bytecode is less than the minimum length, we are probably + // dealing with an interface. This isn't what we're looking for. + if (bytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + const runtimeBytecodeRegex = utils.bytecodeToBytecodeRegex(contractDataCandidate.runtimeBytecode); + if (runtimeBytecodeRegex.length < MIN_CONTRACT_BYTECODE_LENGTH) { + return false; + } + // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so + // collisions are practically impossible and it allows us to reuse that code + return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex)); + }); + return contractData; + }, + isCallLike(op: OpCode): boolean { + return _.includes([OpCode.CallCode, OpCode.StaticCall, OpCode.Call, OpCode.DelegateCall], op); + }, + isEndOpcode(op: OpCode): boolean { + return _.includes([OpCode.Return, OpCode.Stop, OpCode.Revert, OpCode.Invalid, OpCode.SelfDestruct], op); + }, + getAddressFromStackEntry(stackEntry: string): string { + const hexBase = 16; + return addressUtils.padZeros(new BigNumber(addHexPrefix(stackEntry)).toString(hexBase)); + }, + normalizeStructLogs(structLogs: StructLog[]): StructLog[] { + if (structLogs[0].depth === 1) { + // Geth uses 1-indexed depth counter whilst ganache starts from 0 + const newStructLogs = _.map(structLogs, structLog => ({ + ...structLog, + depth: structLog.depth - 1, + })); + return newStructLogs; + } + return structLogs; + }, + getRange(sourceCode: string, range: SingleFileSourceRange): string { + const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); + lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); + lines[0] = lines[0].slice(range.start.column); + return lines.join('\n'); + }, +}; diff --git a/packages/sol-tracing-utils/test/collect_coverage_entries_test.ts b/packages/sol-tracing-utils/test/collect_coverage_entries_test.ts new file mode 100644 index 000000000..7832ec316 --- /dev/null +++ b/packages/sol-tracing-utils/test/collect_coverage_entries_test.ts @@ -0,0 +1,155 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { collectCoverageEntries } from '../src/collect_coverage_entries'; +import { utils } from '../src/utils'; + +const expect = chai.expect; + +describe('Collect coverage entries', () => { + describe('#collectCoverageEntries', () => { + it('correctly collects coverage entries for Simplest contract', () => { + const simplestContractBaseName = 'Simplest.sol'; + const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); + const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simplestContract); + expect(coverageEntries.fnMap).to.be.deep.equal({}); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + expect(coverageEntries.statementMap).to.be.deep.equal({}); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for SimpleStorage contract', () => { + const simpleStorageContractBaseName = 'SimpleStorage.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnIds = _.keys(coverageEntries.fnMap); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(5); + const setFunction = `function set(uint x) { + storedData = x; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(8); + const getFunction = `function get() constant returns (uint retVal) { + return storedData; + }`; + expect(utils.getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( + 'storedData = x', + ); + expect(utils.getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( + 'return storedData;', + ); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for AllSolidityFeatures contract', () => { + const simpleStorageContractBaseName = 'AllSolidityFeatures.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnDescriptions = _.values(coverageEntries.fnMap); + const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name); + const expectedFnNames = [ + 'f', + 'c', + 'test', + 'getChoice', + 'Base', + 'Derived', + 'f', + 'f', + '', + 'g', + 'setData', + 'getData', + 'sendHalf', + 'insert', + 'remove', + 'contains', + 'iterate_start', + 'iterate_valid', + 'iterate_advance', + 'iterate_get', + 'insert', + 'sum', + 'restricted', + 'DualIndex', + 'set', + 'transfer_ownership', + 'lookup', + '', + '', + 'sum', + 'someFunction', + 'fun', + 'at', + 'test', + 'get', + 'returnNumber', + 'alloc', + 'ham', + 'getMyTuple', + 'ham', + 'abstain', + 'foobar', + 'foobar', + 'a', + ]; + expect(fnNames).to.be.deep.equal(expectedFnNames); + + const branchDescriptions = _.values(coverageEntries.branchMap); + const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line); + // tslint:disable-next-line:custom-no-magic-numbers + expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]); + const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type); + expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']); + }); + + it('correctly ignores all coverage entries for Ignore contract', () => { + const solcovIgnoreContractBaseName = 'SolcovIgnore.sol'; + const solcovIgnoreContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + solcovIgnoreContractBaseName, + ); + const solcovIgnoreContract = fs.readFileSync(solcovIgnoreContractFileName).toString(); + const coverageEntries = collectCoverageEntries(solcovIgnoreContract); + const fnIds = _.keys(coverageEntries.fnMap); + + expect(fnIds.length).to.be.equal(1); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + // tslint:disable-next-line:custom-no-magic-numbers + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(6); + const setFunction = `function set(uint x) public { + /* solcov ignore next */ + storedData = x; + }`; + expect(utils.getRange(solcovIgnoreContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(utils.getRange(solcovIgnoreContract, coverageEntries.statementMap[statementIds[0]])).to.be.equal( + setFunction, + ); + expect(statementIds.length).to.be.equal(1); + expect(coverageEntries.modifiersStatementIds.length).to.be.equal(0); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol b/packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol new file mode 100644 index 000000000..21137347e --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/AllSolidityFeatures.sol @@ -0,0 +1,413 @@ +// Examples taken from the Solidity documentation online. + +// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions +pragma solidity 0.4.0; +pragma solidity ^0.4.0; + +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import * as SomeSymbol from "AnotherFile.sol"; +import {symbol1 as alias, symbol2} from "File.sol"; + +interface i { + function f(); +} + +contract c { + function c() + { + val1 = 1 wei; // 1 + val2 = 1 szabo; // 1 * 10 ** 12 + val3 = 1 finney; // 1 * 10 ** 15 + val4 = 1 ether; // 1 * 10 ** 18 + } + uint256 val1; + uint256 val2; + uint256 val3; + uint256 val4; +} + +contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + + function test() + { + choices = ActionChoices.GoStraight; + } + function getChoice() returns (uint d) + { + d = uint256(choices); + } + ActionChoices choices; +} + +contract Base { + function Base(uint i) + { + m_i = i; + } + uint public m_i; +} +contract Derived is Base(0) { + function Derived(uint i) Base(i) {} +} + +contract C { + uint248 x; // 31 bytes: slot 0, offset 0 + uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0) + uint240 z; // 30 bytes: slot 1, offset 2 bytes + uint8 a; // 1 byte: slot 2, offset 0 bytes + struct S { + uint8 a; // 1 byte, slot +0, offset 0 bytes + uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit) + } + S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply) + uint8 alpha; // 1 byte, slot 4 (start new slot after struct) + uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array) + uint8 gamma; // 1 byte, slot 7 (start new slot after array) +} + +contract test { + function f(uint x, uint y) returns (uint z) { + var c = x + 3; + var b = 7 + (c * (8 - 7)) - x; + return -(-b | 0); + } +} + +contract test { + function f(uint x, uint y) returns (uint z) { + return 10; + } +} + +contract c { + function () returns (uint) { return g(8); } + function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); } + function setData(uint pos, uint value) internal { data[pos] = value; } + function getData(uint pos) internal { return data[pos]; } + mapping(uint => uint) data; +} + +contract Sharer { + function sendHalf(address addr) returns (uint balance) { + if (!addr.send(msg.value/2)) + throw; // also reverts the transfer to Sharer + return address(this).balance; + } +} + +/// @dev Models a modifiable and iterable set of uint values. +library IntegerSet +{ + struct data + { + /// Mapping item => index (or zero if not present) + mapping(uint => uint) index; + /// Items by index (index 0 is invalid), items with index[item] == 0 are invalid. + uint[] items; + /// Number of stored items. + uint size; + } + function insert(data storage self, uint value) returns (bool alreadyPresent) + { + uint index = self.index[value]; + if (index > 0) + return true; + else + { + if (self.items.length == 0) self.items.length = 1; + index = self.items.length++; + self.items[index] = value; + self.index[value] = index; + self.size++; + return false; + } + } + function remove(data storage self, uint value) returns (bool success) + { + uint index = self.index[value]; + if (index == 0) + return false; + delete self.index[value]; + delete self.items[index]; + self.size --; + } + function contains(data storage self, uint value) returns (bool) + { + return self.index[value] > 0; + } + function iterate_start(data storage self) returns (uint index) + { + return iterate_advance(self, 0); + } + function iterate_valid(data storage self, uint index) returns (bool) + { + return index < self.items.length; + } + function iterate_advance(data storage self, uint index) returns (uint r_index) + { + index++; + while (iterate_valid(self, index) && self.index[self.items[index]] == index) + index++; + return index; + } + function iterate_get(data storage self, uint index) returns (uint value) + { + return self.items[index]; + } +} + +/// How to use it: +contract User +{ + /// Just a struct holding our data. + IntegerSet.data data; + /// Insert something + function insert(uint v) returns (uint size) + { + /// Sends `data` via reference, so IntegerSet can modify it. + IntegerSet.insert(data, v); + /// We can access members of the struct - but we should take care not to mess with them. + return data.size; + } + /// Computes the sum of all stored data. + function sum() returns (uint s) + { + for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i)) + s += IntegerSet.iterate_get(data, i); + } +} + +// This broke it at one point (namely the modifiers). +contract DualIndex { + mapping(uint => mapping(uint => uint)) data; + address public admin; + + modifier restricted { if (msg.sender == admin) _; } + + function DualIndex() { + admin = msg.sender; + } + + function set(uint key1, uint key2, uint value) restricted { + uint[2][4] memory defaults; // "memory" broke things at one time. + data[key1][key2] = value; + } + + function transfer_ownership(address _admin) restricted { + admin = _admin; + } + + function lookup(uint key1, uint key2) returns(uint) { + return data[key1][key2]; + } +} + +contract A { + +} + +contract B { + +} + +contract C is A, B { + +} + +contract TestPrivate +{ + uint private value; +} + +contract TestInternal +{ + uint internal value; +} + +contract FromSolparse is A, B, TestPrivate, TestInternal { + function() { + uint a = 6 ** 9; + var (x) = 100; + uint y = 2 days; + } +} + +contract CommentedOutFunction { + // FYI: This empty function, as well as the commented + // out function below (bad code) is important to this test. + function() { + + } + + // function something() + // uint x = 10; + // } +} + +library VarHasBrackets { + string constant specialRight = "}"; + //string storage specialLeft = "{"; +} + +library UsingExampleLibrary { + function sum(uint[] storage self) returns (uint s) { + for (uint i = 0; i < self.length; i++) + s += self[i]; + } +} + +contract UsingExampleContract { + using UsingExampleLibrary for uint[]; +} + +contract NewStuff { + uint[] b; + + function someFunction() payable { + string storage a = hex"ab1248fe"; + b[2+2]; + } +} + +// modifier with expression +contract MyContract { + function fun() mymodifier(foo.bar()) {} +} + +library GetCode { + function at(address _addr) returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } +} + +contract assemblyLocalBinding { + function test(){ + assembly { + let v := 1 + let x := 0x00 + let y := x + let z := "hello" + } + } +} + +contract assemblyReturn { + uint a = 10; + + function get() constant returns(uint) { + assembly { + mstore(0x40, sload(0)) + byte(0) + address(0) + return(0x40,32) + } + } +} + +contract usesConst { + uint const = 0; +} + +contract memoryArrays { + uint seven = 7; + + function returnNumber(uint number) returns (uint){ + return number; + } + + function alloc() { + uint[] memory a = new uint[](7); + uint[] memory b = new uint[](returnNumber(seven)); + } +} + +contract DeclarativeExpressions { + uint a; + uint b = 7; + uint b2=0; + uint public c; + uint constant public d; + uint public constant e; + uint private constant f = 7; + struct S { uint q;} + + function ham(S storage s1, uint[] storage arr) internal { + uint x; + uint y = 7; + S storage s2 = s1; + uint[] memory stor; + uint[] storage stor2 = arr; + } +} + +contract VariableDeclarationTuple { + function getMyTuple() returns (bool, bool){ + return (true, false); + } + + function ham (){ + var (x, y) = (10, 20); + var (a, b) = getMyTuple(); + var (,c) = (10, 20); + var (d,,) = (10, 20, 30); + var (,e,,f,) = (10, 20, 30, 40, 50); + + var ( + num1, num2, + num3, ,num5 + ) = (10, 20, 30, 40, 50); + } +} + +contract TypeIndexSpacing { + uint [ 7 ] x; + uint [] y; +} + +contract Ballot { + + struct Voter { + uint weight; + bool voted; + } + + function abstain() returns (bool) { + return false; + } + + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {} + function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names); + + Voter you = Voter(1, true); + + Voter me = Voter({ + weight: 2, + voted: abstain() + }); + + Voter airbnb = Voter({ + weight: 2, + voted: true, + }); +} + +contract multilineReturn { + function a() returns (uint x) { + return + 5; + } +} diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol new file mode 100644 index 000000000..e4b4ac246 --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/SimpleStorage.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.21; + +contract SimpleStorage { + uint public storedData; + function set(uint x) { + storedData = x; + } + function get() constant returns (uint retVal) { + return storedData; + } +} diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol b/packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol new file mode 100644 index 000000000..d71016e07 --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/Simplest.sol @@ -0,0 +1,2 @@ +contract Simplest { +} diff --git a/packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol b/packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol new file mode 100644 index 000000000..a7977ffb4 --- /dev/null +++ b/packages/sol-tracing-utils/test/fixtures/contracts/SolcovIgnore.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.21; + +contract SolcovIgnore { + uint public storedData; + + function set(uint x) public { + /* solcov ignore next */ + storedData = x; + } + + /* solcov ignore next */ + function get() constant public returns (uint retVal) { + return storedData; + } +} + +/* solcov ignore next */ +contract Ignore { + function ignored() public returns (bool) { + return false; + } +} diff --git a/packages/sol-tracing-utils/test/instructions_test.ts b/packages/sol-tracing-utils/test/instructions_test.ts new file mode 100644 index 000000000..058053cf9 --- /dev/null +++ b/packages/sol-tracing-utils/test/instructions_test.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import 'mocha'; + +import { constants } from '../src/constants'; +import { getPcToInstructionIndexMapping } from '../src/instructions'; + +const expect = chai.expect; + +describe('instructions', () => { + describe('#getPcToInstructionIndexMapping', () => { + it('correctly maps pcs to instruction indexed', () => { + // tslint:disable-next-line:custom-no-magic-numbers + const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]); + const pcToInstruction = getPcToInstructionIndexMapping(bytecode); + const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; + expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts b/packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts new file mode 100644 index 000000000..9c58d2cef --- /dev/null +++ b/packages/sol-tracing-utils/test/sol_compiler_artifact_adapter_test.ts @@ -0,0 +1,29 @@ +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { SolCompilerArtifactAdapter } from '../src/artifact_adapters/sol_compiler_artifact_adapter'; + +const expect = chai.expect; + +describe('SolCompilerArtifactAdapter', () => { + describe('#collectContractsData', () => { + it('correctly collects contracts data', async () => { + const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); + const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); + const zeroExArtifactsAdapter = new SolCompilerArtifactAdapter(artifactsPath, sourcesPath); + const contractsData = await zeroExArtifactsAdapter.collectContractsDataAsync(); + _.forEach(contractsData, contractData => { + expect(contractData).to.have.keys([ + 'sourceCodes', + 'sources', + 'sourceMap', + 'sourceMapRuntime', + 'bytecode', + 'runtimeBytecode', + ]); + }); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/source_maps_test.ts b/packages/sol-tracing-utils/test/source_maps_test.ts new file mode 100644 index 000000000..5820bedd7 --- /dev/null +++ b/packages/sol-tracing-utils/test/source_maps_test.ts @@ -0,0 +1,71 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; + +const expect = chai.expect; + +const simplestContractBaseName = 'Simplest.sol'; +const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); +const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + +describe('source maps', () => { + describe('#getLocationByOffset', () => { + it('correctly computes location by offset', () => { + const locationByOffset = getLocationByOffset(simplestContract); + const expectedLocationByOffset = { + '0': { line: 1, column: 0 }, + '1': { line: 1, column: 1 }, + '2': { line: 1, column: 2 }, + '3': { line: 1, column: 3 }, + '4': { line: 1, column: 4 }, + '5': { line: 1, column: 5 }, + '6': { line: 1, column: 6 }, + '7': { line: 1, column: 7 }, + '8': { line: 1, column: 8 }, + '9': { line: 1, column: 9 }, + '10': { line: 1, column: 10 }, + '11': { line: 1, column: 11 }, + '12': { line: 1, column: 12 }, + '13': { line: 1, column: 13 }, + '14': { line: 1, column: 14 }, + '15': { line: 1, column: 15 }, + '16': { line: 1, column: 16 }, + '17': { line: 1, column: 17 }, + '18': { line: 1, column: 18 }, + '19': { line: 1, column: 19 }, + '20': { line: 2, column: 0 }, + '21': { line: 2, column: 1 }, + '22': { line: 3, column: 0 }, + }; + expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); + }); + }); + describe('#parseSourceMap', () => { + it('correctly parses the source map', () => { + // This is the source map and bytecode for an empty contract like Example.sol + const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; + const bytecodeHex = + '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; + const sources = [simplestContractBaseName]; + const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); + const expectedSourceRange = { + location: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 1 }, + }, + fileName: simplestContractBaseName, + }; + _.forEach(pcToSourceRange, sourceRange => { + // Solidity source maps are too short and we map some instructions to undefined + // Source: https://github.com/ethereum/solidity/issues/3741 + if (!_.isUndefined(sourceRange)) { + expect(sourceRange).to.be.deep.equal(expectedSourceRange); + } + }); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/trace_test.ts b/packages/sol-tracing-utils/test/trace_test.ts new file mode 100644 index 000000000..7a034362c --- /dev/null +++ b/packages/sol-tracing-utils/test/trace_test.ts @@ -0,0 +1,55 @@ +import * as chai from 'chai'; +import { OpCode, StructLog } from 'ethereum-types'; +import * as _ from 'lodash'; +import 'mocha'; + +import { getTracesByContractAddress } from '../src/trace'; + +const expect = chai.expect; + +const DEFAULT_STRUCT_LOG: StructLog = { + depth: 0, + error: '', + gas: 0, + gasCost: 0, + memory: [], + op: OpCode.Invalid, + pc: 0, + stack: [], + storage: {}, +}; + +function addDefaultStructLogFields(compactStructLog: Partial & { op: OpCode; depth: number }): StructLog { + return { ...DEFAULT_STRUCT_LOG, ...compactStructLog }; +} + +describe('Trace', () => { + describe('#getTracesByContractAddress', () => { + it('correctly splits trace by contract address', () => { + const delegateCallAddress = '0x0000000000000000000000000000000000000002'; + const trace = [ + { + op: OpCode.DelegateCall, + stack: [delegateCallAddress, '0x'], + depth: 0, + }, + { + op: OpCode.Return, + depth: 1, + }, + { + op: OpCode.Return, + depth: 0, + }, + ]; + const fullTrace = _.map(trace, compactStructLog => addDefaultStructLogFields(compactStructLog)); + const startAddress = '0x0000000000000000000000000000000000000001'; + const traceByContractAddress = getTracesByContractAddress(fullTrace, startAddress); + const expectedTraceByContractAddress = { + [startAddress]: [fullTrace[0], fullTrace[2]], + [delegateCallAddress]: [fullTrace[1]], + }; + expect(traceByContractAddress).to.be.deep.equal(expectedTraceByContractAddress); + }); + }); +}); diff --git a/packages/sol-tracing-utils/test/utils_test.ts b/packages/sol-tracing-utils/test/utils_test.ts new file mode 100644 index 000000000..6fc8fcfe1 --- /dev/null +++ b/packages/sol-tracing-utils/test/utils_test.ts @@ -0,0 +1,53 @@ +import * as chai from 'chai'; +import * as dirtyChai from 'dirty-chai'; +import 'mocha'; + +import { utils } from '../src/utils'; + +chai.use(dirtyChai); +const expect = chai.expect; + +describe('utils', () => { + describe('#compareLineColumn', () => { + it('correctly compares LineColumns', () => { + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); + expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); + }); + }); + + describe('#isRangeInside', () => { + it('returns true if inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, + ), + ).to.be.true(); + }); + it('returns true if the same', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + ), + ).to.be.true(); + }); + it('returns false if not inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, + ), + ).to.be.false(); + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + ), + ).to.be.false(); + }); + }); +}); diff --git a/packages/sol-tracing-utils/tsconfig.json b/packages/sol-tracing-utils/tsconfig.json new file mode 100644 index 000000000..2ee711adc --- /dev/null +++ b/packages/sol-tracing-utils/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": "." + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-tracing-utils/tslint.json b/packages/sol-tracing-utils/tslint.json new file mode 100644 index 000000000..dd9053357 --- /dev/null +++ b/packages/sol-tracing-utils/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0x/tslint-config"] +} diff --git a/tsconfig.json b/tsconfig.json index 1f5da33dc..a49483972 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -55,7 +55,7 @@ { "path": "./packages/sol-coverage" }, { "path": "./packages/sol-profiler" }, { "path": "./packages/sol-trace" }, - { "path": "./packages/sol-trace-based-tools-common" }, + { "path": "./packages/sol-tracing-utils" }, { "path": "./packages/sol-resolver" }, { "path": "./packages/sra-spec" }, { "path": "./packages/subproviders" }, -- cgit v1.2.3 From a8d9263062e586b90ee4c303d3d3aca72e428edc Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 11:42:50 +0100 Subject: Add .gitkeep --- packages/sol-tracing-utils/coverage/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/sol-tracing-utils/coverage/.gitkeep diff --git a/packages/sol-tracing-utils/coverage/.gitkeep b/packages/sol-tracing-utils/coverage/.gitkeep new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From 4c60d591234af87f296884a9ee438960d928e010 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 11:57:18 +0100 Subject: Remove second yarn call --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 25c008c20..f64017b56 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ jobs: command: sudo npm install --global yarn@1.9.4 - run: name: yarn - command: yarn --frozen-lockfile --ignore-engines install || yarn --frozen-lockfile --ignore-engines install + command: yarn --frozen-lockfile --ignore-engines install - run: yarn build:ci:no_website - run: yarn build:ts - save_cache: -- cgit v1.2.3 From c317a69e7e599e79e6b2e841211cc9ceaffa3c6f Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 13:33:56 +0100 Subject: Add missing dependencies --- packages/metacoin/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index 0081e8be1..6812ff823 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -32,6 +32,9 @@ "@0x/abi-gen": "^1.0.20", "@0x/abi-gen-templates": "^1.0.1", "@0x/base-contract": "^3.0.11", + "@0x/sol-coverage": "^1.0.0", + "@0x/sol-profiler": "^1.0.0", + "@0x/sol-trace": "^1.0.0", "@0x/subproviders": "^2.1.9", "@0x/tslint-config": "^2.0.0", "@0x/types": "^1.5.0", -- cgit v1.2.3 From b8e3829fdbd1f516686618562172cb45fbb63bde Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 10 Jan 2019 14:05:29 +0100 Subject: Fix prettier --- packages/sol-coverage/CHANGELOG.json | 3 +-- packages/sol-profiler/CHANGELOG.json | 3 +-- packages/sol-trace/CHANGELOG.json | 3 +-- yarn.lock | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json index c650a4a4b..223400eae 100644 --- a/packages/sol-coverage/CHANGELOG.json +++ b/packages/sol-coverage/CHANGELOG.json @@ -3,8 +3,7 @@ "version": "1.0.0", "changes": [ { - "note": - "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", + "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-profiler/CHANGELOG.json b/packages/sol-profiler/CHANGELOG.json index c650a4a4b..223400eae 100644 --- a/packages/sol-profiler/CHANGELOG.json +++ b/packages/sol-profiler/CHANGELOG.json @@ -3,8 +3,7 @@ "version": "1.0.0", "changes": [ { - "note": - "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", + "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/packages/sol-trace/CHANGELOG.json b/packages/sol-trace/CHANGELOG.json index c650a4a4b..223400eae 100644 --- a/packages/sol-trace/CHANGELOG.json +++ b/packages/sol-trace/CHANGELOG.json @@ -3,8 +3,7 @@ "version": "1.0.0", "changes": [ { - "note": - "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", + "note": "Initial release as a separate package. For historical entries see @0x/sol-tracing-utils", "pr": 1492 } ] diff --git a/yarn.lock b/yarn.lock index 4dd15df4d..19d3c0213 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17366,7 +17366,7 @@ socks@~2.2.0: ip "^1.1.5" smart-buffer "^4.0.1" -solc@0.4.24, solc@^0.4.24: +solc@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.24.tgz#354f14b269b38cbaa82a47d1ff151723502b954e" integrity sha512-2xd7Cf1HeVwrIb6Bu1cwY2/TaLRodrppCq3l7rhLimFQgmxptXhTC3+/wesVLpB09F1A2kZgvbMOgH7wvhFnBQ== -- cgit v1.2.3