Merge branch 'dev-ci' into dev-echidna-printer-fix

pull/763/head
Josselin 4 years ago
commit 0dbcae7692
  1. 48
      .github/workflows/black.yml
  2. 8
      .github/workflows/ci.yml
  3. 8
      .github/workflows/detectors.yml
  4. 26
      .github/workflows/linter.yml
  5. 4
      .github/workflows/parser.yml
  6. 49
      .github/workflows/pylint.yml
  7. 8
      CONTRIBUTING.md
  8. 5
      README.md
  9. 4
      plugin_example/setup.py
  10. 2
      scripts/ci_test_cli.sh
  11. 2
      scripts/ci_test_erc.sh
  12. 2
      scripts/ci_test_find_paths.sh
  13. 2
      scripts/ci_test_kspec.sh
  14. 2
      scripts/ci_test_printers.sh
  15. 2
      scripts/ci_test_simil.sh
  16. 2
      scripts/ci_test_upgradability.sh
  17. 15
      slither/__main__.py
  18. 19
      slither/analyses/data_dependency/data_dependency.py
  19. 5
      slither/analyses/write/are_variables_written.py
  20. 8
      slither/core/cfg/node.py
  21. 23
      slither/core/declarations/function.py
  22. 4
      slither/core/solidity_types/function_type.py
  23. 11
      slither/core/solidity_types/user_defined_type.py
  24. 3
      slither/detectors/attributes/locked_ether.py
  25. 8
      slither/detectors/functions/arbitrary_send.py
  26. 18
      slither/detectors/naming_convention/naming_convention.py
  27. 4
      slither/detectors/operations/block_timestamp.py
  28. 3
      slither/detectors/reentrancy/reentrancy.py
  29. 12
      slither/detectors/reentrancy/reentrancy_benign.py
  30. 10
      slither/detectors/reentrancy/reentrancy_eth.py
  31. 12
      slither/detectors/reentrancy/reentrancy_events.py
  32. 18
      slither/detectors/reentrancy/reentrancy_no_gas.py
  33. 7
      slither/detectors/reentrancy/reentrancy_read_before_write.py
  34. 6
      slither/detectors/statements/incorrect_strict_equality.py
  35. 4
      slither/detectors/statements/unprotected_upgradeable.py
  36. 7
      slither/formatters/attributes/constant_pragma.py
  37. 7
      slither/formatters/naming_convention/naming_convention.py
  38. 58
      slither/printers/call/call_graph.py
  39. 6
      slither/printers/functions/authorization.py
  40. 6
      slither/printers/summary/evm.py
  41. 19
      slither/printers/summary/function.py
  42. 9
      slither/printers/summary/human_summary.py
  43. 5
      slither/printers/summary/require_calls.py
  44. 26
      slither/slithir/convert.py
  45. 6
      slither/slithir/operations/binary.py
  46. 6
      slither/slithir/operations/library_call.py
  47. 9
      slither/slithir/tmp_operations/tmp_call.py
  48. 18
      slither/slithir/utils/ssa.py
  49. 9
      slither/slithir/utils/utils.py
  50. 3
      slither/solc_parsing/declarations/contract.py
  51. 8
      slither/solc_parsing/declarations/function.py
  52. 5
      slither/solc_parsing/declarations/structure_contract.py
  53. 5
      slither/solc_parsing/declarations/structure_top_level.py
  54. 15
      slither/solc_parsing/expressions/expression_parsing.py
  55. 12
      slither/solc_parsing/slitherSolc.py
  56. 3
      slither/solc_parsing/solidity_types/type_parsing.py
  57. 11
      slither/solc_parsing/yul/parse_yul.py
  58. 3
      slither/tools/erc_conformance/__main__.py
  59. 13
      slither/tools/flattening/__main__.py
  60. 0
      slither/tools/flattening/export/__init__.py
  61. 4
      slither/tools/flattening/export/export.py
  62. 15
      slither/tools/flattening/flattening.py
  63. 11
      slither/tools/kspec_coverage/__main__.py
  64. 3
      slither/tools/possible_paths/__main__.py
  65. 3
      slither/tools/possible_paths/possible_paths.py
  66. 4
      slither/tools/properties/__main__.py
  67. 6
      slither/tools/properties/properties/erc20.py
  68. 11
      slither/tools/properties/properties/ercs/erc20/unit_tests/truffle.py
  69. 5
      slither/tools/similarity/__main__.py
  70. 11
      slither/tools/slither_format/__main__.py
  71. 5
      slither/tools/slither_format/slither_format.py
  72. 5
      slither/tools/upgradeability/__main__.py
  73. 43
      slither/utils/erc.py
  74. 12
      slither/utils/expression_manipulations.py
  75. 4
      slither/utils/inheritance_analysis.py
  76. 44
      slither/utils/output.py
  77. 12
      slither/visitors/slithir/expression_to_slithir.py
  78. 0
      tests/__init__.py
  79. 61
      tests/detectors/reentrancy-benign/reentrancy-benign.sol
  80. 4580
      tests/detectors/reentrancy-benign/reentrancy-benign.sol.0.4.26.ReentrancyBenign.json
  81. 10
      tests/test_ast_parsing.py
  82. 5
      tests/test_detectors.py
  83. 261
      tests/test_function.py
  84. 129
      tests/test_function.sol
  85. 45
      trophies.md

@ -0,0 +1,48 @@
---
name: Lint Code Base
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
jobs:
build:
name: Lint Code Base
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Black
uses: docker://github/super-linter:v3
if: always()
env:
# run linter on everything to catch preexisting problems
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run only black
VALIDATE_PYTHON_BLACK: true
PYTHON_BLACK_CONFIG_FILE: pyproject.toml

@ -45,11 +45,9 @@ jobs:
# Used by ci_test.sh # Used by ci_test.sh
pip install deepdiff pip install deepdiff
git clone https://github.com/crytic/solc-select.git pip install solc-select
./solc-select/scripts/install.sh solc-select install all
export PATH=/home/runner/.solc-select:$PATH solc-select use 0.5.1
echo "export PATH=/home/runner/.solc-select:$PATH" >> ~/.bashrc
solc use 0.5.1
- name: Run Tests - name: Run Tests
env: env:

@ -33,11 +33,9 @@ jobs:
pip install deepdiff pip install deepdiff
pip install pytest pip install pytest
git clone https://github.com/crytic/solc-select.git pip install solc-select
./solc-select/scripts/install.sh solc-select install all
export PATH=/home/runner/.solc-select:$PATH solc-select use 0.7.3
echo "export PATH=/home/runner/.solc-select:$PATH" >> ~/.bashrc
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_detectors.py pytest tests/test_detectors.py

@ -35,31 +35,6 @@ jobs:
mkdir -p .github/linters mkdir -p .github/linters
cp pyproject.toml .github/linters cp pyproject.toml .github/linters
- name: Pylint
uses: docker://github/super-linter:v3
if: always()
env:
# run linter on everything to catch preexisting problems
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run only pylint
VALIDATE_PYTHON: true
VALIDATE_PYTHON_PYLINT: true
PYTHON_PYLINT_CONFIG_FILE: pyproject.toml
- name: Black
uses: docker://github/super-linter:v3
if: always()
env:
# run linter on everything to catch preexisting problems
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run only black
VALIDATE_PYTHON_BLACK: true
PYTHON_BLACK_CONFIG_FILE: pyproject.toml
- name: Lint everything else - name: Lint everything else
uses: docker://github/super-linter:v3 uses: docker://github/super-linter:v3
if: always() if: always()
@ -80,4 +55,5 @@ jobs:
VALIDATE_DOCKERFILE: false VALIDATE_DOCKERFILE: false
VALIDATE_DOCKERFILE_HADOLINT: false VALIDATE_DOCKERFILE_HADOLINT: false
VALIDATE_EDITORCONFIG: false VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false
SHELLCHECK_OPTS: "-e SC1090" SHELLCHECK_OPTS: "-e SC1090"

@ -34,9 +34,13 @@ jobs:
pip install pytest pip install pytest
git clone https://github.com/crytic/solc-select.git git clone https://github.com/crytic/solc-select.git
cd solc-select
git checkout 857d6fa883d9283454be1cb2d869a8f9962b27b8
cd ..
./solc-select/scripts/install.sh ./solc-select/scripts/install.sh
export PATH=/home/runner/.solc-select:$PATH export PATH=/home/runner/.solc-select:$PATH
echo "export PATH=/home/runner/.solc-select:$PATH" >> ~/.bashrc echo "export PATH=/home/runner/.solc-select:$PATH" >> ~/.bashrc
solc use 0.7.3
- name: Test with pytest - name: Test with pytest
run: | run: |

@ -0,0 +1,49 @@
---
name: Lint Code Base
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
jobs:
build:
name: Lint Code Base
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Pylint
uses: docker://github/super-linter:v3
if: always()
env:
# run linter on everything to catch preexisting problems
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run only pylint
VALIDATE_PYTHON: true
VALIDATE_PYTHON_PYLINT: true
PYTHON_PYLINT_CONFIG_FILE: pyproject.toml

@ -32,12 +32,12 @@ To run the unit tests, you need
Several linters and security checkers are run on the PRs. Several linters and security checkers are run on the PRs.
To run them locally: To run them locally in the root dir of the repository:
- `pylint slither --rcfile pyproject.toml` - `pylint slither tests --rcfile pyproject.toml`
- `black slither --config pyproject.toml` - `black . --config pyproject.toml`
We use black `19.10b0`. We use pylint `2.8.2` black `20.8b1`.
### Detectors tests ### Detectors tests
For each new detector, at least one regression tests must be present. For each new detector, at least one regression tests must be present.

@ -17,7 +17,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
## Features ## Features
* Detects vulnerable Solidity code with low false positives * Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
* Identifies where the error condition occurs in the source code * Identifies where the error condition occurs in the source code
* Easily integrates into continuous integration and Truffle builds * Easily integrates into continuous integration and Truffle builds
* Built-in 'printers' quickly report crucial contract information * Built-in 'printers' quickly report crucial contract information
@ -30,7 +30,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
## Bugs and Optimizations Detection ## Bugs and Optimizations Detection
Run Slither on a Truffle/Embark/Dapp/Etherlime application: Run Slither on a Truffle/Embark/Dapp/Etherlime/Hardhat application:
```bash ```bash
slither . slither .
``` ```
@ -217,5 +217,6 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
- [ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf), Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma - SANER 20 - [ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf), Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma - SANER 20
- [Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf), Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan - IJMLC 20 - [Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf), Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan - IJMLC 20
- [Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf), Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury - TOSEM 20 - [Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf), Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury - TOSEM 20
- [Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf), Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig - ASE 20
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/). If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

