Merge branch 'dev' into dev-evm-printer-and-api

pull/281/head
Josselin 5 years ago
commit 1029b5233f
  1. 2
      .travis.yml
  2. 24
      CONTRIBUTING.md
  3. 62
      README.md
  4. 26
      examples/printers/constructors.sol
  5. 2
      plugin_example/slither_my_plugin/detectors/example.py
  6. 2
      scripts/tests_generate_expected_json_4.sh
  7. 1
      scripts/tests_generate_expected_json_5.sh
  8. 7
      scripts/travis_install.sh
  9. 1
      scripts/travis_test_5.sh
  10. 5
      scripts/travis_test_dapp.sh
  11. 4
      scripts/travis_test_embark.sh
  12. 20
      scripts/travis_test_erc.sh
  13. 4
      scripts/travis_test_etherlime.sh
  14. 2
      scripts/travis_test_etherscan.sh
  15. 16
      scripts/travis_test_kspec.sh
  16. 110
      scripts/travis_test_upgradability.sh
  17. 17
      setup.py
  18. 286
      slither/__main__.py
  19. 58
      slither/core/cfg/node.py
  20. 12
      slither/core/children/child_expression.py
  21. 4
      slither/core/children/child_node.py
  22. 56
      slither/core/declarations/contract.py
  23. 314
      slither/core/declarations/function.py
  24. 4
      slither/core/declarations/pragma_directive.py
  25. 10
      slither/core/expressions/literal.py
  26. 53
      slither/core/slither_core.py
  27. 33
      slither/core/source_mapping/source_mapping.py
  28. 2
      slither/core/variables/local_variable.py
  29. 23
      slither/core/variables/state_variable.py
  30. 19
      slither/core/variables/variable.py
  31. 220
      slither/detectors/abstract_detector.py
  32. 1
      slither/detectors/all_detectors.py
  33. 29
      slither/detectors/attributes/const_functions.py
  34. 19
      slither/detectors/attributes/constant_pragma.py
  35. 16
      slither/detectors/attributes/incorrect_solc.py
  36. 18
      slither/detectors/attributes/locked_ether.py
  37. 9
      slither/detectors/erc/incorrect_erc20_interface.py
  38. 11
      slither/detectors/erc/incorrect_erc721_interface.py
  39. 13
      slither/detectors/erc/unindexed_event_parameters.py
  40. 10
      slither/detectors/examples/backdoor.py
  41. 15
      slither/detectors/functions/arbitrary_send.py
  42. 7
      slither/detectors/functions/complex_function.py
  43. 31
      slither/detectors/functions/external_function.py
  44. 11
      slither/detectors/functions/suicidal.py
  45. 110
      slither/detectors/naming_convention/naming_convention.py
  46. 16
      slither/detectors/operations/block_timestamp.py
  47. 13
      slither/detectors/operations/low_level_calls.py
  48. 17
      slither/detectors/operations/unused_return_values.py
  49. 45
      slither/detectors/operations/void_constructor.py
  50. 45
      slither/detectors/reentrancy/reentrancy.py
  51. 39
      slither/detectors/reentrancy/reentrancy_benign.py
  52. 36
      slither/detectors/reentrancy/reentrancy_eth.py
  53. 30
      slither/detectors/reentrancy/reentrancy_read_before_write.py
  54. 12
      slither/detectors/shadowing/abstract.py
  55. 37
      slither/detectors/shadowing/builtin_symbols.py
  56. 38
      slither/detectors/shadowing/local.py
  57. 11
      slither/detectors/shadowing/state.py
  58. 15
      slither/detectors/source/rtlo.py
  59. 11
      slither/detectors/statements/assembly.py
  60. 11
      slither/detectors/statements/calls_in_loop.py
  61. 14
      slither/detectors/statements/controlled_delegatecall.py
  62. 16
      slither/detectors/statements/deprecated_calls.py
  63. 14
      slither/detectors/statements/incorrect_strict_equality.py
  64. 13
      slither/detectors/statements/too_many_digits.py
  65. 11
      slither/detectors/statements/tx_origin.py
  66. 15
      slither/detectors/variables/possible_const_state_variables.py
  67. 14
      slither/detectors/variables/uninitialized_local_variables.py
  68. 22
      slither/detectors/variables/uninitialized_state_variables.py
  69. 13
      slither/detectors/variables/uninitialized_storage_variables.py
  70. 22
      slither/detectors/variables/unused_state_variables.py
  71. 0
      slither/formatters/__init__.py
  72. 0
      slither/formatters/attributes/__init__.py
  73. 36
      slither/formatters/attributes/const_functions.py
  74. 69
      slither/formatters/attributes/constant_pragma.py
  75. 59
      slither/formatters/attributes/incorrect_solc.py
  76. 5
      slither/formatters/exceptions.py
  77. 0
      slither/formatters/functions/__init__.py
  78. 42
      slither/formatters/functions/external_function.py
  79. 0
      slither/formatters/naming_convention/__init__.py
  80. 609
      slither/formatters/naming_convention/naming_convention.py
  81. 0
      slither/formatters/utils/__init__.py
  82. 43
      slither/formatters/utils/patches.py
  83. 0
      slither/formatters/variables/__init__.py
  84. 38
      slither/formatters/variables/possible_const_state_variables.py
  85. 28
      slither/formatters/variables/unused_state_variables.py
  86. 11
      slither/printers/abstract_printer.py
  87. 2
      slither/printers/all_printers.py
  88. 29
      slither/printers/call/call_graph.py
  89. 15
      slither/printers/functions/authorization.py
  90. 17
      slither/printers/functions/cfg.py
  91. 0
      slither/printers/guidance/__init__.py
  92. 145
      slither/printers/guidance/echidna.py
  93. 22
      slither/printers/inheritance/inheritance.py
  94. 49
      slither/printers/inheritance/inheritance_graph.py
  95. 52
      slither/printers/summary/constructor_calls.py
  96. 45
      slither/printers/summary/contract.py
  97. 12
      slither/printers/summary/data_depenency.py
  98. 13
      slither/printers/summary/function.py
  99. 25
      slither/printers/summary/function_ids.py
  100. 60
      slither/printers/summary/human_summary.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -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

