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. 4
      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. 3
      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. 60
      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. 200
      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. 58
      slither/tools/mutator/mutators/MIA.py
  60. 50
      slither/tools/mutator/mutators/MVIE.py
  61. 50
      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. 120
      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. 9
      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.
- run: |
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
mv .coverage .coverage.${COVERAGE_UUID}
fi
id: coverage-uuid
shell: bash
- uses: actions/upload-artifact@v3.1.0
- uses: actions/upload-artifact@v4
with:
name: coverage-data
name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }}
path: |
.coverage.*
*.lcov

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

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

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

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

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

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

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

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

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

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

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

@ -126,3 +126,17 @@ print(f"{var_tainted} is tainted: {is_tainted(var_tainted, contract)}")
assert is_tainted(var_tainted, contract)
print(f"{var_not_tainted} is tainted: {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);
}
}
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]
# Obtain the target function
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.

@ -528,12 +528,20 @@ def parse_args(
group_misc.add_argument(
"--triage-mode",
help="Run triage mode (save results in slither.db.json)",
help="Run triage mode (save results in triage database)",
action="store_true",
dest="triage_mode",
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(
"--config-file",
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.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.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
@ -57,6 +58,7 @@ class SlitherCompilationUnit(Context):
self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = []
self._events_top_level: List[EventTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = []
@ -234,6 +236,10 @@ class SlitherCompilationUnit(Context):
def enums_top_level(self) -> List[EnumTopLevel]:
return self._enums_top_level
@property
def events_top_level(self) -> List[EventTopLevel]:
return self._events_top_level
@property
def variables_top_level(self) -> List[TopLevelVariable]:
return self._variables_top_level
@ -296,10 +302,7 @@ class SlitherCompilationUnit(Context):
slot = 0
offset = 0
for var in contract.state_variables_ordered:
if var.is_constant or var.is_immutable:
continue
for var in contract.stored_state_variables_ordered:
assert var.type
size, new_slot = var.type.storage_size

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

@ -33,7 +33,7 @@ if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
Event,
EventContract,
Modifier,
EnumContract,
StructureContract,
@ -73,7 +73,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {}
self._events: Dict[str, "EventContract"] = {}
# map accessible variable from name -> variable
# do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {}
@ -278,28 +278,28 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def events(self) -> List["Event"]:
def events(self) -> List["EventContract"]:
"""
list(Event): List of the events
"""
return list(self._events.values())
@property
def events_inherited(self) -> List["Event"]:
def events_inherited(self) -> List["EventContract"]:
"""
list(Event): List of the inherited events
"""
return [e for e in self.events if e.contract != self]
@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)
"""
return [e for e in self.events if e.contract == self]
@property
def events_as_dict(self) -> Dict[str, "Event"]:
def events_as_dict(self) -> Dict[str, "EventContract"]:
return self._events
# endregion
@ -436,6 +436,33 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
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
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.variables.event_variable import EventVariable
if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ContractLevel, SourceMapping):
class Event(SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
@ -39,25 +35,9 @@ class Event(ContractLevel, SourceMapping):
name, parameters = self.signature
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
def elems(self) -> List["EventVariable"]:
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:
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
"""
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
if "nonReentrant" in [m.name for m in self.modifiers] or "nonreentrant(lock)" in [
m.name for m in self.modifiers
]:
if reentrancy_modifier in [m.name for m in self.modifiers]:
return False
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:
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
###################################################################################

@ -21,7 +21,8 @@ SOLIDITY_VARIABLES = {
}
SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.basefee": "uint256",
"block.blobbasefee": "uint256",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.prevrandao": "uint256",
@ -44,6 +45,7 @@ SOLIDITY_VARIABLES_COMPOSED = {
}
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"blobhash(uint256)": ["bytes32"],
"gasleft()": ["uint256"],
"assert(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.custom_error_top_level import CustomErrorTopLevel
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.using_for_top_level import UsingForTopLevel
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
self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
@ -54,7 +56,7 @@ class FileScope:
# Name -> type alias
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
@ -74,6 +76,9 @@ class FileScope:
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
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):
self.functions |= new_scope.functions
learn_something = True

@ -269,6 +269,8 @@ class SlitherCore(Context):
self._compute_offsets_from_thing(event)
for enum in compilation_unit.enums_top_level:
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:
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:

@ -93,6 +93,13 @@ class Variable(SourceMapping):
def is_constant(self, is_cst: bool) -> None:
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
def is_reentrant(self) -> bool:
return self._is_reentrant

@ -39,7 +39,9 @@ class IncorrectReturn(AbstractDetector):
IMPACT = DetectorClassification.HIGH
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_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution."

@ -20,7 +20,7 @@ class ReturnInsteadOfLeave(AbstractDetector):
IMPACT = DetectorClassification.HIGH
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_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.core.declarations.function_contract import FunctionContract
from slither.utils.output import Output
from slither.core.cfg.node import NodeType
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]:
results = []
in_assembly = False
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:
if isinstance(ir, Binary) and ir.type in [
BinaryType.LEFT_SHIFT,

@ -59,7 +59,7 @@ Bob calls `kill` and destructs the contract."""
if func.visibility not in ["public", "external"]:
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):
return False

@ -133,7 +133,7 @@ def detect_divide_before_multiply(
results: List[Tuple[FunctionContract, List[Node]]] = []
# 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:
continue

@ -8,6 +8,9 @@ from slither.detectors.abstract_detector import (
from slither.slithir.operations import InternalCall
from slither.core.declarations import SolidityVariableComposed, Contract
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]:
@ -37,6 +40,21 @@ def msg_value_in_loop(
for ir in node.all_slithir_operations():
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)
if isinstance(ir, (InternalCall)):
msg_value_in_loop(ir.function.entry_point, in_loop_counter, visited, results)

@ -36,7 +36,7 @@ class PredeclarationUsageLocal(AbstractDetector):
```solidity
contract C {
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;
if (z % 2 == 0) {

@ -25,7 +25,7 @@ def _is_valid_type(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:
@ -92,7 +92,7 @@ class UnchangedStateVariables:
variables = []
functions = []
variables.append(c.state_variables)
variables.append(c.stored_state_variables)
functions.append(c.all_functions_called)
valid_candidates: Set[StateVariable] = {

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

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

@ -630,6 +630,17 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if 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):
# UserdefinedType
t_type = t.type
@ -1564,6 +1575,18 @@ def convert_to_library_or_top_level(
if 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

@ -4,7 +4,7 @@ from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequenc
from slither.core.declarations import (
Modifier,
Event,
EventContract,
EnumContract,
StructureContract,
Function,
@ -747,12 +747,12 @@ class ContractSolc(CallerContextExpression):
self._contract.events_as_dict.update(father.events_as_dict)
for event_to_parse in self._eventsNotParsed:
event = Event()
event = EventContract()
event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze(self) # type: ignore
event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore
event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as 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
if TYPE_CHECKING:
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventSolc:
@ -16,11 +16,12 @@ class EventSolc:
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
event.set_contract(contract_parser.underlying_contract)
self._parser_contract = contract_parser
self._slither_parser = slither_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
@ -41,18 +42,16 @@ class EventSolc:
@property
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:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(
elem_to_parse["src"], self._parser_contract.underlying_contract.compilation_unit
)
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(contract)
elem_parser.analyze(self._slither_parser)
self._event.elems.append(elem)

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

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

@ -134,6 +134,9 @@ def find_top_level(
if var_name in scope.enums:
return scope.enums[var_name], False
if var_name in scope.events:
return scope.events[var_name], False
for import_directive in scope.imports:
if import_directive.alias == var_name:
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.custom_error_top_level import CustomErrorTopLevel
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.import_directive import Import
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.custom_error import CustomErrorSolc
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.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound
@ -347,6 +349,15 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.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:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
@ -421,19 +432,8 @@ Please rename it, this name is reserved for Slither's internals"""
self._contracts_by_id[contract.id] = contract
self._compilation_unit.contracts.append(contract)
# Update of the inheritance
for contract_parser in self._underlying_contract_to_parser.values():
# remove the first elem in linearizedBaseContracts as it is the contract itself
ancestors = []
fathers = []
father_constructors = []
# try:
# Resolve linearized base contracts.
missing_inheritance = None
for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping:
contract_name = contract_parser.remapping[i]
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
@ -447,35 +447,41 @@ Please rename it, this name is reserved for Slither's internals"""
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
for contract_parser in self._underlying_contract_to_parser.values():
ancestors = []
fathers = []
father_constructors = []
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:]:
if i in contract_parser.remapping:
target = resolve_remapping_and_renaming(contract_parser, i)
ancestors.append(target)
elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i])
else:
missing_inheritance = i
# Resolve immediate base contracts
# Resolve immediate base contracts.
for i in contract_parser.baseContracts:
if i in contract_parser.remapping:
fathers.append(
contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
)
target = resolve_remapping_and_renaming(contract_parser, i)
fathers.append(target)
elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i])
else:
missing_inheritance = i
# Resolve immediate base constructor calls
# Resolve immediate base constructor calls.
for i in contract_parser.baseConstructorContractsCalled:
if i in contract_parser.remapping:
father_constructors.append(
contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
)
target = resolve_remapping_and_renaming(contract_parser, i)
father_constructors.append(target)
elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i])
else:

@ -1,7 +1,7 @@
from slither.core.declarations.solidity_variables import SOLIDITY_FUNCTIONS
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 = [
"STOP",
"ADD",
@ -45,6 +45,7 @@ evm_opcodes = [
"EXTCODECOPY",
"RETURNDATASIZE",
"RETURNDATACOPY",
"MCOPY",
"EXTCODEHASH",
"BLOCKHASH",
"COINBASE",
@ -55,12 +56,17 @@ evm_opcodes = [
"GASLIMIT",
"CHAINID",
"SELFBALANCE",
"BASEFEE",
"BLOBHASH",
"BLOBBASEFEE",
"POP",
"MLOAD",
"MSTORE",
"MSTORE8",
"SLOAD",
"SSTORE",
"TLOAD",
"TSTORE",
"JUMP",
"JUMPI",
"PC",
@ -183,11 +189,16 @@ function_args = {
"mstore8": [2, 0],
"sload": [1, 1],
"sstore": [2, 0],
"tload": [1, 1],
"tstore": [2, 0],
"msize": [1, 1],
"gas": [0, 1],
"address": [0, 1],
"balance": [1, 1],
"selfbalance": [0, 1],
"basefee": [0, 1],
"blobhash": [1, 1],
"blobbasefee": [0, 1],
"caller": [0, 1],
"callvalue": [0, 1],
"calldataload": [1, 1],
@ -199,6 +210,7 @@ function_args = {
"extcodecopy": [4, 0],
"returndatasize": [0, 1],
"returndatacopy": [3, 0],
"mcopy": [3, 0],
"extcodehash": [1, 1],
"create": [3, 1],
"create2": [4, 1],

@ -21,7 +21,10 @@ def parse_args() -> argparse.Namespace:
Parse the underlying 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.")

@ -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.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.variables_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 logging
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 slither import Slither
from slither.tools.mutator.mutators import all_mutators
from slither.utils.colors import yellow, magenta
from .mutators.abstract_mutator import AbstractMutator
from .utils.command_line import output_mutators
from .utils.file_handling import (
transfer_and_delete,
backup_source_file,
get_sol_file_list,
)
logging.basicConfig()
logger = logging.getLogger("Slither")
logger = logging.getLogger("Slither-Mutate")
logger.setLevel(logging.INFO)
###################################################################################
###################################################################################
# region Cli Arguments
@ -24,12 +29,16 @@ logger.setLevel(logging.INFO)
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
Returns: The arguments for the program.
"""
parser = argparse.ArgumentParser(
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(
"--list-mutators",
@ -39,6 +48,51 @@ def parse_args() -> argparse.Namespace:
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
cryticparser.init(parser)
@ -49,8 +103,17 @@ def parse_args() -> argparse.Namespace:
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)]
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
@ -59,7 +122,7 @@ class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
checks = _get_mutators()
checks = _get_mutators(None)
output_mutators(checks)
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()
print(args.codebase)
sl = Slither(args.codebase, **vars(args))
# arguments
test_command: str = args.test_cmd
test_directory: Optional[str] = args.test_dir
paths_to_ignore: Optional[str] = args.ignore_dirs
output_dir: Optional[str] = args.output_dir
timeout: Optional[int] = args.timeout
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"
)
)
for compilation_unit in sl.compilation_units:
for M in _get_mutators():
m = M(compilation_unit)
m.mutate()
logger.info(magenta(f"Finished Mutation Campaign in '{args.codebase}' \n"))
# 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 slither.core.cfg.node import NodeType
from slither.formatters.utils.patches import create_patch
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass
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 MIA(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MIA"
HELP = '"if" construct around statement'
FAULTCLASS = FaultClass.Checking
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
for contract in self.slither.contracts:
for function in contract.functions_declared + list(contract.modifiers_declared):
for function in self.contract.functions_and_modifiers_declared:
for node in function.nodes:
if node.type == NodeType.IF:
# 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 = node.source_mapping.start
stop = start + node.source_mapping.length
old_str = in_file_str[start:stop]
# Replace the expression with true
new_str = "true"
create_patch(result, in_file, start, stop, old_str, new_str)
start = node.expression.source_mapping.start
stop = start + node.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 and false
for value in ["true", "false"]:
new_str = value
create_patch_with_line(
result,
self.in_file,
start,
stop,
old_str,
new_str,
line_no[0],
)
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

@ -1,36 +1,60 @@
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, FaultNature, FaultClass
from slither.tools.mutator.utils.generic_patching import remove_assignement
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.tools.mutator.utils.patch import create_patch_with_line
class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIE"
HELP = "variable initialization using an expression"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
for contract in self.slither.contracts:
# Create fault for state variables declaration
for variable in contract.state_variables_declared:
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):
remove_assignement(variable, contract, result)
for function in contract.functions_declared + list(contract.modifiers_declared):
# 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):
remove_assignement(variable, contract, result)
# 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

@ -1,37 +1,59 @@
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, FaultNature, FaultClass
from slither.tools.mutator.utils.generic_patching import remove_assignement
from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.tools.mutator.utils.patch import create_patch_with_line
class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods
NAME = "MVIV"
HELP = "variable initialization using a value"
FAULTCLASS = FaultClass.Assignement
FAULTNATURE = FaultNature.Missing
def _mutate(self) -> Dict:
result: Dict = {}
variable: Variable
for contract in self.slither.contracts:
# Create fault for state variables declaration
for variable in contract.state_variables_declared:
for 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):
remove_assignement(variable, contract, result)
for function in contract.functions_declared + list(contract.modifiers_declared):
# 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 isinstance(variable.expression, Literal):
remove_assignement(variable, contract, result)
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

@ -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 logging
from enum import Enum
from typing import Optional, Dict
from typing import Optional, Dict, Tuple, List
from slither.core.compilation_unit import SlitherCompilationUnit
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):
pass
class FaultClass(Enum):
Assignement = 0
Checking = 1
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
class AbstractMutator(
metaclass=abc.ABCMeta
): # pylint: disable=too-few-public-methods,too-many-instance-attributes
NAME = ""
HELP = ""
FAULTCLASS = FaultClass.Undefined
FAULTNATURE = FaultNature.Undefined
def __init__(
self, compilation_unit: SlitherCompilationUnit, rate: int = 10, seed: Optional[int] = None
):
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def __init__( # pylint: disable=too-many-arguments
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.slither = compilation_unit.core
self.seed = seed
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:
raise IncorrectMutatorInitialization(
@ -52,16 +61,6 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
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:
raise IncorrectMutatorInitialization(
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"""
return {}
def mutate(self) -> None:
all_patches = self._mutate()
def mutate(self) -> Tuple[int, int, List[int]]:
# call _mutate function from different mutators
(all_patches) = self._mutate()
if "patches" not in all_patches:
logger.debug(f"No patches found by {self.NAME}")
return
logger.debug("No patches found by %s", self.NAME)
return (0, 0, self.dont_mutate_line)
for file in all_patches["patches"]:
original_txt = self.slither.source_code[file].encode("utf8")
patched_txt = original_txt
offset = 0
patches = all_patches["patches"][file]
patches.sort(key=lambda x: x["start"])
if not all(patches[i]["end"] <= patches[i + 1]["end"] for i in range(len(patches) - 1)):
logger.info(f"Impossible to generate patch; patches collisions: {patches}")
continue
logger.info(yellow(f"Mutating {file} with {self.NAME} \n"))
for patch in patches:
patched_txt, offset = apply_patch(patched_txt, patch, offset)
# test the patch
flag = test_patch(
file,
patch,
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}")
print(diff)
# 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
from slither.tools.mutator.mutators.MVIV import MVIV
from slither.tools.mutator.mutators.MVIE import MVIE
from slither.tools.mutator.mutators.MIA import MIA
from slither.tools.mutator.mutators.MVIV import MVIV # severity low
from slither.tools.mutator.mutators.MVIE import MVIE # severity low
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 slither.tools.mutator.mutators.abstract_mutator import AbstractMutator
from slither.utils.myprettytable import MyPrettyTable
@ -9,15 +8,13 @@ def output_mutators(mutators_classes: List[Type[AbstractMutator]]) -> None:
for detector in mutators_classes:
argument = detector.NAME
help_info = detector.HELP
fault_class = detector.FAULTCLASS.name
fault_nature = detector.FAULTNATURE.name
mutators_list.append((argument, help_info, fault_class, fault_nature))
table = MyPrettyTable(["Num", "Name", "What it Does", "Fault Class", "Fault Nature"])
mutators_list.append((argument, help_info))
table = MyPrettyTable(["Num", "Name", "What it Does"])
# Sort by class, nature, name
mutators_list = sorted(mutators_list, key=lambda element: (element[2], element[3], element[0]))
# Sort by class
mutators_list = sorted(mutators_list, key=lambda element: (element[0]))
idx = 1
for (argument, help_info, fault_class, fault_nature) in mutators_list:
table.add_row([str(idx), argument, help_info, fault_class, fault_nature])
for argument, help_info in mutators_list:
table.add_row([str(idx), argument, help_info])
idx = idx + 1
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.
"""
parser = argparse.ArgumentParser(
description="Demo",
usage="slither-demo filename",
description="Generates code properties (e.g., invariants) that can be tested with unit tests or Echidna, entirely automatically.",
usage="slither-prop filename",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
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.")

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

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

@ -70,6 +70,7 @@ defaults_flag_in_config = {
"no_fail": False,
"sarif_input": "export.sarif",
"sarif_triage": "export.sarif.sarifexplorer",
"triage_database": "slither.db.json",
**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):
return f"local_solc_variable({ntype(var.type)},{var.location})"
if isinstance(var, variables.StateVariable):
if not (var.is_constant or var.is_immutable):
if var.is_stored:
try:
slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var)
except KeyError:

@ -1,7 +1,10 @@
from typing import List, Dict, Union
from prettytable import PrettyTable
from prettytable.colortable import ColorTable, Themes
from slither.utils.colors import Colors
class MyPrettyTable:
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:
self._rows.append(row)
def to_pretty_table(self) -> ColorTable:
def to_pretty_table(self) -> PrettyTable:
if Colors.COLORIZATION_ENABLED:
table = ColorTable(self._field_names, theme=Themes.OCEAN)
else:
table = PrettyTable(self._field_names)
for row in self._rows:
table.add_row(row)
if len(self._options["set_alignment"]):

@ -81,12 +81,8 @@ def compare(
tainted-contracts: list[TaintedExternalContract]
"""
order_vars1 = [
v for v in v1.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars2 = [
v for v in v2.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars1 = v1.stored_state_variables_ordered
order_vars2 = v2.stored_state_variables_ordered
func_sigs1 = [function.solidity_signature for function in v1.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 (
isinstance(target, StateVariable)
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
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
"""
results = []
order_vars1 = [
v for v in v1.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars2 = [
v for v in v2.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars1 = v1.stored_state_variables_ordered
order_vars2 = v2.stored_state_variables_ordered
if len(order_vars2) < len(order_vars1):
for variable in order_vars1:
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)
if isinstance(delegate, StateVariable):
if not delegate.is_constant and not delegate.is_immutable:
if delegate.is_stored:
srs = SlitherReadStorage([proxy], 20)
return srs.get_storage_slot(delegate, proxy)
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.declarations.function import FunctionVyper
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
@ -478,7 +478,7 @@ class ContractVyper: # pylint: disable=too-many-instance-attributes
def parse_events(self) -> None:
for event_to_parse in self._eventsNotParsed:
event = Event()
event = EventContract()
event.set_contract(self._contract)
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]
parameters = function.parameters
assert (parameters[0].name == 'param1')
assert (parameters[1].name == '')
assert (parameters[2].name == 'param3')
assert parameters[0].name == "param1"
assert parameters[1].name == ""
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

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

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

@ -1,8 +1,11 @@
contract C {
function f() internal returns (uint a) {
function f() internal returns (uint a, uint b) {
assembly {
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)
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