@ -9,5 +9,7 @@ setup(
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6", python_requires=">=3.6",
install_requires=["slither-analyzer==0.1"], install_requires=["slither-analyzer==0.1"],
entry_points={"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin",}, entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin",
},
) )

@ -2,7 +2,7 @@
### Test ### Test
solc use 0.7.0 solc-select use 0.7.0
if ! slither "tests/config/test.sol" --solc-ast --ignore-return-value; then if ! slither "tests/config/test.sol" --solc-ast --ignore-return-value; then
echo "--solc-ast failed" echo "--solc-ast failed"

@ -4,7 +4,7 @@
DIR_TESTS="tests/check-erc" DIR_TESTS="tests/check-erc"
solc use 0.5.0 solc-select use 0.5.0
slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 > test_1.txt 2>&1 slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 > test_1.txt 2>&1
DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")
if [ "$DIFF" != "" ] if [ "$DIFF" != "" ]

@ -4,7 +4,7 @@
DIR_TESTS="tests/possible_paths" DIR_TESTS="tests/possible_paths"
solc use "0.5.0" solc-select use "0.5.0"
slither-find-paths "$DIR_TESTS/paths.sol" A.destination > test_possible_paths.txt 2>&1 slither-find-paths "$DIR_TESTS/paths.sol" A.destination > test_possible_paths.txt 2>&1
DIFF=$(diff test_possible_paths.txt "$DIR_TESTS/paths.txt") DIFF=$(diff test_possible_paths.txt "$DIR_TESTS/paths.txt")
if [ "$DIFF" != "" ] if [ "$DIFF" != "" ]

@ -2,7 +2,7 @@
DIR_TESTS="tests/check-kspec" DIR_TESTS="tests/check-kspec"
solc use "0.5.0" solc-select use "0.5.0"
slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md" > test_1.txt 2>&1 slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md" > test_1.txt 2>&1
DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")
if [ "$DIFF" != "" ] if [ "$DIFF" != "" ]

@ -10,6 +10,6 @@ if ! slither "tests/*.json" --print all --json -; then
exit 1 exit 1
fi fi
solc use "0.5.1" solc-select use "0.5.1"
slither examples/scripts/test_evm_api.sol --print evm slither examples/scripts/test_evm_api.sol --print evm

@ -7,7 +7,7 @@ pip3.6 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip
### Test slither-simil ### Test slither-simil
solc use "0.4.25" solc-select use "0.4.25"
DIR_TESTS="tests/simil" DIR_TESTS="tests/simil"
slither-simil info "" --filename $DIR_TESTS/../complex_func.sol --fname Complex.complexExternalWrites > test_1.txt 2>&1 slither-simil info "" --filename $DIR_TESTS/../complex_func.sol --fname Complex.complexExternalWrites > test_1.txt 2>&1

@ -3,7 +3,7 @@
### Test slither-check-upgradeability ### Test slither-check-upgradeability
DIR_TESTS="tests/check-upgradeability" DIR_TESTS="tests/check-upgradeability"
solc use "0.5.0" solc-select use "0.5.0"
slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_1.txt 2>&1 slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_1.txt 2>&1
DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")

@ -412,7 +412,10 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
) )
group_misc.add_argument( group_misc.add_argument(
"--markdown-root", help="URL for markdown generation", action="store", default="", "--markdown-root",
help="URL for markdown generation",
action="store",
default="",
) )
group_misc.add_argument( group_misc.add_argument(
@ -447,7 +450,10 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
) )
group_misc.add_argument( group_misc.add_argument(
"--solc-ast", help="Provide the contract as a json AST", action="store_true", default=False, "--solc-ast",
help="Provide the contract as a json AST",
action="store_true",
default=False,
) )
group_misc.add_argument( group_misc.add_argument(
@ -500,7 +506,10 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
) )
parser.add_argument( parser.add_argument(
"--perf", help=argparse.SUPPRESS, action="store_true", default=False, "--perf",
help=argparse.SUPPRESS,
action="store_true",
default=False,
) )
# if the json is splitted in different files # if the json is splitted in different files

@ -136,7 +136,9 @@ def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_tai
def get_dependencies( def get_dependencies(
variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False, variable: Variable,
context: Union[Contract, Function],
only_unprotected: bool = False,
) -> Set[Variable]: ) -> Set[Variable]:
""" """
Return the variables for which `variable` depends on. Return the variables for which `variable` depends on.
@ -171,7 +173,9 @@ def get_all_dependencies(
def get_dependencies_ssa( def get_dependencies_ssa(
variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False, variable: Variable,
context: Union[Contract, Function],
only_unprotected: bool = False,
) -> Set[Variable]: ) -> Set[Variable]:
""" """
Return the variables for which `variable` depends on (SSA version). Return the variables for which `variable` depends on (SSA version).
@ -382,7 +386,16 @@ def convert_variable_to_non_ssa(v):
return v.non_ssa_version return v.non_ssa_version
assert isinstance( assert isinstance(
v, v,
(Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type,), (
Constant,
SolidityVariable,
Contract,
Enum,
SolidityFunction,
Structure,
Function,
Type,
),
) )
return v return v

@ -35,7 +35,10 @@ class State: # pylint: disable=too-few-public-methods
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def _visit( def _visit(
node: Node, state: State, variables_written: Set[Variable], variables_to_write: List[Variable], node: Node,
state: State,
variables_written: Set[Variable],
variables_to_write: List[Variable],
): ):
""" """
Explore all the nodes to look for values not written when the node's function return Explore all the nodes to look for values not written when the node's function return

@ -800,11 +800,15 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
################################################################################### ###################################################################################
@property @property
def phi_origins_local_variables(self,) -> Dict[str, Tuple[LocalVariable, Set["Node"]]]: def phi_origins_local_variables(
self,
) -> Dict[str, Tuple[LocalVariable, Set["Node"]]]:
return self._phi_origins_local_variables return self._phi_origins_local_variables
@property @property
def phi_origins_state_variables(self,) -> Dict[str, Tuple[StateVariable, Set["Node"]]]: def phi_origins_state_variables(
self,
) -> Dict[str, Tuple[StateVariable, Set["Node"]]]:
return self._phi_origins_state_variables return self._phi_origins_state_variables
# @property # @property

@ -43,7 +43,6 @@ if TYPE_CHECKING:
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
from slither.slithir.operations import Operation from slither.slithir.operations import Operation
from slither.slither import Slither from slither.slither import Slither
from slither.core.cfg.node import NodeType
from slither.core.slither_core import SlitherCore from slither.core.slither_core import SlitherCore
LOGGER = logging.getLogger("Function") LOGGER = logging.getLogger("Function")
@ -52,7 +51,10 @@ ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
class ModifierStatements: class ModifierStatements:
def __init__( def __init__(
self, modifier: Union["Contract", "Function"], entry_point: "Node", nodes: List["Node"], self,
modifier: Union["Contract", "Function"],
entry_point: "Node",
nodes: List["Node"],
): ):
self._modifier = modifier self._modifier = modifier
self._entry_point = entry_point self._entry_point = entry_point
@ -299,17 +301,18 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
def can_send_eth(self) -> bool: def can_send_eth(self) -> bool:
""" """
Check if the function can send eth Check if the function or any internal (not external) functions called by it can send eth
:return bool: :return bool:
""" """
from slither.slithir.operations import Call from slither.slithir.operations import Call
if self._can_send_eth is None: if self._can_send_eth is None:
self._can_send_eth = False
for ir in self.all_slithir_operations(): for ir in self.all_slithir_operations():
if isinstance(ir, Call) and ir.can_send_eth(): if isinstance(ir, Call) and ir.can_send_eth():
self._can_send_eth = True self._can_send_eth = True
return True return True
return self._can_reenter return self._can_send_eth
@property @property
def slither(self) -> "SlitherCore": def slither(self) -> "SlitherCore":
@ -1147,7 +1150,9 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
@staticmethod @staticmethod
def _explore_func_conditional( def _explore_func_conditional(
func: "Function", f: Callable[["Node"], List[SolidityVariable]], include_loop: bool, func: "Function",
f: Callable[["Node"], List[SolidityVariable]],
include_loop: bool,
): ):
ret = [f(n) for n in func.nodes if n.is_conditional(include_loop)] ret = [f(n) for n in func.nodes if n.is_conditional(include_loop)]
return [item for sublist in ret for item in sublist] return [item for sublist in ret for item in sublist]
@ -1594,10 +1599,14 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
return ret return ret
def get_last_ssa_state_variables_instances(self,) -> Dict[str, Set["SlithIRVariable"]]: def get_last_ssa_state_variables_instances(
self,
) -> Dict[str, Set["SlithIRVariable"]]:
return self._get_last_ssa_variable_instances(target_state=True, target_local=False) return self._get_last_ssa_variable_instances(target_state=True, target_local=False)
def get_last_ssa_local_variables_instances(self,) -> Dict[str, Set["SlithIRVariable"]]: def get_last_ssa_local_variables_instances(
self,
) -> Dict[str, Set["SlithIRVariable"]]:
return self._get_last_ssa_variable_instances(target_state=False, target_local=True) return self._get_last_ssa_variable_instances(target_state=False, target_local=True)
@staticmethod @staticmethod

@ -6,7 +6,9 @@ from slither.core.variables.function_type_variable import FunctionTypeVariable
class FunctionType(Type): class FunctionType(Type):
def __init__( def __init__(
self, params: List[FunctionTypeVariable], return_values: List[FunctionTypeVariable], self,
params: List[FunctionTypeVariable],
return_values: List[FunctionTypeVariable],
): ):
assert all(isinstance(x, FunctionTypeVariable) for x in params) assert all(isinstance(x, FunctionTypeVariable) for x in params)
assert all(isinstance(x, FunctionTypeVariable) for x in return_values) assert all(isinstance(x, FunctionTypeVariable) for x in return_values)

@ -59,12 +59,13 @@ class UserDefinedType(Type):
raise SlitherException(to_log) raise SlitherException(to_log)
def __str__(self): def __str__(self):
from slither.core.declarations.structure import Structure from slither.core.declarations.structure_contract import StructureContract
from slither.core.declarations.enum import Enum from slither.core.declarations.enum_contract import EnumContract
if isinstance(self.type, (Enum, Structure)): type_used = self.type
return str(self.type.contract) + "." + str(self.type.name) if isinstance(type_used, (EnumContract, StructureContract)):
return str(self.type.name) return str(type_used.contract) + "." + str(type_used.name)
return str(type_used.name)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, UserDefinedType): if not isinstance(other, UserDefinedType):

