Merge branch 'dev' into dev-storage-var

dev-storage-var
Feist Josselin 9 months ago committed by GitHub
commit 9f9c572b2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .github/actions/upload-coverage/action.yml
  2. 2
      .github/workflows/black.yml
  3. 6
      .github/workflows/ci.yml
  4. 2
      .github/workflows/docker.yml
  5. 8
      .github/workflows/docs.yml
  6. 2
      .github/workflows/doctor.yml
  7. 2
      .github/workflows/linter.yml
  8. 2
      .github/workflows/pip-audit.yml
  9. 10
      .github/workflows/publish.yml
  10. 2
      .github/workflows/pylint.yml
  11. 9
      .github/workflows/test.yml
  12. 6
      README.md
  13. 14
      examples/scripts/data_dependency.py
  14. 9
      examples/scripts/data_dependency.sol
  15. 3
      examples/scripts/possible_paths.py
  16. 10
      slither/__main__.py
  17. 0
      slither/core/children/__init__.py
  18. 0
      slither/core/children/child_event.py
  19. 11
      slither/core/compilation_unit.py
  20. 2
      slither/core/declarations/__init__.py
  21. 39
      slither/core/declarations/contract.py
  22. 24
      slither/core/declarations/event.py
  23. 25
      slither/core/declarations/event_contract.py
  24. 13
      slither/core/declarations/event_top_level.py
  25. 13
      slither/core/declarations/function.py
  26. 4
      slither/core/declarations/solidity_variables.py
  27. 7
      slither/core/scope/scope.py
  28. 2
      slither/core/slither_core.py
  29. 7
      slither/core/variables/variable.py
  30. 4
      slither/detectors/assembly/incorrect_return.py
  31. 2
      slither/detectors/assembly/return_instead_of_leave.py
  32. 11
      slither/detectors/assembly/shift_parameter_mixup.py
  33. 2
      slither/detectors/functions/suicidal.py
  34. 2
      slither/detectors/statements/divide_before_multiply.py
  35. 18
      slither/detectors/statements/msg_value_in_loop.py
  36. 2
      slither/detectors/variables/predeclaration_usage_local.py
  37. 4
      slither/detectors/variables/unchanged_state_variables.py
  38. 7
      slither/printers/summary/variable_order.py
  39. 2
      slither/slither.py
  40. 23
      slither/slithir/convert.py
  41. 8
      slither/solc_parsing/declarations/contract.py
  42. 19
      slither/solc_parsing/declarations/event.py
  43. 16
      slither/solc_parsing/declarations/function.py
  44. 2
      slither/solc_parsing/declarations/modifier.py
  45. 3
      slither/solc_parsing/expressions/find_variable.py
  46. 68
      slither/solc_parsing/slither_compilation_unit_solc.py
  47. 14
      slither/solc_parsing/yul/evm_functions.py
  48. 5
      slither/tools/documentation/__main__.py
  49. 1
      slither/tools/flattening/flattening.py
  50. 33
      slither/tools/mutator/README.md
  51. 204
      slither/tools/mutator/__main__.py
  52. 54
      slither/tools/mutator/mutators/AOR.py
  53. 65
      slither/tools/mutator/mutators/ASOR.py
  54. 48
      slither/tools/mutator/mutators/BOR.py
  55. 39
      slither/tools/mutator/mutators/CR.py
  56. 42
      slither/tools/mutator/mutators/FHR.py
  57. 86
      slither/tools/mutator/mutators/LIR.py
  58. 46
      slither/tools/mutator/mutators/LOR.py
  59. 64
      slither/tools/mutator/mutators/MIA.py
  60. 68
      slither/tools/mutator/mutators/MVIE.py
  61. 68
      slither/tools/mutator/mutators/MVIV.py
  62. 35
      slither/tools/mutator/mutators/MWA.py
  63. 53
      slither/tools/mutator/mutators/ROR.py
  64. 38
      slither/tools/mutator/mutators/RR.py
  65. 109
      slither/tools/mutator/mutators/SBR.py
  66. 88
      slither/tools/mutator/mutators/UOR.py
  67. 126
      slither/tools/mutator/mutators/abstract_mutator.py
  68. 18
      slither/tools/mutator/mutators/all_mutators.py
  69. 15
      slither/tools/mutator/utils/command_line.py
  70. 130
      slither/tools/mutator/utils/file_handling.py
  71. 36
      slither/tools/mutator/utils/generic_patching.py
  72. 29
      slither/tools/mutator/utils/patch.py
  73. 100
      slither/tools/mutator/utils/testing_generated_mutant.py
  74. 6
      slither/tools/properties/__main__.py
  75. 2
      slither/tools/read_storage/read_storage.py
  76. 4
      slither/tools/upgradeability/checks/variable_initialization.py
  77. 24
      slither/tools/upgradeability/checks/variables_order.py
  78. 1
      slither/utils/command_line.py
  79. 2
      slither/utils/encoding.py
  80. 11
      slither/utils/myprettytable.py
  81. 20
      slither/utils/upgradeability.py
  82. 4
      slither/vyper_parsing/declarations/contract.py
  83. 6
      tests/e2e/compilation/test_resolution.py
  84. 2
      tests/e2e/detectors/snapshots/detectors__detector_ShiftParameterMixup_0_6_11_shift_parameter_mixup_sol__0.txt
  85. 2
      tests/e2e/detectors/snapshots/detectors__detector_ShiftParameterMixup_0_7_6_shift_parameter_mixup_sol__0.txt
  86. 2
      tests/e2e/detectors/snapshots/detectors__detector_Suicidal_0_7_6_suicidal_sol__0.txt
  87. 5
      tests/e2e/detectors/test_data/incorrect-shift/0.4.25/shift_parameter_mixup.sol
  88. BIN
      tests/e2e/detectors/test_data/incorrect-shift/0.4.25/shift_parameter_mixup.sol-0.4.25.zip
  89. 5
      tests/e2e/detectors/test_data/incorrect-shift/0.5.16/shift_parameter_mixup.sol
  90. BIN
      tests/e2e/detectors/test_data/incorrect-shift/0.5.16/shift_parameter_mixup.sol-0.5.16.zip
  91. 5
      tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol
  92. BIN
      tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol-0.6.11.zip
  93. 2
      tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol
  94. BIN
      tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol-0.7.6.zip
  95. 34
      tests/e2e/detectors/test_data/msg-value-loop/0.4.25/msg_value_loop.sol
  96. BIN
      tests/e2e/detectors/test_data/msg-value-loop/0.4.25/msg_value_loop.sol-0.4.25.zip
  97. 34
      tests/e2e/detectors/test_data/msg-value-loop/0.5.16/msg_value_loop.sol
  98. BIN
      tests/e2e/detectors/test_data/msg-value-loop/0.5.16/msg_value_loop.sol-0.5.16.zip
  99. 34
      tests/e2e/detectors/test_data/msg-value-loop/0.6.11/msg_value_loop.sol
  100. BIN
      tests/e2e/detectors/test_data/msg-value-loop/0.6.11/msg_value_loop.sol-0.6.11.zip
  101. Some files were not shown because too many files have changed in this diff Show More

@ -15,15 +15,15 @@ runs:
# This method has the limitation of 1 coverage file per run, limiting some coverage between online/offline tests. # This method has the limitation of 1 coverage file per run, limiting some coverage between online/offline tests.
- run: | - run: |
COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())") COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT echo "COVERAGE_UUID=${COVERAGE_UUID}" >> "$GITHUB_OUTPUT"
if [ -f .coverage ]; then if [ -f .coverage ]; then
mv .coverage .coverage.${COVERAGE_UUID} mv .coverage .coverage.${COVERAGE_UUID}
fi fi
id: coverage-uuid id: coverage-uuid
shell: bash shell: bash
- uses: actions/upload-artifact@v3.1.0 - uses: actions/upload-artifact@v4
with: with:
name: coverage-data name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }}
path: | path: |
.coverage.* .coverage.*
*.lcov *.lcov

@ -32,7 +32,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.8 python-version: 3.8

@ -55,7 +55,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }} - name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
- name: Install dependencies - name: Install dependencies
@ -67,11 +67,11 @@ jobs:
- name: Set up nix - name: Set up nix
if: matrix.type == 'dapp' if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v23 uses: cachix/install-nix-action@v25
- name: Set up cachix - name: Set up cachix
if: matrix.type == 'dapp' if: matrix.type == 'dapp'
uses: cachix/cachix-action@v12 uses: cachix/cachix-action@v14
with: with:
name: dapp name: dapp

@ -30,7 +30,7 @@ jobs:
- name: Set Docker metadata - name: Set Docker metadata
id: metadata id: metadata
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: | images: |
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}

@ -30,17 +30,17 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v3 uses: actions/configure-pages@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: '3.8' python-version: '3.8'
- run: pip install -e ".[doc]" - run: pip install -e ".[doc]"
- run: pdoc -o html/ slither '!slither.tools' #TODO fix import errors on pdoc run - run: pdoc -o html/ slither '!slither.tools' #TODO fix import errors on pdoc run
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v2 uses: actions/upload-pages-artifact@v3
with: with:
# Upload the doc # Upload the doc
path: './html/' path: './html/'
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v2 uses: actions/deploy-pages@v4

@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }} - name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}

@ -31,7 +31,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.8 python-version: 3.8