@ -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).

@ -33,6 +33,8 @@ $ slither tests/uninitialized.sol
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
By default, all the detectors are run.
@ -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,19 +90,21 @@ 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
@ -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
```

@ -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;
}
}

@ -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]

@ -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"

@ -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"

@ -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

@ -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"

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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'
]
}
)

@ -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)
# endregion
###################################################################################
###################################################################################
# region Output
###################################################################################
###################################################################################
return process_single(all_contracts, args, detector_classes, printer_classes)
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
# 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)
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))
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))
(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)
if args.json:
output_json(results, None if stdout_json else args.json)
# 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:
output_error = str(se)
logging.error(red('Error:'))
logging.error(red(se))
logging.error(red(output_error))
logging.error('Please report an issue to https://github.com/crytic/slither/issues')
sys.exit(-1)
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:
output_error = traceback.format_exc()
logging.error('Error in %s' % args.filename)
logging.error(traceback.format_exc())
sys.exit(-1)
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__':

@ -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
###################################################################################
###################################################################################

@ -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

@ -18,3 +18,7 @@ class ChildNode(object):
@property
def contract(self):
return self.node.function.contract
@property
def slither(self):
return self.contract.slither

@ -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):

@ -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:
if self._name == '' and self._function_type == FunctionType.CONSTRUCTOR:
return 'constructor'
else:
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

@ -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)

@ -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)

@ -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,9 +67,16 @@ class Slither(Context):
:param path:
:return:
"""
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)))
@ -229,3 +265,18 @@ class Slither(Context):
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

@ -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}'

@ -52,6 +52,6 @@ class LocalVariable(ChildFunction, Variable):
@property
def canonical_name(self):
return self.name
return '{}.{}'.format(self.function.canonical_name, self.name)

@ -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
###################################################################################
###################################################################################

@ -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

@ -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):
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)
@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)
output.data['check'] = self.ARGUMENT
output.data['impact'] = classification_txt[self.IMPACT]
output.data['confidence'] = classification_txt[self.CONFIDENCE]
d['elements'].append(element)
return output
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)
@staticmethod
def _format(slither, result):
"""Implement format"""
return

@ -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
#
#

@ -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)

@ -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)

@ -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)

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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

@ -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)

@ -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

@ -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

@ -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

@ -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

@ -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:
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)

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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

@ -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

@ -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

@ -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)

@ -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!")

@ -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)

@ -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)

@ -0,0 +1,5 @@
from slither.exceptions import SlitherException
class FormatImpossible(SlitherException): pass
class FormatError(SlitherException): pass

@ -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")

@ -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

@ -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'

@ -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?!")

@ -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,
"")

@ -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"""

@ -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.constructor_calls import ConstructorPrinter
from .guidance.echidna import Echidna
from .summary.evm import PrinterEVM

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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 = '<TR><TD align="left"> %s</TD></TR>'
pattern_shadow = '<TR><TD align="left"><font color="#FFA500"> %s</font></TD></TR>'
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)
with open(filename, 'w', encoding='utf8') as f:
f.write('digraph "" {\n')
content = 'digraph "" {\n'
for c in self.contracts:
f.write(self._summary(c))
f.write('}')
content += self._summary(c) + '\n'
content += '}'
with open(filename, 'w', encoding='utf8') as f:
f.write(content)
res = self.generate_output(info)
res.add_file(filename, content)
return res

@ -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

@ -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,12 +21,15 @@ 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:
collect[a].append(b)
@ -33,15 +37,26 @@ class ContractSummary(AbstractPrinter):
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

@ -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

@ -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

@ -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

@ -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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save