@ -53,7 +53,8 @@ Every Ether sent to `Locked` will be lost."""
for node in function.nodes: for node in function.nodes:
for ir in node.irs: for ir in node.irs:
if isinstance( if isinstance(
ir, (Send, Transfer, HighLevelCall, LowLevelCall, NewContract), ir,
(Send, Transfer, HighLevelCall, LowLevelCall, NewContract),
): ):
if ir.call_value and ir.call_value != 0: if ir.call_value and ir.call_value != 0:
return False return False

@ -41,7 +41,9 @@ def arbitrary_send(func):
if ir.variable_right == SolidityVariableComposed("msg.sender"): if ir.variable_right == SolidityVariableComposed("msg.sender"):
return False return False
if is_dependent( if is_dependent(
ir.variable_right, SolidityVariableComposed("msg.sender"), func.contract, ir.variable_right,
SolidityVariableComposed("msg.sender"),
func.contract,
): ):
return False return False
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
@ -54,7 +56,9 @@ def arbitrary_send(func):
if ir.call_value == SolidityVariableComposed("msg.value"): if ir.call_value == SolidityVariableComposed("msg.value"):
continue continue
if is_dependent( if is_dependent(
ir.call_value, SolidityVariableComposed("msg.value"), func.contract, ir.call_value,
SolidityVariableComposed("msg.value"),
func.contract,
): ):
continue continue

@ -86,10 +86,14 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if func.is_constructor: if func.is_constructor:
continue continue
if not self.is_mixed_case(func.name): if not self.is_mixed_case(func.name):
if func.visibility in [ if (
"internal", func.visibility
"private", in [
] and self.is_mixed_case_with_underscore(func.name): "internal",
"private",
]
and self.is_mixed_case_with_underscore(func.name)
):
continue continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"): if func.name.startswith("echidna_") or func.name.startswith("crytic_"):
continue continue
@ -125,7 +129,11 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
res = self.generate_result(info) res = self.generate_result(info)
res.add( res.add(
var, {"target": "variable", "convention": "l_O_I_should_not_be_used",}, var,
{
"target": "variable",
"convention": "l_O_I_should_not_be_used",
},
) )
results.append(res) results.append(res)

@ -36,7 +36,9 @@ def _timestamp(func: Function) -> List[Node]:
return sorted(list(ret), key=lambda x: x.node_id) return sorted(list(ret), key=lambda x: x.node_id)
def _detect_dangerous_timestamp(contract: Contract,) -> List[Tuple[Function, List[Node]]]: def _detect_dangerous_timestamp(
contract: Contract,
) -> List[Tuple[Function, List[Node]]]:
""" """
Args: Args:
contract (Contract) contract (Contract)

@ -127,7 +127,8 @@ class AbstractState:
) )
self._reads = union_dict(self._reads, father.context[detector.KEY].reads) self._reads = union_dict(self._reads, father.context[detector.KEY].reads)
self._reads_prior_calls = union_dict( self._reads_prior_calls = union_dict(
self.reads_prior_calls, father.context[detector.KEY].reads_prior_calls, self.reads_prior_calls,
father.context[detector.KEY].reads_prior_calls,
) )
def analyze_node(self, node, detector): def analyze_node(self, node, detector):

@ -63,7 +63,11 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
if v in node.context[self.KEY].reads_prior_calls[c] if v in node.context[self.KEY].reads_prior_calls[c]
] ]
not_read_then_written = { not_read_then_written = {
FindingValue(v, node, tuple(sorted(nodes, key=lambda x: x.node_id)),) FindingValue(
v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)),
)
for (v, nodes) in node.context[self.KEY].written.items() for (v, nodes) in node.context[self.KEY].written.items()
if v not in read_then_written if v not in read_then_written
} }
@ -126,7 +130,8 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# #
@ -138,7 +143,8 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# Add all variables written via nodes which write them. # Add all variables written via nodes which write them.

@ -63,7 +63,9 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
continue continue
read_then_written |= { read_then_written |= {
FindingValue( FindingValue(
v, node, tuple(sorted(nodes, key=lambda x: x.node_id)), v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)),
) )
for (v, nodes) in node.context[self.KEY].written.items() for (v, nodes) in node.context[self.KEY].written.items()
if v in node.context[self.KEY].reads_prior_calls[c] if v in node.context[self.KEY].reads_prior_calls[c]
@ -128,7 +130,8 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# If the calls are not the same ones that send eth, add the eth sending nodes. # If the calls are not the same ones that send eth, add the eth sending nodes.
@ -138,7 +141,8 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# Add all variables written via nodes which write them. # Add all variables written via nodes which write them.

@ -61,7 +61,11 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w
send_eth=to_hashable(node.context[self.KEY].send_eth), send_eth=to_hashable(node.context[self.KEY].send_eth),
) )
finding_vars = { finding_vars = {
FindingValue(e, e.node, tuple(sorted(nodes, key=lambda x: x.node_id)),) FindingValue(
e,
e.node,
tuple(sorted(nodes, key=lambda x: x.node_id)),
)
for (e, nodes) in node.context[self.KEY].events.items() for (e, nodes) in node.context[self.KEY].events.items()
} }
if finding_vars: if finding_vars:
@ -115,7 +119,8 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# #
@ -127,7 +132,8 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
for finding_value in events: for finding_value in events:

@ -72,11 +72,19 @@ Only report reentrancy that is based on `transfer` or `send`."""
send_eth=to_hashable(node.context[self.KEY].send_eth), send_eth=to_hashable(node.context[self.KEY].send_eth),
) )
finding_vars = { finding_vars = {
FindingValue(v, node, tuple(sorted(nodes, key=lambda x: x.node_id)),) FindingValue(
v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)),
)
for (v, nodes) in node.context[self.KEY].written.items() for (v, nodes) in node.context[self.KEY].written.items()
} }
finding_vars |= { finding_vars |= {
FindingValue(e, e.node, tuple(sorted(nodes, key=lambda x: x.node_id)),) FindingValue(
e,
e.node,
tuple(sorted(nodes, key=lambda x: x.node_id)),
)
for (e, nodes) in node.context[self.KEY].events.items() for (e, nodes) in node.context[self.KEY].events.items()
} }
if finding_vars: if finding_vars:
@ -151,7 +159,8 @@ Only report reentrancy that is based on `transfer` or `send`."""
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# #
@ -163,7 +172,8 @@ Only report reentrancy that is based on `transfer` or `send`."""
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# Add all variables written via nodes which write them. # Add all variables written via nodes which write them.