@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Python - name: Install Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: "3.10" python-version: "3.10"

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: '3.x' python-version: '3.x'
@ -23,7 +23,7 @@ jobs:
python -m pip install build python -m pip install build
python -m build python -m build
- name: Upload distributions - name: Upload distributions
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: slither-dists name: slither-dists
path: dist/ path: dist/
@ -38,16 +38,16 @@ jobs:
- build-release - build-release
steps: steps:
- name: fetch dists - name: fetch dists
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: slither-dists name: slither-dists
path: dist/ path: dist/
- name: publish - name: publish
uses: pypa/gh-action-pypi-publish@v1.8.10 uses: pypa/gh-action-pypi-publish@v1.8.11
- name: sign - name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.0 uses: sigstore/gh-action-sigstore-python@v2.1.1
with: with:
inputs: ./dist/*.tar.gz ./dist/*.whl inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true release-signing-artifacts: true

@ -29,7 +29,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.8 python-version: 3.8

@ -29,7 +29,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }} - name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
cache: "pip" cache: "pip"
@ -102,16 +102,17 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.8 python-version: 3.8
- run: pip install coverage[toml] - run: pip install coverage[toml]
- name: download coverage data - name: download coverage data
uses: actions/download-artifact@v3.0.2 uses: actions/download-artifact@v4
with: with:
name: coverage-data pattern: coverage-data-*
merge-multiple: true
- name: combine coverage data - name: combine coverage data
id: combinecoverage id: combinecoverage

@ -7,7 +7,7 @@
[![Slither - Read the Docs](https://img.shields.io/badge/Slither-Read_the_Docs-2ea44f)](https://crytic.github.io/slither/slither.html) [![Slither - Read the Docs](https://img.shields.io/badge/Slither-Read_the_Docs-2ea44f)](https://crytic.github.io/slither/slither.html)
[![Slither - Wiki](https://img.shields.io/badge/Slither-Wiki-2ea44f)](https://github.com/crytic/slither/wiki/SlithIR) [![Slither - Wiki](https://img.shields.io/badge/Slither-Wiki-2ea44f)](https://github.com/crytic/slither/wiki/SlithIR)
> Join the Empire Hacking Slack > Join the Empire Hacking Slack
> >
> [![Slack Status](https://slack.empirehacking.nyc/badge.svg)](https://slack.empirehacking.nyc/) > [![Slack Status](https://slack.empirehacking.nyc/badge.svg)](https://slack.empirehacking.nyc/)
> > <sub><i>- Discussions and Support </i></sub> > > <sub><i>- Discussions and Support </i></sub>
@ -131,10 +131,10 @@ Num | Detector | What it Detects | Impact | Confidence
20 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium 20 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
21 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium 21 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
22 | `incorrect-exp` | [Incorrect exponentiation](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation) | High | Medium 22 | `incorrect-exp` | [Incorrect exponentiation](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation) | High | Medium
23 | `incorrect-return` | [If a `return` is incorrectly used in assembly mode.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return) | High | Medium 23 | `incorrect-return` | [If a `return` is incorrectly used in assembly mode.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-return-in-assembly) | High | Medium
24 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium 24 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
25 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium 25 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
26 | `return-leave` | [If a `return` is used instead of a `leave`.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return) | High | Medium 26 | `return-leave` | [If a `return` is used instead of a `leave`.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-instead-of-leave-in-assembly) | High | Medium
27 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium 27 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
28 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium 28 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
29 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium 29 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium

@ -126,3 +126,17 @@ print(f"{var_tainted} is tainted: {is_tainted(var_tainted, contract)}")
assert is_tainted(var_tainted, contract) assert is_tainted(var_tainted, contract)
print(f"{var_not_tainted} is tainted: {is_tainted(var_not_tainted, contract)}") print(f"{var_not_tainted} is tainted: {is_tainted(var_not_tainted, contract)}")
assert not is_tainted(var_not_tainted, contract) assert not is_tainted(var_not_tainted, contract)
print("SimpleModifier contract")
contracts = slither.get_contract_from_name("SimpleModifier")
assert len(contracts) == 1
contract = contracts[0]
dependent_state_var = contract.get_state_variable_from_name("owner")
assert dependent_state_var
baz = contract.get_modifier_from_signature("baz()")
intermediate = baz.get_local_variable_from_name("intermediate")
assert intermediate
print(
f"{intermediate} depends on msg.sender: {is_dependent(intermediate, SolidityVariableComposed('msg.sender'), baz)}"
)
assert is_dependent(intermediate, SolidityVariableComposed("msg.sender"), baz)

@ -115,3 +115,12 @@ contract PropagateThroughReturnValue {
return (var_state); return (var_state);
} }
} }
contract SimpleModifier {
address owner;
modifier baz {
bool intermediate = msg.sender == owner;
require(intermediate);
_;
}
}

@ -19,7 +19,8 @@ def resolve_function(contract_name, function_name):
contract = contracts[0] contract = contracts[0]
# 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_declared if function.name == function_name),
None,
) )
# Verify we have resolved the function specified. # Verify we have resolved the function specified.

@ -528,12 +528,20 @@ def parse_args(
group_misc.add_argument( group_misc.add_argument(
"--triage-mode", "--triage-mode",
help="Run triage mode (save results in slither.db.json)", help="Run triage mode (save results in triage database)",
action="store_true", action="store_true",
dest="triage_mode", dest="triage_mode",
default=False, default=False,
) )
group_misc.add_argument(
"--triage-database",
help="File path to the triage database (default: slither.db.json)",
action="store",
dest="triage_database",
default=defaults_flag_in_config["triage_database"],
)
group_misc.add_argument( group_misc.add_argument(
"--config-file", "--config-file",
help="Provide a config file (default: slither.config.json)", help="Provide a config file (default: slither.config.json)",

@ -16,6 +16,7 @@ from slither.core.declarations import (
) )
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.declarations.using_for_top_level import UsingForTopLevel
@ -57,6 +58,7 @@ class SlitherCompilationUnit(Context):
self.contracts: List[Contract] = [] self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = [] self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = [] self._enums_top_level: List[EnumTopLevel] = []
self._events_top_level: List[EventTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = [] self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = [] self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = [] self._using_for_top_level: List[UsingForTopLevel] = []
@ -234,6 +236,10 @@ class SlitherCompilationUnit(Context):
def enums_top_level(self) -> List[EnumTopLevel]: def enums_top_level(self) -> List[EnumTopLevel]:
return self._enums_top_level return self._enums_top_level
@property
def events_top_level(self) -> List[EventTopLevel]:
return self._events_top_level
@property @property
def variables_top_level(self) -> List[TopLevelVariable]: def variables_top_level(self) -> List[TopLevelVariable]:
return self._variables_top_level return self._variables_top_level
@ -296,10 +302,7 @@ class SlitherCompilationUnit(Context):
slot = 0 slot = 0
offset = 0 offset = 0
for var in contract.state_variables_ordered: for var in contract.stored_state_variables_ordered:
if var.is_constant or var.is_immutable:
continue
assert var.type assert var.type
size, new_slot = var.type.storage_size size, new_slot = var.type.storage_size

@ -1,6 +1,8 @@
from .contract import Contract from .contract import Contract
from .enum import Enum from .enum import Enum
from .event import Event from .event import Event
from .event_contract import EventContract
from .event_top_level import EventTopLevel
from .function import Function from .function import Function
from .import_directive import Import from .import_directive import Import
from .modifier import Modifier from .modifier import Modifier

@ -33,7 +33,7 @@ if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import ( from slither.core.declarations import (
Enum, Enum,
Event, EventContract,
Modifier, Modifier,
EnumContract, EnumContract,
StructureContract, StructureContract,
@ -73,7 +73,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._enums: Dict[str, "EnumContract"] = {} self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {} self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {} self._events: Dict[str, "EventContract"] = {}
# map accessible variable from name -> variable # map accessible variable from name -> variable
# do not contain private variables inherited from contract # do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {} self._variables: Dict[str, "StateVariable"] = {}
@ -278,28 +278,28 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
################################################################################### ###################################################################################
@property @property
def events(self) -> List["Event"]: def events(self) -> List["EventContract"]:
""" """
list(Event): List of the events list(Event): List of the events
""" """
return list(self._events.values()) return list(self._events.values())
@property @property
def events_inherited(self) -> List["Event"]: def events_inherited(self) -> List["EventContract"]:
""" """
list(Event): List of the inherited events list(Event): List of the inherited events
""" """
return [e for e in self.events if e.contract != self] return [e for e in self.events if e.contract != self]
@property @property
def events_declared(self) -> List["Event"]: def events_declared(self) -> List["EventContract"]:
""" """
list(Event): List of the events declared within the contract (not inherited) list(Event): List of the events declared within the contract (not inherited)
""" """
return [e for e in self.events if e.contract == self] return [e for e in self.events if e.contract == self]
@property @property
def events_as_dict(self) -> Dict[str, "Event"]: def events_as_dict(self) -> Dict[str, "EventContract"]:
return self._events return self._events
# endregion # endregion
@ -436,6 +436,33 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
""" """
return list(self._variables.values()) return list(self._variables.values())
@property
def stored_state_variables(self) -> List["StateVariable"]:
"""
Returns state variables with storage locations, excluding private variables from inherited contracts.
Use stored_state_variables_ordered to access variables with storage locations in their declaration order.
This implementation filters out state variables if they are constant or immutable. It will be
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
Returns:
List[StateVariable]: A list of state variables with storage locations.
"""
return [variable for variable in self.state_variables if variable.is_stored]
@property
def stored_state_variables_ordered(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables with storage locations by order of declaration.
This implementation filters out state variables if they are constant or immutable. It will be
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
Returns:
List[StateVariable]: A list of state variables with storage locations ordered by declaration.
"""
return [variable for variable in self.state_variables_ordered if variable.is_stored]
@property @property
def state_variables_entry_points(self) -> List["StateVariable"]: def state_variables_entry_points(self) -> List["StateVariable"]:
""" """

@ -1,14 +1,10 @@
from typing import List, Tuple, TYPE_CHECKING from typing import List, Tuple
from slither.core.declarations.contract_level import ContractLevel
from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable from slither.core.variables.event_variable import EventVariable
if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(SourceMapping):
class Event(ContractLevel, SourceMapping):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._name = None self._name = None
@ -39,25 +35,9 @@ class Event(ContractLevel, SourceMapping):
name, parameters = self.signature name, parameters = self.signature
return name + "(" + ",".join(parameters) + ")" return name + "(" + ",".join(parameters) + ")"
@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name
@property @property
def elems(self) -> List["EventVariable"]: def elems(self) -> List["EventVariable"]:
return self._elems return self._elems
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name

@ -0,0 +1,25 @@
from typing import TYPE_CHECKING
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Event
if TYPE_CHECKING:
from slither.core.declarations import Contract
class EventContract(Event, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract
@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name

@ -0,0 +1,13 @@
from typing import TYPE_CHECKING
from slither.core.declarations import Event
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class EventTopLevel(Event, TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self.file_scope: "FileScope" = scope

@ -1500,10 +1500,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
""" """
Determine if the function can be re-entered Determine if the function can be re-entered
""" """
reentrancy_modifier = "nonReentrant"
if self.function_language == FunctionLanguage.Vyper:
reentrancy_modifier = "nonreentrant(lock)"
# TODO: compare with hash of known nonReentrant modifier instead of the name # TODO: compare with hash of known nonReentrant modifier instead of the name
if "nonReentrant" in [m.name for m in self.modifiers] or "nonreentrant(lock)" in [ if reentrancy_modifier in [m.name for m in self.modifiers]:
m.name for m in self.modifiers
]:
return False return False
if self.visibility in ["public", "external"]: if self.visibility in ["public", "external"]:
@ -1515,7 +1518,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
] ]
if not all_entry_points: if not all_entry_points:
return True return True
return not all(("nonReentrant" in [m.name for m in f.modifiers] for f in all_entry_points)) return not all(
(reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points)
)
# endregion # endregion
################################################################################### ###################################################################################

@ -21,7 +21,8 @@ SOLIDITY_VARIABLES = {
} }
SOLIDITY_VARIABLES_COMPOSED = { SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint", "block.basefee": "uint256",
"block.blobbasefee": "uint256",
"block.coinbase": "address", "block.coinbase": "address",
"block.difficulty": "uint256", "block.difficulty": "uint256",
"block.prevrandao": "uint256", "block.prevrandao": "uint256",
@ -44,6 +45,7 @@ SOLIDITY_VARIABLES_COMPOSED = {
} }
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"blobhash(uint256)": ["bytes32"],
"gasleft()": ["uint256"], "gasleft()": ["uint256"],
"assert(bool)": [], "assert(bool)": [],
"require(bool)": [], "require(bool)": [],

@ -7,6 +7,7 @@ from crytic_compile.utils.naming import Filename
from slither.core.declarations import Contract, Import, Pragma from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel
@ -35,6 +36,7 @@ class FileScope:
# So we simplify the logic and have the scope fields all populated # So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set() self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {} self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict # Functions is a list instead of a dict
# Because we parse the function signature later on # Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated # So we simplify the logic and have the scope fields all populated
@ -54,7 +56,7 @@ class FileScope:
# Name -> type alias # Name -> type alias
self.type_aliases: Dict[str, TypeAlias] = {} self.type_aliases: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool: def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches
""" """
Add information from accessible scopes. Return true if new information was obtained Add information from accessible scopes. Return true if new information was obtained
@ -74,6 +76,9 @@ class FileScope:
if not _dict_contain(new_scope.enums, self.enums): if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums) self.enums.update(new_scope.enums)
learn_something = True learn_something = True
if not _dict_contain(new_scope.events, self.events):
self.events.update(new_scope.events)
learn_something = True
if not new_scope.functions.issubset(self.functions): if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions self.functions |= new_scope.functions
learn_something = True learn_something = True

@ -269,6 +269,8 @@ class SlitherCore(Context):
self._compute_offsets_from_thing(event) self._compute_offsets_from_thing(event)
for enum in compilation_unit.enums_top_level: for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum) self._compute_offsets_from_thing(enum)
for event in compilation_unit.events_top_level:
self._compute_offsets_from_thing(event)
for function in compilation_unit.functions_top_level: for function in compilation_unit.functions_top_level:
self._compute_offsets_from_thing(function) self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level: for st in compilation_unit.structures_top_level:

@ -93,6 +93,13 @@ class Variable(SourceMapping):
def is_constant(self, is_cst: bool) -> None: def is_constant(self, is_cst: bool) -> None:
self._is_constant = is_cst self._is_constant = is_cst
@property
def is_stored(self) -> bool:
"""
Checks if a variable is stored, based on it not being constant or immutable. Future updates may adjust for new non-storage keywords.
"""
return not self._is_constant and not self._is_immutable
@property @property
def is_reentrant(self) -> bool: def is_reentrant(self) -> bool:
return self._is_reentrant return self._is_reentrant

@ -39,7 +39,9 @@ class IncorrectReturn(AbstractDetector):
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return" WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-return-in-assembly"
)
WIKI_TITLE = "Incorrect return in assembly" WIKI_TITLE = "Incorrect return in assembly"
WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution." WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution."

@ -20,7 +20,7 @@ class ReturnInsteadOfLeave(AbstractDetector):
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return" WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-instead-of-leave-in-assembly"
WIKI_TITLE = "Return instead of leave in assembly" WIKI_TITLE = "Return instead of leave in assembly"
WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used." WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used."

@ -8,6 +8,7 @@ from slither.slithir.operations import Binary, BinaryType
from slither.slithir.variables import Constant from slither.slithir.variables import Constant
from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.function_contract import FunctionContract
from slither.utils.output import Output from slither.utils.output import Output
from slither.core.cfg.node import NodeType
class ShiftParameterMixup(AbstractDetector): class ShiftParameterMixup(AbstractDetector):
@ -45,8 +46,18 @@ The shift statement will right-shift the constant 8 by `a` bits"""
def _check_function(self, f: FunctionContract) -> List[Output]: def _check_function(self, f: FunctionContract) -> List[Output]:
results = [] results = []
in_assembly = False
for node in f.nodes: for node in f.nodes:
if node.type == NodeType.ASSEMBLY:
in_assembly = True
continue
if node.type == NodeType.ENDASSEMBLY:
in_assembly = False
continue
if not in_assembly:
continue
for ir in node.irs: for ir in node.irs:
if isinstance(ir, Binary) and ir.type in [ if isinstance(ir, Binary) and ir.type in [
BinaryType.LEFT_SHIFT, BinaryType.LEFT_SHIFT,

@ -59,7 +59,7 @@ Bob calls `kill` and destructs the contract."""
if func.visibility not in ["public", "external"]: if func.visibility not in ["public", "external"]:
return False return False
calls = [c.name for c in func.internal_calls] calls = [c.name for c in func.all_internal_calls()]
if not ("suicide(address)" in calls or "selfdestruct(address)" in calls): if not ("suicide(address)" in calls or "selfdestruct(address)" in calls):
return False return False

@ -133,7 +133,7 @@ def detect_divide_before_multiply(
results: List[Tuple[FunctionContract, List[Node]]] = [] results: List[Tuple[FunctionContract, List[Node]]] = []
# Loop for each function and modifier. # Loop for each function and modifier.
for function in contract.functions_declared: for function in contract.functions_declared + contract.modifiers_declared:
if not function.entry_point: if not function.entry_point:
continue continue

@ -8,6 +8,9 @@ from slither.detectors.abstract_detector import (
from slither.slithir.operations import InternalCall from slither.slithir.operations import InternalCall
from slither.core.declarations import SolidityVariableComposed, Contract from slither.core.declarations import SolidityVariableComposed, Contract
from slither.utils.output import Output from slither.utils.output import Output
from slither.slithir.variables.constant import Constant
from slither.core.variables import Variable
from slither.core.expressions.literal import Literal
def detect_msg_value_in_loop(contract: Contract) -> List[Node]: def detect_msg_value_in_loop(contract: Contract) -> List[Node]:
@ -37,6 +40,21 @@ def msg_value_in_loop(
for ir in node.all_slithir_operations(): for ir in node.all_slithir_operations():
if in_loop_counter > 0 and SolidityVariableComposed("msg.value") in ir.read: if in_loop_counter > 0 and SolidityVariableComposed("msg.value") in ir.read:
# If we find a conditional expression with msg.value and is compared to 0 we don't report it
if ir.node.is_conditional() and SolidityVariableComposed("msg.value") in ir.read:
compared_to = (
ir.read[1]
if ir.read[0] == SolidityVariableComposed("msg.value")
else ir.read[0]
)
if (
isinstance(compared_to, Constant)
and compared_to.value == 0
or isinstance(compared_to, Variable)
and isinstance(compared_to.expression, Literal)
and str(compared_to.expression.value) == "0"
):
continue
results.append(ir.node) results.append(ir.node)
if isinstance(ir, (InternalCall)): if isinstance(ir, (InternalCall)):
msg_value_in_loop(ir.function.entry_point, in_loop_counter, visited, results) msg_value_in_loop(ir.function.entry_point, in_loop_counter, visited, results)

@ -36,7 +36,7 @@ class PredeclarationUsageLocal(AbstractDetector):
```solidity ```solidity
contract C { contract C {
function f(uint z) public returns (uint) { function f(uint z) public returns (uint) {
uint y = x + 9 + z; // 'z' is used pre-declaration uint y = x + 9 + z; // 'x' is used pre-declaration
uint x = 7; uint x = 7;
if (z % 2 == 0) { if (z % 2 == 0) {

@ -25,7 +25,7 @@ def _is_valid_type(v: StateVariable) -> bool:
def _valid_candidate(v: StateVariable) -> bool: def _valid_candidate(v: StateVariable) -> bool:
return _is_valid_type(v) and not (v.is_constant or v.is_immutable) return _is_valid_type(v)
def _is_constant_var(v: Variable) -> bool: def _is_constant_var(v: Variable) -> bool:
@ -92,7 +92,7 @@ class UnchangedStateVariables:
variables = [] variables = []
functions = [] functions = []
variables.append(c.state_variables) variables.append(c.stored_state_variables)
functions.append(c.all_functions_called) functions.append(c.all_functions_called)
valid_candidates: Set[StateVariable] = { valid_candidates: Set[StateVariable] = {

@ -28,10 +28,9 @@ class VariableOrder(AbstractPrinter):
for contract in self.slither.contracts_derived: for contract in self.slither.contracts_derived:
txt += f"\n{contract.name}:\n" txt += f"\n{contract.name}:\n"
table = MyPrettyTable(["Name", "Type", "Slot", "Offset"]) table = MyPrettyTable(["Name", "Type", "Slot", "Offset"])
for variable in contract.state_variables_ordered: for variable in contract.stored_state_variables_ordered:
if not variable.is_constant and not variable.is_immutable: slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
slot, offset = contract.compilation_unit.storage_layout_of(contract, variable) table.add_row([variable.canonical_name, str(variable.type), slot, offset])
table.add_row([variable.canonical_name, str(variable.type), slot, offset])
all_tables.append((contract.name, table)) all_tables.append((contract.name, table))
txt += str(table) + "\n" txt += str(table) + "\n"

@ -135,7 +135,9 @@ class Slither(
self._exclude_dependencies = kwargs.get("exclude_dependencies", False) self._exclude_dependencies = kwargs.get("exclude_dependencies", False)
triage_mode = kwargs.get("triage_mode", False) triage_mode = kwargs.get("triage_mode", False)
triage_database = kwargs.get("triage_database", "slither.db.json")
self._triage_mode = triage_mode self._triage_mode = triage_mode
self._previous_results_filename = triage_database
printers_to_run = kwargs.get("printers_to_run", "") printers_to_run = kwargs.get("printers_to_run", "")
if printers_to_run == "echidna": if printers_to_run == "echidna":

@ -630,6 +630,17 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if new_ir: if new_ir:
return new_ir return new_ir
# convert library function when used with "this"
if (
isinstance(t, ElementaryType)
and t.name == "address"
and ir.destination.name == "this"
and UserDefinedType(node_function.contract) in using_for
):
new_ir = convert_to_library_or_top_level(ir, node, using_for)
if new_ir:
return new_ir
if isinstance(t, UserDefinedType): if isinstance(t, UserDefinedType):
# UserdefinedType # UserdefinedType
t_type = t.type t_type = t.type
@ -1564,6 +1575,18 @@ def convert_to_library_or_top_level(
if new_ir: if new_ir:
return new_ir return new_ir
if (
isinstance(t, ElementaryType)
and t.name == "address"
and ir.destination.name == "this"
and UserDefinedType(node.function.contract) in using_for
):
new_ir = look_for_library_or_top_level(
contract, ir, using_for, UserDefinedType(node.function.contract)
)
if new_ir:
return new_ir
return None return None

@ -4,7 +4,7 @@ from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequenc
from slither.core.declarations import ( from slither.core.declarations import (
Modifier, Modifier,
Event, EventContract,
EnumContract, EnumContract,
StructureContract, StructureContract,
Function, Function,
@ -747,12 +747,12 @@ class ContractSolc(CallerContextExpression):
self._contract.events_as_dict.update(father.events_as_dict) self._contract.events_as_dict.update(father.events_as_dict)
for event_to_parse in self._eventsNotParsed: for event_to_parse in self._eventsNotParsed:
event = Event() event = EventContract()
event.set_contract(self._contract) event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit) event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self) # type: ignore event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore
event_parser.analyze(self) # type: ignore event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e: except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing event {e}") self.log_incorrect_parsing(f"Missing event {e}")

@ -8,7 +8,7 @@ from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.core.declarations.event import Event from slither.core.declarations.event import Event
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventSolc: class EventSolc:
@ -16,11 +16,12 @@ class EventSolc:
Event class Event class
""" """
def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc") -> None: def __init__(
self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
) -> None:
self._event = event self._event = event
event.set_contract(contract_parser.underlying_contract) self._slither_parser = slither_parser
self._parser_contract = contract_parser
if self.is_compact_ast: if self.is_compact_ast:
self._event.name = event_data["name"] self._event.name = event_data["name"]
@ -41,18 +42,16 @@ class EventSolc:
@property @property
def is_compact_ast(self) -> bool: def is_compact_ast(self) -> bool:
return self._parser_contract.is_compact_ast return self._slither_parser.is_compact_ast
def analyze(self, contract: "ContractSolc") -> None: def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed: for elem_to_parse in self._elemsNotParsed:
elem = EventVariable() elem = EventVariable()
# Todo: check if the source offset is always here # Todo: check if the source offset is always here
if "src" in elem_to_parse: if "src" in elem_to_parse:
elem.set_offset( elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_to_parse["src"], self._parser_contract.underlying_contract.compilation_unit
)
elem_parser = EventVariableSolc(elem, elem_to_parse) elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(contract) elem_parser.analyze(self._slither_parser)
self._event.elems.append(elem) self._event.elems.append(elem)

@ -996,7 +996,9 @@ class FunctionSolc(CallerContextExpression):
if "operations" in statement: if "operations" in statement:
asm_node.underlying_node.add_inline_asm(statement["operations"]) asm_node.underlying_node.add_inline_asm(statement["operations"])
link_underlying_nodes(node, asm_node) link_underlying_nodes(node, asm_node)
node = asm_node end_assembly = self._new_node(NodeType.ENDASSEMBLY, statement["src"], scope)
link_underlying_nodes(asm_node, end_assembly)
node = end_assembly
elif name == "DoWhileStatement": elif name == "DoWhileStatement":
node = self._parse_dowhile(statement, node, scope) node = self._parse_dowhile(statement, node, scope)
# For Continue / Break / Return / Throw # For Continue / Break / Return / Throw
@ -1106,11 +1108,13 @@ class FunctionSolc(CallerContextExpression):
return node return node
def _update_reachability(self, node: Node) -> None: def _update_reachability(self, node: Node) -> None:
if node.is_reachable: worklist = [node]
return while worklist:
node.set_is_reachable(True) current = worklist.pop()
for son in node.sons: # fix point
self._update_reachability(son) if not current.is_reachable:
current.set_is_reachable(True)
worklist.extend(current.sons)
def _parse_cfg(self, cfg: Dict) -> None: def _parse_cfg(self, cfg: Dict) -> None:

@ -93,6 +93,8 @@ class ModifierSolc(FunctionSolc):
self._rewrite_ternary_as_if_else() self._rewrite_ternary_as_if_else()
self._remove_alone_endif() self._remove_alone_endif()
if self._function.entry_point:
self._update_reachability(self._function.entry_point)
# self._analyze_read_write() # self._analyze_read_write()
# self._analyze_calls() # self._analyze_calls()

@ -134,6 +134,9 @@ def find_top_level(
if var_name in scope.enums: if var_name in scope.enums:
return scope.enums[var_name], False return scope.enums[var_name], False
if var_name in scope.events:
return scope.events[var_name], False
for import_directive in scope.imports: for import_directive in scope.imports:
if import_directive.alias == var_name: if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive) new_val = SolidityImportPlaceHolder(import_directive)

@ -10,6 +10,7 @@ from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract from slither.core.declarations import Contract
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import from slither.core.declarations.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma from slither.core.declarations.pragma_directive import Pragma
@ -23,6 +24,7 @@ from slither.solc_parsing.declarations.caller_context import CallerContextExpres
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.event import EventSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound from slither.solc_parsing.exceptions import VariableNotFound
@ -347,6 +349,15 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.type_aliases[alias] = type_alias self._compilation_unit.type_aliases[alias] = type_alias
scope.type_aliases[alias] = type_alias scope.type_aliases[alias] = type_alias
elif top_level_data[self.get_key()] == "EventDefinition":
event = EventTopLevel(scope)
event.set_offset(top_level_data["src"], self._compilation_unit)
event_parser = EventSolc(event, top_level_data, self) # type: ignore
event_parser.analyze() # type: ignore
scope.events[event.full_name] = event
self._compilation_unit.events_top_level.append(event)
else: else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported") raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
@ -421,61 +432,56 @@ Please rename it, this name is reserved for Slither's internals"""
self._contracts_by_id[contract.id] = contract self._contracts_by_id[contract.id] = contract
self._compilation_unit.contracts.append(contract) self._compilation_unit.contracts.append(contract)
def resolve_remapping_and_renaming(contract_parser: ContractSolc, want: str) -> Contract:
contract_name = contract_parser.remapping[want]
if contract_name in contract_parser.underlying_contract.file_scope.renaming:
contract_name = contract_parser.underlying_contract.file_scope.renaming[
contract_name
]
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
if target == contract_parser.underlying_contract:
raise InheritanceResolutionError(
"Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name."
)
assert target, f"Contract {contract_name} not found"
return target
# Update of the inheritance # Update of the inheritance
for contract_parser in self._underlying_contract_to_parser.values(): for contract_parser in self._underlying_contract_to_parser.values():
# remove the first elem in linearizedBaseContracts as it is the contract itself
ancestors = [] ancestors = []
fathers = [] fathers = []
father_constructors = [] father_constructors = []
# try:
# Resolve linearized base contracts.
missing_inheritance = None missing_inheritance = None
# Resolve linearized base contracts.
# Remove the first elem in linearizedBaseContracts as it is the contract itself.
for i in contract_parser.linearized_base_contracts[1:]: for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping: if i in contract_parser.remapping:
contract_name = contract_parser.remapping[i] target = resolve_remapping_and_renaming(contract_parser, i)
if contract_name in contract_parser.underlying_contract.file_scope.renaming:
contract_name = contract_parser.underlying_contract.file_scope.renaming[
contract_name
]
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
if target == contract_parser.underlying_contract:
raise InheritanceResolutionError(
"Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name."
)
assert target, f"Contract {contract_name} not found"
ancestors.append(target) ancestors.append(target)
elif i in self._contracts_by_id: elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i]) ancestors.append(self._contracts_by_id[i])
else: else:
missing_inheritance = i missing_inheritance = i
# Resolve immediate base contracts # Resolve immediate base contracts.
for i in contract_parser.baseContracts: for i in contract_parser.baseContracts:
if i in contract_parser.remapping: if i in contract_parser.remapping:
fathers.append( target = resolve_remapping_and_renaming(contract_parser, i)
contract_parser.underlying_contract.file_scope.get_contract_from_name( fathers.append(target)
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
)
elif i in self._contracts_by_id: elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i]) fathers.append(self._contracts_by_id[i])
else: else:
missing_inheritance = i missing_inheritance = i
# Resolve immediate base constructor calls # Resolve immediate base constructor calls.
for i in contract_parser.baseConstructorContractsCalled: for i in contract_parser.baseConstructorContractsCalled:
if i in contract_parser.remapping: if i in contract_parser.remapping:
father_constructors.append( target = resolve_remapping_and_renaming(contract_parser, i)
contract_parser.underlying_contract.file_scope.get_contract_from_name( father_constructors.append(target)
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
)
elif i in self._contracts_by_id: elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i]) father_constructors.append(self._contracts_by_id[i])
else: else:

@ -1,7 +1,7 @@
from slither.core.declarations.solidity_variables import SOLIDITY_FUNCTIONS from slither.core.declarations.solidity_variables import SOLIDITY_FUNCTIONS
from slither.core.expressions import BinaryOperationType, UnaryOperationType from slither.core.expressions import BinaryOperationType, UnaryOperationType
# taken from https://github.com/ethereum/solidity/blob/356cc91084114f840da66804b2a9fc1ac2846cff/libevmasm/Instruction.cpp#L180 # taken from https://github.com/ethereum/solidity/blob/e11b9ed9f2c254bc894d844c0a64a0eb76bbb4fd/libevmasm/Instruction.cpp#L184
evm_opcodes = [ evm_opcodes = [
"STOP", "STOP",
"ADD", "ADD",
@ -45,6 +45,7 @@ evm_opcodes = [
"EXTCODECOPY", "EXTCODECOPY",
"RETURNDATASIZE", "RETURNDATASIZE",
"RETURNDATACOPY", "RETURNDATACOPY",
"MCOPY",
"EXTCODEHASH", "EXTCODEHASH",
"BLOCKHASH", "BLOCKHASH",
"COINBASE", "COINBASE",
@ -55,12 +56,17 @@ evm_opcodes = [
"GASLIMIT", "GASLIMIT",
"CHAINID", "CHAINID",
"SELFBALANCE", "SELFBALANCE",
"BASEFEE",
"BLOBHASH",
"BLOBBASEFEE",
"POP", "POP",
"MLOAD", "MLOAD",
"MSTORE", "MSTORE",
"MSTORE8", "MSTORE8",
"SLOAD", "SLOAD",
"SSTORE", "SSTORE",
"TLOAD",
"TSTORE",
"JUMP", "JUMP",
"JUMPI", "JUMPI",
"PC", "PC",
@ -183,11 +189,16 @@ function_args = {
"mstore8": [2, 0], "mstore8": [2, 0],
"sload": [1, 1], "sload": [1, 1],
"sstore": [2, 0], "sstore": [2, 0],
"tload": [1, 1],
"tstore": [2, 0],
"msize": [1, 1], "msize": [1, 1],
"gas": [0, 1], "gas": [0, 1],
"address": [0, 1], "address": [0, 1],
"balance": [1, 1], "balance": [1, 1],
"selfbalance": [0, 1], "selfbalance": [0, 1],
"basefee": [0, 1],
"blobhash": [1, 1],
"blobbasefee": [0, 1],
"caller": [0, 1], "caller": [0, 1],
"callvalue": [0, 1], "callvalue": [0, 1],
"calldataload": [1, 1], "calldataload": [1, 1],
@ -199,6 +210,7 @@ function_args = {
"extcodecopy": [4, 0], "extcodecopy": [4, 0],
"returndatasize": [0, 1], "returndatasize": [0, 1],
"returndatacopy": [3, 0], "returndatacopy": [3, 0],
"mcopy": [3, 0],
"extcodehash": [1, 1], "extcodehash": [1, 1],
"create": [3, 1], "create": [3, 1],
"create2": [4, 1], "create2": [4, 1],

@ -21,7 +21,10 @@ def parse_args() -> argparse.Namespace:
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
""" """
parser = argparse.ArgumentParser(description="Demo", usage="slither-documentation filename") parser = argparse.ArgumentParser(
description="Auto-generate NatSpec documentation for every function using OpenAI Codex.",
usage="slither-documentation filename",
)
parser.add_argument("project", help="The target directory/Solidity file.") parser.add_argument("project", help="The target directory/Solidity file.")

@ -77,6 +77,7 @@ class Flattening:
self._get_source_code_top_level(compilation_unit.structures_top_level) self._get_source_code_top_level(compilation_unit.structures_top_level)
self._get_source_code_top_level(compilation_unit.enums_top_level) self._get_source_code_top_level(compilation_unit.enums_top_level)
self._get_source_code_top_level(compilation_unit.events_top_level)
self._get_source_code_top_level(compilation_unit.custom_errors) self._get_source_code_top_level(compilation_unit.custom_errors)
self._get_source_code_top_level(compilation_unit.variables_top_level) self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level) self._get_source_code_top_level(compilation_unit.functions_top_level)

@ -0,0 +1,33 @@
# Slither-mutate
`slither-mutate` is a mutation testing tool for solidity based smart contracts.
## Usage
`slither-mutate <codebase> --test-cmd <test-command> <options>`
To view the list of mutators available `slither-mutate --list-mutators`
### CLI Interface
```shell
positional arguments:
codebase Codebase to analyze (.sol file, project directory, ...)
options:
-h, --help show this help message and exit
--list-mutators List available detectors
--test-cmd TEST_CMD Command to run the tests for your project
--test-dir TEST_DIR Tests directory
--ignore-dirs IGNORE_DIRS
Directories to ignore
--timeout TIMEOUT Set timeout for test command (by default 30 seconds)
--output-dir OUTPUT_DIR
Name of output directory (by default 'mutation_campaign')
--verbose output all mutants generated
--mutators-to-run MUTATORS_TO_RUN
mutant generators to run
--contract-names CONTRACT_NAMES
list of contract names you want to mutate
--quick to stop full mutation if revert mutator passes
```

@ -2,20 +2,25 @@ import argparse
import inspect import inspect
import logging import logging
import sys import sys
from typing import Type, List, Any import os
import shutil
from typing import Type, List, Any, Optional
from crytic_compile import cryticparser from crytic_compile import cryticparser
from slither import Slither from slither import Slither
from slither.tools.mutator.mutators import all_mutators from slither.tools.mutator.mutators import all_mutators
from slither.utils.colors import yellow, magenta
from .mutators.abstract_mutator import AbstractMutator from .mutators.abstract_mutator import AbstractMutator
from .utils.command_line import output_mutators from .utils.command_line import output_mutators
from .utils.file_handling import (
transfer_and_delete,
backup_source_file,
get_sol_file_list,
)
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger("Slither") logger = logging.getLogger("Slither-Mutate")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region Cli Arguments # region Cli Arguments
@ -24,12 +29,16 @@ logger.setLevel(logging.INFO)
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
Returns: The arguments for the program.
"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Experimental smart contract mutator. Based on https://arxiv.org/abs/2006.11597", description="Experimental smart contract mutator. Based on https://arxiv.org/abs/2006.11597",
usage="slither-mutate target", usage="slither-mutate <codebase> --test-cmd <test command> <options>",
) )
parser.add_argument("codebase", help="Codebase to analyze (.sol file, truffle directory, ...)") parser.add_argument("codebase", help="Codebase to analyze (.sol file, project directory, ...)")
parser.add_argument( parser.add_argument(
"--list-mutators", "--list-mutators",
@ -39,6 +48,51 @@ def parse_args() -> argparse.Namespace:
default=False, default=False,
) )
# argument to add the test command
parser.add_argument("--test-cmd", help="Command to run the tests for your project")
# argument to add the test directory - containing all the tests
parser.add_argument("--test-dir", help="Tests directory")
# argument to ignore the interfaces, libraries
parser.add_argument("--ignore-dirs", help="Directories to ignore")
# time out argument
parser.add_argument("--timeout", help="Set timeout for test command (by default 30 seconds)")
# output directory argument
parser.add_argument(
"--output-dir", help="Name of output directory (by default 'mutation_campaign')"
)
# to print just all the mutants
parser.add_argument(
"--verbose",
help="output all mutants generated",
action="store_true",
default=False,
)
# select list of mutators to run
parser.add_argument(
"--mutators-to-run",
help="mutant generators to run",
)
# list of contract names you want to mutate
parser.add_argument(
"--contract-names",
help="list of contract names you want to mutate",
)
# flag to run full mutation based revert mutator output
parser.add_argument(
"--quick",
help="to stop full mutation if revert mutator passes",
action="store_true",
default=False,
)
# Initiate all the crytic config cli options # Initiate all the crytic config cli options
cryticparser.init(parser) cryticparser.init(parser)
@ -49,9 +103,18 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args() return parser.parse_args()
def _get_mutators() -> List[Type[AbstractMutator]]: def _get_mutators(mutators_list: List[str] | None) -> List[Type[AbstractMutator]]:
detectors_ = [getattr(all_mutators, name) for name in dir(all_mutators)] detectors_ = [getattr(all_mutators, name) for name in dir(all_mutators)]
detectors = [c for c in detectors_ if inspect.isclass(c) and issubclass(c, AbstractMutator)] if mutators_list is not None:
detectors = [
c
for c in detectors_
if inspect.isclass(c)
and issubclass(c, AbstractMutator)
and str(c.NAME) in mutators_list
]
else:
detectors = [c for c in detectors_ if inspect.isclass(c) and issubclass(c, AbstractMutator)]
return detectors return detectors
@ -59,7 +122,7 @@ class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods
def __call__( def __call__(
self, parser: Any, *args: Any, **kwargs: Any self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs ) -> None: # pylint: disable=signature-differs
checks = _get_mutators() checks = _get_mutators(None)
output_mutators(checks) output_mutators(checks)
parser.exit() parser.exit()
@ -72,17 +135,120 @@ class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods
################################################################################### ###################################################################################
def main() -> None: def main() -> (None): # pylint: disable=too-many-statements,too-many-branches,too-many-locals
args = parse_args() args = parse_args()
print(args.codebase) # arguments
sl = Slither(args.codebase, **vars(args)) test_command: str = args.test_cmd
test_directory: Optional[str] = args.test_dir
for compilation_unit in sl.compilation_units: paths_to_ignore: Optional[str] = args.ignore_dirs
for M in _get_mutators(): output_dir: Optional[str] = args.output_dir
m = M(compilation_unit) timeout: Optional[int] = args.timeout
m.mutate() solc_remappings: Optional[str] = args.solc_remaps
verbose: Optional[bool] = args.verbose
mutators_to_run: Optional[List[str]] = args.mutators_to_run
contract_names: Optional[List[str]] = args.contract_names
quick_flag: Optional[bool] = args.quick
logger.info(magenta(f"Starting Mutation Campaign in '{args.codebase} \n"))
if paths_to_ignore:
paths_to_ignore_list = paths_to_ignore.strip("][").split(",")
logger.info(magenta(f"Ignored paths - {', '.join(paths_to_ignore_list)} \n"))
else:
paths_to_ignore_list = []
# get all the contracts as a list from given codebase
sol_file_list: List[str] = get_sol_file_list(args.codebase, paths_to_ignore_list)
# folder where backup files and valid mutants created
if output_dir is None:
output_dir = "/mutation_campaign"
output_folder = os.getcwd() + output_dir
if os.path.exists(output_folder):
shutil.rmtree(output_folder)
# set default timeout
if timeout is None:
timeout = 30
# setting RR mutator as first mutator
mutators_list = _get_mutators(mutators_to_run)
# insert RR and CR in front of the list
CR_RR_list = []
duplicate_list = mutators_list.copy()
for M in duplicate_list:
if M.NAME == "RR":
mutators_list.remove(M)
CR_RR_list.insert(0, M)
elif M.NAME == "CR":
mutators_list.remove(M)
CR_RR_list.insert(1, M)
mutators_list = CR_RR_list + mutators_list
for filename in sol_file_list: # pylint: disable=too-many-nested-blocks
contract_name = os.path.split(filename)[1].split(".sol")[0]
# slither object
sl = Slither(filename, **vars(args))
# create a backup files
files_dict = backup_source_file(sl.source_code, output_folder)
# total count of mutants
total_count = 0
# count of valid mutants
v_count = 0
# lines those need not be mutated (taken from RR and CR)
dont_mutate_lines = []
# mutation
try:
for compilation_unit_of_main_file in sl.compilation_units:
contract_instance = ""
for contract in compilation_unit_of_main_file.contracts:
if contract_names is not None and contract.name in contract_names:
contract_instance = contract
elif str(contract.name).lower() == contract_name.lower():
contract_instance = contract
if contract_instance == "":
logger.error("Can't find the contract")
else:
for M in mutators_list:
m = M(
compilation_unit_of_main_file,
int(timeout),
test_command,
test_directory,
contract_instance,
solc_remappings,
verbose,
output_folder,
dont_mutate_lines,
)
(count_valid, count_invalid, lines_list) = m.mutate()
v_count += count_valid
total_count += count_valid + count_invalid
dont_mutate_lines = lines_list
if not quick_flag:
dont_mutate_lines = []
except Exception as e: # pylint: disable=broad-except
logger.error(e)
except KeyboardInterrupt:
# transfer and delete the backup files if interrupted
logger.error("\nExecution interrupted by user (Ctrl + C). Cleaning up...")
transfer_and_delete(files_dict)
# transfer and delete the backup files
transfer_and_delete(files_dict)
# output
logger.info(
yellow(
f"Done mutating, '{filename}'. Valid mutant count: '{v_count}' and Total mutant count '{total_count}'.\n"
)
)
logger.info(magenta(f"Finished Mutation Campaign in '{args.codebase}' \n"))
# endregion # endregion

@ -0,0 +1,54 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.core.expressions.unary_operation import UnaryOperation
arithmetic_operators = [
BinaryType.ADDITION,
BinaryType.DIVISION,
BinaryType.MULTIPLICATION,
BinaryType.SUBTRACTION,
BinaryType.MODULO,
]
class AOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "AOR"
HELP = "Arithmetic operator replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
try:
ir_expression = node.expression
except: # pylint: disable=bare-except
continue
for ir in node.irs:
if isinstance(ir, Binary) and ir.type in arithmetic_operators:
if isinstance(ir_expression, UnaryOperation):
continue
alternative_ops = arithmetic_operators[:]
alternative_ops.remove(ir.type)
for op in alternative_ops:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
# Replace the expression with true
new_str = f"{old_str.split(ir.type.value)[0]}{op.value}{old_str.split(ir.type.value)[1]}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,65 @@
from typing import Dict
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.core.expressions.assignment_operation import (
AssignmentOperationType,
AssignmentOperation,
)
assignment_operators = [
AssignmentOperationType.ASSIGN_ADDITION,
AssignmentOperationType.ASSIGN_SUBTRACTION,
AssignmentOperationType.ASSIGN,
AssignmentOperationType.ASSIGN_OR,
AssignmentOperationType.ASSIGN_CARET,
AssignmentOperationType.ASSIGN_AND,
AssignmentOperationType.ASSIGN_LEFT_SHIFT,
AssignmentOperationType.ASSIGN_RIGHT_SHIFT,
AssignmentOperationType.ASSIGN_MULTIPLICATION,
AssignmentOperationType.ASSIGN_DIVISION,
AssignmentOperationType.ASSIGN_MODULO,
]
class ASOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "ASOR"
HELP = "Assignment Operator Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
for ir in node.irs:
if (
isinstance(ir.expression, AssignmentOperation)
and ir.expression.type in assignment_operators
):
if ir.expression.type == AssignmentOperationType.ASSIGN:
continue
alternative_ops = assignment_operators[:]
try:
alternative_ops.remove(ir.expression.type)
except: # pylint: disable=bare-except
continue
for op in alternative_ops:
if op != ir.expression:
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
# Replace the expression with true
new_str = f"{old_str.split(str(ir.expression.type))[0]}{op}{old_str.split(str(ir.expression.type))[1]}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,48 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
bitwise_operators = [
BinaryType.AND,
BinaryType.OR,
BinaryType.LEFT_SHIFT,
BinaryType.RIGHT_SHIFT,
BinaryType.CARET,
]
class BOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "BOR"
HELP = "Bitwise Operator Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, Binary) and ir.type in bitwise_operators:
alternative_ops = bitwise_operators[:]
alternative_ops.remove(ir.type)
for op in alternative_ops:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
# Replace the expression with true
new_str = f"{old_str.split(ir.type.value)[0]}{op.value}{old_str.split(ir.type.value)[1]}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,39 @@
from typing import Dict
from slither.core.cfg.node import NodeType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
class CR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "CR"
HELP = "Comment Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type not in (
NodeType.ENTRYPOINT,
NodeType.ENDIF,
NodeType.ENDLOOP,
):
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
new_str = "//" + old_str
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,42 @@
from typing import Dict
import re
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
function_header_replacements = [
"pure ==> view",
"view ==> pure",
"(\\s)(external|public|internal) ==> \\1private",
"(\\s)(external|public) ==> \\1internal",
]
class FHR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "FHR"
HELP = "Function Header Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for function in self.contract.functions_and_modifiers_declared:
start = function.source_mapping.start
stop = start + function.source_mapping.content.find("{")
old_str = self.in_file_str[start:stop]
line_no = function.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
for value in function_header_replacements:
left_value = value.split(" ==> ", maxsplit=1)[0]
right_value = value.split(" ==> ")[1]
if re.search(re.compile(left_value), old_str) is not None:
new_str = re.sub(re.compile(left_value), right_value, old_str)
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,86 @@
from typing import Dict
from slither.core.expressions import Literal
from slither.core.variables.variable import Variable
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.core.solidity_types import ElementaryType
literal_replacements = []
class LIR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "LIR"
HELP = "Literal Interger Replacement"
def _mutate(self) -> Dict: # pylint: disable=too-many-branches
result: Dict = {}
variable: Variable
# Create fault for state variables declaration
for ( # pylint: disable=too-many-nested-blocks
variable
) in self.contract.state_variables_declared:
if variable.initialized:
# Cannot remove the initialization of constant variables
if variable.is_constant:
continue
if isinstance(variable.expression, Literal):
if isinstance(variable.type, ElementaryType):
literal_replacements.append(variable.type.min) # append data type min value
literal_replacements.append(variable.type.max) # append data type max value
if str(variable.type).startswith("uint"):
literal_replacements.append("1")
elif str(variable.type).startswith("uint"):
literal_replacements.append("-1")
# Get the string
start = variable.source_mapping.start
stop = start + variable.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = variable.node_initialization.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
for value in literal_replacements:
old_value = old_str[old_str.find("=") + 1 :].strip()
if old_value != value:
new_str = f"{old_str.split('=')[0]}= {value}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for variable in function.local_variables:
if variable.initialized and isinstance(variable.expression, Literal):
if isinstance(variable.type, ElementaryType):
literal_replacements.append(variable.type.min)
literal_replacements.append(variable.type.max)
if str(variable.type).startswith("uint"):
literal_replacements.append("1")
elif str(variable.type).startswith("uint"):
literal_replacements.append("-1")
start = variable.source_mapping.start
stop = start + variable.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = variable.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
for new_value in literal_replacements:
old_value = old_str[old_str.find("=") + 1 :].strip()
if old_value != new_value:
new_str = f"{old_str.split('=')[0]}= {new_value}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,46 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
logical_operators = [
BinaryType.OROR,
BinaryType.ANDAND,
]
class LOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "LOR"
HELP = "Logical Operator Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, Binary) and ir.type in logical_operators:
alternative_ops = logical_operators[:]
alternative_ops.remove(ir.type)
for op in alternative_ops:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
# Replace the expression with true
new_str = f"{old_str.split(ir.type.value)[0]} {op.value} {old_str.split(ir.type.value)[1]}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -1,39 +1,47 @@
from typing import Dict from typing import Dict
from slither.core.cfg.node import NodeType from slither.core.cfg.node import NodeType
from slither.formatters.utils.patches import create_patch from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation
class MIA(AbstractMutator): # pylint: disable=too-few-public-methods class MIA(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MIA" NAME = "MIA"
HELP = '"if" construct around statement' HELP = '"if" construct around statement'
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict: def _mutate(self) -> Dict:
result: Dict = {} result: Dict = {}
for function in self.contract.functions_and_modifiers_declared:
for contract in self.slither.contracts: for node in function.nodes:
if node.type == NodeType.IF:
for function in contract.functions_declared + list(contract.modifiers_declared): # Get the string
start = node.expression.source_mapping.start
for node in function.nodes: stop = start + node.expression.source_mapping.length
if node.type == NodeType.IF: old_str = self.in_file_str[start:stop]
# Retrieve the file line_no = node.source_mapping.lines
in_file = contract.source_mapping.filename.absolute if not line_no[0] in self.dont_mutate_line:
# Retrieve the source code # Replace the expression with true and false
in_file_str = contract.compilation_unit.core.source_code[in_file] for value in ["true", "false"]:
new_str = value
# Get the string create_patch_with_line(
start = node.source_mapping.start result,
stop = start + node.source_mapping.length self.in_file,
old_str = in_file_str[start:stop] start,
stop,
# Replace the expression with true old_str,
new_str = "true" new_str,
line_no[0],
create_patch(result, in_file, start, stop, old_str, new_str) )
if not isinstance(node.expression, UnaryOperation):
new_str = str(UnaryOperationType.BANG) + "(" + old_str + ")"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result return result

@ -1,36 +1,60 @@
from typing import Dict from typing import Dict
from slither.core.expressions import Literal from slither.core.expressions import Literal
from slither.core.variables.variable import Variable from slither.core.variables.variable import Variable
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.tools.mutator.utils.generic_patching import remove_assignement from slither.tools.mutator.utils.patch import create_patch_with_line
class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIE" NAME = "MVIE"
HELP = "variable initialization using an expression" HELP = "variable initialization using an expression"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict: def _mutate(self) -> Dict:
result: Dict = {} result: Dict = {}
variable: Variable variable: Variable
for contract in self.slither.contracts:
# Create fault for state variables declaration
for variable in contract.state_variables_declared:
if variable.initialized:
# Cannot remove the initialization of constant variables
if variable.is_constant:
continue
if not isinstance(variable.expression, Literal):
remove_assignement(variable, contract, result)
for function in contract.functions_declared + list(contract.modifiers_declared):
for variable in function.local_variables:
if variable.initialized and not isinstance(variable.expression, Literal):
remove_assignement(variable, contract, result)
# Create fault for state variables declaration
for variable in self.contract.state_variables_declared:
if variable.initialized:
# Cannot remove the initialization of constant variables
if variable.is_constant:
continue
if not isinstance(variable.expression, Literal):
# Get the string
start = variable.source_mapping.start
stop = variable.expression.source_mapping.start
old_str = self.in_file_str[start:stop]
new_str = old_str[: old_str.find("=")]
line_no = variable.node_initialization.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
create_patch_with_line(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no[0],
)
for function in self.contract.functions_and_modifiers_declared:
for variable in function.local_variables:
if variable.initialized and not isinstance(variable.expression, Literal):
# Get the string
start = variable.source_mapping.start
stop = variable.expression.source_mapping.start
old_str = self.in_file_str[start:stop]
new_str = old_str[: old_str.find("=")]
line_no = variable.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
create_patch_with_line(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no[0],
)
return result return result

@ -1,37 +1,59 @@
from typing import Dict from typing import Dict
from slither.core.expressions import Literal from slither.core.expressions import Literal
from slither.core.variables.variable import Variable from slither.core.variables.variable import Variable
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.tools.mutator.utils.generic_patching import remove_assignement from slither.tools.mutator.utils.patch import create_patch_with_line
class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIV" NAME = "MVIV"
HELP = "variable initialization using a value" HELP = "variable initialization using a value"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict: def _mutate(self) -> Dict:
result: Dict = {} result: Dict = {}
variable: Variable variable: Variable
for contract in self.slither.contracts: # Create fault for state variables declaration
for variable in self.contract.state_variables_declared:
# Create fault for state variables declaration if variable.initialized:
for variable in contract.state_variables_declared: # Cannot remove the initialization of constant variables
if variable.initialized: if variable.is_constant:
# Cannot remove the initialization of constant variables continue
if variable.is_constant:
continue if isinstance(variable.expression, Literal):
# Get the string
if isinstance(variable.expression, Literal): start = variable.source_mapping.start
remove_assignement(variable, contract, result) stop = variable.expression.source_mapping.start
old_str = self.in_file_str[start:stop]
for function in contract.functions_declared + list(contract.modifiers_declared): new_str = old_str[: old_str.find("=")]
for variable in function.local_variables: line_no = variable.node_initialization.source_mapping.lines
if variable.initialized and isinstance(variable.expression, Literal): if not line_no[0] in self.dont_mutate_line:
remove_assignement(variable, contract, result) create_patch_with_line(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no[0],
)
for function in self.contract.functions_and_modifiers_declared:
for variable in function.local_variables:
if variable.initialized and isinstance(variable.expression, Literal):
start = variable.source_mapping.start
stop = variable.expression.source_mapping.start
old_str = self.in_file_str[start:stop]
new_str = old_str[: old_str.find("=")]
line_no = variable.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
create_patch_with_line(
result,
self.in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
line_no[0],
)
return result return result

@ -0,0 +1,35 @@
from typing import Dict
from slither.core.cfg.node import NodeType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation
class MWA(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MWA"
HELP = '"while" construct around statement'
def _mutate(self) -> Dict:
result: Dict = {}
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type == NodeType.IFLOOP:
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
if not isinstance(node.expression, UnaryOperation):
new_str = str(UnaryOperationType.BANG) + "(" + old_str + ")"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,53 @@
from typing import Dict
from slither.slithir.operations import Binary, BinaryType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
relational_operators = [
BinaryType.LESS,
BinaryType.GREATER,
BinaryType.LESS_EQUAL,
BinaryType.GREATER_EQUAL,
BinaryType.EQUAL,
BinaryType.NOT_EQUAL,
]
class ROR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "ROR"
HELP = "Relational Operator Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, Binary) and ir.type in relational_operators:
if (
str(ir.variable_left.type) != "address"
and str(ir.variable_right) != "address"
):
alternative_ops = relational_operators[:]
alternative_ops.remove(ir.type)
for op in alternative_ops:
# Get the string
start = ir.expression.source_mapping.start
stop = start + ir.expression.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
# Replace the expression with true
new_str = f"{old_str.split(ir.type.value)[0]} {op.value} {old_str.split(ir.type.value)[1]}"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,38 @@
from typing import Dict
from slither.core.cfg.node import NodeType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
class RR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "RR"
HELP = "Revert Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type not in (
NodeType.ENTRYPOINT,
NodeType.ENDIF,
NodeType.ENDLOOP,
):
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
if old_str != "revert()":
new_str = "revert()"
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,109 @@
from typing import Dict
import re
from slither.core.cfg.node import NodeType
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.core.variables.variable import Variable
solidity_rules = [
"abi\\.encode\\( ==> abi.encodePacked(",
"abi\\.encodePacked\\( ==> abi.encode(",
"\\.call([({]) ==> .delegatecall\\1",
"\\.call([({]) ==> .staticcall\\1",
"\\.delegatecall([({]) ==> .call\\1",
"\\.delegatecall([({]) ==> .staticcall\\1",
"\\.staticcall([({]) ==> .delegatecall\\1",
"\\.staticcall([({]) ==> .call\\1",
"^now$ ==> 0",
"block.timestamp ==> 0",
"msg.value ==> 0",
"msg.value ==> 1",
"(\\s)(wei|gwei) ==> \\1ether",
"(\\s)(ether|gwei) ==> \\1wei",
"(\\s)(wei|ether) ==> \\1gwei",
"(\\s)(minutes|days|hours|weeks) ==> \\1seconds",
"(\\s)(seconds|days|hours|weeks) ==> \\1minutes",
"(\\s)(seconds|minutes|hours|weeks) ==> \\1days",
"(\\s)(seconds|minutes|days|weeks) ==> \\1hours",
"(\\s)(seconds|minutes|days|hours) ==> \\1weeks",
"(\\s)(memory) ==> \\1storage",
"(\\s)(storage) ==> \\1memory",
"(\\s)(constant) ==> \\1immutable",
"addmod ==> mulmod",
"mulmod ==> addmod",
"msg.sender ==> tx.origin",
"tx.origin ==> msg.sender",
"([^u])fixed ==> \\1ufixed",
"ufixed ==> fixed",
"(u?)int16 ==> \\1int8",
"(u?)int32 ==> \\1int16",
"(u?)int64 ==> \\1int32",
"(u?)int128 ==> \\1int64",
"(u?)int256 ==> \\1int128",
"while ==> if",
]
class SBR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "SBR"
HELP = "Solidity Based Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type not in (
NodeType.ENTRYPOINT,
NodeType.ENDIF,
NodeType.ENDLOOP,
):
# Get the string
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
for value in solidity_rules:
left_value = value.split(" ==> ", maxsplit=1)[0]
right_value = value.split(" ==> ")[1]
if re.search(re.compile(left_value), old_str) is not None:
new_str = re.sub(re.compile(left_value), right_value, old_str)
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
for ( # pylint: disable=too-many-nested-blocks
variable
) in self.contract.state_variables_declared:
node = variable.node_initialization
if node:
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
for value in solidity_rules:
left_value = value.split(" ==> ", maxsplit=1)[0]
right_value = value.split(" ==> ")[1]
if re.search(re.compile(left_value), old_str) is not None:
new_str = re.sub(re.compile(left_value), right_value, old_str)
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -0,0 +1,88 @@
from typing import Dict
from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation
from slither.tools.mutator.utils.patch import create_patch_with_line
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
unary_operators = [
UnaryOperationType.PLUSPLUS_PRE,
UnaryOperationType.MINUSMINUS_PRE,
UnaryOperationType.PLUSPLUS_POST,
UnaryOperationType.MINUSMINUS_POST,
UnaryOperationType.MINUS_PRE,
]
class UOR(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "UOR"
HELP = "Unary Operator Replacement"
def _mutate(self) -> Dict:
result: Dict = {}
for ( # pylint: disable=too-many-nested-blocks
function
) in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
try:
ir_expression = node.expression
except: # pylint: disable=bare-except
continue
start = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
if (
isinstance(ir_expression, UnaryOperation)
and ir_expression.type in unary_operators
):
for op in unary_operators:
if not node.expression.is_prefix:
if node.expression.type != op:
variable_read = node.variables_read[0]
new_str = str(variable_read) + str(op)
if new_str != old_str and str(op) != "-":
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
new_str = str(op) + str(variable_read)
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
else:
if node.expression.type != op:
variable_read = node.variables_read[0]
new_str = str(op) + str(variable_read)
if new_str != old_str and str(op) != "-":
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
new_str = str(variable_read) + str(op)
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
return result

@ -1,46 +1,55 @@
import abc import abc
import logging import logging
from enum import Enum from typing import Optional, Dict, Tuple, List
from typing import Optional, Dict
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.formatters.utils.patches import apply_patch, create_diff from slither.formatters.utils.patches import apply_patch, create_diff
from slither.tools.mutator.utils.testing_generated_mutant import test_patch
from slither.utils.colors import yellow
from slither.core.declarations import Contract
logger = logging.getLogger("Slither") logger = logging.getLogger("Slither-Mutate")
class IncorrectMutatorInitialization(Exception): class IncorrectMutatorInitialization(Exception):
pass pass
class FaultClass(Enum): class AbstractMutator(
Assignement = 0 metaclass=abc.ABCMeta
Checking = 1 ): # pylint: disable=too-few-public-methods,too-many-instance-attributes
Interface = 2
Algorithm = 3
Undefined = 100
class FaultNature(Enum):
Missing = 0
Wrong = 1
Extraneous = 2
Undefined = 100
class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-methods
NAME = "" NAME = ""
HELP = "" HELP = ""
FAULTCLASS = FaultClass.Undefined VALID_MUTANTS_COUNT = 0
FAULTNATURE = FaultNature.Undefined INVALID_MUTANTS_COUNT = 0
def __init__( def __init__( # pylint: disable=too-many-arguments
self, compilation_unit: SlitherCompilationUnit, rate: int = 10, seed: Optional[int] = None self,
): compilation_unit: SlitherCompilationUnit,
timeout: int,
testing_command: str,
testing_directory: str,
contract_instance: Contract,
solc_remappings: str | None,
verbose: bool,
output_folder: str,
dont_mutate_line: List[int],
rate: int = 10,
seed: Optional[int] = None,
) -> None:
self.compilation_unit = compilation_unit self.compilation_unit = compilation_unit
self.slither = compilation_unit.core self.slither = compilation_unit.core
self.seed = seed self.seed = seed
self.rate = rate self.rate = rate
self.test_command = testing_command
self.test_directory = testing_directory
self.timeout = timeout
self.solc_remappings = solc_remappings
self.verbose = verbose
self.output_folder = output_folder
self.contract = contract_instance
self.in_file = self.contract.source_mapping.filename.absolute
self.in_file_str = self.contract.compilation_unit.core.source_code[self.in_file]
self.dont_mutate_line = dont_mutate_line
if not self.NAME: if not self.NAME:
raise IncorrectMutatorInitialization( raise IncorrectMutatorInitialization(
@ -52,16 +61,6 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
f"HELP is not initialized {self.__class__.__name__}" f"HELP is not initialized {self.__class__.__name__}"
) )
if self.FAULTCLASS == FaultClass.Undefined:
raise IncorrectMutatorInitialization(
f"FAULTCLASS is not initialized {self.__class__.__name__}"
)
if self.FAULTNATURE == FaultNature.Undefined:
raise IncorrectMutatorInitialization(
f"FAULTNATURE is not initialized {self.__class__.__name__}"
)
if rate < 0 or rate > 100: if rate < 0 or rate > 100:
raise IncorrectMutatorInitialization( raise IncorrectMutatorInitialization(
f"rate must be between 0 and 100 {self.__class__.__name__}" f"rate must be between 0 and 100 {self.__class__.__name__}"
@ -72,25 +71,50 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
"""TODO Documentation""" """TODO Documentation"""
return {} return {}
def mutate(self) -> None: def mutate(self) -> Tuple[int, int, List[int]]:
all_patches = self._mutate() # call _mutate function from different mutators
(all_patches) = self._mutate()
if "patches" not in all_patches: if "patches" not in all_patches:
logger.debug(f"No patches found by {self.NAME}") logger.debug("No patches found by %s", self.NAME)
return return (0, 0, self.dont_mutate_line)
for file in all_patches["patches"]: for file in all_patches["patches"]:
original_txt = self.slither.source_code[file].encode("utf8") original_txt = self.slither.source_code[file].encode("utf8")
patched_txt = original_txt
offset = 0
patches = all_patches["patches"][file] patches = all_patches["patches"][file]
patches.sort(key=lambda x: x["start"]) patches.sort(key=lambda x: x["start"])
if not all(patches[i]["end"] <= patches[i + 1]["end"] for i in range(len(patches) - 1)): logger.info(yellow(f"Mutating {file} with {self.NAME} \n"))
logger.info(f"Impossible to generate patch; patches collisions: {patches}")
continue
for patch in patches: for patch in patches:
patched_txt, offset = apply_patch(patched_txt, patch, offset) # test the patch
diff = create_diff(self.compilation_unit, original_txt, patched_txt, file) flag = test_patch(
if not diff: file,
logger.info(f"Impossible to generate patch; empty {patches}") patch,
print(diff) self.test_command,
self.VALID_MUTANTS_COUNT,
self.NAME,
self.timeout,
self.solc_remappings,
self.verbose,
)
# if RR or CR and valid mutant, add line no.
if self.NAME in ("RR", "CR") and flag:
self.dont_mutate_line.append(patch["line_number"])
# count the valid and invalid mutants
if not flag:
self.INVALID_MUTANTS_COUNT += 1
continue
self.VALID_MUTANTS_COUNT += 1
patched_txt, _ = apply_patch(original_txt, patch, 0)
diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
if not diff:
logger.info(f"Impossible to generate patch; empty {patches}")
# add valid mutant patches to a output file
with open(
self.output_folder + "/patches_file.txt", "a", encoding="utf8"
) as patches_file:
patches_file.write(diff + "\n")
return (
self.VALID_MUTANTS_COUNT,
self.INVALID_MUTANTS_COUNT,
self.dont_mutate_line,
)

@ -1,4 +1,16 @@
# pylint: disable=unused-import # pylint: disable=unused-import
from slither.tools.mutator.mutators.MVIV import MVIV from slither.tools.mutator.mutators.MVIV import MVIV # severity low
from slither.tools.mutator.mutators.MVIE import MVIE from slither.tools.mutator.mutators.MVIE import MVIE # severity low
from slither.tools.mutator.mutators.MIA import MIA from slither.tools.mutator.mutators.LOR import LOR # severity medium
from slither.tools.mutator.mutators.UOR import UOR # severity medium
from slither.tools.mutator.mutators.SBR import SBR # severity medium
from slither.tools.mutator.mutators.AOR import AOR # severity medium
from slither.tools.mutator.mutators.BOR import BOR # severity medium
from slither.tools.mutator.mutators.ASOR import ASOR # severity medium
from slither.tools.mutator.mutators.MWA import MWA # severity medium
from slither.tools.mutator.mutators.LIR import LIR # severity medium
from slither.tools.mutator.mutators.FHR import FHR # severity medium
from slither.tools.mutator.mutators.MIA import MIA # severity medium
from slither.tools.mutator.mutators.ROR import ROR # severity medium
from slither.tools.mutator.mutators.RR import RR # severity high
from slither.tools.mutator.mutators.CR import CR # severity high

@ -1,5 +1,4 @@
from typing import List, Type from typing import List, Type
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.utils.myprettytable import MyPrettyTable from slither.utils.myprettytable import MyPrettyTable
@ -9,15 +8,13 @@ def output_mutators(mutators_classes: List[Type[AbstractMutator]]) -> None:
for detector in mutators_classes: for detector in mutators_classes:
argument = detector.NAME argument = detector.NAME
help_info = detector.HELP help_info = detector.HELP
fault_class = detector.FAULTCLASS.name mutators_list.append((argument, help_info))
fault_nature = detector.FAULTNATURE.name table = MyPrettyTable(["Num", "Name", "What it Does"])
mutators_list.append((argument, help_info, fault_class, fault_nature))
table = MyPrettyTable(["Num", "Name", "What it Does", "Fault Class", "Fault Nature"])
# Sort by class, nature, name # Sort by class
mutators_list = sorted(mutators_list, key=lambda element: (element[2], element[3], element[0])) mutators_list = sorted(mutators_list, key=lambda element: (element[0]))
idx = 1 idx = 1
for (argument, help_info, fault_class, fault_nature) in mutators_list: for argument, help_info in mutators_list:
table.add_row([str(idx), argument, help_info, fault_class, fault_nature]) table.add_row([str(idx), argument, help_info])
idx = idx + 1 idx = idx + 1
print(table) print(table)

@ -0,0 +1,130 @@
import os
from typing import Dict, List
import logging
logger = logging.getLogger("Slither-Mutate")
duplicated_files = {}
def backup_source_file(source_code: Dict, output_folder: str) -> Dict:
"""
function to backup the source file
returns: dictionary of duplicated files
"""
os.makedirs(output_folder, exist_ok=True)
for file_path, content in source_code.items():
directory, filename = os.path.split(file_path)
new_filename = f"{output_folder}/backup_{filename}"
new_file_path = os.path.join(directory, new_filename)
with open(new_file_path, "w", encoding="utf8") as new_file:
new_file.write(content)
duplicated_files[file_path] = new_file_path
return duplicated_files
def transfer_and_delete(files_dict: Dict) -> None:
"""function to transfer the original content to the sol file after campaign"""
try:
files_dict_copy = files_dict.copy()
for item, value in files_dict_copy.items():
with open(value, "r", encoding="utf8") as duplicated_file:
content = duplicated_file.read()
with open(item, "w", encoding="utf8") as original_file:
original_file.write(content)
os.remove(value)
# delete elements from the global dict
del duplicated_files[item]
except Exception as e: # pylint: disable=broad-except
logger.error(f"Error transferring content: {e}")
def create_mutant_file(file: str, count: int, rule: str) -> None:
"""function to create new mutant file"""
try:
_, filename = os.path.split(file)
# Read content from the duplicated file
with open(file, "r", encoding="utf8") as source_file:
content = source_file.read()
# Write content to the original file
mutant_name = filename.split(".")[0]
# create folder for each contract
os.makedirs("mutation_campaign/" + mutant_name, exist_ok=True)
with open(
"mutation_campaign/"
+ mutant_name
+ "/"
+ mutant_name
+ "_"
+ rule
+ "_"
+ str(count)
+ ".sol",
"w",
encoding="utf8",
) as mutant_file:
mutant_file.write(content)
# reset the file
with open(duplicated_files[file], "r", encoding="utf8") as duplicated_file:
duplicate_content = duplicated_file.read()
with open(file, "w", encoding="utf8") as source_file:
source_file.write(duplicate_content)
except Exception as e: # pylint: disable=broad-except
logger.error(f"Error creating mutant: {e}")
def reset_file(file: str) -> None:
"""function to reset the file"""
try:
# directory, filename = os.path.split(file)
# reset the file
with open(duplicated_files[file], "r", encoding="utf8") as duplicated_file:
duplicate_content = duplicated_file.read()
with open(file, "w", encoding="utf8") as source_file:
source_file.write(duplicate_content)
except Exception as e: # pylint: disable=broad-except
logger.error(f"Error resetting file: {e}")
def get_sol_file_list(codebase: str, ignore_paths: List[str] | None) -> List[str]:
"""
function to get the contracts list
returns: list of .sol files
"""
sol_file_list = []
if ignore_paths is None:
ignore_paths = []
# if input is contract file
if os.path.isfile(codebase):
return [codebase]
# if input is folder
if os.path.isdir(codebase):
directory = os.path.abspath(codebase)
for file in os.listdir(directory):
filename = os.path.join(directory, file)
if os.path.isfile(filename):
sol_file_list.append(filename)
elif os.path.isdir(filename):
_, dirname = os.path.split(filename)
if dirname in ignore_paths:
continue
for i in get_sol_file_list(filename, ignore_paths):
sol_file_list.append(i)
return sol_file_list

@ -1,36 +0,0 @@
from typing import Dict
from slither.core.declarations import Contract
from slither.core.variables.variable import Variable
from slither.formatters.utils.patches import create_patch
def remove_assignement(variable: Variable, contract: Contract, result: Dict):
"""
Remove the variable's initial assignement
:param variable:
:param contract:
:param result:
:return:
"""
# Retrieve the file
in_file = contract.source_mapping.filename.absolute
# Retrieve the source code
in_file_str = contract.compilation_unit.core.source_code[in_file]
# Get the string
start = variable.source_mapping.start
stop = variable.expression.source_mapping.start
old_str = in_file_str[start:stop]
new_str = old_str[: old_str.find("=")]
create_patch(
result,
in_file,
start,
stop + variable.expression.source_mapping.length,
old_str,
new_str,
)

@ -0,0 +1,29 @@
from typing import Dict, Union
from collections import defaultdict
# pylint: disable=too-many-arguments
def create_patch_with_line(
result: Dict,
file: str,
start: int,
end: int,
old_str: Union[str, bytes],
new_str: Union[str, bytes],
line_no: int,
) -> None:
if isinstance(old_str, bytes):
old_str = old_str.decode("utf8")
if isinstance(new_str, bytes):
new_str = new_str.decode("utf8")
p = {
"start": start,
"end": end,
"old_string": old_str,
"new_string": new_str,
"line_number": line_no,
}
if "patches" not in result:
result["patches"] = defaultdict(list)
if p not in result["patches"][file]:
result["patches"][file].append(p)

@ -0,0 +1,100 @@
import subprocess
import os
import logging
import time
import signal
from typing import Dict
import crytic_compile
from slither.tools.mutator.utils.file_handling import create_mutant_file, reset_file
from slither.utils.colors import green, red
logger = logging.getLogger("Slither-Mutate")
def compile_generated_mutant(file_path: str, mappings: str) -> bool:
"""
function to compile the generated mutant
returns: status of compilation
"""
try:
crytic_compile.CryticCompile(file_path, solc_remaps=mappings)
return True
except: # pylint: disable=bare-except
return False
def run_test_cmd(cmd: str, test_dir: str, timeout: int) -> bool:
"""
function to run codebase tests
returns: boolean whether the tests passed or not
"""
# future purpose
_ = test_dir
# add --fail-fast for foundry tests, to exit after first failure
if "forge test" in cmd and "--fail-fast" not in cmd:
cmd += " --fail-fast"
# add --bail for hardhat and truffle tests, to exit after first failure
elif "hardhat test" in cmd or "truffle test" in cmd and "--bail" not in cmd:
cmd += " --bail"
start = time.time()
# starting new process
with subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as P:
try:
# checking whether the process is completed or not within 30 seconds(default)
while P.poll() is None and (time.time() - start) < timeout:
time.sleep(0.05)
finally:
if P.poll() is None:
logger.error("HAD TO TERMINATE ANALYSIS (TIMEOUT OR EXCEPTION)")
# sends a SIGTERM signal to process group - bascially killing the process
os.killpg(os.getpgid(P.pid), signal.SIGTERM)
# Avoid any weird race conditions from grabbing the return code
time.sleep(0.05)
# indicates whether the command executed sucessfully or not
r = P.returncode
# if r is 0 then it is valid mutant because tests didn't fail
return r == 0
def test_patch( # pylint: disable=too-many-arguments
file: str,
patch: Dict,
command: str,
index: int,
generator_name: str,
timeout: int,
mappings: str | None,
verbose: bool,
) -> bool:
"""
function to verify the validity of each patch
returns: valid or invalid patch
"""
with open(file, "r", encoding="utf-8") as filepath:
content = filepath.read()
# Perform the replacement based on the index values
replaced_content = content[: patch["start"]] + patch["new_string"] + content[patch["end"] :]
# Write the modified content back to the file
with open(file, "w", encoding="utf-8") as filepath:
filepath.write(replaced_content)
if compile_generated_mutant(file, mappings):
if run_test_cmd(command, file, timeout):
create_mutant_file(file, index, generator_name)
print(
green(
f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' ---> VALID\n"
)
)
return True
reset_file(file)
if verbose:
print(
red(
f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' ---> INVALID\n"
)
)
return False

@ -68,13 +68,13 @@ def parse_args() -> argparse.Namespace:
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Demo", description="Generates code properties (e.g., invariants) that can be tested with unit tests or Echidna, entirely automatically.",
usage="slither-demo filename", usage="slither-prop filename",
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
) )
parser.add_argument( parser.add_argument(
"filename", help="The filename of the contract or truffle directory to analyze." "filename", help="The filename of the contract or project directory to analyze."
) )
parser.add_argument("--contract", help="The targeted contract.") parser.add_argument("--contract", help="The targeted contract.")

@ -398,7 +398,7 @@ class SlitherReadStorage:
for contract in self.contracts: for contract in self.contracts:
for var in contract.state_variables_ordered: for var in contract.state_variables_ordered:
if func(var): if func(var):
if not var.is_constant and not var.is_immutable: if var.is_stored:
self._target_variables.append((contract, var)) self._target_variables.append((contract, var))
elif ( elif (
self.unstructured self.unstructured

@ -43,8 +43,8 @@ Using initialize functions to write initial values in state variables.
def _check(self) -> List[Output]: def _check(self) -> List[Output]:
results = [] results = []
for s in self.contract.state_variables_ordered: for s in self.contract.stored_state_variables_ordered:
if s.initialized and not (s.is_constant or s.is_immutable): if s.initialized:
info: CHECK_INFO = [s, " is a state variable with an initial value.\n"] info: CHECK_INFO = [s, " is a state variable with an initial value.\n"]
json = self.generate_result(info) json = self.generate_result(info)
results.append(json) results.append(json)

@ -115,16 +115,8 @@ Avoid variables in the proxy. If a variable is in the proxy, ensure it has the s
def _check(self) -> List[Output]: def _check(self) -> List[Output]:
contract1 = self._contract1() contract1 = self._contract1()
contract2 = self._contract2() contract2 = self._contract2()
order1 = [ order1 = contract1.stored_state_variables_ordered
variable order2 = contract2.stored_state_variables_ordered
for variable in contract1.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
order2 = [
variable
for variable in contract2.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
results: List[Output] = [] results: List[Output] = []
for idx, _ in enumerate(order1): for idx, _ in enumerate(order1):
@ -244,16 +236,8 @@ Avoid variables in the proxy. If a variable is in the proxy, ensure it has the s
def _check(self) -> List[Output]: def _check(self) -> List[Output]:
contract1 = self._contract1() contract1 = self._contract1()
contract2 = self._contract2() contract2 = self._contract2()
order1 = [ order1 = contract1.stored_state_variables_ordered
variable order2 = contract2.stored_state_variables_ordered
for variable in contract1.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
order2 = [
variable
for variable in contract2.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
results = [] results = []

@ -70,6 +70,7 @@ defaults_flag_in_config = {
"no_fail": False, "no_fail": False,
"sarif_input": "export.sarif", "sarif_input": "export.sarif",
"sarif_triage": "export.sarif.sarifexplorer", "sarif_triage": "export.sarif.sarifexplorer",
"triage_database": "slither.db.json",
**DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE, **DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE,
} }

@ -71,7 +71,7 @@ def encode_var_for_compare(var: Union[variables.Variable, SolidityVariable]) ->
if isinstance(var, variables.LocalVariable): if isinstance(var, variables.LocalVariable):
return f"local_solc_variable({ntype(var.type)},{var.location})" return f"local_solc_variable({ntype(var.type)},{var.location})"
if isinstance(var, variables.StateVariable): if isinstance(var, variables.StateVariable):
if not (var.is_constant or var.is_immutable): if var.is_stored:
try: try:
slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var) slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var)
except KeyError: except KeyError:

@ -1,7 +1,10 @@
from typing import List, Dict, Union from typing import List, Dict, Union
from prettytable import PrettyTable
from prettytable.colortable import ColorTable, Themes from prettytable.colortable import ColorTable, Themes
from slither.utils.colors import Colors
class MyPrettyTable: class MyPrettyTable:
def __init__(self, field_names: List[str], pretty_align: bool = True): # TODO: True by default? def __init__(self, field_names: List[str], pretty_align: bool = True): # TODO: True by default?
@ -19,8 +22,12 @@ class MyPrettyTable:
def add_row(self, row: List[Union[str, List[str]]]) -> None: def add_row(self, row: List[Union[str, List[str]]]) -> None:
self._rows.append(row) self._rows.append(row)
def to_pretty_table(self) -> ColorTable: def to_pretty_table(self) -> PrettyTable:
table = ColorTable(self._field_names, theme=Themes.OCEAN) if Colors.COLORIZATION_ENABLED:
table = ColorTable(self._field_names, theme=Themes.OCEAN)
else:
table = PrettyTable(self._field_names)
for row in self._rows: for row in self._rows:
table.add_row(row) table.add_row(row)
if len(self._options["set_alignment"]): if len(self._options["set_alignment"]):

@ -81,12 +81,8 @@ def compare(
tainted-contracts: list[TaintedExternalContract] tainted-contracts: list[TaintedExternalContract]
""" """
order_vars1 = [ order_vars1 = v1.stored_state_variables_ordered
v for v in v1.state_variables_ordered if not v.is_constant and not v.is_immutable order_vars2 = v2.stored_state_variables_ordered
]
order_vars2 = [
v for v in v2.state_variables_ordered if not v.is_constant and not v.is_immutable
]
func_sigs1 = [function.solidity_signature for function in v1.functions] func_sigs1 = [function.solidity_signature for function in v1.functions]
func_sigs2 = [function.solidity_signature for function in v2.functions] func_sigs2 = [function.solidity_signature for function in v2.functions]
@ -206,7 +202,7 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon
elif ( elif (
isinstance(target, StateVariable) isinstance(target, StateVariable)
and target not in (v for v in tainted_contracts[contract.name].tainted_variables) and target not in (v for v in tainted_contracts[contract.name].tainted_variables)
and not (target.is_constant or target.is_immutable) and target.is_stored
): ):
# Found a new high-level call to a public state variable getter # Found a new high-level call to a public state variable getter
tainted_contracts[contract.name].add_tainted_variable(target) tainted_contracts[contract.name].add_tainted_variable(target)
@ -304,12 +300,8 @@ def get_missing_vars(v1: Contract, v2: Contract) -> List[StateVariable]:
List of StateVariables from v1 missing in v2 List of StateVariables from v1 missing in v2
""" """
results = [] results = []
order_vars1 = [ order_vars1 = v1.stored_state_variables_ordered
v for v in v1.state_variables_ordered if not v.is_constant and not v.is_immutable order_vars2 = v2.stored_state_variables_ordered
]
order_vars2 = [
v for v in v2.state_variables_ordered if not v.is_constant and not v.is_immutable
]
if len(order_vars2) < len(order_vars1): if len(order_vars2) < len(order_vars1):
for variable in order_vars1: for variable in order_vars1:
if variable.name not in [v.name for v in order_vars2]: if variable.name not in [v.name for v in order_vars2]:
@ -366,7 +358,7 @@ def get_proxy_implementation_slot(proxy: Contract) -> Optional[SlotInfo]:
delegate = get_proxy_implementation_var(proxy) delegate = get_proxy_implementation_var(proxy)
if isinstance(delegate, StateVariable): if isinstance(delegate, StateVariable):
if not delegate.is_constant and not delegate.is_immutable: if delegate.is_stored:
srs = SlitherReadStorage([proxy], 20) srs = SlitherReadStorage([proxy], 20)
return srs.get_storage_slot(delegate, proxy) return srs.get_storage_slot(delegate, proxy)
if delegate.is_constant and delegate.type.name == "bytes32": if delegate.is_constant and delegate.type.name == "bytes32":

@ -24,7 +24,7 @@ from slither.vyper_parsing.declarations.struct import StructVyper
from slither.vyper_parsing.variables.state_variable import StateVariableVyper from slither.vyper_parsing.variables.state_variable import StateVariableVyper
from slither.vyper_parsing.declarations.function import FunctionVyper from slither.vyper_parsing.declarations.function import FunctionVyper
from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations import Contract, StructureContract, EnumContract, Event from slither.core.declarations import Contract, StructureContract, EnumContract, EventContract
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
@ -478,7 +478,7 @@ class ContractVyper: # pylint: disable=too-many-instance-attributes
def parse_events(self) -> None: def parse_events(self) -> None:
for event_to_parse in self._eventsNotParsed: for event_to_parse in self._eventsNotParsed:
event = Event() event = EventContract()
event.set_contract(self._contract) event.set_contract(self._contract)
event.set_offset(event_to_parse.src, self._contract.compilation_unit) event.set_offset(event_to_parse.src, self._contract.compilation_unit)

@ -57,6 +57,6 @@ def test_contract_function_parameter(solc_binary_path) -> None:
function = contract.functions[0] function = contract.functions[0]
parameters = function.parameters parameters = function.parameters
assert (parameters[0].name == 'param1') assert parameters[0].name == "param1"
assert (parameters[1].name == '') assert parameters[1].name == ""
assert (parameters[2].name == 'param3') assert parameters[2].name == "param3"

@ -1,2 +1,2 @@
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#3-7) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#5) C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#3-10) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#5)

@ -1,2 +1,2 @@
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#3-8) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#5) C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#3-10) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#5)

@ -1,2 +1,4 @@
C.i_am_a_backdoor2(address) (tests/e2e/detectors/test_data/suicidal/0.7.6/suicidal.sol#8-10) allows anyone to destruct the contract
C.i_am_a_backdoor() (tests/e2e/detectors/test_data/suicidal/0.7.6/suicidal.sol#4-6) allows anyone to destruct the contract C.i_am_a_backdoor() (tests/e2e/detectors/test_data/suicidal/0.7.6/suicidal.sol#4-6) allows anyone to destruct the contract

@ -1,8 +1,11 @@
contract C { contract C {
function f() internal returns (uint a) { function f() internal returns (uint a, uint b) {
assembly { assembly {
a := shr(a, 8) a := shr(a, 8)
b := shl(248, 0xff)
} }
uint y = 1;
uint g = 0xff << y;
} }
} }

@ -1,8 +1,11 @@
contract C { contract C {
function f() internal returns (uint a) { function f() internal returns (uint a, uint b) {
assembly { assembly {
a := shr(a, 8) a := shr(a, 8)
b := shl(248, 0xff)
} }
uint y = 1;
uint g = 0xff << y;
} }
} }

@ -1,8 +1,11 @@
contract C { contract C {
function f() internal returns (uint a) { function f() internal returns (uint a, uint b) {
assembly { assembly {
a := shr(a, 8) a := shr(a, 8)
b := shl(248, 0xff)
} }
uint y = 1;
uint g = 0xff << y;
} }
} }

@ -5,5 +5,7 @@ contract C {
a := shr(a, 8) a := shr(a, 8)
b := shl(248, 0xff) b := shl(248, 0xff)
} }
uint y = 1;
uint g = 0xff << y;
} }
} }

@ -26,4 +26,38 @@ contract C{
} }
} }
function good1(address[] memory receivers) public payable {
require(msg.value == 0);
for (uint256 i = 0; i < receivers.length; i++) {
balances[receivers[i]] += 1;
}
}
function good2(address[] memory receivers) public payable {
uint zero = 0;
for (uint256 i = 0; i < receivers.length; i++) {
assert(msg.value == zero);
balances[receivers[i]] += 1;
}
}
function good3(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
if (0 != msg.value) {
revert();
}
balances[receivers[i]] += 1;
}
}
function good4(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
_g();
balances[receivers[i]] += 1;
}
}
function _g() internal {
require(msg.value == 0);
}
} }

@ -26,4 +26,38 @@ contract C{
} }
} }
function good1(address[] memory receivers) public payable {
require(msg.value == 0);
for (uint256 i = 0; i < receivers.length; i++) {
balances[receivers[i]] += 1;
}
}
function good2(address[] memory receivers) public payable {
uint zero = 0;
for (uint256 i = 0; i < receivers.length; i++) {
assert(msg.value == zero);
balances[receivers[i]] += 1;
}
}
function good3(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
if (0 != msg.value) {
revert();
}
balances[receivers[i]] += 1;
}
}
function good4(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
_g();
balances[receivers[i]] += 1;
}
}
function _g() internal {
require(msg.value == 0);
}
} }

@ -26,4 +26,38 @@ contract C{
} }
} }
function good1(address[] memory receivers) public payable {
require(msg.value == 0);
for (uint256 i = 0; i < receivers.length; i++) {
balances[receivers[i]] += 1;
}
}
function good2(address[] memory receivers) public payable {
uint zero = 0;
for (uint256 i = 0; i < receivers.length; i++) {
assert(msg.value == zero);
balances[receivers[i]] += 1;
}
}
function good3(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
if (0 != msg.value) {
revert();
}
balances[receivers[i]] += 1;
}
}
function good4(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
_g();
balances[receivers[i]] += 1;
}
}
function _g() internal {
require(msg.value == 0);
}
} }

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

Loading…
Cancel
Save