diff --git a/.travis.yml b/.travis.yml index b33582446..088d1a14f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ env: - TEST_SUITE=scripts/travis_test_printers.sh - TEST_SUITE=scripts/travis_test_slither_config.sh - TEST_SUITE=scripts/travis_test_simil.sh + - TEST_SUITE=scripts/travis_test_erc.sh + - TEST_SUITE=scripts/travis_test_kspec.sh branches: only: - master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..8e767e47c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing to Slither +First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements. + +If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels. + +# Bug reports and feature suggestions +Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead. + +# Questions +Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel). + +# Code +Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/). + +Some pull request guidelines: + +- Work from the [`dev`](https://github.com/crytic/slither/tree/dev) branch. We performed extensive tests prior to merging anything to `master`, working from `dev` will allow us to merge your work faster. +- Minimize irrelevant changes (formatting, whitespace, etc) to code that would otherwise not be touched by this patch. Save formatting or style corrections for a separate pull request that does not make any semantic changes. +- When possible, large changes should be split up into smaller focused pull requests. +- Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable. +- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own. + +# Development Environment +Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation). diff --git a/README.md b/README.md index 5d0ff6c85..063cc6d33 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ Run Slither on a single file: $ slither tests/uninitialized.sol ``` -For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. +For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. + +Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. ## Detectors @@ -61,20 +63,22 @@ Num | Detector | What it Detects | Impact | Confidence 20 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium 21 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High 22 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High -23 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium -24 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium -25 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium -26 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High -27 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High -28 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High -29 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High -30 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High -31 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High -32 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High -33 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High -34 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium -35 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High -36 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Optimization | High +23 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High +24 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium +25 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium +26 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium +27 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High +28 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High +29 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High +30 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High +31 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High +32 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High +33 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High +34 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High +35 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium +36 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High +37 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Optimization | High + [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. @@ -86,23 +90,25 @@ Num | Printer | Description --- | --- | --- 1 | `call-graph` | [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph) 2 | `cfg` | [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg) -3 | `contract-summary` | [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary) -4 | `data-dependency` | [Print the data dependencies of the variables](https://github.com/trailofbits/slither/wiki/Printer-documentation#data-dependencies) -5 | `function-id` | [Print the keccack256 signature of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id) -6 | `function-summary` | [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary) -7 | `human-summary` | [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary) -8 | `inheritance` | [Print the inheritance relations between contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance) -9 | `inheritance-graph` | [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph) -10 | `modifiers` | [Print the modifiers called by each function](https://github.com/trailofbits/slither/wiki/Printer-documentation#modifiers) -11 | `require` | [Print the require and assert calls of each function](https://github.com/trailofbits/slither/wiki/Printer-documentation#require) -12 | `slithir` | [Print the slithIR representation of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir) -13 | `slithir-ssa` | [Print the slithIR representation of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir-ssa) -14 | `variable-order` | [Print the storage order of the state variables](https://github.com/trailofbits/slither/wiki/Printer-documentation#variable-order) -15 | `vars-and-auth` | [Print the state variables written and the authorization of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization) +3 | `constructor-calls` | [Print the constructors executed](https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls) +4 | `contract-summary` | [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary) +5 | `data-dependency` | [Print the data dependencies of the variables](https://github.com/trailofbits/slither/wiki/Printer-documentation#data-dependencies) +6 | `echidna` | [Export Echidna guiding information](https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna) +7 | `function-id` | [Print the keccack256 signature of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id) +8 | `function-summary` | [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary) +9 | `human-summary` | [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary) +10 | `inheritance` | [Print the inheritance relations between contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance) +11 | `inheritance-graph` | [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph) +12 | `modifiers` | [Print the modifiers called by each function](https://github.com/trailofbits/slither/wiki/Printer-documentation#modifiers) +13 | `require` | [Print the require and assert calls of each function](https://github.com/trailofbits/slither/wiki/Printer-documentation#require) +14 | `slithir` | [Print the slithIR representation of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir) +15 | `slithir-ssa` | [Print the slithIR representation of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir-ssa) +16 | `variable-order` | [Print the storage order of the state variables](https://github.com/trailofbits/slither/wiki/Printer-documentation#variable-order) +17 | `vars-and-auth` | [Print the state variables written and the authorization of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization) ## How to install -Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. +Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. ### Using Pip @@ -113,7 +119,7 @@ $ pip install slither-analyzer ### Using Git ```bash -$ git clone https://github.com/trailofbits/slither.git && cd slither +$ git clone https://github.com/crytic/slither.git && cd slither $ python setup.py install ``` diff --git a/examples/printers/constructors.sol b/examples/printers/constructors.sol new file mode 100644 index 000000000..845039032 --- /dev/null +++ b/examples/printers/constructors.sol @@ -0,0 +1,26 @@ +pragma solidity >=0.4.22 <0.6.0; +contract test{ + uint a; + constructor()public{ + a =5; + } + +} +contract test2 is test{ + constructor()public{ + a=10; + } +} +contract test3 is test2{ + address owner; + bytes32 name; + constructor(bytes32 _name)public{ + owner = msg.sender; + name = _name; + a=20; + } + function print() public returns(uint b){ + b=a; + + } +} \ No newline at end of file diff --git a/plugin_example/slither_my_plugin/detectors/example.py b/plugin_example/slither_my_plugin/detectors/example.py index ed850d361..5d5a8811e 100644 --- a/plugin_example/slither_my_plugin/detectors/example.py +++ b/plugin_example/slither_my_plugin/detectors/example.py @@ -23,6 +23,6 @@ class Example(AbstractDetector): info = 'This is an example!' - json = self.generate_json_result(info) + json = self.generate_result(info) return [json] diff --git a/scripts/tests_generate_expected_json_4.sh b/scripts/tests_generate_expected_json_4.sh index 4e1def80c..790763ffe 100755 --- a/scripts/tests_generate_expected_json_4.sh +++ b/scripts/tests_generate_expected_json_4.sh @@ -54,5 +54,5 @@ generate_expected_json(){ #generate_expected_json tests/shadowing_builtin_symbols.sol "shadowing-builtin" #generate_expected_json tests/shadowing_local_variable.sol "shadowing-local" #generate_expected_json tests/solc_version_incorrect.sol "solc-version" -#generate_expected_json tests/right_to_left_override.sol "rtlo" +generate_expected_json tests/right_to_left_override.sol "rtlo" #generate_expected_json tests/unchecked_lowlevel.sol "unchecked-lowlevel" diff --git a/scripts/tests_generate_expected_json_5.sh b/scripts/tests_generate_expected_json_5.sh index fb9552437..620fc0b0c 100755 --- a/scripts/tests_generate_expected_json_5.sh +++ b/scripts/tests_generate_expected_json_5.sh @@ -20,6 +20,7 @@ generate_expected_json(){ sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i } +#generate_expected_json tests/void-cst.sol "void-cst" #generate_expected_json tests/solc_version_incorrect_05.ast.json "solc-version" #generate_expected_json tests/uninitialized-0.5.1.sol "uninitialized-state" #generate_expected_json tests/backdoor.sol "backdoor" diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh index e043c9c16..2f55834a3 100755 --- a/scripts/travis_install.sh +++ b/scripts/travis_install.sh @@ -1,12 +1,5 @@ #!/usr/bin/env bash -# TODO: temporary until the next crytic-compile release -git clone https://github.com/crytic/crytic-compile -cd crytic-compile -git checkout dev -python setup.py install -cd .. - python setup.py install # Used by travis_test.sh pip install deepdiff diff --git a/scripts/travis_test_5.sh b/scripts/travis_test_5.sh index 7224638d7..4b3928b5c 100755 --- a/scripts/travis_test_5.sh +++ b/scripts/travis_test_5.sh @@ -69,6 +69,7 @@ test_slither(){ } +test_slither tests/void-cst.sol "void-cst" test_slither tests/solc_version_incorrect_05.ast.json "solc-version" test_slither tests/unchecked_lowlevel-0.5.1.sol "unchecked-lowlevel" test_slither tests/unchecked_send-0.5.1.sol "unchecked-send" diff --git a/scripts/travis_test_dapp.sh b/scripts/travis_test_dapp.sh index 934261312..05a0b823f 100755 --- a/scripts/travis_test_dapp.sh +++ b/scripts/travis_test_dapp.sh @@ -7,6 +7,9 @@ cd test_dapp curl https://nixos.org/nix/install | sh . "$HOME/.nix-profile/etc/profile.d/nix.sh" +nix-env -iA nixpkgs.cachix +cachix use dapp + git clone --recursive https://github.com/dapphub/dapptools $HOME/.dapp/dapptools nix-env -f $HOME/.dapp/dapptools -iA dapp seth solc hevm ethsign @@ -14,7 +17,7 @@ dapp init slither . -if [ $? -eq 23 ] +if [ $? -eq 22 ] then exit 0 fi diff --git a/scripts/travis_test_embark.sh b/scripts/travis_test_embark.sh index 1e9d504c9..eeba9f108 100755 --- a/scripts/travis_test_embark.sh +++ b/scripts/travis_test_embark.sh @@ -7,8 +7,8 @@ cd test_embark curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash source ~/.nvm/nvm.sh -nvm install --lts -nvm use --lts +nvm install 10.17.0 +nvm use 10.17.0 npm --version npm install -g embark diff --git a/scripts/travis_test_erc.sh b/scripts/travis_test_erc.sh new file mode 100755 index 000000000..908760631 --- /dev/null +++ b/scripts/travis_test_erc.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +### Test slither-check-erc + +DIR_TESTS="tests/check-erc" + +slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 --solc solc-0.5.0 > test_1.txt 2>&1 +DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-erc 1 failed" + cat test_1.txt + echo "" + cat "$DIR_TESTS/test_1.txt" + exit -1 +fi + + +rm test_1.txt + diff --git a/scripts/travis_test_etherlime.sh b/scripts/travis_test_etherlime.sh index 226dbba66..d0b5b43cc 100755 --- a/scripts/travis_test_etherlime.sh +++ b/scripts/travis_test_etherlime.sh @@ -7,8 +7,8 @@ cd test_etherlime curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash source ~/.nvm/nvm.sh -nvm install --lts -nvm use --lts +nvm install 10.17.0 +nvm use 10.17.0 npm i -g etherlime etherlime init diff --git a/scripts/travis_test_etherscan.sh b/scripts/travis_test_etherscan.sh index c96eca12e..57b026cc5 100755 --- a/scripts/travis_test_etherscan.sh +++ b/scripts/travis_test_etherscan.sh @@ -18,7 +18,7 @@ fi slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --solc "./solc-0.4.25" -if [ $? -ne 76 ] +if [ $? -ne 70 ] then echo "Etherscan test failed" exit -1 diff --git a/scripts/travis_test_kspec.sh b/scripts/travis_test_kspec.sh new file mode 100755 index 000000000..341af9526 --- /dev/null +++ b/scripts/travis_test_kspec.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +DIR_TESTS="tests/check-kspec" + +slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md" --solc solc-0.5.0 > test_1.txt 2>&1 +DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 1 failed" + cat test_1.txt + echo "" + cat "$DIR_TESTS/test_1.txt" + exit -1 +fi + +rm test_1.txt diff --git a/scripts/travis_test_upgradability.sh b/scripts/travis_test_upgradability.sh index d840a2fb3..610356c92 100755 --- a/scripts/travis_test_upgradability.sh +++ b/scripts/travis_test_upgradability.sh @@ -4,7 +4,7 @@ DIR_TESTS="tests/check-upgradeability" -slither-check-upgradeability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 > test_1.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") if [ "$DIFF" != "" ] then @@ -15,7 +15,7 @@ then exit -1 fi -slither-check-upgradeability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-version "$DIR_TESTS/contractV2.sol" --new-contract-name ContractV2 > test_2.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2.sol" --new-contract-name ContractV2 > test_2.txt 2>&1 DIFF=$(diff test_2.txt "$DIR_TESTS/test_2.txt") if [ "$DIFF" != "" ] then @@ -26,7 +26,7 @@ then exit -1 fi -slither-check-upgradeability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-version "$DIR_TESTS/contractV2_bug.sol" --new-contract-name ContractV2 > test_3.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2_bug.sol" --new-contract-name ContractV2 > test_3.txt 2>&1 DIFF=$(diff test_3.txt "$DIR_TESTS/test_3.txt") if [ "$DIFF" != "" ] then @@ -37,7 +37,7 @@ then exit -1 fi -slither-check-upgradeability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-version "$DIR_TESTS/contractV2_bug2.sol" --new-contract-name ContractV2 > test_4.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2_bug2.sol" --new-contract-name ContractV2 > test_4.txt 2>&1 DIFF=$(diff test_4.txt "$DIR_TESTS/test_4.txt") if [ "$DIFF" != "" ] then @@ -48,7 +48,7 @@ then exit -1 fi -slither-check-upgradeability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --solc solc-0.5.0 > test_5.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_5.txt 2>&1 DIFF=$(diff test_5.txt "$DIR_TESTS/test_5.txt") if [ "$DIFF" != "" ] then @@ -61,9 +61,107 @@ then exit -1 fi +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_5.txt 2>&1 +DIFF=$(diff test_5.txt "$DIR_TESTS/test_5.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 5 failed" + cat test_5.txt + echo "" + cat "$DIR_TESTS/test_5.txt" + echo "" + echo "$DIFF" + exit -1 +fi + + +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_lack_to_call_modifier --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_6.txt 2>&1 +DIFF=$(diff test_6.txt "$DIR_TESTS/test_6.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 6 failed" + cat test_6.txt + echo "" + cat "$DIR_TESTS/test_6.txt" + echo "" + echo "$DIFF" + exit -1 +fi + + +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_not_called_super_init --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_7.txt 2>&1 +DIFF=$(diff test_7.txt "$DIR_TESTS/test_7.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 7 failed" + cat test_7.txt + echo "" + cat "$DIR_TESTS/test_7.txt" + echo "" + echo "$DIFF" + exit -1 +fi + +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_inherits --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_8.txt 2>&1 +DIFF=$(diff test_8.txt "$DIR_TESTS/test_8.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 8 failed" + cat test_8.txt + echo "" + cat "$DIR_TESTS/test_8.txt" + echo "" + echo "$DIFF" + exit -1 +fi + +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_double_call --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_9.txt 2>&1 +DIFF=$(diff test_9.txt "$DIR_TESTS/test_9.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 9 failed" + cat test_9.txt + echo "" + cat "$DIR_TESTS/test_9.txt" + echo "" + echo "$DIFF" + exit -1 +fi + +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contract_v2_constant.sol" --new-contract-name ContractV2 > test_10.txt 2>&1 +DIFF=$(diff test_10.txt "$DIR_TESTS/test_10.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 10 failed" + cat test_10.txt + echo "" + cat "$DIR_TESTS/test_10.txt" + echo "" + echo "$DIFF" + exit -1 +fi + +slither-check-upgradeability "$DIR_TESTS/contract_v1_var_init.sol" ContractV1 --solc solc-0.5.0 > test_11.txt 2>&1 +DIFF=$(diff test_11.txt "$DIR_TESTS/test_11.txt") +if [ "$DIFF" != "" ] +then + echo "slither-check-upgradeability 11 failed" + cat test_11.txt + echo "" + cat "$DIR_TESTS/test_11.txt" + echo "" + echo "$DIFF" + exit -1 +fi + rm test_1.txt rm test_2.txt rm test_3.txt rm test_4.txt rm test_5.txt - +rm test_6.txt +rm test_7.txt +rm test_8.txt +rm test_9.txt +rm test_10.txt +rm test_11.txt diff --git a/setup.py b/setup.py index d6642a502..d1064dbd0 100644 --- a/setup.py +++ b/setup.py @@ -5,18 +5,25 @@ setup( description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/crytic/slither', author='Trail of Bits', - version='0.6.4', + version='0.6.7', packages=find_packages(), python_requires='>=3.6', - install_requires=['prettytable>=0.7.2', 'pysha3>=1.0.2', 'crytic-compile>=0.1.1'], + install_requires=['prettytable>=0.7.2', + 'pysha3>=1.0.2', + 'crytic-compile>=0.1.4'], +# dependency_links=['git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile'], license='AGPL-3.0', long_description=open('README.md').read(), entry_points={ 'console_scripts': [ 'slither = slither.__main__:main', - 'slither-check-upgradeability = utils.upgradeability.__main__:main', - 'slither-find-paths = utils.possible_paths.__main__:main', - 'slither-simil = utils.similarity.__main__:main' + 'slither-check-upgradeability = slither.tools.upgradeability.__main__:main', + 'slither-find-paths = slither.tools.possible_paths.__main__:main', + 'slither-simil = slither.tools.similarity.__main__:main', + 'slither-flat = slither.tools.flattening.__main__:main', + 'slither-format = slither.tools.slither_format.__main__:main', + 'slither-check-erc = slither.tools.erc_conformance.__main__:main', + 'slither-check-kspec = slither.tools.kspec_coverage.__main__:main' ] } ) diff --git a/slither/__main__.py b/slither/__main__.py index a1b4ce4ad..e982b8cde 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -6,12 +6,12 @@ import inspect import json import logging import os -import subprocess import sys import traceback from pkg_resources import iter_entry_points, require from crytic_compile import cryticparser +from crytic_compile.platform.standard import generate_standard_export from slither.detectors import all_detectors from slither.detectors.abstract_detector import (AbstractDetector, @@ -19,11 +19,14 @@ from slither.detectors.abstract_detector import (AbstractDetector, from slither.printers import all_printers from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither +from slither.utils.output import output_to_json +from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, - output_detectors_json, output_printers, - output_to_markdown, output_wiki) -from crytic_compile import is_supported + output_detectors_json, output_printers, output_printers_json, + output_to_markdown, output_wiki, defaults_flag_in_config, + read_config_file, JSON_OUTPUT_TYPES, DEFAULT_JSON_OUTPUT_TYPES) +from crytic_compile import compile_all, is_supported from slither.exceptions import SlitherException logging.basicConfig() @@ -35,7 +38,8 @@ logger = logging.getLogger("Slither") ################################################################################### ################################################################################### -def process(filename, args, detector_classes, printer_classes): + +def process_single(target, args, detector_classes, printer_classes): """ The core high-level code for running Slither static analysis. @@ -45,13 +49,28 @@ def process(filename, args, detector_classes, printer_classes): ast = '--ast-compact-json' if args.legacy_ast: ast = '--ast-json' - args.filter_paths = parse_filter_paths(args) - slither = Slither(filename, + slither = Slither(target, ast_format=ast, **vars(args)) return _process(slither, detector_classes, printer_classes) + +def process_all(target, args, detector_classes, printer_classes): + compilations = compile_all(target, **vars(args)) + slither_instances = [] + results_detectors = [] + results_printers = [] + analyzed_contracts_count = 0 + for compilation in compilations: + (slither, current_results_detectors, current_results_printers, current_analyzed_count) = process_single(compilation, args, detector_classes, printer_classes) + results_detectors.extend(current_results_detectors) + results_printers.extend(current_results_printers) + slither_instances.append(slither) + analyzed_contracts_count += current_analyzed_count + return slither_instances, results_detectors, results_printers, analyzed_contracts_count + + def _process(slither, detector_classes, printer_classes): for detector_cls in detector_classes: slither.register_detector(detector_cls) @@ -61,21 +80,24 @@ def _process(slither, detector_classes, printer_classes): analyzed_contracts_count = len(slither.contracts) - results = [] + results_detectors = [] + results_printers = [] if not printer_classes: detector_results = slither.run_detectors() detector_results = [x for x in detector_results if x] # remove empty results detector_results = [item for sublist in detector_results for item in sublist] # flatten + results_detectors.extend(detector_results) - results.extend(detector_results) - - slither.run_printers() # Currently printers does not return results + else: + printer_results = slither.run_printers() + printer_results = [x for x in printer_results if x] # remove empty results + results_printers.extend(printer_results) - return results, analyzed_contracts_count + return slither, results_detectors, results_printers, analyzed_contracts_count -def process_files(filenames, args, detector_classes, printer_classes): +def process_from_asts(filenames, args, detector_classes, printer_classes): all_contracts = [] for filename in filenames: @@ -83,54 +105,10 @@ def process_files(filenames, args, detector_classes, printer_classes): contract_loaded = json.load(f) all_contracts.append(contract_loaded['ast']) - slither = Slither(all_contracts, - solc=args.solc, - disable_solc_warnings=args.disable_solc_warnings, - solc_arguments=args.solc_args, - filter_paths=parse_filter_paths(args), - triage_mode=args.triage_mode, - exclude_dependencies=args.exclude_dependencies) - - return _process(slither, detector_classes, printer_classes) + return process_single(all_contracts, args, detector_classes, printer_classes) -# endregion -################################################################################### -################################################################################### -# region Output -################################################################################### -################################################################################### -def wrap_json_detectors_results(success, error_message, results=None): - """ - Wrap the detector results. - :param success: - :param error_message: - :param results: - :return: - """ - results_json = {} - if results: - results_json['detectors'] = results - return { - "success": success, - "error": error_message, - "results": results_json - } - - -def output_json(results, filename): - json_result = wrap_json_detectors_results(True, None, results) - if filename is None: - # Write json to console - print(json.dumps(json_result)) - else: - # Write json to file - if os.path.isfile(filename): - logger.info(yellow(f'{filename} exists already, the overwrite is prevented')) - else: - with open(filename, 'w', encoding='utf8') as f: - json.dump(json_result, f, indent=2) # endregion ################################################################################### @@ -203,6 +181,10 @@ def choose_detectors(args, all_detector_classes): detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) return detectors_to_run + if args.exclude_optimization: + detectors_to_run = [d for d in detectors_to_run if + d.IMPACT != DetectorClassification.OPTIMIZATION] + if args.exclude_informational: detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL] @@ -254,31 +236,6 @@ def parse_filter_paths(args): return args.filter_paths.split(',') return [] -# Those are the flags shared by the command line and the config file -defaults_flag_in_config = { - 'detectors_to_run': 'all', - 'printers_to_run': None, - 'detectors_to_exclude': None, - 'exclude_dependencies': False, - 'exclude_informational': False, - 'exclude_low': False, - 'exclude_medium': False, - 'exclude_high': False, - 'solc': 'solc', - 'solc_args': None, - 'disable_solc_warnings': False, - 'json': None, - 'truffle_version': None, - 'disable_color': False, - 'filter_paths': None, - 'truffle_ignore_compile': False, - 'truffle_build_directory': 'build/contracts', - 'embark_ignore_compile': False, - 'embark_overwrite_config': False, - # debug command - 'legacy_ast': False, - 'ignore_return_value': False - } def parse_args(detector_classes, printer_classes): parser = argparse.ArgumentParser(description='Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage', @@ -296,7 +253,7 @@ def parse_args(detector_classes, printer_classes): group_detector = parser.add_argument_group('Detectors') group_printer = parser.add_argument_group('Printers') - group_misc = parser.add_argument_group('Additional option') + group_misc = parser.add_argument_group('Additional options') group_detector.add_argument('--detect', help='Comma-separated list of detectors, defaults to all, ' @@ -337,6 +294,11 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=defaults_flag_in_config['exclude_dependencies']) + group_detector.add_argument('--exclude-optimization', + help='Exclude optimization analyses', + action='store_true', + default=defaults_flag_in_config['exclude_optimization']) + group_detector.add_argument('--exclude-informational', help='Exclude informational impact analyses', action='store_true', @@ -357,12 +319,22 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=defaults_flag_in_config['exclude_high']) - group_misc.add_argument('--json', help='Export the results as a JSON file ("--json -" to export to stdout)', action='store', default=defaults_flag_in_config['json']) + group_misc.add_argument('--json-types', + help=f'Comma-separated list of result types to output to JSON, defaults to ' +\ + f'{",".join(output_type for output_type in DEFAULT_JSON_OUTPUT_TYPES)}. ' +\ + f'Available types: {",".join(output_type for output_type in JSON_OUTPUT_TYPES)}', + action='store', + default=defaults_flag_in_config['json-types']) + + group_misc.add_argument('--markdown-root', + help='URL for markdown generation', + action='store', + default="") group_misc.add_argument('--disable-color', help='Disable output colorization', @@ -392,6 +364,11 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=False) + group_misc.add_argument('--generate-patches', + help='Generate patches (json output only)', + action='store_true', + default=False) + # debugger command parser.add_argument('--debug', help=argparse.SUPPRESS, @@ -403,7 +380,6 @@ def parse_args(detector_classes, printer_classes): action=OutputMarkdown, default=False) - group_misc.add_argument('--checklist', help=argparse.SUPPRESS, action='store_true', @@ -441,19 +417,15 @@ def parse_args(detector_classes, printer_classes): sys.exit(1) args = parser.parse_args() + read_config_file(args) - if os.path.isfile(args.config_file): - try: - with open(args.config_file) as f: - config = json.load(f) - for key, elem in config.items(): - if key not in defaults_flag_in_config: - logger.info(yellow('{} has an unknown key: {} : {}'.format(args.config_file, key, elem))) - continue - if getattr(args, key) == defaults_flag_in_config[key]: - setattr(args, key, elem) - except json.decoder.JSONDecodeError as e: - logger.error(red('Impossible to read {}, please check the file {}'.format(args.config_file, e))) + args.filter_paths = parse_filter_paths(args) + + # Verify our json-type output is valid + args.json_types = set(args.json_types.split(',')) + for json_type in args.json_types: + if json_type not in JSON_OUTPUT_TYPES: + raise Exception(f"Error: \"{json_type}\" is not a valid JSON result output type.") return args @@ -466,7 +438,8 @@ class ListDetectors(argparse.Action): class ListDetectorsJson(argparse.Action): def __call__(self, parser, *args, **kwargs): detectors, _ = get_detectors_and_printers() - output_detectors_json(detectors) + detector_types_json = output_detectors_json(detectors) + print(json.dumps(detector_types_json)) parser.exit() class ListPrinters(argparse.Action): @@ -526,15 +499,23 @@ def main_impl(all_detector_classes, all_printer_classes): :param all_detector_classes: A list of all detectors that can be included/excluded. :param all_printer_classes: A list of all printers that can be included. """ + # Set logger of Slither to info, to catch warnings related to the arg parsing + logger.setLevel(logging.INFO) args = parse_args(all_detector_classes, all_printer_classes) # Set colorization option set_colorization_enabled(not args.disable_color) - # If we are outputting json to stdout, we'll want to disable any logging. - stdout_json = args.json == "-" - if stdout_json: - logging.disable(logging.CRITICAL) + # Define some variables for potential JSON output + json_results = {} + output_error = None + outputting_json = args.json is not None + outputting_json_stdout = args.json == '-' + + # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout + # output. + if outputting_json: + StandardOutputCapture.enable(outputting_json_stdout) printer_classes = choose_printers(args, all_printer_classes) detector_classes = choose_detectors(args, all_detector_classes) @@ -567,66 +548,103 @@ def main_impl(all_detector_classes, all_printer_classes): crytic_compile_error.propagate = False crytic_compile_error.setLevel(logging.INFO) + results_detectors = [] + results_printers = [] try: filename = args.filename - globbed_filenames = glob.glob(filename, recursive=True) - - if os.path.isfile(filename) or is_supported(filename): - (results, number_contracts) = process(filename, args, detector_classes, printer_classes) - - elif os.path.isdir(filename) or len(globbed_filenames) > 0: - extension = "*.sol" if not args.solc_ast else "*.json" - filenames = glob.glob(os.path.join(filename, extension)) + # Determine if we are handling ast from solc + if args.solc_ast or (filename.endswith('.json') and not is_supported(filename)): + globbed_filenames = glob.glob(filename, recursive=True) + filenames = glob.glob(os.path.join(filename, "*.json")) if not filenames: filenames = globbed_filenames number_contracts = 0 - results = [] - if args.splitted and args.solc_ast: - (results, number_contracts) = process_files(filenames, args, detector_classes, printer_classes) + + slither_instances = [] + if args.splitted: + (slither_instance, results_detectors, results_printers, number_contracts) = process_from_asts(filenames, args, detector_classes, printer_classes) + slither_instances.append(slither_instance) else: for filename in filenames: - (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) + (slither_instance, results_detectors_tmp, results_printers_tmp, number_contracts_tmp) = process_single(filename, args, detector_classes, printer_classes) number_contracts += number_contracts_tmp - results += results_tmp + results_detectors += results_detectors_tmp + results_printers += results_printers_tmp + slither_instances.append(slither_instance) + # Rely on CryticCompile to discern the underlying type of compilations. else: - raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) - - if args.json: - output_json(results, None if stdout_json else args.json) + (slither_instances, results_detectors, results_printers, number_contracts) = process_all(filename, args, detector_classes, printer_classes) + + # Determine if we are outputting JSON + if outputting_json: + # Add our compilation information to JSON + if 'compilations' in args.json_types: + compilation_results = [] + for slither_instance in slither_instances: + compilation_results.append(generate_standard_export(slither_instance.crytic_compile)) + json_results['compilations'] = compilation_results + + # Add our detector results to JSON if desired. + if results_detectors and 'detectors' in args.json_types: + json_results['detectors'] = results_detectors + + # Add our printer results to JSON if desired. + if results_printers and 'printers' in args.json_types: + json_results['printers'] = results_printers + + # Add our detector types to JSON + if 'list-detectors' in args.json_types: + detectors, _ = get_detectors_and_printers() + json_results['list-detectors'] = output_detectors_json(detectors) + + # Add our detector types to JSON + if 'list-printers' in args.json_types: + _, printers = get_detectors_and_printers() + json_results['list-printers'] = output_printers_json(printers) + + # Output our results to markdown if we wish to compile a checklist. if args.checklist: - output_results_to_markdown(results) + output_results_to_markdown(results_detectors) + # Dont print the number of result for printers if number_contracts == 0: - logger.warn(red('No contract was analyzed')) + logger.warning(red('No contract was analyzed')) if printer_classes: logger.info('%s analyzed (%d contracts)', filename, number_contracts) else: - logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) + logger.info('%s analyzed (%d contracts with %d detectors), %d result(s) found', filename, + number_contracts, len(detector_classes), len(results_detectors)) if args.ignore_return_value: return - exit(results) except SlitherException as se: - # Output our error accordingly, via JSON or logging. - if stdout_json: - print(json.dumps(wrap_json_detectors_results(False, str(se), []))) - else: - logging.error(red('Error:')) - logging.error(red(se)) - logging.error('Please report an issue to https://github.com/crytic/slither/issues') - sys.exit(-1) + output_error = str(se) + logging.error(red('Error:')) + logging.error(red(output_error)) + logging.error('Please report an issue to https://github.com/crytic/slither/issues') except Exception: - # Output our error accordingly, via JSON or logging. - if stdout_json: - print(json.dumps(wrap_json_detectors_results(False, traceback.format_exc(), []))) - else: - logging.error('Error in %s' % args.filename) - logging.error(traceback.format_exc()) + output_error = traceback.format_exc() + logging.error('Error in %s' % args.filename) + logging.error(output_error) + + # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. + if outputting_json: + if 'console' in args.json_types: + json_results['console'] = { + 'stdout': StandardOutputCapture.get_stdout_output(), + 'stderr': StandardOutputCapture.get_stderr_output() + } + StandardOutputCapture.disable() + output_to_json(None if outputting_json_stdout else args.json, output_error, json_results) + + # Exit with the appropriate status code + if output_error: sys.exit(-1) - + else: + exit(results_detectors) if __name__ == '__main__': diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 0ff442a2b..5bcb000b3 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -62,6 +62,10 @@ class NodeType: # Only modifier node PLACEHOLDER = 0x40 + # Node not related to the CFG + # Use for state variable declaration, or modifier calls + OTHER_ENTRYPOINT = 0x50 + # @staticmethod def str(t): @@ -107,6 +111,23 @@ def link_nodes(n1, n2): n1.add_son(n2) n2.add_father(n1) +def recheable(node): + ''' + Return the set of nodes reacheable from the node + :param node: + :return: set(Node) + ''' + nodes = node.sons + visited = set() + while nodes: + next = nodes[0] + nodes = nodes[1:] + if not next in visited: + visited.add(next) + for son in next.sons: + if not son in visited: + nodes.append(son) + return visited # endregion @@ -179,6 +200,10 @@ class Node(SourceMapping, ChildFunction): self._expression_vars_read = [] self._expression_calls = [] + # Computed on the fly, can be True of False + self._can_reenter = None + self._can_send_eth = None + ################################################################################### ################################################################################### # region General's properties @@ -381,6 +406,39 @@ class Node(SourceMapping, ChildFunction): def calls_as_expression(self): return list(self._expression_calls) + def can_reenter(self, callstack=None): + ''' + Check if the node can re-enter + Do not consider CREATE as potential re-enter, but check if the + destination's constructor can contain a call (recurs. follow nested CREATE) + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + Do not consider Send/Transfer as there is not enough gas + :param callstack: used internally to check for recursion + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_reenter is None: + self._can_reenter = False + for ir in self.irs: + if isinstance(ir, Call) and ir.can_reenter(callstack): + self._can_reenter = True + return True + return self._can_reenter + + def can_send_eth(self): + ''' + Check if the node can send eth + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_send_eth is None: + for ir in self.all_slithir_operations(): + if isinstance(ir, Call) and ir.can_send_eth(): + self._can_send_eth = True + return True + return self._can_reenter + # endregion ################################################################################### ################################################################################### diff --git a/slither/core/children/child_expression.py b/slither/core/children/child_expression.py new file mode 100644 index 000000000..f918e7a52 --- /dev/null +++ b/slither/core/children/child_expression.py @@ -0,0 +1,12 @@ + +class ChildExpression: + def __init__(self): + super(ChildExpression, self).__init__() + self._expression = None + + def set_expression(self, expression): + self._expression = expression + + @property + def expression(self): + return self._expression diff --git a/slither/core/children/child_node.py b/slither/core/children/child_node.py index 8c16e3106..bd6fd4e6f 100644 --- a/slither/core/children/child_node.py +++ b/slither/core/children/child_node.py @@ -18,3 +18,7 @@ class ChildNode(object): @property def contract(self): return self.node.function.contract + + @property + def slither(self): + return self.contract.slither \ No newline at end of file diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 545c8749e..813848a44 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -386,7 +386,7 @@ class Contract(ChildSlither, SourceMapping): accessible_elements = {} contracts_visited = [] for father in self.inheritance_reverse: - functions = {v.full_name: v for (_, v) in getter_available(father) + functions = {v.full_name: v for (v) in getter_available(father) if not v.contract in contracts_visited} contracts_visited.append(father) inherited_elements.update(functions) @@ -508,6 +508,16 @@ class Contract(ChildSlither, SourceMapping): """ return next((v for v in self.state_variables if v.name == variable_name), None) + def get_state_variable_from_canonical_name(self, canonical_name): + """ + Return a state variable from a canonical_name + Args: + canonical_name (str): name of the variable + Returns: + StateVariable + """ + return next((v for v in self.state_variables if v.name == canonical_name), None) + def get_structure_from_name(self, structure_name): """ Return a structure from a name @@ -528,15 +538,25 @@ class Contract(ChildSlither, SourceMapping): """ return next((st for st in self.structures if st.canonical_name == structure_name), None) - def get_event_from_name(self, event_name): + def get_event_from_signature(self, event_signature): """ - Return an event from a name + Return an event from a signature Args: - event_name (str): name of the event + event_signature (str): signature of the event Returns: Event """ - return next((e for e in self.events if e.name == event_name), None) + return next((e for e in self.events if e.full_name == event_signature), None) + + def get_event_from_canonical_name(self, event_canonical_name): + """ + Return an event from a canonical name + Args: + event_canonical_name (str): name of the event + Returns: + Event + """ + return next((e for e in self.events if e.canonical_name == event_canonical_name), None) def get_enum_from_name(self, enum_name): """ @@ -614,6 +634,25 @@ class Contract(ChildSlither, SourceMapping): all_state_variables_read = [item for sublist in all_state_variables_read for item in sublist] return list(set(all_state_variables_read)) + @property + def all_library_calls(self): + ''' + list((Contract, Function): List all of the libraries func called + ''' + all_high_level_calls = [f.all_library_calls() for f in self.functions + self.modifiers] + all_high_level_calls = [item for sublist in all_high_level_calls for item in sublist] + return list(set(all_high_level_calls)) + + @property + def all_high_level_calls(self): + ''' + list((Contract, Function|Variable)): List all of the external high level calls + ''' + all_high_level_calls = [f.all_high_level_calls() for f in self.functions + self.modifiers] + all_high_level_calls = [item for sublist in all_high_level_calls for item in sublist] + return list(set(all_high_level_calls)) + + # endregion ################################################################################### ################################################################################### @@ -621,14 +660,15 @@ class Contract(ChildSlither, SourceMapping): ################################################################################### ################################################################################### - def get_summary(self): + def get_summary(self, include_shadowed=True): """ Return the function summary + :param include_shadowed: boolean to indicate if shadowed functions should be included (default True) Returns: (str, list, list, list, list): (name, inheritance, variables, fuction summaries, modifier summaries) """ - func_summaries = [f.get_summary() for f in self.functions] - modif_summaries = [f.get_summary() for f in self.modifiers] + func_summaries = [f.get_summary() for f in self.functions if (not f.is_shadowed or include_shadowed)] + modif_summaries = [f.get_summary() for f in self.modifiers if (not f.is_shadowed or include_shadowed)] return (self.name, [str(x) for x in self.inheritance], [str(x) for x in self.variables], func_summaries, modif_summaries) def is_signature_only(self): diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 1f7247102..11d39dc92 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -4,6 +4,7 @@ import logging from collections import namedtuple from itertools import groupby +from enum import Enum from slither.core.children.child_contract import ChildContract from slither.core.children.child_inheritance import ChildInheritance @@ -13,12 +14,49 @@ from slither.core.declarations.solidity_variables import (SolidityFunction, from slither.core.expressions import (Identifier, IndexAccess, MemberAccess, UnaryOperation) from slither.core.source_mapping.source_mapping import SourceMapping + from slither.core.variables.state_variable import StateVariable +from slither.utils.utils import unroll logger = logging.getLogger("Function") ReacheableNode = namedtuple('ReacheableNode', ['node', 'ir']) +class ModifierStatements: + + def __init__(self, modifier, entry_point, nodes): + self._modifier = modifier + self._entry_point = entry_point + self._nodes = nodes + + + @property + def modifier(self): + return self._modifier + + @property + def entry_point(self): + return self._entry_point + + @entry_point.setter + def entry_point(self, entry_point): + self._entry_point = entry_point + + @property + def nodes(self): + return self._nodes + + @nodes.setter + def nodes(self, nodes): + self._nodes = nodes + +class FunctionType(Enum): + NORMAL = 0 + CONSTRUCTOR = 1 + FALLBACK = 2 + CONSTRUCTOR_VARIABLES = 3 # Fake function to hold variable declaration statements + CONSTRUCTOR_CONSTANT_VARIABLES = 4 # Fake function to hold variable declaration statements + class Function(ChildContract, ChildInheritance, SourceMapping): """ Function class @@ -31,7 +69,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._pure = None self._payable = None self._visibility = None - self._is_constructor = None + self._is_implemented = None self._is_empty = None self._entry_point = None @@ -76,9 +114,11 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._all_high_level_calls = None self._all_library_calls = None self._all_low_level_calls = None + self._all_solidity_calls = None self._all_state_variables_read = None self._all_solidity_variables_read = None self._all_state_variables_written = None + self._all_slithir_variables = None self._all_conditional_state_variables_read = None self._all_conditional_state_variables_read_with_loop = None self._all_conditional_solidity_variables_read = None @@ -86,11 +126,19 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._all_solidity_variables_used_as_args = None self._is_shadowed = False + self._shadows = False # set(ReacheableNode) self._reachable_from_nodes = set() self._reachable_from_functions = set() + # Constructor, fallback, State variable constructor + self._function_type = None + self._is_constructor = None + + # Computed on the fly, can be True of False + self._can_reenter = None + self._can_send_eth = None ################################################################################### ################################################################################### @@ -103,11 +151,14 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ str: function name """ - if self._name == '': - if self.is_constructor: - return 'constructor' - else: - return 'fallback' + if self._name == '' and self._function_type == FunctionType.CONSTRUCTOR: + return 'constructor' + elif self._function_type == FunctionType.FALLBACK: + return 'fallback' + elif self._function_type == FunctionType.CONSTRUCTOR_VARIABLES: + return 'slitherConstructorVariables' + elif self._function_type == FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES: + return 'slitherConstructorConstantVariables' return self._name @property @@ -128,17 +179,44 @@ class Function(ChildContract, ChildInheritance, SourceMapping): name, parameters, _ = self.signature return self.contract_declarer.name + '.' + name + '(' + ','.join(parameters) + ')' - @property - def is_constructor(self): - """ - bool: True if the function is the constructor - """ - return self._is_constructor or self._name == self.contract_declarer.name - @property def contains_assembly(self): return self._contains_assembly + def can_reenter(self, callstack=None): + ''' + Check if the function can re-enter + Follow internal calls. + Do not consider CREATE as potential re-enter, but check if the + destination's constructor can contain a call (recurs. follow nested CREATE) + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + Do not consider Send/Transfer as there is not enough gas + :param callstack: used internally to check for recursion + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_reenter is None: + self._can_reenter = False + for ir in self.all_slithir_operations(): + if isinstance(ir, Call) and ir.can_reenter(callstack): + self._can_reenter = True + return True + return self._can_reenter + + def can_send_eth(self): + ''' + Check if the function can send eth + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_send_eth is None: + for ir in self.all_slithir_operations(): + if isinstance(ir, Call) and ir.can_send_eth(): + self._can_send_eth = True + return True + return self._can_reenter + @property def slither(self): return self.contract.slither @@ -151,6 +229,41 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ return self.contract_declarer == contract + # endregion + ################################################################################### + ################################################################################### + # region Type (FunctionType) + ################################################################################### + ################################################################################### + + def set_function_type(self, t): + assert isinstance(t, FunctionType) + self._function_type = t + + @property + def is_constructor(self): + """ + bool: True if the function is the constructor + """ + return self._function_type == FunctionType.CONSTRUCTOR + + @property + def is_constructor_variables(self): + """ + bool: True if the function is the constructor of the variables + Slither has inbuilt functions to hold the state variables initialization + """ + return self._function_type in [FunctionType.CONSTRUCTOR_VARIABLES, FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES] + + @property + def is_fallback(self): + """ + Determine if the function is the fallback function for the contract + Returns + (bool) + """ + return self._function_type == FunctionType.FALLBACK + # endregion ################################################################################### ################################################################################### @@ -179,6 +292,9 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ return self._visibility + def set_visibility(self, v): + self._visibility = v + @property def view(self): """ @@ -201,6 +317,14 @@ class Function(ChildContract, ChildInheritance, SourceMapping): def is_shadowed(self, is_shadowed): self._is_shadowed = is_shadowed + @property + def shadows(self): + return self._shadows + + @shadows.setter + def shadows(self, _shadows): + self._shadows = _shadows + # endregion ################################################################################### ################################################################################### @@ -245,6 +369,11 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ return self._entry_point + def add_node(self, node): + if not self._entry_point: + self._entry_point = node + self._nodes.append(node) + # endregion ################################################################################### ################################################################################### @@ -324,6 +453,13 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ list(Modifier): List of the modifiers """ + return [c.modifier for c in self._modifiers] + + @property + def modifiers_statements(self): + """ + list(ModifierCall): List of the modifiers call (include expression and irs) + """ return list(self._modifiers) @property @@ -335,7 +471,16 @@ class Function(ChildContract, ChildInheritance, SourceMapping): included. """ # This is a list of contracts internally, so we convert it to a list of constructor functions. - return [c.constructors_declared for c in self._explicit_base_constructor_calls if c.constructors_declared] + return [c.modifier.constructors_declared for c in self._explicit_base_constructor_calls if c.modifier.constructors_declared] + + @property + def explicit_base_constructor_calls_statements(self): + """ + list(ModifierCall): List of the base constructors called explicitly by this presumed constructor definition. + + """ + # This is a list of contracts internally, so we convert it to a list of constructor functions. + return list(self._explicit_base_constructor_calls) # endregion @@ -665,6 +810,14 @@ class Function(ChildContract, ChildInheritance, SourceMapping): lambda x: x.solidity_variables_read) return self._all_solidity_variables_read + def all_slithir_variables(self): + """ recursive version of slithir_variables + """ + if self._all_slithir_variables is None: + self._all_slithir_variables = self._explore_functions( + lambda x: x.slithir_variable) + return self._all_slithir_variables + def all_expressions(self): """ recursive version of variables_read """ @@ -715,6 +868,13 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._all_library_calls = self._explore_functions(lambda x: x.library_calls) return self._all_library_calls + def all_solidity_calls(self): + """ recursive version of solidity calls + """ + if self._all_solidity_calls is None: + self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls) + return self._all_solidity_calls + @staticmethod def _explore_func_cond_read(func, include_loop): ret = [n.state_variables_read for n in func.nodes if n.is_conditional(include_loop)] @@ -986,6 +1146,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): args_vars = self.all_solidity_variables_used_as_args() return SolidityVariableComposed('msg.sender') in conditional_vars + args_vars + # endregion ################################################################################### ################################################################################### @@ -1089,9 +1250,136 @@ class Function(ChildContract, ChildInheritance, SourceMapping): external_calls_as_expressions = [item for sublist in external_calls_as_expressions for item in sublist] self._external_calls_as_expressions = list(set(external_calls_as_expressions)) + # endregion + ################################################################################### + ################################################################################### + # region SlithIr and SSA + ################################################################################### + ################################################################################### + + def get_last_ssa_state_variables_instances(self): + from slither.slithir.variables import ReferenceVariable + from slither.slithir.operations import OperationWithLValue + from slither.core.cfg.node import NodeType + if not self.is_implemented: + return dict() + + # node, values + to_explore = [(self._entry_point, dict())] + # node -> values + explored = dict() + # name -> instances + ret = dict() + + while to_explore: + node, values = to_explore[0] + to_explore = to_explore[1::] + + if node.type != NodeType.ENTRYPOINT: + for ir_ssa in node.irs_ssa: + if isinstance(ir_ssa, OperationWithLValue): + lvalue = ir_ssa.lvalue + if isinstance(lvalue, ReferenceVariable): + lvalue = lvalue.points_to_origin + if isinstance(lvalue, StateVariable): + values[lvalue.canonical_name] = {lvalue} + + # Check for fixpoint + if node in explored: + if values == explored[node]: + continue + for k, instances in values.items(): + if not k in explored[node]: + explored[node][k] = set() + explored[node][k] |= instances + values = explored[node] + else: + explored[node] = values + + # Return condition + if not node.sons and node.type != NodeType.THROW: + for name, instances in values.items(): + if name not in ret: + ret[name] = set() + ret[name] |= instances + + for son in node.sons: + to_explore.append((son, dict(values))) + + return ret + + @staticmethod + def _unchange_phi(ir): + from slither.slithir.operations import (Phi, PhiCallback) + if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1: + return False + if not ir.rvalues: + return True + return ir.rvalues[0] == ir.lvalue + + def fix_phi(self, last_state_variables_instances, initial_state_variables_instances): + from slither.slithir.operations import (InternalCall, PhiCallback) + from slither.slithir.variables import (Constant, StateIRVariable) + for node in self.nodes: + for ir in node.irs_ssa: + if node == self.entry_point: + if isinstance(ir.lvalue, StateIRVariable): + additional = [initial_state_variables_instances[ir.lvalue.canonical_name]] + additional += last_state_variables_instances[ir.lvalue.canonical_name] + ir.rvalues = list(set(additional + ir.rvalues)) + # function parameter + else: + # find index of the parameter + idx = self.parameters.index(ir.lvalue.non_ssa_version) + # find non ssa version of that index + additional = [n.ir.arguments[idx] for n in self.reachable_from_nodes] + additional = unroll(additional) + additional = [a for a in additional if not isinstance(a, Constant)] + ir.rvalues = list(set(additional + ir.rvalues)) + if isinstance(ir, PhiCallback): + callee_ir = ir.callee_ir + if isinstance(callee_ir, InternalCall): + last_ssa = callee_ir.function.get_last_ssa_state_variables_instances() + if ir.lvalue.canonical_name in last_ssa: + ir.rvalues = list(last_ssa[ir.lvalue.canonical_name]) + else: + ir.rvalues = [ir.lvalue] + else: + additional = last_state_variables_instances[ir.lvalue.canonical_name] + ir.rvalues = list(set(additional + ir.rvalues)) + + node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)] + + def generate_slithir_and_analyze(self): + for node in self.nodes: + node.slithir_generation() + + for modifier_statement in self.modifiers_statements: + for node in modifier_statement.nodes: + node.slithir_generation() + + for modifier_statement in self.explicit_base_constructor_calls_statements: + for node in modifier_statement.nodes: + node.slithir_generation() + + self._analyze_read_write() + self._analyze_calls() + + def generate_slithir_ssa(self, all_ssa_state_variables_instances): + from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa + from slither.core.dominators.utils import (compute_dominance_frontier, + compute_dominators) + compute_dominators(self.nodes) + compute_dominance_frontier(self.nodes) + transform_slithir_vars_to_ssa(self) + add_ssa_ir(self, all_ssa_state_variables_instances) + + def update_read_write_using_ssa(self): + for node in self.nodes: + node.update_read_write_using_ssa() + self._analyze_read_write() - # endregion ################################################################################### ################################################################################### # region Built in definitions diff --git a/slither/core/declarations/pragma_directive.py b/slither/core/declarations/pragma_directive.py index 1747b9229..1b1cd4756 100644 --- a/slither/core/declarations/pragma_directive.py +++ b/slither/core/declarations/pragma_directive.py @@ -17,5 +17,9 @@ class Pragma(SourceMapping): def version(self): return ''.join(self.directive[1:]) + @property + def name(self): + return self.version + def __str__(self): return 'pragma '+''.join(self.directive) diff --git a/slither/core/expressions/literal.py b/slither/core/expressions/literal.py index c1923eff4..1e9b96ec3 100644 --- a/slither/core/expressions/literal.py +++ b/slither/core/expressions/literal.py @@ -1,11 +1,13 @@ from slither.core.expressions.expression import Expression +from slither.utils.arithmetic import convert_subdenomination class Literal(Expression): - def __init__(self, value, type): + def __init__(self, value, type, subdenomination=None): super(Literal, self).__init__() self._value = value self._type = type + self._subdenomination = subdenomination @property def value(self): @@ -15,6 +17,12 @@ class Literal(Expression): def type(self): return self._type + @property + def subdenomination(self): + return self._subdenomination + def __str__(self): + if self.subdenomination: + return str(convert_subdenomination(self._value, self.subdenomination)) # be sure to handle any character return str(self._value) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 94bd7eb35..7a9f3f3d0 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -27,14 +27,20 @@ class Slither(Context): self._raw_source_code = {} self._all_functions = set() self._all_modifiers = set() + self._all_state_variables = None self._previous_results_filename = 'slither.db.json' self._results_to_hide = [] self._previous_results = [] + self._previous_results_ids = set() self._paths_to_filter = set() self._crytic_compile = None + self._generate_patches = False + self._exclude_dependencies = False + + self._markdown_root = "" ################################################################################### ################################################################################### @@ -44,7 +50,7 @@ class Slither(Context): @property def source_code(self): - """ {filename: source_code}: source code """ + """ {filename: source_code (str)}: source code """ return self._raw_source_code @property @@ -61,8 +67,15 @@ class Slither(Context): :param path: :return: """ - with open(path, encoding='utf8', newline='') as f: - self.source_code[path] = f.read() + if self.crytic_compile and path in self.crytic_compile.src_content: + self.source_code[path] = self.crytic_compile.src_content[path] + else: + with open(path, encoding='utf8', newline='') as f: + self.source_code[path] = f.read() + + @property + def markdown_root(self): + return self._markdown_root # endregion ################################################################################### @@ -74,6 +87,8 @@ class Slither(Context): @property def solc_version(self): """str: Solidity version.""" + if self.crytic_compile: + return self.crytic_compile.compiler_version.version return self._solc_version @property @@ -152,6 +167,20 @@ class Slither(Context): if isinstance(ir, InternalCall): ir.function.add_reachable_from_node(node, ir) + # endregion + ################################################################################### + ################################################################################### + # region Variables + ################################################################################### + ################################################################################### + + @property + def state_variables(self): + if self._all_state_variables is None: + state_variables = [c.state_variables for c in self.contracts] + state_variables = [item for sublist in state_variables for item in sublist] + self._all_state_variables = set(state_variables) + return list(self._all_state_variables) # endregion ################################################################################### @@ -189,6 +218,9 @@ class Slither(Context): if r['elements'] and self._exclude_dependencies: return not all(element['source_mapping']['is_dependency'] for element in r['elements']) + if r['id'] in self._previous_results_ids: + return False + # Conserve previous result filtering. This is conserved for compatibility, but is meant to be removed return not r['description'] in [pr['description'] for pr in self._previous_results] def load_previous_results(self): @@ -197,6 +229,10 @@ class Slither(Context): if os.path.isfile(filename): with open(filename) as f: self._previous_results = json.load(f) + if self._previous_results: + for r in self._previous_results: + if 'id' in r: + self._previous_results_ids.add(r['id']) except json.decoder.JSONDecodeError: logger.error(red('Impossible to decode {}. Consider removing the file'.format(filename))) @@ -228,4 +264,19 @@ class Slither(Context): @property def crytic_compile(self): return self._crytic_compile + # endregion + ################################################################################### + ################################################################################### + # region Format + ################################################################################### + ################################################################################### + + @property + def generate_patches(self): + return self._generate_patches + + @generate_patches.setter + def generate_patches(self, p): + self._generate_patches = p + # endregion \ No newline at end of file diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 9217b12fc..839d74a8f 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -90,18 +90,23 @@ class SourceMapping(Context): is_dependency = slither.crytic_compile.is_dependency(filename_absolute) - if filename_absolute in slither.source_code: + if filename_absolute in slither.source_code or filename_absolute in slither.crytic_compile.src_content: filename = filename_absolute elif filename_relative in slither.source_code: filename = filename_relative elif filename_short in slither.source_code: filename = filename_short - else:# - filename = filename_used.used + else: + filename = filename_used else: filename = filename_used - if filename in slither.source_code: + if slither.crytic_compile and filename in slither.crytic_compile.src_content: + source_code = slither.crytic_compile.src_content[filename] + (lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, + s, + l) + elif filename in slither.source_code: source_code = slither.source_code[filename] (lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, s, @@ -127,16 +132,22 @@ class SourceMapping(Context): else: self._source_mapping = self._convert_source_mapping(offset, slither) - - @property - def source_mapping_str(self): - + def _get_lines_str(self, line_descr=""): lines = self.source_mapping.get('lines', None) if not lines: lines = '' elif len(lines) == 1: - lines = '#{}'.format(lines[0]) + lines = '#{}{}'.format(line_descr, lines[0]) else: - lines = '#{}-{}'.format(lines[0], lines[-1]) - return '{}{}'.format(self.source_mapping['filename_short'], lines) + lines = '#{}{}-{}{}'.format(line_descr, lines[0], line_descr, lines[-1]) + return lines + + def source_mapping_to_markdown(self, markdown_root): + lines = self._get_lines_str(line_descr="L") + return f'{markdown_root}{self.source_mapping["filename_relative"]}{lines}' + + @property + def source_mapping_str(self): + lines = self._get_lines_str() + return f'{self.source_mapping["filename_short"]}{lines}' diff --git a/slither/core/variables/local_variable.py b/slither/core/variables/local_variable.py index 39e237271..8b353d530 100644 --- a/slither/core/variables/local_variable.py +++ b/slither/core/variables/local_variable.py @@ -52,6 +52,6 @@ class LocalVariable(ChildFunction, Variable): @property def canonical_name(self): - return self.name + return '{}.{}'.format(self.function.canonical_name, self.name) diff --git a/slither/core/variables/state_variable.py b/slither/core/variables/state_variable.py index 8cebc8758..af5421c53 100644 --- a/slither/core/variables/state_variable.py +++ b/slither/core/variables/state_variable.py @@ -4,6 +4,9 @@ from slither.utils.type import export_nested_types_from_variable class StateVariable(ChildContract, Variable): + def __init__(self): + super(StateVariable, self).__init__() + self._node_initialization = None def is_declared_by(self, contract): """ @@ -61,4 +64,24 @@ class StateVariable(ChildContract, Variable): # endregion ################################################################################### ################################################################################### + # region IRs (initialization) + ################################################################################### + ################################################################################### + + @property + def node_initialization(self): + """ + Node for the state variable initalization + :return: + """ + return self._node_initialization + + @node_initialization.setter + def node_initialization(self, node_initialization): + self._node_initialization = node_initialization + + + # endregion + ################################################################################### + ################################################################################### diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index 8b37c6da1..4d7f26e03 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -78,6 +78,25 @@ class Variable(SourceMapping): assert isinstance(t, (Type, list)) or t is None self._type = t + @property + def function_name(self): + ''' + Return the name of the variable as a function signature + :return: + ''' + from slither.core.solidity_types import ArrayType, MappingType + variable_getter_args = "" + if type(self.type) is ArrayType: + length = 0 + v = self + while type(v.type) is ArrayType: + length += 1 + v = v.type + variable_getter_args = ','.join(["uint256"] * length) + elif type(self.type) is MappingType: + variable_getter_args = self.type.type_from + + return f"{self.name}({variable_getter_args})" def __str__(self): return self._name diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 8a9f6fecf..765b79d15 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -2,8 +2,9 @@ import abc import re from slither.utils.colors import green, yellow, red -from slither.core.source_mapping.source_mapping import SourceMapping -from collections import OrderedDict +from slither.formatters.exceptions import FormatImpossible +from slither.formatters.utils.patches import apply_patch, create_diff +from slither.utils.output import Output class IncorrectDetectorInitialization(Exception): @@ -48,6 +49,8 @@ class AbstractDetector(metaclass=abc.ABCMeta): WIKI_EXPLOIT_SCENARIO = '' WIKI_RECOMMENDATION = '' + STANDARD_JSON = True + def __init__(self, slither, logger): self.slither = slither self.contracts = slither.contracts @@ -94,15 +97,18 @@ class AbstractDetector(metaclass=abc.ABCMeta): raise IncorrectDetectorInitialization('CONFIDENCE is not initialized {}'.format(self.__class__.__name__)) def _log(self, info): - self.logger.info(self.color(info)) + if self.logger: + self.logger.info(self.color(info)) @abc.abstractmethod def _detect(self): """TODO Documentation""" - return + return [] def detect(self): all_results = self._detect() + # Keep only dictionaries + all_results = [r.data for r in all_results] results = [] # only keep valid result, and remove dupplicate [results.append(r) for r in all_results if self.slither.valid_result(r) and r not in results] @@ -115,6 +121,33 @@ class AbstractDetector(metaclass=abc.ABCMeta): info += result['description'] info += 'Reference: {}'.format(self.WIKI) self._log(info) + if self.slither.generate_patches: + for result in results: + try: + self._format(self.slither, result) + if not 'patches' in result: + continue + result['patches_diff'] = dict() + for file in result['patches']: + original_txt = self.slither.source_code[file].encode('utf8') + patched_txt = original_txt + offset = 0 + patches = result['patches'][file] + patches.sort(key=lambda x: x['start']) + if not all(patches[i]['end'] <= patches[i + 1]['end'] for i in range(len(patches) - 1)): + self._log(f'Impossible to generate patch; patches collisions: {patches}') + continue + for patch in patches: + patched_txt, offset = apply_patch(patched_txt, patch, offset) + diff = create_diff(self.slither, original_txt, patched_txt, file) + if not diff: + self._log(f'Impossible to generate patch; empty {result}') + else: + result['patches_diff'][file] = diff + + except FormatImpossible as e: + self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{e}') + if results and self.slither.triage_mode: while True: indexes = input('Results to hide during next runs: "0,1,..." or "All" (enter to not hide results): '.format(len(results))) @@ -139,174 +172,19 @@ class AbstractDetector(metaclass=abc.ABCMeta): def color(self): return classification_colors[self.IMPACT] - def generate_json_result(self, info, additional_fields={}): - d = OrderedDict() - d['check'] = self.ARGUMENT - d['impact'] = classification_txt[self.IMPACT] - d['confidence'] = classification_txt[self.CONFIDENCE] - d['description'] = info - d['elements'] = [] - if additional_fields: - d['additional_fields'] = additional_fields - return d + def generate_result(self, info, additional_fields=None): + output = Output(info, + additional_fields, + standard_format=self.STANDARD_JSON, + markdown_root=self.slither.markdown_root) + + output.data['check'] = self.ARGUMENT + output.data['impact'] = classification_txt[self.IMPACT] + output.data['confidence'] = classification_txt[self.CONFIDENCE] + + return output @staticmethod - def _create_base_element(type, name, source_mapping, type_specific_fields={}, additional_fields={}): - element = {'type': type, - 'name': name, - 'source_mapping': source_mapping} - if type_specific_fields: - element['type_specific_fields'] = type_specific_fields - if additional_fields: - element['additional_fields'] = additional_fields - return element - - def _create_parent_element(self, element): - from slither.core.children.child_contract import ChildContract - from slither.core.children.child_function import ChildFunction - from slither.core.children.child_inheritance import ChildInheritance - if isinstance(element, ChildInheritance): - if element.contract_declarer: - contract = {'elements': []} - self.add_contract_to_json(element.contract_declarer, contract) - return contract['elements'][0] - elif isinstance(element, ChildContract): - if element.contract: - contract = {'elements': []} - self.add_contract_to_json(element.contract, contract) - return contract['elements'][0] - elif isinstance(element, ChildFunction): - if element.function: - function = {'elements': []} - self.add_function_to_json(element.function, function) - return function['elements'][0] - return None - - def add_variable_to_json(self, variable, d, additional_fields={}): - type_specific_fields = { - 'parent': self._create_parent_element(variable) - } - element = self._create_base_element('variable', - variable.name, - variable.source_mapping, - type_specific_fields, - additional_fields) - d['elements'].append(element) - - def add_variables_to_json(self, variables, d): - for variable in sorted(variables, key=lambda x:x.name): - self.add_variable_to_json(variable, d) - - def add_contract_to_json(self, contract, d, additional_fields={}): - element = self._create_base_element('contract', - contract.name, - contract.source_mapping, - {}, - additional_fields) - d['elements'].append(element) - - def add_function_to_json(self, function, d, additional_fields={}): - type_specific_fields = { - 'parent': self._create_parent_element(function), - 'signature': function.full_name - } - element = self._create_base_element('function', - function.name, - function.source_mapping, - type_specific_fields, - additional_fields) - d['elements'].append(element) - - def add_functions_to_json(self, functions, d, additional_fields={}): - for function in sorted(functions, key=lambda x: x.name): - self.add_function_to_json(function, d, additional_fields) - - def add_enum_to_json(self, enum, d, additional_fields={}): - type_specific_fields = { - 'parent': self._create_parent_element(enum) - } - element = self._create_base_element('enum', - enum.name, - enum.source_mapping, - type_specific_fields, - additional_fields) - d['elements'].append(element) - - def add_struct_to_json(self, struct, d, additional_fields={}): - type_specific_fields = { - 'parent': self._create_parent_element(struct) - } - element = self._create_base_element('struct', - struct.name, - struct.source_mapping, - type_specific_fields, - additional_fields) - d['elements'].append(element) - - def add_event_to_json(self, event, d, additional_fields={}): - type_specific_fields = { - 'parent': self._create_parent_element(event), - 'signature': event.full_name - } - element = self._create_base_element('event', - event.name, - event.source_mapping, - type_specific_fields, - additional_fields) - - d['elements'].append(element) - - def add_node_to_json(self, node, d, additional_fields={}): - type_specific_fields = { - 'parent': self._create_parent_element(node), - } - node_name = str(node.expression) if node.expression else "" - element = self._create_base_element('node', - node_name, - node.source_mapping, - type_specific_fields, - additional_fields) - d['elements'].append(element) - - def add_nodes_to_json(self, nodes, d): - for node in sorted(nodes, key=lambda x: x.node_id): - self.add_node_to_json(node, d) - - def add_pragma_to_json(self, pragma, d, additional_fields={}): - type_specific_fields = { - 'directive': pragma.directive - } - element = self._create_base_element('pragma', - pragma.version, - pragma.source_mapping, - type_specific_fields, - additional_fields) - d['elements'].append(element) - - def add_other_to_json(self, name, source_mapping, d, additional_fields={}): - # If this a tuple with (filename, start, end), convert it to a source mapping. - if isinstance(source_mapping, tuple): - # Parse the source id - (filename, start, end) = source_mapping - source_id = next((source_unit_id for (source_unit_id, source_unit_filename) in self.slither.source_units.items() if source_unit_filename == filename), -1) - - # Convert to a source mapping string - source_mapping = f"{start}:{end}:{source_id}" - - # If this is a source mapping string, parse it. - if isinstance(source_mapping, str): - source_mapping_str = source_mapping - source_mapping = SourceMapping() - source_mapping.set_offset(source_mapping_str, self.slither) - - # If this is a source mapping object, get the underlying source mapping dictionary - if isinstance(source_mapping, SourceMapping): - source_mapping = source_mapping.source_mapping - - # Create the underlying element and add it to our resulting json - element = self._create_base_element('other', - name, - source_mapping, - {}, - additional_fields) - d['elements'].append(element) + def _format(slither, result): + """Implement format""" + return \ No newline at end of file diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 9fd21ac83..39822221b 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -36,5 +36,6 @@ from .source.rtlo import RightToLeftOverride from .statements.too_many_digits import TooManyDigits from .operations.unchecked_low_level_return_values import UncheckedLowLevel from .operations.unchecked_send_return_value import UncheckedSend +from .operations.void_constructor import VoidConstructor # # diff --git a/slither/detectors/attributes/const_functions.py b/slither/detectors/attributes/const_functions.py index 2f217eb97..6e892a626 100644 --- a/slither/detectors/attributes/const_functions.py +++ b/slither/detectors/attributes/const_functions.py @@ -3,6 +3,7 @@ Module detecting constant functions Recursively check the called functions """ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.formatters.attributes.const_functions import format class ConstantFunctions(AbstractDetector): @@ -56,23 +57,27 @@ All the calls to `get` revert, breaking Bob's smart contract execution.''' if f.view or f.pure: if f.contains_assembly: attr = 'view' if f.view else 'pure' - info = '{} ({}) is declared {} but contains assembly code\n' - info = info.format(f.canonical_name, f.source_mapping_str, attr) - json = self.generate_json_result(info, {'contains_assembly': True}) - self.add_function_to_json(f, json) - results.append(json) + + info = [f, f' is declared {attr} but contains assembly code\n'] + res = self.generate_result(info, {'contains_assembly': True}) + + results.append(res) variables_written = f.all_state_variables_written() if variables_written: attr = 'view' if f.view else 'pure' - info = '{} ({}) is declared {} but changes state variables:\n' - info = info.format(f.canonical_name, f.source_mapping_str, attr) + + info = [f, f' is declared {attr} but changes state variables:\n'] + for variable_written in variables_written: - info += '\t- {}\n'.format(variable_written.canonical_name) + info += ['\t- ', variable_written, '\n'] + + res = self.generate_result(info, {'contains_assembly': False}) - json = self.generate_json_result(info, {'contains_assembly': False}) - self.add_function_to_json(f, json) - self.add_variables_to_json(variables_written, json) - results.append(json) + results.append(res) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 1f0e905ae..b7d593357 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -3,6 +3,7 @@ """ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.formatters.attributes.constant_pragma import format class ConstantPragma(AbstractDetector): @@ -29,16 +30,18 @@ class ConstantPragma(AbstractDetector): versions = sorted(list(set(versions))) if len(versions) > 1: - info = "Different versions of Solidity is used in {}:\n".format(self.filename) - info += "\t- Version used: {}\n".format([str(v) for v in versions]) + info = [f"Different versions of Solidity is used in {self.filename}:\n"] + info += [f"\t- Version used: {[str(v) for v in versions]}\n"] + for p in pragma: - info += "\t- {} declares {}\n".format(p.source_mapping_str, str(p)) + info += ["\t- ", p, "\n"] - json = self.generate_json_result(info) + res = self.generate_result(info) - # Add each pragma to our elements - for p in pragma: - self.add_pragma_to_json(p, json) - results.append(json) + results.append(res) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index c8fb43109..f63a2bdf2 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -2,8 +2,9 @@ Check if an incorrect version of solc is used """ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification import re +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.formatters.attributes.incorrect_solc import format # group: # 0: ^ > >= < <= (optional) @@ -11,6 +12,7 @@ import re # 2: version number # 3: version number # 4: version number + PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') class IncorrectSolc(AbstractDetector): @@ -42,7 +44,7 @@ Use Solidity 0.4.25 or 0.5.3. Consider using the latest version of Solidity for # Indicates the allowed versions. ALLOWED_VERSIONS = ["0.4.25", "0.4.26", "0.5.3"] # Indicates the versions too recent. - TOO_RECENT_VERSIONS = ["0.5.4", "0.5.7", "0.5.8", "0.5.9", "0.5.10"] + TOO_RECENT_VERSIONS = ["0.5.4", "0.5.7", "0.5.8", "0.5.9", "0.5.10", "0.5.11", "0.5.12", "0.5.13"] # Indicates the versions that should not be used. BUGGY_VERSIONS = ["0.4.22", "0.5.5", "0.5.6", "^0.4.22", "^0.5.5", "^0.5.6"] @@ -98,10 +100,14 @@ Use Solidity 0.4.25 or 0.5.3. Consider using the latest version of Solidity for # If we found any disallowed pragmas, we output our findings. if disallowed_pragmas: for (reason, p) in disallowed_pragmas: - info = f"Pragma version \"{p.version}\" {reason} ({p.source_mapping_str})\n" + info = ["Pragma version", p, f" {reason}\n"] + + json = self.generate_result(info) - json = self.generate_json_result(info) - self.add_pragma_to_json(p, json) results.append(json) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 2a4e1d1c3..97ffb0db2 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -74,18 +74,14 @@ Every ether sent to `Locked` will be lost.''' funcs_payable = [function for function in contract.functions if function.payable] if funcs_payable: if self.do_no_send_ether(contract): - txt = "Contract locking ether found in {}:\n".format(self.filename) - txt += "\tContract {} has payable functions:\n".format(contract.name) + info = [f"Contract locking ether found in {self.filename}:\n"] + info += ["\tContract ", contract, " has payable functions:\n"] for function in funcs_payable: - txt += "\t - {} ({})\n".format(function.name, function.source_mapping_str) - txt += "\tBut does not have a function to withdraw the ether\n" - info = txt.format(self.filename, - contract.name, - [f.name for f in funcs_payable]) - - json = self.generate_json_result(info) - self.add_contract_to_json(contract, json) - self.add_functions_to_json(funcs_payable, json) + info += [f"\t - ", function, "\n"] + info += "\tBut does not have a function to withdraw the ether\n" + + json = self.generate_result(info) + results.append(json) return results diff --git a/slither/detectors/erc/incorrect_erc20_interface.py b/slither/detectors/erc/incorrect_erc20_interface.py index c2c0a4112..b7782a66a 100644 --- a/slither/detectors/erc/incorrect_erc20_interface.py +++ b/slither/detectors/erc/incorrect_erc20_interface.py @@ -87,12 +87,9 @@ contract Token{ functions = IncorrectERC20InterfaceDetection.detect_incorrect_erc20_interface(c) if functions: for function in functions: - info = "{} ({}) has incorrect ERC20 function interface: {} ({})\n".format(c.name, - c.source_mapping_str, - function.full_name, - function.source_mapping_str) - json = self.generate_json_result(info) - self.add_function_to_json(function, json) + info = [c, " has incorrect ERC20 function interface:", function, "\n"] + json = self.generate_result(info) + results.append(json) return results diff --git a/slither/detectors/erc/incorrect_erc721_interface.py b/slither/detectors/erc/incorrect_erc721_interface.py index bff88413c..e6a484631 100644 --- a/slither/detectors/erc/incorrect_erc721_interface.py +++ b/slither/detectors/erc/incorrect_erc721_interface.py @@ -86,12 +86,9 @@ contract Token{ functions = IncorrectERC721InterfaceDetection.detect_incorrect_erc721_interface(c) if functions: for function in functions: - info = "{} ({}) has incorrect ERC721 function interface: {} ({})\n".format(c.name, - c.source_mapping_str, - function.full_name, - function.source_mapping_str) - json = self.generate_json_result(info) - self.add_function_to_json(function, json) - results.append(json) + info = [c, " has incorrect ERC721 function interface:", function, "\n"] + res = self.generate_result(info) + + results.append(res) return results diff --git a/slither/detectors/erc/unindexed_event_parameters.py b/slither/detectors/erc/unindexed_event_parameters.py index 55ab15e25..096c63f16 100644 --- a/slither/detectors/erc/unindexed_event_parameters.py +++ b/slither/detectors/erc/unindexed_event_parameters.py @@ -32,6 +32,8 @@ In this case, Transfer and Approval events should have the 'indexed' keyword on WIKI_RECOMMENDATION = 'Add the `indexed` keyword to event parameters which should include it, according to the ERC20 specification.' + STANDARD_JSON = False + @staticmethod def detect_erc20_unindexed_event_params(contract): """ @@ -71,14 +73,13 @@ In this case, Transfer and Approval events should have the 'indexed' keyword on # Add each problematic event definition to our result list for (event, parameter) in unindexed_params: - info = "ERC20 event {}.{} ({}) does not index parameter '{}'\n".format(c.name, event.name, event.source_mapping_str, parameter.name) + info = ["ERC20 event ", event, f"does not index parameter {parameter}\n"] # Add the events to the JSON (note: we do not add the params/vars as they have no source mapping). - json = self.generate_json_result(info) - self.add_event_to_json(event, json, { - "parameter_name": parameter.name - }) - results.append(json) + res = self.generate_result(info) + + res.add(event, {"parameter_name": parameter.name}) + results.append(res) return results diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index bbc60a8b3..76511d018 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -26,11 +26,11 @@ class Backdoor(AbstractDetector): for f in contract.functions: if 'backdoor' in f.name: # Info to be printed - info = 'Backdoor function found in {}.{} ({})\n' - info = info.format(contract.name, f.name, f.source_mapping_str) + info = ['Backdoor function found in ', f, '\n'] + # Add the result in result - json = self.generate_json_result(info) - self.add_function_to_json(f, json) - results.append(json) + res = self.generate_result(info) + + results.append(res) return results diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index d28cd1258..2c608e73a 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -109,16 +109,13 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract arbitrary_send = self.detect_arbitrary_send(c) for (func, nodes) in arbitrary_send: - info = "{} ({}) sends eth to arbitrary user\n" - info = info.format(func.canonical_name, - func.source_mapping_str) - info += '\tDangerous calls:\n' + info = [func, " sends eth to arbitrary user\n"] + info += ['\tDangerous calls:\n'] for node in nodes: - info += '\t- {} ({})\n'.format(node.expression, node.source_mapping_str) + info += ['\t- ', node, '\n'] - json = self.generate_json_result(info) - self.add_function_to_json(func, json) - self.add_nodes_to_json(nodes, json) - results.append(json) + res = self.generate_result(info) + + results.append(res) return results diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index fae9a0923..21add9239 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -31,6 +31,7 @@ class ComplexFunction(AbstractDetector): CAUSE_EXTERNAL_CALL = "external_calls" CAUSE_STATE_VARS = "state_vars" + STANDARD_JSON = True @staticmethod def detect_complex_func(func): @@ -104,14 +105,14 @@ class ComplexFunction(AbstractDetector): info = info + "\n" self.log(info) - json = self.generate_json_result(info) - self.add_function_to_json(func, json, { + res = self.generate_result(info) + res.add(func, { 'high_number_of_external_calls': cause == self.CAUSE_EXTERNAL_CALL, 'high_number_of_branches': cause == self.CAUSE_CYCLOMATIC, 'high_number_of_state_variables': cause == self.CAUSE_STATE_VARS }) - results.append(json) + results.append(res) return results diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index e2250ba3a..f5d9aad61 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -1,7 +1,9 @@ from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) -from slither.slithir.operations import (HighLevelCall, SolidityCall ) +from slither.slithir.operations import SolidityCall from slither.slithir.operations import (InternalCall, InternalDynamicCall) +from slither.formatters.functions.external_function import format + class ExternalFunction(AbstractDetector): """ @@ -172,14 +174,25 @@ class ExternalFunction(AbstractDetector): if is_called: continue - # Loop for each function definition, and recommend it be declared external. - for function_definition in all_function_definitions: - txt = "{} ({}) should be declared external\n" - info = txt.format(function_definition.canonical_name, - function_definition.source_mapping_str) + # As we collect all shadowed functions in get_all_function_definitions + # Some function coming from a base might already been declared as external + all_function_definitions = [f for f in all_function_definitions if f.visibility == 'public' and + f.contract == f.contract_declarer] + if all_function_definitions: + function_definition = all_function_definitions[0] + all_function_definitions = all_function_definitions[1:] + + info = [f"{function_definition.full_name} should be declared external:\n"] + info += [f"\t- ", function_definition, "\n"] + for other_function_definition in all_function_definitions: + info += [f"\t- ", other_function_definition, "\n"] + + res = self.generate_result(info) - json = self.generate_json_result(info) - self.add_function_to_json(function_definition, json) - results.append(json) + results.append(res) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index fef1c1224..a3599fcad 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -6,6 +6,7 @@ A suicidal contract is an unprotected function that calls selfdestruct from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + class Suicidal(AbstractDetector): """ Unprotected function detector @@ -72,12 +73,10 @@ Bob calls `kill` and destructs the contract.''' functions = self.detect_suicidal(c) for func in functions: - txt = "{} ({}) allows anyone to destruct the contract\n" - info = txt.format(func.canonical_name, - func.source_mapping_str) + info = [func, " allows anyone to destruct the contract\n"] + + res = self.generate_result(info) - json = self.generate_json_result(info) - self.add_function_to_json(func, json) - results.append(json) + results.append(res) return results diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 9c69e41ba..3f07d1cba 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -1,5 +1,6 @@ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification import re +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.formatters.naming_convention.naming_convention import format class NamingConvention(AbstractDetector): @@ -10,6 +11,7 @@ class NamingConvention(AbstractDetector): Exceptions: - Allow constant variables name/symbol/decimals to be lowercase (ERC20) - Allow '_' at the beggining of the mixed_case match for private variables and unused parameters + - Ignore echidna properties (functions with names starting 'echidna_' or 'crytic_' """ ARGUMENT = 'naming-convention' @@ -28,6 +30,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 WIKI_RECOMMENDATION = 'Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions).' + STANDARD_JSON = False @staticmethod def is_cap_words(name): @@ -41,7 +44,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 def is_mixed_case_with_underscore(name): # Allow _ at the beginning to represent private variable # or unused parameters - return re.search('^[a-z_]([A-Za-z0-9]+)?_?$', name) is not None + return re.search('^[_]?[a-z]([A-Za-z0-9]+)?_?$', name) is not None @staticmethod def is_upper_case_with_underscores(name): @@ -57,51 +60,53 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 for contract in self.contracts: if not self.is_cap_words(contract.name): - info = "Contract '{}' ({}) is not in CapWords\n".format(contract.name, - contract.source_mapping_str) + info = ["Contract ", contract, " is not in CapWords\n"] - json = self.generate_json_result(info) - self.add_contract_to_json(contract, json, { + res = self.generate_result(info) + res.add(contract, { "target": "contract", "convention": "CapWords" }) - results.append(json) + results.append(res) for struct in contract.structures_declared: if not self.is_cap_words(struct.name): - info = "Struct '{}' ({}) is not in CapWords\n" - info = info.format(struct.canonical_name, struct.source_mapping_str) + info = ["Struct ", struct, " is not in CapWords\n"] - json = self.generate_json_result(info) - self.add_struct_to_json(struct, json, { + res = self.generate_result(info) + res.add(struct, { "target": "structure", "convention": "CapWords" }) - results.append(json) + results.append(res) for event in contract.events_declared: if not self.is_cap_words(event.name): - info = "Event '{}' ({}) is not in CapWords\n" - info = info.format(event.canonical_name, event.source_mapping_str) + info = ["Event ", event, " is not in CapWords\n"] - json = self.generate_json_result(info) - self.add_event_to_json(event, json, { + res = self.generate_result(info) + res.add(event, { "target": "event", "convention": "CapWords" }) - results.append(json) + results.append(res) for func in contract.functions_declared: + if func.is_constructor: + continue if not self.is_mixed_case(func.name): - info = "Function '{}' ({}) is not in mixedCase\n" - info = info.format(func.canonical_name, func.source_mapping_str) + if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name): + continue + if func.name.startswith("echidna_") or func.name.startswith("crytic_"): + continue + info = ["Function ", func, " is not in mixedCase\n"] - json = self.generate_json_result(info) - self.add_function_to_json(func, json, { + res = self.generate_result(info) + res.add(func, { "target": "function", "convention": "mixedCase" }) - results.append(json) + results.append(res) for argument in func.parameters: # Ignore parameter names that are not specified i.e. empty strings @@ -112,30 +117,26 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 else: correct_naming = self.is_mixed_case_with_underscore(argument.name) if not correct_naming: - info = "Parameter '{}' of {} ({}) is not in mixedCase\n" - info = info.format(argument.name, - argument.canonical_name, - argument.source_mapping_str) + info = ["Parameter ", argument, " is not in mixedCase\n"] - json = self.generate_json_result(info) - self.add_variable_to_json(argument, json, { + res = self.generate_result(info) + res.add(argument, { "target": "parameter", "convention": "mixedCase" }) - results.append(json) + results.append(res) for var in contract.state_variables_declared: if self.should_avoid_name(var.name): if not self.is_upper_case_with_underscores(var.name): - info = "Variable '{}' ({}) used l, O, I, which should not be used\n" - info = info.format(var.canonical_name, var.source_mapping_str) + info = ["Variable ", var," used l, O, I, which should not be used\n"] - json = self.generate_json_result(info) - self.add_variable_to_json(var, json, { + res = self.generate_result(info) + res.add(var, { "target": "variable", "convention": "l_O_I_should_not_be_used" }) - results.append(json) + results.append(res) if var.is_constant is True: # For ERC20 compatibility @@ -143,15 +144,14 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 continue if not self.is_upper_case_with_underscores(var.name): - info = "Constant '{}' ({}) is not in UPPER_CASE_WITH_UNDERSCORES\n" - info = info.format(var.canonical_name, var.source_mapping_str) + info = ["Constant ", var," is not in UPPER_CASE_WITH_UNDERSCORES\n"] - json = self.generate_json_result(info) - self.add_variable_to_json(var, json, { + res = self.generate_result(info) + res.add(var, { "target": "variable_constant", "convention": "UPPER_CASE_WITH_UNDERSCORES" }) - results.append(json) + results.append(res) else: if var.visibility == 'private': @@ -159,39 +159,39 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 else: correct_naming = self.is_mixed_case(var.name) if not correct_naming: - info = "Variable '{}' ({}) is not in mixedCase\n" - info = info.format(var.canonical_name, var.source_mapping_str) + info = ["Variable ", var, " is not in mixedCase\n"] - json = self.generate_json_result(info) - self.add_variable_to_json(var, json, { + res = self.generate_result(info) + res.add(var, { "target": "variable", "convention": "mixedCase" }) - results.append(json) + results.append(res) for enum in contract.enums_declared: if not self.is_cap_words(enum.name): - info = "Enum '{}' ({}) is not in CapWords\n" - info = info.format(enum.canonical_name, enum.source_mapping_str) + info = ["Enum ", enum, " is not in CapWords\n"] - json = self.generate_json_result(info) - self.add_enum_to_json(enum, json, { + res = self.generate_result(info) + res.add(enum, { "target": "enum", "convention": "CapWords" }) - results.append(json) + results.append(res) for modifier in contract.modifiers_declared: if not self.is_mixed_case(modifier.name): - info = "Modifier '{}' ({}) is not in mixedCase\n" - info = info.format(modifier.canonical_name, - modifier.source_mapping_str) + info = ["Modifier ", modifier, " is not in mixedCase\n"] - json = self.generate_json_result(info) - self.add_function_to_json(modifier, json, { + res = self.generate_result(info) + res.add(modifier, { "target": "modifier", "convention": "mixedCase" }) - results.append(json) + results.append(res) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/slither/detectors/operations/block_timestamp.py b/slither/detectors/operations/block_timestamp.py index 81c115341..6dc76ae50 100644 --- a/slither/detectors/operations/block_timestamp.py +++ b/slither/detectors/operations/block_timestamp.py @@ -69,16 +69,14 @@ class Timestamp(AbstractDetector): dangerous_timestamp = self.detect_dangerous_timestamp(c) for (func, nodes) in dangerous_timestamp: - info = "{} ({}) uses timestamp for comparisons\n" - info = info.format(func.canonical_name, - func.source_mapping_str) - info += '\tDangerous comparisons:\n' + info = [func, " uses timestamp for comparisons\n"] + + info += ['\tDangerous comparisons:\n'] for node in nodes: - info += '\t- {} ({})\n'.format(node.expression, node.source_mapping_str) + info += ['\t- ', node, '\n'] + + res = self.generate_result(info) - json = self.generate_json_result(info) - self.add_function_to_json(func, json) - self.add_nodes_to_json(nodes, json) - results.append(json) + results.append(res) return results diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 4e36448a4..1a46a9909 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -48,14 +48,13 @@ class LowLevelCalls(AbstractDetector): for c in self.contracts: values = self.detect_low_level_calls(c) for func, nodes in values: - info = "Low level call in {} ({}):\n" - info = info.format(func.canonical_name, func.source_mapping_str) + info = ["Low level call in ", func,":\n"] + for node in nodes: - info += "\t-{} {}\n".format(str(node.expression), node.source_mapping_str) + info += ['\t- ', node, '\n'] + + res = self.generate_result(info) - json = self.generate_json_result(info) - self.add_function_to_json(func, json) - self.add_nodes_to_json(nodes, json) - results.append(json) + results.append(res) return results diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py index 532b9a72e..b1e2b6d08 100644 --- a/slither/detectors/operations/unused_return_values.py +++ b/slither/detectors/operations/unused_return_values.py @@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi from slither.slithir.operations import HighLevelCall, InternalCall, InternalDynamicCall from slither.core.variables.state_variable import StateVariable + class UnusedReturnValues(AbstractDetector): """ If the return value of a function is never used, it's likely to be bug @@ -73,17 +74,11 @@ contract MyConc{ if unused_return: for node in unused_return: - info = "{} ({}) ignores return value by {} \"{}\" ({})\n" - info = info.format(f.canonical_name, - f.source_mapping_str, - self._txt_description, - node.expression, - node.source_mapping_str) - - json = self.generate_json_result(info) - self.add_node_to_json(node, json) - self.add_function_to_json(f, json) - results.append(json) + info = [f, f" ignores return value by ", node, "\n"] + + res = self.generate_result(info) + + results.append(res) return results diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py new file mode 100644 index 000000000..74abd1329 --- /dev/null +++ b/slither/detectors/operations/void_constructor.py @@ -0,0 +1,45 @@ + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import Nop + + +class VoidConstructor(AbstractDetector): + + ARGUMENT = 'void-cst' + HELP = 'Constructor called not implemented' + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.HIGH + + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor' + + WIKI_TITLE = 'Void Constructor' + WIKI_DESCRIPTION = 'Detect the call to a constructor not implemented' + WIKI_RECOMMENDATION = 'Remove the constructor call.' + WIKI_EXPLOIT_SCENARIO = ''' +```solidity +contract A{} +contract B is A{ + constructor() public A(){} +} +``` +By reading B's constructor definition, the reader might assume that `A()` initiate the contract, while no code is executed.''' + + + def _detect(self): + """ + """ + results = [] + for c in self.contracts: + cst = c.constructor + if cst: + + for constructor_call in cst.explicit_base_constructor_calls_statements: + for node in constructor_call.nodes: + if any(isinstance(ir, Nop) for ir in node.irs): + info = ["Void constructor called in ", cst, ":\n"] + info += ["\t- ", node, "\n"] + + res = self.generate_result(info) + + results.append(res) + return results diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index be5f95dd9..bb5afef17 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -6,14 +6,11 @@ """ from slither.core.cfg.node import NodeType -from slither.core.declarations import Function, SolidityFunction, SolidityVariable +from slither.core.declarations import Function from slither.core.expressions import UnaryOperation, UnaryOperationType -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (HighLevelCall, LowLevelCall, - LibraryCall, - Send, Transfer) -from slither.core.variables.variable import Variable +from slither.detectors.abstract_detector import AbstractDetector +from slither.slithir.operations import Call + def union_dict(d1, d2): d3 = {k: d1.get(k, set()) | d2.get(k, set()) for k in set(list(d1.keys()) + list(d2.keys()))} @@ -25,12 +22,6 @@ def dict_are_equal(d1, d2): return all(set(d1[k]) == set(d2[k]) for k in d1.keys()) class Reentrancy(AbstractDetector): -# This detector is not meant to be registered -# It is inherited by reentrancy variantsœ -# ARGUMENT = 'reentrancy' -# HELP = 'Reentrancy vulnerabilities' -# IMPACT = DetectorClassification.HIGH -# CONFIDENCE = DetectorClassification.HIGH KEY = 'REENTRANCY' @@ -43,27 +34,10 @@ class Reentrancy(AbstractDetector): - low level call - high level call - Do not consider Send/Transfer as there is not enough gas + """ for ir in irs: - if isinstance(ir, LowLevelCall): - return True - if isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall): - # If solidity >0.5, STATICCALL is used - if self.slither.solc_version and self.slither.solc_version.startswith('0.5.'): - if isinstance(ir.function, Function) and (ir.function.view or ir.function.pure): - continue - if isinstance(ir.function, Variable): - continue - # If there is a call to itself - # We can check that the function called is - # reentrancy-safe - if ir.destination == SolidityVariable('this'): - if isinstance(ir.function, Variable): - continue - if not ir.function.all_high_level_calls(): - if not ir.function.all_low_level_calls(): - continue + if isinstance(ir, Call) and ir.can_reenter(): return True return False @@ -73,9 +47,11 @@ class Reentrancy(AbstractDetector): Detect if the node can send eth """ for ir in irs: - if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): - if ir.call_value: - return True + if isinstance(ir, Call) and ir.can_send_eth(): + return True + # if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): + # if ir.call_value: + # return True return False def _filter_if(self, node): @@ -174,7 +150,6 @@ class Reentrancy(AbstractDetector): self._explore(son, visited, node) sons = [sons[0]] - for son in sons: self._explore(son, visited) diff --git a/slither/detectors/reentrancy/reentrancy_benign.py b/slither/detectors/reentrancy/reentrancy_benign.py index 85daabb5d..2ff6e8475 100644 --- a/slither/detectors/reentrancy/reentrancy_benign.py +++ b/slither/detectors/reentrancy/reentrancy_benign.py @@ -5,14 +5,8 @@ Iterate over all the nodes of the graph until reaching a fixpoint """ -from slither.core.cfg.node import NodeType -from slither.core.declarations import Function, SolidityFunction -from slither.core.expressions import UnaryOperation, UnaryOperationType from slither.detectors.abstract_detector import DetectorClassification -from slither.visitors.expression.export_values import ExportValues -from slither.slithir.operations import (HighLevelCall, LowLevelCall, - LibraryCall, - Send, Transfer) + from .reentrancy import Reentrancy @@ -42,6 +36,8 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + STANDARD_JSON = False + def find_reentrancies(self): result = {} for contract in self.contracts: @@ -84,28 +80,29 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr for (func, calls, send_eth), varsWritten in result_sorted: calls = sorted(list(set(calls)), key=lambda x: x.node_id) send_eth = sorted(list(set(send_eth)), key=lambda x: x.node_id) - info = 'Reentrancy in {} ({}):\n' - info = info.format(func.canonical_name, func.source_mapping_str) - info += '\tExternal calls:\n' + info = ['Reentrancy in ', func, ':\n'] + + info += ['\tExternal calls:\n'] for call_info in calls: - info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) + info += ['\t- ' , call_info, '\n'] if calls != send_eth and send_eth: - info += '\tExternal calls sending eth:\n' + info += ['\tExternal calls sending eth:\n'] for call_info in send_eth: - info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) - info += '\tState variables written after the call(s):\n' + info += ['\t- ', call_info, '\n'] + info += ['\tState variables written after the call(s):\n'] for (v, node) in sorted(varsWritten, key=lambda x: (x[0].name, x[1].node_id)): - info += '\t- {} ({})\n'.format(v, node.source_mapping_str) + info += ['\t- ', v, ' in ', node, '\n'] + # Create our JSON result - json = self.generate_json_result(info) + res = self.generate_result(info) # Add the function with the re-entrancy first - self.add_function_to_json(func, json) + res.add(func) # Add all underlying calls in the function which are potentially problematic. for call_info in calls: - self.add_node_to_json(call_info, json, { + res.add(call_info, { "underlying_type": "external_calls" }) @@ -114,18 +111,18 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr # If the calls are not the same ones that send eth, add the eth sending nodes. if calls != send_eth: for call_info in send_eth: - self.add_node_to_json(call_info, json, { + res.add(call_info, { "underlying_type": "external_calls_sending_eth" }) # Add all variables written via nodes which write them. for (v, node) in varsWritten: - self.add_node_to_json(node, json, { + res.add(node, { "underlying_type": "variables_written", "variable_name": v.name }) # Append our result - results.append(json) + results.append(res) return results diff --git a/slither/detectors/reentrancy/reentrancy_eth.py b/slither/detectors/reentrancy/reentrancy_eth.py index 05a0c7a5a..a5f0c0365 100644 --- a/slither/detectors/reentrancy/reentrancy_eth.py +++ b/slither/detectors/reentrancy/reentrancy_eth.py @@ -4,13 +4,7 @@ Based on heuristics, it may lead to FP and FN Iterate over all the nodes of the graph until reaching a fixpoint """ -from slither.core.cfg.node import NodeType -from slither.core.declarations import Function, SolidityFunction -from slither.core.expressions import UnaryOperation, UnaryOperationType from slither.detectors.abstract_detector import DetectorClassification -from slither.slithir.operations import (HighLevelCall, LowLevelCall, - LibraryCall, - Send, Transfer) from .reentrancy import Reentrancy @@ -43,6 +37,7 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + STANDARD_JSON = False def find_reentrancies(self): result = {} @@ -87,28 +82,27 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m calls = sorted(list(set(calls)), key=lambda x: x.node_id) send_eth = sorted(list(set(send_eth)), key=lambda x: x.node_id) - info = 'Reentrancy in {} ({}):\n' - info = info.format(func.canonical_name, func.source_mapping_str) - info += '\tExternal calls:\n' + info = ['Reentrancy in ', func, ':\n'] + info += ['\tExternal calls:\n'] for call_info in calls: - info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) - if calls != send_eth: - info += '\tExternal calls sending eth:\n' + info += ['\t- ' , call_info, '\n'] + if calls != send_eth and send_eth: + info += ['\tExternal calls sending eth:\n'] for call_info in send_eth: - info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) - info += '\tState variables written after the call(s):\n' + info += ['\t- ', call_info, '\n'] + info += ['\tState variables written after the call(s):\n'] for (v, node) in sorted(varsWritten, key=lambda x: (x[0].name, x[1].node_id)): - info += '\t- {} ({})\n'.format(v, node.source_mapping_str) + info += ['\t- ', v, ' in ', node, '\n'] # Create our JSON result - json = self.generate_json_result(info) + res = self.generate_result(info) # Add the function with the re-entrancy first - self.add_function_to_json(func, json) + res.add(func) # Add all underlying calls in the function which are potentially problematic. for call_info in calls: - self.add_node_to_json(call_info, json, { + res.add(call_info, { "underlying_type": "external_calls" }) @@ -117,18 +111,18 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m # If the calls are not the same ones that send eth, add the eth sending nodes. if calls != send_eth: for call_info in send_eth: - self.add_node_to_json(call_info, json, { + res.add(call_info, { "underlying_type": "external_calls_sending_eth" }) # Add all variables written via nodes which write them. for (v, node) in varsWritten: - self.add_node_to_json(node, json, { + res.add(node, { "underlying_type": "variables_written", "variable_name": v.name }) # Append our result - results.append(json) + results.append(res) return results diff --git a/slither/detectors/reentrancy/reentrancy_read_before_write.py b/slither/detectors/reentrancy/reentrancy_read_before_write.py index dfb8aa9cd..f28b5b615 100644 --- a/slither/detectors/reentrancy/reentrancy_read_before_write.py +++ b/slither/detectors/reentrancy/reentrancy_read_before_write.py @@ -5,14 +5,7 @@ Iterate over all the nodes of the graph until reaching a fixpoint """ -from slither.core.cfg.node import NodeType -from slither.core.declarations import Function, SolidityFunction -from slither.core.expressions import UnaryOperation, UnaryOperationType from slither.detectors.abstract_detector import DetectorClassification -from slither.visitors.expression.export_values import ExportValues -from slither.slithir.operations import (HighLevelCall, LowLevelCall, - LibraryCall, - Send, Transfer) from .reentrancy import Reentrancy @@ -43,6 +36,8 @@ Do not report reentrancies that involve ethers (see `reentrancy-eth`)''' ''' WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + STANDARD_JSON = False + def find_reentrancies(self): result = {} for contract in self.contracts: @@ -82,35 +77,36 @@ Do not report reentrancies that involve ethers (see `reentrancy-eth`)''' result_sorted = sorted(list(reentrancies.items()), key=lambda x:x[0][0].name) for (func, calls), varsWritten in result_sorted: calls = sorted(list(set(calls)), key=lambda x: x.node_id) - info = 'Reentrancy in {} ({}):\n' - info = info.format(func.canonical_name, func.source_mapping_str) - info += '\tExternal calls:\n' + + info = ['Reentrancy in ', func, ':\n'] + + info += ['\tExternal calls:\n'] for call_info in calls: - info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) + info += ['\t- ', call_info, '\n'] info += '\tState variables written after the call(s):\n' for (v, node) in sorted(varsWritten, key=lambda x: (x[0].name, x[1].node_id)): - info += '\t- {} ({})\n'.format(v, node.source_mapping_str) + info += ['\t- ', v, ' in ', node, '\n'] # Create our JSON result - json = self.generate_json_result(info) + res = self.generate_result(info) # Add the function with the re-entrancy first - self.add_function_to_json(func, json) + res.add(func) # Add all underlying calls in the function which are potentially problematic. for call_info in calls: - self.add_node_to_json(call_info, json, { + res.add(call_info, { "underlying_type": "external_calls" }) # Add all variables written via nodes which write them. for (v, node) in varsWritten: - self.add_node_to_json(node, json, { + res.add(node, { "underlying_type": "variables_written", "variable_name": v.name }) # Append our result - results.append(json) + results.append(res) return results diff --git a/slither/detectors/shadowing/abstract.py b/slither/detectors/shadowing/abstract.py index 15bb7e2c1..d882a09a3 100644 --- a/slither/detectors/shadowing/abstract.py +++ b/slither/detectors/shadowing/abstract.py @@ -65,14 +65,12 @@ contract DerivedContract is BaseContract{ for all_variables in shadowing: shadow = all_variables[0] variables = all_variables[1:] - info = '{} ({}) shadows:\n'.format(shadow.canonical_name, - shadow.source_mapping_str) + info = [shadow, ' shadows:\n'] for var in variables: - info += "\t- {} ({})\n".format(var.canonical_name, - var.source_mapping_str) + info += ["\t- ", var, "\n"] - json = self.generate_json_result(info) - self.add_variables_to_json(all_variables, json) - results.append(json) + res = self.generate_result(info) + + results.append(res) return results diff --git a/slither/detectors/shadowing/builtin_symbols.py b/slither/detectors/shadowing/builtin_symbols.py index 2cd4cff09..1af228c5a 100644 --- a/slither/detectors/shadowing/builtin_symbols.py +++ b/slither/detectors/shadowing/builtin_symbols.py @@ -77,7 +77,7 @@ contract Bug { results = [] for local in function_or_modifier.variables: if self.is_builtin_symbol(local.name): - results.append((self.SHADOWING_LOCAL_VARIABLE, local, function_or_modifier)) + results.append((self.SHADOWING_LOCAL_VARIABLE, local)) return results def detect_builtin_shadowing_definitions(self, contract): @@ -92,18 +92,18 @@ contract Bug { # Loop through all functions, modifiers, variables (state and local) to detect any built-in symbol keywords. for function in contract.functions_declared: if self.is_builtin_symbol(function.name): - result.append((self.SHADOWING_FUNCTION, function, None)) + result.append((self.SHADOWING_FUNCTION, function)) result += self.detect_builtin_shadowing_locals(function) for modifier in contract.modifiers_declared: if self.is_builtin_symbol(modifier.name): - result.append((self.SHADOWING_MODIFIER, modifier, None)) + result.append((self.SHADOWING_MODIFIER, modifier)) result += self.detect_builtin_shadowing_locals(modifier) for variable in contract.state_variables_declared: if self.is_builtin_symbol(variable.name): - result.append((self.SHADOWING_STATE_VARIABLE, variable, None)) + result.append((self.SHADOWING_STATE_VARIABLE, variable)) for event in contract.events_declared: if self.is_builtin_symbol(event.name): - result.append((self.SHADOWING_EVENT, event, None)) + result.append((self.SHADOWING_EVENT, event)) return result @@ -124,27 +124,10 @@ contract Bug { # Obtain components shadow_type = shadow[0] shadow_object = shadow[1] - local_variable_parent = shadow[2] - - # Build the path for our info string - local_variable_path = contract.name + "." - if local_variable_parent is not None: - local_variable_path += local_variable_parent.name + "." - local_variable_path += shadow_object.name - - info = '{} ({} @ {}) shadows built-in symbol \"{}"\n'.format(local_variable_path, - shadow_type, - shadow_object.source_mapping_str, - shadow_object.name) - - # Generate relevant JSON data for this shadowing definition. - json = self.generate_json_result(info) - if shadow_type in [self.SHADOWING_FUNCTION, self.SHADOWING_MODIFIER]: - self.add_function_to_json(shadow_object, json) - elif shadow_type == self.SHADOWING_EVENT: - self.add_event_to_json(shadow_object, json) - elif shadow_type in [self.SHADOWING_STATE_VARIABLE, self.SHADOWING_LOCAL_VARIABLE]: - self.add_variable_to_json(shadow_object, json) - results.append(json) + + info = [shadow_object, f' ({shadow_type}) shadows built-in symbol"\n'] + + res = self.generate_result(info) + results.append(res) return results diff --git a/slither/detectors/shadowing/local.py b/slither/detectors/shadowing/local.py index 66f769882..7f00f495d 100644 --- a/slither/detectors/shadowing/local.py +++ b/slither/detectors/shadowing/local.py @@ -68,23 +68,23 @@ contract Bug { # Check functions for scope_function in scope_contract.functions_declared: if variable.name == scope_function.name: - overshadowed.append((self.OVERSHADOWED_FUNCTION, scope_contract.name, scope_function)) + overshadowed.append((self.OVERSHADOWED_FUNCTION, scope_function)) # Check modifiers for scope_modifier in scope_contract.modifiers_declared: if variable.name == scope_modifier.name: - overshadowed.append((self.OVERSHADOWED_MODIFIER, scope_contract.name, scope_modifier)) + overshadowed.append((self.OVERSHADOWED_MODIFIER, scope_modifier)) # Check events for scope_event in scope_contract.events_declared: if variable.name == scope_event.name: - overshadowed.append((self.OVERSHADOWED_EVENT, scope_contract.name, scope_event)) + overshadowed.append((self.OVERSHADOWED_EVENT, scope_event)) # Check state variables for scope_state_variable in scope_contract.state_variables_declared: if variable.name == scope_state_variable.name: - overshadowed.append((self.OVERSHADOWED_STATE_VARIABLE, scope_contract.name, scope_state_variable)) + overshadowed.append((self.OVERSHADOWED_STATE_VARIABLE, scope_state_variable)) # If we have found any overshadowed objects, we'll want to add it to our result list. if overshadowed: - result.append((contract.name, function.name, variable, overshadowed)) + result.append((variable, overshadowed)) return result @@ -102,29 +102,15 @@ contract Bug { shadows = self.detect_shadowing_definitions(contract) if shadows: for shadow in shadows: - local_parent_name = shadow[1] - local_variable = shadow[2] - overshadowed = shadow[3] - info = '{}.{}.{} (local variable @ {}) shadows:\n'.format(contract.name, - local_parent_name, - local_variable.name, - local_variable.source_mapping_str) + local_variable = shadow[0] + overshadowed = shadow[1] + info = [local_variable, ' shadows:\n'] for overshadowed_entry in overshadowed: - info += "\t- {}.{} ({} @ {})\n".format(overshadowed_entry[1], - overshadowed_entry[2], - overshadowed_entry[0], - overshadowed_entry[2].source_mapping_str) + info += ["\t- ", overshadowed_entry[1], f" ({overshadowed_entry[0]})\n"] # Generate relevant JSON data for this shadowing definition. - json = self.generate_json_result(info) - self.add_variable_to_json(local_variable, json) - for overshadowed_entry in overshadowed: - if overshadowed_entry[0] in [self.OVERSHADOWED_FUNCTION, self.OVERSHADOWED_MODIFIER]: - self.add_function_to_json(overshadowed_entry[2], json) - elif overshadowed_entry[0] == self.OVERSHADOWED_EVENT: - self.add_event_to_json(overshadowed_entry[2], json) - elif overshadowed_entry[0] == self.OVERSHADOWED_STATE_VARIABLE: - self.add_variable_to_json(overshadowed_entry[2], json) - results.append(json) + res = self.generate_result(info) + + results.append(res) return results diff --git a/slither/detectors/shadowing/state.py b/slither/detectors/shadowing/state.py index 41da711f3..81421cd2d 100644 --- a/slither/detectors/shadowing/state.py +++ b/slither/detectors/shadowing/state.py @@ -76,15 +76,12 @@ contract DerivedContract is BaseContract{ for all_variables in shadowing: shadow = all_variables[0] variables = all_variables[1:] - info = '{} ({}) shadows:\n'.format(shadow.canonical_name, - shadow.source_mapping_str) + info = [shadow, ' shadows:\n'] for var in variables: - info += "\t- {} ({})\n".format(var.canonical_name, - var.source_mapping_str) + info += ["\t- ", var, "\n"] - json = self.generate_json_result(info) - self.add_variables_to_json(all_variables, json) - results.append(json) + res = self.generate_result(info) + results.append(res) return results diff --git a/slither/detectors/source/rtlo.py b/slither/detectors/source/rtlo.py index 5c0599940..dbc976da2 100644 --- a/slither/detectors/source/rtlo.py +++ b/slither/detectors/source/rtlo.py @@ -1,6 +1,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification import re + class RightToLeftOverride(AbstractDetector): """ Detect the usage of a Right-To-Left-Override (U+202E) character @@ -45,6 +46,7 @@ contract Token WIKI_RECOMMENDATION = 'Special control characters must not be allowed.' RTLO_CHARACTER_ENCODED = "\u202e".encode('utf-8') + STANDARD_JSON = False def _detect(self): results = [] @@ -66,15 +68,18 @@ contract Token else: # We found another instance of the character, define our output idx = start_index + result_index - info = f"{filename} contains a unicode right-to-left-override character at byte offset {idx}:\n" + + relative = self.slither.crytic_compile.filename_lookup(filename).relative + info = f"{relative} contains a unicode right-to-left-override character at byte offset {idx}:\n" # We have a patch, so pattern.find will return at least one result info += f"\t- {pattern.findall(source_encoded)[0]}\n" - json = self.generate_json_result(info) - self.add_other_to_json("rtlo-character", - (filename, idx, len(self.RTLO_CHARACTER_ENCODED)), json) - results.append(json) + res = self.generate_result(info) + res.add_other("rtlo-character", + (filename, idx, len(self.RTLO_CHARACTER_ENCODED)), + self.slither) + results.append(res) # Advance the start index for the next iteration start_index = result_index + 1 diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 936794366..bf84dab7a 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -51,15 +51,12 @@ class Assembly(AbstractDetector): for c in self.contracts: values = self.detect_assembly(c) for func, nodes in values: - info = "{} uses assembly ({})\n" - info = info.format(func.canonical_name, func.source_mapping_str) + info = [func, " uses assembly\n"] for node in nodes: - info += "\t- {}\n".format(node.source_mapping_str) + info += ["\t- ", node, "\n"] - json = self.generate_json_result(info) - self.add_function_to_json(func, json) - self.add_nodes_to_json(nodes, json) - results.append(json) + res = self.generate_result(info) + results.append(res) return results diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index 27622ad4a..cf115c858 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -16,7 +16,7 @@ class MultipleCallsInLoop(AbstractDetector): IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop' + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop' WIKI_TITLE = 'Calls inside a loop' @@ -87,11 +87,8 @@ If one of the destinations has a fallback function which reverts, `bad` will alw for node in values: func = node.function - info = "{} has external calls inside a loop: \"{}\" ({})\n" - info = info.format(func.canonical_name, node.expression, node.source_mapping_str) - - json = self.generate_json_result(info) - self.add_node_to_json(node, json) - results.append(json) + info = [func, " has external calls inside a loop: ", node, "\n"] + res = self.generate_result(info) + results.append(res) return results diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py index e84277009..987757847 100644 --- a/slither/detectors/statements/controlled_delegatecall.py +++ b/slither/detectors/statements/controlled_delegatecall.py @@ -2,6 +2,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi from slither.slithir.operations import LowLevelCall from slither.analyses.data_dependency.data_dependency import is_tainted + class ControlledDelegateCall(AbstractDetector): """ """ @@ -46,14 +47,11 @@ Bob calls `delegate` and delegates the execution to its malicious contract. As a continue nodes = self.controlled_delegatecall(f) if nodes: - func_info = '{}.{} ({}) uses delegatecall to a input-controlled function id\n' - func_info = func_info.format(contract.name, f.name, f.source_mapping_str) - for node in nodes: - node_info = func_info + '\t- {} ({})\n'.format(node.expression, node.source_mapping_str) + func_info = [f, ' uses delegatecall to a input-controlled function id\n'] - json = self.generate_json_result(node_info) - self.add_node_to_json(node, json) - self.add_function_to_json(f, json) - results.append(json) + for node in nodes: + node_info = func_info + ['\t- ', node,'\n'] + res = self.generate_result(node_info) + results.append(res) return results diff --git a/slither/detectors/statements/deprecated_calls.py b/slither/detectors/statements/deprecated_calls.py index c18843125..b56a807c5 100644 --- a/slither/detectors/statements/deprecated_calls.py +++ b/slither/detectors/statements/deprecated_calls.py @@ -152,20 +152,12 @@ contract ContractWithDeprecatedReferences { for deprecated_reference in deprecated_references: source_object = deprecated_reference[0] deprecated_entries = deprecated_reference[1] - info = 'Deprecated standard detected @ {}:\n'.format(source_object.source_mapping_str) + info = ['Deprecated standard detected ', source_object, ':\n'] for (dep_id, original_desc, recommended_disc) in deprecated_entries: - info += "\t- Usage of \"{}\" should be replaced with \"{}\"\n".format(original_desc, - recommended_disc) + info += [f"\t- Usage of \"{original_desc}\" should be replaced with \"{recommended_disc}\"\n"] - - # Generate relevant JSON data for this deprecated standard. - json = self.generate_json_result(info) - if isinstance(source_object, StateVariableSolc) or isinstance(source_object, StateVariable): - self.add_variable_to_json(source_object, json) - else: - self.add_nodes_to_json([source_object], json) - - results.append(json) + res = self.generate_result(info) + results.append(res) return results diff --git a/slither/detectors/statements/incorrect_strict_equality.py b/slither/detectors/statements/incorrect_strict_equality.py index fe5da8cbf..b0b47d025 100644 --- a/slither/detectors/statements/incorrect_strict_equality.py +++ b/slither/detectors/statements/incorrect_strict_equality.py @@ -3,7 +3,6 @@ """ -import itertools from slither.analyses.data_dependency.data_dependency import is_dependent_ssa from slither.core.declarations import Function from slither.detectors.abstract_detector import (AbstractDetector, @@ -15,7 +14,7 @@ from slither.core.solidity_types import MappingType, ElementaryType from slither.core.variables.state_variable import StateVariable from slither.core.declarations.solidity_variables import SolidityVariable, SolidityVariableComposed -from slither.slithir.variables import ReferenceVariable + class IncorrectStrictEquality(AbstractDetector): ARGUMENT = 'incorrect-equality' @@ -112,19 +111,16 @@ contract Crowdsale{ ret = sorted(list(ret.items()), key=lambda x:x[0].name) for f, nodes in ret: - func_info = "{} ({}) uses a dangerous strict equality:\n".format(f.canonical_name, - f.source_mapping_str) + func_info = [f, " uses a dangerous strict equality:\n"] # sort the nodes to get deterministic results nodes.sort(key=lambda x: x.node_id) # Output each node with the function info header as a separate result. for node in nodes: - node_info = func_info + f"\t- {str(node.expression)}\n" + node_info = func_info + [f"\t- ", node, "\n"] - json = self.generate_json_result(node_info) - self.add_node_to_json(node, json) - self.add_function_to_json(f, json) - results.append(json) + res = self.generate_result(node_info) + results.append(res) return results diff --git a/slither/detectors/statements/too_many_digits.py b/slither/detectors/statements/too_many_digits.py index b5565094d..32d4ab93c 100644 --- a/slither/detectors/statements/too_many_digits.py +++ b/slither/detectors/statements/too_many_digits.py @@ -5,6 +5,7 @@ Module detecting numbers with too many digits. from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.variables import Constant + class TooManyDigits(AbstractDetector): """ Detect numbers with too many digits @@ -48,7 +49,6 @@ Use: if isinstance(read, Constant): # read.value can return an int or a str. Convert it to str value_as_str = read.original_value - line_of_code = str(node.expression) if '00000' in value_as_str: # Info to be printed ret.append(node) @@ -64,15 +64,12 @@ Use: # iterate over all the nodes ret = self._detect_too_many_digits(f) if ret: - func_info = '{}.{} ({}) uses literals with too many digits:'.format(f.contract.name, - f.name, - f.source_mapping_str) + func_info = [f, ' uses literals with too many digits:'] for node in ret: - node_info = func_info + '\n\t- {}\n'.format(node.expression) + node_info = func_info + ['\n\t- ', node,'\n'] # Add the result in result - json = self.generate_json_result(node_info) - self.add_node_to_json(node, json) - results.append(json) + res = self.generate_result(node_info) + results.append(res) return results diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index e759366eb..adc274b10 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -4,6 +4,7 @@ Module detecting usage of `tx.origin` in a conditional node from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + class TxOrigin(AbstractDetector): """ Detect usage of tx.origin in a conditional node @@ -67,12 +68,8 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls ` for func, nodes in values: for node in nodes: - info = "{} uses tx.origin for authorization: \"{}\" ({})\n".format(func.canonical_name, - node.expression, - node.source_mapping_str) - - json = self.generate_json_result(info) - self.add_node_to_json(node, json) - results.append(json) + info = [func, " uses tx.origin for authorization: ", node, "\n"] + res = self.generate_result(info) + results.append(res) return results diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index f062a0c7a..2d6d40b06 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -7,6 +7,8 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi from slither.visitors.expression.export_values import ExportValues from slither.core.declarations.solidity_variables import SolidityFunction from slither.core.variables.state_variable import StateVariable +from slither.formatters.variables.possible_const_state_variables import format + class ConstCandidateStateVars(AbstractDetector): """ @@ -76,7 +78,7 @@ class ConstCandidateStateVars(AbstractDetector): all_functions = [c.all_functions_called for c in self.slither.contracts] all_functions = list(set([item for sublist in all_functions for item in sublist])) - all_variables_written = [f.state_variables_written for f in all_functions] + all_variables_written = [f.state_variables_written for f in all_functions if not f.is_constructor_variables] all_variables_written = set([item for sublist in all_variables_written for item in sublist]) constable_variables = [v for v in all_non_constant_elementary_variables @@ -86,11 +88,12 @@ class ConstCandidateStateVars(AbstractDetector): # Create a result for each finding for v in constable_variables: - info = "{} should be constant ({})\n".format(v.canonical_name, - v.source_mapping_str) - json = self.generate_json_result(info) - self.add_variable_to_json(v, json) - + info = [v, " should be constant\n"] + json = self.generate_result(info) results.append(json) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index 39219ae4a..09d185208 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -6,8 +6,6 @@ """ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.cfg.node import NodeType -from slither.visitors.expression.find_push import FindPush class UninitializedLocalVars(AbstractDetector): @@ -99,17 +97,9 @@ Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and ar self._detect_uninitialized(function, function.entry_point, []) all_results = list(set(self.results)) for(function, uninitialized_local_variable) in all_results: - var_name = uninitialized_local_variable.name - info = "{} in {} ({}) is a local variable never initialiazed\n" - info = info.format(var_name, - function.canonical_name, - uninitialized_local_variable.source_mapping_str) - - - json = self.generate_json_result(info) - self.add_variable_to_json(uninitialized_local_variable, json) - self.add_function_to_json(function, json) + info = [uninitialized_local_variable, " is a local variable never initialiazed\n"] + json = self.generate_result(info) results.append(json) return results diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 18b89324b..795d74e39 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -10,12 +10,7 @@ """ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.variables.state_variable import StateVariable -from slither.slithir.variables import ReferenceVariable -from slither.slithir.operations.assignment import Assignment - -from slither.slithir.operations import (OperationWithLValue, Index, Member, - InternalCall, InternalDynamicCall, LibraryCall) +from slither.slithir.operations import InternalCall, LibraryCall class UninitializedStateVarsDetection(AbstractDetector): @@ -92,18 +87,13 @@ Initialize all the variables. If a variable is meant to be initialized to zero, for c in self.slither.contracts_derived: ret = self.detect_uninitialized(c) for variable, functions in ret: - info = "{} ({}) is never initialized. It is used in:\n" - info = info.format(variable.canonical_name, - variable.source_mapping_str) - for f in functions: - info += "\t- {} ({})\n".format(f.name, f.source_mapping_str) - source = [variable.source_mapping] - source += [f.source_mapping for f in functions] + info = [variable, " is never initialized. It is used in:\n"] + + for f in functions: + info += ["\t- ", f, "\n"] - json = self.generate_json_result(info) - self.add_variable_to_json(variable, json) - self.add_functions_to_json(functions, json) + json = self.generate_result(info) results.append(json) return results diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index ef1d9ba3c..7efa3391b 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -7,8 +7,6 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.visitors.expression.find_push import FindPush - class UninitializedStorageVars(AbstractDetector): """ @@ -103,15 +101,8 @@ Bob calls `func`. As a result, `owner` is override to 0. self._detect_uninitialized(function, function.entry_point, []) for(function, uninitialized_storage_variable) in self.results: - var_name = uninitialized_storage_variable.name - - info = "{} in {} ({}) is a storage variable never initialiazed\n" - info = info.format(var_name, function.canonical_name, uninitialized_storage_variable.source_mapping_str) - - - json = self.generate_json_result(info) - self.add_variable_to_json(uninitialized_storage_variable, json) - self.add_function_to_json(function, json) + info = [uninitialized_storage_variable, " is a storage variable never initialiazed\n"] + json = self.generate_result(info) results.append(json) return results diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 8f03cae8e..d1b4f2179 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi from slither.core.solidity_types import ArrayType from slither.visitors.expression.export_values import ExportValues from slither.core.variables.state_variable import StateVariable +from slither.formatters.variables.unused_state_variables import format class UnusedStateVars(AbstractDetector): """ @@ -31,8 +32,8 @@ class UnusedStateVars(AbstractDetector): # Get all the variables read in all the functions and modifiers all_functions = (contract.all_functions_called + contract.modifiers) - variables_used = [x.state_variables_read + x.state_variables_written for x in - all_functions] + variables_used = [x.state_variables_read for x in all_functions] + variables_used += [x.state_variables_written for x in all_functions if not x.is_constructor_variables] array_candidates = [x.variables for x in all_functions] array_candidates = [i for sl in array_candidates for i in sl] + contract.state_variables @@ -41,9 +42,12 @@ class UnusedStateVars(AbstractDetector): array_candidates = [i for sl in array_candidates for i in sl] array_candidates = [v for v in array_candidates if isinstance(v, StateVariable)] + + # Flat list variables_used = [item for sublist in variables_used for item in sublist] variables_used = list(set(variables_used + array_candidates)) + # Return the variables unused that are not public return [x for x in contract.variables if x not in variables_used and x.visibility != 'public'] @@ -56,14 +60,12 @@ class UnusedStateVars(AbstractDetector): unusedVars = self.detect_unused(c) if unusedVars: for var in unusedVars: - info = "{} ({}) is never used in {}\n".format(var.canonical_name, - var.source_mapping_str, - c.name) - - - json = self.generate_json_result(info) - self.add_variable_to_json(var, json) - self.add_contract_to_json(c, json) + info = [var, " is never used in ", c, "\n"] + json = self.generate_result(info) results.append(json) return results + + @staticmethod + def _format(slither, result): + format(slither, result) diff --git a/utils/__init__.py b/slither/formatters/__init__.py similarity index 100% rename from utils/__init__.py rename to slither/formatters/__init__.py diff --git a/utils/possible_paths/__init__.py b/slither/formatters/attributes/__init__.py similarity index 100% rename from utils/possible_paths/__init__.py rename to slither/formatters/attributes/__init__.py diff --git a/slither/formatters/attributes/const_functions.py b/slither/formatters/attributes/const_functions.py new file mode 100644 index 000000000..ed16c29a6 --- /dev/null +++ b/slither/formatters/attributes/const_functions.py @@ -0,0 +1,36 @@ +import re +from slither.formatters.exceptions import FormatError +from slither.formatters.utils.patches import create_patch + +def format(slither, result): + elements = result['elements'] + for element in elements: + if element['type'] != "function": + # Skip variable elements + continue + target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name']) + if target_contract: + function = target_contract.get_function_from_signature(element['type_specific_fields']['signature']) + if function: + _patch(slither, + result, + element['source_mapping']['filename_absolute'], + int(function.parameters_src.source_mapping['start'] + + function.parameters_src.source_mapping['length']), + int(function.returns_src.source_mapping['start'])) + + +def _patch(slither, result, in_file, modify_loc_start, modify_loc_end): + in_file_str = slither.source_code[in_file].encode('utf8') + old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] + # Find the keywords view|pure|constant and remove them + m = re.search("(view|pure|constant)", old_str_of_interest.decode('utf-8')) + if m: + create_patch(result, + in_file, + modify_loc_start + m.span()[0], + modify_loc_start + m.span()[1], + m.groups(0)[0], # this is view|pure|constant + "") + else: + raise FormatError("No view/pure/constant specifier exists. Regex failed to remove specifier!") diff --git a/slither/formatters/attributes/constant_pragma.py b/slither/formatters/attributes/constant_pragma.py new file mode 100644 index 000000000..b039f9895 --- /dev/null +++ b/slither/formatters/attributes/constant_pragma.py @@ -0,0 +1,69 @@ +import re +from slither.formatters.exceptions import FormatImpossible +from slither.formatters.utils.patches import create_patch + +# Indicates the recommended versions for replacement +REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"] + +# group: +# 0: ^ > >= < <= (optional) +# 1: ' ' (optional) +# 2: version number +# 3: version number +# 4: version number +PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') + + +def format(slither, result): + elements = result['elements'] + versions_used = [] + for element in elements: + versions_used.append(''.join(element['type_specific_fields']['directive'][1:])) + solc_version_replace = _analyse_versions(versions_used) + for element in elements: + _patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace, + element['source_mapping']['start'], + element['source_mapping']['start'] + element['source_mapping']['length']) + + +def _analyse_versions(used_solc_versions): + replace_solc_versions = list() + for version in used_solc_versions: + replace_solc_versions.append(_determine_solc_version_replacement(version)) + if not all(version == replace_solc_versions[0] for version in replace_solc_versions): + raise FormatImpossible("Multiple incompatible versions!") + else: + return replace_solc_versions[0] + + +def _determine_solc_version_replacement(used_solc_version): + versions = PATTERN.findall(used_solc_version) + if len(versions) == 1: + version = versions[0] + minor_version = '.'.join(version[2:])[2] + if minor_version == '4': + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' + elif minor_version == '5': + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + else: + raise FormatImpossible("Unknown version!") + elif len(versions) == 2: + version_right = versions[1] + minor_version_right = '.'.join(version_right[2:])[2] + if minor_version_right == '4': + # Replace with 0.4.25 + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' + elif minor_version_right in ['5', '6']: + # Replace with 0.5.3 + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + + +def _patch(slither, result, in_file, pragma, modify_loc_start, modify_loc_end): + in_file_str = slither.source_code[in_file].encode('utf8') + old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] + create_patch(result, + in_file, + int(modify_loc_start), + int(modify_loc_end), + old_str_of_interest, + pragma) diff --git a/slither/formatters/attributes/incorrect_solc.py b/slither/formatters/attributes/incorrect_solc.py new file mode 100644 index 000000000..33013c954 --- /dev/null +++ b/slither/formatters/attributes/incorrect_solc.py @@ -0,0 +1,59 @@ +import re +from slither.formatters.exceptions import FormatImpossible +from slither.formatters.utils.patches import create_patch + + +# Indicates the recommended versions for replacement +REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"] + +# group: +# 0: ^ > >= < <= (optional) +# 1: ' ' (optional) +# 2: version number +# 3: version number +# 4: version number +PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') + +def format(slither, result): + elements = result['elements'] + for element in elements: + solc_version_replace = _determine_solc_version_replacement( + ''.join(element['type_specific_fields']['directive'][1:])) + + _patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace, + element['source_mapping']['start'], element['source_mapping']['start'] + + element['source_mapping']['length']) + +def _determine_solc_version_replacement(used_solc_version): + versions = PATTERN.findall(used_solc_version) + if len(versions) == 1: + version = versions[0] + minor_version = '.'.join(version[2:])[2] + if minor_version == '4': + # Replace with 0.4.25 + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' + elif minor_version == '5': + # Replace with 0.5.3 + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + else: + raise FormatImpossible(f"Unknown version {versions}") + elif len(versions) == 2: + version_right = versions[1] + minor_version_right = '.'.join(version_right[2:])[2] + if minor_version_right == '4': + # Replace with 0.4.25 + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' + elif minor_version_right in ['5','6']: + # Replace with 0.5.3 + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + + +def _patch(slither, result, in_file, solc_version, modify_loc_start, modify_loc_end): + in_file_str = slither.source_code[in_file].encode('utf8') + old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] + create_patch(result, + in_file, + int(modify_loc_start), + int(modify_loc_end), + old_str_of_interest, + solc_version) diff --git a/slither/formatters/exceptions.py b/slither/formatters/exceptions.py new file mode 100644 index 000000000..eac7df205 --- /dev/null +++ b/slither/formatters/exceptions.py @@ -0,0 +1,5 @@ +from slither.exceptions import SlitherException + +class FormatImpossible(SlitherException): pass + +class FormatError(SlitherException): pass diff --git a/utils/upgradeability/__init__.py b/slither/formatters/functions/__init__.py similarity index 100% rename from utils/upgradeability/__init__.py rename to slither/formatters/functions/__init__.py diff --git a/slither/formatters/functions/external_function.py b/slither/formatters/functions/external_function.py new file mode 100644 index 000000000..1eb027902 --- /dev/null +++ b/slither/formatters/functions/external_function.py @@ -0,0 +1,42 @@ +import re +from slither.formatters.utils.patches import create_patch + +def format(slither, result): + elements = result['elements'] + for element in elements: + target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name']) + if target_contract: + function = target_contract.get_function_from_signature(element['type_specific_fields']['signature']) + if function: + _patch(slither, + result, + element['source_mapping']['filename_absolute'], + int(function.parameters_src.source_mapping['start']), + int(function.returns_src.source_mapping['start'])) + + +def _patch(slither, result, in_file, modify_loc_start, modify_loc_end): + in_file_str = slither.source_code[in_file].encode('utf8') + old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] + # Search for 'public' keyword which is in-between the function name and modifier name (if present) + # regex: 'public' could have spaces around or be at the end of the line + m = re.search(r'((\spublic)\s+)|(\spublic)$|(\)public)$', old_str_of_interest.decode('utf-8')) + if m is None: + # No visibility specifier exists; public by default. + create_patch(result, + in_file, + # start after the function definition's closing paranthesis + modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1, + # end is same as start because we insert the keyword `external` at that location + modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1, + "", + " external") # replace_text is `external` + else: + create_patch(result, + in_file, + # start at the keyword `public` + modify_loc_start + m.span()[0] + 1, + # end after the keyword `public` = start + len('public'') + modify_loc_start + m.span()[0] + 1 + len('public'), + "public", + "external") diff --git a/slither/formatters/naming_convention/__init__.py b/slither/formatters/naming_convention/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/formatters/naming_convention/naming_convention.py b/slither/formatters/naming_convention/naming_convention.py new file mode 100644 index 000000000..7480524f5 --- /dev/null +++ b/slither/formatters/naming_convention/naming_convention.py @@ -0,0 +1,609 @@ +import re +import logging +from slither.slithir.operations import Send, Transfer, OperationWithLValue, HighLevelCall, LowLevelCall, \ + InternalCall, InternalDynamicCall +from slither.core.declarations import Modifier +from slither.core.solidity_types import UserDefinedType, MappingType +from slither.core.declarations import Enum, Contract, Structure, Function +from slither.core.solidity_types.elementary_type import ElementaryTypeName +from slither.core.variables.local_variable import LocalVariable +from slither.formatters.exceptions import FormatError, FormatImpossible +from slither.formatters.utils.patches import create_patch + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('Slither.Format') + +def format(slither, result): + elements = result['elements'] + for element in elements: + target = element['additional_fields']['target'] + + convention = element['additional_fields']['convention'] + + if convention == "l_O_I_should_not_be_used": + # l_O_I_should_not_be_used cannot be automatically patched + logger.info(f'The following naming convention cannot be patched: \n{result["description"]}') + continue + + _patch(slither, result, element, target) + +# endregion +################################################################################### +################################################################################### +# region Conventions +################################################################################### +################################################################################### + +KEY = 'ALL_NAMES_USED' + +# https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#reserved-keywords +SOLIDITY_KEYWORDS = ['abstract', 'after', 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', 'default', 'define', + 'final', 'immutable', 'implements', 'in', 'inline', 'let', 'macro', 'match', 'mutable', 'null', + 'of', 'override', 'partial', 'promise', 'reference', 'relocatable', 'sealed', 'sizeof', 'static', + 'supports', 'switch', 'try', 'typedef', 'typeof', 'unchecked'] + +# https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#language-grammar +SOLIDITY_KEYWORDS += ['pragma', 'import', 'contract', 'library', 'contract', 'function', 'using', 'struct', 'enum', + 'public', 'private', 'internal', 'external', 'calldata', 'memory', 'modifier', 'view', 'pure', + 'constant', 'storage', 'for', 'if', 'while', 'break', 'return', 'throw', 'else', 'type'] + +SOLIDITY_KEYWORDS += ElementaryTypeName + +def _name_already_use(slither, name): + # Do not convert to a name used somewhere else + if not KEY in slither.context: + all_names = set() + for contract in slither.contracts_derived: + all_names = all_names.union(set([st.name for st in contract.structures])) + all_names = all_names.union(set([f.name for f in contract.functions_and_modifiers])) + all_names = all_names.union(set([e.name for e in contract.enums])) + all_names = all_names.union(set([s.name for s in contract.state_variables])) + + for function in contract.functions: + all_names = all_names.union(set([v.name for v in function.variables])) + + slither.context[KEY] = all_names + return name in slither.context[KEY] + +def _convert_CapWords(original_name, slither): + name = original_name.capitalize() + + while '_' in name: + offset = name.find('_') + if len(name) > offset: + name = name[0:offset] + name[offset+1].upper() + name[offset+1:] + + if _name_already_use(slither, name): + raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)') + + if name in SOLIDITY_KEYWORDS: + raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)') + return name + +def _convert_mixedCase(original_name, slither): + name = original_name + if isinstance(name, bytes): + name = name.decode('utf8') + + while '_' in name: + offset = name.find('_') + if len(name) > offset: + name = name[0:offset] + name[offset + 1].upper() + name[offset + 2:] + + name = name[0].lower() + name[1:] + if _name_already_use(slither, name): + raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)') + if name in SOLIDITY_KEYWORDS: + raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)') + return name + +def _convert_UPPER_CASE_WITH_UNDERSCORES(name, slither): + if _name_already_use(slither, name.upper()): + raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (already used)') + if name.upper() in SOLIDITY_KEYWORDS: + raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (Solidity keyword)') + return name.upper() + +conventions ={ + "CapWords":_convert_CapWords, + "mixedCase":_convert_mixedCase, + "UPPER_CASE_WITH_UNDERSCORES":_convert_UPPER_CASE_WITH_UNDERSCORES +} + + +# endregion +################################################################################### +################################################################################### +# region Helpers +################################################################################### +################################################################################### + +def _get_from_contract(slither, element, name, getter): + contract_name = element['type_specific_fields']['parent']['name'] + contract = slither.get_contract_from_name(contract_name) + return getattr(contract, getter)(name) + +# endregion +################################################################################### +################################################################################### +# region Patch dispatcher +################################################################################### +################################################################################### + +def _patch(slither, result, element, _target): + + if _target == "contract": + target = slither.get_contract_from_name(element['name']) + + elif _target == "structure": + target = _get_from_contract(slither, element, element['name'], 'get_structure_from_name') + + elif _target == "event": + target = _get_from_contract(slither, element, element['name'], 'get_event_from_name') + + elif _target == "function": + # Avoid constructor (FP?) + if element['name'] != element['type_specific_fields']['parent']['name']: + function_sig = element['type_specific_fields']['signature'] + target = _get_from_contract(slither, element, function_sig, 'get_function_from_signature') + + elif _target == "modifier": + modifier_sig = element['type_specific_fields']['signature'] + target = _get_from_contract(slither, element, modifier_sig, 'get_modifier_from_signature') + + elif _target == "parameter": + contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name'] + function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature'] + param_name = element['name'] + contract = slither.get_contract_from_name(contract_name) + function = contract.get_function_from_signature(function_sig) + target = function.get_local_variable_from_name(param_name) + + elif _target in ["variable", "variable_constant"]: + # Local variable + if element['type_specific_fields']['parent'] == 'function': + contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name'] + function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature'] + var_name = element['name'] + contract = slither.get_contract_from_name(contract_name) + function = contract.get_function_from_signature(function_sig) + target = function.get_local_variable_from_name(var_name) + # State variable + else: + target = _get_from_contract(slither, element, element['name'], 'get_state_variable_from_name') + + elif _target == "enum": + target = _get_from_contract(slither, element, element['name'], 'get_enum_from_canonical_name') + + else: + raise FormatError("Unknown naming convention! " + _target) + + _explore(slither, + result, + target, + conventions[element['additional_fields']['convention']]) + + +# endregion +################################################################################### +################################################################################### +# region Explore functions +################################################################################### +################################################################################### + +# group 1: beginning of the from type +# group 2: beginning of the to type +# nested mapping are within the group 1 +#RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)' +RE_MAPPING_FROM = b'([a-zA-Z0-9\._\[\]]*)' +RE_MAPPING_TO = b'([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)' +RE_MAPPING = b'[ ]*mapping[ ]*\([ ]*' + RE_MAPPING_FROM + b'[ ]*' + b'=>' + b'[ ]*'+ RE_MAPPING_TO + b'\)' + + +def _is_var_declaration(slither, filename, start): + ''' + Detect usage of 'var ' for Solidity < 0.5 + :param slither: + :param filename: + :param start: + :return: + ''' + v = 'var ' + return slither.source_code[filename][start:start + len(v)] == v + + +def _explore_type(slither, result, target, convert, type, filename_source_code, start, end): + if isinstance(type, UserDefinedType): + # Patch type based on contract/enum + if isinstance(type.type, (Enum, Contract)): + if type.type == target: + old_str = type.type.name + new_str = convert(old_str, slither) + + loc_start = start + if _is_var_declaration(slither, filename_source_code, start): + loc_end = loc_start + len('var') + else: + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + + else: + # Patch type based on structure + assert isinstance(type.type, Structure) + if type.type == target: + old_str = type.type.name + new_str = convert(old_str, slither) + + loc_start = start + if _is_var_declaration(slither, filename_source_code, start): + loc_end = loc_start + len('var') + else: + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + # Structure contain a list of elements, that might need patching + # .elems return a list of VariableStructure + _explore_variables_declaration(slither, + type.type.elems.values(), + result, + target, + convert) + + if isinstance(type, MappingType): + # Mapping has three steps: + # Convert the "from" type + # Convert the "to" type + # Convert nested type in the "to" + # Ex: mapping (mapping (badName => uint) => uint) + + # Do the comparison twice, so we can factor together the re matching + # mapping can only have elementary type in type_from + if isinstance(type.type_to, (UserDefinedType, MappingType)) or target in [type.type_from, type.type_to]: + + full_txt_start = start + full_txt_end = end + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + re_match = re.match(RE_MAPPING, full_txt) + assert re_match + + if type.type_from == target: + old_str = type.type_from.name + new_str = convert(old_str, slither) + + loc_start = start + re_match.start(1) + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + if type.type_to == target: + + old_str = type.type_to.name + new_str = convert(old_str, slither) + + loc_start = start + re_match.start(2) + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + if isinstance(type.type_to, (UserDefinedType, MappingType)): + loc_start = start + re_match.start(2) + loc_end = start + re_match.end(2) + _explore_type(slither, + result, + target, + convert, + type.type_to, + filename_source_code, + loc_start, + loc_end) + + + +def _explore_variables_declaration(slither, variables, result, target, convert, patch_comment=False): + for variable in variables: + # First explore the type of the variable + filename_source_code = variable.source_mapping['filename_absolute'] + full_txt_start = variable.source_mapping['start'] + full_txt_end = full_txt_start + variable.source_mapping['length'] + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + + _explore_type(slither, + result, + target, + convert, + variable.type, + filename_source_code, + full_txt_start, + variable.source_mapping['start'] + variable.source_mapping['length']) + + # If the variable is the target + if variable == target: + old_str = variable.name + new_str = convert(old_str, slither) + + loc_start = full_txt_start + full_txt.find(old_str.encode('utf8')) + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + # Patch comment only makes sense for local variable declaration in the parameter list + if patch_comment and isinstance(variable, LocalVariable): + if 'lines' in variable.source_mapping and variable.source_mapping['lines']: + func = variable.function + end_line = func.source_mapping['lines'][0] + if variable in func.parameters: + idx = len(func.parameters) - func.parameters.index(variable) + 1 + first_line = end_line - idx - 2 + + potential_comments = slither.source_code[filename_source_code].encode('utf8') + potential_comments = potential_comments.splitlines(keepends=True)[first_line:end_line-1] + + idx_beginning = func.source_mapping['start'] + idx_beginning += - func.source_mapping['starting_column'] + 1 + idx_beginning += - sum([len(c) for c in potential_comments]) + + old_comment = f'@param {old_str}'.encode('utf8') + + for line in potential_comments: + idx = line.find(old_comment) + if idx >=0: + loc_start = idx + idx_beginning + loc_end = loc_start + len(old_comment) + new_comment = f'@param {new_str}'.encode('utf8') + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_comment, + new_comment) + + break + idx_beginning += len(line) + + + + +def _explore_modifiers_calls(slither, function, result, target, convert): + for modifier in function.modifiers_statements: + for node in modifier.nodes: + if node.irs: + _explore_irs(slither, node.irs, result, target, convert) + for modifier in function.explicit_base_constructor_calls_statements: + for node in modifier.nodes: + if node.irs: + _explore_irs(slither, node.irs, result, target, convert) + +def _explore_structures_declaration(slither, structures, result, target, convert): + for st in structures: + # Explore the variable declared within the structure (VariableStructure) + _explore_variables_declaration(slither, st.elems.values(), result, target, convert) + + # If the structure is the target + if st == target: + old_str = st.name + new_str = convert(old_str, slither) + + filename_source_code = st.source_mapping['filename_absolute'] + full_txt_start = st.source_mapping['start'] + full_txt_end = full_txt_start + st.source_mapping['length'] + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + + # The name is after the space + matches = re.finditer(b'struct[ ]*', full_txt) + # Look for the end offset of the largest list of ' ' + loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + +def _explore_events_declaration(slither, events, result, target, convert): + for event in events: + # Explore the parameters + _explore_variables_declaration(slither, event.elems, result, target, convert) + + # If the event is the target + if event == target: + filename_source_code = event.source_mapping['filename_absolute'] + + old_str = event.name + new_str = convert(old_str, slither) + + loc_start = event.source_mapping['start'] + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + +def get_ir_variables(ir): + vars = ir.read + + if isinstance(ir, (InternalCall, InternalDynamicCall, HighLevelCall)): + vars += [ir.function] + + if isinstance(ir, (HighLevelCall, Send, LowLevelCall, Transfer)): + vars += [ir.call_value] + + if isinstance(ir, (HighLevelCall, LowLevelCall)): + vars += [ir.call_gas] + + if isinstance(ir, OperationWithLValue): + vars += [ir.lvalue] + + return [v for v in vars if v] + +def _explore_irs(slither, irs, result, target, convert): + if irs is None: + return + for ir in irs: + for v in get_ir_variables(ir): + if target == v or ( + isinstance(target, Function) and isinstance(v, Function) and + v.canonical_name == target.canonical_name): + source_mapping = ir.expression.source_mapping + filename_source_code = source_mapping['filename_absolute'] + full_txt_start = source_mapping['start'] + full_txt_end = full_txt_start + source_mapping['length'] + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + + if not target.name.encode('utf8') in full_txt: + raise FormatError(f'{target} not found in {full_txt} ({source_mapping}') + + old_str = target.name.encode('utf8') + new_str = convert(old_str, slither) + + counter = 0 + # Can be found multiple time on the same IR + # We patch one by one + while old_str in full_txt: + + target_found_at = full_txt.find((old_str)) + + full_txt = full_txt[target_found_at+1:] + counter += target_found_at + + loc_start = full_txt_start + counter + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + +def _explore_functions(slither, functions, result, target, convert): + for function in functions: + _explore_variables_declaration(slither, function.variables, result, target, convert, True) + _explore_modifiers_calls(slither, function, result, target, convert) + _explore_irs(slither, function.all_slithir_operations(), result, target, convert) + + if isinstance(target, Function) and function.canonical_name == target.canonical_name: + old_str = function.name + new_str = convert(old_str, slither) + + filename_source_code = function.source_mapping['filename_absolute'] + full_txt_start = function.source_mapping['start'] + full_txt_end = full_txt_start + function.source_mapping['length'] + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + + # The name is after the space + if isinstance(target, Modifier): + matches = re.finditer(b'modifier([ ]*)', full_txt) + else: + matches = re.finditer(b'function([ ]*)', full_txt) + # Look for the end offset of the largest list of ' ' + loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + +def _explore_enums(slither, enums, result, target, convert): + for enum in enums: + if enum == target: + old_str = enum.name + new_str = convert(old_str, slither) + + filename_source_code = enum.source_mapping['filename_absolute'] + full_txt_start = enum.source_mapping['start'] + full_txt_end = full_txt_start + enum.source_mapping['length'] + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + + # The name is after the space + matches = re.finditer(b'enum([ ]*)', full_txt) + # Look for the end offset of the largest list of ' ' + loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + +def _explore_contract(slither, contract, result, target, convert): + _explore_variables_declaration(slither, contract.state_variables, result, target, convert) + _explore_structures_declaration(slither, contract.structures, result, target, convert) + _explore_functions(slither, contract.functions_and_modifiers, result, target, convert) + _explore_enums(slither, contract.enums, result, target, convert) + + if contract == target: + filename_source_code = contract.source_mapping['filename_absolute'] + full_txt_start = contract.source_mapping['start'] + full_txt_end = full_txt_start + contract.source_mapping['length'] + full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + + old_str = contract.name + new_str = convert(old_str, slither) + + # The name is after the space + matches = re.finditer(b'contract[ ]*', full_txt) + # Look for the end offset of the largest list of ' ' + loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() + + loc_end = loc_start + len(old_str) + + create_patch(result, + filename_source_code, + loc_start, + loc_end, + old_str, + new_str) + + +def _explore(slither, result, target, convert): + for contract in slither.contracts_derived: + _explore_contract(slither, contract, result, target, convert) + + + + +# endregion + + diff --git a/slither/formatters/utils/__init__.py b/slither/formatters/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/formatters/utils/patches.py b/slither/formatters/utils/patches.py new file mode 100644 index 000000000..d92c42c56 --- /dev/null +++ b/slither/formatters/utils/patches.py @@ -0,0 +1,43 @@ +import os +import difflib +from collections import defaultdict + +def create_patch(result, file, start, end, old_str, new_str): + if isinstance(old_str, bytes): + old_str = old_str.decode('utf8') + if isinstance(new_str, bytes): + new_str = new_str.decode('utf8') + p = {"start": start, + "end": end, + "old_string": old_str, + "new_string": new_str + } + if 'patches' not in result: + result['patches'] = defaultdict(list) + if p not in result['patches'][file]: + result['patches'][file].append(p) + + +def apply_patch(original_txt, patch, offset): + patched_txt = original_txt[:int(patch['start'] + offset)] + patched_txt += patch['new_string'].encode('utf8') + patched_txt += original_txt[int(patch['end'] + offset):] + + # Keep the diff of text added or sub, in case of multiple patches + patch_length_diff = len(patch['new_string']) - (patch['end'] - patch['start']) + return patched_txt, patch_length_diff + offset + + +def create_diff(slither, original_txt, patched_txt, filename): + if slither.crytic_compile: + relative_path = slither.crytic_compile.filename_lookup(filename).relative + relative_path = os.path.join('.', relative_path) + else: + relative_path = filename + diff = difflib.unified_diff(original_txt.decode('utf8').splitlines(False), + patched_txt.decode('utf8').splitlines(False), + fromfile=relative_path, + tofile=relative_path, + lineterm='') + + return '\n'.join(list(diff)) + '\n' diff --git a/slither/formatters/variables/__init__.py b/slither/formatters/variables/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/formatters/variables/possible_const_state_variables.py b/slither/formatters/variables/possible_const_state_variables.py new file mode 100644 index 000000000..2c4c378dd --- /dev/null +++ b/slither/formatters/variables/possible_const_state_variables.py @@ -0,0 +1,38 @@ +import re +from slither.formatters.exceptions import FormatError, FormatImpossible +from slither.formatters.utils.patches import create_patch + +def format(slither, result): + elements = result['elements'] + for element in elements: + + # TODO: decide if this should be changed in the constant detector + contract_name = element['type_specific_fields']['parent']['name'] + contract = slither.get_contract_from_name(contract_name) + var = contract.get_state_variable_from_name(element['name']) + if not var.expression: + raise FormatImpossible(f'{var.name} is uninitialized and cannot become constant.') + + _patch(slither, result, element['source_mapping']['filename_absolute'], + element['name'], + "constant " + element['name'], + element['source_mapping']['start'], + element['source_mapping']['start'] + element['source_mapping']['length']) + + +def _patch(slither, result, in_file, match_text, replace_text, modify_loc_start, modify_loc_end): + in_file_str = slither.source_code[in_file].encode('utf8') + old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] + # Add keyword `constant` before the variable name + (new_str_of_interest, num_repl) = re.subn(match_text, replace_text, old_str_of_interest.decode('utf-8'), 1) + if num_repl != 0: + create_patch(result, + in_file, + modify_loc_start, + modify_loc_end, + old_str_of_interest, + new_str_of_interest) + + else: + raise FormatError("State variable not found?!") + diff --git a/slither/formatters/variables/unused_state_variables.py b/slither/formatters/variables/unused_state_variables.py new file mode 100644 index 000000000..c1a6e535c --- /dev/null +++ b/slither/formatters/variables/unused_state_variables.py @@ -0,0 +1,28 @@ +from slither.formatters.utils.patches import create_patch + + +def format(slither, result): + elements = result['elements'] + for element in elements: + if element['type'] == "variable": + _patch(slither, + result, + element['source_mapping']['filename_absolute'], + element['source_mapping']['start']) + + +def _patch(slither, result, in_file, modify_loc_start): + in_file_str = slither.source_code[in_file].encode('utf8') + old_str_of_interest = in_file_str[modify_loc_start:] + old_str = old_str_of_interest.decode('utf-8').partition(';')[0]\ + + old_str_of_interest.decode('utf-8').partition(';')[1] + + create_patch(result, + in_file, + int(modify_loc_start), + # Remove the entire declaration until the semicolon + int(modify_loc_start + len(old_str_of_interest.decode('utf-8').partition(';')[0]) + 1), + old_str, + "") + + diff --git a/slither/printers/abstract_printer.py b/slither/printers/abstract_printer.py index 4926c1de2..ceaa75d5d 100644 --- a/slither/printers/abstract_printer.py +++ b/slither/printers/abstract_printer.py @@ -1,5 +1,7 @@ import abc +from slither.utils import output + class IncorrectPrinterInitialization(Exception): pass @@ -30,6 +32,15 @@ class AbstractPrinter(metaclass=abc.ABCMeta): if self.logger: self.logger.info(info) + + def generate_output(self, info, additional_fields=None): + if additional_fields is None: + additional_fields = {} + d = output.Output(info, additional_fields) + d.data['printer'] = self.ARGUMENT + + return d + @abc.abstractmethod def output(self, filename): """TODO Documentation""" diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index bf339f8fd..97f19b853 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -13,4 +13,6 @@ from .summary.variable_order import VariableOrder from .summary.data_depenency import DataDependency from .summary.modifier_calls import Modifiers from .summary.require_calls import RequireOrAssert -from .summary.evm import PrinterEVM +from .summary.constructor_calls import ConstructorPrinter +from .guidance.echidna import Echidna +from .summary.evm import PrinterEVM \ No newline at end of file diff --git a/slither/printers/call/call_graph.py b/slither/printers/call/call_graph.py index 4c9b9e385..8710e82ed 100644 --- a/slither/printers/call/call_graph.py +++ b/slither/printers/call/call_graph.py @@ -9,13 +9,10 @@ from collections import defaultdict from slither.printers.abstract_printer import AbstractPrinter from slither.core.declarations.solidity_variables import SolidityFunction from slither.core.declarations.function import Function -from slither.core.declarations.contract import Contract -from slither.core.expressions.member_access import MemberAccess -from slither.core.expressions.identifier import Identifier from slither.core.variables.variable import Variable -from slither.core.solidity_types.user_defined_type import UserDefinedType -# return unique id for contract to use as subgraph name + + def _contract_subgraph(contract): return f'cluster_{contract.id}_{contract.name}' @@ -163,13 +160,25 @@ class PrinterCallGraph(AbstractPrinter): if filename == ".dot": filename = "all_contracts.dot" + info = '' + results = [] with open(filename, 'w', encoding='utf8') as f: - self.info(f'Call Graph: {filename}') - f.write('\n'.join(['strict digraph {'] + [self._process_functions(self.slither.functions)] + ['}'])) - + info += f'Call Graph: {filename}' + content = '\n'.join(['strict digraph {'] + [self._process_functions(self.slither.functions)] + ['}']) + f.write(content) + results.append((filename, content)) for derived_contract in self.slither.contracts_derived: with open(f'{derived_contract.name}.dot', 'w', encoding='utf8') as f: - self.info(f'Call Graph: {derived_contract.name}.dot') - f.write('\n'.join(['strict digraph {'] + [self._process_functions(derived_contract.functions)] + ['}'])) + info += f'Call Graph: {derived_contract.name}.dot' + content = '\n'.join(['strict digraph {'] + [self._process_functions(derived_contract.functions)] + ['}']) + f.write(content) + results.append((filename, content)) + + self.info(info) + res = self.generate_output(info) + for filename, content in results: + res.add_file(filename, content) + + return res diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index 00af8f1a6..24f606f1a 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -6,6 +6,7 @@ from prettytable import PrettyTable from slither.printers.abstract_printer import AbstractPrinter from slither.core.declarations.function import Function + class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): ARGUMENT = 'vars-and-auth' @@ -33,12 +34,22 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): _filename(string) """ + txt = '' + all_tables = [] for contract in self.contracts: - txt = "\nContract %s\n"%contract.name + txt += "\nContract %s\n"%contract.name table = PrettyTable(["Function", "State variables written", "Conditions on msg.sender"]) for function in contract.functions: state_variables_written = [v.name for v in function.all_state_variables_written()] msg_sender_condition = self.get_msg_sender_checks(function) table.add_row([function.name, str(state_variables_written), str(msg_sender_condition)]) - self.info(txt + str(table)) + all_tables.append((contract.name, table)) + txt += str(table) + '\n' + + self.info(txt) + res = self.generate_output(txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + + return res \ No newline at end of file diff --git a/slither/printers/functions/cfg.py b/slither/printers/functions/cfg.py index 06be72636..a8b53a3be 100644 --- a/slither/printers/functions/cfg.py +++ b/slither/printers/functions/cfg.py @@ -2,7 +2,7 @@ """ from slither.printers.abstract_printer import AbstractPrinter -from slither.core.declarations.function import Function + class CFG(AbstractPrinter): @@ -18,9 +18,20 @@ class CFG(AbstractPrinter): _filename(string) """ + info = '' + all_files = [] for contract in self.contracts: for function in contract.functions + contract.modifiers: filename = "{}-{}-{}.dot".format(original_filename, contract.name, function.full_name) - self.info('Export {}'.format(filename)) - function.slithir_cfg_to_dot(filename) + info += 'Export {}'.format(filename) + content = function.slithir_cfg_to_dot(filename) + with open(filename, 'w', encoding='utf8') as f: + f.write(content) + all_files.append((filename, content)) + + self.info(info) + res = self.generate_output(info) + for filename, content in all_files: + res.add_file(filename, content) + return res \ No newline at end of file diff --git a/slither/printers/guidance/__init__.py b/slither/printers/guidance/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py new file mode 100644 index 000000000..ab17cd15b --- /dev/null +++ b/slither/printers/guidance/echidna.py @@ -0,0 +1,145 @@ +""" +""" + +import json +from collections import defaultdict +from slither.printers.abstract_printer import AbstractPrinter +from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction +from slither.slithir.operations.binary import Binary, BinaryType +from slither.core.variables.state_variable import StateVariable +from slither.slithir.variables import Constant + + +def _extract_payable(slither): + ret = {} + for contract in slither.contracts: + payable_functions = [f.full_name for f in contract.functions_entry_points if f.payable] + if payable_functions: + ret[contract.name] = payable_functions + return ret + +def _extract_solidity_variable_usage(slither, sol_var): + ret = {} + for contract in slither.contracts: + functions_using_sol_var = [] + for f in contract.functions_entry_points: + for v in f.all_solidity_variables_read(): + if v == sol_var: + functions_using_sol_var.append(f.full_name) + break + if functions_using_sol_var: + ret[contract.name] = functions_using_sol_var + return ret + +def _extract_constant_functions(slither): + ret = {} + for contract in slither.contracts: + cst_functions = [f.full_name for f in contract.functions_entry_points if f.view or f.pure] + cst_functions += [v.function_name for v in contract.state_variables if v.visibility in ['public']] + if cst_functions: + ret[contract.name] = cst_functions + return ret + +def _extract_assert(slither): + ret = {} + for contract in slither.contracts: + functions_using_assert = [] + for f in contract.functions_entry_points: + for v in f.all_solidity_calls(): + if v == SolidityFunction('assert(bool)'): + functions_using_assert.append(f.full_name) + break + if functions_using_assert: + ret[contract.name] = functions_using_assert + return ret + +def _extract_constants_from_irs(irs, all_cst_used, all_cst_used_in_binary, context_explored): + for ir in irs: + if isinstance(ir, Binary): + for r in ir.read: + if isinstance(r, Constant): + all_cst_used_in_binary[BinaryType.str(ir.type)].append(r.value) + for r in ir.read: + if isinstance(r, Constant): + all_cst_used.append(r.value) + if isinstance(r, StateVariable): + if r.node_initialization: + if r.node_initialization.irs: + if r.node_initialization in context_explored: + continue + else: + context_explored.add(r.node_initialization) + _extract_constants_from_irs(r.node_initialization.irs, + all_cst_used, + all_cst_used_in_binary, + context_explored) + +def _extract_constants(slither): + ret_cst_used = defaultdict(dict) + ret_cst_used_in_binary = defaultdict(dict) + for contract in slither.contracts: + for function in contract.functions_entry_points: + all_cst_used = [] + all_cst_used_in_binary = defaultdict(list) + + context_explored = set() + context_explored.add(function) + _extract_constants_from_irs(function.all_slithir_operations(), + all_cst_used, + all_cst_used_in_binary, + context_explored) + + if all_cst_used: + ret_cst_used[contract.name][function.full_name] = all_cst_used + if all_cst_used_in_binary: + ret_cst_used_in_binary[contract.name][function.full_name] = all_cst_used_in_binary + return (ret_cst_used, ret_cst_used_in_binary) + + + + +class Echidna(AbstractPrinter): + ARGUMENT = 'echidna' + HELP = 'Export Echidna guiding information' + + WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna' + + + def output(self, filename): + """ + Output the inheritance relation + + _filename is not used + Args: + _filename(string) + """ + + payable = _extract_payable(self.slither) + timestamp = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('block.timestamp')) + block_number = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('block.number')) + msg_sender = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('msg.sender')) + msg_gas = _extract_solidity_variable_usage(self.slither, + SolidityVariableComposed('msg.gas')) + assert_usage = _extract_assert(self.slither) + cst_functions = _extract_constant_functions(self.slither) + (cst_used, cst_used_in_binary) = _extract_constants(self.slither) + + + d = {'payable': payable, + 'timestamp': timestamp, + 'block_number': block_number, + 'msg_sender': msg_sender, + 'msg_gas': msg_gas, + 'assert': assert_usage, + 'constant_functions': cst_functions, + 'constants_used': cst_used, + 'constants_used_in_binary': cst_used_in_binary} + + self.info(json.dumps(d, indent=4)) + + res = self.generate_output(json.dumps(d, indent=4)) + + return res \ No newline at end of file diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index c80fa7cb2..e6b2972fb 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -35,24 +35,46 @@ class PrinterInheritance(AbstractPrinter): info += blue('Child_Contract -> ') + green('Immediate_Base_Contracts') info += green(' [Not_Immediate_Base_Contracts]') + + result = {} + result['child_to_base'] = {} + for child in self.contracts: info += blue(f'\n+ {child.name}') + result['child_to_base'][child.name] = {'immediate': [], + 'not_immediate': []} if child.inheritance: + immediate = child.immediate_inheritance not_immediate = [i for i in child.inheritance if i not in immediate] + info += ' -> ' + green(", ".join(map(str, immediate))) + result['child_to_base'][child.name]['immediate'] = list(map(str, immediate)) if not_immediate: info += ", ["+ green(", ".join(map(str, not_immediate))) + "]" + result['child_to_base'][child.name]['not_immediate'] = list(map(str, not_immediate)) info += green('\n\nBase_Contract -> ') + blue('Immediate_Child_Contracts') info += blue(' [Not_Immediate_Child_Contracts]') + + result['base_to_child'] = {} for base in self.contracts: info += green(f'\n+ {base.name}') children = list(self._get_child_contracts(base)) + + result['base_to_child'][base.name] = {'immediate': [], + 'not_immediate': []} if children: immediate = [child for child in children if base in child.immediate_inheritance] not_immediate = [child for child in children if not child in immediate] + info += ' -> ' + blue(", ".join(map(str, immediate))) + result['base_to_child'][base.name]['immediate'] = list(map(str, immediate)) if not_immediate: info += ', [' + blue(", ".join(map(str, not_immediate))) + ']' + result['base_to_child'][base.name]['not_immediate'] = list(map(str, immediate)) self.info(info) + + res = self.generate_output(info, additional_fields=result) + + return res diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py index 73ee72af8..de527809a 100644 --- a/slither/printers/inheritance/inheritance_graph.py +++ b/slither/printers/inheritance/inheritance_graph.py @@ -10,7 +10,6 @@ from slither.core.declarations.contract import Contract from slither.core.solidity_types.user_defined_type import UserDefinedType from slither.printers.abstract_printer import AbstractPrinter from slither.utils.inheritance_analysis import (detect_c3_function_shadowing, - detect_function_shadowing, detect_state_variable_shadowing) @@ -26,19 +25,6 @@ class PrinterInheritanceGraph(AbstractPrinter): inheritance = [x.inheritance for x in slither.contracts] self.inheritance = set([item for sublist in inheritance for item in sublist]) - # Create a lookup of direct shadowing instances. - self.direct_overshadowing_functions = {} - shadows = detect_function_shadowing(slither.contracts, True, False) - for overshadowing_instance in shadows: - overshadowing_function = overshadowing_instance[2] - - # Add overshadowing function entry. - if overshadowing_function not in self.direct_overshadowing_functions: - self.direct_overshadowing_functions[overshadowing_function] = set() - self.direct_overshadowing_functions[overshadowing_function].add(overshadowing_instance) - - # Create a lookup of shadowing state variables. - # Format: { colliding_variable : set([colliding_variables]) } self.overshadowing_state_variables = {} shadows = detect_state_variable_shadowing(slither.contracts) for overshadowing_instance in shadows: @@ -55,7 +41,7 @@ class PrinterInheritanceGraph(AbstractPrinter): func_name = func.full_name pattern = ' %s' pattern_shadow = ' %s' - if func in self.direct_overshadowing_functions: + if func.shadows: return pattern_shadow % func_name return pattern % func_name @@ -89,12 +75,10 @@ class PrinterInheritanceGraph(AbstractPrinter): # If this variable is an overshadowing variable, we'll want to return information describing it. result = [] indirect_shadows = detect_c3_function_shadowing(contract) - if indirect_shadows: - for collision_set in sorted(indirect_shadows, key=lambda x: x[0][1].name): - winner = collision_set[-1][1].contract_declarer.name - collision_steps = [colliding_function.contract_declarer.name for _, colliding_function in collision_set] - collision_steps = ', '.join(collision_steps) - result.append(f"'{collision_set[0][1].full_name}' collides in inherited contracts {collision_steps} where {winner} is chosen.") + for winner, colliding_functions in indirect_shadows.items(): + collision_steps = ', '.join([f.contract_declarer.name + for f in colliding_functions] + [winner.contract_declarer.name]) + result.append(f"'{winner.full_name}' collides in inherited contracts {collision_steps} where {winner.contract_declarer.name} is chosen.") return '\n'.join(result) def _get_port_id(self, var, contract): @@ -116,10 +100,12 @@ class PrinterInheritanceGraph(AbstractPrinter): # Functions visibilities = ['public', 'external'] public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and f.contract_declarer == contract and f.visibility in visibilities] + not f.is_constructor and not f.is_constructor_variables + and f.contract_declarer == contract and f.visibility in visibilities] public_functions = ''.join(public_functions) private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and f.contract_declarer == contract and f.visibility not in visibilities] + not f.is_constructor and not f.is_constructor_variables + and f.contract_declarer == contract and f.visibility not in visibilities] private_functions = ''.join(private_functions) # Modifiers @@ -170,14 +156,23 @@ class PrinterInheritanceGraph(AbstractPrinter): Args: filename(string) """ + if filename == '': filename = 'contracts.dot' if not filename.endswith('.dot'): filename += ".dot" info = 'Inheritance Graph: ' + filename self.info(info) + + content = 'digraph "" {\n' + for c in self.contracts: + content += self._summary(c) + '\n' + content += '}' + with open(filename, 'w', encoding='utf8') as f: - f.write('digraph "" {\n') - for c in self.contracts: - f.write(self._summary(c)) - f.write('}') + f.write(content) + + res = self.generate_output(info) + res.add_file(filename, content) + + return res \ No newline at end of file diff --git a/slither/printers/summary/constructor_calls.py b/slither/printers/summary/constructor_calls.py new file mode 100644 index 000000000..aae307827 --- /dev/null +++ b/slither/printers/summary/constructor_calls.py @@ -0,0 +1,52 @@ +""" + Module printing summary of the contract +""" +from slither.printers.abstract_printer import AbstractPrinter +from slither.utils import output + + +class ConstructorPrinter(AbstractPrinter): + WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls' + ARGUMENT = 'constructor-calls' + HELP = 'Print the constructors executed' + + def _get_soruce_code(self, cst): + src_mapping = cst.source_mapping + content = self.slither.source_code[src_mapping['filename_absolute']] + start = src_mapping['start'] + end = src_mapping['start'] + src_mapping['length'] + initial_space = src_mapping['starting_column'] + return ' ' * initial_space + content[start:end] + + def output(self, _filename): + info = '' + for contract in self.contracts: + stack_name = [] + stack_definition = [] + info += "\n\nContact Name: " + contract.name + info += " Constructor Call Sequence: " + cst = contract.constructors_declared + if cst: + stack_name.append(contract.name) + stack_definition.append(self._get_soruce_code(cst)) + for inherited_contract in contract.inheritance: + cst = inherited_contract.constructors_declared + if cst: + stack_name.append(inherited_contract.name) + stack_definition.append(self._get_soruce_code(cst)) + if len(stack_name) > 0: + info += " " + ' '.join(stack_name[len(stack_name) - 1]) + count = len(stack_name) - 2 + while count >= 0: + info += "-->" + ' '.join(stack_name[count]) + count = count - 1 + info += "\n Constructor Definitions:" + count = len(stack_definition) - 1 + while count >= 0: + info += "\n Contract name:" + str(stack_name[count]) + info += "\n" + str(stack_definition[count]) + count = count - 1 + + self.info(info) + res = output.Output(info) + return res diff --git a/slither/printers/summary/contract.py b/slither/printers/summary/contract.py index 908fa2599..e8f91d897 100644 --- a/slither/printers/summary/contract.py +++ b/slither/printers/summary/contract.py @@ -3,10 +3,11 @@ """ import collections from slither.printers.abstract_printer import AbstractPrinter +from slither.utils import output from slither.utils.colors import blue, green, magenta -class ContractSummary(AbstractPrinter): +class ContractSummary(AbstractPrinter): ARGUMENT = 'contract-summary' HELP = 'Print a summary of the contracts' @@ -20,28 +21,42 @@ class ContractSummary(AbstractPrinter): """ txt = "" + + all_contracts = [] for c in self.contracts: - (name, _inheritance, _var, func_summaries, _modif_summaries) = c.get_summary() - txt += blue("\n+ Contract %s\n"%name) - # (c_name, f_name, visi, _, _, _, _, _) in func_summaries - public = [(elem[0], (elem[1], elem[2]) ) for elem in func_summaries] + txt += blue("\n+ Contract %s\n" % c.name) + additional_fields = output.Output('') + # Order the function with + # contract_declarer -> list_functions + public = [(f.contract_declarer.name, f) for f in c.functions if (not f.is_shadowed)] collect = collections.defaultdict(list) - for a,b in public: + for a, b in public: collect[a].append(b) public = list(collect.items()) for contract, functions in public: txt += blue(" - From {}\n".format(contract)) - functions = sorted(functions) - for (function, visi) in functions: - if visi in ['external', 'public']: - txt += green(" - {} ({})\n".format(function, visi)) - for (function, visi) in functions: - if visi in ['internal', 'private']: - txt += magenta(" - {} ({})\n".format(function, visi)) - for (function, visi) in functions: - if visi not in ['external', 'public', 'internal', 'private']: - txt += " - {}  ({})\n".format(function, visi) + + functions = sorted(functions, key=lambda f: f.full_name) + + for function in functions: + if function.visibility in ['external', 'public']: + txt += green(" - {} ({})\n".format(function, function.visibility)) + if function.visibility in ['internal', 'private']: + txt += magenta(" - {} ({})\n".format(function, function.visibility)) + if function.visibility not in ['external', 'public', 'internal', 'private']: + txt += " - {}  ({})\n".format(function, function.visibility) + + additional_fields.add(function, additional_fields={"visibility": + function.visibility}) + + all_contracts.append((c, additional_fields.data)) self.info(txt) + + res = self.generate_output(txt) + for contract, additional_fields in all_contracts: + res.add(contract, additional_fields=additional_fields) + + return res diff --git a/slither/printers/summary/data_depenency.py b/slither/printers/summary/data_depenency.py index c8fb38277..7e657ca0b 100644 --- a/slither/printers/summary/data_depenency.py +++ b/slither/printers/summary/data_depenency.py @@ -25,6 +25,9 @@ class DataDependency(AbstractPrinter): _filename(string) """ + all_tables = [] + all_txt = '' + txt = '' for c in self.contracts: txt += "\nContract %s\n"%c.name @@ -44,3 +47,12 @@ class DataDependency(AbstractPrinter): table.add_row([v.canonical_name, _get(v, f)]) txt += str(table) self.info(txt) + + all_txt += txt + all_tables.append((c.name, table)) + + res = self.generate_output(all_txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + + return res diff --git a/slither/printers/summary/function.py b/slither/printers/summary/function.py index 13650ba74..f144c9736 100644 --- a/slither/printers/summary/function.py +++ b/slither/printers/summary/function.py @@ -5,6 +5,7 @@ from prettytable import PrettyTable from slither.printers.abstract_printer import AbstractPrinter + class FunctionSummary(AbstractPrinter): ARGUMENT = 'function-summary' @@ -28,6 +29,9 @@ class FunctionSummary(AbstractPrinter): _filename(string) """ + all_tables = [] + all_txt = '' + for c in self.contracts: (name, inheritance, var, func_summaries, modif_summaries) = c.get_summary() txt = "\nContract %s"%name @@ -62,3 +66,12 @@ class FunctionSummary(AbstractPrinter): txt += "\n\n"+str(table) txt += "\n" self.info(txt) + + all_tables.append((name, table)) + all_txt += txt + + res = self.generate_output(all_txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + + return res diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 169a2a318..dc1560e5d 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -1,12 +1,9 @@ """ Module printing summary of the contract """ -import collections from prettytable import PrettyTable -from slither.core.solidity_types import ArrayType, MappingType from slither.printers.abstract_printer import AbstractPrinter -from slither.utils.colors import blue, green, magenta from slither.utils.function import get_function_id class FunctionIds(AbstractPrinter): @@ -24,6 +21,7 @@ class FunctionIds(AbstractPrinter): """ txt = '' + all_tables = [] for contract in self.slither.contracts_derived: txt += '\n{}:\n'.format(contract.name) table = PrettyTable(['Name', 'ID']) @@ -32,18 +30,15 @@ class FunctionIds(AbstractPrinter): table.add_row([function.full_name, hex(get_function_id(function.full_name))]) for variable in contract.state_variables: if variable.visibility in ['public']: - variable_getter_args = "" - if type(variable.type) is ArrayType: - length = 0 - v = variable - while type(v.type) is ArrayType: - length += 1 - v = v.type - variable_getter_args = ','.join(["uint256"]*length) - elif type(variable.type) is MappingType: - variable_getter_args = variable.type.type_from - - table.add_row([f"{variable.name}({variable_getter_args})", hex(get_function_id(f"{variable.name}({variable_getter_args})"))]) + sig = variable.function_name + table.add_row([sig, hex(get_function_id(sig))]) txt += str(table) + '\n' + all_tables.append((contract.name, table)) self.info(txt) + + res = self.generate_output(txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + + return res \ No newline at end of file diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index 2e126f8a3..9c986065d 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -4,6 +4,7 @@ Module printing summary of the contract import logging from slither.printers.abstract_printer import AbstractPrinter +from slither.utils import output from slither.utils.code_complexity import compute_cyclomatic_complexity from slither.utils.colors import green, red, yellow from slither.utils.standard_libraries import is_standard_library @@ -66,11 +67,16 @@ class PrinterHumanSummary(AbstractPrinter): logger = logging.getLogger('Detectors') logger.setLevel(logging.ERROR) + checks_optimization = self.slither.detectors_optimization checks_informational = self.slither.detectors_informational checks_low = self.slither.detectors_low checks_medium = self.slither.detectors_medium checks_high = self.slither.detectors_high + issues_optimization = [c.detect() for c in checks_optimization] + issues_optimization = [c for c in issues_optimization if c] + issues_optimization = [item for sublist in issues_optimization for item in sublist] + issues_informational = [c.detect() for c in checks_informational] issues_informational = [c for c in issues_informational if c] issues_informational = [item for sublist in issues_informational for item in sublist] @@ -89,14 +95,16 @@ class PrinterHumanSummary(AbstractPrinter): - return (len(issues_informational), + return (len(issues_optimization), + len(issues_informational), len(issues_low), len(issues_medium), len(issues_high)) def get_detectors_result(self): - issues_informational, issues_low, issues_medium, issues_high = self._get_detectors_result() - txt = "Number of informational issues: {}\n".format(green(issues_informational)) + issues_optimization, issues_informational, issues_low, issues_medium, issues_high = self._get_detectors_result() + txt = "Number of optimization issues: {}\n".format(green(issues_optimization)) + txt += "Number of informational issues: {}\n".format(green(issues_informational)) txt += "Number of low issues: {}\n".format(green(issues_low)) if issues_medium > 0: txt += "Number of medium issues: {}\n".format(yellow(issues_medium)) @@ -188,10 +196,23 @@ class PrinterHumanSummary(AbstractPrinter): txt = "\n" txt += self._compilation_type() + results = { + 'contracts': { + "elements": [] + }, + 'number_lines': 0, + 'number_lines_in_dependencies': 0, + 'standard_libraries': [], + 'ercs': [], + } + + lines_number = self._lines_number() if lines_number: total_lines, total_dep_lines = lines_number txt += f'Number of lines: {total_lines} (+ {total_dep_lines} in dependencies)\n' + results['number_lines'] = total_lines + results['number_lines__dependencies'] = total_dep_lines number_contracts, number_contracts_deps = self._number_contracts() txt += f'Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies) \n\n' @@ -201,10 +222,12 @@ class PrinterHumanSummary(AbstractPrinter): libs = self._standard_libraries() if libs: txt += f'\nUse: {", ".join(libs)}\n' + results['standard_libraries'] = [str(l) for l in libs] ercs = self._ercs() if ercs: txt += f'ERCs: {", ".join(ercs)}\n' + results['ercs'] = [str(e) for e in ercs] for contract in self.slither.contracts_derived: txt += "\nContract {}\n".format(contract.name) @@ -219,3 +242,34 @@ class PrinterHumanSummary(AbstractPrinter): txt += self.get_summary_erc20(contract) self.info(txt) + + results_contract = output.Output('') + for contract in self.slither.contracts_derived: + optimization, info, low, medium, high = self._get_detectors_result() + contract_d = {'contract_name': contract.name, + 'is_complex_code': self._is_complex_code(contract), + 'optimization_issues': optimization, + 'informational_issues': info, + 'low_issues': low, + 'medium_issues': medium, + 'high_issues': high, + 'is_erc20': contract.is_erc20(), + 'number_functions': self._number_functions(contract)} + if contract_d['is_erc20']: + pause, mint_limited, race_condition_mitigated = self._get_summary_erc20(contract) + contract_d['erc20_pause'] = pause + if mint_limited is not None: + contract_d['erc20_can_mint'] = True + contract_d['erc20_mint_limited'] = mint_limited + else: + contract_d['erc20_can_mint'] = False + contract_d['erc20_race_condition_mitigated'] = race_condition_mitigated + + results_contract.add_contract(contract, additional_fields=contract_d) + + results['contracts']['elements'] = results_contract.elements + + json = self.generate_output(txt, additional_fields=results) + + return json + diff --git a/slither/printers/summary/modifier_calls.py b/slither/printers/summary/modifier_calls.py index b76736937..4d3cb7182 100644 --- a/slither/printers/summary/modifier_calls.py +++ b/slither/printers/summary/modifier_calls.py @@ -6,6 +6,7 @@ from prettytable import PrettyTable from slither.core.declarations import Function from slither.printers.abstract_printer import AbstractPrinter + class Modifiers(AbstractPrinter): ARGUMENT = 'modifiers' @@ -20,6 +21,9 @@ class Modifiers(AbstractPrinter): _filename(string) """ + all_txt = '' + all_tables = [] + for contract in self.slither.contracts_derived: txt = "\nContract %s"%contract.name table = PrettyTable(["Function", @@ -35,3 +39,9 @@ class Modifiers(AbstractPrinter): table.add_row([function.name, [m.name for m in set(modifiers)]]) txt += "\n"+str(table) self.info(txt) + + res = self.generate_output(all_txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + + return res \ No newline at end of file diff --git a/slither/printers/summary/require_calls.py b/slither/printers/summary/require_calls.py index 34149b07c..25ae4af74 100644 --- a/slither/printers/summary/require_calls.py +++ b/slither/printers/summary/require_calls.py @@ -29,6 +29,8 @@ class RequireOrAssert(AbstractPrinter): _filename(string) """ + all_tables = [] + all_txt = '' for contract in self.slither.contracts_derived: txt = "\nContract %s"%contract.name table = PrettyTable(["Function", @@ -40,3 +42,11 @@ class RequireOrAssert(AbstractPrinter): table.add_row([function.name, self._convert([str(m.expression) for m in set(require)])]) txt += "\n"+str(table) self.info(txt) + all_tables.append((contract.name, table)) + all_txt += txt + + res = self.generate_output(all_txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + + return res diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index cd4a7299f..f75c8b062 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -3,10 +3,9 @@ """ from slither.printers.abstract_printer import AbstractPrinter -from slither.utils.colors import blue, green, magenta -class PrinterSlithIR(AbstractPrinter): +class PrinterSlithIR(AbstractPrinter): ARGUMENT = 'slithir' HELP = 'Print the slithIR representation of the functions' @@ -21,26 +20,32 @@ class PrinterSlithIR(AbstractPrinter): txt = "" for contract in self.contracts: - print('Contract {}'.format(contract.name)) + txt += 'Contract {}'.format(contract.name) for function in contract.functions: - print(f'\tFunction {function.canonical_name}') + txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}' for node in function.nodes: if node.expression: - print('\t\tExpression: {}'.format(node.expression)) - print('\t\tIRs:') + txt += '\t\tExpression: {}'.format(node.expression) + txt += '\t\tIRs:' for ir in node.irs: - print('\t\t\t{}'.format(ir)) + txt += '\t\t\t{}'.format(ir) elif node.irs: - print('\t\tIRs:') + txt += '\t\tIRs:' for ir in node.irs: - print('\t\t\t{}'.format(ir)) + txt += '\t\t\t{}'.format(ir) + for modifier_statement in function.modifiers_statements: + txt += f'\t\tModifier Call {modifier_statement.entry_point.expression}' + for modifier_statement in function.explicit_base_constructor_calls_statements: + txt += f'\t\tConstructor Call {modifier_statement.entry_point.expression}' for modifier in contract.modifiers: - print('\tModifier {}'.format(modifier.canonical_name)) + txt += '\tModifier {}'.format(modifier.canonical_name) for node in modifier.nodes: - print(node) + txt += str(node) if node.expression: - print('\t\tExpression: {}'.format(node.expression)) - print('\t\tIRs:') + txt += '\t\tExpression: {}'.format(node.expression) + txt += '\t\tIRs:' for ir in node.irs: - print('\t\t\t{}'.format(ir)) + txt += '\t\t\t{}'.format(ir) self.info(txt) + res = self.generate_output(txt) + return res diff --git a/slither/printers/summary/slithir_ssa.py b/slither/printers/summary/slithir_ssa.py index c97a291fa..26d962401 100644 --- a/slither/printers/summary/slithir_ssa.py +++ b/slither/printers/summary/slithir_ssa.py @@ -3,7 +3,7 @@ """ from slither.printers.abstract_printer import AbstractPrinter -from slither.utils.colors import blue, green, magenta + class PrinterSlithIRSSA(AbstractPrinter): @@ -21,24 +21,26 @@ class PrinterSlithIRSSA(AbstractPrinter): txt = "" for contract in self.contracts: - print('Contract {}'.format(contract.name)) + txt += 'Contract {}'.format(contract.name) for function in contract.functions: - print('\tFunction {}'.format(function.canonical_name)) + txt += '\tFunction {}'.format(function.canonical_name) for node in function.nodes: if node.expression: - print('\t\tExpression: {}'.format(node.expression)) + txt += '\t\tExpression: {}'.format(node.expression) if node.irs_ssa: - print('\t\tIRs:') + txt += '\t\tIRs:' for ir in node.irs_ssa: - print('\t\t\t{}'.format(ir)) + txt += '\t\t\t{}'.format(ir) for modifier in contract.modifiers: - print('\tModifier {}'.format(modifier.canonical_name)) + txt += '\tModifier {}'.format(modifier.canonical_name) for node in modifier.nodes: - print(node) + txt += str(node) if node.expression: - print('\t\tExpression: {}'.format(node.expression)) + txt += '\t\tExpression: {}'.format(node.expression) if node.irs_ssa: - print('\t\tIRs:') + txt += '\t\tIRs:' for ir in node.irs_ssa: - print('\t\t\t{}'.format(ir)) + txt += '\t\t\t{}'.format(ir) self.info(txt) + res = self.generate_output(txt) + return res diff --git a/slither/printers/summary/variable_order.py b/slither/printers/summary/variable_order.py index 48d96356b..1e78cf1df 100644 --- a/slither/printers/summary/variable_order.py +++ b/slither/printers/summary/variable_order.py @@ -5,6 +5,7 @@ from prettytable import PrettyTable from slither.printers.abstract_printer import AbstractPrinter + class VariableOrder(AbstractPrinter): ARGUMENT = 'variable-order' @@ -20,12 +21,22 @@ class VariableOrder(AbstractPrinter): """ txt = '' + + all_tables = [] + for contract in self.slither.contracts_derived: txt += '\n{}:\n'.format(contract.name) table = PrettyTable(['Name', 'Type']) for variable in contract.state_variables_ordered: if not variable.is_constant: table.add_row([variable.canonical_name, str(variable.type)]) + + all_tables.append((contract.name, table)) txt += str(table) + '\n' self.info(txt) + + res = self.generate_output(txt) + for name, table in all_tables: + res.add_pretty_table(table, name) + return res \ No newline at end of file diff --git a/slither/slither.py b/slither/slither.py index d0c0ccf02..55698de74 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -22,18 +22,19 @@ logger_printer = logging.getLogger("Printers") class Slither(SlitherSolc): - def __init__(self, contract, **kwargs): + def __init__(self, target, **kwargs): ''' Args: - contract (str| list(json)) + target (str | list(json) | CryticCompile) Keyword Args: solc (str): solc binary location (default 'solc') disable_solc_warnings (bool): True to disable solc warnings (default false) - solc_argeuments (str): solc arguments (default '') + solc_arguments (str): solc arguments (default '') ast_format (str): ast format (default '--ast-compact-json') filter_paths (list(str)): list of path to filter (default []) triage_mode (bool): if true, switch to triage mode (default false) exclude_dependencies (bool): if true, exclude results that are only related to dependencies + generate_patches (bool): if true, patches are generated (json output only) truffle_ignore (bool): ignore truffle.js presence (default false) truffle_build_directory (str): build truffle directory (default 'build/contracts') @@ -46,14 +47,17 @@ class Slither(SlitherSolc): ''' # list of files provided (see --splitted option) - if isinstance(contract, list): - self._init_from_list(contract) - elif contract.endswith('.json'): - self._init_from_raw_json(contract) + if isinstance(target, list): + self._init_from_list(target) + elif isinstance(target, str) and target.endswith('.json'): + self._init_from_raw_json(target) else: super(Slither, self).__init__('') try: - crytic_compile = CryticCompile(contract, **kwargs) + if isinstance(target, CryticCompile): + crytic_compile = target + else: + crytic_compile = CryticCompile(target, **kwargs) self._crytic_compile = crytic_compile except InvalidCompilation as e: raise SlitherError('Invalid compilation: \n'+str(e)) @@ -61,6 +65,11 @@ class Slither(SlitherSolc): self._parse_contracts_from_loaded_json(ast, path) self._add_source_code(path) + if kwargs.get('generate_patches', False): + self.generate_patches = True + + self._markdown_root = kwargs.get('markdown_root', "") + self._detectors = [] self._printers = [] @@ -119,6 +128,10 @@ class Slither(SlitherSolc): def detectors_informational(self): return [d for d in self.detectors if d.IMPACT == DetectorClassification.INFORMATIONAL] + @property + def detectors_optimization(self): + return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION] + def register_detector(self, detector_class): """ :param detector_class: Class inheriting from `AbstractDetector`. @@ -152,7 +165,7 @@ class Slither(SlitherSolc): :return: List of registered printers outputs. """ - return [p.output(self.filename) for p in self._printers] + return [p.output(self.filename).data for p in self._printers] def _check_common_things(self, thing_name, cls, base_cls, instances_list): diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 63284db77..bfd04ca6a 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -21,7 +21,7 @@ from slither.slithir.operations import (Assignment, Balance, Binary, NewElementaryType, NewStructure, OperationWithLValue, Push, Return, Send, SolidityCall, Transfer, - TypeConversion, Unary, Unpack) + TypeConversion, Unary, Unpack, Nop) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -45,10 +45,14 @@ def convert_expression(expression, node): if isinstance(expression, Literal) and node.type in [NodeType.IF, NodeType.IFLOOP]: cst = Constant(expression.value, expression.type) - result = [Condition(cst)] + cond = Condition(cst) + cond.set_expression(expression) + result = [cond] return result if isinstance(expression, Identifier) and node.type in [NodeType.IF, NodeType.IFLOOP]: - result = [Condition(expression.value)] + cond = Condition(expression.value) + cond.set_expression(expression) + result = [cond] return result @@ -60,11 +64,15 @@ def convert_expression(expression, node): if result: if node.type in [NodeType.IF, NodeType.IFLOOP]: assert isinstance(result[-1], (OperationWithLValue)) - result.append(Condition(result[-1].lvalue)) + cond = Condition(result[-1].lvalue) + cond.set_expression(expression) + result.append(cond) elif node.type == NodeType.RETURN: # May return None if isinstance(result[-1], (OperationWithLValue)): - result.append(Return(result[-1].lvalue)) + r = Return(result[-1].lvalue) + r.set_expression(expression) + result.append(r) return result @@ -326,6 +334,7 @@ def _convert_type_contract(ir, slither): assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType('bytes')) + assignment.set_expression(ir.expression) assignment.lvalue.set_type(ElementaryType('bytes')) return assignment if ir.variable_right == 'runtimeCode': @@ -338,12 +347,14 @@ def _convert_type_contract(ir, slither): assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType('bytes')) + assignment.set_expression(ir.expression) assignment.lvalue.set_type(ElementaryType('bytes')) return assignment if ir.variable_right == 'name': assignment = Assignment(ir.lvalue, Constant(contract.name), ElementaryType('string')) + assignment.set_expression(ir.expression) assignment.lvalue.set_type(ElementaryType('string')) return assignment @@ -375,6 +386,10 @@ def propagate_types(ir, node): if t is None: return + if isinstance(t, ElementaryType) and t.name == 'address': + if can_be_solidity_func(ir): + return convert_to_solidity_func(ir) + # convert library if t in using_for or '*' in using_for: new_ir = convert_to_library(ir, node, using_for) @@ -392,7 +407,8 @@ def propagate_types(ir, node): if isinstance(t, ElementaryType) and t.name == 'address': if ir.destination.name == 'this': return convert_type_of_high_and_internal_level_call(ir, node.function.contract) - return convert_to_low_level(ir) + if can_be_low_level(ir): + return convert_to_low_level(ir) # Convert push operations # May need to insert a new operation @@ -441,14 +457,18 @@ def propagate_types(ir, node): # TODO we should convert the reference to a temporary if the member is a length or a balance if ir.variable_right == 'length' and not isinstance(ir.variable_left, Contract) and isinstance(ir.variable_left.type, (ElementaryType, ArrayType)): length = Length(ir.variable_left, ir.lvalue) + length.set_expression(ir.expression) length.lvalue.points_to = ir.variable_left return length if ir.variable_right == 'balance'and not isinstance(ir.variable_left, Contract) and isinstance(ir.variable_left.type, ElementaryType): - return Balance(ir.variable_left, ir.lvalue) + b = Balance(ir.variable_left, ir.lvalue) + b.set_expression(ir.expression) + return b if ir.variable_right == 'selector' and isinstance(ir.variable_left.type, Function): assignment = Assignment(ir.lvalue, Constant(str(get_function_id(ir.variable_left.type.full_name))), ElementaryType('bytes4')) + assignment.set_expression(ir.expression) assignment.lvalue.set_type(ElementaryType('bytes4')) return assignment if isinstance(ir.variable_left, TemporaryVariable) and isinstance(ir.variable_left.type, TypeInformation): @@ -529,6 +549,7 @@ def extract_tmp_call(ins, contract): if isinstance(ins.called, Variable) and isinstance(ins.called.type, FunctionType): call = InternalDynamicCall(ins.lvalue, ins.called, ins.called.type) + call.set_expression(ins.expression) call.call_id = ins.call_id return call if isinstance(ins.ori, Member): @@ -536,23 +557,28 @@ def extract_tmp_call(ins, contract): if ins.ori.variable_left in contract.inheritance + [contract]: if str(ins.ori.variable_right) in [f.name for f in contract.functions]: internalcall = InternalCall((ins.ori.variable_right, ins.ori.variable_left.name), ins.nbr_arguments, ins.lvalue, ins.type_call) + internalcall.set_expression(ins.expression) internalcall.call_id = ins.call_id return internalcall if str(ins.ori.variable_right) in [f.name for f in contract.events]: - eventcall = EventCall(ins.ori.variable_right) - eventcall.call_id = ins.call_id - return eventcall + eventcall = EventCall(ins.ori.variable_right) + eventcall.set_expression(ins.expression) + eventcall.call_id = ins.call_id + return eventcall if isinstance(ins.ori.variable_left, Contract): st = ins.ori.variable_left.get_structure_from_name(ins.ori.variable_right) if st: op = NewStructure(st, ins.lvalue) + op.set_expression(ins.expression) op.call_id = ins.call_id return op libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) + libcall.set_expression(ins.expression) libcall.call_id = ins.call_id return libcall msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) msgcall.call_id = ins.call_id + msgcall.set_expression(ins.expression) return msgcall if isinstance(ins.ori, TmpCall): @@ -562,29 +588,52 @@ def extract_tmp_call(ins, contract): if str(ins.called) == 'block.blockhash': ins.called = SolidityFunction('blockhash(uint256)') elif str(ins.called) == 'this.balance': - return SolidityCall(SolidityFunction('this.balance()'), ins.nbr_arguments, ins.lvalue, ins.type_call) + s = SolidityCall(SolidityFunction('this.balance()'), ins.nbr_arguments, ins.lvalue, ins.type_call) + s.set_expression(ins.expression) + return s if isinstance(ins.called, SolidityFunction): - return SolidityCall(ins.called, ins.nbr_arguments, ins.lvalue, ins.type_call) + s = SolidityCall(ins.called, ins.nbr_arguments, ins.lvalue, ins.type_call) + s.set_expression(ins.expression) + return s if isinstance(ins.ori, TmpNewElementaryType): - return NewElementaryType(ins.ori.type, ins.lvalue) + n = NewElementaryType(ins.ori.type, ins.lvalue) + n.set_expression(ins.expression) + return n if isinstance(ins.ori, TmpNewContract): op = NewContract(Constant(ins.ori.contract_name), ins.lvalue) + op.set_expression(ins.expression) op.call_id = ins.call_id return op if isinstance(ins.ori, TmpNewArray): - return NewArray(ins.ori.depth, ins.ori.array_type, ins.lvalue) + n = NewArray(ins.ori.depth, ins.ori.array_type, ins.lvalue) + n.set_expression(ins.expression) + return n if isinstance(ins.called, Structure): op = NewStructure(ins.called, ins.lvalue) + op.set_expression(ins.expression) op.call_id = ins.call_id + op.set_expression(ins.expression) return op if isinstance(ins.called, Event): - return EventCall(ins.called.name) + e = EventCall(ins.called.name) + e.set_expression(ins.expression) + return e + + if isinstance(ins.called, Contract): + # Called a base constructor, where there is no constructor + if ins.called.constructor is None: + return Nop() + internalcall = InternalCall(ins.called.constructor, ins.nbr_arguments, ins.lvalue, + ins.type_call) + internalcall.call_id = ins.call_id + internalcall.set_expression(ins.expression) + return internalcall raise Exception('Not extracted {} {}'.format(type(ins.called), ins)) @@ -596,37 +645,35 @@ def extract_tmp_call(ins, contract): ################################################################################### ################################################################################### +def can_be_low_level(ir): + return ir.function_name in ['transfer', + 'send', + 'call', + 'delegatecall', + 'callcode', + 'staticcall'] + def convert_to_low_level(ir): """ Convert to a transfer/send/or low level call The funciton assume to receive a correct IR The checks must be done by the caller - Additionally convert abi... to solidityfunction + Must be called after can_be_low_level """ if ir.function_name == 'transfer': assert len(ir.arguments) == 1 + prev_ir = ir ir = Transfer(ir.destination, ir.arguments[0]) + ir.set_expression(prev_ir.expression) return ir elif ir.function_name == 'send': assert len(ir.arguments) == 1 + prev_ir = ir ir = Send(ir.destination, ir.arguments[0], ir.lvalue) + ir.set_expression(prev_ir.expression) ir.lvalue.set_type(ElementaryType('bool')) return ir - elif ir.destination.name == 'abi' and ir.function_name in ['encode', - 'encodePacked', - 'encodeWithSelector', - 'encodeWithSignature', - 'decode']: - - call = SolidityFunction('abi.{}()'.format(ir.function_name)) - new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) - new_ir.arguments = ir.arguments - if isinstance(call.return_type, list) and len(call.return_type) == 1: - new_ir.lvalue.set_type(call.return_type[0]) - else: - new_ir.lvalue.set_type(call.return_type) - return new_ir elif ir.function_name in ['call', 'delegatecall', 'callcode', @@ -640,9 +687,34 @@ def convert_to_low_level(ir): new_ir.call_value = ir.call_value new_ir.arguments = ir.arguments new_ir.lvalue.set_type(ElementaryType('bool')) + new_ir.set_expression(ir.expression) return new_ir raise SlithIRError('Incorrect conversion to low level {}'.format(ir)) + +def can_be_solidity_func(ir): + return ir.destination.name == 'abi' and ir.function_name in ['encode', + 'encodePacked', + 'encodeWithSelector', + 'encodeWithSignature', + 'decode'] + +def convert_to_solidity_func(ir): + """ + Must be called after can_be_solidity_func + :param ir: + :return: + """ + call = SolidityFunction('abi.{}()'.format(ir.function_name)) + new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) + new_ir.arguments = ir.arguments + new_ir.set_expression(ir.expression) + if isinstance(call.return_type, list) and len(call.return_type) == 1: + new_ir.lvalue.set_type(call.return_type[0]) + else: + new_ir.lvalue.set_type(call.return_type) + return new_ir + def convert_to_push(ir, node): """ Convert a call to a PUSH operaiton @@ -662,9 +734,12 @@ def convert_to_push(ir, node): val = TemporaryVariable(node) operation = InitArray(ir.arguments[0], val) + operation.set_expression(ir.expression) ret.append(operation) + prev_ir = ir ir = Push(ir.destination, val) + ir.set_expression(prev_ir.expression) length = Literal(len(operation.init_values), 'uint256') t = operation.init_values[0].type @@ -674,18 +749,22 @@ def convert_to_push(ir, node): if lvalue: length = Length(ir.array, lvalue) + length.set_expression(ir.expression) length.lvalue.points_to = ir.lvalue ret.append(length) return ret + prev_ir = ir ir = Push(ir.destination, ir.arguments[0]) + ir.set_expression(prev_ir.expression) if lvalue: ret = [] ret.append(ir) length = Length(ir.array, lvalue) + length.set_expression(ir.expression) length.lvalue.points_to = ir.lvalue ret.append(length) return ret @@ -701,6 +780,7 @@ def look_for_library(contract, ir, node, using_for, t): ir.nbr_arguments, ir.lvalue, ir.type_call) + lib_call.set_expression(ir.expression) lib_call.call_gas = ir.call_gas lib_call.arguments = [ir.destination] + ir.arguments new_ir = convert_type_library_call(lib_call, lib_contract) @@ -806,12 +886,11 @@ def convert_type_of_high_and_internal_level_call(ir, contract): func = function break # lowlelvel lookup needs to be done at last step - if not func and ir.function_name in ['call', - 'delegatecall', - 'callcode', - 'transfer', - 'send']: - return convert_to_low_level(ir) + if not func: + if can_be_low_level(ir): + return convert_to_low_level(ir) + if can_be_solidity_func(ir): + return convert_to_solidity_func(ir) if not func: logger.error('Function not found {}'.format(sig)) ir.function = func diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index b20df950d..cccc812e5 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -30,3 +30,4 @@ from .length import Length from .balance import Balance from .phi import Phi from .phi_callback import PhiCallback +from .nop import Nop diff --git a/slither/slithir/operations/call.py b/slither/slithir/operations/call.py index 25d929c92..60d150f7f 100644 --- a/slither/slithir/operations/call.py +++ b/slither/slithir/operations/call.py @@ -15,3 +15,16 @@ class Call(Operation): def arguments(self, v): self._arguments = v + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return False + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return False \ No newline at end of file diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index 87a67a80f..38314c1aa 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -2,6 +2,7 @@ from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable from slither.core.declarations.solidity_variables import SolidityVariable +from slither.core.declarations.function import Function from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.variables.constant import Constant @@ -86,6 +87,55 @@ class HighLevelCall(Call, OperationWithLValue): def type_call(self): return self._type_call + ################################################################################### + ################################################################################### + # region Analyses + ################################################################################### + ################################################################################### + + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + :param callstack: check for recursion + :return: bool + ''' + # If solidity >0.5, STATICCALL is used + if self.slither.solc_version and self.slither.solc_version.startswith('0.5.'): + if isinstance(self.function, Function) and (self.function.view or self.function.pure): + return False + if isinstance(self.function, Variable): + return False + # If there is a call to itself + # We can check that the function called is + # reentrancy-safe + if self.destination == SolidityVariable('this'): + if isinstance(self.function, Variable): + return False + # In case of recursion, return False + callstack = [] if callstack is None else callstack + if self.function in callstack: + return False + callstack = callstack + [self.function] + if self.function.can_reenter(callstack): + return True + return True + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return self._call_value is not None + + # endregion + ################################################################################### + ################################################################################### + # region Built in + ################################################################################### + ################################################################################### + def __str__(self): value = '' gas = '' diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index 76abfbe8a..ae3858834 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -9,6 +9,18 @@ class LibraryCall(HighLevelCall): def _check_destination(self, destination): assert isinstance(destination, (Contract)) + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + # In case of recursion, return False + callstack = [] if callstack is None else callstack + if self.function in callstack: + return False + callstack = callstack + [self.function] + return self.function.can_reenter(callstack) + def __str__(self): gas = '' if self.call_gas: diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index a55482ad7..2f8f03b12 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -54,6 +54,20 @@ class LowLevelCall(Call, OperationWithLValue): # remove None return self._unroll([x for x in all_read if x]) + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return True + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return self._call_value is not None + @property def destination(self): return self._destination diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index 7326a659e..52060ba2b 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -31,16 +31,52 @@ class NewContract(Call, OperationWithLValue): def call_id(self, c): self._callid = c - @property def contract_name(self): return self._contract_name - @property def read(self): return self._unroll(self.arguments) + @property + def contract_created(self): + contract_name = self.contract_name + contract_instance = self.slither.get_contract_from_name(contract_name) + return contract_instance + + ################################################################################### + ################################################################################### + # region Analyses + ################################################################################### + ################################################################################### + + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + :param callstack: check for recursion + :return: bool + ''' + callstack = [] if callstack is None else callstack + constructor = self.contract_created.constructor + if constructor is None: + return False + if constructor in callstack: + return False + callstack = callstack + [constructor] + return constructor.can_reenter(callstack) + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return self._call_value is not None + + # endregion + def __str__(self): value = '' if self.call_value: diff --git a/slither/slithir/operations/nop.py b/slither/slithir/operations/nop.py new file mode 100644 index 000000000..fb26813c1 --- /dev/null +++ b/slither/slithir/operations/nop.py @@ -0,0 +1,14 @@ +from .operation import Operation + +class Nop(Operation): + + @property + def read(self): + return [] + + @property + def used(self): + return [] + + def __str__(self): + return "NOP" \ No newline at end of file diff --git a/slither/slithir/operations/operation.py b/slither/slithir/operations/operation.py index b127bd715..636921fe3 100644 --- a/slither/slithir/operations/operation.py +++ b/slither/slithir/operations/operation.py @@ -1,5 +1,6 @@ import abc from slither.core.context.context import Context +from slither.core.children.child_expression import ChildExpression from slither.core.children.child_node import ChildNode from slither.utils.utils import unroll @@ -21,7 +22,7 @@ class AbstractOperation(abc.ABC): """ pass -class Operation(Context, ChildNode, AbstractOperation): +class Operation(Context, ChildExpression, ChildNode, AbstractOperation): @property def used(self): diff --git a/slither/slithir/operations/phi.py b/slither/slithir/operations/phi.py index a11dfa139..e0d007b0f 100644 --- a/slither/slithir/operations/phi.py +++ b/slither/slithir/operations/phi.py @@ -1,10 +1,6 @@ -import logging from slither.slithir.operations.lvalue import OperationWithLValue -from slither.core.variables.variable import Variable -from slither.slithir.variables import TupleVariable -from slither.core.declarations.function import Function -from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.slithir.utils.utils import is_valid_lvalue class Phi(OperationWithLValue): diff --git a/slither/slithir/operations/send.py b/slither/slithir/operations/send.py index 201de989a..690459cf2 100644 --- a/slither/slithir/operations/send.py +++ b/slither/slithir/operations/send.py @@ -17,6 +17,9 @@ class Send(Call, OperationWithLValue): self._call_value = value + def can_send_eth(self): + return True + @property def call_value(self): return self._call_value diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py index b334d02ce..b46d96b73 100644 --- a/slither/slithir/operations/transfer.py +++ b/slither/slithir/operations/transfer.py @@ -11,6 +11,8 @@ class Transfer(Call): self._call_value = value + def can_send_eth(self): + return True @property def call_value(self): diff --git a/slither/slithir/tmp_operations/tmp_call.py b/slither/slithir/tmp_operations/tmp_call.py index 0d7fda1e9..3c8faec3c 100644 --- a/slither/slithir/tmp_operations/tmp_call.py +++ b/slither/slithir/tmp_operations/tmp_call.py @@ -1,14 +1,13 @@ -from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.declarations import Event, Contract, SolidityVariableComposed, SolidityFunction, Structure from slither.core.variables.variable import Variable -from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction -from slither.core.declarations.structure import Structure -from slither.core.declarations.event import Event +from slither.slithir.operations.lvalue import OperationWithLValue class TmpCall(OperationWithLValue): def __init__(self, called, nbr_arguments, result, type_call): - assert isinstance(called, (Variable, + assert isinstance(called, (Contract, + Variable, SolidityVariableComposed, SolidityFunction, Structure, diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index fde9f5eba..4049e5cab 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -181,6 +181,8 @@ def generate_ssa_irs(node, local_variables_instances, all_local_variables_instan tuple_variables_instances, all_local_variables_instances) + new_ir.set_expression(ir.expression) + update_lvalue(new_ir, node, local_variables_instances, diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 64c3e4fdf..10802ac09 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -1,14 +1,18 @@ from .variable import SlithIRVariable from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint - +from slither.utils.arithmetic import convert_subdenomination class Constant(SlithIRVariable): - def __init__(self, val, type=None): + def __init__(self, val, type=None, subdenomination=None): super(Constant, self).__init__() assert isinstance(val, str) self._original_value = val + self._subdenomination = subdenomination + + if subdenomination: + val = str(convert_subdenomination(val, subdenomination)) if type: assert isinstance(type, ElementaryType) diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 1775084f6..08be502e2 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -37,10 +37,12 @@ class NodeSolc(Node): if self.type == NodeType.VARIABLE: # Update the expression to be an assignement to the variable #print(self.variable_declaration) - self._expression = AssignmentOperation(Identifier(self.variable_declaration), - self.expression, - AssignmentOperationType.ASSIGN, - self.variable_declaration.type) + _expression = AssignmentOperation(Identifier(self.variable_declaration), + self.expression, + AssignmentOperationType.ASSIGN, + self.variable_declaration.type) + _expression.set_offset(self.expression.source_mapping, self.slither) + self._expression = _expression expression = self.expression pp = ReadVar(expression) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index d3a01c51f..b667d51f1 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -1,7 +1,10 @@ import logging from slither.core.declarations.contract import Contract +from slither.core.declarations.function import Function, FunctionType from slither.core.declarations.enum import Enum +from slither.core.cfg.node import Node, NodeType +from slither.core.expressions import AssignmentOperation, Identifier, AssignmentOperationType from slither.slithir.variables import StateIRVariable from slither.solc_parsing.declarations.event import EventSolc from slither.solc_parsing.declarations.function import FunctionSolc @@ -17,7 +20,7 @@ class ContractSolc04(Contract): def __init__(self, slitherSolc, data): - assert slitherSolc.solc_version.startswith('0.4') + #assert slitherSolc.solc_version.startswith('0.4') super(ContractSolc04, self).__init__() self.set_slither(slitherSolc) @@ -287,7 +290,7 @@ class ContractSolc04(Contract): elements_no_params = self._modifiers_no_params getter = lambda f: f.modifiers - getter_available = lambda f: f.available_modifiers_as_dict().items() + getter_available = lambda f: f.modifiers_declared Cls = ModifierSolc self._modifiers = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) @@ -299,7 +302,7 @@ class ContractSolc04(Contract): elements_no_params = self._functions_no_params getter = lambda f: f.functions - getter_available = lambda f: f.available_functions_as_dict().items() + getter_available = lambda f: f.functions_declared Cls = FunctionSolc self._functions = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) @@ -319,7 +322,6 @@ class ContractSolc04(Contract): :return: """ all_elements = {} - accessible_elements = {} for father in self.inheritance: for element in getter(father): @@ -352,6 +354,7 @@ class ContractSolc04(Contract): for element in all_elements.values(): if accessible_elements[element.full_name] != all_elements[element.canonical_name]: element.is_shadowed = True + accessible_elements[element.full_name].shadows = True return all_elements @@ -368,6 +371,79 @@ class ContractSolc04(Contract): pass return + + def _create_node(self, func, counter, variable): + # Function uses to create node for state variable declaration statements + node = Node(NodeType.OTHER_ENTRYPOINT, counter) + node.set_offset(variable.source_mapping, self.slither) + node.set_function(func) + func.add_node(node) + expression = AssignmentOperation(Identifier(variable), + variable.expression, + AssignmentOperationType.ASSIGN, + variable.type) + + expression.set_offset(variable.source_mapping, self.slither) + node.add_expression(expression) + return node + + def add_constructor_variables(self): + if self.state_variables: + for (idx, variable_candidate) in enumerate(self.state_variables): + if variable_candidate.expression and not variable_candidate.is_constant: + + constructor_variable = Function() + constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_VARIABLES) + constructor_variable.set_contract(self) + constructor_variable.set_contract_declarer(self) + constructor_variable.set_visibility('internal') + # For now, source mapping of the constructor variable is the whole contract + # Could be improved with a targeted source mapping + constructor_variable.set_offset(self.source_mapping, self.slither) + self._functions[constructor_variable.canonical_name] = constructor_variable + + prev_node = self._create_node(constructor_variable, 0, variable_candidate) + variable_candidate.node_initialization = prev_node + counter = 1 + for v in self.state_variables[idx+1:]: + if v.expression and not v.is_constant: + next_node = self._create_node(constructor_variable, counter, v) + v.node_initialization = next_node + prev_node.add_son(next_node) + next_node.add_father(prev_node) + counter += 1 + break + + for (idx, variable_candidate) in enumerate(self.state_variables): + if variable_candidate.expression and variable_candidate.is_constant: + + constructor_variable = Function() + constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES) + constructor_variable.set_contract(self) + constructor_variable.set_contract_declarer(self) + constructor_variable.set_visibility('internal') + # For now, source mapping of the constructor variable is the whole contract + # Could be improved with a targeted source mapping + constructor_variable.set_offset(self.source_mapping, self.slither) + self._functions[constructor_variable.canonical_name] = constructor_variable + + prev_node = self._create_node(constructor_variable, 0, variable_candidate) + variable_candidate.node_initialization = prev_node + counter = 1 + for v in self.state_variables[idx+1:]: + if v.expression and v.is_constant: + next_node = self._create_node(constructor_variable, counter, v) + v.node_initialization = next_node + prev_node.add_son(next_node) + next_node.add_father(prev_node) + counter += 1 + + break + + + + + def analyze_state_variables(self): for var in self.variables: var.analyze(self) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 721f49941..e54f7cce6 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -2,18 +2,12 @@ """ import logging -from slither.core.cfg.node import NodeType, link_nodes +from slither.core.cfg.node import NodeType, link_nodes, recheable from slither.core.declarations.contract import Contract -from slither.core.declarations.function import Function -from slither.core.dominators.utils import (compute_dominance_frontier, - compute_dominators) +from slither.core.declarations.function import Function, ModifierStatements, FunctionType + from slither.core.expressions import AssignmentOperation -from slither.core.variables.state_variable import StateVariable -from slither.slithir.operations import (InternalCall, OperationWithLValue, Phi, - PhiCallback) -from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa -from slither.slithir.variables import (Constant, ReferenceVariable, - StateIRVariable) + from slither.solc_parsing.cfg.node import NodeSolc from slither.solc_parsing.expressions.expression_parsing import \ parse_expression @@ -23,7 +17,6 @@ from slither.solc_parsing.variables.local_variable_init_from_tuple import \ from slither.solc_parsing.variables.variable_declaration import \ MultipleVariablesDeclaration from slither.utils.expression_manipulations import SplitTernaryExpression -from slither.utils.utils import unroll from slither.visitors.expression.export_values import ExportValues from slither.visitors.expression.has_conditional import HasConditional from slither.solc_parsing.exceptions import ParsingError @@ -105,9 +98,10 @@ class FunctionSolc(Function): # This is done to prevent collision during SSA translation # Use of while in case of collision # In the worst case, the name will be really long - while local_var.name in self._variables: - local_var.name += "_scope_{}".format(self._counter_scope_local_variables) - self._counter_scope_local_variables += 1 + if local_var.name: + while local_var.name in self._variables: + local_var.name += "_scope_{}".format(self._counter_scope_local_variables) + self._counter_scope_local_variables += 1 if not local_var.reference_id is None: self._variables_renamed[local_var.reference_id] = local_var self._variables[local_var.name] = local_var @@ -139,14 +133,20 @@ class FunctionSolc(Function): if 'constant' in attributes: self._view = attributes['constant'] - self._is_constructor = False + if self._name == '': + self._function_type = FunctionType.FALLBACK + else: + self._function_type = FunctionType.NORMAL + + if self._name == self.contract_declarer.name: + self._function_type = FunctionType.CONSTRUCTOR - if 'isConstructor' in attributes: - self._is_constructor = attributes['isConstructor'] + if 'isConstructor' in attributes and attributes['isConstructor']: + self._function_type = FunctionType.CONSTRUCTOR if 'kind' in attributes: if attributes['kind'] == 'constructor': - self._is_constructor = True + self._function_type = FunctionType.CONSTRUCTOR if 'visibility' in attributes: self._visibility = attributes['visibility'] @@ -220,7 +220,13 @@ class FunctionSolc(Function): for node in self.nodes: node.analyze_expressions(self) - self._filter_ternary() + if self._filter_ternary(): + for modifier_statement in self.modifiers_statements: + modifier_statement.nodes = recheable(modifier_statement.entry_point) + + for modifier_statement in self.explicit_base_constructor_calls_statements: + modifier_statement.nodes = recheable(modifier_statement.entry_point) + self._remove_alone_endif() @@ -493,7 +499,8 @@ class FunctionSolc(Function): variables = statement['declarations'] count = len(variables) - if statement['initialValue']['nodeType'] == 'TupleExpression': + if statement['initialValue']['nodeType'] == 'TupleExpression' and \ + len(statement['initialValue']['components']) == count: inits = statement['initialValue']['components'] i = 0 new_node = node @@ -818,7 +825,12 @@ class FunctionSolc(Function): end_node = self._find_end_loop(node, [], 0) if not end_node: - raise ParsingError('Break in no-loop context {}'.format(node)) + # If there is not end condition on the loop + # The exploration will reach a STARTLOOP before reaching the endloop + # We start with -1 as counter to catch this corner case + end_node = self._find_end_loop(node, [], -1) + if not end_node: + raise ParsingError('Break in no-loop context {}'.format(node.function)) for son in node.sons: son.remove_father(node) @@ -897,9 +909,21 @@ class FunctionSolc(Function): self._expression_modifiers.append(m) for m in ExportValues(m).result(): if isinstance(m, Function): - self._modifiers.append(m) + entry_point = self._new_node(NodeType.OTHER_ENTRYPOINT, modifier['src']) + node = self._new_node(NodeType.EXPRESSION, modifier['src']) + node.add_unparsed_expression(modifier) + link_nodes(entry_point, node) + self._modifiers.append(ModifierStatements(modifier=m, + entry_point=entry_point, + nodes=[entry_point, node])) elif isinstance(m, Contract): - self._explicit_base_constructor_calls.append(m) + entry_point = self._new_node(NodeType.OTHER_ENTRYPOINT, modifier['src']) + node = self._new_node(NodeType.EXPRESSION, modifier['src']) + node.add_unparsed_expression(modifier) + link_nodes(entry_point, node) + self._explicit_base_constructor_calls.append(ModifierStatements(modifier=m, + entry_point=entry_point, + nodes=[entry_point, node])) # endregion ################################################################################### @@ -959,9 +983,10 @@ class FunctionSolc(Function): def _filter_ternary(self): ternary_found = True + updated = False while ternary_found: ternary_found = False - for node in self.nodes: + for node in self._nodes: has_cond = HasConditional(node.expression) if has_cond.result(): st = SplitTernaryExpression(node.expression) @@ -970,11 +995,13 @@ class FunctionSolc(Function): raise ParsingError(f'Incorrect ternary conversion {node.expression} {node.source_mapping_str}') true_expr = st.true_expression false_expr = st.false_expression - self.split_ternary_node(node, condition, true_expr, false_expr) + self._split_ternary_node(node, condition, true_expr, false_expr) ternary_found = True + updated = True break + return updated - def split_ternary_node(self, node, condition, true_expr, false_expr): + def _split_ternary_node(self, node, condition, true_expr, false_expr): condition_node = self._new_node(NodeType.IF, node.source_mapping) condition_node.add_expression(condition) condition_node.analyze_expressions(self) @@ -1026,113 +1053,5 @@ class FunctionSolc(Function): # endregion - ################################################################################### - ################################################################################### - # region SlithIr and SSA - ################################################################################### - ################################################################################### - def get_last_ssa_state_variables_instances(self): - if not self.is_implemented: - return dict() - - # node, values - to_explore = [(self._entry_point, dict())] - # node -> values - explored = dict() - # name -> instances - ret = dict() - - while to_explore: - node, values = to_explore[0] - to_explore = to_explore[1::] - - if node.type != NodeType.ENTRYPOINT: - for ir_ssa in node.irs_ssa: - if isinstance(ir_ssa, OperationWithLValue): - lvalue = ir_ssa.lvalue - if isinstance(lvalue, ReferenceVariable): - lvalue = lvalue.points_to_origin - if isinstance(lvalue, StateVariable): - values[lvalue.canonical_name] = {lvalue} - - # Check for fixpoint - if node in explored: - if values == explored[node]: - continue - for k, instances in values.items(): - if not k in explored[node]: - explored[node][k] = set() - explored[node][k] |= instances - values = explored[node] - else: - explored[node] = values - - # Return condition - if not node.sons and node.type != NodeType.THROW: - for name, instances in values.items(): - if name not in ret: - ret[name] = set() - ret[name] |= instances - - for son in node.sons: - to_explore.append((son, dict(values))) - - return ret - - @staticmethod - def _unchange_phi(ir): - if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1: - return False - if not ir.rvalues: - return True - return ir.rvalues[0] == ir.lvalue - - def fix_phi(self, last_state_variables_instances, initial_state_variables_instances): - for node in self.nodes: - for ir in node.irs_ssa: - if node == self.entry_point: - if isinstance(ir.lvalue, StateIRVariable): - additional = [initial_state_variables_instances[ir.lvalue.canonical_name]] - additional += last_state_variables_instances[ir.lvalue.canonical_name] - ir.rvalues = list(set(additional + ir.rvalues)) - # function parameter - else: - # find index of the parameter - idx = self.parameters.index(ir.lvalue.non_ssa_version) - # find non ssa version of that index - additional = [n.ir.arguments[idx] for n in self.reachable_from_nodes] - additional = unroll(additional) - additional = [a for a in additional if not isinstance(a, Constant)] - ir.rvalues = list(set(additional + ir.rvalues)) - if isinstance(ir, PhiCallback): - callee_ir = ir.callee_ir - if isinstance(callee_ir, InternalCall): - last_ssa = callee_ir.function.get_last_ssa_state_variables_instances() - if ir.lvalue.canonical_name in last_ssa: - ir.rvalues = list(last_ssa[ir.lvalue.canonical_name]) - else: - ir.rvalues = [ir.lvalue] - else: - additional = last_state_variables_instances[ir.lvalue.canonical_name] - ir.rvalues = list(set(additional + ir.rvalues)) - - node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)] - - def generate_slithir_and_analyze(self): - for node in self.nodes: - node.slithir_generation() - self._analyze_read_write() - self._analyze_calls() - - def generate_slithir_ssa(self, all_ssa_state_variables_instances): - compute_dominators(self.nodes) - compute_dominance_frontier(self.nodes) - transform_slithir_vars_to_ssa(self) - add_ssa_ir(self, all_ssa_state_variables_instances) - - def update_read_write_using_ssa(self): - for node in self.nodes: - node.update_read_write_using_ssa() - self._analyze_read_write() diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 5055c886f..a3d48c7f2 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -117,18 +117,18 @@ def find_variable(var_name, caller_context, referenced_declaration=None, is_supe return conc_variables_ptr[var_name] if is_super: - getter_available = lambda f: f.available_functions_as_dict().items() + getter_available = lambda f: f.functions_declared d = {f.canonical_name:f for f in contract.functions} - functions = {f.full_name:f for f in contract.available_elements_from_inheritances(d, getter_available).values()} + functions = {f.full_name:f for f in contract_declarer.available_elements_from_inheritances(d, getter_available).values()} else: functions = contract.available_functions_as_dict() if var_name in functions: return functions[var_name] if is_super: - getter_available = lambda m: m.available_modifiers_as_dict().items() + getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} - modifiers = {m.full_name: m for m in contract.available_elements_from_inheritances(d, getter_available).values()} + modifiers = {m.full_name: m for m in contract_declarer.available_elements_from_inheritances(d, getter_available).values()} else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: @@ -220,45 +220,7 @@ def filter_name(value): return value # endregion -################################################################################### -################################################################################### -# region Conversion -################################################################################### -################################################################################### - -def convert_subdenomination(value, sub): - if sub is None: - return value - # to allow 0.1 ether conversion - if value[0:2] == "0x": - value = float(int(value, 16)) - else: - value = float(value) - if sub == 'wei': - return int(value) - if sub == 'szabo': - return int(value * int(1e12)) - if sub == 'finney': - return int(value * int(1e15)) - if sub == 'ether': - return int(value * int(1e18)) - if sub == 'seconds': - return int(value) - if sub == 'minutes': - return int(value * 60) - if sub == 'hours': - return int(value * 60 * 60) - if sub == 'days': - return int(value * 60 * 60 * 24) - if sub == 'weeks': - return int(value * 60 * 60 * 24 * 7) - if sub == 'years': - return int(value * 60 * 60 * 24 * 7 * 365) - - logger.error('Subdemoniation not found {}'.format(sub)) - return int(value) -# endregion ################################################################################### ################################################################################### # region Parsing @@ -266,7 +228,7 @@ def convert_subdenomination(value, sub): ################################################################################### def parse_call(expression, caller_context): - + src = expression['src'] if caller_context.is_compact_ast: attributes = expression type_conversion = expression['kind'] == 'typeConversion' @@ -299,6 +261,7 @@ def parse_call(expression, caller_context): expression = parse_expression(expression_to_parse, caller_context) t = TypeConversion(expression, type_call) + t.set_offset(src, caller_context.slither) return t if caller_context.is_compact_ast: @@ -312,9 +275,11 @@ def parse_call(expression, caller_context): arguments = [parse_expression(a, caller_context) for a in children[1::]] if isinstance(called, SuperCallExpression): - return SuperCallExpression(called, arguments, type_return) + sp = SuperCallExpression(called, arguments, type_return) + sp.set_offset(expression['src'], caller_context.slither) + return sp call_expression = CallExpression(called, arguments, type_return) - call_expression.set_offset(expression['src'], caller_context.slither) + call_expression.set_offset(src, caller_context.slither) return call_expression def parse_super_name(expression, is_compact_ast): @@ -349,7 +314,9 @@ def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_co value = expression['attributes']['value'] t = parse_type(UnknownType(value), caller_context) - return ElementaryTypeNameExpression(t) + e = ElementaryTypeNameExpression(t) + e.set_offset(expression['src'], caller_context.slither) + return e def parse_expression(expression, caller_context): """ @@ -383,6 +350,7 @@ def parse_expression(expression, caller_context): # The AST naming does not follow the spec name = expression[caller_context.get_key()] is_compact_ast = caller_context.is_compact_ast + src = expression['src'] if name == 'UnaryOperation': if is_compact_ast: @@ -398,6 +366,7 @@ def parse_expression(expression, caller_context): assert len(expression['children']) == 1 expression = parse_expression(expression['children'][0], caller_context) unary_op = UnaryOperation(expression, operation_type) + unary_op.set_offset(src, caller_context.slither) return unary_op elif name == 'BinaryOperation': @@ -415,10 +384,11 @@ def parse_expression(expression, caller_context): left_expression = parse_expression(expression['children'][0], caller_context) right_expression = parse_expression(expression['children'][1], caller_context) binary_op = BinaryOperation(left_expression, right_expression, operation_type) + binary_op.set_offset(src, caller_context.slither) return binary_op elif name == 'FunctionCall': - return parse_call(expression, caller_context) + return parse_call(expression, caller_context) elif name == 'TupleExpression': """ @@ -433,7 +403,7 @@ def parse_expression(expression, caller_context): Note: this is only possible with Solidity >= 0.4.12 """ if is_compact_ast: - expressions = [parse_expression(e, caller_context) if e else None for e in expression['components']] + expressions = [parse_expression(e, caller_context) if e else None for e in expression['components']] else: if 'children' not in expression : attributes = expression['attributes'] @@ -452,6 +422,7 @@ def parse_expression(expression, caller_context): if elems[idx] == '': expressions.insert(idx, None) t = TupleExpression(expressions) + t.set_offset(src, caller_context.slither) return t elif name == 'Conditional': @@ -466,6 +437,7 @@ def parse_expression(expression, caller_context): then_expression = parse_expression(children[1], caller_context) else_expression = parse_expression(children[2], caller_context) conditional = ConditionalExpression(if_expression, then_expression, else_expression) + conditional.set_offset(src, caller_context.slither) return conditional elif name == 'Assignment': @@ -487,16 +459,22 @@ def parse_expression(expression, caller_context): operation_return_type = attributes['type'] assignement = AssignmentOperation(left_expression, right_expression, operation_type, operation_return_type) + assignement.set_offset(src, caller_context.slither) return assignement + + elif name == 'Literal': + + subdenomination = None + assert 'children' not in expression if is_compact_ast: value = expression['value'] if value: if 'subdenomination' in expression and expression['subdenomination']: - value = str(convert_subdenomination(value, expression['subdenomination'])) + subdenomination = expression['subdenomination'] elif not value and value != "": value = '0x'+expression['hexValue'] type = expression['typeDescriptions']['typeString'] @@ -509,7 +487,7 @@ def parse_expression(expression, caller_context): value = expression['attributes']['value'] if value: if 'subdenomination' in expression['attributes'] and expression['attributes']['subdenomination']: - value = str(convert_subdenomination(value, expression['attributes']['subdenomination'])) + subdenomination = expression['attributes']['subdenomination'] elif value is None: # for literal declared as hex # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals @@ -530,7 +508,8 @@ def parse_expression(expression, caller_context): type = ElementaryType('address') else: type = ElementaryType('string') - literal = Literal(value, type) + literal = Literal(value, type, subdenomination) + literal.set_offset(src, caller_context.slither) return literal elif name == 'Identifier': @@ -561,7 +540,7 @@ def parse_expression(expression, caller_context): var = find_variable(value, caller_context, referenced_declaration) identifier = Identifier(var) - identifier.set_offset(expression['src'], caller_context.slither) + identifier.set_offset(src, caller_context.slither) return identifier elif name == 'IndexAccess': @@ -579,11 +558,12 @@ def parse_expression(expression, caller_context): # if abi.decode is used # For example, abi.decode(data, ...(uint[]) ) if right is None: - return _parse_elementary_type_name_expression(left, is_compact_ast, caller_context) + return parse_expression(left, caller_context) left_expression = parse_expression(left, caller_context) right_expression = parse_expression(right, caller_context) index = IndexAccess(left_expression, right_expression, index_type) + index.set_offset(src, caller_context.slither) return index elif name == 'MemberAccess': @@ -602,10 +582,15 @@ def parse_expression(expression, caller_context): var = find_variable(super_name, caller_context, is_super=True) if var is None: raise VariableNotFound('Variable not found: {}'.format(super_name)) - return SuperIdentifier(var) + sup = SuperIdentifier(var) + sup.set_offset(src, caller_context.slither) + return sup member_access = MemberAccess(member_name, member_type, member_expression) + member_access.set_offset(src, caller_context.slither) if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: - return Identifier(SolidityVariableComposed(str(member_access))) + idx = Identifier(SolidityVariableComposed(str(member_access))) + idx.set_offset(src, caller_context.slither) + return idx return member_access elif name == 'ElementaryTypeNameExpression': @@ -647,6 +632,7 @@ def parse_expression(expression, caller_context): else: raise ParsingError('Incorrect type array {}'.format(type_name)) array = NewArray(depth, array_type) + array.set_offset(src, caller_context.slither) return array if type_name[caller_context.get_key()] == 'ElementaryTypeName': @@ -655,6 +641,7 @@ def parse_expression(expression, caller_context): else: elem_type = ElementaryType(type_name['attributes']['name']) new_elem = NewElementaryType(elem_type) + new_elem.set_offset(src, caller_context.slither) return new_elem assert type_name[caller_context.get_key()] == 'UserDefinedTypeName' @@ -664,6 +651,7 @@ def parse_expression(expression, caller_context): else: contract_name = type_name['attributes']['name'] new = NewContract(contract_name) + new.set_offset(src, caller_context.slither) return new elif name == 'ModifierInvocation': @@ -679,7 +667,7 @@ def parse_expression(expression, caller_context): arguments = [parse_expression(a, caller_context) for a in children[1::]] call = CallExpression(called, arguments, 'Modifier') - call.set_offset(expression['src'], caller_context.slither) + call.set_offset(src, caller_context.slither) return call raise ParsingError('Expression not parsed %s'%name) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 6d11e2cc8..8a695ae6a 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -104,30 +104,27 @@ class SlitherSolc(Slither): return for contract_data in data_loaded[self.get_children()]: - # if self.solc_version == '0.3': - # assert contract_data[self.get_key()] == 'Contract' - # contract = ContractSolc03(self, contract_data) - if self.solc_version == '0.4': - assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] - if contract_data[self.get_key()] == 'ContractDefinition': - contract = ContractSolc04(self, contract_data) - if 'src' in contract_data: - contract.set_offset(contract_data['src'], self) - self._contractsNotParsed.append(contract) - elif contract_data[self.get_key()] == 'PragmaDirective': - if self._is_compact_ast: - pragma = Pragma(contract_data['literals']) - else: - pragma = Pragma(contract_data['attributes']["literals"]) - pragma.set_offset(contract_data['src'], self) - self._pragma_directives.append(pragma) - elif contract_data[self.get_key()] == 'ImportDirective': - if self.is_compact_ast: - import_directive = Import(contract_data["absolutePath"]) - else: - import_directive = Import(contract_data['attributes']["absolutePath"]) - import_directive.set_offset(contract_data['src'], self) - self._import_directives.append(import_directive) + + assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] + if contract_data[self.get_key()] == 'ContractDefinition': + contract = ContractSolc04(self, contract_data) + if 'src' in contract_data: + contract.set_offset(contract_data['src'], self) + self._contractsNotParsed.append(contract) + elif contract_data[self.get_key()] == 'PragmaDirective': + if self._is_compact_ast: + pragma = Pragma(contract_data['literals']) + else: + pragma = Pragma(contract_data['attributes']["literals"]) + pragma.set_offset(contract_data['src'], self) + self._pragma_directives.append(pragma) + elif contract_data[self.get_key()] == 'ImportDirective': + if self.is_compact_ast: + import_directive = Import(contract_data["absolutePath"]) + else: + import_directive = Import(contract_data['attributes']["absolutePath"]) + import_directive.set_offset(contract_data['src'], self) + self._import_directives.append(import_directive) def _parse_source_unit(self, data, filename): @@ -224,11 +221,11 @@ class SlitherSolc(Slither): else: father_constructors.append(self._contracts_by_id[i]) - except KeyError: - txt = 'A contract was not found, it is likely that your codebase contains muliple contracts with the same name' - txt += 'Truffle does not handle this case during compilation' - txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error' - txt += 'And update your code to remove the duplicate' + except KeyError as e: + txt = f'A contract was not found (id {e}), it is likely that your codebase contains muliple contracts with the same name. ' + txt += 'Truffle does not handle this case during compilation. ' + txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error, ' + txt += 'and update your code to remove the duplicate' raise ParsingContractNotFound(txt) contract.setInheritance(ancestors, fathers, father_constructors) @@ -373,10 +370,14 @@ class SlitherSolc(Slither): contract.analyze_content_modifiers() contract.analyze_content_functions() + + contract.set_is_analyzed(True) def _convert_to_slithir(self): + for contract in self.contracts: + contract.add_constructor_variables() contract.convert_expression_to_slithir() self._propagate_function_calls() for contract in self.contracts: diff --git a/slither/tools/__init__.py b/slither/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/demo/README.md b/slither/tools/demo/README.md new file mode 100644 index 000000000..00bdec0b4 --- /dev/null +++ b/slither/tools/demo/README.md @@ -0,0 +1,6 @@ +## Demo + +This directory contains an example of Slither utility. + +See the [utility documentation](https://github.com/crytic/slither/wiki/Adding-a-new-utility) + diff --git a/slither/tools/demo/__init__.py b/slither/tools/demo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/demo/__main__.py b/slither/tools/demo/__main__.py new file mode 100644 index 000000000..4bee3b449 --- /dev/null +++ b/slither/tools/demo/__main__.py @@ -0,0 +1,38 @@ +import os +import argparse +import logging +from slither import Slither +from crytic_compile import cryticparser + +logging.basicConfig() +logging.getLogger("Slither").setLevel(logging.INFO) + +logger = logging.getLogger("Slither-demo") + +def parse_args(): + """ + Parse the underlying arguments for the program. + :return: Returns the arguments for the program. + """ + parser = argparse.ArgumentParser(description='Demo', + usage='slither-demo filename') + + parser.add_argument('filename', + help='The filename of the contract or truffle directory to analyze.') + + # Add default arguments from crytic-compile + cryticparser.init(parser) + + return parser.parse_args() + + +def main(): + args = parse_args() + + # Perform slither analysis on the given filename + slither = Slither(args.filename, **vars(args)) + + logger.info('Analysis done!') + +if __name__ == '__main__': + main() diff --git a/slither/tools/erc_conformance/README.md b/slither/tools/erc_conformance/README.md new file mode 100644 index 000000000..a04677a65 --- /dev/null +++ b/slither/tools/erc_conformance/README.md @@ -0,0 +1 @@ +## ERC conformance \ No newline at end of file diff --git a/slither/tools/erc_conformance/__init__.py b/slither/tools/erc_conformance/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/erc_conformance/__main__.py b/slither/tools/erc_conformance/__main__.py new file mode 100644 index 000000000..fe0fbb845 --- /dev/null +++ b/slither/tools/erc_conformance/__main__.py @@ -0,0 +1,99 @@ +import argparse +import logging +from collections import defaultdict + +from slither import Slither +from crytic_compile import cryticparser +from slither.utils.erc import ERCS +from slither.utils.output import output_to_json +from .erc.ercs import generic_erc_checks +from .erc.erc20 import check_erc20 + +logging.basicConfig() +logging.getLogger("Slither").setLevel(logging.INFO) + +logger = logging.getLogger("Slither-conformance") +logger.setLevel(logging.INFO) + +ch = logging.StreamHandler() +ch.setLevel(logging.INFO) +formatter = logging.Formatter('%(message)s') +logger.addHandler(ch) +logger.handlers[0].setFormatter(formatter) +logger.propagate = False + +ADDITIONAL_CHECKS = { + "ERC20": check_erc20 +} + +def parse_args(): + """ + Parse the underlying arguments for the program. + :return: Returns the arguments for the program. + """ + parser = argparse.ArgumentParser(description='Check the ERC 20 conformance', + usage='slither-erc project contractName') + + parser.add_argument('project', + help='The codebase to be tested.') + + parser.add_argument('contract_name', + help='The name of the contract. Specify the first case contract that follow the standard. Derived contracts will be checked.') + + parser.add_argument( + "--erc", + help=f"ERC to be tested, available {','.join(ERCS.keys())} (default ERC20)", + action="store", + default="erc20", + ) + + parser.add_argument('--json', + help='Export the results as a JSON file ("--json -" to export to stdout)', + action='store', + default=False) + + # Add default arguments from crytic-compile + cryticparser.init(parser) + + return parser.parse_args() + +def _log_error(err, args): + if args.json: + output_to_json(args.json, str(err), {"upgradeability-check": []}) + + logger.error(err) + +def main(): + args = parse_args() + + # Perform slither analysis on the given filename + slither = Slither(args.project, **vars(args)) + + ret = defaultdict(list) + + if args.erc.upper() in ERCS: + + contract = slither.get_contract_from_name(args.contract_name) + + if not contract: + err = f'Contract not found: {args.contract_name}' + _log_error(err, args) + return + # First elem is the function, second is the event + erc = ERCS[args.erc.upper()] + generic_erc_checks(contract, erc[0], erc[1], ret) + + if args.erc.upper() in ADDITIONAL_CHECKS: + ADDITIONAL_CHECKS[args.erc.upper()](contract, ret) + + else: + err = f'Incorrect ERC selected {args.erc}' + _log_error(err, args) + return + + if args.json: + output_to_json(args.json, None, {"upgradeability-check": ret}) + + +if __name__ == '__main__': + main() diff --git a/slither/tools/erc_conformance/erc/__init__.py b/slither/tools/erc_conformance/erc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/erc_conformance/erc/erc20.py b/slither/tools/erc_conformance/erc/erc20.py new file mode 100644 index 000000000..25473bc84 --- /dev/null +++ b/slither/tools/erc_conformance/erc/erc20.py @@ -0,0 +1,37 @@ +import logging + +from slither.utils import output + +logger = logging.getLogger("Slither-conformance") + + +def approval_race_condition(contract, ret): + increaseAllowance = contract.get_function_from_signature('increaseAllowance(address,uint256)') + + if not increaseAllowance: + increaseAllowance = contract.get_function_from_signature('safeIncreaseAllowance(address,uint256)') + + if increaseAllowance: + txt = f'\t[✓] {contract.name} has {increaseAllowance.full_name}' + logger.info(txt) + else: + txt = f'\t[ ] {contract.name} is not protected for the ERC20 approval race condition' + logger.info(txt) + + lack_of_erc20_race_condition_protection = output.Output(txt) + lack_of_erc20_race_condition_protection.add(contract) + ret["lack_of_erc20_race_condition_protection"].append(lack_of_erc20_race_condition_protection.data) + + +def check_erc20(contract, ret, explored=None): + if explored is None: + explored = set() + + explored.add(contract) + + approval_race_condition(contract, ret) + + for derived_contract in contract.derived_contracts: + check_erc20(derived_contract, ret, explored) + + return ret diff --git a/slither/tools/erc_conformance/erc/ercs.py b/slither/tools/erc_conformance/erc/ercs.py new file mode 100644 index 000000000..5af000357 --- /dev/null +++ b/slither/tools/erc_conformance/erc/ercs.py @@ -0,0 +1,194 @@ +import logging + +from slither.slithir.operations import EventCall +from slither.utils import output +from slither.utils.type import export_nested_types_from_variable, export_return_type_from_variable + +logger = logging.getLogger("Slither-conformance") + + +def _check_signature(erc_function, contract, ret): + name = erc_function.name + parameters = erc_function.parameters + return_type = erc_function.return_type + view = erc_function.view + required = erc_function.required + events = erc_function.events + + sig = f'{name}({",".join(parameters)})' + function = contract.get_function_from_signature(sig) + + if not function: + # The check on state variable is needed until we have a better API to handle state variable getters + state_variable_as_function = contract.get_state_variable_from_name(name) + + if not state_variable_as_function or not state_variable_as_function.visibility in ['public', 'external']: + txt = f'[ ] {sig} is missing {"" if required else "(optional)"}' + logger.info(txt) + missing_func = output.Output(txt, additional_fields={ + "function": sig, + "required": required + }) + missing_func.add(contract) + ret["missing_function"].append(missing_func.data) + return + + types = [str(x) for x in export_nested_types_from_variable(state_variable_as_function)] + + if types != parameters: + txt = f'[ ] {sig} is missing {"" if required else "(optional)"}' + logger.info(txt) + missing_func = output.Output(txt, additional_fields={ + "function": sig, + "required": required + }) + missing_func.add(contract) + ret["missing_function"].append(missing_func.data) + return + + function_return_type = [export_return_type_from_variable(state_variable_as_function)] + + function_view = True + else: + function_return_type = function.return_type + function_view = function.view + + txt = f'[✓] {sig} is present' + logger.info(txt) + + if function_return_type: + function_return_type = ','.join([str(x) for x in function_return_type]) + if function_return_type == return_type: + txt = f'\t[✓] {sig} -> () (correct return value)' + logger.info(txt) + else: + txt = f'\t[ ] {sig} -> () should return {return_type}' + logger.info(txt) + + incorrect_return = output.Output(txt, additional_fields={ + "expected_return_type": return_type, + "actual_return_type": function_return_type + }) + incorrect_return.add(function) + ret["incorrect_return_type"].append(incorrect_return.data) + + elif not return_type: + txt = f'\t[✓] {sig} -> () (correct return type)' + logger.info(txt) + else: + txt = f'\t[ ] {sig} -> () should return {return_type}' + logger.info(txt) + + incorrect_return = output.Output(txt, additional_fields={ + "expected_return_type": return_type, + "actual_return_type": function_return_type + }) + incorrect_return.add(function) + ret["incorrect_return_type"].append(incorrect_return.data) + + if view: + if function_view: + txt = f'\t[✓] {sig} is view' + logger.info(txt) + else: + txt = f'\t[ ] {sig} should be view' + logger.info(txt) + + should_be_view = output.Output(txt) + should_be_view.add(function) + ret["should_be_view"].append(should_be_view.data) + + if events: + for event in events: + event_sig = f'{event.name}({",".join(event.parameters)})' + + if not function: + txt = f'\t[ ] Must emit be view {event_sig}' + logger.info(txt) + + missing_event_emmited = output.Output(txt, additional_fields={ + "missing_event": event_sig + }) + missing_event_emmited.add(function) + ret["missing_event_emmited"].append(missing_event_emmited.data) + + else: + event_found = False + for ir in function.all_slithir_operations(): + if isinstance(ir, EventCall): + if ir.name == event.name: + if event.parameters == [str(a.type) for a in ir.arguments]: + event_found = True + break + if event_found: + txt = f'\t[✓] {event_sig} is emitted' + logger.info(txt) + else: + txt = f'\t[ ] Must emit be view {event_sig}' + logger.info(txt) + + missing_event_emmited = output.Output(txt, additional_fields={ + "missing_event": event_sig + }) + missing_event_emmited.add(function) + ret["missing_event_emmited"].append(missing_event_emmited.data) + + +def _check_events(erc_event, contract, ret): + name = erc_event.name + parameters = erc_event.parameters + indexes = erc_event.indexes + + sig = f'{name}({",".join(parameters)})' + event = contract.get_event_from_signature(sig) + + if not event: + txt = f'[ ] {sig} is missing' + logger.info(txt) + + missing_event = output.Output(txt, additional_fields={ + "event": sig + }) + missing_event.add(contract) + ret["missing_event"].append(missing_event.data) + return + + txt = f'[✓] {sig} is present' + logger.info(txt) + + for i, index in enumerate(indexes): + if index: + if event.elems[i].indexed: + txt = f'\t[✓] parameter {i} is indexed' + logger.info(txt) + else: + txt = f'\t[ ] parameter {i} should be indexed' + logger.info(txt) + + missing_event_index = output.Output(txt, additional_fields={ + "missing_index": i + }) + missing_event_index.add_event(event) + ret["missing_event_index"].append(missing_event_index.data) + + +def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None): + + if explored is None: + explored = set() + + explored.add(contract) + + logger.info(f'# Check {contract.name}\n') + + logger.info(f'## Check functions') + for erc_function in erc_functions: + _check_signature(erc_function, contract, ret) + logger.info(f'\n## Check events') + for erc_event in erc_events: + _check_events(erc_event, contract, ret) + + logger.info('\n') + + for derived_contract in contract.derived_contracts: + generic_erc_checks(derived_contract, erc_functions, erc_events, ret, explored) diff --git a/slither/tools/flattening/__init__.py b/slither/tools/flattening/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/flattening/__main__.py b/slither/tools/flattening/__main__.py new file mode 100644 index 000000000..735d924a2 --- /dev/null +++ b/slither/tools/flattening/__main__.py @@ -0,0 +1,47 @@ +import argparse +import logging +from slither import Slither +from crytic_compile import cryticparser +from .flattening import Flattening + +logging.basicConfig() +logging.getLogger("Slither").setLevel(logging.INFO) +logger = logging.getLogger("Slither-flattening") +logger.setLevel(logging.INFO) + +def parse_args(): + """ + Parse the underlying arguments for the program. + :return: Returns the arguments for the program. + """ + parser = argparse.ArgumentParser(description='Contracts flattening', + usage='slither-flat filename') + + parser.add_argument('filename', + help='The filename of the contract or project to analyze.') + + parser.add_argument('--convert-external', + help='Convert external to public.', + action='store_true') + + parser.add_argument('--contract', + help='Flatten a specific contract (default: all most derived contracts).', + default=None) + + # Add default arguments from crytic-compile + cryticparser.init(parser) + + return parser.parse_args() + + +def main(): + args = parse_args() + + slither = Slither(args.filename, **vars(args)) + flat = Flattening(slither, external_to_public=args.convert_external) + + flat.export(target=args.contract) + + +if __name__ == '__main__': + main() diff --git a/slither/tools/flattening/flattening.py b/slither/tools/flattening/flattening.py new file mode 100644 index 000000000..ccf398009 --- /dev/null +++ b/slither/tools/flattening/flattening.py @@ -0,0 +1,157 @@ +from pathlib import Path +import re +import logging +from slither.exceptions import SlitherException +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.declarations.structure import Structure +from slither.core.declarations.enum import Enum +from slither.core.declarations.contract import Contract +from slither.slithir.operations import NewContract, TypeConversion + +logger = logging.getLogger("Slither-flattening") + +class Flattening: + + DEFAULT_EXPORT_PATH = Path('crytic-export/flattening') + + def __init__(self, slither, external_to_public=False): + self._source_codes = {} + self._slither = slither + self._external_to_public = external_to_public + self._use_abi_encoder_v2 = False + + self._check_abi_encoder_v2() + + for contract in slither.contracts: + self._get_source_code(contract) + + def _check_abi_encoder_v2(self): + for p in self._slither.pragma_directives: + if 'ABIEncoderV2' in str(p.directive): + self._use_abi_encoder_v2 = True + return + + def _get_source_code(self, contract): + src_mapping = contract.source_mapping + content = self._slither.source_code[src_mapping['filename_absolute']] + start = src_mapping['start'] + end = src_mapping['start'] + src_mapping['length'] + + # interface must use external + if self._external_to_public and contract.contract_kind != "interface": + # to_patch is a list of (index, bool). The bool indicates + # if the index is for external -> public (true) + # or a calldata -> memory (false) + to_patch = [] + for f in contract.functions_declared: + # fallback must be external + if f.is_fallback or f.is_constructor_variables: + continue + if f.visibility == 'external': + attributes_start = (f.parameters_src.source_mapping['start'] + + f.parameters_src.source_mapping['length']) + attributes_end = f.returns_src.source_mapping['start'] + attributes = content[attributes_start:attributes_end] + regex = re.search(r'((\sexternal)\s+)|(\sexternal)$|(\)external)$', attributes) + if regex: + to_patch.append((attributes_start + regex.span()[0] + 1, True)) + else: + raise SlitherException(f'External keyword not found {f.name} {attributes}') + + for var in f.parameters: + if var.location == "calldata": + calldata_start = var.source_mapping['start'] + calldata_end = calldata_start + var.source_mapping['length'] + calldata_idx = content[calldata_start:calldata_end].find(' calldata ') + to_patch.append((calldata_start + calldata_idx + 1, False)) + + to_patch.sort(key=lambda x:x[0], reverse=True) + + content = content[start:end] + for (index, is_external) in to_patch: + index = index - start + if is_external: + content = content[:index] + 'public' + content[index + len('external'):] + else: + content = content[:index] + 'memory' + content[index + len('calldata'):] + else: + content = content[start:end] + + self._source_codes[contract] = content + + + def _export_from_type(self, t, contract, exported, list_contract): + if isinstance(t, UserDefinedType): + if isinstance(t.type, (Enum, Structure)): + if t.type.contract != contract and not t.type.contract in exported: + self._export_contract(t.type.contract, exported, list_contract) + else: + assert isinstance(t.type, Contract) + if t.type != contract and not t.type in exported: + self._export_contract(t.type, exported, list_contract) + + def _export_contract(self, contract, exported, list_contract): + if contract.name in exported: + return + for inherited in contract.inheritance: + self._export_contract(inherited, exported, list_contract) + + # Find all the external contracts called + externals = contract.all_library_calls + contract.all_high_level_calls + # externals is a list of (contract, function) + # We also filter call to itself to avoid infilite loop + externals = list(set([e[0] for e in externals if e[0] != contract])) + + for inherited in externals: + self._export_contract(inherited, exported, list_contract) + + # Find all the external contracts use as a base type + local_vars = [] + for f in contract.functions_declared: + local_vars += f.variables + + for v in contract.variables + local_vars: + self._export_from_type(v.type, contract, exported, list_contract) + + # Find all convert and "new" operation that can lead to use an external contract + for f in contract.functions_declared: + for ir in f.slithir_operations: + if isinstance(ir, NewContract): + if ir.contract_created != contract and not ir.contract_created in exported: + self._export_contract(ir.contract_created, exported, list_contract) + if isinstance(ir, TypeConversion): + self._export_from_type(ir.type, contract, exported, list_contract) + if contract.name in exported: + return + exported.add(contract.name) + list_contract.append(self._source_codes[contract]) + + def _export(self, contract, ret): + self._export_contract(contract, set(), ret) + path = Path(self.DEFAULT_EXPORT_PATH, f'{contract.name}.sol') + logger.info(f'Export {path}') + with open(path, 'w') as f: + if self._slither.solc_version: + f.write(f'pragma solidity {self._slither.solc_version};\n') + if self._use_abi_encoder_v2: + f.write('pragma experimental ABIEncoderV2;\n') + f.write('\n'.join(ret)) + f.write('\n') + + def export(self, target=None): + + if not self.DEFAULT_EXPORT_PATH.exists(): + self.DEFAULT_EXPORT_PATH.mkdir(parents=True) + + if target is None: + for contract in self._slither.contracts_derived: + ret = [] + self._export(contract, ret) + else: + contract = self._slither.get_contract_from_name(target) + if contract is None: + logger.error(f'{target} not found') + else: + ret = [] + self._export(contract, ret) + diff --git a/slither/tools/kspec_coverage/__init__.py b/slither/tools/kspec_coverage/__init__.py new file mode 100755 index 000000000..286b5a55a --- /dev/null +++ b/slither/tools/kspec_coverage/__init__.py @@ -0,0 +1 @@ +from .analysis import run_analysis diff --git a/slither/tools/kspec_coverage/__main__.py b/slither/tools/kspec_coverage/__main__.py new file mode 100644 index 000000000..47dc5c9fa --- /dev/null +++ b/slither/tools/kspec_coverage/__main__.py @@ -0,0 +1,58 @@ +import sys +import logging +import argparse +from slither import Slither +from .kspec_coverage import kspec_coverage +from crytic_compile import cryticparser + +logging.basicConfig() +logger = logging.getLogger("Slither.kspec") +logger.setLevel(logging.INFO) + +ch = logging.StreamHandler() +ch.setLevel(logging.INFO) +formatter = logging.Formatter('%(message)s') +logger.addHandler(ch) +logger.handlers[0].setFormatter(formatter) +logger.propagate = False + +def parse_args(): + """ + Parse the underlying arguments for the program. + :return: Returns the arguments for the program. + """ + parser = argparse.ArgumentParser(description='slither-kspec-coverage', + usage='slither-kspec-coverage contract.sol kspec.md') + + parser.add_argument('contract', help='The filename of the contract or truffle directory to analyze.') + parser.add_argument('kspec', help='The filename of the Klab spec markdown for the analyzed contract(s)') + + parser.add_argument('--version', help='displays the current version', version='0.1.0',action='version') + parser.add_argument('--json', + help='Export the results as a JSON file ("--json -" to export to stdout)', + action='store', + default=False + ) + + cryticparser.init(parser) + + if len(sys.argv) < 2: + parser.print_help(sys.stderr) + sys.exit(1) + + return parser.parse_args() + + +def main(): + # ------------------------------ + # Usage: slither-kspec-coverage contract kspec + # Example: slither-kspec-coverage contract.sol kspec.md + # ------------------------------ + # Parse all arguments + + args = parse_args() + + kspec_coverage(args) + +if __name__ == '__main__': + main() diff --git a/slither/tools/kspec_coverage/analysis.py b/slither/tools/kspec_coverage/analysis.py new file mode 100755 index 000000000..d2daf03d0 --- /dev/null +++ b/slither/tools/kspec_coverage/analysis.py @@ -0,0 +1,149 @@ +import re +import logging + +from slither.core.declarations import Function +from slither.core.variables.variable import Variable +from slither.utils.colors import yellow, green, red +from slither.utils import output + +logging.basicConfig(level=logging.WARNING) +logger = logging.getLogger('Slither.kspec') + + +def _refactor_type(type): + return { + 'uint': 'uint256', + 'int': 'int256' + }.get(type, type) + + +def _get_all_covered_kspec_functions(target): + # Create a set of our discovered functions which are covered + covered_functions = set() + + BEHAVIOUR_PATTERN = re.compile('behaviour\s+(\S+)\s+of\s+(\S+)') + INTERFACE_PATTERN = re.compile('interface\s+([^\r\n]+)') + + # Read the file contents + with open(target, 'r', encoding='utf8') as target_file: + lines = target_file.readlines() + + # Loop for each line, if a line matches our behaviour regex, and the next one matches our interface regex, + # we add our finding + i = 0 + while i < len(lines): + match = BEHAVIOUR_PATTERN.match(lines[i]) + if match: + contract_name = match.groups()[1] + match = INTERFACE_PATTERN.match(lines[i + 1]) + if match: + function_full_name = match.groups()[0] + start, end = function_full_name.index('(') + 1, function_full_name.index(')') + function_arguments = function_full_name[start:end].split(',') + function_arguments = [_refactor_type(arg.strip().split(' ')[0]) for arg in function_arguments] + function_full_name = function_full_name[:start] + ','.join(function_arguments) + ')' + covered_functions.add((contract_name, function_full_name)) + i += 1 + i += 1 + return covered_functions + + +def _get_slither_functions(slither): + # Use contract == contract_declarer to avoid dupplicate + all_functions_declared = [f for f in slither.functions if (f.contract == f.contract_declarer + and f.is_implemented + and not f.is_constructor + and not f.is_constructor_variables)] + # Use list(set()) because same state variable instances can be shared accross contracts + # TODO: integrate state variables + all_functions_declared += list(set([s for s in slither.state_variables if s.visibility in ['public', 'external']])) + slither_functions = {(function.contract.name, function.full_name): function for function in all_functions_declared} + + return slither_functions + + +def _generate_output(kspec, message, color, generate_json): + info = "" + for function in kspec: + info += f"{message} {function.contract.name}.{function.full_name}\n" + if info: + logger.info(color(info)) + + if generate_json: + json_kspec_present = output.Output(info) + for function in kspec: + json_kspec_present.add(function) + return json_kspec_present.data + return None + + +def _generate_output_unresolved(kspec, message, color, generate_json): + info = "" + for contract, function in kspec: + info += f"{message} {contract}.{function}\n" + if info: + logger.info(color(info)) + + if generate_json: + json_kspec_present = output.Output(info, additional_fields={"signatures": kspec}) + return json_kspec_present.data + return None + + +def _run_coverage_analysis(args, slither, kspec_functions): + # Collect all slither functions + slither_functions = _get_slither_functions(slither) + + # Determine which klab specs were not resolved. + slither_functions_set = set(slither_functions) + kspec_functions_resolved = kspec_functions & slither_functions_set + kspec_functions_unresolved = kspec_functions - kspec_functions_resolved + + kspec_missing = [] + kspec_present = [] + + for slither_func_desc in sorted(slither_functions_set): + slither_func = slither_functions[slither_func_desc] + + if slither_func_desc in kspec_functions: + kspec_present.append(slither_func) + else: + kspec_missing.append(slither_func) + + logger.info('## Check for functions coverage') + json_kspec_present = _generate_output(kspec_present, "[✓]", green, args.json) + json_kspec_missing_functions = _generate_output([f for f in kspec_missing if isinstance(f, Function)], + "[ ] (Missing function)", + red, + args.json) + json_kspec_missing_variables = _generate_output([f for f in kspec_missing if isinstance(f, Variable)], + "[ ] (Missing variable)", + yellow, + args.json) + json_kspec_unresolved = _generate_output_unresolved(kspec_functions_unresolved, + "[ ] (Unresolved)", + yellow, + args.json) + + # Handle unresolved kspecs + if args.json: + output.output_to_json(args.json, None, { + "functions_present": json_kspec_present, + "functions_missing": json_kspec_missing_functions, + "variables_missing": json_kspec_missing_variables, + "functions_unresolved": json_kspec_unresolved + }) + + +def run_analysis(args, slither, kspec): + # Get all of our kspec'd functions (tuple(contract_name, function_name)). + if ',' in kspec: + kspecs = kspec.split(',') + kspec_functions = set() + for kspec in kspecs: + kspec_functions |= _get_all_covered_kspec_functions(kspec) + else: + kspec_functions = _get_all_covered_kspec_functions(kspec) + + # Run coverage analysis + _run_coverage_analysis(args, slither, kspec_functions) diff --git a/slither/tools/kspec_coverage/kspec_coverage.py b/slither/tools/kspec_coverage/kspec_coverage.py new file mode 100755 index 000000000..2ee25477f --- /dev/null +++ b/slither/tools/kspec_coverage/kspec_coverage.py @@ -0,0 +1,14 @@ +from slither.tools.kspec_coverage.analysis import run_analysis +from slither import Slither + +def kspec_coverage(args): + + contract = args.contract + kspec = args.kspec + + slither = Slither(contract, **vars(args)) + + # Run the analysis on the Klab specs + run_analysis(args, slither, kspec) + + diff --git a/slither/tools/possible_paths/__init__.py b/slither/tools/possible_paths/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/possible_paths/__main__.py b/slither/tools/possible_paths/__main__.py similarity index 100% rename from utils/possible_paths/__main__.py rename to slither/tools/possible_paths/__main__.py diff --git a/utils/possible_paths/possible_paths.py b/slither/tools/possible_paths/possible_paths.py similarity index 100% rename from utils/possible_paths/possible_paths.py rename to slither/tools/possible_paths/possible_paths.py diff --git a/utils/similarity/__init__.py b/slither/tools/similarity/__init__.py similarity index 100% rename from utils/similarity/__init__.py rename to slither/tools/similarity/__init__.py diff --git a/utils/similarity/__main__.py b/slither/tools/similarity/__main__.py similarity index 100% rename from utils/similarity/__main__.py rename to slither/tools/similarity/__main__.py diff --git a/utils/similarity/cache.py b/slither/tools/similarity/cache.py similarity index 100% rename from utils/similarity/cache.py rename to slither/tools/similarity/cache.py diff --git a/utils/similarity/encode.py b/slither/tools/similarity/encode.py similarity index 99% rename from utils/similarity/encode.py rename to slither/tools/similarity/encode.py index f20d316a8..06b3691ed 100644 --- a/utils/similarity/encode.py +++ b/slither/tools/similarity/encode.py @@ -195,7 +195,7 @@ def encode_contract(cfilename, **kwargs): # Iterate over all the functions for function in contract.functions_declared: - if function.nodes == []: + if function.nodes == [] or function.is_constructor_variables: continue x = (cfilename,contract.name,function.name) diff --git a/utils/similarity/info.py b/slither/tools/similarity/info.py similarity index 100% rename from utils/similarity/info.py rename to slither/tools/similarity/info.py diff --git a/utils/similarity/model.py b/slither/tools/similarity/model.py similarity index 100% rename from utils/similarity/model.py rename to slither/tools/similarity/model.py diff --git a/utils/similarity/plot.py b/slither/tools/similarity/plot.py similarity index 100% rename from utils/similarity/plot.py rename to slither/tools/similarity/plot.py diff --git a/utils/similarity/similarity.py b/slither/tools/similarity/similarity.py similarity index 100% rename from utils/similarity/similarity.py rename to slither/tools/similarity/similarity.py diff --git a/utils/similarity/test.py b/slither/tools/similarity/test.py similarity index 100% rename from utils/similarity/test.py rename to slither/tools/similarity/test.py diff --git a/utils/similarity/train.py b/slither/tools/similarity/train.py similarity index 100% rename from utils/similarity/train.py rename to slither/tools/similarity/train.py diff --git a/slither/tools/slither_format/.gitignore b/slither/tools/slither_format/.gitignore new file mode 100644 index 000000000..cbef61e67 --- /dev/null +++ b/slither/tools/slither_format/.gitignore @@ -0,0 +1,14 @@ +# .format files are the output files produced by slither-format +# .patch files are the output files produced by slither-format +*.format +*.patch + +# Temporary files (Emacs backup files ending in tilde and others) +*~ +*.err +*.out + + + + + diff --git a/slither/tools/slither_format/__init__.py b/slither/tools/slither_format/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/slither_format/__main__.py b/slither/tools/slither_format/__main__.py new file mode 100644 index 000000000..82256a5fa --- /dev/null +++ b/slither/tools/slither_format/__main__.py @@ -0,0 +1,89 @@ +import sys +import argparse +from slither import Slither +from slither.utils.command_line import read_config_file +import logging +from .slither_format import slither_format +from crytic_compile import cryticparser + +logging.basicConfig() +logger = logging.getLogger("Slither").setLevel(logging.INFO) + +# Slither detectors for which slither-format currently works +available_detectors = ["unused-state", + "solc-version", + "pragma", + "naming-convention", + "external-function", + "constable-states", + "constant-function"] + +detectors_to_run = [] + +def parse_args(): + """ + Parse the underlying arguments for the program. + :return: Returns the arguments for the program. + """ + parser = argparse.ArgumentParser(description='slither_format', + usage='slither_format filename') + + parser.add_argument('filename', help='The filename of the contract or truffle directory to analyze.') + parser.add_argument('--verbose-test', '-v', help='verbose mode output for testing',action='store_true',default=False) + parser.add_argument('--verbose-json', '-j', help='verbose json output',action='store_true',default=False) + parser.add_argument('--version', + help='displays the current version', + version='0.1.0', + action='version') + + parser.add_argument('--config-file', + help='Provide a config file (default: slither.config.json)', + action='store', + dest='config_file', + default='slither.config.json') + + + group_detector = parser.add_argument_group('Detectors') + group_detector.add_argument('--detect', + help='Comma-separated list of detectors, defaults to all, ' + 'available detectors: {}'.format( + ', '.join(d for d in available_detectors)), + action='store', + dest='detectors_to_run', + default='all') + + group_detector.add_argument('--exclude', + help='Comma-separated list of detectors to exclude,' + 'available detectors: {}'.format( + ', '.join(d for d in available_detectors)), + action='store', + dest='detectors_to_exclude', + default='all') + + cryticparser.init(parser) + + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + + return parser.parse_args() + + +def main(): + # ------------------------------ + # Usage: python3 -m slither_format filename + # Example: python3 -m slither_format contract.sol + # ------------------------------ + # Parse all arguments + args = parse_args() + + read_config_file(args) + + + # Perform slither analysis on the given filename + slither = Slither(args.filename, **vars(args)) + + # Format the input files based on slither analysis + slither_format(slither, **vars(args)) +if __name__ == '__main__': + main() diff --git a/slither/tools/slither_format/slither_format.py b/slither/tools/slither_format/slither_format.py new file mode 100644 index 000000000..dd9a745ee --- /dev/null +++ b/slither/tools/slither_format/slither_format.py @@ -0,0 +1,151 @@ +import logging +from pathlib import Path +from slither.detectors.variables.unused_state_variables import UnusedStateVars +from slither.detectors.attributes.incorrect_solc import IncorrectSolc +from slither.detectors.attributes.constant_pragma import ConstantPragma +from slither.detectors.naming_convention.naming_convention import NamingConvention +from slither.detectors.functions.external_function import ExternalFunction +from slither.detectors.variables.possible_const_state_variables import ConstCandidateStateVars +from slither.detectors.attributes.const_functions import ConstantFunctions +from slither.utils.colors import yellow + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('Slither.Format') + +all_detectors = { + 'unused-state': UnusedStateVars, + 'solc-version': IncorrectSolc, + 'pragma': ConstantPragma, + 'naming-convention': NamingConvention, + 'external-function': ExternalFunction, + 'constable-states' : ConstCandidateStateVars, + 'constant-function': ConstantFunctions +} + +def slither_format(slither, **kwargs): + '''' + Keyword Args: + detectors_to_run (str): Comma-separated list of detectors, defaults to all + ''' + + detectors_to_run = choose_detectors(kwargs.get('detectors_to_run', 'all'), + kwargs.get('detectors_to_exclude', '')) + + for detector in detectors_to_run: + slither.register_detector(detector) + + slither.generate_patches = True + + detector_results = slither.run_detectors() + detector_results = [x for x in detector_results if x] # remove empty results + detector_results = [item for sublist in detector_results for item in sublist] # flatten + + export = Path('crytic-export', 'patches') + + export.mkdir(parents=True, exist_ok=True) + + counter_result = 0 + + logger.info(yellow('slither-format is in beta, carefully review each patch before merging it.')) + + for result in detector_results: + if not 'patches' in result: + continue + one_line_description = result["description"].split("\n")[0] + + export_result = Path(export, f'{counter_result}') + export_result.mkdir(parents=True, exist_ok=True) + counter_result += 1 + counter = 0 + + logger.info(f'Issue: {one_line_description}') + logger.info(f'Generated: ({export_result})') + + for file, diff, in result['patches_diff'].items(): + filename = f'fix_{counter}.patch' + path = Path(export_result, filename) + logger.info(f'\t- {filename}') + with open(path, 'w') as f: + f.write(diff) + counter += 1 + + +# endregion +################################################################################### +################################################################################### +# region Detectors +################################################################################### +################################################################################### + +def choose_detectors(detectors_to_run, detectors_to_exclude): + # If detectors are specified, run only these ones + cls_detectors_to_run = [] + exclude = detectors_to_exclude.split(',') + if detectors_to_run == 'all': + for d in all_detectors: + if d in exclude: + continue + cls_detectors_to_run.append(all_detectors[d]) + else: + exclude = detectors_to_exclude.split(',') + for d in detectors_to_run.split(','): + if d in all_detectors: + if d in exclude: + continue + cls_detectors_to_run.append(all_detectors[d]) + else: + raise Exception('Error: {} is not a detector'.format(d)) + return cls_detectors_to_run + +# endregion +################################################################################### +################################################################################### +# region Debug functions +################################################################################### +################################################################################### + +def print_patches(number_of_slither_results, patches): + logger.info("Number of Slither results: " + str(number_of_slither_results)) + number_of_patches = 0 + for file in patches: + number_of_patches += len(patches[file]) + logger.info("Number of patches: " + str(number_of_patches)) + for file in patches: + logger.info("Patch file: " + file) + for patch in patches[file]: + logger.info("Detector: " + patch['detector']) + logger.info("Old string: " + patch['old_string'].replace("\n","")) + logger.info("New string: " + patch['new_string'].replace("\n","")) + logger.info("Location start: " + str(patch['start'])) + logger.info("Location end: " + str(patch['end'])) + +def print_patches_json(number_of_slither_results, patches): + print('{',end='') + print("\"Number of Slither results\":" + '"' + str(number_of_slither_results) + '",') + print("\"Number of patchlets\":" + "\"" + str(len(patches)) + "\"", ',') + print("\"Patchlets\":" + '[') + for index, file in enumerate(patches): + if index > 0: + print(',') + print('{',end='') + print("\"Patch file\":" + '"' + file + '",') + print("\"Number of patches\":" + "\"" + str(len(patches[file])) + "\"", ',') + print("\"Patches\":" + '[') + for index, patch in enumerate(patches[file]): + if index > 0: + print(',') + print('{',end='') + print("\"Detector\":" + '"' + patch['detector'] + '",') + print("\"Old string\":" + '"' + patch['old_string'].replace("\n","") + '",') + print("\"New string\":" + '"' + patch['new_string'].replace("\n","") + '",') + print("\"Location start\":" + '"' + str(patch['start']) + '",') + print("\"Location end\":" + '"' + str(patch['end']) + '"') + if 'overlaps' in patch: + print("\"Overlaps\":" + "Yes") + print('}',end='') + print(']',end='') + print('}',end='') + print(']',end='') + print('}') + + diff --git a/slither/tools/upgradeability/__init__.py b/slither/tools/upgradeability/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/upgradeability/__main__.py b/slither/tools/upgradeability/__main__.py new file mode 100644 index 000000000..df43a7dfb --- /dev/null +++ b/slither/tools/upgradeability/__main__.py @@ -0,0 +1,194 @@ +import logging +import argparse +import sys +from collections import defaultdict + +from slither import Slither +from crytic_compile import cryticparser +from slither.exceptions import SlitherException +from slither.utils.colors import red +from slither.utils.output import output_to_json + +from .compare_variables_order import compare_variables_order +from .compare_function_ids import compare_function_ids +from .check_initialization import check_initialization +from .check_variable_initialization import check_variable_initialization +from .constant_checks import constant_conformance_check + +logging.basicConfig() +logger = logging.getLogger("Slither-check-upgradeability") +logger.setLevel(logging.INFO) + +ch = logging.StreamHandler() +ch.setLevel(logging.INFO) +formatter = logging.Formatter('%(message)s') +logger.addHandler(ch) +logger.handlers[0].setFormatter(formatter) +logger.propagate = False + +def parse_args(): + + parser = argparse.ArgumentParser(description='Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.', + usage="slither-check-upgradeability contract.sol ContractName") + + + parser.add_argument('contract.sol', help='Codebase to analyze') + parser.add_argument('ContractName', help='Contract name (logic contract)') + + parser.add_argument('--proxy-name', help='Proxy name') + parser.add_argument('--proxy-filename', help='Proxy filename (if different)') + + parser.add_argument('--new-contract-name', help='New contract name (if changed)') + parser.add_argument('--new-contract-filename', help='New implementation filename (if different)') + + parser.add_argument('--json', + help='Export the results as a JSON file ("--json -" to export to stdout)', + action='store', + default=False) + + cryticparser.init(parser) + + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + + return parser.parse_args() + + +################################################################################### +################################################################################### +# region +################################################################################### +################################################################################### + +def _checks_on_contract(contract, json_results): + """ + + :param contract: + :param json_results: + :return: + """ + json_results['check-initialization'][contract.name] = check_initialization(contract) + json_results['variable-initialization'][contract.name] = check_variable_initialization(contract) + + +def _checks_on_contract_update(contract_v1, contract_v2, json_results): + """ + + :param contract_v1: + :param contract_v2: + :param json_results: + :return: + """ + + ret = compare_variables_order(contract_v1, contract_v2) + json_results['compare-variables-order-implementation'][contract_v1.name][contract_v2.name] = ret + + json_results['constant_conformance'][contract_v1.name][contract_v2.name] = constant_conformance_check(contract_v1, + contract_v2) + + +def _checks_on_contract_and_proxy(contract, proxy, json_results, missing_variable_check=True): + """ + + :param contract: + :param proxy: + :param json_results: + :return: + """ + json_results['compare-function-ids'][contract.name] = compare_function_ids(contract, proxy) + json_results['compare-variables-order-proxy'][contract.name] = compare_variables_order(contract, + proxy, + missing_variable_check) + +# endregion +################################################################################### +################################################################################### +# region Main +################################################################################### +################################################################################### + + +def main(): + json_results = { + 'check-initialization': defaultdict(dict), + 'variable-initialization': defaultdict(dict), + 'compare-function-ids': defaultdict(dict), + 'compare-variables-order-implementation': defaultdict(dict), + 'compare-variables-order-proxy': defaultdict(dict), + 'constant_conformance': defaultdict(dict), + 'proxy-present': False, + 'contract_v2-present': False + } + + args = parse_args() + + v1_filename = vars(args)['contract.sol'] + + try: + v1 = Slither(v1_filename, **vars(args)) + + # Analyze logic contract + v1_name = args.ContractName + v1_contract = v1.get_contract_from_name(v1_name) + if v1_contract is None: + info = 'Contract {} not found in {}'.format(v1_name, v1.filename) + logger.error(red(info)) + if args.json: + output_to_json(args.json, str(info), {"upgradeability-check": json_results}) + return + + _checks_on_contract(v1_contract, json_results) + + # Analyze Proxy + proxy_contract = None + if args.proxy_name: + if args.proxy_filename: + proxy = Slither(args.proxy_filename, **vars(args)) + else: + proxy = v1 + + proxy_contract = proxy.get_contract_from_name(args.proxy_name) + if proxy_contract is None: + info = 'Proxy {} not found in {}'.format(args.proxy_name, proxy.filename) + logger.error(red(info)) + if args.json: + output_to_json(args.json, str(info), {"upgradeability-check": json_results}) + return + json_results['proxy-present'] = True + _checks_on_contract_and_proxy(v1_contract, proxy_contract, json_results) + + # Analyze new version + if args.new_contract_name: + if args.new_contract_filename: + v2 = Slither(args.new_contract_filename, **vars(args)) + else: + v2 = v1 + + v2_contract = v2.get_contract_from_name(args.new_contract_name) + if v2_contract is None: + info = 'New logic contract {} not found in {}'.format(args.new_contract_name, v2.filename) + logger.error(red(info)) + if args.json: + output_to_json(args.json, str(info), {"upgradeability-check": json_results}) + return + json_results['contract_v2-present'] = True + + if proxy_contract: + _checks_on_contract_and_proxy(v2_contract, + proxy_contract, + json_results, + missing_variable_check=False) + + _checks_on_contract_update(v1_contract, v2_contract, json_results) + + if args.json: + output_to_json(args.json, None, {"upgradeability-check": json_results}) + + except SlitherException as e: + logger.error(str(e)) + if args.json: + output_to_json(args.json, str(e), {"upgradeability-check": json_results}) + return + +# endregion diff --git a/slither/tools/upgradeability/check_initialization.py b/slither/tools/upgradeability/check_initialization.py new file mode 100644 index 000000000..4fe07801a --- /dev/null +++ b/slither/tools/upgradeability/check_initialization.py @@ -0,0 +1,138 @@ +import logging + +from slither.slithir.operations import InternalCall +from slither.utils.output import Output +from slither.utils.colors import red, yellow, green + +logger = logging.getLogger("Slither-check-upgradeability") + +class MultipleInitTarget(Exception): + pass + +def _get_initialize_functions(contract): + return [f for f in contract.functions if f.name == 'initialize' and f.is_implemented] + +def _get_all_internal_calls(function): + all_ir = function.all_slithir_operations() + return [i.function for i in all_ir if isinstance(i, InternalCall) and i.function_name == "initialize"] + + +def _get_most_derived_init(contract): + init_functions = [f for f in contract.functions if not f.is_shadowed and f.name == 'initialize'] + if len(init_functions) > 1: + if len([f for f in init_functions if f.contract_declarer == contract]) == 1: + return next((f for f in init_functions if f.contract_declarer == contract)) + raise MultipleInitTarget + if init_functions: + return init_functions[0] + return None + +def check_initialization(contract): + + results = { + 'Initializable-present': False, + 'Initializable-inherited': False, + 'Initializable.initializer()-present': False, + 'missing-initializer-modifier': [], + 'initialize_target': {}, + 'missing-calls': [], + 'multiple-calls': [] + } + + error_found = False + + logger.info(green( + '\n## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks)')) + + # Check if the Initializable contract is present + initializable = contract.slither.get_contract_from_name('Initializable') + if initializable is None: + logger.info(yellow('Initializable contract not found, the contract does not follow a standard initalization schema.')) + return results + results['Initializable-present'] = True + + # Check if the Initializable contract is inherited + if initializable not in contract.inheritance: + logger.info( + yellow('The logic contract does not call the initializer.')) + return results + results['Initializable-inherited'] = True + + # Check if the Initializable contract is inherited + initializer = contract.get_modifier_from_canonical_name('Initializable.initializer()') + if initializer is None: + logger.info( + yellow('Initializable.initializer() does not exist')) + return results + results['Initializable.initializer()-present'] = True + + # Check if a init function lacks the initializer modifier + initializer_modifier_missing = False + all_init_functions = _get_initialize_functions(contract) + for f in all_init_functions: + if not initializer in f.modifiers: + initializer_modifier_missing = True + info = f'{f.canonical_name} does not call the initializer modifier' + logger.info(red(info)) + res = Output(info) + res.add(f) + results['missing-initializer-modifier'].append(res.data) + + if not initializer_modifier_missing: + logger.info(green('All the init functions have the initializer modifier')) + + # Check if we can determine the initialize function that will be called + # TODO: handle MultipleInitTarget + try: + most_derived_init = _get_most_derived_init(contract) + except MultipleInitTarget: + logger.info(red('Too many init targets')) + return results + + if most_derived_init is None: + init_info = f'{contract.name} has no initialize function\n' + logger.info(green(init_info)) + results['initialize_target'] = {} + return results + # results['initialize_target'] is set at the end, as we want to print it last + + # Check if an initialize function is not called from the most_derived_init function + missing_call = False + all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init] + missing_calls = [f for f in all_init_functions if not f in all_init_functions_called] + for f in missing_calls: + info = f'Missing call to {f.canonical_name} in {most_derived_init.canonical_name}' + logger.info(red(info)) + res = Output(info) + res.add(f, {"is_most_derived_init_function": False}) + res.add(most_derived_init, {"is_most_derived_init_function": True}) + results['missing-calls'].append(res.data) + missing_call = True + if not missing_call: + logger.info(green('No missing call to an init function found')) + + # Check if an init function is called multiple times + double_calls = list(set([f for f in all_init_functions_called if all_init_functions_called.count(f) > 1])) + double_calls_found = False + for f in double_calls: + info = f'{f.canonical_name} is called multiple times in {most_derived_init.full_name}' + logger.info(red(info)) + res = Output(info) + res.add(f) + results['multiple-calls'].append(res.data) + double_calls_found = True + if not double_calls_found: + logger.info(green('No double call to init functions found')) + + # Print the initialize_target info + + init_info = f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n' + logger.info(green('Check the deployement script to ensure that these functions are called:\n' + init_info)) + res = Output(init_info) + res.add(most_derived_init) + results['initialize_target'] = res.data + + if not error_found: + logger.info(green('No error found')) + + return results diff --git a/slither/tools/upgradeability/check_variable_initialization.py b/slither/tools/upgradeability/check_variable_initialization.py new file mode 100644 index 000000000..cb2473f67 --- /dev/null +++ b/slither/tools/upgradeability/check_variable_initialization.py @@ -0,0 +1,31 @@ +import logging + +from slither.utils import output +from slither.utils.colors import red, green + +logger = logging.getLogger("Slither-check-upgradeability") + + +def check_variable_initialization(contract): + results = { + 'variables-initialized': [] + } + + logger.info(green( + '\n## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks)')) + + error_found = False + + for s in contract.state_variables: + if s.initialized and not s.is_constant: + info = f'{s.canonical_name} has an initial value ({s.source_mapping_str})' + logger.info(red(info)) + res = output.Output(info) + res.add(s) + results['variables-initialized'].append(res.data) + error_found = True + + if not error_found: + logger.info(green('No error found')) + + return results \ No newline at end of file diff --git a/slither/tools/upgradeability/compare_function_ids.py b/slither/tools/upgradeability/compare_function_ids.py new file mode 100644 index 000000000..34b63d8bf --- /dev/null +++ b/slither/tools/upgradeability/compare_function_ids.py @@ -0,0 +1,86 @@ +''' + Check for functions collisions between a proxy and the implementation + More for information: https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357 +''' + +import logging +from slither.core.declarations import Function +from slither.exceptions import SlitherError +from slither.utils.output import Output +from slither.utils.function import get_function_id +from slither.utils.colors import red, green + +logger = logging.getLogger("Slither-check-upgradeability") + +def get_signatures(c): + functions = c.functions + functions = [f.full_name for f in functions if f.visibility in ['public', 'external'] and + not f.is_constructor and not f.is_fallback] + + variables = c.state_variables + variables = [variable.name+ '()' for variable in variables if variable.visibility in ['public']] + return list(set(functions+variables)) + + +def _get_function_or_variable(contract, signature): + f = contract.get_function_from_signature(signature) + + if f: + return f + + for variable in contract.state_variables: + # Todo: can lead to incorrect variable in case of shadowing + if variable.visibility in ['public']: + if variable.name + '()' == signature: + return variable + + raise SlitherError(f'Function id checks: {signature} not found in {contract.name}') + +def compare_function_ids(implem, proxy): + + results = { + 'function-id-collision':[], + 'shadowing':[], + } + + logger.info(green('\n## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks)')) + + signatures_implem = get_signatures(implem) + signatures_proxy = get_signatures(proxy) + + signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} + signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} + + error_found = False + for (k, _) in signatures_ids_implem.items(): + if k in signatures_ids_proxy: + error_found = True + if signatures_ids_implem[k] != signatures_ids_proxy[k]: + + implem_function = _get_function_or_variable(implem, signatures_ids_implem[k]) + proxy_function = _get_function_or_variable(proxy, signatures_ids_proxy[k]) + + info = f'Function id collision found: {implem_function.canonical_name} ({implem_function.source_mapping_str}) {proxy_function.canonical_name} ({proxy_function.source_mapping_str})' + logger.info(red(info)) + res = Output(info) + res.add(implem_function) + res.add(proxy_function) + results['function-id-collision'].append(res.data) + + else: + + implem_function = _get_function_or_variable(implem, signatures_ids_implem[k]) + proxy_function = _get_function_or_variable(proxy, signatures_ids_proxy[k]) + + info = f'Shadowing between {implem_function.canonical_name} ({implem_function.source_mapping_str}) and {proxy_function.canonical_name} ({proxy_function.source_mapping_str})' + logger.info(red(info)) + + res = Output(info) + res.add(implem_function) + res.add(proxy_function) + results['shadowing'].append(res.data) + + if not error_found: + logger.info(green('No error found')) + + return results diff --git a/slither/tools/upgradeability/compare_variables_order.py b/slither/tools/upgradeability/compare_variables_order.py new file mode 100644 index 000000000..ea5852d3f --- /dev/null +++ b/slither/tools/upgradeability/compare_variables_order.py @@ -0,0 +1,73 @@ +''' + Check if the variables respect the same ordering +''' +import logging + +from slither.utils.output import Output +from slither.utils.colors import red, green, yellow + +logger = logging.getLogger("Slither-check-upgradeability") + + +def compare_variables_order(contract1, contract2, missing_variable_check=True): + + results = { + 'missing_variables': [], + 'different-variables': [], + 'extra-variables': [] + } + + logger.info(green( + f'\n## Run variables ordering checks between {contract1.name} and {contract2.name}... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks)')) + + order1 = [variable for variable in contract1.state_variables if not variable.is_constant] + order2 = [variable for variable in contract2.state_variables if not variable.is_constant] + + error_found = False + idx = 0 + for idx in range(0, len(order1)): + variable1 = order1[idx] + if len(order2) <= idx: + if missing_variable_check: + info = f'Variable only in {contract1.name}: {variable1.name} ({variable1.source_mapping_str})' + logger.info(yellow(info)) + + res = Output(info) + res.add(variable1) + results['missing_variables'].append(res.data) + + error_found = True + continue + + variable2 = order2[idx] + + if (variable1.name != variable2.name) or (variable1.type != variable2.type): + info = f'Different variables between {contract1.name} and {contract2.name}:\n' + info += f'\t Variable {idx} in {contract1.name}: {variable1.name} {variable1.type} ({variable1.source_mapping_str})\n' + info += f'\t Variable {idx} in {contract2.name}: {variable2.name} {variable2.type} ({variable2.source_mapping_str})\n' + logger.info(red(info)) + + res = Output(info, additional_fields={'index': idx}) + res.add(variable1) + res.add(variable2) + results['different-variables'].append(res.data) + + error_found = True + + idx = idx + 1 + + while idx < len(order2): + variable2 = order2[idx] + + info = f'Extra variables in {contract2.name}: {variable2.name} ({variable2.source_mapping_str})\n' + logger.info(yellow(info)) + res = Output(info, additional_fields={'index': idx}) + res.add(variable2) + results['extra-variables'].append(res.data) + idx = idx + 1 + + if not error_found: + logger.info(green('No error found')) + + return results + diff --git a/slither/tools/upgradeability/constant_checks.py b/slither/tools/upgradeability/constant_checks.py new file mode 100644 index 000000000..e25924350 --- /dev/null +++ b/slither/tools/upgradeability/constant_checks.py @@ -0,0 +1,85 @@ +import logging + +from slither.utils.output import Output +from slither.utils.colors import red, yellow, green + +logger = logging.getLogger("Slither-check-upgradeability") + +def constant_conformance_check(contract_v1, contract_v2): + + results = { + "became_constants": [], + "were_constants": [], + "not_found_in_v2": [], + } + + logger.info(green( + '\n## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks)')) + error_found = False + + state_variables_v1 = contract_v1.state_variables + state_variables_v2 = contract_v2.state_variables + + v2_additional_variables = len(state_variables_v2) - len(state_variables_v1) + if v2_additional_variables < 0: + v2_additional_variables = 0 + + # We keep two index, because we need to have them out of sync if v2 + # has additional non constant variables + idx_v1 = 0 + idx_v2 = 0 + while idx_v1 < len(state_variables_v1): + + state_v1 = contract_v1.state_variables[idx_v1] + if len(state_variables_v2) <= idx_v2: + break + + state_v2 = contract_v2.state_variables[idx_v2] + + if state_v2: + if state_v1.is_constant: + if not state_v2.is_constant: + + # If v2 has additional non constant variables, we need to skip them + if (state_v1.name != state_v2.name or state_v1.type != state_v2.type) and v2_additional_variables>0: + v2_additional_variables -= 1 + idx_v2 += 1 + continue + + info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was constant and {state_v2.canonical_name} is not ({state_v2.source_mapping_str})' + logger.info(red(info)) + + res = Output(info) + res.add(state_v1) + res.add(state_v2) + results['were_constants'].append(res.data) + error_found = True + + elif state_v2.is_constant: + info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was not constant but {state_v2.canonical_name} is ({state_v2.source_mapping_str})' + logger.info(red(info)) + + res = Output(info) + res.add(state_v1) + res.add(state_v2) + results['became_constants'].append(res.data) + error_found = True + + else: + info = f'{state_v1.canonical_name} not found in {contract_v2.name}, not check was done' + logger.info(yellow(info)) + + res = Output(info) + res.add(state_v1) + res.add(contract_v2) + results['not_found_in_v2'].append(res.data) + + error_found = True + + idx_v1 += 1 + idx_v2 += 1 + + if not error_found: + logger.info(green('No error found')) + + return results \ No newline at end of file diff --git a/slither/tools/utils/__init__.py b/slither/tools/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/utils/arithmetic.py b/slither/utils/arithmetic.py new file mode 100644 index 000000000..241eca28a --- /dev/null +++ b/slither/utils/arithmetic.py @@ -0,0 +1,32 @@ +from slither.exceptions import SlitherException + + +def convert_subdenomination(value, sub): + + # to allow 0.1 ether conversion + if value[0:2] == "0x": + value = float(int(value, 16)) + else: + value = float(value) + if sub == 'wei': + return int(value) + if sub == 'szabo': + return int(value * int(1e12)) + if sub == 'finney': + return int(value * int(1e15)) + if sub == 'ether': + return int(value * int(1e18)) + if sub == 'seconds': + return int(value) + if sub == 'minutes': + return int(value * 60) + if sub == 'hours': + return int(value * 60 * 60) + if sub == 'days': + return int(value * 60 * 60 * 24) + if sub == 'weeks': + return int(value * 60 * 60 * 24 * 7) + if sub == 'years': + return int(value * 60 * 60 * 24 * 7 * 365) + + raise SlitherException(f'Subdemonination conversion impossible {value} {sub}') \ No newline at end of file diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index d1a495a23..2ead86223 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -1,8 +1,57 @@ +import os +import logging import json +import os +import logging from collections import defaultdict from prettytable import PrettyTable +from crytic_compile.cryticparser.defaults import defaults_flag_in_config as defaults_flag_in_config_crytic_compile from slither.detectors.abstract_detector import classification_txt +from .colors import yellow, red + +logger = logging.getLogger("Slither") + +DEFAULT_JSON_OUTPUT_TYPES = ["detectors", "printers"] +JSON_OUTPUT_TYPES = ["compilations", "console", "detectors", "printers", "list-detectors", "list-printers"] + + +# Those are the flags shared by the command line and the config file +defaults_flag_in_config = { + 'detectors_to_run': 'all', + 'printers_to_run': None, + 'detectors_to_exclude': None, + 'exclude_dependencies': False, + 'exclude_informational': False, + 'exclude_optimization': False, + 'exclude_low': False, + 'exclude_medium': False, + 'exclude_high': False, + 'json': None, + 'json-types': ','.join(DEFAULT_JSON_OUTPUT_TYPES), + 'disable_color': False, + 'filter_paths': None, + 'generate_patches': False, + # debug command + 'legacy_ast': False, + 'ignore_return_value': False, + **defaults_flag_in_config_crytic_compile + } + +def read_config_file(args): + if os.path.isfile(args.config_file): + try: + with open(args.config_file) as f: + config = json.load(f) + for key, elem in config.items(): + if key not in defaults_flag_in_config: + logger.info(yellow('{} has an unknown key: {} : {}'.format(args.config_file, key, elem))) + continue + if getattr(args, key) == defaults_flag_in_config[key]: + setattr(args, key, elem) + except json.decoder.JSONDecodeError as e: + logger.error(red('Impossible to read {}, please check the file {}'.format(args.config_file, e))) + def output_to_markdown(detector_classes, printer_classes, filter_wiki): @@ -155,6 +204,7 @@ def output_detectors(detector_classes): idx = idx + 1 print(table) + def output_detectors_json(detector_classes): detectors_list = [] for detector in detector_classes: @@ -193,7 +243,7 @@ def output_detectors_json(detector_classes): 'exploit_scenario':exploit, 'recommendation':recommendation}) idx = idx + 1 - print(json.dumps(table)) + return table def output_printers(printer_classes): printers_list = [] @@ -212,3 +262,25 @@ def output_printers(printer_classes): table.add_row([idx, argument, help_info]) idx = idx + 1 print(table) + + +def output_printers_json(printer_classes): + printers_list = [] + for printer in printer_classes: + argument = printer.ARGUMENT + help_info = printer.HELP + + printers_list.append((argument, + help_info)) + + # Sort by name + printers_list = sorted(printers_list, key=lambda element: (element[0])) + idx = 1 + table = [] + for (argument, help_info) in printers_list: + table.append({'index': idx, + 'check': argument, + 'title': help_info}) + idx = idx + 1 + return table + diff --git a/slither/utils/erc.py b/slither/utils/erc.py index 21c2c53a5..4226cdf87 100644 --- a/slither/utils/erc.py +++ b/slither/utils/erc.py @@ -1,69 +1,134 @@ +from collections import namedtuple def erc_to_signatures(erc): - return [f'{e[0]}({",".join(e[1])})' for e in erc] + ''' + Return the list of mandatory signatures + :param erc: + :return: + ''' + return [f'{e.name}({",".join(e.parameters)})' for e in erc if e.required] +ERC = namedtuple('ERC', ['name', 'parameters', 'return_type', 'view', 'required', 'events']) + +ERC_EVENT = namedtuple('ERC_EVENT', ['name', 'parameters', "indexes"]) + # Final # https://eips.ethereum.org/EIPS/eip-20 -# name, symbolc, decimals are optionals -ERC20 = [('totalSupply', [], 'uint256'), - ('balanceOf', ['address'], 'uint256'), - ('transfer', ['address', 'uint256'], 'bool'), - ('transferFrom', ['address', 'address', 'uint256'], 'bool'), - ('approve', ['address', 'uint256'], 'bool'), - ('allowance', ['address', 'address'], 'uint256')] + +ERC20_transfer_event = ERC_EVENT("Transfer", ["address", "address", "uint256"], [True, True, False]) +ERC20_approval_event = ERC_EVENT("Approval", ["address", "address", "uint256"], [True, True, False]) +ERC20_EVENTS = [ERC20_transfer_event, ERC20_approval_event] + +ERC20 = [ERC('totalSupply', [], 'uint256', True, True, []), + ERC('balanceOf', ['address'], 'uint256', True, True, []), + ERC('transfer', ['address', 'uint256'], 'bool', False, True, [ERC20_transfer_event]), + ERC('transferFrom', ['address', 'address', 'uint256'], 'bool', False, True, [ERC20_transfer_event]), + ERC('approve', ['address', 'uint256'], 'bool', False, True, [ERC20_approval_event]), + ERC('allowance', ['address', 'address'], 'uint256', True, True, [])] + +ERC20_OPTIONAL = [ERC('name', [], 'string', True, False, []), + ERC('symbol', [], 'string', True, False, []), + ERC('decimals', [], 'uint8', True, False, [])] + +ERC20 = ERC20 + ERC20_OPTIONAL + ERC20_signatures = erc_to_signatures(ERC20) # Draft # https://github.com/ethereum/eips/issues/223 -ERC223 = [('name', [], 'string'), - ('symbol', [], 'string'), - ('decimals', [], 'uint8'), - ('totalSupply', [], 'uint256'), - ('balanceOf', ['address'], 'uint256'), - ('transfer', ['address', 'uint256'], 'bool'), - ('transfer', ['address', 'uint256', 'bytes'], 'bool'), - ('transfer', ['address', 'uint256', 'bytes', 'string'], 'bool')] + +ERC223_transfer_event = ERC_EVENT("Transfer", ["address", "address", "uint256", "bytes"], [True, True, False, False]) + +ERC223_EVENTS = [ERC223_transfer_event] + +ERC223 = [ERC('name', [], 'string', True, True, []), + ERC('symbol', [], 'string', True, True, []), + ERC('decimals', [], 'uint8', True, True, []), + ERC('totalSupply', [], 'uint256', True, True, []), + ERC('balanceOf', ['address'], 'uint256', True, True, []), + ERC('transfer', ['address', 'uint256'], 'bool', False, True, [ERC223_transfer_event]), + ERC('transfer', ['address', 'uint256', 'bytes'], 'bool', False, True, [ERC223_transfer_event]), + ERC('transfer', ['address', 'uint256', 'bytes', 'string'], 'bool', False, True, [ERC223_transfer_event])] ERC223_signatures = erc_to_signatures(ERC223) # Final # https://eips.ethereum.org/EIPS/eip-165 -ERC165 = [('supportsInterface', ['bytes4'], 'bool')] + +ERC165_EVENTS = [] + +ERC165 = [ERC('supportsInterface', ['bytes4'], 'bool', True, True, [])] ERC165_signatures = erc_to_signatures(ERC165) # Final # https://eips.ethereum.org/EIPS/eip-721 # Must have ERC165 -# name, symbol, tokenURI are optionals -ERC721 = [('balanceOf', ['address'], 'uint256'), - ('ownerOf', ['uint256'], 'address'), - ('safeTransferFrom', ['address', 'address', 'uint256', 'bytes'], ''), - ('safeTransferFrom', ['address', 'address', 'uint256'], ''), - ('transferFrom', ['address', 'address', 'uint256'], ''), - ('approve', ['address', 'uint256'], ''), - ('setApprovalForAll', ['address', 'bool'], ''), - ('getApproved', ['uint256'], 'address'), - ('isApprovedForAll', ['address', 'address'], 'bool')] + ERC165 + +ERC721_transfer_event = ERC_EVENT("Transfer", ["address", "address", "uint256"], [True, True, True]) +ERC721_approval_event = ERC_EVENT("Approval", ["address", "address", "uint256"], [True, True, True]) +ERC721_approvalforall_event = ERC_EVENT("ApprovalForAll", ["address", "address", "bool"], [True, True, False]) +ERC721_EVENTS = [ERC721_transfer_event, ERC721_approval_event, ERC721_approvalforall_event] + +ERC721 = [ERC('balanceOf', ['address'], 'uint256', True, True, []), + ERC('ownerOf', ['uint256'], 'address', True, True, []), + ERC('safeTransferFrom', ['address', 'address', 'uint256', 'bytes'], '', False, True, [ERC721_transfer_event]), + ERC('safeTransferFrom', ['address', 'address', 'uint256'], '', False, True, [ERC721_transfer_event]), + ERC('transferFrom', ['address', 'address', 'uint256'], '', False, True, [ERC721_transfer_event]), + ERC('approve', ['address', 'uint256'], '', False, True, [ERC721_approval_event]), + ERC('setApprovalForAll', ['address', 'bool'], '', False, True, [ERC721_approvalforall_event]), + ERC('getApproved', ['uint256'], 'address', True, True, []), + ERC('isApprovedForAll', ['address', 'address'], 'bool', True, True, [])] + ERC165 + +ERC721_OPTIONAL = [ERC('name', [], 'string', True, False, []), + ERC('symbol', [], 'string', False, False, []), + ERC('tokenURI', ['uint256'], 'string', False, False, [])] + +ERC721 = ERC721 + ERC721_OPTIONAL + ERC721_signatures = erc_to_signatures(ERC721) # Final # https://eips.ethereum.org/EIPS/eip-1820 -ERC1820 = [('canImplementInterfaceForAddress', ['bytes32', 'address'], 'bytes32')] +ERC1820_EVENTS = [] +ERC1820 = [ERC('canImplementInterfaceForAddress', ['bytes32', 'address'], 'bytes32', True, True, [])] ERC1820_signatures = erc_to_signatures(ERC1820) # Last Call # https://eips.ethereum.org/EIPS/eip-777 -ERC777 = [('name', [], 'string'), - ('symbol', [], 'string'), - ('totalSupply', [], 'uint256'), - ('balanceOf', ['address'], 'uint256'), - ('granularity', [], 'uint256'), - ('defaultOperators', [], 'address[]'), - ('isOperatorFor', ['address', 'address'], 'bool'), - ('authorizeOperator', ['address'], ''), - ('revokeOperator', ['address'], ''), - ('send', ['address', 'uint256', 'bytes'], ''), - ('operatorSend', ['address', 'address', 'uint256', 'bytes', 'bytes'], ''), - ('burn', ['uint256', 'bytes'] , ''), - ('operatorBurn', ['address', 'uint256', 'bytes', 'bytes'] , '')] -ERC777_signatures = erc_to_signatures(ERC777) \ No newline at end of file +ERC777_sent_event = ERC_EVENT('Sent', + ["address", "address", "address", "uint256", "bytes", "bytes"], + [True, True, True, False, False, False]) +ERC777_minted_event = ERC_EVENT('Minted', + ["address", "address", "uint256", "bytes", "bytes"], + [True, True, False, False, False]) +ERC777_burned_event = ERC_EVENT('Burned', + ["address", "address", "uint256", "bytes", "bytes"], + [True, True, False, False, False]) +ERC777_authorizedOperator_event = ERC_EVENT('AuthorizedOperator', ["address", "address"], [True, True]) +ERC777_revokedoperator_event = ERC_EVENT('RevokedOperator', ["address", "address"], [True, True]) +ERC777_EVENTS = [ERC777_sent_event, ERC777_minted_event, ERC777_burned_event, + ERC777_authorizedOperator_event, ERC777_revokedoperator_event] + +ERC777 = [ERC('name', [], 'string', True, True, []), + ERC('symbol', [], 'string', True, True, []), + ERC('totalSupply', [], 'uint256', True, True, []), + ERC('balanceOf', ['address'], 'uint256', True, True, []), + ERC('granularity', [], 'uint256', True, True, []), + ERC('defaultOperators', [], 'address[]', True, True, []), + ERC('isOperatorFor', ['address', 'address'], 'bool', True, True, []), + ERC('authorizeOperator', ['address'], '', False, True, [ERC777_authorizedOperator_event]), + ERC('revokeOperator', ['address'], '', False, True, [ERC777_revokedoperator_event]), + ERC('send', ['address', 'uint256', 'bytes'], '', False, True, [ERC777_sent_event]), + ERC('operatorSend', ['address', 'address', 'uint256', 'bytes', 'bytes'], '', False, True, [ERC777_sent_event]), + ERC('burn', ['uint256', 'bytes'] , '', False, True, [ERC777_burned_event]), + ERC('operatorBurn', ['address', 'uint256', 'bytes', 'bytes'], '', False, True, [ERC777_burned_event])] +ERC777_signatures = erc_to_signatures(ERC777) + +ERCS = { + "ERC20": (ERC20, ERC20_EVENTS), + "ERC223": (ERC223, ERC223_EVENTS), + "ERC165": (ERC165, ERC165_EVENTS), + "ERC721": (ERC721, ERC721_EVENTS), + "ERC1820": (ERC1820, ERC1820_EVENTS), + "ERC777": (ERC777, ERC777_EVENTS), +} \ No newline at end of file diff --git a/slither/utils/function.py b/slither/utils/function.py index 7e142e342..a7448d48e 100644 --- a/slither/utils/function.py +++ b/slither/utils/function.py @@ -1,4 +1,3 @@ -import hashlib import sha3 def get_function_id(sig): diff --git a/slither/utils/inheritance_analysis.py b/slither/utils/inheritance_analysis.py index 013de531e..11f6e19d7 100644 --- a/slither/utils/inheritance_analysis.py +++ b/slither/utils/inheritance_analysis.py @@ -2,6 +2,7 @@ Detects various properties of inheritance in provided contracts. """ +from collections import defaultdict def detect_c3_function_shadowing(contract): """ @@ -9,112 +10,21 @@ def detect_c3_function_shadowing(contract): properties, despite not directly inheriting from each other. :param contract: The contract to check for potential C3 linearization shadowing within. - :return: A list of list of tuples: (contract, function), where each inner list describes colliding functions. - The later elements in the inner list overshadow the earlier ones. The contract-function pair's function does not - need to be defined in its paired contract, it may have been inherited within it. + :return: A dict (function winner -> [shadowed functions]) """ - # Loop through all contracts, and all underlying functions. - results = {} - for i in range(0, len(contract.immediate_inheritance) - 1): - inherited_contract1 = contract.immediate_inheritance[i] + targets = {f.full_name:f for f in contract.functions_inherited if f.shadows and not f.is_constructor + and not f.is_constructor_variables} - for function1 in inherited_contract1.functions_and_modifiers_declared: - # If this function has already be handled or is unimplemented, we skip it - if function1.full_name in results or function1.is_constructor or not function1.is_implemented: - continue - - # Define our list of function instances which overshadow each other. - functions_matching = [(inherited_contract1, function1)] - already_processed = set([function1]) - - # Loop again through other contracts and functions to compare to. - for x in range(i + 1, len(contract.immediate_inheritance)): - inherited_contract2 = contract.immediate_inheritance[x] - - # Loop for each function in this contract - for function2 in inherited_contract2.functions_and_modifiers: - # Skip this function if it is the last function that was shadowed. - if function2 in already_processed or function2.is_constructor or not function2.is_implemented: - continue - - # If this function does have the same full name, it is shadowing through C3 linearization. - if function1.full_name == function2.full_name: - functions_matching.append((inherited_contract2, function2)) - already_processed.add(function2) - - # If we have more than one definition matching the same signature, we add it to the results. - if len(functions_matching) > 1: - results[function1.full_name] = functions_matching - - return list(results.values()) + collisions = defaultdict(set) - -def detect_direct_function_shadowing(contract): - """ - Detects and obtains functions which are shadowed immediately by the provided ancestor contract. - :param contract: The ancestor contract which we check for function shadowing within. - :return: A list of tuples (overshadowing_function, overshadowed_immediate_base_contract, overshadowed_function) - -overshadowing_function is the function defined within the provided contract that overshadows another - definition. - -overshadowed_immediate_base_contract is the immediate inherited-from contract that provided the shadowed - function (could have provided it through inheritance, does not need to directly define it). - -overshadowed_function is the function definition which is overshadowed by the provided contract's definition. - """ - functions_declared = {function.full_name: function for function in contract.functions_and_modifiers_declared} - results = {} - for base_contract in reversed(contract.immediate_inheritance): - for base_function in base_contract.functions_and_modifiers: - - # We already found the most immediate shadowed definition for this function, skip to the next. - if base_function.full_name in results: + for contract_inherited in contract.immediate_inheritance: + for candidate in contract_inherited.functions: + if candidate.full_name not in targets or candidate.is_shadowed: continue - - # If this function is implemented and it collides with a definition in our immediate contract, we add - # it to our results. - if base_function.is_implemented and base_function.full_name in functions_declared: - results[base_function.full_name] = (functions_declared[base_function.full_name], base_contract, base_function) - - return list(results.values()) - - -def detect_function_shadowing(contracts, direct_shadowing=True, indirect_shadowing=True): - """ - Detects all overshadowing and overshadowed functions in the provided contracts. - :param contracts: The contracts to detect shadowing within. - :param direct_shadowing: Include results from direct inheritance/inheritance ancestry. - :param indirect_shadowing: Include results from indirect inheritance collisions as a result of multiple - inheritance/c3 linearization. - :return: Returns a set of tuples(contract_scope, overshadowing_contract, overshadowing_function, - overshadowed_contract, overshadowed_function), where: - -The contract_scope defines where the detection of shadowing is most immediately found. - -For each contract-function pair, contract is the first contract where the function is seen, while the function - refers to the actual definition. The function does not need to be defined in the contract (could be inherited). - """ - results = set() - for contract in contracts: - - # Detect immediate inheritance shadowing. - if direct_shadowing: - shadows = detect_direct_function_shadowing(contract) - for (overshadowing_function, overshadowed_base_contract, overshadowed_function) in shadows: - results.add((contract, contract, overshadowing_function, overshadowed_base_contract, - overshadowed_function)) - - # Detect c3 linearization shadowing (multi inheritance shadowing). - if indirect_shadowing: - shadows = detect_c3_function_shadowing(contract) - for colliding_functions in shadows: - for x in range(0, len(colliding_functions) - 1): - for y in range(x + 1, len(colliding_functions)): - # The same function definition can appear more than once in the inheritance chain, - # overshadowing items between, so it is important to remember to filter it out here. - if colliding_functions[y][1].contract_declarer != colliding_functions[x][1].contract_declarer: - results.add((contract, colliding_functions[y][0], colliding_functions[y][1], - colliding_functions[x][0], colliding_functions[x][1])) - - return results - + if targets[candidate.full_name].canonical_name != candidate.canonical_name: + collisions[targets[candidate.full_name]].add(candidate) + return collisions def detect_state_variable_shadowing(contracts): """ diff --git a/slither/utils/output.py b/slither/utils/output.py new file mode 100644 index 000000000..01132c5a9 --- /dev/null +++ b/slither/utils/output.py @@ -0,0 +1,483 @@ +import hashlib +import os +import json +import logging +from collections import OrderedDict + +from slither.core.cfg.node import Node +from slither.core.declarations import Contract, Function, Enum, Event, Structure, Pragma +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables.variable import Variable +from slither.exceptions import SlitherError +from slither.utils.colors import yellow + +logger = logging.getLogger("Slither") + + +################################################################################### +################################################################################### +# region Output +################################################################################### +################################################################################### + +def output_to_json(filename, error, results): + """ + + :param filename: Filename where the json will be written. If None or "-", write to stdout + :param error: Error to report + :param results: Results to report + :param logger: Logger where to log potential info + :return: + """ + # Create our encapsulated JSON result. + json_result = { + "success": error is None, + "error": error, + "results": results + } + + if filename == "-": + filename = None + + # Determine if we should output to stdout + if filename is None: + # Write json to console + print(json.dumps(json_result)) + else: + # Write json to file + if os.path.isfile(filename): + logger.info(yellow(f'{filename} exists already, the overwrite is prevented')) + else: + with open(filename, 'w', encoding='utf8') as f: + json.dump(json_result, f, indent=2) + + +# endregion +################################################################################### +################################################################################### +# region Json generation +################################################################################### +################################################################################### + +def _convert_to_description(d): + if isinstance(d, str): + return d + + if not isinstance(d, SourceMapping): + raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible') + + if isinstance(d, Node): + if d.expression: + return f'{d.expression} ({d.source_mapping_str})' + else: + return f'{str(d)} ({d.source_mapping_str})' + + if hasattr(d, 'canonical_name'): + return f'{d.canonical_name} ({d.source_mapping_str})' + + if hasattr(d, 'name'): + return f'{d.name} ({d.source_mapping_str})' + + raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name') + + +def _convert_to_markdown(d, markdown_root): + if isinstance(d, str): + return d + + if not isinstance(d, SourceMapping): + raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible') + + if isinstance(d, Node): + if d.expression: + return f'[{d.expression}]({d.source_mapping_to_markdown(markdown_root)})' + else: + return f'[{str(d)}]({d.source_mapping_to_markdown(markdown_root)})' + + if hasattr(d, 'canonical_name'): + return f'[{d.canonical_name}]({d.source_mapping_to_markdown(markdown_root)})' + + if hasattr(d, 'name'): + return f'[{d.name}]({d.source_mapping_to_markdown(markdown_root)})' + + raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name') + +def _convert_to_id(d): + ''' + Id keeps the source mapping of the node, otherwise we risk to consider two different node as the same + :param d: + :return: + ''' + if isinstance(d, str): + return d + + if not isinstance(d, SourceMapping): + raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible') + + if isinstance(d, Node): + if d.expression: + return f'{d.expression} ({d.source_mapping_str})' + else: + return f'{str(d)} ({d.source_mapping_str})' + + if hasattr(d, 'canonical_name'): + return f'{d.canonical_name}' + + if hasattr(d, 'name'): + return f'{d.name}' + + raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name') + +# endregion +################################################################################### +################################################################################### +# region Internal functions +################################################################################### +################################################################################### + +def _create_base_element(type, name, source_mapping, type_specific_fields=None, additional_fields=None): + if additional_fields is None: + additional_fields = {} + if type_specific_fields is None: + type_specific_fields = {} + element = {'type': type, + 'name': name, + 'source_mapping': source_mapping} + if type_specific_fields: + element['type_specific_fields'] = type_specific_fields + if additional_fields: + element['additional_fields'] = additional_fields + return element + + +def _create_parent_element(element): + from slither.core.children.child_contract import ChildContract + from slither.core.children.child_function import ChildFunction + from slither.core.children.child_inheritance import ChildInheritance + if isinstance(element, ChildInheritance): + if element.contract_declarer: + contract = Output('') + contract.add_contract(element.contract_declarer) + return contract.data['elements'][0] + elif isinstance(element, ChildContract): + if element.contract: + contract = Output('') + contract.add_contract(element.contract) + return contract.data['elements'][0] + elif isinstance(element, ChildFunction): + if element.function: + function = Output('') + function.add_function(element.function) + return function.data['elements'][0] + return None + + +class Output: + + def __init__(self, info, additional_fields=None, markdown_root='', standard_format=True): + if additional_fields is None: + additional_fields = {} + + # Allow info to be a string to simplify the API + if isinstance(info, str): + info = [info] + + self._data = OrderedDict() + self._data['elements'] = [] + self._data['description'] = ''.join(_convert_to_description(d) for d in info) + self._data['markdown'] = ''.join(_convert_to_markdown(d, markdown_root) for d in info) + + id_txt = ''.join(_convert_to_id(d) for d in info) + self._data['id'] = hashlib.sha3_256(id_txt.encode('utf-8')).hexdigest() + + if standard_format: + to_add = [i for i in info if not isinstance(i, str)] + + for add in to_add: + self.add(add) + + if additional_fields: + self._data['additional_fields'] = additional_fields + + + def add(self, add, additional_fields=None): + if isinstance(add, Variable): + self.add_variable(add, additional_fields=additional_fields) + elif isinstance(add, Contract): + self.add_contract(add, additional_fields=additional_fields) + elif isinstance(add, Function): + self.add_function(add, additional_fields=additional_fields) + elif isinstance(add, Enum): + self.add_enum(add, additional_fields=additional_fields) + elif isinstance(add, Event): + self.add_event(add, additional_fields=additional_fields) + elif isinstance(add, Structure): + self.add_struct(add, additional_fields=additional_fields) + elif isinstance(add, Pragma): + self.add_pragma(add, additional_fields=additional_fields) + elif isinstance(add, Node): + self.add_node(add, additional_fields=additional_fields) + else: + raise SlitherError(f'Impossible to add {type(add)} to the json') + + @property + def data(self): + return self._data + + @property + def elements(self): + return self._data['elements'] + + # endregion + ################################################################################### + ################################################################################### + # region Variables + ################################################################################### + ################################################################################### + + def add_variable(self, variable, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'parent': _create_parent_element(variable) + } + element = _create_base_element('variable', + variable.name, + variable.source_mapping, + type_specific_fields, + additional_fields) + self._data['elements'].append(element) + + def add_variables(self, variables): + for variable in sorted(variables, key=lambda x: x.name): + self.add_variable(variable) + + # endregion + ################################################################################### + ################################################################################### + # region Contract + ################################################################################### + ################################################################################### + + def add_contract(self, contract, additional_fields=None): + if additional_fields is None: + additional_fields = {} + element = _create_base_element('contract', + contract.name, + contract.source_mapping, + {}, + additional_fields) + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region Functions + ################################################################################### + ################################################################################### + + def add_function(self, function, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'parent': _create_parent_element(function), + 'signature': function.full_name + } + element = _create_base_element('function', + function.name, + function.source_mapping, + type_specific_fields, + additional_fields) + self._data['elements'].append(element) + + def add_functions(self, functions, additional_fields=None): + if additional_fields is None: + additional_fields = {} + for function in sorted(functions, key=lambda x: x.name): + self.add_function(function, additional_fields) + + # endregion + ################################################################################### + ################################################################################### + # region Enum + ################################################################################### + ################################################################################### + + def add_enum(self, enum, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'parent': _create_parent_element(enum) + } + element = _create_base_element('enum', + enum.name, + enum.source_mapping, + type_specific_fields, + additional_fields) + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region Structures + ################################################################################### + ################################################################################### + + def add_struct(self, struct, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'parent': _create_parent_element(struct) + } + element = _create_base_element('struct', + struct.name, + struct.source_mapping, + type_specific_fields, + additional_fields) + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region Events + ################################################################################### + ################################################################################### + + def add_event(self, event, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'parent': _create_parent_element(event), + 'signature': event.full_name + } + element = _create_base_element('event', + event.name, + event.source_mapping, + type_specific_fields, + additional_fields) + + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region Nodes + ################################################################################### + ################################################################################### + + def add_node(self, node, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'parent': _create_parent_element(node), + } + node_name = str(node.expression) if node.expression else "" + element = _create_base_element('node', + node_name, + node.source_mapping, + type_specific_fields, + additional_fields) + self._data['elements'].append(element) + + def add_nodes(self, nodes): + for node in sorted(nodes, key=lambda x: x.node_id): + self.add_node(node) + + # endregion + ################################################################################### + ################################################################################### + # region Pragma + ################################################################################### + ################################################################################### + + def add_pragma(self, pragma, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'directive': pragma.directive + } + element = _create_base_element('pragma', + pragma.version, + pragma.source_mapping, + type_specific_fields, + additional_fields) + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region File + ################################################################################### + ################################################################################### + + def add_file(self, filename, content, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'filename': filename, + 'content': content + } + element = _create_base_element('file', + type_specific_fields, + additional_fields) + + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region Pretty Table + ################################################################################### + ################################################################################### + + def add_pretty_table(self, content, name, additional_fields=None): + if additional_fields is None: + additional_fields = {} + type_specific_fields = { + 'content': content, + 'name': name + } + element = _create_base_element('pretty_table', + type_specific_fields, + additional_fields) + + self._data['elements'].append(element) + + # endregion + ################################################################################### + ################################################################################### + # region Others + ################################################################################### + ################################################################################### + + def add_other(self, name, source_mapping, slither, additional_fields=None): + # If this a tuple with (filename, start, end), convert it to a source mapping. + if additional_fields is None: + additional_fields = {} + if isinstance(source_mapping, tuple): + # Parse the source id + (filename, start, end) = source_mapping + source_id = next( + (source_unit_id for (source_unit_id, source_unit_filename) in slither.source_units.items() if + source_unit_filename == filename), -1) + + # Convert to a source mapping string + source_mapping = f"{start}:{end}:{source_id}" + + # If this is a source mapping string, parse it. + if isinstance(source_mapping, str): + source_mapping_str = source_mapping + source_mapping = SourceMapping() + source_mapping.set_offset(source_mapping_str, slither) + + # If this is a source mapping object, get the underlying source mapping dictionary + if isinstance(source_mapping, SourceMapping): + source_mapping = source_mapping.source_mapping + + # Create the underlying element and add it to our resulting json + element = _create_base_element('other', + name, + source_mapping, + {}, + additional_fields) + self._data['elements'].append(element) diff --git a/slither/utils/output_capture.py b/slither/utils/output_capture.py new file mode 100644 index 000000000..d557ede1e --- /dev/null +++ b/slither/utils/output_capture.py @@ -0,0 +1,90 @@ +import io +import logging +import sys + + +class CapturingStringIO(io.StringIO): + """ + I/O implementation which captures output, and optionally mirrors it to the original I/O stream it replaces. + """ + def __init__(self, original_io=None): + super(CapturingStringIO, self).__init__() + self.original_io = original_io + + def write(self, s): + super().write(s) + if self.original_io: + self.original_io.write(s) + + +class StandardOutputCapture: + """ + Redirects and captures standard output/errors. + """ + original_stdout = None + original_stderr = None + original_logger_handlers = None + + @staticmethod + def enable(block_original=True): + """ + Redirects stdout and stderr to a capturable StringIO. + :param block_original: If True, blocks all output to the original stream. If False, duplicates output. + :return: None + """ + # Redirect stdout + if StandardOutputCapture.original_stdout is None: + StandardOutputCapture.original_stdout = sys.stdout + sys.stdout = CapturingStringIO(None if block_original else StandardOutputCapture.original_stdout) + + # Redirect stderr + if StandardOutputCapture.original_stderr is None: + StandardOutputCapture.original_stderr = sys.stderr + sys.stderr = CapturingStringIO(None if block_original else StandardOutputCapture.original_stderr) + + # Backup and swap root logger handlers + root_logger = logging.getLogger() + StandardOutputCapture.original_logger_handlers = root_logger.handlers + root_logger.handlers = [logging.StreamHandler(sys.stderr)] + + @staticmethod + def disable(): + """ + Disables redirection of stdout/stderr, if previously enabled. + :return: None + """ + # If we have a stdout backup, restore it. + if StandardOutputCapture.original_stdout is not None: + sys.stdout.close() + sys.stdout = StandardOutputCapture.original_stdout + StandardOutputCapture.original_stdout = None + + # If we have an stderr backup, restore it. + if StandardOutputCapture.original_stderr is not None: + sys.stderr.close() + sys.stderr = StandardOutputCapture.original_stderr + StandardOutputCapture.original_stderr = None + + # Restore our logging handlers + if StandardOutputCapture.original_logger_handlers is not None: + root_logger = logging.getLogger() + root_logger.handlers = StandardOutputCapture.original_logger_handlers + StandardOutputCapture.original_logger_handlers = None + + @staticmethod + def get_stdout_output(): + """ + Obtains the output from the currently set stdout + :return: Returns stdout output as a string + """ + sys.stdout.seek(0) + return sys.stdout.read() + + @staticmethod + def get_stderr_output(): + """ + Obtains the output from the currently set stderr + :return: Returns stderr output as a string + """ + sys.stderr.seek(0) + return sys.stderr.read() diff --git a/slither/utils/standard_libraries.py b/slither/utils/standard_libraries.py index c73a29b93..e138e6c35 100644 --- a/slither/utils/standard_libraries.py +++ b/slither/utils/standard_libraries.py @@ -13,6 +13,24 @@ libraries = { 'Dapphub-DSToken': lambda x: is_dapphub_ds_token(x), 'Dapphub-DSProxy': lambda x: is_dapphub_ds_proxy(x), 'Dapphub-DSGroup': lambda x: is_dapphub_ds_group(x), + 'AragonOS-SafeMath': lambda x: is_aragonos_safemath(x), + 'AragonOS-ERC20': lambda x: is_aragonos_erc20(x), + 'AragonOS-AppProxyBase': lambda x: is_aragonos_app_proxy_base(x), + 'AragonOS-AppProxyPinned': lambda x: is_aragonos_app_proxy_pinned(x), + 'AragonOS-AppProxyUpgradeable': lambda x: is_aragonos_app_proxy_upgradeable(x), + 'AragonOS-AppStorage': lambda x: is_aragonos_app_storage(x), + 'AragonOS-AragonApp': lambda x: is_aragonos_aragon_app(x), + 'AragonOS-UnsafeAragonApp': lambda x: is_aragonos_unsafe_aragon_app(x), + 'AragonOS-Autopetrified': lambda x: is_aragonos_autopetrified(x), + 'AragonOS-DelegateProxy': lambda x: is_aragonos_delegate_proxy(x), + 'AragonOS-DepositableDelegateProxy': lambda x: is_aragonos_depositable_delegate_proxy(x), + 'AragonOS-DepositableStorage': lambda x: is_aragonos_delegate_proxy(x), + 'AragonOS-Initializable': lambda x: is_aragonos_initializable(x), + 'AragonOS-IsContract': lambda x: is_aragonos_is_contract(x), + 'AragonOS-Petrifiable': lambda x: is_aragonos_petrifiable(x), + 'AragonOS-ReentrancyGuard': lambda x: is_aragonos_reentrancy_guard(x), + 'AragonOS-TimeHelpers': lambda x: is_aragonos_time_helpers(x), + 'AragonOS-VaultRecoverable': lambda x: is_aragonos_vault_recoverable(x) } def is_standard_library(contract): @@ -41,6 +59,12 @@ def is_zos(contract): return 'zos-lib' in Path(contract.source_mapping['filename_absolute']).parts +def is_aragonos(contract): + if not contract.is_from_dependency(): + return False + return '@aragon/os' in Path(contract.source_mapping['filename_absolute']).parts + + # endregion ################################################################################### ################################################################################### @@ -56,6 +80,11 @@ def is_safemath(contract): def is_openzepellin_safemath(contract): return is_safemath(contract) and is_openzepellin(contract) + +def is_aragonos_safemath(contract): + return is_safemath(contract) and is_aragonos(contract) + + # endregion ################################################################################### ################################################################################### @@ -104,6 +133,10 @@ def is_openzepellin_erc20(contract): return is_erc20(contract) and is_openzepellin(contract) +def is_aragonos_erc20(contract): + return is_erc20(contract) and is_aragonos(contract) + + # endregion ################################################################################### ################################################################################### @@ -191,3 +224,61 @@ def is_ds_group(contract): def is_dapphub_ds_group(contract): return _is_dappdhub_ds(contract, 'DSGroup') + +# endregion +################################################################################### +################################################################################### +# region Aragon +################################################################################### +################################################################################### + +def is_aragonos_app_proxy_base(contract): + return contract.name == "AppProxyBase" and is_aragonos(contract) + +def is_aragonos_app_proxy_pinned(contract): + return contract.name == "AppProxyPinned" and is_aragonos(contract) + +def is_aragonos_app_proxy_upgradeable(contract): + return contract.name == "AppProxyUpgradeable" and is_aragonos(contract) + +def is_aragonos_app_storage(contract): + return contract.name == "AppStorage" and is_aragonos(contract) + +def is_aragonos_aragon_app(contract): + return contract.name == "AragonApp" and is_aragonos(contract) + +def is_aragonos_unsafe_aragon_app(contract): + return contract.name == "UnsafeAragonApp" and is_aragonos(contract) + +def is_aragonos_autopetrified(contract): + return contract.name == "Autopetrified" and is_aragonos(contract) + +def is_aragonos_delegate_proxy(contract): + return contract.name == "DelegateProxy" and is_aragonos(contract) + +def is_aragonos_depositable_delegate_proxy(contract): + return contract.name == "DepositableDelegateProxy" and is_aragonos(contract) + +def is_aragonos_depositable_storage(contract): + return contract.name == "DepositableStorage" and is_aragonos(contract) + +def is_aragonos_ether_token_contract(contract): + return contract.name == "EtherTokenConstant" and is_aragonos(contract) + +def is_aragonos_initializable(contract): + return contract.name == "Initializable" and is_aragonos(contract) + +def is_aragonos_is_contract(contract): + return contract.name == "IsContract" and is_aragonos(contract) + +def is_aragonos_petrifiable(contract): + return contract.name == "Petrifiable" and is_aragonos(contract) + +def is_aragonos_reentrancy_guard(contract): + return contract.name == "ReentrancyGuard" and is_aragonos(contract) + +def is_aragonos_time_helpers(contract): + return contract.name == "TimeHelpers" and is_aragonos(contract) + +def is_aragonos_vault_recoverable(contract): + return contract.name == "VaultRecoverable" and is_aragonos(contract) diff --git a/slither/utils/type.py b/slither/utils/type.py index 3b0577987..6939889c8 100644 --- a/slither/utils/type.py +++ b/slither/utils/type.py @@ -32,3 +32,18 @@ def export_nested_types_from_variable(variable): return l +def export_return_type_from_variable(variable): + """ + Return the type returned by a variable + :param variable + :return: Type + """ + if isinstance(variable.type, MappingType): + return export_return_type_from_variable(variable.type.type_to) + + if isinstance(variable.type, ArrayType): + return variable.type.type + + return variable.type + + diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 60568881b..372a383b5 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -99,6 +99,13 @@ class ConstantFolding(ExpressionVisitor): raise NotConstant def _post_tuple_expression(self, expression): + if expression.expressions: + if len(expression.expressions) == 1: + cf = ConstantFolding(expression.expressions[0], self._type) + expr = cf.result() + assert isinstance(expr, Literal) + set_val(expression, int(expr.value)) + return raise NotConstant def _post_type_conversion(self, expression): diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 7799cf128..ed53f9ef7 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -67,7 +67,9 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result = [] self._visit_expression(self.expression) if node.type == NodeType.RETURN: - self._result.append(Return(get(self.expression))) + r = Return(get(self.expression)) + r.set_expression(expression) + self._result.append(r) for ir in self._result: ir.set_node(node) @@ -83,6 +85,7 @@ class ExpressionToSlithIR(ExpressionVisitor): for idx in range(len(left)): if not left[idx] is None: operation = convert_assignment(left[idx], right[idx], expression.type, expression.expression_return_type) + operation.set_expression(expression) self._result.append(operation) set_val(expression, None) else: @@ -90,6 +93,7 @@ class ExpressionToSlithIR(ExpressionVisitor): for idx in range(len(left)): if not left[idx] is None: operation = Unpack(left[idx], right, idx) + operation.set_expression(expression) self._result.append(operation) set_val(expression, None) else: @@ -97,10 +101,12 @@ class ExpressionToSlithIR(ExpressionVisitor): # uint8[2] var = [1,2]; if isinstance(right, list): operation = InitArray(right, left) + operation.set_expression(expression) self._result.append(operation) set_val(expression, left) else: operation = convert_assignment(left, right, expression.type, expression.expression_return_type) + operation.set_expression(expression) self._result.append(operation) # Return left to handle # a = b = 1; @@ -112,6 +118,7 @@ class ExpressionToSlithIR(ExpressionVisitor): val = TemporaryVariable(self._node) operation = Binary(val, left, right, expression.type) + operation.set_expression(expression) self._result.append(operation) set_val(expression, val) @@ -120,6 +127,7 @@ class ExpressionToSlithIR(ExpressionVisitor): args = [get(a) for a in expression.arguments if a] for arg in args: arg_ = Argument(arg) + arg_.set_expression(expression) self._result.append(arg_) if isinstance(called, Function): # internal call @@ -130,11 +138,10 @@ class ExpressionToSlithIR(ExpressionVisitor): else: val = TemporaryVariable(self._node) internal_call = InternalCall(called, len(args), val, expression.type_call) + internal_call.set_expression(expression) self._result.append(internal_call) set_val(expression, val) else: - val = TemporaryVariable(self._node) - # If tuple if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': val = TupleVariable(self._node) @@ -142,6 +149,7 @@ class ExpressionToSlithIR(ExpressionVisitor): val = TemporaryVariable(self._node) message_call = TmpCall(called, len(args), val, expression.type_call) + message_call.set_expression(expression) self._result.append(message_call) set_val(expression, val) @@ -165,31 +173,36 @@ class ExpressionToSlithIR(ExpressionVisitor): init_array_right = left left = init_array_val operation = InitArray(init_array_right, init_array_val) + operation.set_expression(expression) self._result.append(operation) operation = Index(val, left, right, expression.type) + operation.set_expression(expression) self._result.append(operation) set_val(expression, val) def _post_literal(self, expression): - cst = Constant(expression.value, expression.type) + cst = Constant(expression.value, expression.type, expression.subdenomination) set_val(expression, cst) def _post_member_access(self, expression): expr = get(expression.expression) val = ReferenceVariable(self._node) member = Member(expr, Constant(expression.member_name), val) + member.set_expression(expression) self._result.append(member) set_val(expression, val) def _post_new_array(self, expression): val = TemporaryVariable(self._node) operation = TmpNewArray(expression.depth, expression.array_type, val) + operation.set_expression(expression) self._result.append(operation) set_val(expression, val) def _post_new_contract(self, expression): val = TemporaryVariable(self._node) operation = TmpNewContract(expression.contract_name, val) + operation.set_expression(expression) self._result.append(operation) set_val(expression, val) @@ -197,6 +210,7 @@ class ExpressionToSlithIR(ExpressionVisitor): # TODO unclear if this is ever used? val = TemporaryVariable(self._node) operation = TmpNewElementaryType(expression.type, val) + operation.set_expression(expression) self._result.append(operation) set_val(expression, val) @@ -212,6 +226,7 @@ class ExpressionToSlithIR(ExpressionVisitor): expr = get(expression.expression) val = TemporaryVariable(self._node) operation = TypeConversion(val, expr, expression.type) + operation.set_expression(expression) self._result.append(operation) set_val(expression, val) @@ -220,41 +235,50 @@ class ExpressionToSlithIR(ExpressionVisitor): if expression.type in [UnaryOperationType.BANG, UnaryOperationType.TILD]: lvalue = TemporaryVariable(self._node) operation = Unary(lvalue, value, expression.type) + operation.set_expression(expression) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.DELETE]: operation = Delete(value, value) + operation.set_expression(expression) self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_PRE]: - operation = Binary(value, value, Constant("1"), BinaryType.ADDITION) + operation = Binary(value, value, Constant("1", value.type), BinaryType.ADDITION) + operation.set_expression(expression) self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.MINUSMINUS_PRE]: - operation = Binary(value, value, Constant("1"), BinaryType.SUBTRACTION) + operation = Binary(value, value, Constant("1", value.type), BinaryType.SUBTRACTION) + operation.set_expression(expression) self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_POST]: lvalue = TemporaryVariable(self._node) operation = Assignment(lvalue, value, value.type) + operation.set_expression(expression) self._result.append(operation) - operation = Binary(value, value, Constant("1"), BinaryType.ADDITION) + operation = Binary(value, value, Constant("1", value.type), BinaryType.ADDITION) + operation.set_expression(expression) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.MINUSMINUS_POST]: lvalue = TemporaryVariable(self._node) operation = Assignment(lvalue, value, value.type) + operation.set_expression(expression) self._result.append(operation) - operation = Binary(value, value, Constant("1"), BinaryType.SUBTRACTION) + operation = Binary(value, value, Constant("1", value.type), BinaryType.SUBTRACTION) + operation.set_expression(expression) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.PLUS_PRE]: set_val(expression, value) elif expression.type in [UnaryOperationType.MINUS_PRE]: lvalue = TemporaryVariable(self._node) - operation = Binary(lvalue, Constant("0"), value, BinaryType.SUBTRACTION) + operation = Binary(lvalue, Constant("0", value.type), value, BinaryType.SUBTRACTION) + operation.set_expression(expression) self._result.append(operation) set_val(expression, lvalue) else: - raise Exception('Unary operation to IR not supported {}'.format(expression)) + raise SlithIRError('Unary operation to IR not supported {}'.format(expression)) diff --git a/tests/check-erc/erc20.sol b/tests/check-erc/erc20.sol new file mode 100644 index 000000000..8ba65a918 --- /dev/null +++ b/tests/check-erc/erc20.sol @@ -0,0 +1,6 @@ +contract ERC20{ + + + uint public totalSupply; + +} diff --git a/tests/check-erc/test_1.txt b/tests/check-erc/test_1.txt new file mode 100644 index 000000000..1e088017f --- /dev/null +++ b/tests/check-erc/test_1.txt @@ -0,0 +1,21 @@ +# Check ERC20 + +## Check functions +[✓] totalSupply() is present + [✓] totalSupply() -> () (correct return value) + [✓] totalSupply() is view +[ ] balanceOf(address) is missing +[ ] transfer(address,uint256) is missing +[ ] transferFrom(address,address,uint256) is missing +[ ] approve(address,uint256) is missing +[ ] allowance(address,address) is missing +[ ] name() is missing (optional) +[ ] symbol() is missing (optional) +[ ] decimals() is missing (optional) + +## Check events +[ ] Transfer(address,address,uint256) is missing +[ ] Approval(address,address,uint256) is missing + + + [ ] ERC20 is not protected for the ERC20 approval race condition diff --git a/tests/check-kspec/safeAdd/safeAdd.sol b/tests/check-kspec/safeAdd/safeAdd.sol new file mode 100644 index 000000000..04c49e834 --- /dev/null +++ b/tests/check-kspec/safeAdd/safeAdd.sol @@ -0,0 +1,10 @@ +pragma solidity >=0.4.24; + +contract SafeAdd { + function add(uint x, uint y) public pure returns (uint z) { + require((z = x + y) >= x); + } + function add_v2(uint x, uint y) public pure returns (uint z) { + require((z = x + y) >= x); + } +} diff --git a/tests/check-kspec/safeAdd/spec.md b/tests/check-kspec/safeAdd/spec.md new file mode 100644 index 000000000..5fa1f516a --- /dev/null +++ b/tests/check-kspec/safeAdd/spec.md @@ -0,0 +1,29 @@ + +```act +behaviour add of SafeAdd +interface add(uint256 X, uint256 Y) + +iff in range uint256 + + X + Y + +iff + + VCallValue == 0 + +returns X + Y +``` +```act +behaviour addv2 of SafeAdd +interface addv2(uint256 X, uint256 Y) + +iff in range uint256 + + X + Y + +iff + + VCallValue == 0 + +returns X + Y +``` diff --git a/tests/check-kspec/test_1.txt b/tests/check-kspec/test_1.txt new file mode 100644 index 000000000..ed0482c87 --- /dev/null +++ b/tests/check-kspec/test_1.txt @@ -0,0 +1,7 @@ +## Check for functions coverage +[✓] SafeAdd.add(uint256,uint256) + +[ ] (Missing function) SafeAdd.add_v2(uint256,uint256) + +[ ] (Unresolved) SafeAdd.addv2(uint256,uint256) + diff --git a/tests/check-upgradeability/contract_v1_var_init.sol b/tests/check-upgradeability/contract_v1_var_init.sol new file mode 100644 index 000000000..df9bb3fa1 --- /dev/null +++ b/tests/check-upgradeability/contract_v1_var_init.sol @@ -0,0 +1,3 @@ +contract ContractV1{ + address destination = address(0x41); +} diff --git a/tests/check-upgradeability/contract_v2_constant.sol b/tests/check-upgradeability/contract_v2_constant.sol new file mode 100644 index 000000000..c985e3f98 --- /dev/null +++ b/tests/check-upgradeability/contract_v2_constant.sol @@ -0,0 +1,3 @@ +contract ContractV2{ + address constant destination = address(0x41); +} diff --git a/tests/check-upgradeability/test_1.txt b/tests/check-upgradeability/test_1.txt index 2bb466c54..df22e2702 100644 --- a/tests/check-upgradeability/test_1.txt +++ b/tests/check-upgradeability/test_1.txt @@ -1,7 +1,12 @@ -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CompareFunctions:Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) -INFO:CompareFunctions:No function ids collision found -INFO:VariablesOrder:Run variables order checks between the implementation and the proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Variable in the proxy: destination address -INFO:VariablesOrder:No variables ordering error found between implementation and the proxy + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Initializable contract not found, the contract does not follow a standard initalization schema. + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between ContractV1 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found diff --git a/tests/check-upgradeability/test_10.txt b/tests/check-upgradeability/test_10.txt new file mode 100644 index 000000000..89589b364 --- /dev/null +++ b/tests/check-upgradeability/test_10.txt @@ -0,0 +1,12 @@ + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Initializable contract not found, the contract does not follow a standard initalization schema. + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run variables ordering checks between ContractV1 and ContractV2... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +Variable only in ContractV1: destination (tests/check-upgradeability/contractV1.sol#2) + +## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +ContractV1.destination (tests/check-upgradeability/contractV1.sol#2) was not constant but ContractV2.destination is (tests/check-upgradeability/contract_v2_constant.sol#2) diff --git a/tests/check-upgradeability/test_11.txt b/tests/check-upgradeability/test_11.txt new file mode 100644 index 000000000..24764c8c5 --- /dev/null +++ b/tests/check-upgradeability/test_11.txt @@ -0,0 +1,6 @@ + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Initializable contract not found, the contract does not follow a standard initalization schema. + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +ContractV1.destination has an initial value (tests/check-upgradeability/contract_v1_var_init.sol#2) diff --git a/tests/check-upgradeability/test_2.txt b/tests/check-upgradeability/test_2.txt index 611c9db4c..9137454fe 100644 --- a/tests/check-upgradeability/test_2.txt +++ b/tests/check-upgradeability/test_2.txt @@ -1,11 +1,24 @@ -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CompareFunctions:Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) -INFO:CompareFunctions:No function ids collision found -INFO:VariablesOrder:Run variables order checks between the implementation and the proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Variable in the proxy: destination address -INFO:VariablesOrder:No variables ordering error found between implementation and the proxy -INFO:VariablesOrder:Run variables order checks between implementations... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:No variables ordering error found between implementations + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Initializable contract not found, the contract does not follow a standard initalization schema. + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between ContractV1 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between ContractV2 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found + +## Run variables ordering checks between ContractV1 and ContractV2... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found + +## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found diff --git a/tests/check-upgradeability/test_3.txt b/tests/check-upgradeability/test_3.txt index 2954e66c9..ebb21b107 100644 --- a/tests/check-upgradeability/test_3.txt +++ b/tests/check-upgradeability/test_3.txt @@ -1,11 +1,32 @@ -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CompareFunctions:Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) -INFO:CompareFunctions:Shadowing between proxy and implementation found myFunc() -INFO:VariablesOrder:Run variables order checks between the implementation and the proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Different variables between proxy and implem: destination address -> destination uint256 -INFO:VariablesOrder:Run variables order checks between implementations... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Different variables between v1 and v2: destination address -> destination uint256 -INFO:VariablesOrder:New variable: myFunc uint256 + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Initializable contract not found, the contract does not follow a standard initalization schema. + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between ContractV1 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +Shadowing between ContractV2.myFunc (tests/check-upgradeability/contractV2_bug.sol#4) and Proxy.myFunc() (tests/check-upgradeability/proxy.sol#11) + +## Run variables ordering checks between ContractV2 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +Different variables between ContractV2 and Proxy: + Variable 0 in ContractV2: destination uint256 (tests/check-upgradeability/contractV2_bug.sol#2) + Variable 0 in Proxy: destination address (tests/check-upgradeability/proxy.sol#9) + + +## Run variables ordering checks between ContractV1 and ContractV2... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +Different variables between ContractV1 and ContractV2: + Variable 0 in ContractV1: destination address (tests/check-upgradeability/contractV1.sol#2) + Variable 0 in ContractV2: destination uint256 (tests/check-upgradeability/contractV2_bug.sol#2) + +Extra variables in ContractV2: myFunc (tests/check-upgradeability/contractV2_bug.sol#4) + + +## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found diff --git a/tests/check-upgradeability/test_4.txt b/tests/check-upgradeability/test_4.txt index 5f9dbb0c7..cf9c9acd6 100644 --- a/tests/check-upgradeability/test_4.txt +++ b/tests/check-upgradeability/test_4.txt @@ -1,11 +1,32 @@ -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema. -INFO:CompareFunctions:Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) -INFO:CompareFunctions:No function ids collision found -INFO:VariablesOrder:Run variables order checks between the implementation and the proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Different variables between proxy and implem: destination address -> val uint256 -INFO:VariablesOrder:Run variables order checks between implementations... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Different variables between v1 and v2: destination address -> val uint256 -INFO:VariablesOrder:New variable: destination address + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Initializable contract not found, the contract does not follow a standard initalization schema. + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between ContractV1 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between ContractV2 and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +Different variables between ContractV2 and Proxy: + Variable 0 in ContractV2: val uint256 (tests/check-upgradeability/contractV2_bug2.sol#2) + Variable 0 in Proxy: destination address (tests/check-upgradeability/proxy.sol#9) + + +## Run variables ordering checks between ContractV1 and ContractV2... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +Different variables between ContractV1 and ContractV2: + Variable 0 in ContractV1: destination address (tests/check-upgradeability/contractV1.sol#2) + Variable 0 in ContractV2: val uint256 (tests/check-upgradeability/contractV2_bug2.sol#2) + +Extra variables in ContractV2: destination (tests/check-upgradeability/contractV2_bug2.sol#5) + + +## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found diff --git a/tests/check-upgradeability/test_5.txt b/tests/check-upgradeability/test_5.txt index fc7bf6e2c..58547b641 100644 --- a/tests/check-upgradeability/test_5.txt +++ b/tests/check-upgradeability/test_5.txt @@ -1,16 +1,18 @@ -INFO:CheckInitialization:Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) -INFO:CheckInitialization:Contract_lack_to_call_modifier.initialize() does not call initializer -INFO:CheckInitialization:Missing call to Contract_no_bug.initialize() in Contract_not_called_super_init -INFO:CheckInitialization:Contract_no_bug.initialize() is called multiple time in Contract_double_call -INFO:CheckInitialization:Check the deployement script to ensure that these functions are called: + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +All the init functions have the initializer modifier +No missing call to an init function found +No double call to init functions found +Check the deployement script to ensure that these functions are called: Contract_no_bug needs to be initialized by initialize() -Contract_lack_to_call_modifier needs to be initialized by initialize() -Contract_not_called_super_init needs to be initialized by initialize() -Contract_no_bug_inherits needs to be initialized by initialize() -Contract_double_call needs to be initialized by initialize()  -INFO:CompareFunctions:Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) -INFO:CompareFunctions:No function ids collision found -INFO:VariablesOrder:Run variables order checks between the implementation and the proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) -INFO:VariablesOrder:Variable in the proxy: destination address -INFO:VariablesOrder:No variables ordering error found between implementation and the proxy +No error found + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between Contract_no_bug and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found diff --git a/tests/check-upgradeability/test_6.txt b/tests/check-upgradeability/test_6.txt new file mode 100644 index 000000000..85da69ae4 --- /dev/null +++ b/tests/check-upgradeability/test_6.txt @@ -0,0 +1,18 @@ + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +Contract_lack_to_call_modifier.initialize() does not call the initializer modifier +No missing call to an init function found +No double call to init functions found +Check the deployement script to ensure that these functions are called: +Contract_lack_to_call_modifier needs to be initialized by initialize() + +No error found + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between Contract_lack_to_call_modifier and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found diff --git a/tests/check-upgradeability/test_7.txt b/tests/check-upgradeability/test_7.txt new file mode 100644 index 000000000..d2110df9c --- /dev/null +++ b/tests/check-upgradeability/test_7.txt @@ -0,0 +1,18 @@ + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +All the init functions have the initializer modifier +Missing call to Contract_no_bug.initialize() in Contract_not_called_super_init.initialize() +No double call to init functions found +Check the deployement script to ensure that these functions are called: +Contract_not_called_super_init needs to be initialized by initialize() + +No error found + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between Contract_not_called_super_init and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found diff --git a/tests/check-upgradeability/test_8.txt b/tests/check-upgradeability/test_8.txt new file mode 100644 index 000000000..69ff9ebec --- /dev/null +++ b/tests/check-upgradeability/test_8.txt @@ -0,0 +1,18 @@ + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +All the init functions have the initializer modifier +No missing call to an init function found +No double call to init functions found +Check the deployement script to ensure that these functions are called: +Contract_no_bug_inherits needs to be initialized by initialize() + +No error found + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between Contract_no_bug_inherits and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found diff --git a/tests/check-upgradeability/test_9.txt b/tests/check-upgradeability/test_9.txt new file mode 100644 index 000000000..3e12a4525 --- /dev/null +++ b/tests/check-upgradeability/test_9.txt @@ -0,0 +1,18 @@ + +## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks) +All the init functions have the initializer modifier +No missing call to an init function found +Contract_no_bug.initialize() is called multiple times in initialize() +Check the deployement script to ensure that these functions are called: +Contract_double_call needs to be initialized by initialize() + +No error found + +## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks) +No error found + +## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks) +No error found + +## Run variables ordering checks between Contract_double_call and Proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks) +No error found diff --git a/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.json b/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.json index 6e544d0c4..fb337f7ce 100644 --- a/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "arbitrary-send", - "impact": "High", - "confidence": "Medium", - "description": "Test.direct() (tests/arbitrary_send-0.5.1.sol#11-13) sends eth to arbitrary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#12)\n", "elements": [ { "type": "function", @@ -191,13 +187,15 @@ } } } - ] - }, - { + ], + "description": "Test.direct() (tests/arbitrary_send-0.5.1.sol#11-13) sends eth to arbitrary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#12)\n", + "markdown": "[Test.direct()](tests/arbitrary_send-0.5.1.sol#L11-L13) sends eth to arbitrary user\n\tDangerous calls:\n\t- [msg.sender.send(address(this).balance)](tests/arbitrary_send-0.5.1.sol#L12)\n", + "id": "5479aabac8e802dcfc47713305fe8ed8d58001b9891d028dd9e4d61d5b2a3a3e", "check": "arbitrary-send", "impact": "High", - "confidence": "Medium", - "description": "Test.indirect() (tests/arbitrary_send-0.5.1.sol#19-21) sends eth to arbitrary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#20)\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -381,7 +379,13 @@ } } } - ] + ], + "description": "Test.indirect() (tests/arbitrary_send-0.5.1.sol#19-21) sends eth to arbitrary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#20)\n", + "markdown": "[Test.indirect()](tests/arbitrary_send-0.5.1.sol#L19-L21) sends eth to arbitrary user\n\tDangerous calls:\n\t- [destination.send(address(this).balance)](tests/arbitrary_send-0.5.1.sol#L20)\n", + "id": "94e4360762e1ecac015c1c6cc6045bb0b0599cb576aa64dd0f9b5554631607a6", + "check": "arbitrary-send", + "impact": "High", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.txt b/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.txt index cc2b1953f..f439b3e58 100644 --- a/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.txt +++ b/tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.txt @@ -1,4 +1,4 @@ -INFO:Detectors: + Test.direct() (tests/arbitrary_send-0.5.1.sol#11-13) sends eth to arbitrary user Dangerous calls: - msg.sender.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#12) @@ -6,4 +6,4 @@ Test.indirect() (tests/arbitrary_send-0.5.1.sol#19-21) sends eth to arbitrary us Dangerous calls: - destination.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#20) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations -INFO:Slither:tests/arbitrary_send-0.5.1.sol analyzed (1 contracts), 2 result(s) found +tests/arbitrary_send-0.5.1.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/arbitrary_send.arbitrary-send.json b/tests/expected_json/arbitrary_send.arbitrary-send.json index 993747b20..3a35743ae 100644 --- a/tests/expected_json/arbitrary_send.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send.arbitrary-send.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "arbitrary-send", - "impact": "High", - "confidence": "Medium", - "description": "Test.direct() (tests/arbitrary_send.sol#11-13) sends eth to arbitrary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send.sol#12)\n", "elements": [ { "type": "function", @@ -191,13 +187,15 @@ } } } - ] - }, - { + ], + "description": "Test.direct() (tests/arbitrary_send.sol#11-13) sends eth to arbitrary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send.sol#12)\n", + "markdown": "[Test.direct()](tests/arbitrary_send.sol#L11-L13) sends eth to arbitrary user\n\tDangerous calls:\n\t- [msg.sender.send(address(this).balance)](tests/arbitrary_send.sol#L12)\n", + "id": "db62976d0c290f0b09f4df06f97bf2921c4d7ee734eb086b5f1a619f3583d83b", "check": "arbitrary-send", "impact": "High", - "confidence": "Medium", - "description": "Test.indirect() (tests/arbitrary_send.sol#19-21) sends eth to arbitrary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send.sol#20)\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -381,7 +379,13 @@ } } } - ] + ], + "description": "Test.indirect() (tests/arbitrary_send.sol#19-21) sends eth to arbitrary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send.sol#20)\n", + "markdown": "[Test.indirect()](tests/arbitrary_send.sol#L19-L21) sends eth to arbitrary user\n\tDangerous calls:\n\t- [destination.send(address(this).balance)](tests/arbitrary_send.sol#L20)\n", + "id": "643af1938b9f04e949e0ccaffd907915b3ed93652671e3f5613e8b5f3ceef2bf", + "check": "arbitrary-send", + "impact": "High", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/arbitrary_send.arbitrary-send.txt b/tests/expected_json/arbitrary_send.arbitrary-send.txt index ee8440aa4..4b8a2e920 100644 --- a/tests/expected_json/arbitrary_send.arbitrary-send.txt +++ b/tests/expected_json/arbitrary_send.arbitrary-send.txt @@ -1,4 +1,4 @@ -INFO:Detectors: + Test.direct() (tests/arbitrary_send.sol#11-13) sends eth to arbitrary user Dangerous calls: - msg.sender.send(address(this).balance) (tests/arbitrary_send.sol#12) @@ -6,4 +6,4 @@ Test.indirect() (tests/arbitrary_send.sol#19-21) sends eth to arbitrary user Dangerous calls: - destination.send(address(this).balance) (tests/arbitrary_send.sol#20) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations -INFO:Slither:tests/arbitrary_send.sol analyzed (1 contracts), 2 result(s) found +tests/arbitrary_send.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/backdoor.backdoor.json b/tests/expected_json/backdoor.backdoor.json index 243fabcc7..4af157e1e 100644 --- a/tests/expected_json/backdoor.backdoor.json +++ b/tests/expected_json/backdoor.backdoor.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "backdoor", - "impact": "High", - "confidence": "High", - "description": "Backdoor function found in C.i_am_a_backdoor (tests/backdoor.sol#4-6)\n", "elements": [ { "type": "function", @@ -56,7 +52,13 @@ "signature": "i_am_a_backdoor()" } } - ] + ], + "description": "Backdoor function found in C.i_am_a_backdoor() (tests/backdoor.sol#4-6)\n", + "markdown": "Backdoor function found in [C.i_am_a_backdoor()](tests/backdoor.sol#L4-L6)\n", + "id": "8a9008f2f5cd23b34feb0235dcc30ecb8d09a10eff151b522939caead117ef7a", + "check": "backdoor", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/backdoor.backdoor.txt b/tests/expected_json/backdoor.backdoor.txt index 76d21139d..68d34c305 100644 --- a/tests/expected_json/backdoor.backdoor.txt +++ b/tests/expected_json/backdoor.backdoor.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -Backdoor function found in C.i_am_a_backdoor (tests/backdoor.sol#4-6) + +Backdoor function found in C.i_am_a_backdoor() (tests/backdoor.sol#4-6) Reference: https://github.com/trailofbits/slither/wiki/Adding-a-new-detector +tests/backdoor.sol analyzed (1 contracts with 1 detectors), 1 result(s) found INFO:Slither:/home/travis/build/crytic/slither/scripts/../tests/expected_json/backdoor.backdoor.json exists already, the overwrite is prevented -INFO:Slither:tests/backdoor.sol analyzed (1 contracts), 1 result(s) found diff --git a/tests/expected_json/backdoor.suicidal.json b/tests/expected_json/backdoor.suicidal.json index 73ff578a5..5bce85de7 100644 --- a/tests/expected_json/backdoor.suicidal.json +++ b/tests/expected_json/backdoor.suicidal.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "suicidal", - "impact": "High", - "confidence": "High", - "description": "C.i_am_a_backdoor() (tests/backdoor.sol#4-6) allows anyone to destruct the contract\n", "elements": [ { "type": "function", @@ -56,7 +52,13 @@ "signature": "i_am_a_backdoor()" } } - ] + ], + "description": "C.i_am_a_backdoor() (tests/backdoor.sol#4-6) allows anyone to destruct the contract\n", + "markdown": "[C.i_am_a_backdoor()](tests/backdoor.sol#L4-L6) allows anyone to destruct the contract\n", + "id": "bb1e4596537b6e2c29f4221e733692fd6dac8555095181718e440ca525016eb7", + "check": "suicidal", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/backdoor.suicidal.txt b/tests/expected_json/backdoor.suicidal.txt index eefe0e4bf..80a385db6 100644 --- a/tests/expected_json/backdoor.suicidal.txt +++ b/tests/expected_json/backdoor.suicidal.txt @@ -1,5 +1,5 @@ -INFO:Detectors: + C.i_am_a_backdoor() (tests/backdoor.sol#4-6) allows anyone to destruct the contract Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal +tests/backdoor.sol analyzed (1 contracts with 1 detectors), 1 result(s) found INFO:Slither:/home/travis/build/crytic/slither/scripts/../tests/expected_json/backdoor.suicidal.json exists already, the overwrite is prevented -INFO:Slither:tests/backdoor.sol analyzed (1 contracts), 1 result(s) found diff --git a/tests/expected_json/const_state_variables.constable-states.json b/tests/expected_json/const_state_variables.constable-states.json index 60aeaa59b..0acef53d6 100644 --- a/tests/expected_json/const_state_variables.constable-states.json +++ b/tests/expected_json/const_state_variables.constable-states.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "constable-states", - "impact": "Optimization", - "confidence": "High", - "description": "A.myFriendsAddress should be constant (tests/const_state_variables.sol#7)\n", "elements": [ { "type": "variable", @@ -64,13 +60,15 @@ } } } - ] - }, - { + ], + "description": "A.myFriendsAddress (tests/const_state_variables.sol#7) should be constant\n", + "markdown": "[A.myFriendsAddress](tests/const_state_variables.sol#L7) should be constant\n", + "id": "1454db80653b732bf6acbe54ff0ae4707002207a2a8216708c12d61c88a43e5f", "check": "constable-states", "impact": "Optimization", - "confidence": "High", - "description": "A.test should be constant (tests/const_state_variables.sol#10)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -127,13 +125,15 @@ } } } - ] - }, - { + ], + "description": "A.test (tests/const_state_variables.sol#10) should be constant\n", + "markdown": "[A.test](tests/const_state_variables.sol#L10) should be constant\n", + "id": "5d9e3fb413322b71a93e90f7e89bd8c83cd4884d577d039598c681fe9db38b1d", "check": "constable-states", "impact": "Optimization", - "confidence": "High", - "description": "A.text2 should be constant (tests/const_state_variables.sol#14)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -190,13 +190,15 @@ } } } - ] - }, - { + ], + "description": "A.text2 (tests/const_state_variables.sol#14) should be constant\n", + "markdown": "[A.text2](tests/const_state_variables.sol#L14) should be constant\n", + "id": "df11e6201c4558a8c5cd90b55b134b9ca8f07203b2264d3aa93bd7745e8cb4ba", "check": "constable-states", "impact": "Optimization", - "confidence": "High", - "description": "B.mySistersAddress should be constant (tests/const_state_variables.sol#26)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -249,13 +251,15 @@ } } } - ] - }, - { + ], + "description": "B.mySistersAddress (tests/const_state_variables.sol#26) should be constant\n", + "markdown": "[B.mySistersAddress](tests/const_state_variables.sol#L26) should be constant\n", + "id": "bee93a722c8eae4a48aade67d8ef537d84c106f48fc9eb738c795fce10d3bc63", "check": "constable-states", "impact": "Optimization", - "confidence": "High", - "description": "MyConc.should_be_constant should be constant (tests/const_state_variables.sol#42)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -308,13 +312,15 @@ } } } - ] - }, - { + ], + "description": "MyConc.should_be_constant (tests/const_state_variables.sol#42) should be constant\n", + "markdown": "[MyConc.should_be_constant](tests/const_state_variables.sol#L42) should be constant\n", + "id": "cbcafa2a3efba4d21ac1b51b4b823e5082d556bc3d6cf3fd2ab3188f9f218fc1", "check": "constable-states", "impact": "Optimization", - "confidence": "High", - "description": "MyConc.should_be_constant_2 should be constant (tests/const_state_variables.sol#43)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -367,7 +373,13 @@ } } } - ] + ], + "description": "MyConc.should_be_constant_2 (tests/const_state_variables.sol#43) should be constant\n", + "markdown": "[MyConc.should_be_constant_2](tests/const_state_variables.sol#L43) should be constant\n", + "id": "9a48a4122de1a6a4774a9f1e0d4917bd0fa08f17b4af41b86ba07689e51bf711", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" } ] } diff --git a/tests/expected_json/const_state_variables.constable-states.txt b/tests/expected_json/const_state_variables.constable-states.txt index d7b05ff8e..27670357d 100644 --- a/tests/expected_json/const_state_variables.constable-states.txt +++ b/tests/expected_json/const_state_variables.constable-states.txt @@ -1,9 +1,9 @@ -INFO:Detectors: -A.myFriendsAddress should be constant (tests/const_state_variables.sol#7) -A.test should be constant (tests/const_state_variables.sol#10) -A.text2 should be constant (tests/const_state_variables.sol#14) -B.mySistersAddress should be constant (tests/const_state_variables.sol#26) -MyConc.should_be_constant should be constant (tests/const_state_variables.sol#42) -MyConc.should_be_constant_2 should be constant (tests/const_state_variables.sol#43) + +A.myFriendsAddress (tests/const_state_variables.sol#7) should be constant +A.test (tests/const_state_variables.sol#10) should be constant +A.text2 (tests/const_state_variables.sol#14) should be constant +B.mySistersAddress (tests/const_state_variables.sol#26) should be constant +MyConc.should_be_constant (tests/const_state_variables.sol#42) should be constant +MyConc.should_be_constant_2 (tests/const_state_variables.sol#43) should be constant Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant -INFO:Slither:tests/const_state_variables.sol analyzed (3 contracts), 6 result(s) found +tests/const_state_variables.sol analyzed (3 contracts with 1 detectors), 6 result(s) found diff --git a/tests/expected_json/constant-0.5.1.constant-function.json b/tests/expected_json/constant-0.5.1.constant-function.json index 92880bd23..dab6537ea 100644 --- a/tests/expected_json/constant-0.5.1.constant-function.json +++ b/tests/expected_json/constant-0.5.1.constant-function.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "constant-function", - "impact": "Medium", - "confidence": "Medium", - "description": "Constant.test_assembly_bug() (tests/constant-0.5.1.sol#15-17) is declared view but contains assembly code\n", "elements": [ { "type": "function", @@ -68,9 +64,15 @@ } } ], + "description": "Constant.test_assembly_bug() (tests/constant-0.5.1.sol#15-17) is declared view but contains assembly code\n", + "markdown": "[Constant.test_assembly_bug()](tests/constant-0.5.1.sol#L15-L17) is declared view but contains assembly code\n", + "id": "1f892cae08b89096bdc4d6ecdf55a3adc4b4314390e054fe2547d9c8e9f76e23", "additional_fields": { "contains_assembly": true - } + }, + "check": "constant-function", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/constant-0.5.1.constant-function.txt b/tests/expected_json/constant-0.5.1.constant-function.txt index 2f308dd12..1fa22f64d 100644 --- a/tests/expected_json/constant-0.5.1.constant-function.txt +++ b/tests/expected_json/constant-0.5.1.constant-function.txt @@ -1,4 +1,4 @@ -INFO:Detectors: + Constant.test_assembly_bug() (tests/constant-0.5.1.sol#15-17) is declared view but contains assembly code Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state -INFO:Slither:tests/constant-0.5.1.sol analyzed (1 contracts), 1 result(s) found +tests/constant-0.5.1.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/constant.constant-function.json b/tests/expected_json/constant.constant-function.json index cf6f53771..40c0c626e 100644 --- a/tests/expected_json/constant.constant-function.json +++ b/tests/expected_json/constant.constant-function.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "constant-function", - "impact": "Medium", - "confidence": "Medium", - "description": "Constant.test_view_bug() (tests/constant.sol#5-7) is declared view but changes state variables:\n\t- Constant.a\n", "elements": [ { "type": "function", @@ -137,15 +133,17 @@ } } ], + "description": "Constant.test_view_bug() (tests/constant.sol#5-7) is declared view but changes state variables:\n\t- Constant.a (tests/constant.sol#3)\n", + "markdown": "[Constant.test_view_bug()](tests/constant.sol#L5-L7) is declared view but changes state variables:\n\t- [Constant.a](tests/constant.sol#L3)\n", + "id": "4dee61d8835d20c6f1f7c195d8bd1e9de5dbcc096396a5b8db391136f9f5fdf1", "additional_fields": { "contains_assembly": false - } - }, - { + }, "check": "constant-function", "impact": "Medium", - "confidence": "Medium", - "description": "Constant.test_constant_bug() (tests/constant.sol#9-11) is declared view but changes state variables:\n\t- Constant.a\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -275,15 +273,17 @@ } } ], + "description": "Constant.test_constant_bug() (tests/constant.sol#9-11) is declared view but changes state variables:\n\t- Constant.a (tests/constant.sol#3)\n", + "markdown": "[Constant.test_constant_bug()](tests/constant.sol#L9-L11) is declared view but changes state variables:\n\t- [Constant.a](tests/constant.sol#L3)\n", + "id": "145e2d34dfc5b932c8d67d480c0eaec9baa8c728e2a310529572c0c4a5c6046a", "additional_fields": { "contains_assembly": false - } - }, - { + }, "check": "constant-function", "impact": "Medium", - "confidence": "Medium", - "description": "Constant.test_assembly_bug() (tests/constant.sol#22-24) is declared view but contains assembly code\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -351,9 +351,15 @@ } } ], + "description": "Constant.test_assembly_bug() (tests/constant.sol#22-24) is declared view but contains assembly code\n", + "markdown": "[Constant.test_assembly_bug()](tests/constant.sol#L22-L24) is declared view but contains assembly code\n", + "id": "1f892cae08b89096bdc4d6ecdf55a3adc4b4314390e054fe2547d9c8e9f76e23", "additional_fields": { "contains_assembly": true - } + }, + "check": "constant-function", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/constant.constant-function.txt b/tests/expected_json/constant.constant-function.txt index 00eed4577..f2b695232 100644 --- a/tests/expected_json/constant.constant-function.txt +++ b/tests/expected_json/constant.constant-function.txt @@ -1,8 +1,8 @@ -INFO:Detectors: + Constant.test_view_bug() (tests/constant.sol#5-7) is declared view but changes state variables: - - Constant.a + - Constant.a (tests/constant.sol#3) Constant.test_constant_bug() (tests/constant.sol#9-11) is declared view but changes state variables: - - Constant.a + - Constant.a (tests/constant.sol#3) Constant.test_assembly_bug() (tests/constant.sol#22-24) is declared view but contains assembly code Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state -INFO:Slither:tests/constant.sol analyzed (1 contracts), 3 result(s) found +tests/constant.sol analyzed (1 contracts with 1 detectors), 3 result(s) found diff --git a/tests/expected_json/controlled_delegatecall.controlled-delegatecall.json b/tests/expected_json/controlled_delegatecall.controlled-delegatecall.json index 8ee63d9cc..1f0265c35 100644 --- a/tests/expected_json/controlled_delegatecall.controlled-delegatecall.json +++ b/tests/expected_json/controlled_delegatecall.controlled-delegatecall.json @@ -4,11 +4,73 @@ "results": { "detectors": [ { - "check": "controlled-delegatecall", - "impact": "High", - "confidence": "Medium", - "description": "C.bad_delegate_call (tests/controlled_delegatecall.sol#8-11) uses delegatecall to a input-controlled function id\n\t- addr_bad.delegatecall(data) (tests/controlled_delegatecall.sol#10)\n", "elements": [ + { + "type": "function", + "name": "bad_delegate_call", + "source_mapping": { + "start": 101, + "length": 134, + "filename_used": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", + "filename_relative": "tests/controlled_delegatecall.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", + "filename_short": "tests/controlled_delegatecall.sol", + "is_dependency": false, + "lines": [ + 8, + 9, + 10, + 11 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "C", + "source_mapping": { + "start": 0, + "length": 585, + "filename_used": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", + "filename_relative": "tests/controlled_delegatecall.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", + "filename_short": "tests/controlled_delegatecall.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad_delegate_call(bytes)" + } + }, { "type": "node", "name": "addr_bad.delegatecall(data)", @@ -94,23 +156,32 @@ } } } - }, + } + ], + "description": "C.bad_delegate_call(bytes) (tests/controlled_delegatecall.sol#8-11) uses delegatecall to a input-controlled function id\n\t- addr_bad.delegatecall(data) (tests/controlled_delegatecall.sol#10)\n", + "markdown": "[C.bad_delegate_call(bytes)](tests/controlled_delegatecall.sol#L8-L11) uses delegatecall to a input-controlled function id\n\t- [addr_bad.delegatecall(data)](tests/controlled_delegatecall.sol#L10)\n", + "id": "a97990bb03ea7d6b3844cfe53689f9d3f32e49baf83e603760588e34c7cba64f", + "check": "controlled-delegatecall", + "impact": "High", + "confidence": "Medium" + }, + { + "elements": [ { "type": "function", - "name": "bad_delegate_call", + "name": "bad_delegate_call2", "source_mapping": { - "start": 101, - "length": 134, + "start": 337, + "length": 118, "filename_used": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", "filename_relative": "tests/controlled_delegatecall.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", "filename_short": "tests/controlled_delegatecall.sol", "is_dependency": false, "lines": [ - 8, - 9, - 10, - 11 + 18, + 19, + 20 ], "starting_column": 5, "ending_column": 6 @@ -158,17 +229,9 @@ "ending_column": 2 } }, - "signature": "bad_delegate_call(bytes)" + "signature": "bad_delegate_call2(bytes)" } - } - ] - }, - { - "check": "controlled-delegatecall", - "impact": "High", - "confidence": "Medium", - "description": "C.bad_delegate_call2 (tests/controlled_delegatecall.sol#18-20) uses delegatecall to a input-controlled function id\n\t- addr_bad.delegatecall(abi.encode(func_id,data)) (tests/controlled_delegatecall.sol#19)\n", - "elements": [ + }, { "type": "node", "name": "addr_bad.delegatecall(abi.encode(func_id,data))", @@ -253,73 +316,14 @@ } } } - }, - { - "type": "function", - "name": "bad_delegate_call2", - "source_mapping": { - "start": 337, - "length": 118, - "filename_used": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", - "filename_relative": "tests/controlled_delegatecall.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", - "filename_short": "tests/controlled_delegatecall.sol", - "is_dependency": false, - "lines": [ - 18, - 19, - 20 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "C", - "source_mapping": { - "start": 0, - "length": 585, - "filename_used": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", - "filename_relative": "tests/controlled_delegatecall.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/controlled_delegatecall.sol", - "filename_short": "tests/controlled_delegatecall.sol", - "is_dependency": false, - "lines": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "bad_delegate_call2(bytes)" - } } - ] + ], + "description": "C.bad_delegate_call2(bytes) (tests/controlled_delegatecall.sol#18-20) uses delegatecall to a input-controlled function id\n\t- addr_bad.delegatecall(abi.encode(func_id,data)) (tests/controlled_delegatecall.sol#19)\n", + "markdown": "[C.bad_delegate_call2(bytes)](tests/controlled_delegatecall.sol#L18-L20) uses delegatecall to a input-controlled function id\n\t- [addr_bad.delegatecall(abi.encode(func_id,data))](tests/controlled_delegatecall.sol#L19)\n", + "id": "611ca2baf5d14852abb16f98b888d9d52760b8e729599d472c669a9b2f008026", + "check": "controlled-delegatecall", + "impact": "High", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/controlled_delegatecall.controlled-delegatecall.txt b/tests/expected_json/controlled_delegatecall.controlled-delegatecall.txt index c97185377..ade36ba5b 100644 --- a/tests/expected_json/controlled_delegatecall.controlled-delegatecall.txt +++ b/tests/expected_json/controlled_delegatecall.controlled-delegatecall.txt @@ -1,7 +1,7 @@ -INFO:Detectors: -C.bad_delegate_call (tests/controlled_delegatecall.sol#8-11) uses delegatecall to a input-controlled function id + +C.bad_delegate_call(bytes) (tests/controlled_delegatecall.sol#8-11) uses delegatecall to a input-controlled function id - addr_bad.delegatecall(data) (tests/controlled_delegatecall.sol#10) -C.bad_delegate_call2 (tests/controlled_delegatecall.sol#18-20) uses delegatecall to a input-controlled function id +C.bad_delegate_call2(bytes) (tests/controlled_delegatecall.sol#18-20) uses delegatecall to a input-controlled function id - addr_bad.delegatecall(abi.encode(func_id,data)) (tests/controlled_delegatecall.sol#19) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall -INFO:Slither:tests/controlled_delegatecall.sol analyzed (1 contracts), 2 result(s) found +tests/controlled_delegatecall.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/deprecated_calls.deprecated-standards.json b/tests/expected_json/deprecated_calls.deprecated-standards.json index c25d611b8..a071350b7 100644 --- a/tests/expected_json/deprecated_calls.deprecated-standards.json +++ b/tests/expected_json/deprecated_calls.deprecated-standards.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "deprecated-standards", - "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#2:\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", "elements": [ { "type": "variable", @@ -73,13 +69,15 @@ } } } - ] - }, - { + ], + "description": "Deprecated standard detected ContractWithDeprecatedReferences.globalBlockHash (tests/deprecated_calls.sol#2):\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "markdown": "Deprecated standard detected [ContractWithDeprecatedReferences.globalBlockHash](tests/deprecated_calls.sol#L2):\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "id": "84ecc7e9ce51f3a7103d3674e237fe1ba07899cca5d40de91172c36f434fc9ce", "check": "deprecated-standards", "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#7:\n\t- Usage of \"msg.gas\" should be replaced with \"gasleft()\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "node", @@ -172,13 +170,15 @@ } } } - ] - }, - { + ], + "description": "Deprecated standard detected msg.gas == msg.value (tests/deprecated_calls.sol#7):\n\t- Usage of \"msg.gas\" should be replaced with \"gasleft()\"\n", + "markdown": "Deprecated standard detected [msg.gas == msg.value](tests/deprecated_calls.sol#L7):\n\t- Usage of \"msg.gas\" should be replaced with \"gasleft()\"\n", + "id": "0af1a05682eb2ab89143849898f6e2fb9bdfa6bdd01d43d81d41e10c4dfe58d7", "check": "deprecated-standards", "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#9:\n\t- Usage of \"throw\" should be replaced with \"revert()\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "node", @@ -271,13 +271,15 @@ } } } - ] - }, - { + ], + "description": "Deprecated standard detected THROW None (tests/deprecated_calls.sol#9):\n\t- Usage of \"throw\" should be replaced with \"revert()\"\n", + "markdown": "Deprecated standard detected [THROW None](tests/deprecated_calls.sol#L9):\n\t- Usage of \"throw\" should be replaced with \"revert()\"\n", + "id": "1a3241d58683640c33ae9e1bcd65bafd7472e7b7c9d2ec695f84590a2cef286a", "check": "deprecated-standards", "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#16:\n\t- Usage of \"sha3()\" should be replaced with \"keccak256()\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "node", @@ -376,13 +378,15 @@ } } } - ] - }, - { + ], + "description": "Deprecated standard detected sha3Result = sha3()(test deprecated sha3 usage) (tests/deprecated_calls.sol#16):\n\t- Usage of \"sha3()\" should be replaced with \"keccak256()\"\n", + "markdown": "Deprecated standard detected [sha3Result = sha3()(test deprecated sha3 usage)](tests/deprecated_calls.sol#L16):\n\t- Usage of \"sha3()\" should be replaced with \"keccak256()\"\n", + "id": "3926d80bb1119eb9faf7abdf2c2b61ce7accc5bcf4c0eaed120910e6662891f3", "check": "deprecated-standards", "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#19:\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "node", @@ -481,13 +485,15 @@ } } } - ] - }, - { + ], + "description": "Deprecated standard detected blockHashResult = block.blockhash(0) (tests/deprecated_calls.sol#19):\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "markdown": "Deprecated standard detected [blockHashResult = block.blockhash(0)](tests/deprecated_calls.sol#L19):\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "id": "0e462458439683a8a7a6d2113e962b00bb79ead8d7f6f462a158cf114bd22e96", "check": "deprecated-standards", "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#22:\n\t- Usage of \"callcode\" should be replaced with \"delegatecall\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "node", @@ -586,13 +592,15 @@ } } } - ] - }, - { + ], + "description": "Deprecated standard detected address(this).callcode() (tests/deprecated_calls.sol#22):\n\t- Usage of \"callcode\" should be replaced with \"delegatecall\"\n", + "markdown": "Deprecated standard detected [address(this).callcode()](tests/deprecated_calls.sol#L22):\n\t- Usage of \"callcode\" should be replaced with \"delegatecall\"\n", + "id": "8d21d721e732845b6a72e96498c98f07a214f4acfb2f956a93dc48b57ed5de95", "check": "deprecated-standards", "impact": "Informational", - "confidence": "High", - "description": "Deprecated standard detected @ tests/deprecated_calls.sol#25:\n\t- Usage of \"suicide()\" should be replaced with \"selfdestruct()\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "node", @@ -691,7 +699,134 @@ } } } - ] + ], + "description": "Deprecated standard detected suicide(address)(address(0)) (tests/deprecated_calls.sol#25):\n\t- Usage of \"suicide()\" should be replaced with \"selfdestruct()\"\n", + "markdown": "Deprecated standard detected [suicide(address)(address(0))](tests/deprecated_calls.sol#L25):\n\t- Usage of \"suicide()\" should be replaced with \"selfdestruct()\"\n", + "id": "741bd57823d2933e7f19c5bebab1a57506f0fd377e148ae9383c2b40ca768508", + "check": "deprecated-standards", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [ + { + "type": "node", + "name": "globalBlockHash = block.blockhash(0)", + "source_mapping": { + "start": 48, + "length": 44, + "filename_used": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_relative": "tests/deprecated_calls.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_short": "tests/deprecated_calls.sol", + "is_dependency": false, + "lines": [ + 2 + ], + "starting_column": 5, + "ending_column": 49 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "slitherConstructorVariables", + "source_mapping": { + "start": 0, + "length": 906, + "filename_used": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_relative": "tests/deprecated_calls.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_short": "tests/deprecated_calls.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27 + ], + "starting_column": 1, + "ending_column": null + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "ContractWithDeprecatedReferences", + "source_mapping": { + "start": 0, + "length": 906, + "filename_used": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_relative": "tests/deprecated_calls.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_short": "tests/deprecated_calls.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27 + ], + "starting_column": 1, + "ending_column": null + } + }, + "signature": "slitherConstructorVariables()" + } + } + } + } + ], + "description": "Deprecated standard detected globalBlockHash = block.blockhash(0) (tests/deprecated_calls.sol#2):\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "markdown": "Deprecated standard detected [globalBlockHash = block.blockhash(0)](tests/deprecated_calls.sol#L2):\n\t- Usage of \"block.blockhash()\" should be replaced with \"blockhash()\"\n", + "id": "1a17fcbea6521ac0d23bbae6d75bcb85c6ba345a57e976e9e3a8d7862bbd9773", + "check": "deprecated-standards", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/deprecated_calls.deprecated-standards.txt b/tests/expected_json/deprecated_calls.deprecated-standards.txt index 7056ea4ce..a8180ef3b 100644 --- a/tests/expected_json/deprecated_calls.deprecated-standards.txt +++ b/tests/expected_json/deprecated_calls.deprecated-standards.txt @@ -1,17 +1,19 @@ -INFO:Detectors: -Deprecated standard detected @ tests/deprecated_calls.sol#2: + +Deprecated standard detected ContractWithDeprecatedReferences.globalBlockHash (tests/deprecated_calls.sol#2): - Usage of "block.blockhash()" should be replaced with "blockhash()" -Deprecated standard detected @ tests/deprecated_calls.sol#7: +Deprecated standard detected msg.gas == msg.value (tests/deprecated_calls.sol#7): - Usage of "msg.gas" should be replaced with "gasleft()" -Deprecated standard detected @ tests/deprecated_calls.sol#9: +Deprecated standard detected THROW None (tests/deprecated_calls.sol#9): - Usage of "throw" should be replaced with "revert()" -Deprecated standard detected @ tests/deprecated_calls.sol#16: +Deprecated standard detected sha3Result = sha3()(test deprecated sha3 usage) (tests/deprecated_calls.sol#16): - Usage of "sha3()" should be replaced with "keccak256()" -Deprecated standard detected @ tests/deprecated_calls.sol#19: +Deprecated standard detected blockHashResult = block.blockhash(0) (tests/deprecated_calls.sol#19): - Usage of "block.blockhash()" should be replaced with "blockhash()" -Deprecated standard detected @ tests/deprecated_calls.sol#22: +Deprecated standard detected address(this).callcode() (tests/deprecated_calls.sol#22): - Usage of "callcode" should be replaced with "delegatecall" -Deprecated standard detected @ tests/deprecated_calls.sol#25: +Deprecated standard detected suicide(address)(address(0)) (tests/deprecated_calls.sol#25): - Usage of "suicide()" should be replaced with "selfdestruct()" +Deprecated standard detected globalBlockHash = block.blockhash(0) (tests/deprecated_calls.sol#2): + - Usage of "block.blockhash()" should be replaced with "blockhash()" Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards -INFO:Slither:tests/deprecated_calls.sol analyzed (1 contracts), 7 result(s) found +tests/deprecated_calls.sol analyzed (1 contracts with 1 detectors), 8 result(s) found diff --git a/tests/expected_json/erc20_indexed.erc20-indexed.json b/tests/expected_json/erc20_indexed.erc20-indexed.json index b6d7aafcb..431d70da5 100644 --- a/tests/expected_json/erc20_indexed.erc20-indexed.json +++ b/tests/expected_json/erc20_indexed.erc20-indexed.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "erc20-indexed", - "impact": "Informational", - "confidence": "High", - "description": "ERC20 event IERC20Bad.Transfer (tests/erc20_indexed.sol#19) does not index parameter 'from'\n", "elements": [ { "type": "event", @@ -60,13 +56,15 @@ "parameter_name": "from" } } - ] - }, - { + ], + "description": "ERC20 event IERC20BadTransfer(address,address,uint256) (tests/erc20_indexed.sol#19)does not index parameter from\n", + "markdown": "ERC20 event [IERC20BadTransfer(address,address,uint256)](tests/erc20_indexed.sol#L19)does not index parameter from\n", + "id": "a86c7a54115f270548e82d71570dc4d2900b622b0f82c6fce137f3a35314af53", "check": "erc20-indexed", "impact": "Informational", - "confidence": "High", - "description": "ERC20 event IERC20Bad.Transfer (tests/erc20_indexed.sol#19) does not index parameter 'to'\n", + "confidence": "High" + }, + { "elements": [ { "type": "event", @@ -119,13 +117,15 @@ "parameter_name": "to" } } - ] - }, - { + ], + "description": "ERC20 event IERC20BadTransfer(address,address,uint256) (tests/erc20_indexed.sol#19)does not index parameter to\n", + "markdown": "ERC20 event [IERC20BadTransfer(address,address,uint256)](tests/erc20_indexed.sol#L19)does not index parameter to\n", + "id": "29c46eb3a4695004959847ae09377729cdf3aa583de95560090b9bd49977c49b", "check": "erc20-indexed", "impact": "Informational", - "confidence": "High", - "description": "ERC20 event IERC20Bad.Approval (tests/erc20_indexed.sol#20) does not index parameter 'owner'\n", + "confidence": "High" + }, + { "elements": [ { "type": "event", @@ -178,13 +178,15 @@ "parameter_name": "owner" } } - ] - }, - { + ], + "description": "ERC20 event IERC20BadApproval(address,address,uint256) (tests/erc20_indexed.sol#20)does not index parameter owner\n", + "markdown": "ERC20 event [IERC20BadApproval(address,address,uint256)](tests/erc20_indexed.sol#L20)does not index parameter owner\n", + "id": "7d72b56a71ca96db304878f25484c496af1d283a9b777dc788f1473974057025", "check": "erc20-indexed", "impact": "Informational", - "confidence": "High", - "description": "ERC20 event IERC20Bad.Approval (tests/erc20_indexed.sol#20) does not index parameter 'spender'\n", + "confidence": "High" + }, + { "elements": [ { "type": "event", @@ -237,7 +239,13 @@ "parameter_name": "spender" } } - ] + ], + "description": "ERC20 event IERC20BadApproval(address,address,uint256) (tests/erc20_indexed.sol#20)does not index parameter spender\n", + "markdown": "ERC20 event [IERC20BadApproval(address,address,uint256)](tests/erc20_indexed.sol#L20)does not index parameter spender\n", + "id": "df4d927d202bdca1fc411d6960d3f62ed2784f5eca7435cb0503f4154f2e3bc6", + "check": "erc20-indexed", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/erc20_indexed.erc20-indexed.txt b/tests/expected_json/erc20_indexed.erc20-indexed.txt index db0314cbd..4f7728022 100644 --- a/tests/expected_json/erc20_indexed.erc20-indexed.txt +++ b/tests/expected_json/erc20_indexed.erc20-indexed.txt @@ -1,7 +1,7 @@ -INFO:Detectors: -ERC20 event IERC20Bad.Transfer (tests/erc20_indexed.sol#19) does not index parameter 'from' -ERC20 event IERC20Bad.Transfer (tests/erc20_indexed.sol#19) does not index parameter 'to' -ERC20 event IERC20Bad.Approval (tests/erc20_indexed.sol#20) does not index parameter 'owner' -ERC20 event IERC20Bad.Approval (tests/erc20_indexed.sol#20) does not index parameter 'spender' + +ERC20 event IERC20BadTransfer(address,address,uint256) (tests/erc20_indexed.sol#19)does not index parameter from +ERC20 event IERC20BadTransfer(address,address,uint256) (tests/erc20_indexed.sol#19)does not index parameter to +ERC20 event IERC20BadApproval(address,address,uint256) (tests/erc20_indexed.sol#20)does not index parameter owner +ERC20 event IERC20BadApproval(address,address,uint256) (tests/erc20_indexed.sol#20)does not index parameter spender Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters -INFO:Slither:tests/erc20_indexed.sol analyzed (3 contracts), 4 result(s) found +tests/erc20_indexed.sol analyzed (3 contracts with 1 detectors), 4 result(s) found diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index d60c5ef49..fa3214e2b 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "external-function", - "impact": "Optimization", - "confidence": "High", - "description": "ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15) should be declared external\n", "elements": [ { "type": "function", @@ -68,13 +64,15 @@ "signature": "funcNotCalled3()" } } - ] - }, - { + ], + "description": "funcNotCalled3() should be declared external:\n\t- ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15)\n", + "markdown": "funcNotCalled3() should be declared external:\n\t- [ContractWithFunctionNotCalled.funcNotCalled3()](tests/external_function.sol#L13-L15)\n", + "id": "026d9a579ea0304e58c8a5174296494f4b672e4ea032f4e17504f3dac327c4e6", "check": "external-function", "impact": "Optimization", - "confidence": "High", - "description": "ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19) should be declared external\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -135,13 +133,15 @@ "signature": "funcNotCalled2()" } } - ] - }, - { + ], + "description": "funcNotCalled2() should be declared external:\n\t- ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19)\n", + "markdown": "funcNotCalled2() should be declared external:\n\t- [ContractWithFunctionNotCalled.funcNotCalled2()](tests/external_function.sol#L17-L19)\n", + "id": "1ef1f19a92a8ab8d27df156d50dd75628ec3057b5f5eb16b7d1faa0e5c3850a0", "check": "external-function", "impact": "Optimization", - "confidence": "High", - "description": "ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23) should be declared external\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -202,13 +202,15 @@ "signature": "funcNotCalled()" } } - ] - }, - { + ], + "description": "funcNotCalled() should be declared external:\n\t- ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23)\n", + "markdown": "funcNotCalled() should be declared external:\n\t- [ContractWithFunctionNotCalled.funcNotCalled()](tests/external_function.sol#L21-L23)\n", + "id": "369a2f3d071735755ff4f5bc43081fe858bbfb07eed94e5c6dda3c8daa22ba26", "check": "external-function", "impact": "Optimization", - "confidence": "High", - "description": "ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) should be declared external\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -265,13 +267,15 @@ "signature": "funcNotCalled()" } } - ] - }, - { + ], + "description": "funcNotCalled() should be declared external:\n\t- ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39)\n", + "markdown": "funcNotCalled() should be declared external:\n\t- [ContractWithFunctionNotCalled2.funcNotCalled()](tests/external_function.sol#L32-L39)\n", + "id": "80a0a3a3954cc6e314079a1d8d96d6739d521ddbcf738e63078d7f210e443562", "check": "external-function", "impact": "Optimization", - "confidence": "High", - "description": "FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) should be declared external\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -324,7 +328,13 @@ "signature": "parameter_read_ok_for_external(uint256)" } } - ] + ], + "description": "parameter_read_ok_for_external(uint256) should be declared external:\n\t- FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76)\n", + "markdown": "parameter_read_ok_for_external(uint256) should be declared external:\n\t- [FunctionParameterWrite.parameter_read_ok_for_external(uint256)](tests/external_function.sol#L74-L76)\n", + "id": "3a0a42d128eff9fb04d8f7605bf2d6f7574c2cbbdffa2dcabbae66d7568ecc59", + "check": "external-function", + "impact": "Optimization", + "confidence": "High" } ] } diff --git a/tests/expected_json/external_function.external-function.txt b/tests/expected_json/external_function.external-function.txt index 82470ba0d..a0f3d6499 100644 --- a/tests/expected_json/external_function.external-function.txt +++ b/tests/expected_json/external_function.external-function.txt @@ -1,8 +1,13 @@ -INFO:Detectors: -ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15) should be declared external -ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19) should be declared external -ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23) should be declared external -ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) should be declared external -FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) should be declared external + +funcNotCalled3() should be declared external: + - ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15) +funcNotCalled2() should be declared external: + - ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19) +funcNotCalled() should be declared external: + - ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23) +funcNotCalled() should be declared external: + - ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) +parameter_read_ok_for_external(uint256) should be declared external: + - FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external -INFO:Slither:tests/external_function.sol analyzed (6 contracts), 5 result(s) found +tests/external_function.sol analyzed (6 contracts with 1 detectors), 5 result(s) found diff --git a/tests/expected_json/external_function_2.external-function.txt b/tests/expected_json/external_function_2.external-function.txt index 352324d7f..a61fb7600 100644 --- a/tests/expected_json/external_function_2.external-function.txt +++ b/tests/expected_json/external_function_2.external-function.txt @@ -1 +1 @@ -INFO:Slither:tests/external_function_2.sol analyzed (4 contracts), 0 result(s) found +tests/external_function_2.sol analyzed (4 contracts with 1 detectors), 0 result(s) found diff --git a/tests/expected_json/incorrect_equality.incorrect-equality.json b/tests/expected_json/incorrect_equality.incorrect-equality.json index 3802e83b6..2f5b0723d 100644 --- a/tests/expected_json/incorrect_equality.incorrect-equality.json +++ b/tests/expected_json/incorrect_equality.incorrect-equality.json @@ -4,11 +4,66 @@ "results": { "detectors": [ { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "ERC20TestBalance.bad0(ERC20Function) (tests/incorrect_equality.sol#21-23) uses a dangerous strict equality:\n\t- require(bool)(erc.balanceOf(address(this)) == 10)\n", "elements": [ + { + "type": "function", + "name": "bad0", + "source_mapping": { + "start": 404, + "length": 101, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", + "filename_relative": "tests/incorrect_equality.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", + "filename_short": "tests/incorrect_equality.sol", + "is_dependency": false, + "lines": [ + 21, + 22, + 23 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "ERC20TestBalance", + "source_mapping": { + "start": 165, + "length": 445, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", + "filename_relative": "tests/incorrect_equality.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", + "filename_short": "tests/incorrect_equality.sol", + "is_dependency": false, + "lines": [ + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad0(ERC20Function)" + } + }, { "type": "node", "name": "require(bool)(erc.balanceOf(address(this)) == 10)", @@ -87,22 +142,32 @@ } } } - }, + } + ], + "description": "ERC20TestBalance.bad0(ERC20Function) (tests/incorrect_equality.sol#21-23) uses a dangerous strict equality:\n\t- require(bool)(erc.balanceOf(address(this)) == 10) (tests/incorrect_equality.sol#22)\n", + "markdown": "[ERC20TestBalance.bad0(ERC20Function)](tests/incorrect_equality.sol#L21-L23) uses a dangerous strict equality:\n\t- [require(bool)(erc.balanceOf(address(this)) == 10)](tests/incorrect_equality.sol#L22)\n", + "id": "75aa0ac0f7038b6a92030dee5c4c8f4cc6ab3f491558e18c61b6db5fbbf971e4", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad0", + "name": "bad1", "source_mapping": { - "start": 404, - "length": 101, + "start": 511, + "length": 97, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 21, - 22, - 23 + 25, + 26, + 27 ], "starting_column": 5, "ending_column": 6 @@ -144,17 +209,9 @@ "ending_column": 2 } }, - "signature": "bad0(ERC20Function)" + "signature": "bad1(ERC20Variable)" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "ERC20TestBalance.bad1(ERC20Variable) (tests/incorrect_equality.sol#25-27) uses a dangerous strict equality:\n\t- require(bool)(erc.balanceOf(msg.sender) == 10)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(erc.balanceOf(msg.sender) == 10)", @@ -233,22 +290,33 @@ } } } - }, + } + ], + "description": "ERC20TestBalance.bad1(ERC20Variable) (tests/incorrect_equality.sol#25-27) uses a dangerous strict equality:\n\t- require(bool)(erc.balanceOf(msg.sender) == 10) (tests/incorrect_equality.sol#26)\n", + "markdown": "[ERC20TestBalance.bad1(ERC20Variable)](tests/incorrect_equality.sol#L25-L27) uses a dangerous strict equality:\n\t- [require(bool)(erc.balanceOf(msg.sender) == 10)](tests/incorrect_equality.sol#L26)\n", + "id": "747d47c020b94e00fa06cc310b205306c37fda3811bafde5ee820ff84656127e", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad1", + "name": "bad0", "source_mapping": { - "start": 511, - "length": 97, + "start": 648, + "length": 133, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 25, - 26, - 27 + 32, + 33, + 34, + 35 ], "starting_column": 5, "ending_column": 6 @@ -256,51 +324,92 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "ERC20TestBalance", + "name": "TestContractBalance", "source_mapping": { - "start": 165, - "length": 445, + "start": 612, + "length": 1754, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28 + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97 ], "starting_column": 1, "ending_column": 2 } }, - "signature": "bad1(ERC20Variable)" + "signature": "bad0()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad0() (tests/incorrect_equality.sol#32-35) uses a dangerous strict equality:\n\t- require(bool)(address(address(this)).balance == 10000000000000000000)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(address(address(this)).balance == 10000000000000000000)", @@ -429,12 +538,22 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad0() (tests/incorrect_equality.sol#32-35) uses a dangerous strict equality:\n\t- require(bool)(address(address(this)).balance == 10000000000000000000) (tests/incorrect_equality.sol#33)\n", + "markdown": "[TestContractBalance.bad0()](tests/incorrect_equality.sol#L32-L35) uses a dangerous strict equality:\n\t- [require(bool)(address(address(this)).balance == 10000000000000000000)](tests/incorrect_equality.sol#L33)\n", + "id": "90491e5c72bcd00b473b5d5748a62145c1c156d5805735ee893953595c144f7b", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad0", + "name": "bad1", "source_mapping": { - "start": 648, + "start": 787, "length": 133, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", @@ -442,10 +561,10 @@ "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 32, - 33, - 34, - 35 + 37, + 38, + 39, + 40 ], "starting_column": 5, "ending_column": 6 @@ -536,17 +655,9 @@ "ending_column": 2 } }, - "signature": "bad0()" + "signature": "bad1()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad1() (tests/incorrect_equality.sol#37-40) uses a dangerous strict equality:\n\t- require(bool)(10000000000000000000 == address(address(this)).balance)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(10000000000000000000 == address(address(this)).balance)", @@ -675,23 +786,33 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad1() (tests/incorrect_equality.sol#37-40) uses a dangerous strict equality:\n\t- require(bool)(10000000000000000000 == address(address(this)).balance) (tests/incorrect_equality.sol#38)\n", + "markdown": "[TestContractBalance.bad1()](tests/incorrect_equality.sol#L37-L40) uses a dangerous strict equality:\n\t- [require(bool)(10000000000000000000 == address(address(this)).balance)](tests/incorrect_equality.sol#L38)\n", + "id": "1931d5770ec78d317bd036dc509a5f9914db54236e9ae451204efc17559396cb", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad1", + "name": "bad2", "source_mapping": { - "start": 787, - "length": 133, + "start": 926, + "length": 124, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 37, - 38, - 39, - 40 + 42, + 43, + 44, + 45 ], "starting_column": 5, "ending_column": 6 @@ -782,17 +903,9 @@ "ending_column": 2 } }, - "signature": "bad1()" + "signature": "bad2()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad2() (tests/incorrect_equality.sol#42-45) uses a dangerous strict equality:\n\t- require(bool)(address(this).balance == 10000000000000000000)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(address(this).balance == 10000000000000000000)", @@ -921,12 +1034,22 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad2() (tests/incorrect_equality.sol#42-45) uses a dangerous strict equality:\n\t- require(bool)(address(this).balance == 10000000000000000000) (tests/incorrect_equality.sol#43)\n", + "markdown": "[TestContractBalance.bad2()](tests/incorrect_equality.sol#L42-L45) uses a dangerous strict equality:\n\t- [require(bool)(address(this).balance == 10000000000000000000)](tests/incorrect_equality.sol#L43)\n", + "id": "55f882e1ab114a792fce75d5cce66a3213ac069aa7cbb5625ca5623eb20171a0", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad2", + "name": "bad3", "source_mapping": { - "start": 926, + "start": 1056, "length": 124, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", @@ -934,10 +1057,10 @@ "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 42, - 43, - 44, - 45 + 47, + 48, + 49, + 50 ], "starting_column": 5, "ending_column": 6 @@ -1028,17 +1151,9 @@ "ending_column": 2 } }, - "signature": "bad2()" + "signature": "bad3()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad3() (tests/incorrect_equality.sol#47-50) uses a dangerous strict equality:\n\t- require(bool)(10000000000000000000 == address(this).balance)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(10000000000000000000 == address(this).balance)", @@ -1167,23 +1282,35 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad3() (tests/incorrect_equality.sol#47-50) uses a dangerous strict equality:\n\t- require(bool)(10000000000000000000 == address(this).balance) (tests/incorrect_equality.sol#48)\n", + "markdown": "[TestContractBalance.bad3()](tests/incorrect_equality.sol#L47-L50) uses a dangerous strict equality:\n\t- [require(bool)(10000000000000000000 == address(this).balance)](tests/incorrect_equality.sol#L48)\n", + "id": "0f6f6872f53fd271e9a246cb5d6bbe56cdeb34df832f28844dd1e758d1ea8e20", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad3", + "name": "bad4", "source_mapping": { - "start": 1056, - "length": 124, + "start": 1186, + "length": 170, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 47, - 48, - 49, - 50 + 52, + 53, + 54, + 55, + 56, + 57 ], "starting_column": 5, "ending_column": 6 @@ -1274,17 +1401,9 @@ "ending_column": 2 } }, - "signature": "bad3()" + "signature": "bad4()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad4() (tests/incorrect_equality.sol#52-57) uses a dangerous strict equality:\n\t- balance == 10000000000000000000\n", - "elements": [ + }, { "type": "node", "name": "balance == 10000000000000000000", @@ -1415,12 +1534,22 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad4() (tests/incorrect_equality.sol#52-57) uses a dangerous strict equality:\n\t- balance == 10000000000000000000 (tests/incorrect_equality.sol#54)\n", + "markdown": "[TestContractBalance.bad4()](tests/incorrect_equality.sol#L52-L57) uses a dangerous strict equality:\n\t- [balance == 10000000000000000000](tests/incorrect_equality.sol#L54)\n", + "id": "10a836ebf912b1c384df149242be6ac888bd7739ab7b0e28e20221665a35dc1b", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad4", + "name": "bad5", "source_mapping": { - "start": 1186, + "start": 1362, "length": 170, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", @@ -1428,12 +1557,12 @@ "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 52, - 53, - 54, - 55, - 56, - 57 + 59, + 60, + 61, + 62, + 63, + 64 ], "starting_column": 5, "ending_column": 6 @@ -1524,17 +1653,9 @@ "ending_column": 2 } }, - "signature": "bad4()" + "signature": "bad5()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad5() (tests/incorrect_equality.sol#59-64) uses a dangerous strict equality:\n\t- 10000000000000000000 == balance\n", - "elements": [ + }, { "type": "node", "name": "10000000000000000000 == balance", @@ -1665,25 +1786,35 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad5() (tests/incorrect_equality.sol#59-64) uses a dangerous strict equality:\n\t- 10000000000000000000 == balance (tests/incorrect_equality.sol#61)\n", + "markdown": "[TestContractBalance.bad5()](tests/incorrect_equality.sol#L59-L64) uses a dangerous strict equality:\n\t- [10000000000000000000 == balance](tests/incorrect_equality.sol#L61)\n", + "id": "627eca9af27adf553a7508940ba1f8985ab374b291311fab8ca51db936af8a45", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad5", + "name": "bad6", "source_mapping": { - "start": 1362, - "length": 170, + "start": 1538, + "length": 179, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 59, - 60, - 61, - 62, - 63, - 64 + 66, + 67, + 68, + 69, + 70, + 71 ], "starting_column": 5, "ending_column": 6 @@ -1774,17 +1905,9 @@ "ending_column": 2 } }, - "signature": "bad5()" + "signature": "bad6()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestContractBalance.bad6() (tests/incorrect_equality.sol#66-71) uses a dangerous strict equality:\n\t- balance == 10000000000000000000\n", - "elements": [ + }, { "type": "node", "name": "balance == 10000000000000000000", @@ -1915,25 +2038,32 @@ } } } - }, + } + ], + "description": "TestContractBalance.bad6() (tests/incorrect_equality.sol#66-71) uses a dangerous strict equality:\n\t- balance == 10000000000000000000 (tests/incorrect_equality.sol#68)\n", + "markdown": "[TestContractBalance.bad6()](tests/incorrect_equality.sol#L66-L71) uses a dangerous strict equality:\n\t- [balance == 10000000000000000000](tests/incorrect_equality.sol#L68)\n", + "id": "76e879b9ffe8cbfbecfe665508a4e826d2f7a1276895e72df3c027f8e7d48dc5", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad6", + "name": "bad0", "source_mapping": { - "start": 1538, - "length": 179, + "start": 2935, + "length": 59, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 66, - 67, - 68, - 69, - 70, - 71 + 123, + 124, + 125 ], "starting_column": 5, "ending_column": 6 @@ -1941,100 +2071,61 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "TestContractBalance", + "name": "TestSolidityKeyword", "source_mapping": { - "start": 612, - "length": 1754, + "start": 2368, + "length": 774, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97 + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135 ], "starting_column": 1, "ending_column": 2 } }, - "signature": "bad6()" + "signature": "bad0()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestSolidityKeyword.bad0() (tests/incorrect_equality.sol#123-125) uses a dangerous strict equality:\n\t- require(bool)(now == 0)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(now == 0)", @@ -2131,22 +2222,32 @@ } } } - }, + } + ], + "description": "TestSolidityKeyword.bad0() (tests/incorrect_equality.sol#123-125) uses a dangerous strict equality:\n\t- require(bool)(now == 0) (tests/incorrect_equality.sol#124)\n", + "markdown": "[TestSolidityKeyword.bad0()](tests/incorrect_equality.sol#L123-L125) uses a dangerous strict equality:\n\t- [require(bool)(now == 0)](tests/incorrect_equality.sol#L124)\n", + "id": "e1a99aa0ace22569091c55b4b2c0d5f6b2650aa86a5e65aba3c7fa75ab50c5e9", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad0", + "name": "bad1", "source_mapping": { - "start": 2935, - "length": 59, + "start": 3000, + "length": 66, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 123, - 124, - 125 + 127, + 128, + 129 ], "starting_column": 5, "ending_column": 6 @@ -2206,17 +2307,9 @@ "ending_column": 2 } }, - "signature": "bad0()" + "signature": "bad1()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestSolidityKeyword.bad1() (tests/incorrect_equality.sol#127-129) uses a dangerous strict equality:\n\t- require(bool)(block.number == 0)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(block.number == 0)", @@ -2313,22 +2406,32 @@ } } } - }, + } + ], + "description": "TestSolidityKeyword.bad1() (tests/incorrect_equality.sol#127-129) uses a dangerous strict equality:\n\t- require(bool)(block.number == 0) (tests/incorrect_equality.sol#128)\n", + "markdown": "[TestSolidityKeyword.bad1()](tests/incorrect_equality.sol#L127-L129) uses a dangerous strict equality:\n\t- [require(bool)(block.number == 0)](tests/incorrect_equality.sol#L128)\n", + "id": "554e3fc05ff46a0c04a7a239f3ed04926fd3506208c5badaaebb1dc4ba8ef13a", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" + }, + { + "elements": [ { "type": "function", - "name": "bad1", + "name": "bad2", "source_mapping": { - "start": 3000, - "length": 66, + "start": 3072, + "length": 67, "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_relative": "tests/incorrect_equality.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", "filename_short": "tests/incorrect_equality.sol", "is_dependency": false, "lines": [ - 127, - 128, - 129 + 131, + 132, + 133 ], "starting_column": 5, "ending_column": 6 @@ -2388,17 +2491,9 @@ "ending_column": 2 } }, - "signature": "bad1()" + "signature": "bad2()" } - } - ] - }, - { - "check": "incorrect-equality", - "impact": "Medium", - "confidence": "High", - "description": "TestSolidityKeyword.bad2() (tests/incorrect_equality.sol#131-133) uses a dangerous strict equality:\n\t- require(bool)(block.number == 0)\n", - "elements": [ + }, { "type": "node", "name": "require(bool)(block.number == 0)", @@ -2495,85 +2590,14 @@ } } } - }, - { - "type": "function", - "name": "bad2", - "source_mapping": { - "start": 3072, - "length": 67, - "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", - "filename_relative": "tests/incorrect_equality.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", - "filename_short": "tests/incorrect_equality.sol", - "is_dependency": false, - "lines": [ - 131, - 132, - 133 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "TestSolidityKeyword", - "source_mapping": { - "start": 2368, - "length": 774, - "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", - "filename_relative": "tests/incorrect_equality.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_equality.sol", - "filename_short": "tests/incorrect_equality.sol", - "is_dependency": false, - "lines": [ - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132, - 133, - 134, - 135 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "bad2()" - } } - ] + ], + "description": "TestSolidityKeyword.bad2() (tests/incorrect_equality.sol#131-133) uses a dangerous strict equality:\n\t- require(bool)(block.number == 0) (tests/incorrect_equality.sol#132)\n", + "markdown": "[TestSolidityKeyword.bad2()](tests/incorrect_equality.sol#L131-L133) uses a dangerous strict equality:\n\t- [require(bool)(block.number == 0)](tests/incorrect_equality.sol#L132)\n", + "id": "820ed197f8fa55086981bdcc258e50464c75815e1ec873a426297361ad32e1d8", + "check": "incorrect-equality", + "impact": "Medium", + "confidence": "High" } ] } diff --git a/tests/expected_json/incorrect_equality.incorrect-equality.txt b/tests/expected_json/incorrect_equality.incorrect-equality.txt index afe5a41c4..fedb9a764 100644 --- a/tests/expected_json/incorrect_equality.incorrect-equality.txt +++ b/tests/expected_json/incorrect_equality.incorrect-equality.txt @@ -1,27 +1,27 @@ -INFO:Detectors: + ERC20TestBalance.bad0(ERC20Function) (tests/incorrect_equality.sol#21-23) uses a dangerous strict equality: - - require(bool)(erc.balanceOf(address(this)) == 10) + - require(bool)(erc.balanceOf(address(this)) == 10) (tests/incorrect_equality.sol#22) ERC20TestBalance.bad1(ERC20Variable) (tests/incorrect_equality.sol#25-27) uses a dangerous strict equality: - - require(bool)(erc.balanceOf(msg.sender) == 10) + - require(bool)(erc.balanceOf(msg.sender) == 10) (tests/incorrect_equality.sol#26) TestContractBalance.bad0() (tests/incorrect_equality.sol#32-35) uses a dangerous strict equality: - - require(bool)(address(address(this)).balance == 10000000000000000000) + - require(bool)(address(address(this)).balance == 10000000000000000000) (tests/incorrect_equality.sol#33) TestContractBalance.bad1() (tests/incorrect_equality.sol#37-40) uses a dangerous strict equality: - - require(bool)(10000000000000000000 == address(address(this)).balance) + - require(bool)(10000000000000000000 == address(address(this)).balance) (tests/incorrect_equality.sol#38) TestContractBalance.bad2() (tests/incorrect_equality.sol#42-45) uses a dangerous strict equality: - - require(bool)(address(this).balance == 10000000000000000000) + - require(bool)(address(this).balance == 10000000000000000000) (tests/incorrect_equality.sol#43) TestContractBalance.bad3() (tests/incorrect_equality.sol#47-50) uses a dangerous strict equality: - - require(bool)(10000000000000000000 == address(this).balance) + - require(bool)(10000000000000000000 == address(this).balance) (tests/incorrect_equality.sol#48) TestContractBalance.bad4() (tests/incorrect_equality.sol#52-57) uses a dangerous strict equality: - - balance == 10000000000000000000 + - balance == 10000000000000000000 (tests/incorrect_equality.sol#54) TestContractBalance.bad5() (tests/incorrect_equality.sol#59-64) uses a dangerous strict equality: - - 10000000000000000000 == balance + - 10000000000000000000 == balance (tests/incorrect_equality.sol#61) TestContractBalance.bad6() (tests/incorrect_equality.sol#66-71) uses a dangerous strict equality: - - balance == 10000000000000000000 + - balance == 10000000000000000000 (tests/incorrect_equality.sol#68) TestSolidityKeyword.bad0() (tests/incorrect_equality.sol#123-125) uses a dangerous strict equality: - - require(bool)(now == 0) + - require(bool)(now == 0) (tests/incorrect_equality.sol#124) TestSolidityKeyword.bad1() (tests/incorrect_equality.sol#127-129) uses a dangerous strict equality: - - require(bool)(block.number == 0) + - require(bool)(block.number == 0) (tests/incorrect_equality.sol#128) TestSolidityKeyword.bad2() (tests/incorrect_equality.sol#131-133) uses a dangerous strict equality: - - require(bool)(block.number == 0) + - require(bool)(block.number == 0) (tests/incorrect_equality.sol#132) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities -INFO:Slither:tests/incorrect_equality.sol analyzed (5 contracts), 12 result(s) found +tests/incorrect_equality.sol analyzed (5 contracts with 1 detectors), 12 result(s) found diff --git a/tests/expected_json/incorrect_erc20_interface.erc20-interface.json b/tests/expected_json/incorrect_erc20_interface.erc20-interface.json index c0b6c6b99..a3a58db0e 100644 --- a/tests/expected_json/incorrect_erc20_interface.erc20-interface.json +++ b/tests/expected_json/incorrect_erc20_interface.erc20-interface.json @@ -4,11 +4,32 @@ "results": { "detectors": [ { - "check": "erc20-interface", - "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: transfer(address,uint256) (tests/incorrect_erc20_interface.sol#4)\n", "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 26, + "length": 355, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_relative": "tests/incorrect_erc20_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_short": "tests/incorrect_erc20_interface.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "transfer", @@ -55,14 +76,41 @@ "signature": "transfer(address,uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.transfer(address,uint256) (tests/incorrect_erc20_interface.sol#4)\n", + "markdown": "[Token](tests/incorrect_erc20_interface.sol#L3-L10) has incorrect ERC20 function interface:[Token.transfer(address,uint256)](tests/incorrect_erc20_interface.sol#L4)\n", + "id": "d3df2e48ae6e8a1b05b275de574b480853a0839c272ce889e8a1664ae432698e", "check": "erc20-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: approve(address,uint256) (tests/incorrect_erc20_interface.sol#5)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 26, + "length": 355, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_relative": "tests/incorrect_erc20_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_short": "tests/incorrect_erc20_interface.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "approve", @@ -109,14 +157,41 @@ "signature": "approve(address,uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.approve(address,uint256) (tests/incorrect_erc20_interface.sol#5)\n", + "markdown": "[Token](tests/incorrect_erc20_interface.sol#L3-L10) has incorrect ERC20 function interface:[Token.approve(address,uint256)](tests/incorrect_erc20_interface.sol#L5)\n", + "id": "0fced3029cf59cf348a6b79c58dbb032d837fdd5a5f355600edebda1878e9e2e", "check": "erc20-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: transferFrom(address,address,uint256) (tests/incorrect_erc20_interface.sol#6)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 26, + "length": 355, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_relative": "tests/incorrect_erc20_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_short": "tests/incorrect_erc20_interface.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "transferFrom", @@ -163,14 +238,41 @@ "signature": "transferFrom(address,address,uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.transferFrom(address,address,uint256) (tests/incorrect_erc20_interface.sol#6)\n", + "markdown": "[Token](tests/incorrect_erc20_interface.sol#L3-L10) has incorrect ERC20 function interface:[Token.transferFrom(address,address,uint256)](tests/incorrect_erc20_interface.sol#L6)\n", + "id": "ba13a1588595032984a3fad39610a2414bb8fcb522d1e632d52fa947ff207d73", "check": "erc20-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: totalSupply() (tests/incorrect_erc20_interface.sol#7)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 26, + "length": 355, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_relative": "tests/incorrect_erc20_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_short": "tests/incorrect_erc20_interface.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "totalSupply", @@ -217,14 +319,41 @@ "signature": "totalSupply()" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.totalSupply() (tests/incorrect_erc20_interface.sol#7)\n", + "markdown": "[Token](tests/incorrect_erc20_interface.sol#L3-L10) has incorrect ERC20 function interface:[Token.totalSupply()](tests/incorrect_erc20_interface.sol#L7)\n", + "id": "c951e429e546af28ac08e241d391e874c1c9c70b0732ccfb63f3bbfb3eaac16e", "check": "erc20-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: balanceOf(address) (tests/incorrect_erc20_interface.sol#8)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 26, + "length": 355, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_relative": "tests/incorrect_erc20_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_short": "tests/incorrect_erc20_interface.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "balanceOf", @@ -271,14 +400,41 @@ "signature": "balanceOf(address)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.balanceOf(address) (tests/incorrect_erc20_interface.sol#8)\n", + "markdown": "[Token](tests/incorrect_erc20_interface.sol#L3-L10) has incorrect ERC20 function interface:[Token.balanceOf(address)](tests/incorrect_erc20_interface.sol#L8)\n", + "id": "758ca2456030a36dbd6115f2ccb1a43f53f1dabd66ed079806df0f6b7b4d21ef", "check": "erc20-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: allowance(address,address) (tests/incorrect_erc20_interface.sol#9)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 26, + "length": 355, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_relative": "tests/incorrect_erc20_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc20_interface.sol", + "filename_short": "tests/incorrect_erc20_interface.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "allowance", @@ -325,7 +481,13 @@ "signature": "allowance(address,address)" } } - ] + ], + "description": "Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.allowance(address,address) (tests/incorrect_erc20_interface.sol#9)\n", + "markdown": "[Token](tests/incorrect_erc20_interface.sol#L3-L10) has incorrect ERC20 function interface:[Token.allowance(address,address)](tests/incorrect_erc20_interface.sol#L9)\n", + "id": "1286abfe21b09e21e1cec8b991f73664e104fa39f7f4190690ece3af45bc0c7a", + "check": "erc20-interface", + "impact": "Medium", + "confidence": "High" } ] } diff --git a/tests/expected_json/incorrect_erc20_interface.erc20-interface.txt b/tests/expected_json/incorrect_erc20_interface.erc20-interface.txt index 75b71be01..24bcc5276 100644 --- a/tests/expected_json/incorrect_erc20_interface.erc20-interface.txt +++ b/tests/expected_json/incorrect_erc20_interface.erc20-interface.txt @@ -1,9 +1,9 @@ -INFO:Detectors: -Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: transfer(address,uint256) (tests/incorrect_erc20_interface.sol#4) -Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: approve(address,uint256) (tests/incorrect_erc20_interface.sol#5) -Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: transferFrom(address,address,uint256) (tests/incorrect_erc20_interface.sol#6) -Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: totalSupply() (tests/incorrect_erc20_interface.sol#7) -Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: balanceOf(address) (tests/incorrect_erc20_interface.sol#8) -Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface: allowance(address,address) (tests/incorrect_erc20_interface.sol#9) + +Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.transfer(address,uint256) (tests/incorrect_erc20_interface.sol#4) +Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.approve(address,uint256) (tests/incorrect_erc20_interface.sol#5) +Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.transferFrom(address,address,uint256) (tests/incorrect_erc20_interface.sol#6) +Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.totalSupply() (tests/incorrect_erc20_interface.sol#7) +Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.balanceOf(address) (tests/incorrect_erc20_interface.sol#8) +Token (tests/incorrect_erc20_interface.sol#3-10) has incorrect ERC20 function interface:Token.allowance(address,address) (tests/incorrect_erc20_interface.sol#9) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface -INFO:Slither:tests/incorrect_erc20_interface.sol analyzed (1 contracts), 6 result(s) found +tests/incorrect_erc20_interface.sol analyzed (1 contracts with 1 detectors), 6 result(s) found diff --git a/tests/expected_json/incorrect_erc721_interface.erc721-interface.json b/tests/expected_json/incorrect_erc721_interface.erc721-interface.json index 528be3fc0..075eac2a4 100644 --- a/tests/expected_json/incorrect_erc721_interface.erc721-interface.json +++ b/tests/expected_json/incorrect_erc721_interface.erc721-interface.json @@ -4,11 +4,35 @@ "results": { "detectors": [ { - "check": "erc721-interface", - "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: supportsInterface(bytes4) (tests/incorrect_erc721_interface.sol#4)\n", "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "supportsInterface", @@ -50,14 +74,44 @@ "signature": "supportsInterface(bytes4)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:IERC165.supportsInterface(bytes4) (tests/incorrect_erc721_interface.sol#4)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[IERC165.supportsInterface(bytes4)](tests/incorrect_erc721_interface.sol#L4)\n", + "id": "a8593587ca70c51a9ab827843babec3b3eb7f9a08d76eea1e5528e668f7b291d", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: balanceOf(address) (tests/incorrect_erc721_interface.sol#7)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "balanceOf", @@ -107,14 +161,44 @@ "signature": "balanceOf(address)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.balanceOf(address) (tests/incorrect_erc721_interface.sol#7)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.balanceOf(address)](tests/incorrect_erc721_interface.sol#L7)\n", + "id": "6fb9d0320e0b63e2c70f9844d5bea2be958e73beb6eaa4ccb2323ead0c7ef991", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: ownerOf(uint256) (tests/incorrect_erc721_interface.sol#8)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "ownerOf", @@ -164,14 +248,44 @@ "signature": "ownerOf(uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.ownerOf(uint256) (tests/incorrect_erc721_interface.sol#8)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.ownerOf(uint256)](tests/incorrect_erc721_interface.sol#L8)\n", + "id": "7d9235dd4ef8bc29a3b7700597cc1e4efb846377c928e5e50c5f49cb37f288d2", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: safeTransferFrom(address,address,uint256,bytes) (tests/incorrect_erc721_interface.sol#9)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "safeTransferFrom", @@ -221,14 +335,44 @@ "signature": "safeTransferFrom(address,address,uint256,bytes)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.safeTransferFrom(address,address,uint256,bytes) (tests/incorrect_erc721_interface.sol#9)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.safeTransferFrom(address,address,uint256,bytes)](tests/incorrect_erc721_interface.sol#L9)\n", + "id": "ccec612c4b5db00ab59b766b5dde3f8d3a8c6408ef595ab08bff21628587e2a1", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: safeTransferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#10)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "safeTransferFrom", @@ -278,14 +422,44 @@ "signature": "safeTransferFrom(address,address,uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.safeTransferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#10)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.safeTransferFrom(address,address,uint256)](tests/incorrect_erc721_interface.sol#L10)\n", + "id": "50ab7b0f39f327ac6deccf3c16b4e6fee1dc249072ac41a4bd485ccf0c12315b", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: transferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#11)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "transferFrom", @@ -335,14 +509,44 @@ "signature": "transferFrom(address,address,uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.transferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#11)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.transferFrom(address,address,uint256)](tests/incorrect_erc721_interface.sol#L11)\n", + "id": "847b11227f3bfc9b120e0ea573f385a4bbc61c4b7f89f434864612a679b1133e", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: approve(address,uint256) (tests/incorrect_erc721_interface.sol#12)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "approve", @@ -392,14 +596,44 @@ "signature": "approve(address,uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.approve(address,uint256) (tests/incorrect_erc721_interface.sol#12)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.approve(address,uint256)](tests/incorrect_erc721_interface.sol#L12)\n", + "id": "439c95972d0e084aff057161164b13ab63f85bee31d80b568b7155e58eac4b5d", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: setApprovalForAll(address,bool) (tests/incorrect_erc721_interface.sol#13)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "setApprovalForAll", @@ -449,14 +683,44 @@ "signature": "setApprovalForAll(address,bool)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.setApprovalForAll(address,bool) (tests/incorrect_erc721_interface.sol#13)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.setApprovalForAll(address,bool)](tests/incorrect_erc721_interface.sol#L13)\n", + "id": "b95e9bb000fb073c25fdbd9fff7bf0a3c44e04e70fc1a7da27c94c6b7fb8be40", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: getApproved(uint256) (tests/incorrect_erc721_interface.sol#14)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "getApproved", @@ -506,14 +770,44 @@ "signature": "getApproved(uint256)" } } - ] - }, - { + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.getApproved(uint256) (tests/incorrect_erc721_interface.sol#14)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.getApproved(uint256)](tests/incorrect_erc721_interface.sol#L14)\n", + "id": "2dce4891c7abea0fa8a8a20a8b8482e7e1d46d54bfd750701c604d5dadd8b937", "check": "erc721-interface", "impact": "Medium", - "confidence": "High", - "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: isApprovedForAll(address,address) (tests/incorrect_erc721_interface.sol#15)\n", + "confidence": "High" + }, + { "elements": [ + { + "type": "contract", + "name": "Token", + "source_mapping": { + "start": 109, + "length": 739, + "filename_used": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_relative": "tests/incorrect_erc721_interface.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/incorrect_erc721_interface.sol", + "filename_short": "tests/incorrect_erc721_interface.sol", + "is_dependency": false, + "lines": [ + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "starting_column": 1, + "ending_column": 2 + } + }, { "type": "function", "name": "isApprovedForAll", @@ -563,7 +857,13 @@ "signature": "isApprovedForAll(address,address)" } } - ] + ], + "description": "Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.isApprovedForAll(address,address) (tests/incorrect_erc721_interface.sol#15)\n", + "markdown": "[Token](tests/incorrect_erc721_interface.sol#L6-L16) has incorrect ERC721 function interface:[Token.isApprovedForAll(address,address)](tests/incorrect_erc721_interface.sol#L15)\n", + "id": "fa9985c505689f9a45d1ac51e1dd8cf79eeb2c939946abfb5ac78f46e692d0eb", + "check": "erc721-interface", + "impact": "Medium", + "confidence": "High" } ] } diff --git a/tests/expected_json/incorrect_erc721_interface.erc721-interface.txt b/tests/expected_json/incorrect_erc721_interface.erc721-interface.txt index cfd388267..4a82240e2 100644 --- a/tests/expected_json/incorrect_erc721_interface.erc721-interface.txt +++ b/tests/expected_json/incorrect_erc721_interface.erc721-interface.txt @@ -1,13 +1,13 @@ -INFO:Detectors: -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: supportsInterface(bytes4) (tests/incorrect_erc721_interface.sol#4) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: balanceOf(address) (tests/incorrect_erc721_interface.sol#7) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: ownerOf(uint256) (tests/incorrect_erc721_interface.sol#8) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: safeTransferFrom(address,address,uint256,bytes) (tests/incorrect_erc721_interface.sol#9) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: safeTransferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#10) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: transferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#11) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: approve(address,uint256) (tests/incorrect_erc721_interface.sol#12) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: setApprovalForAll(address,bool) (tests/incorrect_erc721_interface.sol#13) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: getApproved(uint256) (tests/incorrect_erc721_interface.sol#14) -Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface: isApprovedForAll(address,address) (tests/incorrect_erc721_interface.sol#15) + +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:IERC165.supportsInterface(bytes4) (tests/incorrect_erc721_interface.sol#4) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.balanceOf(address) (tests/incorrect_erc721_interface.sol#7) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.ownerOf(uint256) (tests/incorrect_erc721_interface.sol#8) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.safeTransferFrom(address,address,uint256,bytes) (tests/incorrect_erc721_interface.sol#9) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.safeTransferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#10) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.transferFrom(address,address,uint256) (tests/incorrect_erc721_interface.sol#11) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.approve(address,uint256) (tests/incorrect_erc721_interface.sol#12) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.setApprovalForAll(address,bool) (tests/incorrect_erc721_interface.sol#13) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.getApproved(uint256) (tests/incorrect_erc721_interface.sol#14) +Token (tests/incorrect_erc721_interface.sol#6-16) has incorrect ERC721 function interface:Token.isApprovedForAll(address,address) (tests/incorrect_erc721_interface.sol#15) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface -INFO:Slither:tests/incorrect_erc721_interface.sol analyzed (2 contracts), 10 result(s) found +tests/incorrect_erc721_interface.sol analyzed (2 contracts with 1 detectors), 10 result(s) found diff --git a/tests/expected_json/inline_assembly_contract-0.5.1.assembly.json b/tests/expected_json/inline_assembly_contract-0.5.1.assembly.json index 24790e712..6e8fddd29 100644 --- a/tests/expected_json/inline_assembly_contract-0.5.1.assembly.json +++ b/tests/expected_json/inline_assembly_contract-0.5.1.assembly.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "assembly", - "impact": "Informational", - "confidence": "High", - "description": "GetCode.at(address) uses assembly (tests/inline_assembly_contract-0.5.1.sol#6-20)\n\t- tests/inline_assembly_contract-0.5.1.sol#7-20\n", "elements": [ { "type": "function", @@ -180,7 +176,13 @@ } } } - ] + ], + "description": "GetCode.at(address) (tests/inline_assembly_contract-0.5.1.sol#6-20) uses assembly\n\t- INLINE ASM None (tests/inline_assembly_contract-0.5.1.sol#7-20)\n", + "markdown": "[GetCode.at(address)](tests/inline_assembly_contract-0.5.1.sol#L6-L20) uses assembly\n\t- [INLINE ASM None](tests/inline_assembly_contract-0.5.1.sol#L7-L20)\n", + "id": "8f8a296feb87f8865449404161841580a3bb405864ed5f2622fdffdbe6a93ad8", + "check": "assembly", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/inline_assembly_contract-0.5.1.assembly.txt b/tests/expected_json/inline_assembly_contract-0.5.1.assembly.txt index f832cfe46..3fe0a4672 100644 --- a/tests/expected_json/inline_assembly_contract-0.5.1.assembly.txt +++ b/tests/expected_json/inline_assembly_contract-0.5.1.assembly.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -GetCode.at(address) uses assembly (tests/inline_assembly_contract-0.5.1.sol#6-20) - - tests/inline_assembly_contract-0.5.1.sol#7-20 + +GetCode.at(address) (tests/inline_assembly_contract-0.5.1.sol#6-20) uses assembly + - INLINE ASM None (tests/inline_assembly_contract-0.5.1.sol#7-20) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage -INFO:Slither:tests/inline_assembly_contract-0.5.1.sol analyzed (1 contracts), 1 result(s) found +tests/inline_assembly_contract-0.5.1.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/inline_assembly_contract.assembly.json b/tests/expected_json/inline_assembly_contract.assembly.json index 75e439a7e..333d9a94c 100644 --- a/tests/expected_json/inline_assembly_contract.assembly.json +++ b/tests/expected_json/inline_assembly_contract.assembly.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "assembly", - "impact": "Informational", - "confidence": "High", - "description": "GetCode.at(address) uses assembly (tests/inline_assembly_contract.sol#6-20)\n\t- tests/inline_assembly_contract.sol#7-20\n", "elements": [ { "type": "function", @@ -180,7 +176,13 @@ } } } - ] + ], + "description": "GetCode.at(address) (tests/inline_assembly_contract.sol#6-20) uses assembly\n\t- INLINE ASM None (tests/inline_assembly_contract.sol#7-20)\n", + "markdown": "[GetCode.at(address)](tests/inline_assembly_contract.sol#L6-L20) uses assembly\n\t- [INLINE ASM None](tests/inline_assembly_contract.sol#L7-L20)\n", + "id": "8f448cbd104ddfcc1c099119fcb367b581476208f7cc3ab5897f4a9b5b4a9080", + "check": "assembly", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/inline_assembly_contract.assembly.txt b/tests/expected_json/inline_assembly_contract.assembly.txt index 78c4fa50c..63450506c 100644 --- a/tests/expected_json/inline_assembly_contract.assembly.txt +++ b/tests/expected_json/inline_assembly_contract.assembly.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -GetCode.at(address) uses assembly (tests/inline_assembly_contract.sol#6-20) - - tests/inline_assembly_contract.sol#7-20 + +GetCode.at(address) (tests/inline_assembly_contract.sol#6-20) uses assembly + - INLINE ASM None (tests/inline_assembly_contract.sol#7-20) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage -INFO:Slither:tests/inline_assembly_contract.sol analyzed (1 contracts), 1 result(s) found +tests/inline_assembly_contract.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/inline_assembly_library-0.5.1.assembly.json b/tests/expected_json/inline_assembly_library-0.5.1.assembly.json index bd9b4d97f..db8469ec5 100644 --- a/tests/expected_json/inline_assembly_library-0.5.1.assembly.json +++ b/tests/expected_json/inline_assembly_library-0.5.1.assembly.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "assembly", - "impact": "Informational", - "confidence": "High", - "description": "VectorSum.sumAsm(uint256[]) uses assembly (tests/inline_assembly_library-0.5.1.sol#16-22)\n\t- tests/inline_assembly_library-0.5.1.sol#18-21\n", "elements": [ { "type": "function", @@ -208,13 +204,15 @@ } } } - ] - }, - { + ], + "description": "VectorSum.sumAsm(uint256[]) (tests/inline_assembly_library-0.5.1.sol#16-22) uses assembly\n\t- INLINE ASM None (tests/inline_assembly_library-0.5.1.sol#18-21)\n", + "markdown": "[VectorSum.sumAsm(uint256[])](tests/inline_assembly_library-0.5.1.sol#L16-L22) uses assembly\n\t- [INLINE ASM None](tests/inline_assembly_library-0.5.1.sol#L18-L21)\n", + "id": "2d646463bf10753d2433783ea4a212d84dfb51269bb062f4ea5db21bd8ec6cb6", "check": "assembly", "impact": "Informational", - "confidence": "High", - "description": "VectorSum.sumPureAsm(uint256[]) uses assembly (tests/inline_assembly_library-0.5.1.sol#25-47)\n\t- tests/inline_assembly_library-0.5.1.sol#26-47\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -465,7 +463,13 @@ } } } - ] + ], + "description": "VectorSum.sumPureAsm(uint256[]) (tests/inline_assembly_library-0.5.1.sol#25-47) uses assembly\n\t- INLINE ASM None (tests/inline_assembly_library-0.5.1.sol#26-47)\n", + "markdown": "[VectorSum.sumPureAsm(uint256[])](tests/inline_assembly_library-0.5.1.sol#L25-L47) uses assembly\n\t- [INLINE ASM None](tests/inline_assembly_library-0.5.1.sol#L26-L47)\n", + "id": "df1118184108ae4b7b7300da137ebfc86c3279fcaffabf289d8e79bc23228105", + "check": "assembly", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/inline_assembly_library-0.5.1.assembly.txt b/tests/expected_json/inline_assembly_library-0.5.1.assembly.txt index 5624ad2cf..ce08b243b 100644 --- a/tests/expected_json/inline_assembly_library-0.5.1.assembly.txt +++ b/tests/expected_json/inline_assembly_library-0.5.1.assembly.txt @@ -1,7 +1,7 @@ -INFO:Detectors: -VectorSum.sumAsm(uint256[]) uses assembly (tests/inline_assembly_library-0.5.1.sol#16-22) - - tests/inline_assembly_library-0.5.1.sol#18-21 -VectorSum.sumPureAsm(uint256[]) uses assembly (tests/inline_assembly_library-0.5.1.sol#25-47) - - tests/inline_assembly_library-0.5.1.sol#26-47 + +VectorSum.sumAsm(uint256[]) (tests/inline_assembly_library-0.5.1.sol#16-22) uses assembly + - INLINE ASM None (tests/inline_assembly_library-0.5.1.sol#18-21) +VectorSum.sumPureAsm(uint256[]) (tests/inline_assembly_library-0.5.1.sol#25-47) uses assembly + - INLINE ASM None (tests/inline_assembly_library-0.5.1.sol#26-47) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage -INFO:Slither:tests/inline_assembly_library-0.5.1.sol analyzed (1 contracts), 2 result(s) found +tests/inline_assembly_library-0.5.1.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/inline_assembly_library.assembly.json b/tests/expected_json/inline_assembly_library.assembly.json index 5c1230028..72e621970 100644 --- a/tests/expected_json/inline_assembly_library.assembly.json +++ b/tests/expected_json/inline_assembly_library.assembly.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "assembly", - "impact": "Informational", - "confidence": "High", - "description": "VectorSum.sumAsm(uint256[]) uses assembly (tests/inline_assembly_library.sol#16-22)\n\t- tests/inline_assembly_library.sol#18-21\n", "elements": [ { "type": "function", @@ -208,13 +204,15 @@ } } } - ] - }, - { + ], + "description": "VectorSum.sumAsm(uint256[]) (tests/inline_assembly_library.sol#16-22) uses assembly\n\t- INLINE ASM None (tests/inline_assembly_library.sol#18-21)\n", + "markdown": "[VectorSum.sumAsm(uint256[])](tests/inline_assembly_library.sol#L16-L22) uses assembly\n\t- [INLINE ASM None](tests/inline_assembly_library.sol#L18-L21)\n", + "id": "3ff39c86f26b81703ac5df9ce9d176a0a2c9948abccc76da3768272588588c04", "check": "assembly", "impact": "Informational", - "confidence": "High", - "description": "VectorSum.sumPureAsm(uint256[]) uses assembly (tests/inline_assembly_library.sol#25-47)\n\t- tests/inline_assembly_library.sol#26-47\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -465,7 +463,13 @@ } } } - ] + ], + "description": "VectorSum.sumPureAsm(uint256[]) (tests/inline_assembly_library.sol#25-47) uses assembly\n\t- INLINE ASM None (tests/inline_assembly_library.sol#26-47)\n", + "markdown": "[VectorSum.sumPureAsm(uint256[])](tests/inline_assembly_library.sol#L25-L47) uses assembly\n\t- [INLINE ASM None](tests/inline_assembly_library.sol#L26-L47)\n", + "id": "423795860d3892652fcf5005055c4781586100070e0ecdffb7ca1014c3958191", + "check": "assembly", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/inline_assembly_library.assembly.txt b/tests/expected_json/inline_assembly_library.assembly.txt index 8bc566c04..2700f394b 100644 --- a/tests/expected_json/inline_assembly_library.assembly.txt +++ b/tests/expected_json/inline_assembly_library.assembly.txt @@ -1,7 +1,7 @@ -INFO:Detectors: -VectorSum.sumAsm(uint256[]) uses assembly (tests/inline_assembly_library.sol#16-22) - - tests/inline_assembly_library.sol#18-21 -VectorSum.sumPureAsm(uint256[]) uses assembly (tests/inline_assembly_library.sol#25-47) - - tests/inline_assembly_library.sol#26-47 + +VectorSum.sumAsm(uint256[]) (tests/inline_assembly_library.sol#16-22) uses assembly + - INLINE ASM None (tests/inline_assembly_library.sol#18-21) +VectorSum.sumPureAsm(uint256[]) (tests/inline_assembly_library.sol#25-47) uses assembly + - INLINE ASM None (tests/inline_assembly_library.sol#26-47) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage -INFO:Slither:tests/inline_assembly_library.sol analyzed (1 contracts), 2 result(s) found +tests/inline_assembly_library.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/locked_ether-0.5.1.locked-ether.json b/tests/expected_json/locked_ether-0.5.1.locked-ether.json index d848114fd..f8ef8749d 100644 --- a/tests/expected_json/locked_ether-0.5.1.locked-ether.json +++ b/tests/expected_json/locked_ether-0.5.1.locked-ether.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "locked-ether", - "impact": "Medium", - "confidence": "High", - "description": "Contract locking ether found in :\n\tContract OnlyLocked has payable functions:\n\t - receive (tests/locked_ether-0.5.1.sol#4-6)\n\tBut does not have a function to withdraw the ether\n", "elements": [ { "type": "contract", @@ -74,7 +70,13 @@ "signature": "receive()" } } - ] + ], + "description": "Contract locking ether found in :\n\tContract OnlyLocked (tests/locked_ether-0.5.1.sol#26) has payable functions:\n\t - Locked.receive() (tests/locked_ether-0.5.1.sol#4-6)\n\tBut does not have a function to withdraw the ether\n", + "markdown": "Contract locking ether found in :\n\tContract [OnlyLocked](tests/locked_ether-0.5.1.sol#L26) has payable functions:\n\t - [Locked.receive()](tests/locked_ether-0.5.1.sol#L4-L6)\n\tBut does not have a function to withdraw the ether\n", + "id": "38b2d47301e59ffa598ec48a8e874e6a7070d6cf4e4ab2909f33a8b72fc28a4b", + "check": "locked-ether", + "impact": "Medium", + "confidence": "High" } ] } diff --git a/tests/expected_json/locked_ether-0.5.1.locked-ether.txt b/tests/expected_json/locked_ether-0.5.1.locked-ether.txt index 1d0fa6d3b..932326656 100644 --- a/tests/expected_json/locked_ether-0.5.1.locked-ether.txt +++ b/tests/expected_json/locked_ether-0.5.1.locked-ether.txt @@ -1,7 +1,7 @@ -INFO:Detectors: + Contract locking ether found in : - Contract OnlyLocked has payable functions: - - receive (tests/locked_ether-0.5.1.sol#4-6) + Contract OnlyLocked (tests/locked_ether-0.5.1.sol#26) has payable functions: + - Locked.receive() (tests/locked_ether-0.5.1.sol#4-6) But does not have a function to withdraw the ether Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether -INFO:Slither:tests/locked_ether-0.5.1.sol analyzed (4 contracts), 1 result(s) found +tests/locked_ether-0.5.1.sol analyzed (4 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/locked_ether.locked-ether.json b/tests/expected_json/locked_ether.locked-ether.json index 1daa8631b..07f410f9d 100644 --- a/tests/expected_json/locked_ether.locked-ether.json +++ b/tests/expected_json/locked_ether.locked-ether.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "locked-ether", - "impact": "Medium", - "confidence": "High", - "description": "Contract locking ether found in :\n\tContract OnlyLocked has payable functions:\n\t - receive (tests/locked_ether.sol#4-6)\n\tBut does not have a function to withdraw the ether\n", "elements": [ { "type": "contract", @@ -74,7 +70,13 @@ "signature": "receive()" } } - ] + ], + "description": "Contract locking ether found in :\n\tContract OnlyLocked (tests/locked_ether.sol#26) has payable functions:\n\t - Locked.receive() (tests/locked_ether.sol#4-6)\n\tBut does not have a function to withdraw the ether\n", + "markdown": "Contract locking ether found in :\n\tContract [OnlyLocked](tests/locked_ether.sol#L26) has payable functions:\n\t - [Locked.receive()](tests/locked_ether.sol#L4-L6)\n\tBut does not have a function to withdraw the ether\n", + "id": "38b2d47301e59ffa598ec48a8e874e6a7070d6cf4e4ab2909f33a8b72fc28a4b", + "check": "locked-ether", + "impact": "Medium", + "confidence": "High" } ] } diff --git a/tests/expected_json/locked_ether.locked-ether.txt b/tests/expected_json/locked_ether.locked-ether.txt index a27ff383b..304347703 100644 --- a/tests/expected_json/locked_ether.locked-ether.txt +++ b/tests/expected_json/locked_ether.locked-ether.txt @@ -1,7 +1,7 @@ -INFO:Detectors: + Contract locking ether found in : - Contract OnlyLocked has payable functions: - - receive (tests/locked_ether.sol#4-6) + Contract OnlyLocked (tests/locked_ether.sol#26) has payable functions: + - Locked.receive() (tests/locked_ether.sol#4-6) But does not have a function to withdraw the ether Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether -INFO:Slither:tests/locked_ether.sol analyzed (4 contracts), 1 result(s) found +tests/locked_ether.sol analyzed (4 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/low_level_calls.low-level-calls.json b/tests/expected_json/low_level_calls.low-level-calls.json index 71a20c4fe..90a8fea54 100644 --- a/tests/expected_json/low_level_calls.low-level-calls.json +++ b/tests/expected_json/low_level_calls.low-level-calls.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "low-level-calls", - "impact": "Informational", - "confidence": "High", - "description": "Low level call in Sender.send(address) (tests/low_level_calls.sol#5-7):\n\t-_receiver.call.value(msg.value).gas(7777)() tests/low_level_calls.sol#6\n", "elements": [ { "type": "function", @@ -119,7 +115,13 @@ } } } - ] + ], + "description": "Low level call in Sender.send(address) (tests/low_level_calls.sol#5-7):\n\t- _receiver.call.value(msg.value).gas(7777)() (tests/low_level_calls.sol#6)\n", + "markdown": "Low level call in [Sender.send(address)](tests/low_level_calls.sol#L5-L7):\n\t- [_receiver.call.value(msg.value).gas(7777)()](tests/low_level_calls.sol#L6)\n", + "id": "5cb382cdae5ceb68ac0c67a88d9233a949dc990d5899578415da8139804cf149", + "check": "low-level-calls", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/low_level_calls.low-level-calls.txt b/tests/expected_json/low_level_calls.low-level-calls.txt index db7034d4a..ab94fa3e0 100644 --- a/tests/expected_json/low_level_calls.low-level-calls.txt +++ b/tests/expected_json/low_level_calls.low-level-calls.txt @@ -1,5 +1,5 @@ -INFO:Detectors: + Low level call in Sender.send(address) (tests/low_level_calls.sol#5-7): - -_receiver.call.value(msg.value).gas(7777)() tests/low_level_calls.sol#6 + - _receiver.call.value(msg.value).gas(7777)() (tests/low_level_calls.sol#6) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls -INFO:Slither:tests/low_level_calls.sol analyzed (2 contracts), 1 result(s) found +tests/low_level_calls.sol analyzed (2 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/multiple_calls_in_loop.calls-loop.json b/tests/expected_json/multiple_calls_in_loop.calls-loop.json index d08dfb95d..b2779d505 100644 --- a/tests/expected_json/multiple_calls_in_loop.calls-loop.json +++ b/tests/expected_json/multiple_calls_in_loop.calls-loop.json @@ -4,11 +4,64 @@ "results": { "detectors": [ { - "check": "calls-loop", - "impact": "Low", - "confidence": "Medium", - "description": "CallInLoop.bad() has external calls inside a loop: \"destinations[i].transfer(i)\" (tests/multiple_calls_in_loop.sol#11)\n", "elements": [ + { + "type": "function", + "name": "bad", + "source_mapping": { + "start": 153, + "length": 135, + "filename_used": "/home/travis/build/crytic/slither/tests/multiple_calls_in_loop.sol", + "filename_relative": "tests/multiple_calls_in_loop.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/multiple_calls_in_loop.sol", + "filename_short": "tests/multiple_calls_in_loop.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11, + 12, + 13 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "CallInLoop", + "source_mapping": { + "start": 0, + "length": 291, + "filename_used": "/home/travis/build/crytic/slither/tests/multiple_calls_in_loop.sol", + "filename_relative": "tests/multiple_calls_in_loop.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/multiple_calls_in_loop.sol", + "filename_short": "tests/multiple_calls_in_loop.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad()" + } + }, { "type": "node", "name": "destinations[i].transfer(i)", @@ -86,7 +139,13 @@ } } } - ] + ], + "description": "CallInLoop.bad() (tests/multiple_calls_in_loop.sol#9-13) has external calls inside a loop: destinations[i].transfer(i) (tests/multiple_calls_in_loop.sol#11)\n", + "markdown": "[CallInLoop.bad()](tests/multiple_calls_in_loop.sol#L9-L13) has external calls inside a loop: [destinations[i].transfer(i)](tests/multiple_calls_in_loop.sol#L11)\n", + "id": "70793ed549e12ccf0735e919c865f639eec14665073037a97794396066e650ed", + "check": "calls-loop", + "impact": "Low", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/multiple_calls_in_loop.calls-loop.txt b/tests/expected_json/multiple_calls_in_loop.calls-loop.txt index 7803df6de..1114957d7 100644 --- a/tests/expected_json/multiple_calls_in_loop.calls-loop.txt +++ b/tests/expected_json/multiple_calls_in_loop.calls-loop.txt @@ -1,4 +1,4 @@ -INFO:Detectors: -CallInLoop.bad() has external calls inside a loop: "destinations[i].transfer(i)" (tests/multiple_calls_in_loop.sol#11) -Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop -INFO:Slither:tests/multiple_calls_in_loop.sol analyzed (1 contracts), 1 result(s) found + +CallInLoop.bad() (tests/multiple_calls_in_loop.sol#9-13) has external calls inside a loop: destinations[i].transfer(i) (tests/multiple_calls_in_loop.sol#11) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop +tests/multiple_calls_in_loop.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index 794250fcc..9e9d4d317 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "naming-convention", - "impact": "Informational", - "confidence": "High", - "description": "Contract 'naming' (tests/naming_convention.sol#3-48) is not in CapWords\n", "elements": [ { "type": "contract", @@ -76,13 +72,15 @@ "convention": "CapWords" } } - ] - }, - { + ], + "description": "Contract naming (tests/naming_convention.sol#3-48) is not in CapWords\n", + "markdown": "Contract [naming](tests/naming_convention.sol#L3-L48) is not in CapWords\n", + "id": "7247d550fb327e3aeb21c82714137e5b45a7e9eeaa6a1bc878102c8081033f85", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Struct 'naming.test' (tests/naming_convention.sol#14-16) is not in CapWords\n", + "confidence": "High" + }, + { "elements": [ { "type": "struct", @@ -173,13 +171,15 @@ "convention": "CapWords" } } - ] - }, - { + ], + "description": "Struct naming.test (tests/naming_convention.sol#14-16) is not in CapWords\n", + "markdown": "Struct [naming.test](tests/naming_convention.sol#L14-L16) is not in CapWords\n", + "id": "0ef3ea412cb30b1f0df5fa2af4a7a06e2bf0373fae0770fd9e301aed12c209cf", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Event 'namingevent_(uint256)' (tests/naming_convention.sol#23) is not in CapWords\n", + "confidence": "High" + }, + { "elements": [ { "type": "event", @@ -269,13 +269,15 @@ "convention": "CapWords" } } - ] - }, - { + ], + "description": "Event namingevent_(uint256) (tests/naming_convention.sol#23) is not in CapWords\n", + "markdown": "Event [namingevent_(uint256)](tests/naming_convention.sol#L23) is not in CapWords\n", + "id": "978ecf4a2c8b96d947e60f6601cf60d0e25e07ebe80ebbc37a7e7f279afd1405", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Function 'naming.GetOne()' (tests/naming_convention.sol#30-33) is not in mixedCase\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -368,13 +370,15 @@ "convention": "mixedCase" } } - ] - }, - { + ], + "description": "Function naming.GetOne() (tests/naming_convention.sol#30-33) is not in mixedCase\n", + "markdown": "Function [naming.GetOne()](tests/naming_convention.sol#L30-L33) is not in mixedCase\n", + "id": "bf6f97d6a82b84284efdade52d01bd6112007426e2e88d1568190d63c5c4a049", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Parameter 'Number2' of Number2 (tests/naming_convention.sol#35) is not in mixedCase\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -487,13 +491,15 @@ "convention": "mixedCase" } } - ] - }, - { + ], + "description": "Parameter naming.setInt(uint256,uint256).Number2 (tests/naming_convention.sol#35) is not in mixedCase\n", + "markdown": "Parameter [naming.setInt(uint256,uint256).Number2](tests/naming_convention.sol#L35) is not in mixedCase\n", + "id": "f03bff0b488524254e19ff7d688d34211cd2f29934e22417c9f1fa43fc4a08ad", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Constant 'naming.MY_other_CONSTANT' (tests/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -582,13 +588,15 @@ "convention": "UPPER_CASE_WITH_UNDERSCORES" } } - ] - }, - { + ], + "description": "Constant naming.MY_other_CONSTANT (tests/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES\n", + "markdown": "Constant [naming.MY_other_CONSTANT](tests/naming_convention.sol#L9) is not in UPPER_CASE_WITH_UNDERSCORES\n", + "id": "596c2e8064f8f2df55cd5c878eb59c0a74ac7f20719c420d8af307f2431a1a90", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Variable 'naming.Var_One' (tests/naming_convention.sol#11) is not in mixedCase\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -677,13 +685,15 @@ "convention": "mixedCase" } } - ] - }, - { + ], + "description": "Variable naming.Var_One (tests/naming_convention.sol#11) is not in mixedCase\n", + "markdown": "Variable [naming.Var_One](tests/naming_convention.sol#L11) is not in mixedCase\n", + "id": "34b7c817201b3f3086fc3541f140898d9e9aabe999b1c0a6ef8639ec04351f26", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Enum 'naming.numbers' (tests/naming_convention.sol#6) is not in CapWords\n", + "confidence": "High" + }, + { "elements": [ { "type": "enum", @@ -772,13 +782,15 @@ "convention": "CapWords" } } - ] - }, - { + ], + "description": "Enum naming.numbers (tests/naming_convention.sol#6) is not in CapWords\n", + "markdown": "Enum [naming.numbers](tests/naming_convention.sol#L6) is not in CapWords\n", + "id": "7c87b076ea2865060182cf11d155caadb1dcea415ccce0ca8563a74a01611fc2", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Modifier 'naming.CantDo()' (tests/naming_convention.sol#41-43) is not in mixedCase\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -870,13 +882,15 @@ "convention": "mixedCase" } } - ] - }, - { + ], + "description": "Modifier naming.CantDo() (tests/naming_convention.sol#41-43) is not in mixedCase\n", + "markdown": "Modifier [naming.CantDo()](tests/naming_convention.sol#L41-L43) is not in mixedCase\n", + "id": "b8a754a01bd47127f00032cdedd0ade3e27e6543631d8f5bc9e44365ab732895", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Parameter '_used' of _used (tests/naming_convention.sol#59) is not in mixedCase\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -956,13 +970,15 @@ "convention": "mixedCase" } } - ] - }, - { + ], + "description": "Parameter T.test(uint256,uint256)._used (tests/naming_convention.sol#59) is not in mixedCase\n", + "markdown": "Parameter [T.test(uint256,uint256)._used](tests/naming_convention.sol#L59) is not in mixedCase\n", + "id": "818962ad9f50f13eb87b5c7deade22666431945fb60055f572b38246cfbf311e", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Variable 'T._myPublicVar' (tests/naming_convention.sol#56) is not in mixedCase\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -1020,13 +1036,15 @@ "convention": "mixedCase" } } - ] - }, - { + ], + "description": "Variable T._myPublicVar (tests/naming_convention.sol#56) is not in mixedCase\n", + "markdown": "Variable [T._myPublicVar](tests/naming_convention.sol#L56) is not in mixedCase\n", + "id": "8acd53815786acad5b92b51366daf79182a67ab438daa41a6e1ec8a9601fa9a3", "check": "naming-convention", "impact": "Informational", - "confidence": "High", - "description": "Variable 'T.l' (tests/naming_convention.sol#67) used l, O, I, which should not be used\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -1084,7 +1102,13 @@ "convention": "l_O_I_should_not_be_used" } } - ] + ], + "description": "Variable T.l (tests/naming_convention.sol#67) used l, O, I, which should not be used\n", + "markdown": "Variable [T.l](tests/naming_convention.sol#L67) used l, O, I, which should not be used\n", + "id": "b595f9e6d03b8b501b7c4a9bf8ff0ad9bf11448a25f53d63ab5031c95f8ae89c", + "check": "naming-convention", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/naming_convention.naming-convention.txt b/tests/expected_json/naming_convention.naming-convention.txt index 25be91780..45c53084c 100644 --- a/tests/expected_json/naming_convention.naming-convention.txt +++ b/tests/expected_json/naming_convention.naming-convention.txt @@ -1,15 +1,15 @@ -INFO:Detectors: -Contract 'naming' (tests/naming_convention.sol#3-48) is not in CapWords -Struct 'naming.test' (tests/naming_convention.sol#14-16) is not in CapWords -Event 'namingevent_(uint256)' (tests/naming_convention.sol#23) is not in CapWords -Function 'naming.GetOne()' (tests/naming_convention.sol#30-33) is not in mixedCase -Parameter 'Number2' of Number2 (tests/naming_convention.sol#35) is not in mixedCase -Constant 'naming.MY_other_CONSTANT' (tests/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES -Variable 'naming.Var_One' (tests/naming_convention.sol#11) is not in mixedCase -Enum 'naming.numbers' (tests/naming_convention.sol#6) is not in CapWords -Modifier 'naming.CantDo()' (tests/naming_convention.sol#41-43) is not in mixedCase -Parameter '_used' of _used (tests/naming_convention.sol#59) is not in mixedCase -Variable 'T._myPublicVar' (tests/naming_convention.sol#56) is not in mixedCase -Variable 'T.l' (tests/naming_convention.sol#67) used l, O, I, which should not be used + +Contract naming (tests/naming_convention.sol#3-48) is not in CapWords +Struct naming.test (tests/naming_convention.sol#14-16) is not in CapWords +Event namingevent_(uint256) (tests/naming_convention.sol#23) is not in CapWords +Function naming.GetOne() (tests/naming_convention.sol#30-33) is not in mixedCase +Parameter naming.setInt(uint256,uint256).Number2 (tests/naming_convention.sol#35) is not in mixedCase +Constant naming.MY_other_CONSTANT (tests/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES +Variable naming.Var_One (tests/naming_convention.sol#11) is not in mixedCase +Enum naming.numbers (tests/naming_convention.sol#6) is not in CapWords +Modifier naming.CantDo() (tests/naming_convention.sol#41-43) is not in mixedCase +Parameter T.test(uint256,uint256)._used (tests/naming_convention.sol#59) is not in mixedCase +Variable T._myPublicVar (tests/naming_convention.sol#56) is not in mixedCase +Variable T.l (tests/naming_convention.sol#67) used l, O, I, which should not be used Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions -INFO:Slither:tests/naming_convention.sol analyzed (4 contracts), 12 result(s) found +tests/naming_convention.sol analyzed (4 contracts with 1 detectors), 12 result(s) found diff --git a/tests/expected_json/old_solc.sol.json.solc-version.json b/tests/expected_json/old_solc.sol.json.solc-version.json index e9800bc58..6a466b562 100644 --- a/tests/expected_json/old_solc.sol.json.solc-version.json +++ b/tests/expected_json/old_solc.sol.json.solc-version.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "solc-version", - "impact": "Informational", - "confidence": "High", - "description": "Pragma version \"0.4.21\" allows old versions (None)\n", "elements": [ { "type": "pragma", @@ -32,7 +28,13 @@ ] } } - ] + ], + "description": "Pragma version0.4.21 (None) allows old versions\n", + "markdown": "Pragma version[0.4.21](None) allows old versions\n", + "id": "328ceb03806c74c48201abc80cb5625f6c8f47711913161dc99748ee6a99b64f", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/old_solc.sol.json.solc-version.txt b/tests/expected_json/old_solc.sol.json.solc-version.txt index 119e34692..e8e4c5ad1 100644 --- a/tests/expected_json/old_solc.sol.json.solc-version.txt +++ b/tests/expected_json/old_solc.sol.json.solc-version.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -Pragma version "0.4.21" allows old versions (None) + +Pragma version0.4.21 (None) allows old versions Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity +tests/old_solc.sol.json analyzed (1 contracts with 1 detectors), 1 result(s) found INFO:Slither:/home/travis/build/crytic/slither/scripts/../tests/expected_json/old_solc.sol.json.solc-version.json exists already, the overwrite is prevented -INFO:Slither:tests/old_solc.sol.json analyzed (1 contracts), 1 result(s) found diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json index 42a1a4092..855d1a84e 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.json +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "pragma", - "impact": "Informational", - "confidence": "High", - "description": "Different versions of Solidity is used in :\n\t- Version used: ['^0.4.23', '^0.4.24']\n\t- tests/pragma.0.4.23.sol#1 declares pragma solidity^0.4.23\n\t- tests/pragma.0.4.24.sol#1 declares pragma solidity^0.4.24\n", "elements": [ { "type": "pragma", @@ -61,7 +57,13 @@ ] } } - ] + ], + "description": "Different versions of Solidity is used in :\n\t- Version used: ['^0.4.23', '^0.4.24']\n\t- ^0.4.23 (tests/pragma.0.4.23.sol#1)\n\t- ^0.4.24 (tests/pragma.0.4.24.sol#1)\n", + "markdown": "Different versions of Solidity is used in :\n\t- Version used: ['^0.4.23', '^0.4.24']\n\t- [^0.4.23](tests/pragma.0.4.23.sol#L1)\n\t- [^0.4.24](tests/pragma.0.4.24.sol#L1)\n", + "id": "ab9edf8af725d611d1eaeb16713c0f2e9f51804dc985b1bc823bd87645713fb7", + "check": "pragma", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/pragma.0.4.24.pragma.txt b/tests/expected_json/pragma.0.4.24.pragma.txt index 6961c94cc..8318ed773 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.txt +++ b/tests/expected_json/pragma.0.4.24.pragma.txt @@ -1,7 +1,7 @@ -INFO:Detectors: + Different versions of Solidity is used in : - Version used: ['^0.4.23', '^0.4.24'] - - tests/pragma.0.4.23.sol#1 declares pragma solidity^0.4.23 - - tests/pragma.0.4.24.sol#1 declares pragma solidity^0.4.24 + - ^0.4.23 (tests/pragma.0.4.23.sol#1) + - ^0.4.24 (tests/pragma.0.4.24.sol#1) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used -INFO:Slither:tests/pragma.0.4.24.sol analyzed (1 contracts), 1 result(s) found +tests/pragma.0.4.24.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.json b/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.json index 32cc329fc..59a7be011 100644 --- a/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.json +++ b/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "reentrancy-eth", - "impact": "High", - "confidence": "Medium", - "description": "Reentrancy in Reentrancy.withdrawBalance() (tests/reentrancy-0.5.1.sol#14-22):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/reentrancy-0.5.1.sol#17)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy-0.5.1.sol#21)\n", "elements": [ { "type": "function", @@ -350,13 +346,15 @@ "variable_name": "userBalance" } } - ] - }, - { + ], + "description": "Reentrancy in Reentrancy.withdrawBalance() (tests/reentrancy-0.5.1.sol#14-22):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/reentrancy-0.5.1.sol#17)\n\tState variables written after the call(s):\n\t- Reentrancy.userBalance (tests/reentrancy-0.5.1.sol#4) in userBalance[msg.sender] = 0 (tests/reentrancy-0.5.1.sol#21)\n", + "markdown": "Reentrancy in [Reentrancy.withdrawBalance()](tests/reentrancy-0.5.1.sol#L14-L22):\n\tExternal calls:\n\t- [(ret,mem) = msg.sender.call.value(userBalance[msg.sender])()](tests/reentrancy-0.5.1.sol#L17)\n\tState variables written after the call(s):\n\t- [Reentrancy.userBalance](tests/reentrancy-0.5.1.sol#L4) in [userBalance[msg.sender] = 0](tests/reentrancy-0.5.1.sol#L21)\n", + "id": "90119c12446a44e514bb4ccd7a0e869f210d7c3ec349d24d4237ace685a75df9", "check": "reentrancy-eth", "impact": "High", - "confidence": "Medium", - "description": "Reentrancy in Reentrancy.withdrawBalance_fixed_3() (tests/reentrancy-0.5.1.sol#44-53):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(amount)() (tests/reentrancy-0.5.1.sol#49)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy-0.5.1.sol#51)\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -702,7 +700,13 @@ "variable_name": "userBalance" } } - ] + ], + "description": "Reentrancy in Reentrancy.withdrawBalance_fixed_3() (tests/reentrancy-0.5.1.sol#44-53):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(amount)() (tests/reentrancy-0.5.1.sol#49)\n\tState variables written after the call(s):\n\t- Reentrancy.userBalance (tests/reentrancy-0.5.1.sol#4) in userBalance[msg.sender] = amount (tests/reentrancy-0.5.1.sol#51)\n", + "markdown": "Reentrancy in [Reentrancy.withdrawBalance_fixed_3()](tests/reentrancy-0.5.1.sol#L44-L53):\n\tExternal calls:\n\t- [(ret,mem) = msg.sender.call.value(amount)()](tests/reentrancy-0.5.1.sol#L49)\n\tState variables written after the call(s):\n\t- [Reentrancy.userBalance](tests/reentrancy-0.5.1.sol#L4) in [userBalance[msg.sender] = amount](tests/reentrancy-0.5.1.sol#L51)\n", + "id": "048dce6132290bdee94cd1e52c330873fed4b1c9ad2f49df4ba3d6ef49266a41", + "check": "reentrancy-eth", + "impact": "High", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.txt b/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.txt index ebf2a8c52..7f98e13f9 100644 --- a/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.txt +++ b/tests/expected_json/reentrancy-0.5.1.reentrancy-eth.txt @@ -1,13 +1,13 @@ -INFO:Detectors: + Reentrancy in Reentrancy.withdrawBalance() (tests/reentrancy-0.5.1.sol#14-22): External calls: - (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/reentrancy-0.5.1.sol#17) State variables written after the call(s): - - userBalance (tests/reentrancy-0.5.1.sol#21) + - Reentrancy.userBalance (tests/reentrancy-0.5.1.sol#4) in userBalance[msg.sender] = 0 (tests/reentrancy-0.5.1.sol#21) Reentrancy in Reentrancy.withdrawBalance_fixed_3() (tests/reentrancy-0.5.1.sol#44-53): External calls: - (ret,mem) = msg.sender.call.value(amount)() (tests/reentrancy-0.5.1.sol#49) State variables written after the call(s): - - userBalance (tests/reentrancy-0.5.1.sol#51) + - Reentrancy.userBalance (tests/reentrancy-0.5.1.sol#4) in userBalance[msg.sender] = amount (tests/reentrancy-0.5.1.sol#51) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities -INFO:Slither:tests/reentrancy-0.5.1.sol analyzed (1 contracts), 2 result(s) found +tests/reentrancy-0.5.1.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/reentrancy.reentrancy-eth.json b/tests/expected_json/reentrancy.reentrancy-eth.json index cf7f5ae09..2c0065199 100644 --- a/tests/expected_json/reentrancy.reentrancy-eth.json +++ b/tests/expected_json/reentrancy.reentrancy-eth.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "reentrancy-eth", - "impact": "High", - "confidence": "Medium", - "description": "Reentrancy in Reentrancy.withdrawBalance() (tests/reentrancy.sol#14-21):\n\tExternal calls:\n\t- ! (msg.sender.call.value(userBalance[msg.sender])()) (tests/reentrancy.sol#17)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy.sol#20)\n", "elements": [ { "type": "function", @@ -401,13 +397,15 @@ "variable_name": "userBalance" } } - ] - }, - { + ], + "description": "Reentrancy in Reentrancy.withdrawBalance() (tests/reentrancy.sol#14-21):\n\tExternal calls:\n\t- ! (msg.sender.call.value(userBalance[msg.sender])()) (tests/reentrancy.sol#17)\n\tState variables written after the call(s):\n\t- Reentrancy.userBalance (tests/reentrancy.sol#4) in userBalance[msg.sender] = 0 (tests/reentrancy.sol#20)\n", + "markdown": "Reentrancy in [Reentrancy.withdrawBalance()](tests/reentrancy.sol#L14-L21):\n\tExternal calls:\n\t- [! (msg.sender.call.value(userBalance[msg.sender])())](tests/reentrancy.sol#L17)\n\tState variables written after the call(s):\n\t- [Reentrancy.userBalance](tests/reentrancy.sol#L4) in [userBalance[msg.sender] = 0](tests/reentrancy.sol#L20)\n", + "id": "330f0fadcfdda2b4364ee67d3112ff00e0e369162004c451163f0663c9b01313", "check": "reentrancy-eth", "impact": "High", - "confidence": "Medium", - "description": "Reentrancy in Reentrancy.withdrawBalance_nested() (tests/reentrancy.sol#64-70):\n\tExternal calls:\n\t- msg.sender.call.value(amount / 2)() (tests/reentrancy.sol#67)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy.sol#68)\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -798,7 +796,13 @@ "variable_name": "userBalance" } } - ] + ], + "description": "Reentrancy in Reentrancy.withdrawBalance_nested() (tests/reentrancy.sol#64-70):\n\tExternal calls:\n\t- msg.sender.call.value(amount / 2)() (tests/reentrancy.sol#67)\n\tState variables written after the call(s):\n\t- Reentrancy.userBalance (tests/reentrancy.sol#4) in userBalance[msg.sender] = 0 (tests/reentrancy.sol#68)\n", + "markdown": "Reentrancy in [Reentrancy.withdrawBalance_nested()](tests/reentrancy.sol#L64-L70):\n\tExternal calls:\n\t- [msg.sender.call.value(amount / 2)()](tests/reentrancy.sol#L67)\n\tState variables written after the call(s):\n\t- [Reentrancy.userBalance](tests/reentrancy.sol#L4) in [userBalance[msg.sender] = 0](tests/reentrancy.sol#L68)\n", + "id": "30220828bfef61c2377527c7cee2ae5be443001f8dd39992d6e5af67153c402f", + "check": "reentrancy-eth", + "impact": "High", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/reentrancy.reentrancy-eth.txt b/tests/expected_json/reentrancy.reentrancy-eth.txt index e1160ca4c..f5ab77567 100644 --- a/tests/expected_json/reentrancy.reentrancy-eth.txt +++ b/tests/expected_json/reentrancy.reentrancy-eth.txt @@ -1,13 +1,13 @@ -INFO:Detectors: + Reentrancy in Reentrancy.withdrawBalance() (tests/reentrancy.sol#14-21): External calls: - ! (msg.sender.call.value(userBalance[msg.sender])()) (tests/reentrancy.sol#17) State variables written after the call(s): - - userBalance (tests/reentrancy.sol#20) + - Reentrancy.userBalance (tests/reentrancy.sol#4) in userBalance[msg.sender] = 0 (tests/reentrancy.sol#20) Reentrancy in Reentrancy.withdrawBalance_nested() (tests/reentrancy.sol#64-70): External calls: - msg.sender.call.value(amount / 2)() (tests/reentrancy.sol#67) State variables written after the call(s): - - userBalance (tests/reentrancy.sol#68) + - Reentrancy.userBalance (tests/reentrancy.sol#4) in userBalance[msg.sender] = 0 (tests/reentrancy.sol#68) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities -INFO:Slither:tests/reentrancy.sol analyzed (1 contracts), 2 result(s) found +tests/reentrancy.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/right_to_left_override.rtlo.json b/tests/expected_json/right_to_left_override.rtlo.json index c189dbcd7..525e2c9ce 100644 --- a/tests/expected_json/right_to_left_override.rtlo.json +++ b/tests/expected_json/right_to_left_override.rtlo.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "rtlo", - "impact": "High", - "confidence": "High", - "description": "/home/travis/build/crytic/slither/tests/right_to_left_override.sol contains a unicode right-to-left-override character at byte offset 96:\n\t- b' test1(/*A\\xe2\\x80\\xae/*B*/2 , 1/*\\xe2\\x80\\xad'\n", "elements": [ { "type": "other", @@ -27,7 +23,13 @@ "ending_column": 21 } } - ] + ], + "description": "tests/right_to_left_override.sol contains a unicode right-to-left-override character at byte offset 96:\n\t- b' test1(/*A\\xe2\\x80\\xae/*B*/2 , 1/*\\xe2\\x80\\xad'\n", + "markdown": "tests/right_to_left_override.sol contains a unicode right-to-left-override character at byte offset 96:\n\t- b' test1(/*A\\xe2\\x80\\xae/*B*/2 , 1/*\\xe2\\x80\\xad'\n", + "id": "7d1d0efc4c3c51bc1cc85572c67fa5244f01af85ab9e4bcef199d928a3b5447b", + "check": "rtlo", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/right_to_left_override.rtlo.txt b/tests/expected_json/right_to_left_override.rtlo.txt index 9d77008f0..96fd1dd4c 100644 --- a/tests/expected_json/right_to_left_override.rtlo.txt +++ b/tests/expected_json/right_to_left_override.rtlo.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -/home/travis/build/crytic/slither/tests/right_to_left_override.sol contains a unicode right-to-left-override character at byte offset 96: + +tests/right_to_left_override.sol contains a unicode right-to-left-override character at byte offset 96: - b' test1(/*A\xe2\x80\xae/*B*/2 , 1/*\xe2\x80\xad' Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character -INFO:Slither:tests/right_to_left_override.sol analyzed (1 contracts), 1 result(s) found +tests/right_to_left_override.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/shadowing_abstract.shadowing-abstract.json b/tests/expected_json/shadowing_abstract.shadowing-abstract.json index ec993de29..44da69923 100644 --- a/tests/expected_json/shadowing_abstract.shadowing-abstract.json +++ b/tests/expected_json/shadowing_abstract.shadowing-abstract.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "shadowing-abstract", - "impact": "Medium", - "confidence": "High", - "description": "DerivedContract.owner (tests/shadowing_abstract.sol#7) shadows:\n\t- BaseContract.owner (tests/shadowing_abstract.sol#2)\n", "elements": [ { "type": "variable", @@ -91,7 +87,13 @@ } } } - ] + ], + "description": "DerivedContract.owner (tests/shadowing_abstract.sol#7) shadows:\n\t- BaseContract.owner (tests/shadowing_abstract.sol#2)\n", + "markdown": "[DerivedContract.owner](tests/shadowing_abstract.sol#L7) shadows:\n\t- [BaseContract.owner](tests/shadowing_abstract.sol#L2)\n", + "id": "9c5c3fc5091b9ecd6ec271fdbb3036d9d3426cdf9a09d6cc293fd7de9240e4ab", + "check": "shadowing-abstract", + "impact": "Medium", + "confidence": "High" } ] } diff --git a/tests/expected_json/shadowing_abstract.shadowing-abstract.txt b/tests/expected_json/shadowing_abstract.shadowing-abstract.txt index fed0e9e24..93b722f63 100644 --- a/tests/expected_json/shadowing_abstract.shadowing-abstract.txt +++ b/tests/expected_json/shadowing_abstract.shadowing-abstract.txt @@ -1,5 +1,5 @@ -INFO:Detectors: + DerivedContract.owner (tests/shadowing_abstract.sol#7) shadows: - BaseContract.owner (tests/shadowing_abstract.sol#2) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts -INFO:Slither:tests/shadowing_abstract.sol analyzed (2 contracts), 1 result(s) found +tests/shadowing_abstract.sol analyzed (2 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.json b/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.json index a328fa79a..6f599cd31 100644 --- a/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.json +++ b/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "shadowing-builtin", - "impact": "Low", - "confidence": "High", - "description": "BaseContract.blockhash (state variable @ tests/shadowing_builtin_symbols.sol#4) shadows built-in symbol \"blockhash\"\n", "elements": [ { "type": "variable", @@ -52,13 +48,15 @@ } } } - ] - }, - { + ], + "description": "BaseContract.blockhash (tests/shadowing_builtin_symbols.sol#4) (state variable) shadows built-in symbol\"\n", + "markdown": "[BaseContract.blockhash](tests/shadowing_builtin_symbols.sol#L4) (state variable) shadows built-in symbol\"\n", + "id": "61d1c1452e694c321d00576decdf35c62efbe8860f664273955fadce5e063cc8", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "BaseContract.now (state variable @ tests/shadowing_builtin_symbols.sol#5) shadows built-in symbol \"now\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -103,13 +101,15 @@ } } } - ] - }, - { + ], + "description": "BaseContract.now (tests/shadowing_builtin_symbols.sol#5) (state variable) shadows built-in symbol\"\n", + "markdown": "[BaseContract.now](tests/shadowing_builtin_symbols.sol#L5) (state variable) shadows built-in symbol\"\n", + "id": "942ad0405af0bc533374dded6e2474c53892747c98d2df14d59cbb6840fa18b3", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "BaseContract.revert (event @ tests/shadowing_builtin_symbols.sol#7) shadows built-in symbol \"revert\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "event", @@ -155,13 +155,15 @@ "signature": "revert(bool)" } } - ] - }, - { + ], + "description": "BaseContractrevert(bool) (tests/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol\"\n", + "markdown": "[BaseContractrevert(bool)](tests/shadowing_builtin_symbols.sol#L7) (event) shadows built-in symbol\"\n", + "id": "9c428e61cb4832d899b2bb711c8d428154425dbadf5dfc2e2d857254824d8f72", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "ExtendedContract.assert (function @ tests/shadowing_builtin_symbols.sol#13-15) shadows built-in symbol \"assert\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -210,13 +212,15 @@ "signature": "assert(bool)" } } - ] - }, - { + ], + "description": "ExtendedContract.assert(bool) (tests/shadowing_builtin_symbols.sol#13-15) (function) shadows built-in symbol\"\n", + "markdown": "[ExtendedContract.assert(bool)](tests/shadowing_builtin_symbols.sol#L13-L15) (function) shadows built-in symbol\"\n", + "id": "b3a1b23c1daed52b1a2ff5fb76d4fcdf2bc0b2126524303321cf8e7835116d6f", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "ExtendedContract.assert.msg (local variable @ tests/shadowing_builtin_symbols.sol#14) shadows built-in symbol \"msg\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -285,13 +289,15 @@ } } } - ] - }, - { + ], + "description": "ExtendedContract.assert(bool).msg (tests/shadowing_builtin_symbols.sol#14) (local variable) shadows built-in symbol\"\n", + "markdown": "[ExtendedContract.assert(bool).msg](tests/shadowing_builtin_symbols.sol#L14) (local variable) shadows built-in symbol\"\n", + "id": "a2a7e1e27320d38e52b51c9b1ec67cca0a403673ff6fdd59652f9cd8425d011f", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "ExtendedContract.ecrecover (state variable @ tests/shadowing_builtin_symbols.sol#11) shadows built-in symbol \"ecrecover\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -337,13 +343,15 @@ } } } - ] - }, - { + ], + "description": "ExtendedContract.ecrecover (tests/shadowing_builtin_symbols.sol#11) (state variable) shadows built-in symbol\"\n", + "markdown": "[ExtendedContract.ecrecover](tests/shadowing_builtin_symbols.sol#L11) (state variable) shadows built-in symbol\"\n", + "id": "39737925cf3bd4ebf9a1c7bbe39da8b80ba28e5a3d93a938d1a4cca78a6a436d", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.require (modifier @ tests/shadowing_builtin_symbols.sol#23-28) shadows built-in symbol \"require\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "function", @@ -400,13 +408,15 @@ "signature": "require()" } } - ] - }, - { + ], + "description": "FurtherExtendedContract.require() (tests/shadowing_builtin_symbols.sol#23-28) (modifier) shadows built-in symbol\"\n", + "markdown": "[FurtherExtendedContract.require()](tests/shadowing_builtin_symbols.sol#L23-L28) (modifier) shadows built-in symbol\"\n", + "id": "db6c26c9a7c319c1a34c486e6e625c0906a2b118f8cd72f38a38c167832aab4f", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.require.keccak256 (local variable @ tests/shadowing_builtin_symbols.sol#25) shadows built-in symbol \"keccak256\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -483,13 +493,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.require().keccak256 (tests/shadowing_builtin_symbols.sol#25) (local variable) shadows built-in symbol\"\n", + "markdown": "[FurtherExtendedContract.require().keccak256](tests/shadowing_builtin_symbols.sol#L25) (local variable) shadows built-in symbol\"\n", + "id": "40f0453d27abf2d9ed76fe60853b6e2e0cd9a443d639e9da457460ea02b2bdc7", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.require.sha3 (local variable @ tests/shadowing_builtin_symbols.sol#26) shadows built-in symbol \"sha3\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -566,13 +578,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.require().sha3 (tests/shadowing_builtin_symbols.sol#26) (local variable) shadows built-in symbol\"\n", + "markdown": "[FurtherExtendedContract.require().sha3](tests/shadowing_builtin_symbols.sol#L26) (local variable) shadows built-in symbol\"\n", + "id": "c481dbbf77c99cb337740a656ebabae1c89bf13b9d7b7d315dcf54feeab1cd63", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.blockhash (state variable @ tests/shadowing_builtin_symbols.sol#19) shadows built-in symbol \"blockhash\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -623,13 +637,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.blockhash (tests/shadowing_builtin_symbols.sol#19) (state variable) shadows built-in symbol\"\n", + "markdown": "[FurtherExtendedContract.blockhash](tests/shadowing_builtin_symbols.sol#L19) (state variable) shadows built-in symbol\"\n", + "id": "d405ccbec679f921252d475591a890a89a023b375dc4994119967693692f8da9", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.this (state variable @ tests/shadowing_builtin_symbols.sol#20) shadows built-in symbol \"this\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -680,13 +696,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.this (tests/shadowing_builtin_symbols.sol#20) (state variable) shadows built-in symbol\"\n", + "markdown": "[FurtherExtendedContract.this](tests/shadowing_builtin_symbols.sol#L20) (state variable) shadows built-in symbol\"\n", + "id": "87e1cc0cb95181dd120d3e6e55d89fdc7b5cd01da2f89cd7a3d105738f57c10b", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.abi (state variable @ tests/shadowing_builtin_symbols.sol#21) shadows built-in symbol \"abi\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -737,13 +755,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.abi (tests/shadowing_builtin_symbols.sol#21) (state variable) shadows built-in symbol\"\n", + "markdown": "[FurtherExtendedContract.abi](tests/shadowing_builtin_symbols.sol#L21) (state variable) shadows built-in symbol\"\n", + "id": "4665243a2df090e3543ab26447528fa3401916f4669fe1264145891846d4bc40", "check": "shadowing-builtin", "impact": "Low", - "confidence": "High", - "description": "Reserved.mutable (state variable @ tests/shadowing_builtin_symbols.sol#32) shadows built-in symbol \"mutable\"\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -786,7 +806,13 @@ } } } - ] + ], + "description": "Reserved.mutable (tests/shadowing_builtin_symbols.sol#32) (state variable) shadows built-in symbol\"\n", + "markdown": "[Reserved.mutable](tests/shadowing_builtin_symbols.sol#L32) (state variable) shadows built-in symbol\"\n", + "id": "11840553a9e11623596d7a07275814e65a5b1d90277ae0e2954cd8ce74d6a6d2", + "check": "shadowing-builtin", + "impact": "Low", + "confidence": "High" } ] } diff --git a/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.txt b/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.txt index 18d9c370a..18473dd98 100644 --- a/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.txt +++ b/tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.txt @@ -1,16 +1,16 @@ -INFO:Detectors: -BaseContract.blockhash (state variable @ tests/shadowing_builtin_symbols.sol#4) shadows built-in symbol "blockhash" -BaseContract.now (state variable @ tests/shadowing_builtin_symbols.sol#5) shadows built-in symbol "now" -BaseContract.revert (event @ tests/shadowing_builtin_symbols.sol#7) shadows built-in symbol "revert" -ExtendedContract.assert (function @ tests/shadowing_builtin_symbols.sol#13-15) shadows built-in symbol "assert" -ExtendedContract.assert.msg (local variable @ tests/shadowing_builtin_symbols.sol#14) shadows built-in symbol "msg" -ExtendedContract.ecrecover (state variable @ tests/shadowing_builtin_symbols.sol#11) shadows built-in symbol "ecrecover" -FurtherExtendedContract.require (modifier @ tests/shadowing_builtin_symbols.sol#23-28) shadows built-in symbol "require" -FurtherExtendedContract.require.keccak256 (local variable @ tests/shadowing_builtin_symbols.sol#25) shadows built-in symbol "keccak256" -FurtherExtendedContract.require.sha3 (local variable @ tests/shadowing_builtin_symbols.sol#26) shadows built-in symbol "sha3" -FurtherExtendedContract.blockhash (state variable @ tests/shadowing_builtin_symbols.sol#19) shadows built-in symbol "blockhash" -FurtherExtendedContract.this (state variable @ tests/shadowing_builtin_symbols.sol#20) shadows built-in symbol "this" -FurtherExtendedContract.abi (state variable @ tests/shadowing_builtin_symbols.sol#21) shadows built-in symbol "abi" -Reserved.mutable (state variable @ tests/shadowing_builtin_symbols.sol#32) shadows built-in symbol "mutable" + +BaseContract.blockhash (tests/shadowing_builtin_symbols.sol#4) (state variable) shadows built-in symbol" +BaseContract.now (tests/shadowing_builtin_symbols.sol#5) (state variable) shadows built-in symbol" +BaseContractrevert(bool) (tests/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol" +ExtendedContract.assert(bool) (tests/shadowing_builtin_symbols.sol#13-15) (function) shadows built-in symbol" +ExtendedContract.assert(bool).msg (tests/shadowing_builtin_symbols.sol#14) (local variable) shadows built-in symbol" +ExtendedContract.ecrecover (tests/shadowing_builtin_symbols.sol#11) (state variable) shadows built-in symbol" +FurtherExtendedContract.require() (tests/shadowing_builtin_symbols.sol#23-28) (modifier) shadows built-in symbol" +FurtherExtendedContract.require().keccak256 (tests/shadowing_builtin_symbols.sol#25) (local variable) shadows built-in symbol" +FurtherExtendedContract.require().sha3 (tests/shadowing_builtin_symbols.sol#26) (local variable) shadows built-in symbol" +FurtherExtendedContract.blockhash (tests/shadowing_builtin_symbols.sol#19) (state variable) shadows built-in symbol" +FurtherExtendedContract.this (tests/shadowing_builtin_symbols.sol#20) (state variable) shadows built-in symbol" +FurtherExtendedContract.abi (tests/shadowing_builtin_symbols.sol#21) (state variable) shadows built-in symbol" +Reserved.mutable (tests/shadowing_builtin_symbols.sol#32) (state variable) shadows built-in symbol" Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing -INFO:Slither:tests/shadowing_builtin_symbols.sol analyzed (4 contracts), 13 result(s) found +tests/shadowing_builtin_symbols.sol analyzed (4 contracts with 1 detectors), 13 result(s) found diff --git a/tests/expected_json/shadowing_local_variable.shadowing-local.json b/tests/expected_json/shadowing_local_variable.shadowing-local.json index 616a637df..859d5326b 100644 --- a/tests/expected_json/shadowing_local_variable.shadowing-local.json +++ b/tests/expected_json/shadowing_local_variable.shadowing-local.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "shadowing-local", - "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.shadowingParent.x (local variable @ tests/shadowing_local_variable.sol#25) shadows:\n\t- FurtherExtendedContract.x (state variable @ tests/shadowing_local_variable.sol#17)\n\t- ExtendedContract.x (state variable @ tests/shadowing_local_variable.sol#9)\n\t- BaseContract.x (state variable @ tests/shadowing_local_variable.sol#4)\n", "elements": [ { "type": "variable", @@ -211,13 +207,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.shadowingParent(uint256).x (tests/shadowing_local_variable.sol#25) shadows:\n\t- FurtherExtendedContract.x (tests/shadowing_local_variable.sol#17) (state variable)\n\t- ExtendedContract.x (tests/shadowing_local_variable.sol#9) (state variable)\n\t- BaseContract.x (tests/shadowing_local_variable.sol#4) (state variable)\n", + "markdown": "[FurtherExtendedContract.shadowingParent(uint256).x](tests/shadowing_local_variable.sol#L25) shadows:\n\t- [FurtherExtendedContract.x](tests/shadowing_local_variable.sol#L17) (state variable)\n\t- [ExtendedContract.x](tests/shadowing_local_variable.sol#L9) (state variable)\n\t- [BaseContract.x](tests/shadowing_local_variable.sol#L4) (state variable)\n", + "id": "0991435c12aa2d6f15e8da2a00a18e9c58ef65dcf31137cdb561655317353247", "check": "shadowing-local", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.shadowingParent.y (local variable @ tests/shadowing_local_variable.sol#25) shadows:\n\t- BaseContract.y (state variable @ tests/shadowing_local_variable.sol#5)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -329,13 +327,15 @@ } } } - ] - }, - { + ], + "description": "FurtherExtendedContract.shadowingParent(uint256).y (tests/shadowing_local_variable.sol#25) shadows:\n\t- BaseContract.y (tests/shadowing_local_variable.sol#5) (state variable)\n", + "markdown": "[FurtherExtendedContract.shadowingParent(uint256).y](tests/shadowing_local_variable.sol#L25) shadows:\n\t- [BaseContract.y](tests/shadowing_local_variable.sol#L5) (state variable)\n", + "id": "465bd81cbb09a3d2cc84ea6102fb059296f1970e85e2d86a171f8219f1a34508", "check": "shadowing-local", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.shadowingParent.z (local variable @ tests/shadowing_local_variable.sol#25) shadows:\n\t- ExtendedContract.z (function @ tests/shadowing_local_variable.sol#11)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -451,13 +451,15 @@ "signature": "z()" } } - ] - }, - { + ], + "description": "FurtherExtendedContract.shadowingParent(uint256).z (tests/shadowing_local_variable.sol#25) shadows:\n\t- ExtendedContract.z() (tests/shadowing_local_variable.sol#11) (function)\n", + "markdown": "[FurtherExtendedContract.shadowingParent(uint256).z](tests/shadowing_local_variable.sol#L25) shadows:\n\t- [ExtendedContract.z()](tests/shadowing_local_variable.sol#L11) (function)\n", + "id": "e3d2948e9c1252fe84e0d7e58f6682af7af84ef209f6e71f039faccabf07b0bd", "check": "shadowing-local", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.shadowingParent.w (local variable @ tests/shadowing_local_variable.sol#25) shadows:\n\t- FurtherExtendedContract.w (modifier @ tests/shadowing_local_variable.sol#20-23)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -580,13 +582,15 @@ "signature": "w()" } } - ] - }, - { + ], + "description": "FurtherExtendedContract.shadowingParent(uint256).w (tests/shadowing_local_variable.sol#25) shadows:\n\t- FurtherExtendedContract.w() (tests/shadowing_local_variable.sol#20-23) (modifier)\n", + "markdown": "[FurtherExtendedContract.shadowingParent(uint256).w](tests/shadowing_local_variable.sol#L25) shadows:\n\t- [FurtherExtendedContract.w()](tests/shadowing_local_variable.sol#L20-L23) (modifier)\n", + "id": "a94a2b9331482c75582868e6d3cc5c9b01487e7505f219abcf36a20d76e0b089", "check": "shadowing-local", "impact": "Low", - "confidence": "High", - "description": "FurtherExtendedContract.shadowingParent.v (local variable @ tests/shadowing_local_variable.sol#25) shadows:\n\t- ExtendedContract.v (event @ tests/shadowing_local_variable.sol#13)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -702,7 +706,13 @@ "signature": "v()" } } - ] + ], + "description": "FurtherExtendedContract.shadowingParent(uint256).v (tests/shadowing_local_variable.sol#25) shadows:\n\t- ExtendedContractv() (tests/shadowing_local_variable.sol#13) (event)\n", + "markdown": "[FurtherExtendedContract.shadowingParent(uint256).v](tests/shadowing_local_variable.sol#L25) shadows:\n\t- [ExtendedContractv()](tests/shadowing_local_variable.sol#L13) (event)\n", + "id": "973e31cc30dc7a3e1f089dfa5848234075f237f78fa492c772b1083e12c79054", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" } ] } diff --git a/tests/expected_json/shadowing_local_variable.shadowing-local.txt b/tests/expected_json/shadowing_local_variable.shadowing-local.txt index 944c3bd1b..40eb29c54 100644 --- a/tests/expected_json/shadowing_local_variable.shadowing-local.txt +++ b/tests/expected_json/shadowing_local_variable.shadowing-local.txt @@ -1,15 +1,15 @@ -INFO:Detectors: -FurtherExtendedContract.shadowingParent.x (local variable @ tests/shadowing_local_variable.sol#25) shadows: - - FurtherExtendedContract.x (state variable @ tests/shadowing_local_variable.sol#17) - - ExtendedContract.x (state variable @ tests/shadowing_local_variable.sol#9) - - BaseContract.x (state variable @ tests/shadowing_local_variable.sol#4) -FurtherExtendedContract.shadowingParent.y (local variable @ tests/shadowing_local_variable.sol#25) shadows: - - BaseContract.y (state variable @ tests/shadowing_local_variable.sol#5) -FurtherExtendedContract.shadowingParent.z (local variable @ tests/shadowing_local_variable.sol#25) shadows: - - ExtendedContract.z (function @ tests/shadowing_local_variable.sol#11) -FurtherExtendedContract.shadowingParent.w (local variable @ tests/shadowing_local_variable.sol#25) shadows: - - FurtherExtendedContract.w (modifier @ tests/shadowing_local_variable.sol#20-23) -FurtherExtendedContract.shadowingParent.v (local variable @ tests/shadowing_local_variable.sol#25) shadows: - - ExtendedContract.v (event @ tests/shadowing_local_variable.sol#13) + +FurtherExtendedContract.shadowingParent(uint256).x (tests/shadowing_local_variable.sol#25) shadows: + - FurtherExtendedContract.x (tests/shadowing_local_variable.sol#17) (state variable) + - ExtendedContract.x (tests/shadowing_local_variable.sol#9) (state variable) + - BaseContract.x (tests/shadowing_local_variable.sol#4) (state variable) +FurtherExtendedContract.shadowingParent(uint256).y (tests/shadowing_local_variable.sol#25) shadows: + - BaseContract.y (tests/shadowing_local_variable.sol#5) (state variable) +FurtherExtendedContract.shadowingParent(uint256).z (tests/shadowing_local_variable.sol#25) shadows: + - ExtendedContract.z() (tests/shadowing_local_variable.sol#11) (function) +FurtherExtendedContract.shadowingParent(uint256).w (tests/shadowing_local_variable.sol#25) shadows: + - FurtherExtendedContract.w() (tests/shadowing_local_variable.sol#20-23) (modifier) +FurtherExtendedContract.shadowingParent(uint256).v (tests/shadowing_local_variable.sol#25) shadows: + - ExtendedContractv() (tests/shadowing_local_variable.sol#13) (event) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing -INFO:Slither:tests/shadowing_local_variable.sol analyzed (3 contracts), 5 result(s) found +tests/shadowing_local_variable.sol analyzed (3 contracts with 1 detectors), 5 result(s) found diff --git a/tests/expected_json/shadowing_state_variable.shadowing-state.json b/tests/expected_json/shadowing_state_variable.shadowing-state.json index 2833ee769..bea1419f7 100644 --- a/tests/expected_json/shadowing_state_variable.shadowing-state.json +++ b/tests/expected_json/shadowing_state_variable.shadowing-state.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "shadowing-state", - "impact": "High", - "confidence": "High", - "description": "DerivedContract.owner (tests/shadowing_state_variable.sol#12) shadows:\n\t- BaseContract.owner (tests/shadowing_state_variable.sol#2)\n", "elements": [ { "type": "variable", @@ -103,7 +99,13 @@ } } } - ] + ], + "description": "DerivedContract.owner (tests/shadowing_state_variable.sol#12) shadows:\n\t- BaseContract.owner (tests/shadowing_state_variable.sol#2)\n", + "markdown": "[DerivedContract.owner](tests/shadowing_state_variable.sol#L12) shadows:\n\t- [BaseContract.owner](tests/shadowing_state_variable.sol#L2)\n", + "id": "9c5c3fc5091b9ecd6ec271fdbb3036d9d3426cdf9a09d6cc293fd7de9240e4ab", + "check": "shadowing-state", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/shadowing_state_variable.shadowing-state.txt b/tests/expected_json/shadowing_state_variable.shadowing-state.txt index 965837378..5109991c2 100644 --- a/tests/expected_json/shadowing_state_variable.shadowing-state.txt +++ b/tests/expected_json/shadowing_state_variable.shadowing-state.txt @@ -1,5 +1,5 @@ -INFO:Detectors: + DerivedContract.owner (tests/shadowing_state_variable.sol#12) shadows: - BaseContract.owner (tests/shadowing_state_variable.sol#2) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing -INFO:Slither:tests/shadowing_state_variable.sol analyzed (2 contracts), 1 result(s) found +tests/shadowing_state_variable.sol analyzed (2 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/solc_version_incorrect.solc-version.json b/tests/expected_json/solc_version_incorrect.solc-version.json index eb0c2f514..121466f3e 100644 --- a/tests/expected_json/solc_version_incorrect.solc-version.json +++ b/tests/expected_json/solc_version_incorrect.solc-version.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "solc-version", - "impact": "Informational", - "confidence": "High", - "description": "Pragma version \"^0.4.23\" allows old versions (tests/solc_version_incorrect.sol#2)\n", "elements": [ { "type": "pragma", @@ -35,13 +31,15 @@ ] } } - ] - }, - { + ], + "description": "Pragma version^0.4.23 (tests/solc_version_incorrect.sol#2) allows old versions\n", + "markdown": "Pragma version[^0.4.23](tests/solc_version_incorrect.sol#L2) allows old versions\n", + "id": "c88d5b50c78f468b95732feb91693016824f78333adab2fdcca3152eec2bfa73", "check": "solc-version", "impact": "Informational", - "confidence": "High", - "description": "Pragma version \">=0.4.0<0.6.0\" allows old versions (tests/solc_version_incorrect.sol#3)\n", + "confidence": "High" + }, + { "elements": [ { "type": "pragma", @@ -72,7 +70,13 @@ ] } } - ] + ], + "description": "Pragma version>=0.4.0<0.6.0 (tests/solc_version_incorrect.sol#3) allows old versions\n", + "markdown": "Pragma version[>=0.4.0<0.6.0](tests/solc_version_incorrect.sol#L3) allows old versions\n", + "id": "dfd3e70c2367b0c8eca5afa86d1773b9c7aa1d0a155e3a0495188800c41fe390", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/solc_version_incorrect.solc-version.txt b/tests/expected_json/solc_version_incorrect.solc-version.txt index 60a0c9707..d9432c890 100644 --- a/tests/expected_json/solc_version_incorrect.solc-version.txt +++ b/tests/expected_json/solc_version_incorrect.solc-version.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -Pragma version "^0.4.23" allows old versions (tests/solc_version_incorrect.sol#2) -Pragma version ">=0.4.0<0.6.0" allows old versions (tests/solc_version_incorrect.sol#3) + +Pragma version^0.4.23 (tests/solc_version_incorrect.sol#2) allows old versions +Pragma version>=0.4.0<0.6.0 (tests/solc_version_incorrect.sol#3) allows old versions Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity -INFO:Slither:tests/solc_version_incorrect.sol analyzed (1 contracts), 2 result(s) found +tests/solc_version_incorrect.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json index 00b47a15a..d04fb3bbb 100644 --- a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json +++ b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "solc-version", - "impact": "Informational", - "confidence": "High", - "description": "Pragma version \"^0.5.5\" is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html) (None)\n", "elements": [ { "type": "pragma", @@ -33,13 +29,15 @@ ] } } - ] - }, - { + ], + "description": "Pragma version^0.5.5 (None) is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html)\n", + "markdown": "Pragma version[^0.5.5](None) is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html)\n", + "id": "8bcf06a2e2c13723dbc16f843f73830ebdb86b7e8ee599524a0b9824bb89381c", "check": "solc-version", "impact": "Informational", - "confidence": "High", - "description": "Pragma version \"0.5.7\" necessitates versions too recent to be trusted. Consider deploying with 0.5.3 (None)\n", + "confidence": "High" + }, + { "elements": [ { "type": "pragma", @@ -64,7 +62,13 @@ ] } } - ] + ], + "description": "Pragma version0.5.7 (None) necessitates versions too recent to be trusted. Consider deploying with 0.5.3\n", + "markdown": "Pragma version[0.5.7](None) necessitates versions too recent to be trusted. Consider deploying with 0.5.3\n", + "id": "997cf0cfc722f31f7566c3fd180c01b0c7466a19df7c65ee590e9f3778cf62ee", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt index 6673c1a56..0e09462e6 100644 --- a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt +++ b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -Pragma version "^0.5.5" is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html) (None) -Pragma version "0.5.7" necessitates versions too recent to be trusted. Consider deploying with 0.5.3 (None) + +Pragma version^0.5.5 (None) is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html) +Pragma version0.5.7 (None) necessitates versions too recent to be trusted. Consider deploying with 0.5.3 Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity -INFO:Slither:tests/solc_version_incorrect_05.ast.json analyzed (1 contracts), 2 result(s) found +tests/solc_version_incorrect_05.ast.json analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/timestamp.timestamp.json b/tests/expected_json/timestamp.timestamp.json index f35e6f173..798c87186 100644 --- a/tests/expected_json/timestamp.timestamp.json +++ b/tests/expected_json/timestamp.timestamp.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "timestamp", - "impact": "Low", - "confidence": "Medium", - "description": "Timestamp.bad0() (tests/timestamp.sol#4-6) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- require(bool)(block.timestamp == 0) (tests/timestamp.sol#5)\n", "elements": [ { "type": "function", @@ -149,13 +145,15 @@ } } } - ] - }, - { + ], + "description": "Timestamp.bad0() (tests/timestamp.sol#4-6) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- require(bool)(block.timestamp == 0) (tests/timestamp.sol#5)\n", + "markdown": "[Timestamp.bad0()](tests/timestamp.sol#L4-L6) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- [require(bool)(block.timestamp == 0)](tests/timestamp.sol#L5)\n", + "id": "40a3d28a47f4ff093afc58a30950747eef4db7fe47ace94e10bc4e763e77d82f", "check": "timestamp", "impact": "Low", - "confidence": "Medium", - "description": "Timestamp.bad1() (tests/timestamp.sol#8-11) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- require(bool)(time == 0) (tests/timestamp.sol#10)\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -299,13 +297,15 @@ } } } - ] - }, - { + ], + "description": "Timestamp.bad1() (tests/timestamp.sol#8-11) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- require(bool)(time == 0) (tests/timestamp.sol#10)\n", + "markdown": "[Timestamp.bad1()](tests/timestamp.sol#L8-L11) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- [require(bool)(time == 0)](tests/timestamp.sol#L10)\n", + "id": "62968bb5164984fc4ed41aa04d9413d371e20127d858b730fe122bc03c8e5c21", "check": "timestamp", "impact": "Low", - "confidence": "Medium", - "description": "Timestamp.bad2() (tests/timestamp.sol#13-15) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- block.timestamp > 0 (tests/timestamp.sol#14)\n", + "confidence": "Medium" + }, + { "elements": [ { "type": "function", @@ -447,7 +447,13 @@ } } } - ] + ], + "description": "Timestamp.bad2() (tests/timestamp.sol#13-15) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- block.timestamp > 0 (tests/timestamp.sol#14)\n", + "markdown": "[Timestamp.bad2()](tests/timestamp.sol#L13-L15) uses timestamp for comparisons\n\tDangerous comparisons:\n\t- [block.timestamp > 0](tests/timestamp.sol#L14)\n", + "id": "64a9f860d4e2c9cf89239b055236c4d39129725ffc45e3348bbd1856822b331d", + "check": "timestamp", + "impact": "Low", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/timestamp.timestamp.txt b/tests/expected_json/timestamp.timestamp.txt index ef64d694e..f26348c0c 100644 --- a/tests/expected_json/timestamp.timestamp.txt +++ b/tests/expected_json/timestamp.timestamp.txt @@ -1,4 +1,4 @@ -INFO:Detectors: + Timestamp.bad0() (tests/timestamp.sol#4-6) uses timestamp for comparisons Dangerous comparisons: - require(bool)(block.timestamp == 0) (tests/timestamp.sol#5) @@ -9,4 +9,4 @@ Timestamp.bad2() (tests/timestamp.sol#13-15) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > 0 (tests/timestamp.sol#14) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp -INFO:Slither:tests/timestamp.sol analyzed (1 contracts), 3 result(s) found +tests/timestamp.sol analyzed (1 contracts with 1 detectors), 3 result(s) found diff --git a/tests/expected_json/too_many_digits.too-many-digits.json b/tests/expected_json/too_many_digits.too-many-digits.json index a8f2311f6..a847ee5ed 100644 --- a/tests/expected_json/too_many_digits.too-many-digits.json +++ b/tests/expected_json/too_many_digits.too-many-digits.json @@ -4,11 +4,89 @@ "results": { "detectors": [ { - "check": "too-many-digits", - "impact": "Informational", - "confidence": "Medium", - "description": "C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x1 = 0x000001\n", "elements": [ + { + "type": "function", + "name": "f", + "source_mapping": { + "start": 174, + "length": 195, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "C", + "source_mapping": { + "start": 25, + "length": 897, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "f()" + } + }, { "type": "node", "name": "x1 = 0x000001", @@ -56,7 +134,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -95,7 +173,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -106,14 +189,98 @@ } } } - ] - }, - { + ], + "description": "C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x1 = 0x000001 (tests/too_many_digits.sol#10)\n", + "markdown": "[C.f()](tests/too_many_digits.sol#L9-L15) uses literals with too many digits:\n\t- [x1 = 0x000001](tests/too_many_digits.sol#L10)\n", + "id": "ad33e4cde46622380bf364b6f0829b9a7c8491e1c219e977211e3ed81a9927ed", "check": "too-many-digits", "impact": "Informational", - "confidence": "Medium", - "description": "C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x2 = 0x0000000000001\n", + "confidence": "Medium" + }, + { "elements": [ + { + "type": "function", + "name": "f", + "source_mapping": { + "start": 174, + "length": 195, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "C", + "source_mapping": { + "start": 25, + "length": 897, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "f()" + } + }, { "type": "node", "name": "x2 = 0x0000000000001", @@ -161,7 +328,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -200,7 +367,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -211,14 +383,98 @@ } } } - ] - }, - { + ], + "description": "C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x2 = 0x0000000000001 (tests/too_many_digits.sol#11)\n", + "markdown": "[C.f()](tests/too_many_digits.sol#L9-L15) uses literals with too many digits:\n\t- [x2 = 0x0000000000001](tests/too_many_digits.sol#L11)\n", + "id": "584a7ffb54e80195135c9bdc36073c0f8edd8fb9f3208b3b2b181a5612f66c25", "check": "too-many-digits", "impact": "Informational", - "confidence": "Medium", - "description": "C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x3 = 1000000000000000000\n", + "confidence": "Medium" + }, + { "elements": [ + { + "type": "function", + "name": "f", + "source_mapping": { + "start": 174, + "length": 195, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "C", + "source_mapping": { + "start": 25, + "length": 897, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "f()" + } + }, { "type": "node", "name": "x3 = 1000000000000000000", @@ -266,7 +522,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -305,7 +561,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -316,14 +577,98 @@ } } } - ] - }, - { + ], + "description": "C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x3 = 1000000000000000000 (tests/too_many_digits.sol#12)\n", + "markdown": "[C.f()](tests/too_many_digits.sol#L9-L15) uses literals with too many digits:\n\t- [x3 = 1000000000000000000](tests/too_many_digits.sol#L12)\n", + "id": "b88a6bfe113e8a92f42e445b6c3dfeb799c2254bc3c9c17c8fd92cb48f342385", "check": "too-many-digits", "impact": "Informational", - "confidence": "Medium", - "description": "C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x4 = 100000\n", + "confidence": "Medium" + }, + { "elements": [ + { + "type": "function", + "name": "f", + "source_mapping": { + "start": 174, + "length": 195, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "C", + "source_mapping": { + "start": 25, + "length": 897, + "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_relative": "tests/too_many_digits.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", + "filename_short": "tests/too_many_digits.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "f()" + } + }, { "type": "node", "name": "x4 = 100000", @@ -371,7 +716,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -410,7 +755,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -421,152 +771,131 @@ } } } - ] - }, - { + ], + "description": "C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits:\n\t- x4 = 100000 (tests/too_many_digits.sol#13)\n", + "markdown": "[C.f()](tests/too_many_digits.sol#L9-L15) uses literals with too many digits:\n\t- [x4 = 100000](tests/too_many_digits.sol#L13)\n", + "id": "3f65c504c516f8c6d309479729b47889c1b002be56d955a99eaafe9accab5688", "check": "too-many-digits", "impact": "Informational", - "confidence": "Medium", - "description": "C.h (tests/too_many_digits.sol#20-24) uses literals with too many digits:\n\t- x2 = 100000\n", + "confidence": "Medium" + }, + { "elements": [ { - "type": "node", - "name": "x2 = 100000", + "type": "function", + "name": "h", "source_mapping": { - "start": 509, - "length": 16, + "start": 453, + "length": 113, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_short": "tests/too_many_digits.sol", "is_dependency": false, "lines": [ - 22 + 20, + 21, + 22, + 23, + 24 ], - "starting_column": 9, - "ending_column": 25 + "starting_column": 5, + "ending_column": 6 }, "type_specific_fields": { "parent": { - "type": "function", - "name": "h", + "type": "contract", + "name": "C", "source_mapping": { - "start": 453, - "length": 113, + "start": 25, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_short": "tests/too_many_digits.sol", "is_dependency": false, "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, 20, 21, 22, 23, - 24 + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "C", - "source_mapping": { - "start": 25, - "length": 833, - "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_relative": "tests/too_many_digits.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_short": "tests/too_many_digits.sol", - "is_dependency": false, - "lines": [ - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "h()" + "starting_column": 1, + "ending_column": 2 } - } + }, + "signature": "h()" } - } - ] - }, - { - "check": "too-many-digits", - "impact": "Informational", - "confidence": "Medium", - "description": "C.i (tests/too_many_digits.sol#29-33) uses literals with too many digits:\n\t- x2 = 1000000000000 + 10000000000000 + 100000000000000 + 1000000000000000 + 10000000000000000\n", - "elements": [ + }, { "type": "node", - "name": "x2 = 1000000000000 + 10000000000000 + 100000000000000 + 1000000000000000 + 10000000000000000", + "name": "x2 = 100000", "source_mapping": { - "start": 749, - "length": 67, + "start": 509, + "length": 16, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_short": "tests/too_many_digits.sol", "is_dependency": false, "lines": [ - 31 + 22 ], "starting_column": 9, - "ending_column": 76 + "ending_column": 25 }, "type_specific_fields": { "parent": { "type": "function", - "name": "i", + "name": "h", "source_mapping": { - "start": 650, - "length": 201, + "start": 453, + "length": 113, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_short": "tests/too_many_digits.sol", "is_dependency": false, "lines": [ - 29, - 30, - 31, - 32, - 33 + 20, + 21, + 22, + 23, + 24 ], "starting_column": 5, "ending_column": 6 @@ -577,7 +906,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -616,18 +945,29 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 } }, - "signature": "i()" + "signature": "h()" } } } } - ] + ], + "description": "C.h() (tests/too_many_digits.sol#20-24) uses literals with too many digits:\n\t- x2 = 100000 (tests/too_many_digits.sol#22)\n", + "markdown": "[C.h()](tests/too_many_digits.sol#L20-L24) uses literals with too many digits:\n\t- [x2 = 100000](tests/too_many_digits.sol#L22)\n", + "id": "5f803e158c8bf519958ed6d12ba22e3100916c9a24f952d5cd574fd9ffe3aedb", + "check": "too-many-digits", + "impact": "Informational", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/too_many_digits.too-many-digits.txt b/tests/expected_json/too_many_digits.too-many-digits.txt index ccc93c52d..621ba8e85 100644 --- a/tests/expected_json/too_many_digits.too-many-digits.txt +++ b/tests/expected_json/too_many_digits.too-many-digits.txt @@ -1,15 +1,13 @@ -INFO:Detectors: -C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: - - x1 = 0x000001 -C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: - - x2 = 0x0000000000001 -C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: - - x3 = 1000000000000000000 -C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: - - x4 = 100000 -C.h (tests/too_many_digits.sol#20-24) uses literals with too many digits: - - x2 = 100000 -C.i (tests/too_many_digits.sol#29-33) uses literals with too many digits: - - x2 = 1000000000000 + 10000000000000 + 100000000000000 + 1000000000000000 + 10000000000000000 + +C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits: + - x1 = 0x000001 (tests/too_many_digits.sol#10) +C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits: + - x2 = 0x0000000000001 (tests/too_many_digits.sol#11) +C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits: + - x3 = 1000000000000000000 (tests/too_many_digits.sol#12) +C.f() (tests/too_many_digits.sol#9-15) uses literals with too many digits: + - x4 = 100000 (tests/too_many_digits.sol#13) +C.h() (tests/too_many_digits.sol#20-24) uses literals with too many digits: + - x2 = 100000 (tests/too_many_digits.sol#22) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits -INFO:Slither:tests/too_many_digits.sol analyzed (1 contracts), 6 result(s) found +tests/too_many_digits.sol analyzed (1 contracts with 1 detectors), 5 result(s) found diff --git a/tests/expected_json/tx_origin-0.5.1.tx-origin.json b/tests/expected_json/tx_origin-0.5.1.tx-origin.json index 020e85f5e..fa5576ca0 100644 --- a/tests/expected_json/tx_origin-0.5.1.tx-origin.json +++ b/tests/expected_json/tx_origin-0.5.1.tx-origin.json @@ -4,11 +4,71 @@ "results": { "detectors": [ { - "check": "tx-origin", - "impact": "Medium", - "confidence": "Medium", - "description": "TxOrigin.bug0() uses tx.origin for authorization: \"require(bool)(tx.origin == owner)\" (tests/tx_origin-0.5.1.sol#10)\n", "elements": [ + { + "type": "function", + "name": "bug0", + "source_mapping": { + "start": 127, + "length": 66, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_relative": "tests/tx_origin-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_short": "tests/tx_origin-0.5.1.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "TxOrigin", + "source_mapping": { + "start": 25, + "length": 442, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_relative": "tests/tx_origin-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_short": "tests/tx_origin-0.5.1.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bug0()" + } + }, { "type": "node", "name": "require(bool)(tx.origin == owner)", @@ -93,14 +153,82 @@ } } } - ] - }, - { + ], + "description": "TxOrigin.bug0() (tests/tx_origin-0.5.1.sol#9-11) uses tx.origin for authorization: require(bool)(tx.origin == owner) (tests/tx_origin-0.5.1.sol#10)\n", + "markdown": "[TxOrigin.bug0()](tests/tx_origin-0.5.1.sol#L9-L11) uses tx.origin for authorization: [require(bool)(tx.origin == owner)](tests/tx_origin-0.5.1.sol#L10)\n", + "id": "a0c994da7e4fde05152f6b40d0db7741dc9154c0c27417d123775a43017fbd0c", "check": "tx-origin", "impact": "Medium", - "confidence": "Medium", - "description": "TxOrigin.bug2() uses tx.origin for authorization: \"tx.origin != owner\" (tests/tx_origin-0.5.1.sol#14)\n", + "confidence": "Medium" + }, + { "elements": [ + { + "type": "function", + "name": "bug2", + "source_mapping": { + "start": 199, + "length": 95, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_relative": "tests/tx_origin-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_short": "tests/tx_origin-0.5.1.sol", + "is_dependency": false, + "lines": [ + 13, + 14, + 15, + 16, + 17 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "TxOrigin", + "source_mapping": { + "start": 25, + "length": 442, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_relative": "tests/tx_origin-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin-0.5.1.sol", + "filename_short": "tests/tx_origin-0.5.1.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bug2()" + } + }, { "type": "node", "name": "tx.origin != owner", @@ -187,7 +315,13 @@ } } } - ] + ], + "description": "TxOrigin.bug2() (tests/tx_origin-0.5.1.sol#13-17) uses tx.origin for authorization: tx.origin != owner (tests/tx_origin-0.5.1.sol#14)\n", + "markdown": "[TxOrigin.bug2()](tests/tx_origin-0.5.1.sol#L13-L17) uses tx.origin for authorization: [tx.origin != owner](tests/tx_origin-0.5.1.sol#L14)\n", + "id": "d7ce45ed56ab8579d6a9e88b9d57b5a3847a0925e3dd18dcb4a2f85b75bd7540", + "check": "tx-origin", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/tx_origin-0.5.1.tx-origin.txt b/tests/expected_json/tx_origin-0.5.1.tx-origin.txt index 24811f6e4..e5e11d02f 100644 --- a/tests/expected_json/tx_origin-0.5.1.tx-origin.txt +++ b/tests/expected_json/tx_origin-0.5.1.tx-origin.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -TxOrigin.bug0() uses tx.origin for authorization: "require(bool)(tx.origin == owner)" (tests/tx_origin-0.5.1.sol#10) -TxOrigin.bug2() uses tx.origin for authorization: "tx.origin != owner" (tests/tx_origin-0.5.1.sol#14) + +TxOrigin.bug0() (tests/tx_origin-0.5.1.sol#9-11) uses tx.origin for authorization: require(bool)(tx.origin == owner) (tests/tx_origin-0.5.1.sol#10) +TxOrigin.bug2() (tests/tx_origin-0.5.1.sol#13-17) uses tx.origin for authorization: tx.origin != owner (tests/tx_origin-0.5.1.sol#14) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin -INFO:Slither:tests/tx_origin-0.5.1.sol analyzed (1 contracts), 2 result(s) found +tests/tx_origin-0.5.1.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/tx_origin.tx-origin.json b/tests/expected_json/tx_origin.tx-origin.json index baa703378..97cb3c5f2 100644 --- a/tests/expected_json/tx_origin.tx-origin.json +++ b/tests/expected_json/tx_origin.tx-origin.json @@ -4,11 +4,71 @@ "results": { "detectors": [ { - "check": "tx-origin", - "impact": "Medium", - "confidence": "Medium", - "description": "TxOrigin.bug0() uses tx.origin for authorization: \"require(bool)(tx.origin == owner)\" (tests/tx_origin.sol#10)\n", "elements": [ + { + "type": "function", + "name": "bug0", + "source_mapping": { + "start": 116, + "length": 60, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_relative": "tests/tx_origin.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_short": "tests/tx_origin.sol", + "is_dependency": false, + "lines": [ + 9, + 10, + 11 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "TxOrigin", + "source_mapping": { + "start": 28, + "length": 393, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_relative": "tests/tx_origin.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_short": "tests/tx_origin.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bug0()" + } + }, { "type": "node", "name": "require(bool)(tx.origin == owner)", @@ -93,14 +153,82 @@ } } } - ] - }, - { + ], + "description": "TxOrigin.bug0() (tests/tx_origin.sol#9-11) uses tx.origin for authorization: require(bool)(tx.origin == owner) (tests/tx_origin.sol#10)\n", + "markdown": "[TxOrigin.bug0()](tests/tx_origin.sol#L9-L11) uses tx.origin for authorization: [require(bool)(tx.origin == owner)](tests/tx_origin.sol#L10)\n", + "id": "ee75fdded0c273df2f7875bab74fc778ff8f0c85bbed968c80d5fd787bbef62e", "check": "tx-origin", "impact": "Medium", - "confidence": "Medium", - "description": "TxOrigin.bug2() uses tx.origin for authorization: \"tx.origin != owner\" (tests/tx_origin.sol#14)\n", + "confidence": "Medium" + }, + { "elements": [ + { + "type": "function", + "name": "bug2", + "source_mapping": { + "start": 182, + "length": 89, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_relative": "tests/tx_origin.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_short": "tests/tx_origin.sol", + "is_dependency": false, + "lines": [ + 13, + 14, + 15, + 16, + 17 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "TxOrigin", + "source_mapping": { + "start": 28, + "length": 393, + "filename_used": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_relative": "tests/tx_origin.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/tx_origin.sol", + "filename_short": "tests/tx_origin.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bug2()" + } + }, { "type": "node", "name": "tx.origin != owner", @@ -187,7 +315,13 @@ } } } - ] + ], + "description": "TxOrigin.bug2() (tests/tx_origin.sol#13-17) uses tx.origin for authorization: tx.origin != owner (tests/tx_origin.sol#14)\n", + "markdown": "[TxOrigin.bug2()](tests/tx_origin.sol#L13-L17) uses tx.origin for authorization: [tx.origin != owner](tests/tx_origin.sol#L14)\n", + "id": "76446f7e20ba58a5f2094d357d8982c01225c8f61d73f33ed98316c3a4e892f0", + "check": "tx-origin", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/tx_origin.tx-origin.txt b/tests/expected_json/tx_origin.tx-origin.txt index ad8f9b78a..c9f9fbc92 100644 --- a/tests/expected_json/tx_origin.tx-origin.txt +++ b/tests/expected_json/tx_origin.tx-origin.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -TxOrigin.bug0() uses tx.origin for authorization: "require(bool)(tx.origin == owner)" (tests/tx_origin.sol#10) -TxOrigin.bug2() uses tx.origin for authorization: "tx.origin != owner" (tests/tx_origin.sol#14) + +TxOrigin.bug0() (tests/tx_origin.sol#9-11) uses tx.origin for authorization: require(bool)(tx.origin == owner) (tests/tx_origin.sol#10) +TxOrigin.bug2() (tests/tx_origin.sol#13-17) uses tx.origin for authorization: tx.origin != owner (tests/tx_origin.sol#14) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin -INFO:Slither:tests/tx_origin.sol analyzed (1 contracts), 2 result(s) found +tests/tx_origin.sol analyzed (1 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.json b/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.json index 3548d3215..4b8604f09 100644 --- a/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.json +++ b/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.json @@ -4,11 +4,58 @@ "results": { "detectors": [ { - "check": "unchecked-lowlevel", - "impact": "Medium", - "confidence": "Medium", - "description": "MyConc.bad(address) (tests/unchecked_lowlevel-0.5.1.sol#2-4) ignores return value by low-level calls \"dst.call.value(msg.value)()\" (tests/unchecked_lowlevel-0.5.1.sol#3)\n", "elements": [ + { + "type": "function", + "name": "bad", + "source_mapping": { + "start": 21, + "length": 96, + "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", + "filename_relative": "tests/unchecked_lowlevel-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", + "filename_short": "tests/unchecked_lowlevel-0.5.1.sol", + "is_dependency": false, + "lines": [ + 2, + 3, + 4 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "MyConc", + "source_mapping": { + "start": 0, + "length": 274, + "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", + "filename_relative": "tests/unchecked_lowlevel-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", + "filename_short": "tests/unchecked_lowlevel-0.5.1.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad(address)" + } + }, { "type": "node", "name": "dst.call.value(msg.value)()", @@ -79,59 +126,14 @@ } } } - }, - { - "type": "function", - "name": "bad", - "source_mapping": { - "start": 21, - "length": 96, - "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", - "filename_relative": "tests/unchecked_lowlevel-0.5.1.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", - "filename_short": "tests/unchecked_lowlevel-0.5.1.sol", - "is_dependency": false, - "lines": [ - 2, - 3, - 4 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "MyConc", - "source_mapping": { - "start": 0, - "length": 274, - "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", - "filename_relative": "tests/unchecked_lowlevel-0.5.1.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel-0.5.1.sol", - "filename_short": "tests/unchecked_lowlevel-0.5.1.sol", - "is_dependency": false, - "lines": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "bad(address)" - } } - ] + ], + "description": "MyConc.bad(address) (tests/unchecked_lowlevel-0.5.1.sol#2-4) ignores return value by dst.call.value(msg.value)() (tests/unchecked_lowlevel-0.5.1.sol#3)\n", + "markdown": "[MyConc.bad(address)](tests/unchecked_lowlevel-0.5.1.sol#L2-L4) ignores return value by [dst.call.value(msg.value)()](tests/unchecked_lowlevel-0.5.1.sol#L3)\n", + "id": "44ab2ac22a418dc8ec66536dc7409a0a2c23fd3276b3fb7fe919ec1f1eaf483f", + "check": "unchecked-lowlevel", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.txt b/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.txt index 42dbda5b4..59a0478e0 100644 --- a/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.txt +++ b/tests/expected_json/unchecked_lowlevel-0.5.1.unchecked-lowlevel.txt @@ -1,4 +1,4 @@ -INFO:Detectors: -MyConc.bad(address) (tests/unchecked_lowlevel-0.5.1.sol#2-4) ignores return value by low-level calls "dst.call.value(msg.value)()" (tests/unchecked_lowlevel-0.5.1.sol#3) + +MyConc.bad(address) (tests/unchecked_lowlevel-0.5.1.sol#2-4) ignores return value by dst.call.value(msg.value)() (tests/unchecked_lowlevel-0.5.1.sol#3) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls -INFO:Slither:tests/unchecked_lowlevel-0.5.1.sol analyzed (1 contracts), 1 result(s) found +tests/unchecked_lowlevel-0.5.1.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.json b/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.json index 9c964c90b..77b157ea7 100644 --- a/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.json +++ b/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.json @@ -4,11 +4,57 @@ "results": { "detectors": [ { - "check": "unchecked-lowlevel", - "impact": "Medium", - "confidence": "Medium", - "description": "MyConc.bad(address) (tests/unchecked_lowlevel.sol#2-4) ignores return value by low-level calls \"dst.call.value(msg.value)()\" (tests/unchecked_lowlevel.sol#3)\n", "elements": [ + { + "type": "function", + "name": "bad", + "source_mapping": { + "start": 21, + "length": 88, + "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", + "filename_relative": "tests/unchecked_lowlevel.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", + "filename_short": "tests/unchecked_lowlevel.sol", + "is_dependency": false, + "lines": [ + 2, + 3, + 4 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "MyConc", + "source_mapping": { + "start": 0, + "length": 214, + "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", + "filename_relative": "tests/unchecked_lowlevel.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", + "filename_short": "tests/unchecked_lowlevel.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad(address)" + } + }, { "type": "node", "name": "dst.call.value(msg.value)()", @@ -78,58 +124,14 @@ } } } - }, - { - "type": "function", - "name": "bad", - "source_mapping": { - "start": 21, - "length": 88, - "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", - "filename_relative": "tests/unchecked_lowlevel.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", - "filename_short": "tests/unchecked_lowlevel.sol", - "is_dependency": false, - "lines": [ - 2, - 3, - 4 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "MyConc", - "source_mapping": { - "start": 0, - "length": 214, - "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", - "filename_relative": "tests/unchecked_lowlevel.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_lowlevel.sol", - "filename_short": "tests/unchecked_lowlevel.sol", - "is_dependency": false, - "lines": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "bad(address)" - } } - ] + ], + "description": "MyConc.bad(address) (tests/unchecked_lowlevel.sol#2-4) ignores return value by dst.call.value(msg.value)() (tests/unchecked_lowlevel.sol#3)\n", + "markdown": "[MyConc.bad(address)](tests/unchecked_lowlevel.sol#L2-L4) ignores return value by [dst.call.value(msg.value)()](tests/unchecked_lowlevel.sol#L3)\n", + "id": "2975c4b0d2410cbe0a0901b36b4117c3c1017be5188e08339f0dd99a55b511ce", + "check": "unchecked-lowlevel", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.txt b/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.txt index 18ab55207..7506fb57b 100644 --- a/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.txt +++ b/tests/expected_json/unchecked_lowlevel.unchecked-lowlevel.txt @@ -1,4 +1,4 @@ -INFO:Detectors: -MyConc.bad(address) (tests/unchecked_lowlevel.sol#2-4) ignores return value by low-level calls "dst.call.value(msg.value)()" (tests/unchecked_lowlevel.sol#3) + +MyConc.bad(address) (tests/unchecked_lowlevel.sol#2-4) ignores return value by dst.call.value(msg.value)() (tests/unchecked_lowlevel.sol#3) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls -INFO:Slither:tests/unchecked_lowlevel.sol analyzed (1 contracts), 1 result(s) found +tests/unchecked_lowlevel.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/unchecked_send-0.5.1.unchecked-send.json b/tests/expected_json/unchecked_send-0.5.1.unchecked-send.json index 26bf7efdd..aef8a36f7 100644 --- a/tests/expected_json/unchecked_send-0.5.1.unchecked-send.json +++ b/tests/expected_json/unchecked_send-0.5.1.unchecked-send.json @@ -4,11 +4,65 @@ "results": { "detectors": [ { - "check": "unchecked-send", - "impact": "Medium", - "confidence": "Medium", - "description": "MyConc.bad(address) (tests/unchecked_send-0.5.1.sol#2-4) ignores return value by send calls \"dst.send(msg.value)\" (tests/unchecked_send-0.5.1.sol#3)\n", "elements": [ + { + "type": "function", + "name": "bad", + "source_mapping": { + "start": 21, + "length": 86, + "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", + "filename_relative": "tests/unchecked_send-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", + "filename_short": "tests/unchecked_send-0.5.1.sol", + "is_dependency": false, + "lines": [ + 2, + 3, + 4 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "MyConc", + "source_mapping": { + "start": 0, + "length": 419, + "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", + "filename_relative": "tests/unchecked_send-0.5.1.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", + "filename_short": "tests/unchecked_send-0.5.1.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad(address)" + } + }, { "type": "node", "name": "dst.send(msg.value)", @@ -86,66 +140,14 @@ } } } - }, - { - "type": "function", - "name": "bad", - "source_mapping": { - "start": 21, - "length": 86, - "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", - "filename_relative": "tests/unchecked_send-0.5.1.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", - "filename_short": "tests/unchecked_send-0.5.1.sol", - "is_dependency": false, - "lines": [ - 2, - 3, - 4 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "MyConc", - "source_mapping": { - "start": 0, - "length": 419, - "filename_used": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", - "filename_relative": "tests/unchecked_send-0.5.1.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unchecked_send-0.5.1.sol", - "filename_short": "tests/unchecked_send-0.5.1.sol", - "is_dependency": false, - "lines": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "bad(address)" - } } - ] + ], + "description": "MyConc.bad(address) (tests/unchecked_send-0.5.1.sol#2-4) ignores return value by dst.send(msg.value) (tests/unchecked_send-0.5.1.sol#3)\n", + "markdown": "[MyConc.bad(address)](tests/unchecked_send-0.5.1.sol#L2-L4) ignores return value by [dst.send(msg.value)](tests/unchecked_send-0.5.1.sol#L3)\n", + "id": "9e4d70a9e602a08c249500b8a32bd5cfe289229d2621a0f299c694c30c30d883", + "check": "unchecked-send", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/unchecked_send-0.5.1.unchecked-send.txt b/tests/expected_json/unchecked_send-0.5.1.unchecked-send.txt index 9feafa034..62d6a01ef 100644 --- a/tests/expected_json/unchecked_send-0.5.1.unchecked-send.txt +++ b/tests/expected_json/unchecked_send-0.5.1.unchecked-send.txt @@ -1,4 +1,4 @@ -INFO:Detectors: -MyConc.bad(address) (tests/unchecked_send-0.5.1.sol#2-4) ignores return value by send calls "dst.send(msg.value)" (tests/unchecked_send-0.5.1.sol#3) + +MyConc.bad(address) (tests/unchecked_send-0.5.1.sol#2-4) ignores return value by dst.send(msg.value) (tests/unchecked_send-0.5.1.sol#3) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send -INFO:Slither:tests/unchecked_send-0.5.1.sol analyzed (1 contracts), 1 result(s) found +tests/unchecked_send-0.5.1.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/uninitialized-0.5.1.uninitialized-state.json b/tests/expected_json/uninitialized-0.5.1.uninitialized-state.json index eca28e3ca..9ee33fc72 100644 --- a/tests/expected_json/uninitialized-0.5.1.uninitialized-state.json +++ b/tests/expected_json/uninitialized-0.5.1.uninitialized-state.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "uninitialized-state", - "impact": "High", - "confidence": "High", - "description": "Uninitialized.destination (tests/uninitialized-0.5.1.sol#5) is never initialized. It is used in:\n\t- transfer (tests/uninitialized-0.5.1.sol#7-9)\n", "elements": [ { "type": "variable", @@ -104,13 +100,15 @@ "signature": "transfer()" } } - ] - }, - { + ], + "description": "Uninitialized.destination (tests/uninitialized-0.5.1.sol#5) is never initialized. It is used in:\n\t- Uninitialized.transfer() (tests/uninitialized-0.5.1.sol#7-9)\n", + "markdown": "[Uninitialized.destination](tests/uninitialized-0.5.1.sol#L5) is never initialized. It is used in:\n\t- [Uninitialized.transfer()](tests/uninitialized-0.5.1.sol#L7-L9)\n", + "id": "e4711aebbd53922a9fe1e728917bf8e98eac065305e20d766b6b552debe79e44", "check": "uninitialized-state", "impact": "High", - "confidence": "High", - "description": "Test.balances (tests/uninitialized-0.5.1.sol#15) is never initialized. It is used in:\n\t- use (tests/uninitialized-0.5.1.sol#23-26)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -218,13 +216,15 @@ "signature": "use()" } } - ] - }, - { + ], + "description": "Test.balances (tests/uninitialized-0.5.1.sol#15) is never initialized. It is used in:\n\t- Test.use() (tests/uninitialized-0.5.1.sol#23-26)\n", + "markdown": "[Test.balances](tests/uninitialized-0.5.1.sol#L15) is never initialized. It is used in:\n\t- [Test.use()](tests/uninitialized-0.5.1.sol#L23-L26)\n", + "id": "a2750d175b02d51aeb47a4576f74725ba991d3c8cf828a33ee78ccc34cf9e7d7", "check": "uninitialized-state", "impact": "High", - "confidence": "High", - "description": "Test2.st (tests/uninitialized-0.5.1.sol#45) is never initialized. It is used in:\n\t- use (tests/uninitialized-0.5.1.sol#53-56)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -338,13 +338,15 @@ "signature": "use()" } } - ] - }, - { + ], + "description": "Test2.st (tests/uninitialized-0.5.1.sol#45) is never initialized. It is used in:\n\t- Test2.use() (tests/uninitialized-0.5.1.sol#53-56)\n", + "markdown": "[Test2.st](tests/uninitialized-0.5.1.sol#L45) is never initialized. It is used in:\n\t- [Test2.use()](tests/uninitialized-0.5.1.sol#L53-L56)\n", + "id": "427f100397f455d8000eff7b1d2463763ca8e452d5d98f7b7de693fd5e625a32", "check": "uninitialized-state", "impact": "High", - "confidence": "High", - "description": "Test2.v (tests/uninitialized-0.5.1.sol#47) is never initialized. It is used in:\n\t- init (tests/uninitialized-0.5.1.sol#49-51)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -457,7 +459,13 @@ "signature": "init()" } } - ] + ], + "description": "Test2.v (tests/uninitialized-0.5.1.sol#47) is never initialized. It is used in:\n\t- Test2.init() (tests/uninitialized-0.5.1.sol#49-51)\n", + "markdown": "[Test2.v](tests/uninitialized-0.5.1.sol#L47) is never initialized. It is used in:\n\t- [Test2.init()](tests/uninitialized-0.5.1.sol#L49-L51)\n", + "id": "bf96eee949943a12926cf1407a2df2b07e99b30a6fc2e78aebf088cdefcf77a7", + "check": "uninitialized-state", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/uninitialized-0.5.1.uninitialized-state.txt b/tests/expected_json/uninitialized-0.5.1.uninitialized-state.txt index ea27bfd31..9d4d66e14 100644 --- a/tests/expected_json/uninitialized-0.5.1.uninitialized-state.txt +++ b/tests/expected_json/uninitialized-0.5.1.uninitialized-state.txt @@ -1,11 +1,11 @@ -INFO:Detectors: + Uninitialized.destination (tests/uninitialized-0.5.1.sol#5) is never initialized. It is used in: - - transfer (tests/uninitialized-0.5.1.sol#7-9) + - Uninitialized.transfer() (tests/uninitialized-0.5.1.sol#7-9) Test.balances (tests/uninitialized-0.5.1.sol#15) is never initialized. It is used in: - - use (tests/uninitialized-0.5.1.sol#23-26) + - Test.use() (tests/uninitialized-0.5.1.sol#23-26) Test2.st (tests/uninitialized-0.5.1.sol#45) is never initialized. It is used in: - - use (tests/uninitialized-0.5.1.sol#53-56) + - Test2.use() (tests/uninitialized-0.5.1.sol#53-56) Test2.v (tests/uninitialized-0.5.1.sol#47) is never initialized. It is used in: - - init (tests/uninitialized-0.5.1.sol#49-51) + - Test2.init() (tests/uninitialized-0.5.1.sol#49-51) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables -INFO:Slither:tests/uninitialized-0.5.1.sol analyzed (4 contracts), 4 result(s) found +tests/uninitialized-0.5.1.sol analyzed (4 contracts with 1 detectors), 4 result(s) found diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index 288a04851..b53a6402b 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "uninitialized-state", - "impact": "High", - "confidence": "High", - "description": "Uninitialized.destination (tests/uninitialized.sol#5) is never initialized. It is used in:\n\t- transfer (tests/uninitialized.sol#7-9)\n", "elements": [ { "type": "variable", @@ -104,13 +100,15 @@ "signature": "transfer()" } } - ] - }, - { + ], + "description": "Uninitialized.destination (tests/uninitialized.sol#5) is never initialized. It is used in:\n\t- Uninitialized.transfer() (tests/uninitialized.sol#7-9)\n", + "markdown": "[Uninitialized.destination](tests/uninitialized.sol#L5) is never initialized. It is used in:\n\t- [Uninitialized.transfer()](tests/uninitialized.sol#L7-L9)\n", + "id": "e4711aebbd53922a9fe1e728917bf8e98eac065305e20d766b6b552debe79e44", "check": "uninitialized-state", "impact": "High", - "confidence": "High", - "description": "Test.balances (tests/uninitialized.sol#15) is never initialized. It is used in:\n\t- use (tests/uninitialized.sol#23-26)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -218,13 +216,15 @@ "signature": "use()" } } - ] - }, - { + ], + "description": "Test.balances (tests/uninitialized.sol#15) is never initialized. It is used in:\n\t- Test.use() (tests/uninitialized.sol#23-26)\n", + "markdown": "[Test.balances](tests/uninitialized.sol#L15) is never initialized. It is used in:\n\t- [Test.use()](tests/uninitialized.sol#L23-L26)\n", + "id": "a2750d175b02d51aeb47a4576f74725ba991d3c8cf828a33ee78ccc34cf9e7d7", "check": "uninitialized-state", "impact": "High", - "confidence": "High", - "description": "Test2.st (tests/uninitialized.sol#45) is never initialized. It is used in:\n\t- use (tests/uninitialized.sol#53-56)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -338,13 +338,15 @@ "signature": "use()" } } - ] - }, - { + ], + "description": "Test2.st (tests/uninitialized.sol#45) is never initialized. It is used in:\n\t- Test2.use() (tests/uninitialized.sol#53-56)\n", + "markdown": "[Test2.st](tests/uninitialized.sol#L45) is never initialized. It is used in:\n\t- [Test2.use()](tests/uninitialized.sol#L53-L56)\n", + "id": "427f100397f455d8000eff7b1d2463763ca8e452d5d98f7b7de693fd5e625a32", "check": "uninitialized-state", "impact": "High", - "confidence": "High", - "description": "Test2.v (tests/uninitialized.sol#47) is never initialized. It is used in:\n\t- init (tests/uninitialized.sol#49-51)\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -457,7 +459,13 @@ "signature": "init()" } } - ] + ], + "description": "Test2.v (tests/uninitialized.sol#47) is never initialized. It is used in:\n\t- Test2.init() (tests/uninitialized.sol#49-51)\n", + "markdown": "[Test2.v](tests/uninitialized.sol#L47) is never initialized. It is used in:\n\t- [Test2.init()](tests/uninitialized.sol#L49-L51)\n", + "id": "bf96eee949943a12926cf1407a2df2b07e99b30a6fc2e78aebf088cdefcf77a7", + "check": "uninitialized-state", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/uninitialized.uninitialized-state.txt b/tests/expected_json/uninitialized.uninitialized-state.txt index a516750bf..43fd9bb17 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.txt +++ b/tests/expected_json/uninitialized.uninitialized-state.txt @@ -1,11 +1,11 @@ -INFO:Detectors: + Uninitialized.destination (tests/uninitialized.sol#5) is never initialized. It is used in: - - transfer (tests/uninitialized.sol#7-9) + - Uninitialized.transfer() (tests/uninitialized.sol#7-9) Test.balances (tests/uninitialized.sol#15) is never initialized. It is used in: - - use (tests/uninitialized.sol#23-26) + - Test.use() (tests/uninitialized.sol#23-26) Test2.st (tests/uninitialized.sol#45) is never initialized. It is used in: - - use (tests/uninitialized.sol#53-56) + - Test2.use() (tests/uninitialized.sol#53-56) Test2.v (tests/uninitialized.sol#47) is never initialized. It is used in: - - init (tests/uninitialized.sol#49-51) + - Test2.init() (tests/uninitialized.sol#49-51) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables -INFO:Slither:tests/uninitialized.sol analyzed (4 contracts), 4 result(s) found +tests/uninitialized.sol analyzed (4 contracts with 1 detectors), 4 result(s) found diff --git a/tests/expected_json/uninitialized_local_variable.uninitialized-local.json b/tests/expected_json/uninitialized_local_variable.uninitialized-local.json index 7434ab404..8e2b0d379 100644 --- a/tests/expected_json/uninitialized_local_variable.uninitialized-local.json +++ b/tests/expected_json/uninitialized_local_variable.uninitialized-local.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "uninitialized-local", - "impact": "Medium", - "confidence": "Medium", - "description": "uint_not_init in Uninitialized.func() (tests/uninitialized_local_variable.sol#4) is a local variable never initialiazed\n", "elements": [ { "type": "variable", @@ -79,59 +75,14 @@ } } } - }, - { - "type": "function", - "name": "func", - "source_mapping": { - "start": 29, - "length": 143, - "filename_used": "/home/travis/build/crytic/slither/tests/uninitialized_local_variable.sol", - "filename_relative": "tests/uninitialized_local_variable.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/uninitialized_local_variable.sol", - "filename_short": "tests/uninitialized_local_variable.sol", - "is_dependency": false, - "lines": [ - 3, - 4, - 5, - 6, - 7 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "Uninitialized", - "source_mapping": { - "start": 0, - "length": 179, - "filename_used": "/home/travis/build/crytic/slither/tests/uninitialized_local_variable.sol", - "filename_relative": "tests/uninitialized_local_variable.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/uninitialized_local_variable.sol", - "filename_short": "tests/uninitialized_local_variable.sol", - "is_dependency": false, - "lines": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "func()" - } } - ] + ], + "description": "Uninitialized.func().uint_not_init (tests/uninitialized_local_variable.sol#4) is a local variable never initialiazed\n", + "markdown": "[Uninitialized.func().uint_not_init](tests/uninitialized_local_variable.sol#L4) is a local variable never initialiazed\n", + "id": "8cb0b6b473ffecf3c2284584eb0012c742d36709f75c7516c3ba9748f3c27a98", + "check": "uninitialized-local", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/uninitialized_local_variable.uninitialized-local.txt b/tests/expected_json/uninitialized_local_variable.uninitialized-local.txt index 6a013ffd9..6d5827508 100644 --- a/tests/expected_json/uninitialized_local_variable.uninitialized-local.txt +++ b/tests/expected_json/uninitialized_local_variable.uninitialized-local.txt @@ -1,4 +1,4 @@ -INFO:Detectors: -uint_not_init in Uninitialized.func() (tests/uninitialized_local_variable.sol#4) is a local variable never initialiazed + +Uninitialized.func().uint_not_init (tests/uninitialized_local_variable.sol#4) is a local variable never initialiazed Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables -INFO:Slither:tests/uninitialized_local_variable.sol analyzed (1 contracts), 1 result(s) found +tests/uninitialized_local_variable.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json index 08364d527..02c5143d2 100644 --- a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "uninitialized-storage", - "impact": "High", - "confidence": "High", - "description": "st_bug in Uninitialized.func() (tests/uninitialized_storage_pointer.sol#10) is a storage variable never initialiazed\n", "elements": [ { "type": "variable", @@ -85,65 +81,14 @@ } } } - }, - { - "type": "function", - "name": "func", - "source_mapping": { - "start": 67, - "length": 143, - "filename_used": "/home/travis/build/crytic/slither/tests/uninitialized_storage_pointer.sol", - "filename_relative": "tests/uninitialized_storage_pointer.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/uninitialized_storage_pointer.sol", - "filename_short": "tests/uninitialized_storage_pointer.sol", - "is_dependency": false, - "lines": [ - 7, - 8, - 9, - 10, - 11, - 12 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "Uninitialized", - "source_mapping": { - "start": 0, - "length": 217, - "filename_used": "/home/travis/build/crytic/slither/tests/uninitialized_storage_pointer.sol", - "filename_relative": "tests/uninitialized_storage_pointer.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/uninitialized_storage_pointer.sol", - "filename_short": "tests/uninitialized_storage_pointer.sol", - "is_dependency": false, - "lines": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "func()" - } } - ] + ], + "description": "Uninitialized.func().st_bug (tests/uninitialized_storage_pointer.sol#10) is a storage variable never initialiazed\n", + "markdown": "[Uninitialized.func().st_bug](tests/uninitialized_storage_pointer.sol#L10) is a storage variable never initialiazed\n", + "id": "af9ceb591701024a2199a04e66a121086d330d4cdeb3b160514cce2bb440579f", + "check": "uninitialized-storage", + "impact": "High", + "confidence": "High" } ] } diff --git a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.txt b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.txt index 486ff5ec1..6c104545b 100644 --- a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.txt +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.txt @@ -1,4 +1,4 @@ -INFO:Detectors: -st_bug in Uninitialized.func() (tests/uninitialized_storage_pointer.sol#10) is a storage variable never initialiazed + +Uninitialized.func().st_bug (tests/uninitialized_storage_pointer.sol#10) is a storage variable never initialiazed Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables -INFO:Slither:tests/uninitialized_storage_pointer.sol analyzed (1 contracts), 1 result(s) found +tests/uninitialized_storage_pointer.sol analyzed (1 contracts with 1 detectors), 1 result(s) found diff --git a/tests/expected_json/unused_return.unused-return.json b/tests/expected_json/unused_return.unused-return.json index e597b7392..c00154b8a 100644 --- a/tests/expected_json/unused_return.unused-return.json +++ b/tests/expected_json/unused_return.unused-return.json @@ -4,11 +4,75 @@ "results": { "detectors": [ { - "check": "unused-return", - "impact": "Medium", - "confidence": "Medium", - "description": "User.test(Target) (tests/unused_return.sol#17-29) ignores return value by external calls \"t.f()\" (tests/unused_return.sol#18)\n", "elements": [ + { + "type": "function", + "name": "test", + "source_mapping": { + "start": 239, + "length": 354, + "filename_used": "/home/travis/build/crytic/slither/tests/unused_return.sol", + "filename_relative": "tests/unused_return.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unused_return.sol", + "filename_short": "tests/unused_return.sol", + "is_dependency": false, + "lines": [ + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "User", + "source_mapping": { + "start": 189, + "length": 406, + "filename_used": "/home/travis/build/crytic/slither/tests/unused_return.sol", + "filename_relative": "tests/unused_return.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/unused_return.sol", + "filename_short": "tests/unused_return.sol", + "is_dependency": false, + "lines": [ + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "test(Target)" + } + }, { "type": "node", "name": "t.f()", @@ -96,7 +160,17 @@ } } } - }, + } + ], + "description": "User.test(Target) (tests/unused_return.sol#17-29) ignores return value by t.f() (tests/unused_return.sol#18)\n", + "markdown": "[User.test(Target)](tests/unused_return.sol#L17-L29) ignores return value by [t.f()](tests/unused_return.sol#L18)\n", + "id": "69f2810e24dbba754b406ce8b47e37543e9e491c0aa60d4dd2198c960e82b096", + "check": "unused-return", + "impact": "Medium", + "confidence": "Medium" + }, + { + "elements": [ { "type": "function", "name": "test", @@ -164,15 +238,7 @@ }, "signature": "test(Target)" } - } - ] - }, - { - "check": "unused-return", - "impact": "Medium", - "confidence": "Medium", - "description": "User.test(Target) (tests/unused_return.sol#17-29) ignores return value by external calls \"a.add(0)\" (tests/unused_return.sol#22)\n", - "elements": [ + }, { "type": "node", "name": "a.add(0)", @@ -260,76 +326,14 @@ } } } - }, - { - "type": "function", - "name": "test", - "source_mapping": { - "start": 239, - "length": 354, - "filename_used": "/home/travis/build/crytic/slither/tests/unused_return.sol", - "filename_relative": "tests/unused_return.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unused_return.sol", - "filename_short": "tests/unused_return.sol", - "is_dependency": false, - "lines": [ - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "User", - "source_mapping": { - "start": 189, - "length": 406, - "filename_used": "/home/travis/build/crytic/slither/tests/unused_return.sol", - "filename_relative": "tests/unused_return.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/unused_return.sol", - "filename_short": "tests/unused_return.sol", - "is_dependency": false, - "lines": [ - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "test(Target)" - } } - ] + ], + "description": "User.test(Target) (tests/unused_return.sol#17-29) ignores return value by a.add(0) (tests/unused_return.sol#22)\n", + "markdown": "[User.test(Target)](tests/unused_return.sol#L17-L29) ignores return value by [a.add(0)](tests/unused_return.sol#L22)\n", + "id": "502f40d2e259e5e0268547489b716077dff7ce3df82fb05eb76ccb5ffa38f72b", + "check": "unused-return", + "impact": "Medium", + "confidence": "Medium" } ] } diff --git a/tests/expected_json/unused_return.unused-return.txt b/tests/expected_json/unused_return.unused-return.txt index cae24dcf1..82ac0d99a 100644 --- a/tests/expected_json/unused_return.unused-return.txt +++ b/tests/expected_json/unused_return.unused-return.txt @@ -1,5 +1,5 @@ -INFO:Detectors: -User.test(Target) (tests/unused_return.sol#17-29) ignores return value by external calls "t.f()" (tests/unused_return.sol#18) -User.test(Target) (tests/unused_return.sol#17-29) ignores return value by external calls "a.add(0)" (tests/unused_return.sol#22) + +User.test(Target) (tests/unused_return.sol#17-29) ignores return value by t.f() (tests/unused_return.sol#18) +User.test(Target) (tests/unused_return.sol#17-29) ignores return value by a.add(0) (tests/unused_return.sol#22) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return -INFO:Slither:tests/unused_return.sol analyzed (3 contracts), 2 result(s) found +tests/unused_return.sol analyzed (3 contracts with 1 detectors), 2 result(s) found diff --git a/tests/expected_json/unused_state.unused-state.json b/tests/expected_json/unused_state.unused-state.json index 3a01bcb4a..786586e01 100644 --- a/tests/expected_json/unused_state.unused-state.json +++ b/tests/expected_json/unused_state.unused-state.json @@ -4,10 +4,6 @@ "results": { "detectors": [ { - "check": "unused-state", - "impact": "Informational", - "confidence": "High", - "description": "A.unused (tests/unused_state.sol#4) is never used in B\n", "elements": [ { "type": "variable", @@ -76,13 +72,15 @@ "ending_column": 2 } } - ] - }, - { + ], + "description": "A.unused (tests/unused_state.sol#4) is never used in B (tests/unused_state.sol#11-16)\n", + "markdown": "[A.unused](tests/unused_state.sol#L4) is never used in [B](tests/unused_state.sol#L11-L16)\n", + "id": "195279490862ae355bac3d27d0cdb1aa18200a5daed8f3dbd84dc5b120e29482", "check": "unused-state", "impact": "Informational", - "confidence": "High", - "description": "A.unused2 (tests/unused_state.sol#5) is never used in B\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -151,13 +149,15 @@ "ending_column": 2 } } - ] - }, - { + ], + "description": "A.unused2 (tests/unused_state.sol#5) is never used in B (tests/unused_state.sol#11-16)\n", + "markdown": "[A.unused2](tests/unused_state.sol#L5) is never used in [B](tests/unused_state.sol#L11-L16)\n", + "id": "886250d01813743573f3d311b742e0f818e0012ccbf8ad97738c029fd129d870", "check": "unused-state", "impact": "Informational", - "confidence": "High", - "description": "A.unused3 (tests/unused_state.sol#6) is never used in B\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -226,13 +226,15 @@ "ending_column": 2 } } - ] - }, - { + ], + "description": "A.unused3 (tests/unused_state.sol#6) is never used in B (tests/unused_state.sol#11-16)\n", + "markdown": "[A.unused3](tests/unused_state.sol#L6) is never used in [B](tests/unused_state.sol#L11-L16)\n", + "id": "e2ac51590509d97ff791ce50d9a711fc5ad01c20d23eacf6fb31939bd91b9f48", "check": "unused-state", "impact": "Informational", - "confidence": "High", - "description": "A.unused4 (tests/unused_state.sol#7) is never used in B\n", + "confidence": "High" + }, + { "elements": [ { "type": "variable", @@ -301,7 +303,13 @@ "ending_column": 2 } } - ] + ], + "description": "A.unused4 (tests/unused_state.sol#7) is never used in B (tests/unused_state.sol#11-16)\n", + "markdown": "[A.unused4](tests/unused_state.sol#L7) is never used in [B](tests/unused_state.sol#L11-L16)\n", + "id": "562d3e6a04f6f6068c3e4f0c074ecdbcff87929e43ec6fbeb6c088e715f63cf2", + "check": "unused-state", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/unused_state.unused-state.txt b/tests/expected_json/unused_state.unused-state.txt index 66be3708e..c719aa7ad 100644 --- a/tests/expected_json/unused_state.unused-state.txt +++ b/tests/expected_json/unused_state.unused-state.txt @@ -1,7 +1,7 @@ -INFO:Detectors: -A.unused (tests/unused_state.sol#4) is never used in B -A.unused2 (tests/unused_state.sol#5) is never used in B -A.unused3 (tests/unused_state.sol#6) is never used in B -A.unused4 (tests/unused_state.sol#7) is never used in B + +A.unused (tests/unused_state.sol#4) is never used in B (tests/unused_state.sol#11-16) +A.unused2 (tests/unused_state.sol#5) is never used in B (tests/unused_state.sol#11-16) +A.unused3 (tests/unused_state.sol#6) is never used in B (tests/unused_state.sol#11-16) +A.unused4 (tests/unused_state.sol#7) is never used in B (tests/unused_state.sol#11-16) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables -INFO:Slither:tests/unused_state.sol analyzed (2 contracts), 4 result(s) found +tests/unused_state.sol analyzed (2 contracts with 1 detectors), 4 result(s) found diff --git a/tests/expected_json/void-cst.void-cst.json b/tests/expected_json/void-cst.void-cst.json new file mode 100644 index 000000000..6792be666 --- /dev/null +++ b/tests/expected_json/void-cst.void-cst.json @@ -0,0 +1,132 @@ +{ + "success": true, + "error": null, + "results": { + "detectors": [ + { + "elements": [ + { + "type": "function", + "name": "constructor", + "source_mapping": { + "start": 41, + "length": 32, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 10, + 11, + 12 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "D", + "source_mapping": { + "start": 19, + "length": 57, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "constructor()" + } + }, + { + "type": "node", + "name": "C()", + "source_mapping": { + "start": 62, + "length": 3, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 10 + ], + "starting_column": 26, + "ending_column": 29 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "constructor", + "source_mapping": { + "start": 41, + "length": 32, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 10, + 11, + 12 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "D", + "source_mapping": { + "start": 19, + "length": 57, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "constructor()" + } + } + } + } + ], + "description": "Void constructor called in D.constructor() (tests/void-cst.sol#10-12):\n\t- C() (tests/void-cst.sol#10)\n", + "markdown": "Void constructor called in [D.constructor()](tests/void-cst.sol#L10-L12):\n\t- [C()](tests/void-cst.sol#L10)\n", + "id": "2d4e0ed4dc3c0871d43c6de59147f2bc48b0b2591237bfca8cdc3576388fce5a", + "check": "void-cst", + "impact": "Low", + "confidence": "High" + } + ] + } +} \ No newline at end of file diff --git a/tests/expected_json/void-cst.void-cst.txt b/tests/expected_json/void-cst.void-cst.txt new file mode 100644 index 000000000..95daeabe6 --- /dev/null +++ b/tests/expected_json/void-cst.void-cst.txt @@ -0,0 +1,5 @@ + +Void constructor called in D.constructor() (tests/void-cst.sol#10-12): + - C() (tests/void-cst.sol#10) +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor +tests/void-cst.sol analyzed (2 contracts with 1 detectors), 1 result(s) found diff --git a/tests/too_many_digits.sol b/tests/too_many_digits.sol index 84e930056..6156d0da5 100644 --- a/tests/too_many_digits.sol +++ b/tests/too_many_digits.sol @@ -31,5 +31,10 @@ contract C { uint x2 = 1 szabo + 10 szabo + 100 szabo + 1000 szabo + 10000 szabo; balance += x1 + x2; } + + function good() external{ + + uint x = 1 ether; + } } diff --git a/tests/void-cst.sol b/tests/void-cst.sol new file mode 100644 index 000000000..43edc9d9a --- /dev/null +++ b/tests/void-cst.sol @@ -0,0 +1,14 @@ + + +contract C{ + + +} + +contract D is C{ + + constructor() public C(){ + + } + +} diff --git a/utils/upgradeability/__main__.py b/utils/upgradeability/__main__.py deleted file mode 100644 index e9183a852..000000000 --- a/utils/upgradeability/__main__.py +++ /dev/null @@ -1,62 +0,0 @@ -import logging -import argparse -import sys - -from slither import Slither -from crytic_compile import cryticparser - -from .compare_variables_order import compare_variables_order_implementation, compare_variables_order_proxy -from .compare_function_ids import compare_function_ids -from .check_initialization import check_initialization - -logging.basicConfig() -logging.getLogger("Slither-check-upgradeability").setLevel(logging.INFO) -logging.getLogger("Slither").setLevel(logging.INFO) - - -def parse_args(): - - parser = argparse.ArgumentParser(description='Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.', - usage="slither-check-upgradeability proxy.sol ProxyName implem.sol ContractName") - - - parser.add_argument('proxy.sol', help='Proxy filename') - parser.add_argument('ProxyName', help='Contract name') - - parser.add_argument('implem.sol', help='Implementation filename') - parser.add_argument('ContractName', help='Contract name') - - parser.add_argument('--new-version', help='New implementation filename') - parser.add_argument('--new-contract-name', help='New contract name (if changed)') - - cryticparser.init(parser) - - if len(sys.argv) == 1: - parser.print_help(sys.stderr) - sys.exit(1) - - return parser.parse_args() - -def main(): - args = parse_args() - - proxy_filename = vars(args)['proxy.sol'] - proxy = Slither(proxy_filename, **vars(args)) - proxy_name = args.ProxyName - - v1_filename = vars(args)['implem.sol'] - v1 = Slither(v1_filename, **vars(args)) - v1_name = args.ContractName - - check_initialization(v1) - - if not args.new_version: - compare_function_ids(v1, v1_name, proxy, proxy_name) - compare_variables_order_proxy(v1, v1_name, proxy, proxy_name) - else: - v2 = Slither(args.new_version, **vars(args)) - v2_name = v1_name if not args.new_contract_name else args.new_contract_name - check_initialization(v2) - compare_function_ids(v2, v2_name, proxy, proxy_name) - compare_variables_order_proxy(v2, v2_name, proxy, proxy_name) - compare_variables_order_implementation(v1, v1_name, v2, v2_name) diff --git a/utils/upgradeability/check_initialization.py b/utils/upgradeability/check_initialization.py deleted file mode 100644 index 22885ed31..000000000 --- a/utils/upgradeability/check_initialization.py +++ /dev/null @@ -1,78 +0,0 @@ -import logging -from slither import Slither -from slither.slithir.operations import InternalCall -from slither.utils.colors import green,red -from slither.utils.colors import red, yellow, green - -logger = logging.getLogger("CheckInitialization") -logger.setLevel(logging.INFO) - -class MultipleInitTarget(Exception): - pass - -def _get_initialize_functions(contract): - return [f for f in contract.functions if f.name == 'initialize'] - -def _get_all_internal_calls(function): - all_ir = function.all_slithir_operations() - return [i.function for i in all_ir if isinstance(i, InternalCall) and i.function_name == "initialize"] - - -def _get_most_derived_init(contract): - init_functions = [f for f in contract.functions if not f.is_shadowed and f.name == 'initialize'] - if len(init_functions) > 1: - raise MultipleInitTarget - if init_functions: - return init_functions[0] - return None - -def check_initialization(s): - - initializable = s.get_contract_from_name('Initializable') - - logger.info(green('Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks)')) - - if initializable is None: - logger.info(yellow('Initializable contract not found, the contract does not follow a standard initalization schema.')) - return - - init_info = '' - - double_calls_found = False - missing_call = False - initializer_modifier_missing = False - - for contract in s.contracts: - if initializable in contract.inheritance: - initializer = contract.get_modifier_from_canonical_name('Initializable.initializer()') - all_init_functions = _get_initialize_functions(contract) - for f in all_init_functions: - if not initializer in f.modifiers: - initializer_modifier_missing = True - logger.info(red(f'{f.canonical_name} does not call initializer')) - most_derived_init = _get_most_derived_init(contract) - if most_derived_init is None: - init_info += f'{contract.name} has no initialize function\n' - continue - else: - init_info += f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n' - all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init] - missing_calls = [f for f in all_init_functions if not f in all_init_functions_called] - for f in missing_calls: - logger.info(red(f'Missing call to {f.canonical_name} in {contract.name}')) - missing_call = True - double_calls = list(set([f for f in all_init_functions_called if all_init_functions_called.count(f) > 1])) - for f in double_calls: - logger.info(red(f'{f.canonical_name} is called multiple time in {contract.name}')) - double_calls_found = True - - if not initializer_modifier_missing: - logger.info(green('All the init functions have the initiliazer modifier')) - - if not double_calls_found: - logger.info(green('No double call to init functions found')) - - if not missing_call: - logger.info(green('No missing call to an init function found')) - - logger.info(green('Check the deployement script to ensure that these functions are called:\n'+ init_info)) diff --git a/utils/upgradeability/compare_function_ids.py b/utils/upgradeability/compare_function_ids.py deleted file mode 100644 index dd1f85313..000000000 --- a/utils/upgradeability/compare_function_ids.py +++ /dev/null @@ -1,54 +0,0 @@ -''' - Check for functions collisions between a proxy and the implementation - More for information: https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357 -''' - -import logging -from slither import Slither -from slither.utils.function import get_function_id -from slither.utils.colors import red, green - -logger = logging.getLogger("CompareFunctions") -logger.setLevel(logging.INFO) - -def get_signatures(c): - functions = c.functions - functions = [f.full_name for f in functions if f.visibility in ['public', 'external'] and not f.is_constructor] - - variables = c.state_variables - variables = [variable.name+ '()' for variable in variables if variable.visibility in ['public']] - return list(set(functions+variables)) - - -def compare_function_ids(implem, implem_name, proxy, proxy_name): - - logger.info(green('Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks)')) - - implem_contract = implem.get_contract_from_name(implem_name) - if implem_contract is None: - logger.info(red(f'{implem_name} not found in {implem.filename}')) - return - proxy_contract = proxy.get_contract_from_name(proxy_name) - if proxy_contract is None: - logger.info(red(f'{proxy_name} not found in {proxy.filename}')) - return - - signatures_implem = get_signatures(implem_contract) - signatures_proxy = get_signatures(proxy_contract) - - signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} - signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} - - found = False - for (k, _) in signatures_ids_implem.items(): - if k in signatures_ids_proxy: - found = True - if signatures_ids_implem[k] != signatures_ids_proxy[k]: - logger.info(red('Function id collision found {} {}'.format(signatures_ids_implem[k], - signatures_ids_proxy[k]))) - else: - logger.info(red('Shadowing between proxy and implementation found {}'.format(signatures_ids_implem[k]))) - - if not found: - logger.info(green('No function ids collision found')) - diff --git a/utils/upgradeability/compare_variables_order.py b/utils/upgradeability/compare_variables_order.py deleted file mode 100644 index 59a3b2f5a..000000000 --- a/utils/upgradeability/compare_variables_order.py +++ /dev/null @@ -1,100 +0,0 @@ -''' - Check if the variables respect the same ordering -''' -import logging -from slither import Slither -from slither.utils.function import get_function_id -from slither.utils.colors import red, green, yellow - -logger = logging.getLogger("VariablesOrder") -logger.setLevel(logging.INFO) - -def compare_variables_order_implementation(v1, contract_name1, v2, contract_name2): - - logger.info(green('Run variables order checks between implementations... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks)')) - - contract_v1 = v1.get_contract_from_name(contract_name1) - if contract_v1 is None: - logger.info(red('Contract {} not found in {}'.format(contract_name1, v1.filename))) - exit(-1) - - contract_v2 = v2.get_contract_from_name(contract_name2) - if contract_v2 is None: - logger.info(red('Contract {} not found in {}'.format(contract_name2, v2.filename))) - exit(-1) - - - order_v1 = [(variable.name, variable.type) for variable in contract_v1.state_variables if not variable.is_constant] - order_v2 = [(variable.name, variable.type) for variable in contract_v2.state_variables if not variable.is_constant] - - - found = False - for idx in range(0, len(order_v1)): - (v1_name, v1_type) = order_v1[idx] - if len(order_v2) < idx: - logger.info(red('Missing variable in the new version: {} {}'.format(v1_name, v1_type))) - continue - (v2_name, v2_type) = order_v2[idx] - - if (v1_name != v2_name) or (v1_type != v2_type): - found = True - logger.info(red('Different variables between v1 and v2: {} {} -> {} {}'.format(v1_name, - v1_type, - v2_name, - v2_type))) - - if len(order_v2) > len(order_v1): - new_variables = order_v2[len(order_v1):] - for (name, t) in new_variables: - logger.info(green('New variable: {} {}'.format(name, t))) - - if not found: - logger.info(green('No variables ordering error found between implementations')) - -def compare_variables_order_proxy(implem, implem_name, proxy, proxy_name): - - logger.info(green('Run variables order checks between the implementation and the proxy... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks)')) - - contract_implem = implem.get_contract_from_name(implem_name) - if contract_implem is None: - logger.info(red('Contract {} not found in {}'.format(implem_name, implem.filename))) - exit(-1) - - contract_proxy = proxy.get_contract_from_name(proxy_name) - if contract_proxy is None: - logger.info(red('Contract {} not found in {}'.format(proxy_name, proxy.filename))) - exit(-1) - - - order_implem = [(variable.name, variable.type) for variable in contract_implem.state_variables if not variable.is_constant] - order_proxy = [(variable.name, variable.type) for variable in contract_proxy.state_variables if not variable.is_constant] - - - found = False - for idx in range(0, len(order_proxy)): - (proxy_name, proxy_type) = order_proxy[idx] - if len(order_implem) <= idx: - logger.info(red('Extra variable in the proxy: {} {}'.format(proxy_name, proxy_type))) - continue - (implem_name, implem_type) = order_implem[idx] - - if (proxy_name != implem_name) or (proxy_type != implem_type): - found = True - logger.info(red('Different variables between proxy and implem: {} {} -> {} {}'.format(proxy_name, - proxy_type, - implem_name, - implem_type))) - else: - logger.info(yellow('Variable in the proxy: {} {}'.format(proxy_name, - proxy_type))) - - - #if len(order_implem) > len(order_proxy): - # new_variables = order_implem[len(order_proxy):] - # for (name, t) in new_variables: - # logger.info(green('Variable only in implem: {} {}'.format(name, t))) - - if not found: - logger.info(green('No variables ordering error found between implementation and the proxy')) - -