@ -58,7 +58,9 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`)."""
continue continue
read_then_written |= { read_then_written |= {
FindingValue( FindingValue(
v, node, tuple(sorted(nodes, key=lambda x: x.node_id)), v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)),
) )
for (v, nodes) in node.context[self.KEY].written.items() for (v, nodes) in node.context[self.KEY].written.items()
if v in node.context[self.KEY].reads_prior_calls[c] if v in node.context[self.KEY].reads_prior_calls[c]
@ -114,7 +116,8 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`)."""
for call_list_info in calls_list: for call_list_info in calls_list:
if call_list_info != call_info: if call_list_info != call_info:
res.add( res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}, call_list_info,
{"underlying_type": "external_calls_sending_eth"},
) )
# Add all variables written via nodes which write them. # Add all variables written via nodes which write them.

@ -60,13 +60,13 @@ contract Crowdsale{
return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL
@staticmethod @staticmethod
def is_any_tainted(variables, taints, function): def is_any_tainted(variables, taints, function) -> bool:
return any( return any(
[ (
is_dependent_ssa(var, taint, function.contract) is_dependent_ssa(var, taint, function.contract)
for var in variables for var in variables
for taint in taints for taint in taints
] )
) )
def taint_balance_equalities(self, functions): def taint_balance_equalities(self, functions):

@ -79,7 +79,9 @@ class UnprotectedUpgradeable(AbstractDetector):
" is an upgradeable contract that does not protect its initiliaze functions: ", " is an upgradeable contract that does not protect its initiliaze functions: ",
] ]
+ initiliaze_functions + initiliaze_functions
+ [". Anyone can delete the contract with: ",] + [
". Anyone can delete the contract with: ",
]
+ functions_that_can_destroy + functions_that_can_destroy
) )

@ -70,5 +70,10 @@ def _patch(
in_file_str = slither.source_code[in_file].encode("utf8") in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end]
create_patch( create_patch(
result, in_file, int(modify_loc_start), int(modify_loc_end), old_str_of_interest, pragma, result,
in_file,
int(modify_loc_start),
int(modify_loc_end),
old_str_of_interest,
pragma,
) )

@ -575,7 +575,12 @@ def _explore_irs(slither, irs, result, target, convert):
loc_end = loc_start + len(old_str) loc_end = loc_start + len(old_str)
create_patch( create_patch(
result, filename_source_code, loc_start, loc_end, old_str, new_str, result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str,
) )

@ -33,21 +33,39 @@ def _edge(from_node, to_node):
# return dot language string to add graph node (with optional label) # return dot language string to add graph node (with optional label)
def _node(node, label=None): def _node(node, label=None):
return " ".join((f'"{node}"', f'[label="{label}"]' if label is not None else "",)) return " ".join(
(
f'"{node}"',
f'[label="{label}"]' if label is not None else "",
)
)
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def _process_internal_call( def _process_internal_call(
contract, function, internal_call, contract_calls, solidity_functions, solidity_calls, contract,
function,
internal_call,
contract_calls,
solidity_functions,
solidity_calls,
): ):
if isinstance(internal_call, (Function)): if isinstance(internal_call, (Function)):
contract_calls[contract].add( contract_calls[contract].add(
_edge(_function_node(contract, function), _function_node(contract, internal_call),) _edge(
_function_node(contract, function),
_function_node(contract, internal_call),
)
) )
elif isinstance(internal_call, (SolidityFunction)): elif isinstance(internal_call, (SolidityFunction)):
solidity_functions.add(_node(_solidity_function_node(internal_call)),) solidity_functions.add(
_node(_solidity_function_node(internal_call)),
)
solidity_calls.add( solidity_calls.add(
_edge(_function_node(contract, function), _solidity_function_node(internal_call),) _edge(
_function_node(contract, function),
_solidity_function_node(internal_call),
)
) )
@ -84,7 +102,12 @@ def _render_solidity_calls(solidity_functions, solidity_calls):
def _process_external_call( def _process_external_call(
contract, function, external_call, contract_functions, external_calls, all_contracts, contract,
function,
external_call,
contract_functions,
external_calls,
all_contracts,
): ):
external_contract, external_function = external_call external_contract, external_function = external_call
@ -94,7 +117,10 @@ def _process_external_call(
# add variable as node to respective contract # add variable as node to respective contract
if isinstance(external_function, (Variable)): if isinstance(external_function, (Variable)):
contract_functions[external_contract].add( contract_functions[external_contract].add(
_node(_function_node(external_contract, external_function), external_function.name,) _node(
_function_node(external_contract, external_function),
external_function.name,
)
) )
external_calls.add( external_calls.add(
@ -116,15 +142,27 @@ def _process_function(
external_calls, external_calls,
all_contracts, all_contracts,
): ):
contract_functions[contract].add(_node(_function_node(contract, function), function.name),) contract_functions[contract].add(
_node(_function_node(contract, function), function.name),
)
for internal_call in function.internal_calls: for internal_call in function.internal_calls:
_process_internal_call( _process_internal_call(
contract, function, internal_call, contract_calls, solidity_functions, solidity_calls, contract,
function,
internal_call,
contract_calls,
solidity_functions,
solidity_calls,
) )
for external_call in function.high_level_calls: for external_call in function.high_level_calls:
_process_external_call( _process_external_call(
contract, function, external_call, contract_functions, external_calls, all_contracts, contract,
function,
external_call,
contract_functions,
external_calls,
all_contracts,
) )

@ -52,7 +52,11 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
state_variables_written = [v.name for v in function.all_state_variables_written()] state_variables_written = [v.name for v in function.all_state_variables_written()]
msg_sender_condition = self.get_msg_sender_checks(function) msg_sender_condition = self.get_msg_sender_checks(function)
table.add_row( table.add_row(
[function.name, str(state_variables_written), str(msg_sender_condition),] [
function.name,
str(state_variables_written),
str(msg_sender_condition),
]
) )
all_tables.append((contract.name, table)) all_tables.append((contract.name, table))
txt += str(table) + "\n" txt += str(table) + "\n"

@ -101,7 +101,8 @@ class PrinterEVM(AbstractPrinter):
) )
txt += green( txt += green(
"\t\tSource line {}: {}\n".format( "\t\tSource line {}: {}\n".format(
node_source_line, contract_file_lines[node_source_line - 1].rstrip(), node_source_line,
contract_file_lines[node_source_line - 1].rstrip(),
) )
) )
txt += magenta("\t\tEVM Instructions:\n") txt += magenta("\t\tEVM Instructions:\n")
@ -123,7 +124,8 @@ class PrinterEVM(AbstractPrinter):
) )
txt += green( txt += green(
"\t\tSource line {}: {}\n".format( "\t\tSource line {}: {}\n".format(
node_source_line, contract_file_lines[node_source_line - 1].rstrip(), node_source_line,
contract_file_lines[node_source_line - 1].rstrip(),
) )
) )
txt += magenta("\t\tEVM Instructions:\n") txt += magenta("\t\tEVM Instructions:\n")

@ -65,11 +65,26 @@ class FunctionSummary(AbstractPrinter):
internal_calls = self._convert(internal_calls) internal_calls = self._convert(internal_calls)
external_calls = self._convert(external_calls) external_calls = self._convert(external_calls)
table.add_row( table.add_row(
[f_name, visi, modifiers, read, write, internal_calls, external_calls,] [
f_name,
visi,
modifiers,
read,
write,
internal_calls,
external_calls,
]
) )
txt += "\n \n" + str(table) txt += "\n \n" + str(table)
table = MyPrettyTable( table = MyPrettyTable(
["Modifiers", "Visibility", "Read", "Write", "Internal Calls", "External Calls",] [
"Modifiers",
"Visibility",
"Read",
"Write",
"Internal Calls",
"External Calls",
]
) )
for ( for (
_c_name, _c_name,

@ -379,7 +379,14 @@ class PrinterHumanSummary(AbstractPrinter):
) )
table.add_row( table.add_row(
[contract.name, number_functions, ercs, erc20_info, is_complex, features,] [
contract.name,
number_functions,
ercs,
erc20_info,
is_complex,
features,
]
) )
self.info(txt + "\n" + str(table)) self.info(txt + "\n" + str(table))

@ -46,7 +46,10 @@ class RequireOrAssert(AbstractPrinter):
] ]
require = [ir.node for ir in require] require = [ir.node for ir in require]
table.add_row( table.add_row(
[function.name, self._convert([str(m.expression) for m in set(require)]),] [
function.name,
self._convert([str(m.expression) for m in set(require)]),
]
) )
txt += "\n" + str(table) txt += "\n" + str(table)
self.info(txt) self.info(txt)

@ -210,7 +210,8 @@ def convert_arguments(arguments):
def is_temporary(ins): def is_temporary(ins):
return isinstance( return isinstance(
ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure), ins,
(Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure),
) )
@ -624,7 +625,8 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
# We dont need to check for function collision, as solc prevents the use of selector # We dont need to check for function collision, as solc prevents the use of selector
# if there are multiple functions with the same name # if there are multiple functions with the same name
f = next( f = next(
(f for f in type_t.functions if f.name == ir.variable_right), None, (f for f in type_t.functions if f.name == ir.variable_right),
None,
) )
if f: if f:
ir.lvalue.set_type(f) ir.lvalue.set_type(f)
@ -835,7 +837,10 @@ def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
ins.called = SolidityFunction("blockhash(uint256)") ins.called = SolidityFunction("blockhash(uint256)")
elif str(ins.called) == "this.balance": elif str(ins.called) == "this.balance":
s = SolidityCall( s = SolidityCall(
SolidityFunction("this.balance()"), ins.nbr_arguments, ins.lvalue, ins.type_call, SolidityFunction("this.balance()"),
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
) )
s.set_expression(ins.expression) s.set_expression(ins.expression)
return s return s
@ -1153,7 +1158,11 @@ def look_for_library(contract, ir, using_for, t):
lib_contract = contract.slither.get_contract_from_name(str(destination)) lib_contract = contract.slither.get_contract_from_name(str(destination))
if lib_contract: if lib_contract:
lib_call = LibraryCall( lib_call = LibraryCall(
lib_contract, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call, lib_contract,
ir.function_name,
ir.nbr_arguments,
ir.lvalue,
ir.type_call,
) )
lib_call.set_expression(ir.expression) lib_call.set_expression(ir.expression)
lib_call.set_node(ir.node) lib_call.set_node(ir.node)
@ -1386,7 +1395,14 @@ def remove_temporary(result):
ins ins
for ins in result for ins in result
if not isinstance( if not isinstance(
ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure,), ins,
(
Argument,
TmpNewElementaryType,
TmpNewContract,
TmpNewArray,
TmpNewStructure,
),
) )
] ]

@ -175,7 +175,11 @@ class Binary(OperationWithLValue):
while isinstance(points, ReferenceVariable): while isinstance(points, ReferenceVariable):
points = points.points_to points = points.points_to
return "{}(-> {}) = {} {} {}".format( return "{}(-> {}) = {} {} {}".format(
str(self.lvalue), points, self.variable_left, self.type_str, self.variable_right, str(self.lvalue),
points,
self.variable_left,
self.type_str,
self.variable_right,
) )
return "{}({}) = {} {} {}".format( return "{}({}) = {} {} {}".format(
str(self.lvalue), str(self.lvalue),

@ -38,5 +38,9 @@ class LibraryCall(HighLevelCall):
lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type)
txt = "{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}" txt = "{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}"
return txt.format( return txt.format(
lvalue, self.destination, self.function_name, [str(x) for x in arguments], gas, lvalue,
self.destination,
self.function_name,
[str(x) for x in arguments],
gas,
) )

@ -13,7 +13,14 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
def __init__(self, called, nbr_arguments, result, type_call): def __init__(self, called, nbr_arguments, result, type_call):
assert isinstance( assert isinstance(
called, called,
(Contract, Variable, SolidityVariableComposed, SolidityFunction, Structure, Event,), (
Contract,
Variable,
SolidityVariableComposed,
SolidityFunction,
Structure,
Event,
),
) )
super().__init__() super().__init__()
self._called = called self._called = called

@ -173,7 +173,9 @@ def add_ssa_ir(function, all_state_variables_instances):
init_state_variables_instances = dict(all_state_variables_instances) init_state_variables_instances = dict(all_state_variables_instances)
initiate_all_local_variables_instances( initiate_all_local_variables_instances(
function.nodes, init_local_variables_instances, all_init_local_variables_instances, function.nodes,
init_local_variables_instances,
all_init_local_variables_instances,
) )
generate_ssa_irs( generate_ssa_irs(
@ -506,7 +508,8 @@ def add_phi_origins(node, local_variables_definition, state_variables_definition
# We keep the instance as we want to avoid to add __hash__ on v.name in Variable # We keep the instance as we want to avoid to add __hash__ on v.name in Variable
# That might work for this used, but could create collision for other uses # That might work for this used, but could create collision for other uses
local_variables_definition = dict( local_variables_definition = dict(
local_variables_definition, **{v.name: (v, node) for v in node.local_variables_written}, local_variables_definition,
**{v.name: (v, node) for v in node.local_variables_written},
) )
state_variables_definition = dict( state_variables_definition = dict(
state_variables_definition, state_variables_definition,
@ -598,7 +601,16 @@ def get(
return tuple_variables_instances[variable.index] return tuple_variables_instances[variable.index]
assert isinstance( assert isinstance(
variable, variable,
(Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type,), (
Constant,
SolidityVariable,
Contract,
Enum,
SolidityFunction,
Structure,
Function,
Type,
),
) # type for abi.decode(.., t) ) # type for abi.decode(.., t)
return variable return variable

@ -25,5 +25,12 @@ def is_valid_rvalue(v):
def is_valid_lvalue(v): def is_valid_lvalue(v):
return isinstance( return isinstance(
v, (StateVariable, LocalVariable, TemporaryVariable, ReferenceVariable, TupleVariable,), v,
(
StateVariable,
LocalVariable,
TemporaryVariable,
ReferenceVariable,
TupleVariable,
),
) )

@ -411,7 +411,8 @@ class ContractSolc:
assert isinstance(underlying_function, FunctionContract) assert isinstance(underlying_function, FunctionContract)
elem.set_contract_declarer(underlying_function.contract_declarer) elem.set_contract_declarer(underlying_function.contract_declarer)
elem.set_offset( elem.set_offset(
element_parser.function_not_parsed["src"], self._contract.slither, element_parser.function_not_parsed["src"],
self._contract.slither,
) )
elem_parser = Cls_parser( elem_parser = Cls_parser(

@ -454,7 +454,10 @@ class FunctionSolc:
return key in attributes and not attributes[key] return key in attributes and not attributes[key]
if attributes and any( if attributes and any(
map(has_hint, ["condition", "initializationExpression", "loopExpression"],) map(
has_hint,
["condition", "initializationExpression", "loopExpression"],
)
): ):
# if we have attribute hints, rely on those # if we have attribute hints, rely on those
@ -602,7 +605,8 @@ class FunctionSolc:
link_underlying_nodes(node_startDoWhile, node_condition) link_underlying_nodes(node_startDoWhile, node_condition)
else: else:
link_nodes( link_nodes(
node_startDoWhile.underlying_node, node_condition.underlying_node.sons[0], node_startDoWhile.underlying_node,
node_condition.underlying_node.sons[0],
) )
link_underlying_nodes(statement, node_condition) link_underlying_nodes(statement, node_condition)
link_underlying_nodes(node_condition, node_endDoWhile) link_underlying_nodes(node_condition, node_endDoWhile)

@ -19,7 +19,10 @@ class StructureContractSolc: # pylint: disable=too-few-public-methods
# elems = [(type, name)] # elems = [(type, name)]
def __init__( # pylint: disable=too-many-arguments def __init__( # pylint: disable=too-many-arguments
self, st: Structure, struct: Dict, contract_parser: "ContractSolc", self,
st: Structure,
struct: Dict,
contract_parser: "ContractSolc",
): ):
if contract_parser.is_compact_ast: if contract_parser.is_compact_ast:

@ -19,7 +19,10 @@ class StructureTopLevelSolc: # pylint: disable=too-few-public-methods
# elems = [(type, name)] # elems = [(type, name)]
def __init__( # pylint: disable=too-many-arguments def __init__( # pylint: disable=too-many-arguments
self, st: Structure, struct: Dict, slither_parser: "SlitherSolc", self,
st: Structure,
struct: Dict,
slither_parser: "SlitherSolc",
): ):
if slither_parser.is_compact_ast: if slither_parser.is_compact_ast:

@ -269,7 +269,14 @@ def find_variable(
referenced_declaration: Optional[int] = None, referenced_declaration: Optional[int] = None,
is_super=False, is_super=False,
) -> Union[ ) -> Union[
Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure, Variable,
Function,
Contract,
SolidityVariable,
SolidityFunction,
Event,
Enum,
Structure,
]: ]:
from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.contract import ContractSolc
@ -312,9 +319,9 @@ def find_variable(
if ret: if ret:
return ret return ret
function_parser: Optional[FunctionSolc] = caller_context if isinstance( function_parser: Optional[FunctionSolc] = (
caller_context, FunctionSolc caller_context if isinstance(caller_context, FunctionSolc) else None
) else None )
ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration) ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret: if ret:
return ret return ret

@ -410,7 +410,9 @@ Please rename it, this name is reserved for Slither's internals"""
contracts_to_be_analyzed += [contract] contracts_to_be_analyzed += [contract]
def _analyze_first_part( def _analyze_first_part(
self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc], self,
contracts_to_be_analyzed: List[ContractSolc],
libraries: List[ContractSolc],
): ):
for lib in libraries: for lib in libraries:
self._parse_struct_var_modifiers_functions(lib) self._parse_struct_var_modifiers_functions(lib)
@ -435,7 +437,9 @@ Please rename it, this name is reserved for Slither's internals"""
contracts_to_be_analyzed += [contract] contracts_to_be_analyzed += [contract]
def _analyze_second_part( def _analyze_second_part(
self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc], self,
contracts_to_be_analyzed: List[ContractSolc],
libraries: List[ContractSolc],
): ):
for lib in libraries: for lib in libraries:
self._analyze_struct_events(lib) self._analyze_struct_events(lib)
@ -462,7 +466,9 @@ Please rename it, this name is reserved for Slither's internals"""
contracts_to_be_analyzed += [contract] contracts_to_be_analyzed += [contract]
def _analyze_third_part( def _analyze_third_part(
self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc], self,
contracts_to_be_analyzed: List[ContractSolc],
libraries: List[ContractSolc],
): ):
for lib in libraries: for lib in libraries:
self._analyze_variables_modifiers_functions(lib) self._analyze_variables_modifiers_functions(lib)

@ -155,7 +155,8 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
found = re.findall("mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name) found = re.findall("mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name)
else: else:
found = re.findall( found = re.findall(
"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)", name, "mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)",
name,
) )
assert len(found) == 1 assert len(found) == 1
from_ = found[0][0] from_ = found[0][0]

@ -165,7 +165,10 @@ class YulScope(metaclass=abc.ABCMeta):
self._yul_local_functions.append(func) self._yul_local_functions.append(func)
def get_yul_local_function_from_name(self, func_name): def get_yul_local_function_from_name(self, func_name):
return next((v for v in self._yul_local_functions if v.underlying.name == func_name), None,) return next(
(v for v in self._yul_local_functions if v.underlying.name == func_name),
None,
)
class YulLocalVariable: # pylint: disable=too-few-public-methods class YulLocalVariable: # pylint: disable=too-few-public-methods
@ -457,7 +460,11 @@ def convert_yul_switch(root: YulScope, parent: YulNode, ast: Dict) -> YulNode:
"name": "eq", "name": "eq",
}, },
"arguments": [ "arguments": [
{"nodeType": "YulIdentifier", "src": case_ast["src"], "name": switch_expr_var,}, {
"nodeType": "YulIdentifier",
"src": case_ast["src"],
"name": switch_expr_var,
},
value_ast, value_ast,
], ],
}, },

@ -31,7 +31,8 @@ def parse_args():
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Check the ERC 20 conformance", usage="slither-check-erc project contractName", description="Check the ERC 20 conformance",
usage="slither-check-erc project contractName",
) )
parser.add_argument("project", help="The codebase to be tested.") parser.add_argument("project", help="The codebase to be tested.")

@ -41,7 +41,9 @@ def parse_args():
group_export = parser.add_argument_group("Export options") group_export = parser.add_argument_group("Export options")
group_export.add_argument( group_export.add_argument(
"--dir", help=f"Export directory (default: {DEFAULT_EXPORT_PATH}).", default=None, "--dir",
help=f"Export directory (default: {DEFAULT_EXPORT_PATH}).",
default=None,
) )
group_export.add_argument( group_export.add_argument(
@ -52,7 +54,10 @@ def parse_args():
) )
parser.add_argument( parser.add_argument(
"--zip", help="Export all the files to a zip file", action="store", default=None, "--zip",
help="Export all the files to a zip file",
action="store",
default=None,
) )
parser.add_argument( parser.add_argument(
@ -69,7 +74,9 @@ def parse_args():
) )
group_patching.add_argument( group_patching.add_argument(
"--convert-private", help="Convert private variables to internal.", action="store_true", "--convert-private",
help="Convert private variables to internal.",
action="store_true",
) )
group_patching.add_argument( group_patching.add_argument(

@ -24,7 +24,9 @@ def save_to_zip(files: List[Export], zip_filename: str, zip_type: str = "lzma"):
""" """
logger.info(f"Export {zip_filename}") logger.info(f"Export {zip_filename}")
with zipfile.ZipFile( with zipfile.ZipFile(
zip_filename, "w", compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA), zip_filename,
"w",
compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA),
) as file_desc: ) as file_desc:
for f in files: for f in files:
file_desc.writestr(str(f.filename), f.content) file_desc.writestr(str(f.filename), f.content)

@ -108,7 +108,10 @@ class Flattening:
regex = re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes) regex = re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes)
if regex: if regex:
to_patch.append( to_patch.append(
Patch(attributes_start + regex.span()[0] + 1, "public_to_external",) Patch(
attributes_start + regex.span()[0] + 1,
"public_to_external",
)
) )
else: else:
raise SlitherException(f"External keyword not found {f.name} {attributes}") raise SlitherException(f"External keyword not found {f.name} {attributes}")
@ -119,7 +122,10 @@ class Flattening:
calldata_end = calldata_start + var.source_mapping["length"] calldata_end = calldata_start + var.source_mapping["length"]
calldata_idx = content[calldata_start:calldata_end].find(" calldata ") calldata_idx = content[calldata_start:calldata_end].find(" calldata ")
to_patch.append( to_patch.append(
Patch(calldata_start + calldata_idx + 1, "calldata_to_memory",) Patch(
calldata_start + calldata_idx + 1,
"calldata_to_memory",
)
) )
if self._private_to_internal: if self._private_to_internal:
@ -133,7 +139,10 @@ class Flattening:
regex = re.search(r" private ", attributes) regex = re.search(r" private ", attributes)
if regex: if regex:
to_patch.append( to_patch.append(
Patch(attributes_start + regex.span()[0] + 1, "private_to_internal",) Patch(
attributes_start + regex.span()[0] + 1,
"private_to_internal",
)
) )
else: else:
raise SlitherException( raise SlitherException(

@ -22,18 +22,23 @@ def parse_args():
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="slither-kspec-coverage", usage="slither-kspec-coverage contract.sol kspec.md", description="slither-kspec-coverage",
usage="slither-kspec-coverage contract.sol kspec.md",
) )
parser.add_argument( parser.add_argument(
"contract", help="The filename of the contract or truffle directory to analyze." "contract", help="The filename of the contract or truffle directory to analyze."
) )
parser.add_argument( parser.add_argument(
"kspec", help="The filename of the Klab spec markdown for the analyzed contract(s)", "kspec",
help="The filename of the Klab spec markdown for the analyzed contract(s)",
) )
parser.add_argument( parser.add_argument(
"--version", help="displays the current version", version="0.1.0", action="version", "--version",
help="displays the current version",
version="0.1.0",
action="version",
) )
parser.add_argument( parser.add_argument(
"--json", "--json",

@ -21,7 +21,8 @@ def parse_args():
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="PossiblePaths", usage="possible_paths.py filename [contract.function targets]", description="PossiblePaths",
usage="possible_paths.py filename [contract.function targets]",
) )
parser.add_argument( parser.add_argument(

@ -18,7 +18,8 @@ def resolve_function(slither, contract_name, function_name):
# Obtain the target function # Obtain the target function
target_function = next( target_function = next(
(function for function in contract.functions if function.name == function_name), None, (function for function in contract.functions if function.name == function_name),
None,
) )
# Verify we have resolved the function specified. # Verify we have resolved the function specified.

@ -105,7 +105,9 @@ def parse_args():
) )
parser.add_argument( parser.add_argument(
"--address-attacker", help=f"Attacker address. Default {ATTACKER_ADDRESS}", default=None, "--address-attacker",
help=f"Attacker address. Default {ATTACKER_ADDRESS}",
default=None,
) )
# Add default arguments from crytic-compile # Add default arguments from crytic-compile

@ -107,7 +107,11 @@ def generate_erc20(
# Generate the Test contract # Generate the Test contract
initialization_recommendation = _initialization_recommendation(type_property) initialization_recommendation = _initialization_recommendation(type_property)
contract_filename, contract_name = generate_test_contract( contract_filename, contract_name = generate_test_contract(
contract, type_property, output_dir, property_file, initialization_recommendation, contract,
type_property,
output_dir,
property_file,
initialization_recommendation,
) )
# Generate Echidna config file # Generate Echidna config file

@ -15,7 +15,10 @@ logger = logging.getLogger("Slither")
def generate_truffle_test( def generate_truffle_test(
contract: Contract, type_property: str, unit_tests: List[Property], addresses: Addresses, contract: Contract,
type_property: str,
unit_tests: List[Property],
addresses: Addresses,
) -> str: ) -> str:
test_contract = f"Test{contract.name}{type_property}" test_contract = f"Test{contract.name}{type_property}"
filename_init = f"Initialization{test_contract}.js" filename_init = f"Initialization{test_contract}.js"
@ -35,7 +38,11 @@ def generate_truffle_test(
) )
generate_unit_test( generate_unit_test(
test_contract, filename, unit_tests, output_dir, addresses, test_contract,
filename,
unit_tests,
output_dir,
addresses,
) )
log_info = "\n" log_info = "\n"

@ -54,7 +54,10 @@ def parse_args():
) )
parser.add_argument( parser.add_argument(
"--version", help="displays the current version", version="0.0", action="version", "--version",
help="displays the current version",
version="0.0",
action="version",
) )
cryticparser.init(parser) cryticparser.init(parser)

@ -41,10 +41,17 @@ def parse_args():
default=False, default=False,
) )
parser.add_argument( parser.add_argument(
"--verbose-json", "-j", help="verbose json output", action="store_true", default=False, "--verbose-json",
"-j",
help="verbose json output",
action="store_true",
default=False,
) )
parser.add_argument( parser.add_argument(
"--version", help="displays the current version", version="0.1.0", action="version", "--version",
help="displays the current version",
version="0.1.0",
action="version",
) )
parser.add_argument( parser.add_argument(

@ -65,7 +65,10 @@ def slither_format(slither, **kwargs): # pylint: disable=too-many-locals
logger.info(f"Issue: {one_line_description}") logger.info(f"Issue: {one_line_description}")
logger.info(f"Generated: ({export_result})") logger.info(f"Generated: ({export_result})")
for (_, diff,) in result["patches_diff"].items(): for (
_,
diff,
) in result["patches_diff"].items():
filename = f"fix_{counter}.patch" filename = f"fix_{counter}.patch"
path = Path(export_result, filename) path = Path(export_result, filename)
logger.info(f"\t- {filename}") logger.info(f"\t- {filename}")

@ -57,7 +57,10 @@ def parse_args():
) )
parser.add_argument( parser.add_argument(
"--markdown-root", help="URL for markdown generation", action="store", default="", "--markdown-root",
help="URL for markdown generation",
action="store",
default="",
) )
parser.add_argument( parser.add_argument(

@ -63,7 +63,14 @@ ERC223 = [
ERC("totalSupply", [], "uint256", True, True, []), ERC("totalSupply", [], "uint256", True, True, []),
ERC("balanceOf", ["address"], "uint256", True, True, []), ERC("balanceOf", ["address"], "uint256", True, True, []),
ERC("transfer", ["address", "uint256"], "bool", False, True, [ERC223_transfer_event]), ERC("transfer", ["address", "uint256"], "bool", False, True, [ERC223_transfer_event]),
ERC("transfer", ["address", "uint256", "bytes"], "bool", False, True, [ERC223_transfer_event],), ERC(
"transfer",
["address", "uint256", "bytes"],
"bool",
False,
True,
[ERC223_transfer_event],
),
ERC( ERC(
"transfer", "transfer",
["address", "uint256", "bytes", "string"], ["address", "uint256", "bytes", "string"],
@ -118,10 +125,22 @@ ERC721 = [
[ERC721_transfer_event], [ERC721_transfer_event],
), ),
ERC( ERC(
"transferFrom", ["address", "address", "uint256"], "", False, True, [ERC721_transfer_event], "transferFrom",
["address", "address", "uint256"],
"",
False,
True,
[ERC721_transfer_event],
), ),
ERC("approve", ["address", "uint256"], "", False, True, [ERC721_approval_event]), ERC("approve", ["address", "uint256"], "", False, True, [ERC721_approval_event]),
ERC("setApprovalForAll", ["address", "bool"], "", False, True, [ERC721_approvalforall_event],), ERC(
"setApprovalForAll",
["address", "bool"],
"",
False,
True,
[ERC721_approvalforall_event],
),
ERC("getApproved", ["uint256"], "address", True, True, []), ERC("getApproved", ["uint256"], "address", True, True, []),
ERC("isApprovedForAll", ["address", "address"], "bool", True, True, []), ERC("isApprovedForAll", ["address", "address"], "bool", True, True, []),
] + ERC165 ] + ERC165
@ -140,7 +159,14 @@ ERC721_signatures = erc_to_signatures(ERC721)
# https://eips.ethereum.org/EIPS/eip-1820 # https://eips.ethereum.org/EIPS/eip-1820
ERC1820_EVENTS: List = [] ERC1820_EVENTS: List = []
ERC1820 = [ ERC1820 = [
ERC("canImplementInterfaceForAddress", ["bytes32", "address"], "bytes32", True, True, [],) ERC(
"canImplementInterfaceForAddress",
["bytes32", "address"],
"bytes32",
True,
True,
[],
)
] ]
ERC1820_signatures = erc_to_signatures(ERC1820) ERC1820_signatures = erc_to_signatures(ERC1820)
@ -181,7 +207,14 @@ ERC777 = [
ERC("granularity", [], "uint256", True, True, []), ERC("granularity", [], "uint256", True, True, []),
ERC("defaultOperators", [], "address[]", True, True, []), ERC("defaultOperators", [], "address[]", True, True, []),
ERC("isOperatorFor", ["address", "address"], "bool", True, True, []), ERC("isOperatorFor", ["address", "address"], "bool", True, True, []),
ERC("authorizeOperator", ["address"], "", False, True, [ERC777_authorizedOperator_event],), ERC(
"authorizeOperator",
["address"],
"",
False,
True,
[ERC777_authorizedOperator_event],
),
ERC("revokeOperator", ["address"], "", False, True, [ERC777_revokedoperator_event]), ERC("revokeOperator", ["address"], "", False, True, [ERC777_revokedoperator_event]),
ERC("send", ["address", "uint256", "bytes"], "", False, True, [ERC777_sent_event]), ERC("send", ["address", "uint256", "bytes"], "", False, True, [ERC777_sent_event]),
ERC( ERC(

@ -110,21 +110,27 @@ class SplitTernaryExpression:
if self.apply_copy(next_expr, true_expression, false_expression, f_call): if self.apply_copy(next_expr, true_expression, false_expression, f_call):
# always on last arguments added # always on last arguments added
self.copy_expression( self.copy_expression(
next_expr, true_expression.arguments[-1], false_expression.arguments[-1], next_expr,
true_expression.arguments[-1],
false_expression.arguments[-1],
) )
elif isinstance(expression, TypeConversion): elif isinstance(expression, TypeConversion):
next_expr = expression.expression next_expr = expression.expression
if self.apply_copy(next_expr, true_expression, false_expression, f_expression): if self.apply_copy(next_expr, true_expression, false_expression, f_expression):
self.copy_expression( self.copy_expression(
expression.expression, true_expression.expression, false_expression.expression, expression.expression,
true_expression.expression,
false_expression.expression,
) )
elif isinstance(expression, UnaryOperation): elif isinstance(expression, UnaryOperation):
next_expr = expression.expression next_expr = expression.expression
if self.apply_copy(next_expr, true_expression, false_expression, f_expression): if self.apply_copy(next_expr, true_expression, false_expression, f_expression):
self.copy_expression( self.copy_expression(
expression.expression, true_expression.expression, false_expression.expression, expression.expression,
true_expression.expression,
false_expression.expression,
) )
else: else:

@ -10,7 +10,9 @@ if TYPE_CHECKING:
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
def detect_c3_function_shadowing(contract: "Contract",) -> Dict["Function", Set["Function"]]: def detect_c3_function_shadowing(
contract: "Contract",
) -> Dict["Function", Set["Function"]]:
""" """
Detects and obtains functions which are indirectly shadowed via multiple inheritance by C3 linearization Detects and obtains functions which are indirectly shadowed via multiple inheritance by C3 linearization
properties, despite not directly inheriting from each other. properties, despite not directly inheriting from each other.

@ -78,7 +78,9 @@ def output_to_zip(filename: str, error: Optional[str], results: Dict, zip_type:
logger.info(yellow(f"{filename} exists already, the overwrite is prevented")) logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
else: else:
with ZipFile( with ZipFile(
filename, "w", compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA), filename,
"w",
compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA),
) as file_desc: ) as file_desc:
file_desc.writestr("slither_results.json", json.dumps(json_result).encode("utf8")) file_desc.writestr("slither_results.json", json.dumps(json_result).encode("utf8"))
@ -355,7 +357,11 @@ class Output:
additional_fields = {} additional_fields = {}
type_specific_fields = {"parent": _create_parent_element(enum)} type_specific_fields = {"parent": _create_parent_element(enum)}
element = _create_base_element( element = _create_base_element(
"enum", enum.name, enum.source_mapping, type_specific_fields, additional_fields, "enum",
enum.name,
enum.source_mapping,
type_specific_fields,
additional_fields,
) )
self._data["elements"].append(element) self._data["elements"].append(element)
@ -371,7 +377,11 @@ class Output:
additional_fields = {} additional_fields = {}
type_specific_fields = {"parent": _create_parent_element(struct)} type_specific_fields = {"parent": _create_parent_element(struct)}
element = _create_base_element( element = _create_base_element(
"struct", struct.name, struct.source_mapping, type_specific_fields, additional_fields, "struct",
struct.name,
struct.source_mapping,
type_specific_fields,
additional_fields,
) )
self._data["elements"].append(element) self._data["elements"].append(element)
@ -390,7 +400,11 @@ class Output:
"signature": event.full_name, "signature": event.full_name,
} }
element = _create_base_element( element = _create_base_element(
"event", event.name, event.source_mapping, type_specific_fields, additional_fields, "event",
event.name,
event.source_mapping,
type_specific_fields,
additional_fields,
) )
self._data["elements"].append(element) self._data["elements"].append(element)
@ -410,7 +424,11 @@ class Output:
} }
node_name = str(node.expression) if node.expression else "" node_name = str(node.expression) if node.expression else ""
element = _create_base_element( element = _create_base_element(
"node", node_name, node.source_mapping, type_specific_fields, additional_fields, "node",
node_name,
node.source_mapping,
type_specific_fields,
additional_fields,
) )
self._data["elements"].append(element) self._data["elements"].append(element)
@ -461,7 +479,10 @@ class Output:
################################################################################### ###################################################################################
def add_pretty_table( def add_pretty_table(
self, content: MyPrettyTable, name: str, additional_fields: Optional[Dict] = None, self,
content: MyPrettyTable,
name: str,
additional_fields: Optional[Dict] = None,
): ):
if additional_fields is None: if additional_fields is None:
additional_fields = {} additional_fields = {}
@ -478,7 +499,11 @@ class Output:
################################################################################### ###################################################################################
def add_other( def add_other(
self, name: str, source_mapping, slither, additional_fields: Optional[Dict] = None, self,
name: str,
source_mapping,
slither,
additional_fields: Optional[Dict] = None,
): ):
# If this a tuple with (filename, start, end), convert it to a source mapping. # If this a tuple with (filename, start, end), convert it to a source mapping.
if additional_fields is None: if additional_fields is None:
@ -489,7 +514,10 @@ class Output:
source_id = next( source_id = next(
( (
source_unit_id source_unit_id
for (source_unit_id, source_unit_filename,) in slither.source_units.items() for (
source_unit_id,
source_unit_filename,
) in slither.source_units.items()
if source_unit_filename == filename if source_unit_filename == filename
), ),
-1, -1,

@ -370,9 +370,17 @@ class ExpressionToSlithIR(ExpressionVisitor):
assert isinstance(type_expression_found, ElementaryTypeNameExpression) assert isinstance(type_expression_found, ElementaryTypeNameExpression)
type_found = type_expression_found.type type_found = type_expression_found.type
if expression.member_name == "min:": if expression.member_name == "min:":
op = Assignment(val, Constant(str(type_found.min), type_found), type_found,) op = Assignment(
val,
Constant(str(type_found.min), type_found),
type_found,
)
else: else:
op = Assignment(val, Constant(str(type_found.max), type_found), type_found,) op = Assignment(
val,
Constant(str(type_found.max), type_found),
type_found,
)
self._result.append(op) self._result.append(op)
set_val(expression, val) set_val(expression, val)
return return

@ -0,0 +1,61 @@
pragma solidity ^0.4.0;
contract ReentrancyBenign {
uint8 anotherVariableToChange;
uint8 counter = 0;
function bad0() public {
if (!(msg.sender.call())) {
revert();
}
counter += 1;
}
function bad1(address target) public {
(bool success) = target.call();
require(success);
counter += 1;
}
function bad2(address target) public {
(bool success) = target.call();
if (success) {
address(target).call.value(1000)();
counter += 1;
}
else {
revert();
}
}
function bad3(address target) public {
externalCaller(target);
varChanger();
ethSender(target);
}
function bad4(address target) public {
externalCaller(target);
ethSender(address(0));
varChanger();
address(target).call.value(2)();
}
function bad5(address target) public {
ethSender(address(0));
varChanger();
ethSender(address(0));
}
function externalCaller(address target) private {
address(target).call();
}
function ethSender(address target) private {
address(target).call.value(1)();
}
function varChanger() private {
anotherVariableToChange++;
}
}

@ -470,7 +470,15 @@ def get_tests(solc_versions) -> Dict[str, List[str]]:
return tests return tests
Item = namedtuple("TestItem", ["test_id", "base_ver", "solc_ver", "is_legacy",],) Item = namedtuple(
"TestItem",
[
"test_id",
"base_ver",
"solc_ver",
"is_legacy",
],
)
def get_all_test() -> List[Item]: def get_all_test() -> List[Item]:

@ -53,6 +53,11 @@ def id_test(test_item: Test):
ALL_TESTS = [ ALL_TESTS = [
Test(
all_detectors.ReentrancyBenign,
"tests/detectors/reentrancy-benign/reentrancy-benign.sol",
"0.4.26",
),
Test( Test(
all_detectors.ReentrancyReadBeforeWritten, all_detectors.ReentrancyReadBeforeWritten,
"tests/detectors/reentrancy-before-write/reentrancy-write.sol", "tests/detectors/reentrancy-before-write/reentrancy-write.sol",

@ -0,0 +1,261 @@
"""
tests for `slither.core.declarations.Function`.
tests that `tests/test_function.sol` gets translated into correct
`slither.core.declarations.Function` objects or its subclasses
and that these objects behave correctly.
"""
from slither import Slither
from slither.core.declarations.function import FunctionType
from slither.core.solidity_types.elementary_type import ElementaryType
def test_functions():
# pylint: disable=too-many-statements
slither = Slither("tests/test_function.sol")
functions = slither.contracts_as_dict["TestFunction"].available_functions_as_dict()
f = functions["external_payable(uint256)"]
assert f.name == "external_payable"
assert f.full_name == "external_payable(uint256)"
assert f.canonical_name == "TestFunction.external_payable(uint256)"
assert f.solidity_signature == "external_payable(uint256)"
assert f.signature_str == "external_payable(uint256) returns(uint256)"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is False
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is True
assert f.visibility == "external"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters[0].name == "_a"
assert f.parameters[0].type == ElementaryType("uint256")
assert f.return_type[0] == ElementaryType("uint256")
f = functions["public_reenter()"]
assert f.name == "public_reenter"
assert f.full_name == "public_reenter()"
assert f.canonical_name == "TestFunction.public_reenter()"
assert f.solidity_signature == "public_reenter()"
assert f.signature_str == "public_reenter() returns()"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is False
assert f.can_reenter() is True
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is False
assert f.visibility == "public"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters == []
assert f.return_type is None
f = functions["public_payable_reenter_send(bool)"]
assert f.name == "public_payable_reenter_send"
assert f.full_name == "public_payable_reenter_send(bool)"
assert f.canonical_name == "TestFunction.public_payable_reenter_send(bool)"
assert f.solidity_signature == "public_payable_reenter_send(bool)"
assert f.signature_str == "public_payable_reenter_send(bool) returns()"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is False
assert f.can_reenter() is True
assert f.can_send_eth() is True
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is True
assert f.visibility == "public"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters[0].name == "_b"
assert f.parameters[0].type == ElementaryType("bool")
assert f.return_type is None
f = functions["external_send(uint8)"]
assert f.name == "external_send"
assert f.full_name == "external_send(uint8)"
assert f.canonical_name == "TestFunction.external_send(uint8)"
assert f.solidity_signature == "external_send(uint8)"
assert f.signature_str == "external_send(uint8) returns()"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is False
assert f.can_reenter() is True
assert f.can_send_eth() is True
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is False
assert f.visibility == "external"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters[0].name == "_c"
assert f.parameters[0].type == ElementaryType("uint8")
assert f.return_type is None
f = functions["internal_assembly(bytes)"]
assert f.name == "internal_assembly"
assert f.full_name == "internal_assembly(bytes)"
assert f.canonical_name == "TestFunction.internal_assembly(bytes)"
assert f.solidity_signature == "internal_assembly(bytes)"
assert f.signature_str == "internal_assembly(bytes) returns(uint256)"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is True
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is False
assert f.visibility == "internal"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters[0].name == "_d"
assert f.parameters[0].type == ElementaryType("bytes")
assert f.return_type[0] == ElementaryType("uint256")
f = functions["fallback()"]
assert f.name == "fallback"
assert f.full_name == "fallback()"
assert f.canonical_name == "TestFunction.fallback()"
assert f.solidity_signature == "fallback()"
assert f.signature_str == "fallback() returns()"
assert f.function_type == FunctionType.FALLBACK
assert f.contains_assembly is False
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is True
assert f.is_receive is False
assert f.payable is False
assert f.visibility == "external"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is True
assert f.parameters == []
assert f.return_type is None
f = functions["receive()"]
assert f.name == "receive"
assert f.full_name == "receive()"
assert f.canonical_name == "TestFunction.receive()"
assert f.solidity_signature == "receive()"
assert f.signature_str == "receive() returns()"
assert f.function_type == FunctionType.RECEIVE
assert f.contains_assembly is False
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is True
assert f.payable is True
assert f.visibility == "external"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is True
assert f.parameters == []
assert f.return_type is None
f = functions["constructor(address)"]
assert f.name == "constructor"
assert f.full_name == "constructor(address)"
assert f.canonical_name == "TestFunction.constructor(address)"
assert f.solidity_signature == "constructor(address)"
assert f.signature_str == "constructor(address) returns()"
assert f.function_type == FunctionType.CONSTRUCTOR
assert f.contains_assembly is False
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is True
assert f.visibility == "public"
assert f.view is False
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is True
assert f.parameters[0].name == "_e"
assert f.parameters[0].type == ElementaryType("address")
assert f.return_type is None
f = functions["private_view()"]
assert f.name == "private_view"
assert f.full_name == "private_view()"
assert f.canonical_name == "TestFunction.private_view()"
assert f.solidity_signature == "private_view()"
assert f.signature_str == "private_view() returns(bool)"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is False
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is False
assert f.visibility == "private"
assert f.view is True
assert f.pure is False
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters == []
assert f.return_type[0] == ElementaryType("bool")
f = functions["public_pure()"]
assert f.name == "public_pure"
assert f.full_name == "public_pure()"
assert f.canonical_name == "TestFunction.public_pure()"
assert f.solidity_signature == "public_pure()"
assert f.signature_str == "public_pure() returns(bool)"
assert f.function_type == FunctionType.NORMAL
assert f.contains_assembly is False
assert f.can_reenter() is False
assert f.can_send_eth() is False
assert f.is_constructor is False
assert f.is_fallback is False
assert f.is_receive is False
assert f.payable is False
assert f.visibility == "public"
assert f.view is True
assert f.pure is True
assert f.is_implemented is True
assert f.is_empty is False
assert f.parameters == []
assert f.return_type[0] == ElementaryType("bool")
def test_function_can_send_eth():
slither = Slither("tests/test_function.sol")
functions = slither.contracts_as_dict["TestFunctionCanSendEth"].available_functions_as_dict()
assert functions["send_direct()"].can_send_eth() is True
assert functions["transfer_direct()"].can_send_eth() is True
assert functions["call_direct()"].can_send_eth() is True
assert functions["highlevel_call_direct()"].can_send_eth() is True
assert functions["send_via_internal()"].can_send_eth() is True
assert functions["transfer_via_internal()"].can_send_eth() is True
assert functions["call_via_internal()"].can_send_eth() is True
assert functions["highlevel_call_via_internal()"].can_send_eth() is True
assert functions["send_via_external()"].can_send_eth() is False
assert functions["transfer_via_external()"].can_send_eth() is False
assert functions["call_via_external()"].can_send_eth() is False
assert functions["highlevel_call_via_external()"].can_send_eth() is False

@ -0,0 +1,129 @@
pragma solidity ^0.6.12;
// solidity source used by tests/test_function.py.
// tests/test_function.py tests that the functions below get translated into correct
// `slither.core.declarations.Function` objects or its subclasses
// and that these objects behave correctly.
contract TestFunction {
bool entered = false;
function external_payable(uint _a) external payable returns (uint) {
return 1;
}
function public_reenter() public {
msg.sender.call("");
}
function public_payable_reenter_send(bool _b) public payable {
msg.sender.call{value: 1}("");
}
function external_send(uint8 _c) external {
require(!entered);
entered = true;
msg.sender.call{value: 1}("");
}
function internal_assembly(bytes calldata _d) internal returns (uint) {
uint256 chain;
assembly {
chain := chainid()
}
return chain;
}
fallback() external {
}
receive() external payable {
}
constructor(address payable _e) public payable {
}
function private_view() private view returns (bool) {
return entered;
}
function public_pure() public pure returns (bool) {
return true;
}
}
contract TestFunctionCanSendEth {
function send_direct() internal {
address(1).send(1);
}
function transfer_direct() internal {
address(1).transfer(1);
}
function call_direct() internal {
address(1).call{value: 1}("");
}
function highlevel_call_direct() internal {
TestFunctionCanSendEthOther(address(5)).i_am_payable{value: 1}();
}
function send_via_internal() public {
send_direct();
}
function transfer_via_internal() public {
transfer_direct();
}
function call_via_internal() public {
call_direct();
}
function highlevel_call_via_internal() public {
highlevel_call_direct();
}
function send_via_external() public {
TestFunctionCanSendEthOther(address(5)).send_direct();
}
function transfer_via_external() public {
TestFunctionCanSendEthOther(address(5)).transfer_direct();
}
function call_via_external() public {
TestFunctionCanSendEthOther(address(5)).call_direct();
}
function highlevel_call_via_external() public {
TestFunctionCanSendEthOther(address(5)).highlevel_call_direct();
}
}
contract TestFunctionCanSendEthOther {
function i_am_payable() external payable {
}
function send_direct() external {
address(1).send(1);
}
function transfer_direct() external {
address(1).transfer(1);
}
function call_direct() external {
address(1).call{value: 1}("");
}
function highlevel_call_direct() external {
TestFunctionCanSendEthOther(address(5)).i_am_payable{value: 1}();
}
}

@ -0,0 +1,45 @@
# Slither Trophies
The following lists security vulnerabilities that were found by Slither.
If you found a security vulnerability using Slither,
please submit a PR with the relevant information.
| Project | Vulnerability | Date |
|--|--|--|
[Parity](https://github.com/trailofbits/publications/blob/master/reviews/parity.pdf) | Incorrect constructor name | July 2018
[Parity](https://github.com/trailofbits/publications/blob/master/reviews/parity.pdf) | Deletion of a mapping with structure | July 2018
[Parity](https://github.com/trailofbits/publications/blob/master/reviews/parity.pdf) | Uninitialized state variables | July 2018
[Basis](https://github.com/trailofbits/publications/blob/master/reviews/basis.pdf) | Missing return value check | Oct 2018
[Origin protocol](https://github.com/trailofbits/publications/blob/master/reviews/origin.pdf) | Reentrancy | Nov 2018
[Numerai](https://github.com/trailofbits/publications/blob/master/reviews/numerai.pdf) | Deletion of a mapping with structure | Jul 2019
[Numerai](https://github.com/trailofbits/publications/blob/master/reviews/numerai.pdf) | Missing return value | Jul 2019
[Flexa](https://github.com/trailofbits/publications/blob/master/reviews/Flexa.pdf) | Reentrancy (events out of order) | Sep 2019
[0x](https://github.com/trailofbits/publications/blob/master/reviews/0x-protocol.pdf) | Missing return value | Oct 2019
[Token mint](https://certificate.quantstamp.com/full/token-mint) | Reentrancies | Dec 2019
[Airswap](https://certificate.quantstamp.com/full/airswap) | Missing return value check | Feb 2020
[Stake Technologies Lockdrop](https://certificate.quantstamp.com/full/stake-technologies-lockdrop) | Dangerous strict equality | Mar 2020
[E&Y’s Nightfall](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | Missing return value | May 2020
[E&Y’s Nightfall](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | Empty return value | May 2020
[DefiStrategies](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | Modifier can return the default value | May 2020
[DefiStrategies](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | Dangerous strict equality allows the contract to be trapped | May 2020
[DOSnetwork](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | Abi `encodedPacked` collision | May 2020
[EthKids](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | `msg.value` is used two times to compute a price | May 2020
[HQ20](https://blog.trailofbits.com/2020/05/15/bug-hunting-with-crytic/) | Reentrancy | May 2020
[Dloop](https://certificate.quantstamp.com/full/dloop-art-registry-smart-contract) | Dangerous `block.timestamp` usage | Jun 2020
[Atomic Loans](https://certificate.quantstamp.com/full/atomic-loans) | Uninitialized state variable | Jul 2020
[Atomic Loans](https://certificate.quantstamp.com/full/atomic-loans) | State variable shadowing | Jul 2020
[Atomic Loans](https://certificate.quantstamp.com/full/atomic-loans) | Reentrancy | Jul 2020
[Amp](https://github.com/trailofbits/publications/blob/master/reviews/amp.pdf) | Duplicate contract name | Aug 2020
[PerlinXRewards](https://certificate.quantstamp.com/full/perlin-x-rewards-sol) | Multiple reentrancies | Aug 2020
[Linkswap](https://certificate.quantstamp.com/full/linkswap) | Lack of return value check | Nov 2020
[Linkswap](https://certificate.quantstamp.com/full/linkswap) | Uninitialized state variable | Nov 2020
[Cryptex](https://certificate.quantstamp.com/full/cryptex) | Lack of return value check | Nov 2020
[Hermez](https://github.com/trailofbits/publications/blob/master/reviews/hermez.pdf) | Reentrancy | Nov 2020
[Unoswap](https://www.unos.finance/wp-content/uploads/2020/11/block-audit.pdf) | Contract locking ethers | Nov 2020
[Idle](https://certificate.quantstamp.com/full/idle-finance) | Dangerous divide before multiply operations | Dec 2020
[RariCapital](https://certificate.quantstamp.com/full/rari-capital) | Lack of return value check | Dec 2020
[RariCapital](https://certificate.quantstamp.com/full/rari-capital) | Uninitialized state variable | Dec 2020
[wfil-factory](https://github.com/wfil/wfil-factory/commit/a43c1ddf52cf1191ccf1e71a637df02d78b98cc0) | Reentrancy | Dec 2020
[Origin Dollar](https://github.com/trailofbits/publications/blob/master/reviews/OriginDollar.pdf) | Reentrancy | Jan 2021
[Origin Dollar](https://github.com/trailofbits/publications/blob/master/reviews/OriginDollar.pdf) | Variable shadowing | Jan 2021
[OriginTrait](https://github.com/OriginTrail/starfleet-boarding-contract/commit/6481b12abc3cfd0d782abd0e32eabd103d8f6953) | Reentrancy | Jan 2021
Loading…
Cancel
Save