diff --git a/.github/workflows/IR.yml b/.github/workflows/IR.yml index 434cef75b..891de2bfb 100644 --- a/.github/workflows/IR.yml +++ b/.github/workflows/IR.yml @@ -34,9 +34,14 @@ jobs: - name: Install dependencies run: | pip install ".[dev]" - solc-select install all - solc-select use 0.8.11 + solc-select install 0.5.0 + solc-select use 0.8.11 --always-install + + - name: Install old solc + if: matrix.os == 'ubuntu-latest' + run: solc-select install 0.4.0 + - name: Test with pytest run: | - pytest tests/test_ssa_generation.py + pytest tests/test_ssa_generation.py \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ae5326a8..9913e487d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: # "embark", "erc", # "etherlime", - # "etherscan" + "etherscan", "find_paths", "flat", "kspec", @@ -55,8 +55,9 @@ jobs: - name: Install dependencies run: | pip install ".[dev]" - solc-select install all - solc-select use 0.5.1 + solc-select use 0.4.25 --always-install + solc-select use 0.8.0 --always-install + solc-select use 0.5.1 --always-install pip install typing_extensions==4.1.1 pip install importlib_metadata==4.8.3 diff --git a/.github/workflows/detectors.yml b/.github/workflows/detectors.yml index 8f3b45d15..15aa8a5dd 100644 --- a/.github/workflows/detectors.yml +++ b/.github/workflows/detectors.yml @@ -35,8 +35,7 @@ jobs: run: | pip install ".[dev]" - solc-select install all - solc-select use 0.7.3 + solc-select use 0.7.3 --always-install - name: Test with pytest run: | pytest tests/test_detectors.py diff --git a/.github/workflows/doctor.yml b/.github/workflows/doctor.yml new file mode 100644 index 000000000..b6124216a --- /dev/null +++ b/.github/workflows/doctor.yml @@ -0,0 +1,87 @@ +--- +name: CI + +defaults: + run: + shell: bash + +on: + workflow_dispatch: + pull_request: + paths: + - 'slither/tools/doctor/**' + - '.github/workflows/doctor.yml' + +jobs: + slither-doctor: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-2022"] + python: ["3.8", "3.9", "3.10", "3.11"] + exclude: + # strange failure + - os: windows-2022 + python: 3.8 + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Try system-wide Slither + run: | + echo "::group::Install slither" + pip3 install . + echo "::endgroup::" + + # escape cwd so python doesn't pick up local module + cd / + + echo "::group::Via module" + python3 -m slither.tools.doctor . + echo "::endgroup::" + + echo "::group::Via binary" + slither-doctor . + echo "::endgroup::" + + - name: Try user Slither + run: | + echo "::group::Install slither" + pip3 install --user . + echo "::endgroup::" + + # escape cwd so python doesn't pick up local module + cd / + + echo "::group::Via module" + python3 -m slither.tools.doctor . + echo "::endgroup::" + + echo "::group::Via binary" + slither-doctor . + echo "::endgroup::" + + - name: Try venv Slither + run: | + echo "::group::Install slither" + python3 -m venv venv + source venv/bin/activate || source venv/Scripts/activate + hash -r + pip3 install . + echo "::endgroup::" + + # escape cwd so python doesn't pick up local module + cd / + + echo "::group::Via module" + python3 -m slither.tools.doctor . + echo "::endgroup::" + + echo "::group::Via binary" + slither-doctor . + echo "::endgroup::" diff --git a/.github/workflows/features.yml b/.github/workflows/features.yml index 49db14793..5007fd7bf 100644 --- a/.github/workflows/features.yml +++ b/.github/workflows/features.yml @@ -35,8 +35,7 @@ jobs: run: | pip install ".[dev]" - solc-select install all - solc-select use 0.8.0 + solc-select use 0.8.0 --always-install cd tests/test_node_modules/ npm install hardhat diff --git a/.github/workflows/pip-audit.yml b/.github/workflows/pip-audit.yml index d0fcb419c..636093071 100644 --- a/.github/workflows/pip-audit.yml +++ b/.github/workflows/pip-audit.yml @@ -26,7 +26,7 @@ jobs: python -m venv /tmp/pip-audit-env source /tmp/pip-audit-env/bin/activate - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools wheel python -m pip install . - name: Run pip-audit diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..c92f0d79d --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,6 @@ +* @montyly @0xalpharush @smonicas +/slither/tools/read_storage/ @0xalpharush +/slither/tools/doctor/ @elopez +/slither/slithir/ @montyly +/slither/analyses/ @montyly +/.github/workflows/ @elopez diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a30595ec..8568ef709 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,10 @@ For each new detector, at least one regression tests must be present. - If updating an existing detector, identify the respective json artifacts and then delete them, or run `python ./tests/test_detectors.py --overwrite` instead. - Run `pytest ./tests/test_detectors.py` and check that everything worked. -To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html` +To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`. +To run tests for a specific detector, run `pytest tests/test_detectors.py -k ReentrancyReadBeforeWritten` (the detector's class name is the argument). +To run tests for a specific version, run `pytest tests/test_detectors.py -k 0.7.6`. +The IDs of tests can be inspected using `pytest tests/test_detectors.py --collect-only`. ### Parser tests - Create a test in `tests/ast-parsing` @@ -73,6 +76,10 @@ To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/d - Run `pytest ./tests/test_ast_parsing.py` and check that everything worked. To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html` +To run tests for a specific test case, run `pytest tests/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument). +To run tests for a specific version, run `pytest tests/test_ast_parsing.py -k 0.8.12`. +To run tests for a specific compiler json format, run `pytest tests/test_ast_parsing.py -k legacy` (can be legacy or compact). +The IDs of tests can be inspected using ``pytest tests/test_ast_parsing.py --collect-only`. ### Synchronization with crytic-compile By default, `slither` follows either the latest version of crytic-compile in pip, or `crytic-compile@master` (look for dependencies in [`setup.py`](./setup.py). If crytic-compile development comes with breaking changes, the process to update `slither` is: diff --git a/Dockerfile b/Dockerfile index 71bb9f57f..d0a7d67be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM ubuntu:jammy AS python-wheels RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ gcc \ + git \ python3-dev \ python3-pip \ && rm -rf /var/lib/apt/lists/* @@ -9,7 +10,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins COPY . /slither RUN cd /slither && \ - echo pip3 install --no-cache-dir --upgrade pip && \ + pip3 install --no-cache-dir --upgrade pip && \ pip3 wheel -w /wheels . solc-select pip setuptools wheel @@ -44,7 +45,7 @@ ENV PATH="/home/slither/.local/bin:${PATH}" # no-index ensures we install the freshly-built wheels RUN --mount=type=bind,target=/mnt,source=/wheels,from=python-wheels \ - pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt pip slither-analyzer solc-select + pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt --no-deps /mnt/*.whl RUN solc-select install 0.4.25 && solc-select use 0.4.25 diff --git a/scripts/ci_test_etherscan.sh b/scripts/ci_test_etherscan.sh index c8e69958c..694690691 100755 --- a/scripts/ci_test_etherscan.sh +++ b/scripts/ci_test_etherscan.sh @@ -5,15 +5,24 @@ mkdir etherscan cd etherscan || exit 255 -if ! slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN"; then - echo "Etherscan test failed" +echo "::group::Etherscan mainnet" +if ! slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN" --no-fail-pedantic; then + echo "Etherscan mainnet test failed" exit 1 fi +echo "::endgroup::" -if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN"; then - echo "Etherscan test failed" +# Perform a small sleep when API key is not available (e.g. on PR CI from external contributor) +if [ "$GITHUB_ETHERSCAN" = "" ]; then + sleep $(( ( RANDOM % 5 ) + 1 ))s +fi + +echo "::group::Etherscan rinkeby" +if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN" --no-fail-pedantic; then + echo "Etherscan rinkeby test failed" exit 1 fi +echo "::endgroup::" exit 0 diff --git a/setup.py b/setup.py index 03fe64c42..cc125011e 100644 --- a/setup.py +++ b/setup.py @@ -12,10 +12,11 @@ setup( packages=find_packages(), python_requires=">=3.8", install_requires=[ + "packaging", "prettytable>=0.7.2", "pycryptodome>=3.4.6", - "crytic-compile>=0.2.4", - # "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile", + # "crytic-compile>=0.2.4", + "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile", ], extras_require={ "dev": [ @@ -46,6 +47,7 @@ setup( "slither-mutate = slither.tools.mutator.__main__:main", "slither-read-storage = slither.tools.read_storage.__main__:main", "slither-doctor = slither.tools.doctor.__main__:main", + "slither-documentation = slither.tools.documentation.__main__:main", ] }, ) diff --git a/slither/__main__.py b/slither/__main__.py index 75707af06..9d611532e 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -24,6 +24,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi from slither.printers import all_printers from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither +from slither.utils import codex from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, ZIP_TYPES_ACCEPTED from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, set_colorization_enabled @@ -314,7 +315,6 @@ def parse_args( "Checklist (consider using https://github.com/crytic/slither-action)" ) group_misc = parser.add_argument_group("Additional options") - group_codex = parser.add_argument_group("Codex (https://beta.openai.com/docs/guides/code)") group_detector.add_argument( "--detect", @@ -555,47 +555,14 @@ def parse_args( default=False, ) - group_codex.add_argument( - "--codex", - help="Enable codex (require an OpenAI API Key)", - action="store_true", - default=defaults_flag_in_config["codex"], - ) - - group_codex.add_argument( - "--codex-log", - help="Log codex queries (in crytic_export/codex/)", + group_misc.add_argument( + "--no-fail", + help="Do not fail in case of parsing (echidna mode only)", action="store_true", - default=False, - ) - - group_codex.add_argument( - "--codex-contracts", - help="Comma separated list of contracts to submit to OpenAI Codex", - action="store", - default=defaults_flag_in_config["codex_contracts"], + default=defaults_flag_in_config["no_fail"], ) - group_codex.add_argument( - "--codex-model", - help="Name of the Codex model to use (affects pricing). Defaults to 'text-davinci-003'", - action="store", - default=defaults_flag_in_config["codex_model"], - ) - - group_codex.add_argument( - "--codex-temperature", - help="Temperature to use with Codex. Lower number indicates a more precise answer while higher numbers return more creative answers. Defaults to 0", - action="store", - default=defaults_flag_in_config["codex_temperature"], - ) - - group_codex.add_argument( - "--codex-max-tokens", - help="Maximum amount of tokens to use on the response. This number plus the size of the prompt can be no larger than the limit (4097 for text-davinci-003)", - action="store", - default=defaults_flag_in_config["codex_max_tokens"], - ) + codex.init_parser(parser) # debugger command parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False) diff --git a/slither/core/children/child_function.py b/slither/core/children/child_function.py index cb4f0109f..5367320ca 100644 --- a/slither/core/children/child_function.py +++ b/slither/core/children/child_function.py @@ -5,11 +5,11 @@ if TYPE_CHECKING: class ChildFunction: - def __init__(self): + def __init__(self) -> None: super().__init__() self._function = None - def set_function(self, function: "Function"): + def set_function(self, function: "Function") -> None: self._function = function @property diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index a2568a5de..d97b7fbf5 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -16,6 +16,7 @@ from slither.core.declarations import ( from slither.core.declarations.custom_error import CustomError from slither.core.declarations.enum_top_level import EnumTopLevel 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 from slither.core.scope.scope import FileScope from slither.core.variables.state_variable import StateVariable @@ -41,6 +42,7 @@ class SlitherCompilationUnit(Context): self._enums_top_level: List[EnumTopLevel] = [] self._variables_top_level: List[TopLevelVariable] = [] self._functions_top_level: List[FunctionTopLevel] = [] + self._using_for_top_level: List[UsingForTopLevel] = [] self._pragma_directives: List[Pragma] = [] self._import_directives: List[Import] = [] self._custom_errors: List[CustomError] = [] @@ -205,6 +207,10 @@ class SlitherCompilationUnit(Context): def functions_top_level(self) -> List[FunctionTopLevel]: return self._functions_top_level + @property + def using_for_top_level(self) -> List[UsingForTopLevel]: + return self._using_for_top_level + @property def custom_errors(self) -> List[CustomError]: return self._custom_errors diff --git a/slither/core/declarations/__init__.py b/slither/core/declarations/__init__.py index 3b619c1d1..f891ad621 100644 --- a/slither/core/declarations/__init__.py +++ b/slither/core/declarations/__init__.py @@ -12,5 +12,8 @@ from .solidity_variables import ( ) from .structure import Structure from .enum_contract import EnumContract +from .enum_top_level import EnumTopLevel from .structure_contract import StructureContract +from .structure_top_level import StructureTopLevel from .function_contract import FunctionContract +from .function_top_level import FunctionTopLevel diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index a90e2591e..d1feebb05 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -70,6 +70,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods self._enums: Dict[str, "EnumContract"] = {} self._structures: Dict[str, "StructureContract"] = {} self._events: Dict[str, "Event"] = {} + # map accessible variable from name -> variable + # do not contain private variables inherited from contract self._variables: Dict[str, "StateVariable"] = {} self._variables_ordered: List["StateVariable"] = [] self._modifiers: Dict[str, "Modifier"] = {} @@ -79,6 +81,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods # The only str is "*" self._using_for: Dict[Union[str, Type], List[Type]] = {} + self._using_for_complete: Dict[Union[str, Type], List[Type]] = None self._kind: Optional[str] = None self._is_interface: bool = False self._is_library: bool = False @@ -266,6 +269,27 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods def using_for(self) -> Dict[Union[str, Type], List[Type]]: return self._using_for + @property + def using_for_complete(self) -> Dict[Union[str, Type], List[Type]]: + """ + Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive + """ + + def _merge_using_for(uf1, uf2): + result = {**uf1, **uf2} + for key, value in result.items(): + if key in uf1 and key in uf2: + result[key] = value + uf1[key] + return result + + if self._using_for_complete is None: + result = self.using_for + top_level_using_for = self.file_scope.using_for_directives + for uftl in top_level_using_for: + result = _merge_using_for(result, uftl.using_for) + self._using_for_complete = result + return self._using_for_complete + # endregion ################################################################################### ################################################################################### @@ -308,7 +332,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods @property def variables(self) -> List["StateVariable"]: """ - list(StateVariable): List of the state variables. Alias to self.state_variables + Returns all the accessible variables (do not include private variable from inherited contract) + + list(StateVariable): List of the state variables. Alias to self.state_variables. """ return list(self.state_variables) @@ -319,6 +345,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods @property def state_variables(self) -> List["StateVariable"]: """ + Returns all the accessible variables (do not include private variable from inherited contract). + Use state_variables_ordered for all the variables following the storage order + list(StateVariable): List of the state variables. """ return list(self._variables.values()) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index a4624feec..2fdea7210 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -220,6 +220,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu self._id: Optional[str] = None + # To be improved with a parsing of the documentation + self.has_documentation: bool = False + ################################################################################### ################################################################################### # region General properties diff --git a/slither/core/declarations/using_for_top_level.py b/slither/core/declarations/using_for_top_level.py new file mode 100644 index 000000000..a1b43e1c1 --- /dev/null +++ b/slither/core/declarations/using_for_top_level.py @@ -0,0 +1,18 @@ +from typing import TYPE_CHECKING, List, Dict, Union + +from slither.core.solidity_types.type import Type +from slither.core.declarations.top_level import TopLevel + +if TYPE_CHECKING: + from slither.core.scope.scope import FileScope + + +class UsingForTopLevel(TopLevel): + def __init__(self, scope: "FileScope"): + super().__init__() + self._using_for: Dict[Union[str, Type], List[Type]] = {} + self.file_scope: "FileScope" = scope + + @property + def using_for(self) -> Dict[Type, List[Type]]: + return self._using_for diff --git a/slither/core/expressions/identifier.py b/slither/core/expressions/identifier.py index ab40472a4..0b10c5615 100644 --- a/slither/core/expressions/identifier.py +++ b/slither/core/expressions/identifier.py @@ -7,7 +7,7 @@ if TYPE_CHECKING: class Identifier(ExpressionTyped): - def __init__(self, value): + def __init__(self, value) -> None: super().__init__() self._value: "Variable" = value @@ -15,5 +15,5 @@ class Identifier(ExpressionTyped): def value(self) -> "Variable": return self._value - def __str__(self): + def __str__(self) -> str: return str(self._value) diff --git a/slither/core/expressions/literal.py b/slither/core/expressions/literal.py index 87090b93f..2eaeb715d 100644 --- a/slither/core/expressions/literal.py +++ b/slither/core/expressions/literal.py @@ -10,16 +10,25 @@ if TYPE_CHECKING: class Literal(Expression): - def __init__(self, value, custom_type, subdenomination=None): + def __init__( + self, value: Union[int, str], custom_type: "Type", subdenomination: Optional[str] = None + ): super().__init__() - self._value: Union[int, str] = value + self._value = value self._type = custom_type - self._subdenomination: Optional[str] = subdenomination + self._subdenomination = subdenomination @property def value(self) -> Union[int, str]: return self._value + @property + def converted_value(self) -> Union[int, str]: + """Return the value of the literal, accounting for subdenomination e.g. ether""" + if self.subdenomination: + return convert_subdenomination(self._value, self.subdenomination) + return self._value + @property def type(self) -> "Type": return self._type @@ -28,9 +37,9 @@ class Literal(Expression): def subdenomination(self) -> Optional[str]: return self._subdenomination - def __str__(self): + def __str__(self) -> str: if self.subdenomination: - return str(convert_subdenomination(self._value, self.subdenomination)) + return str(self.converted_value) if self.type in Int + Uint + Fixed + Ufixed + ["address"]: return str(convert_string_to_int(self._value)) @@ -38,7 +47,7 @@ class Literal(Expression): # be sure to handle any character return str(self._value) - def __eq__(self, other): + def __eq__(self, other) -> bool: if not isinstance(other, Literal): return False return (self.value, self.subdenomination) == (other.value, other.subdenomination) diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index c6d18556e..1eb344c2b 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -1,10 +1,14 @@ -from typing import List, Any, Dict, Optional, Union, Set +from typing import List, Any, Dict, Optional, Union, Set, TypeVar, Callable + +from crytic_compile import CompilationUnit +from crytic_compile.source_unit import SourceUnit 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.function_top_level import FunctionTopLevel +from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.solidity_types import TypeAlias from slither.core.variables.top_level_variable import TopLevelVariable @@ -35,6 +39,7 @@ class FileScope: # Because we parse the function signature later on # So we simplify the logic and have the scope fields all populated self.functions: Set[FunctionTopLevel] = set() + self.using_for_directives: Set[UsingForTopLevel] = set() self.imports: Set[Import] = set() self.pragmas: Set[Pragma] = set() self.structures: Dict[str, StructureTopLevel] = {} @@ -72,6 +77,9 @@ class FileScope: if not new_scope.functions.issubset(self.functions): self.functions |= new_scope.functions learn_something = True + if not new_scope.using_for_directives.issubset(self.using_for_directives): + self.using_for_directives |= new_scope.using_for_directives + learn_something = True if not new_scope.imports.issubset(self.imports): self.imports |= new_scope.imports learn_something = True @@ -98,6 +106,117 @@ class FileScope: return self.contracts.get(name.name, None) return self.contracts.get(name, None) + AbstractReturnType = TypeVar("AbstractReturnType") + + def _generic_source_unit_getter( + self, + crytic_compile_compilation_unit: CompilationUnit, + name: str, + getter: Callable[[SourceUnit], Dict[str, AbstractReturnType]], + ) -> Optional[AbstractReturnType]: + + assert self.filename in crytic_compile_compilation_unit.source_units + + source_unit = crytic_compile_compilation_unit.source_unit(self.filename) + + if name in getter(source_unit): + return getter(source_unit)[name] + + for scope in self.accessible_scopes: + source_unit = crytic_compile_compilation_unit.source_unit(scope.filename) + if name in getter(source_unit): + return getter(source_unit)[name] + + return None + + def bytecode_init( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[str]: + """ + Return the init bytecode + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, str]] = lambda x: x.bytecodes_init + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def bytecode_runtime( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[str]: + """ + Return the runtime bytecode + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, str]] = lambda x: x.bytecodes_runtime + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def srcmap_init( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[List[str]]: + """ + Return the init scrmap + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.srcmaps_init + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def srcmap_runtime( + self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str + ) -> Optional[List[str]]: + """ + Return the runtime srcmap + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.srcmaps_runtime + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + + def abi(self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str) -> Any: + """ + Return the abi + + Args: + crytic_compile_compilation_unit: + contract_name: + + Returns: + + """ + getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.abis + return self._generic_source_unit_getter( + crytic_compile_compilation_unit, contract_name, getter + ) + # region Built in definitions ################################################################################### ################################################################################### diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 691699067..66b8fc430 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -92,6 +92,10 @@ class SlitherCore(Context): # But we allow to alter this (ex: file.sol:1) for vscode integration self.line_prefix: str = "#" + # Use by the echidna printer + # If true, partial analysis is allowed + self.no_fail = False + @property def compilation_units(self) -> List[SlitherCompilationUnit]: return list(self._compilation_units) diff --git a/slither/core/variables/local_variable.py b/slither/core/variables/local_variable.py index 5eb641fb4..7b7b4f8bc 100644 --- a/slither/core/variables/local_variable.py +++ b/slither/core/variables/local_variable.py @@ -11,11 +11,11 @@ from slither.core.declarations.structure import Structure class LocalVariable(ChildFunction, Variable): - def __init__(self): + def __init__(self) -> None: super().__init__() self._location: Optional[str] = None - def set_location(self, loc: str): + def set_location(self, loc: str) -> None: self._location = loc @property diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index fb2e3c731..1cab317ad 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -19,7 +19,8 @@ from .reentrancy.reentrancy_eth import ReentrancyEth from .reentrancy.reentrancy_no_gas import ReentrancyNoGas from .reentrancy.reentrancy_events import ReentrancyEvent from .variables.unused_state_variables import UnusedStateVars -from .variables.possible_const_state_variables import ConstCandidateStateVars +from .variables.could_be_constant import CouldBeConstant +from .variables.could_be_immutable import CouldBeImmutable from .statements.tx_origin import TxOrigin from .statements.assembly import Assembly from .operations.low_level_calls import LowLevelCalls diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index fe68051c2..0c77b69ca 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -32,7 +32,7 @@ class ConstantPragma(AbstractDetector): info = ["Different versions of Solidity are used:\n"] info += [f"\t- Version used: {[str(v) for v in versions]}\n"] - for p in pragma: + for p in sorted(pragma, key=lambda x: x.version): info += ["\t- ", p, "\n"] res = self.generate_result(info) diff --git a/slither/detectors/variables/could_be_constant.py b/slither/detectors/variables/could_be_constant.py new file mode 100644 index 000000000..0a294dd8d --- /dev/null +++ b/slither/detectors/variables/could_be_constant.py @@ -0,0 +1,45 @@ +from typing import List, Dict +from slither.utils.output import Output +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.formatters.variables.unchanged_state_variables import custom_format +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from .unchanged_state_variables import UnchangedStateVariables + + +class CouldBeConstant(AbstractDetector): + """ + State variables that could be declared as constant. + Not all types for constants are implemented in Solidity as of 0.4.25. + The only supported types are value types and strings (ElementaryType). + Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables + """ + + ARGUMENT = "constable-states" + HELP = "State variables that could be declared constant" + IMPACT = DetectorClassification.OPTIMIZATION + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant" + + WIKI_TITLE = "State variables that could be declared constant" + WIKI_DESCRIPTION = "State variables that are not updated following deployment should be declared constant to save gas." + WIKI_RECOMMENDATION = "Add the `constant` attribute to state variables that never change." + + def _detect(self) -> List[Output]: + """Detect state variables that could be constant""" + results = {} + + unchanged_state_variables = UnchangedStateVariables(self.compilation_unit) + unchanged_state_variables.detect() + + for variable in unchanged_state_variables.constant_candidates: + results[variable.canonical_name] = self.generate_result( + [variable, " should be constant \n"] + ) + + # Order by canonical name for deterministic results + return [results[k] for k in sorted(results)] + + @staticmethod + def _format(compilation_unit: SlitherCompilationUnit, result: Dict) -> None: + custom_format(compilation_unit, result, "constant") diff --git a/slither/detectors/variables/could_be_immutable.py b/slither/detectors/variables/could_be_immutable.py new file mode 100644 index 000000000..a9ecb60cd --- /dev/null +++ b/slither/detectors/variables/could_be_immutable.py @@ -0,0 +1,44 @@ +from typing import List, Dict +from slither.utils.output import Output +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.formatters.variables.unchanged_state_variables import custom_format +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from .unchanged_state_variables import UnchangedStateVariables + + +class CouldBeImmutable(AbstractDetector): + """ + State variables that could be declared immutable. + # Immutable attribute available in Solidity 0.6.5 and above + # https://blog.soliditylang.org/2020/04/06/solidity-0.6.5-release-announcement/ + """ + + # VULNERABLE_SOLC_VERSIONS = + ARGUMENT = "immutable-states" + HELP = "State variables that could be declared immutable" + IMPACT = DetectorClassification.OPTIMIZATION + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable" + + WIKI_TITLE = "State variables that could be declared immutable" + WIKI_DESCRIPTION = "State variables that are not updated following deployment should be declared immutable to save gas." + WIKI_RECOMMENDATION = "Add the `immutable` attribute to state variables that never change or are set only in the constructor." + + def _detect(self) -> List[Output]: + """Detect state variables that could be immutable""" + results = {} + unchanged_state_variables = UnchangedStateVariables(self.compilation_unit) + unchanged_state_variables.detect() + + for variable in unchanged_state_variables.immutable_candidates: + results[variable.canonical_name] = self.generate_result( + [variable, " should be immutable \n"] + ) + + # Order by canonical name for deterministic results + return [results[k] for k in sorted(results)] + + @staticmethod + def _format(compilation_unit: SlitherCompilationUnit, result: Dict) -> None: + custom_format(compilation_unit, result, "immutable") diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py deleted file mode 100644 index e5b35bb79..000000000 --- a/slither/detectors/variables/possible_const_state_variables.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Module detecting state variables that could be declared as constant -""" -from typing import Set, List, Dict - -from slither.core.compilation_unit import SlitherCompilationUnit -from slither.core.solidity_types.elementary_type import ElementaryType -from slither.core.solidity_types.user_defined_type import UserDefinedType -from slither.core.variables.variable import Variable -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.utils.output import Output -from slither.visitors.expression.export_values import ExportValues -from slither.core.declarations import Contract, Function -from slither.core.declarations.solidity_variables import SolidityFunction -from slither.core.variables.state_variable import StateVariable -from slither.formatters.variables.possible_const_state_variables import custom_format - - -def _is_valid_type(v: StateVariable) -> bool: - t = v.type - if isinstance(t, ElementaryType): - return True - if isinstance(t, UserDefinedType) and isinstance(t.type, Contract): - return True - return False - - -def _valid_candidate(v: StateVariable) -> bool: - return _is_valid_type(v) and not (v.is_constant or v.is_immutable) - - -def _is_constant_var(v: Variable) -> bool: - if isinstance(v, StateVariable): - return v.is_constant - return False - - -class ConstCandidateStateVars(AbstractDetector): - """ - State variables that could be declared as constant detector. - Not all types for constants are implemented in Solidity as of 0.4.25. - The only supported types are value types and strings (ElementaryType). - Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables - """ - - ARGUMENT = "constable-states" - HELP = "State variables that could be declared constant" - IMPACT = DetectorClassification.OPTIMIZATION - CONFIDENCE = DetectorClassification.HIGH - - WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant" - - WIKI_TITLE = "State variables that could be declared constant" - WIKI_DESCRIPTION = "Constant state variables should be declared constant to save gas." - WIKI_RECOMMENDATION = "Add the `constant` attributes to state variables that never change." - - # https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables - valid_solidity_function = [ - SolidityFunction("keccak256()"), - SolidityFunction("keccak256(bytes)"), - SolidityFunction("sha256()"), - SolidityFunction("sha256(bytes)"), - SolidityFunction("ripemd160()"), - SolidityFunction("ripemd160(bytes)"), - SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"), - SolidityFunction("addmod(uint256,uint256,uint256)"), - SolidityFunction("mulmod(uint256,uint256,uint256)"), - ] - - def _constant_initial_expression(self, v: Variable) -> bool: - if not v.expression: - return True - - export = ExportValues(v.expression) - values = export.result() - if not values: - return True - if all((val in self.valid_solidity_function or _is_constant_var(val) for val in values)): - return True - return False - - def _detect(self) -> List[Output]: - """Detect state variables that could be const""" - results = [] - - all_variables_l = [c.state_variables for c in self.compilation_unit.contracts] - all_variables: Set[StateVariable] = { - item for sublist in all_variables_l for item in sublist - } - all_non_constant_elementary_variables = {v for v in all_variables if _valid_candidate(v)} - - all_functions_nested = [c.all_functions_called for c in self.compilation_unit.contracts] - all_functions = list( - { - item1 - for sublist in all_functions_nested - for item1 in sublist - if isinstance(item1, Function) - } - ) - - all_variables_written = [ - f.state_variables_written for f in all_functions if not f.is_constructor_variables - ] - all_variables_written = {item for sublist in all_variables_written for item in sublist} - - constable_variables: List[Variable] = [ - v - for v in all_non_constant_elementary_variables - if (v not in all_variables_written) and self._constant_initial_expression(v) - ] - # Order for deterministic results - constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name) - - # Create a result for each finding - for v in constable_variables: - info = [v, " should be constant\n"] - json = self.generate_result(info) - results.append(json) - - return results - - @staticmethod - def _format(compilation_unit: SlitherCompilationUnit, result: Dict) -> None: - custom_format(compilation_unit, result) diff --git a/slither/detectors/variables/unchanged_state_variables.py b/slither/detectors/variables/unchanged_state_variables.py new file mode 100644 index 000000000..0dccb6d1c --- /dev/null +++ b/slither/detectors/variables/unchanged_state_variables.py @@ -0,0 +1,125 @@ +""" +Module detecting state variables that could be declared as constant +""" +from typing import Set, List +from packaging import version +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.variables.variable import Variable + +from slither.visitors.expression.export_values import ExportValues +from slither.core.declarations import Contract, Function +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.variables.state_variable import StateVariable +from slither.core.expressions import CallExpression, NewContract + + +def _is_valid_type(v: StateVariable) -> bool: + t = v.type + if isinstance(t, ElementaryType): + return True + if isinstance(t, UserDefinedType) and isinstance(t.type, Contract): + return True + return False + + +def _valid_candidate(v: StateVariable) -> bool: + return _is_valid_type(v) and not (v.is_constant or v.is_immutable) + + +def _is_constant_var(v: Variable) -> bool: + if isinstance(v, StateVariable): + return v.is_constant + return False + + +# https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables +valid_solidity_function = [ + SolidityFunction("keccak256()"), + SolidityFunction("keccak256(bytes)"), + SolidityFunction("sha256()"), + SolidityFunction("sha256(bytes)"), + SolidityFunction("ripemd160()"), + SolidityFunction("ripemd160(bytes)"), + SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"), + SolidityFunction("addmod(uint256,uint256,uint256)"), + SolidityFunction("mulmod(uint256,uint256,uint256)"), +] + + +def _constant_initial_expression(v: Variable) -> bool: + if not v.expression: + return True + + # B b = new B(); b cannot be constant, so filter out and recommend it be immutable + if isinstance(v.expression, CallExpression) and isinstance(v.expression.called, NewContract): + return False + + export = ExportValues(v.expression) + values = export.result() + if not values: + return True + + return all((val in valid_solidity_function or _is_constant_var(val) for val in values)) + + +class UnchangedStateVariables: + """ + Find state variables that could be declared as constant or immutable (not written after deployment). + """ + + def __init__(self, compilation_unit: SlitherCompilationUnit): + self.compilation_unit = compilation_unit + self._constant_candidates: List[StateVariable] = [] + self._immutable_candidates: List[StateVariable] = [] + + @property + def immutable_candidates(self) -> List[StateVariable]: + """Return the immutable candidates""" + return self._immutable_candidates + + @property + def constant_candidates(self) -> List[StateVariable]: + """Return the constant candidates""" + return self._constant_candidates + + def detect(self): + """Detect state variables that could be constant or immutable""" + for c in self.compilation_unit.contracts_derived: + variables = [] + functions = [] + + variables.append(c.state_variables) + functions.append(c.all_functions_called) + + valid_candidates: Set[StateVariable] = { + item for sublist in variables for item in sublist if _valid_candidate(item) + } + + all_functions: List[Function] = list( + {item1 for sublist in functions for item1 in sublist if isinstance(item1, Function)} + ) + + variables_written = [] + constructor_variables_written = [] + variables_initialized = [] + for f in all_functions: + if f.is_constructor_variables: + variables_initialized.extend(f.state_variables_written) + elif f.is_constructor: + constructor_variables_written.extend(f.state_variables_written) + else: + variables_written.extend(f.state_variables_written) + + for v in valid_candidates: + if v not in variables_written: + if _constant_initial_expression(v) and v not in constructor_variables_written: + self.constant_candidates.append(v) + + elif ( + v in constructor_variables_written or v in variables_initialized + ) and version.parse(self.compilation_unit.solc_version) >= version.parse( + "0.6.5" + ): + self.immutable_candidates.append(v) diff --git a/slither/formatters/variables/possible_const_state_variables.py b/slither/formatters/variables/unchanged_state_variables.py similarity index 94% rename from slither/formatters/variables/possible_const_state_variables.py rename to slither/formatters/variables/unchanged_state_variables.py index 88f92b841..c7c8bf003 100644 --- a/slither/formatters/variables/possible_const_state_variables.py +++ b/slither/formatters/variables/unchanged_state_variables.py @@ -5,7 +5,7 @@ from slither.formatters.exceptions import FormatError, FormatImpossible from slither.formatters.utils.patches import create_patch -def custom_format(compilation_unit: SlitherCompilationUnit, result): +def custom_format(compilation_unit: SlitherCompilationUnit, result, attribute: str) -> None: elements = result["elements"] for element in elements: @@ -15,14 +15,14 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result): contract = scope.get_contract_from_name(contract_name) var = contract.get_state_variable_from_name(element["name"]) if not var.expression: - raise FormatImpossible(f"{var.name} is uninitialized and cannot become constant.") + raise FormatImpossible(f"{var.name} is uninitialized and cannot become {attribute}.") _patch( compilation_unit, result, element["source_mapping"]["filename_absolute"], element["name"], - "constant " + element["name"], + f"{attribute} " + element["name"], element["source_mapping"]["start"], element["source_mapping"]["start"] + element["source_mapping"]["length"], ) diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index dbfa54121..95d113a84 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -30,6 +30,7 @@ from slither.slithir.operations import ( ) from slither.slithir.operations.binary import Binary from slither.slithir.variables import Constant +from slither.visitors.expression.constants_folding import ConstantFolding def _get_name(f: Union[Function, Variable]) -> str: @@ -175,6 +176,11 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n all_cst_used_in_binary[str(ir.type)].append( ConstantValue(str(r.value), str(r.type)) ) + if isinstance(ir.variable_left, Constant) and isinstance(ir.variable_right, Constant): + if ir.lvalue: + type_ = ir.lvalue.type + cst = ConstantFolding(ir.expression, type_).result() + all_cst_used.append(ConstantValue(str(cst.value), str(type_))) if isinstance(ir, TypeConversion): if isinstance(ir.variable, Constant): all_cst_used.append(ConstantValue(str(ir.variable.value), str(ir.type))) @@ -323,27 +329,32 @@ def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]: ret: Dict[str, List[Dict]] = defaultdict(list) for contract in slither.contracts: # pylint: disable=too-many-nested-blocks for function in contract.functions_entry_points: - for ir in function.all_slithir_operations(): - if isinstance(ir, HighLevelCall): - for idx, parameter in enumerate(function.parameters): - if is_dependent(ir.destination, parameter, function): - ret[contract.name].append( - { - "function": _get_name(function), - "parameter_idx": idx, - "signature": _get_name(ir.function), - } - ) - if isinstance(ir, LowLevelCall): - for idx, parameter in enumerate(function.parameters): - if is_dependent(ir.destination, parameter, function): - ret[contract.name].append( - { - "function": _get_name(function), - "parameter_idx": idx, - "signature": None, - } - ) + try: + for ir in function.all_slithir_operations(): + if isinstance(ir, HighLevelCall): + for idx, parameter in enumerate(function.parameters): + if is_dependent(ir.destination, parameter, function): + ret[contract.name].append( + { + "function": _get_name(function), + "parameter_idx": idx, + "signature": _get_name(ir.function), + } + ) + if isinstance(ir, LowLevelCall): + for idx, parameter in enumerate(function.parameters): + if is_dependent(ir.destination, parameter, function): + ret[contract.name].append( + { + "function": _get_name(function), + "parameter_idx": idx, + "signature": None, + } + ) + except Exception as e: + if slither.no_fail: + continue + raise e return ret diff --git a/slither/printers/summary/evm.py b/slither/printers/summary/evm.py index 8476deaca..660d91204 100644 --- a/slither/printers/summary/evm.py +++ b/slither/printers/summary/evm.py @@ -21,14 +21,8 @@ def _extract_evm_info(slither): CFG = load_evm_cfg_builder() for contract in slither.contracts_derived: - contract_bytecode_runtime = ( - contract.compilation_unit.crytic_compile_compilation_unit.bytecode_runtime( - contract.name - ) - ) - contract_srcmap_runtime = ( - contract.compilation_unit.crytic_compile_compilation_unit.srcmap_runtime(contract.name) - ) + contract_bytecode_runtime = contract.scope.bytecode_runtime(contract.name) + contract_srcmap_runtime = contract.scope.srcmap_runtime(contract.name) cfg = CFG(contract_bytecode_runtime) evm_info["cfg", contract.name] = cfg evm_info["mapping", contract.name] = generate_source_to_evm_ins_mapping( @@ -38,12 +32,8 @@ def _extract_evm_info(slither): contract.source_mapping.filename.absolute, ) - contract_bytecode_init = ( - contract.compilation_unit.crytic_compile_compilation_unit.bytecode_init(contract.name) - ) - contract_srcmap_init = ( - contract.compilation_unit.crytic_compile_compilation_unit.srcmap_init(contract.name) - ) + contract_bytecode_init = contract.scope.bytecode_init(contract.name) + contract_srcmap_init = contract.scope.srcmap_init(contract.name) cfg_init = CFG(contract_bytecode_init) evm_info["cfg_init", contract.name] = cfg_init diff --git a/slither/slither.py b/slither/slither.py index 81e920d01..45d99906f 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -91,6 +91,8 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes self.codex_max_tokens = kwargs.get("codex_max_tokens", 300) self.codex_log = kwargs.get("codex_log", False) + self.no_fail = kwargs.get("no_fail", False) + self._parsers: List[SlitherCompilationUnitSolc] = [] try: if isinstance(target, CryticCompile): @@ -128,41 +130,27 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes triage_mode = kwargs.get("triage_mode", False) self._triage_mode = triage_mode + self._init_parsing_and_analyses(kwargs.get("skip_analyze", False)) + + def _init_parsing_and_analyses(self, skip_analyze: bool) -> None: for parser in self._parsers: - parser.parse_contracts() + try: + parser.parse_contracts() + except Exception as e: + if self.no_fail: + continue + raise e # skip_analyze is only used for testing - if not kwargs.get("skip_analyze", False): + if not skip_analyze: for parser in self._parsers: - parser.analyze_contracts() - - # def _init_from_raw_json(self, filename): - # if not os.path.isfile(filename): - # raise SlitherError( - # "{} does not exist (are you in the correct directory?)".format(filename) - # ) - # assert filename.endswith("json") - # with open(filename, encoding="utf8") as astFile: - # stdout = astFile.read() - # if not stdout: - # to_log = f"Empty AST file: {filename}" - # raise SlitherError(to_log) - # contracts_json = stdout.split("\n=") - # - # self._parser = SlitherCompilationUnitSolc(filename, self) - # - # for c in contracts_json: - # self._parser.parse_top_level_from_json(c) - - # def _init_from_list(self, contract): - # self._parser = SlitherCompilationUnitSolc("", self) - # for c in contract: - # if "absolutePath" in c: - # path = c["absolutePath"] - # else: - # path = c["attributes"]["absolutePath"] - # self._parser.parse_top_level_from_loaded_json(c, path) + try: + parser.analyze_contracts() + except Exception as e: + if self.no_fail: + continue + raise e @property def detectors(self): diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c5817f4cd..89f85499c 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -15,6 +15,7 @@ from slither.core.declarations import ( ) from slither.core.declarations.custom_error import CustomError from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder from slither.core.declarations.solidity_variables import SolidityCustomRevert from slither.core.expressions import Identifier, Literal @@ -199,18 +200,22 @@ def _fits_under_byte(val: Union[int, str]) -> List[str]: return [f"bytes{f}" for f in range(length, 33)] + ["bytes"] -def _find_function_from_parameter(ir: Call, candidates: List[Function]) -> Optional[Function]: +def _find_function_from_parameter( + arguments: List[Variable], candidates: List[Function], full_comparison: bool +) -> Optional[Function]: """ - Look for a function in candidates that can be the target of the ir's call + Look for a function in candidates that can be the target based on the ir's call arguments Try the implicit type conversion for uint/int/bytes. Constant values can be both uint/int - While variables stick to their base type, but can changed the size + While variables stick to their base type, but can changed the size. + If full_comparison is True it will do a comparison of all the arguments regardless if + the candidate remained is one. - :param ir: + :param arguments: :param candidates: + :param full_comparison: :return: """ - arguments = ir.arguments type_args: List[str] for idx, arg in enumerate(arguments): if isinstance(arg, (list,)): @@ -258,7 +263,7 @@ def _find_function_from_parameter(ir: Call, candidates: List[Function]) -> Optio not_found = False candidates_kept.append(candidate) - if len(candidates_kept) == 1: + if len(candidates_kept) == 1 and not full_comparison: return candidates_kept[0] candidates = candidates_kept if len(candidates) == 1: @@ -374,7 +379,7 @@ def integrate_value_gas(result): ################################################################################### -def propagate_type_and_convert_call(result, node): +def propagate_type_and_convert_call(result: List[Operation], node: "Node") -> List[Operation]: """ Propagate the types variables and convert tmp call to real call operation """ @@ -450,19 +455,25 @@ def propagate_type_and_convert_call(result, node): return result -def _convert_type_contract(ir, compilation_unit: "SlitherCompilationUnit"): +def _convert_type_contract(ir: Member) -> Assignment: assert isinstance(ir.variable_left.type, TypeInformation) contract = ir.variable_left.type.type + scope = ir.node.file_scope + if ir.variable_right == "creationCode": - bytecode = compilation_unit.crytic_compile_compilation_unit.bytecode_init(contract.name) + bytecode = scope.bytecode_init( + ir.node.compilation_unit.crytic_compile_compilation_unit, contract.name + ) assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) assignment.lvalue.set_type(ElementaryType("bytes")) return assignment if ir.variable_right == "runtimeCode": - bytecode = compilation_unit.crytic_compile_compilation_unit.bytecode_runtime(contract.name) + bytecode = scope.bytecode_runtime( + ir.node.compilation_unit.crytic_compile_compilation_unit, contract.name + ) assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) @@ -497,7 +508,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals # propagate the type node_function = node.function using_for = ( - node_function.contract.using_for if isinstance(node_function, FunctionContract) else {} + node_function.contract.using_for_complete + if isinstance(node_function, FunctionContract) + else {} ) if isinstance(ir, OperationWithLValue): # Force assignment in case of missing previous correct type @@ -524,9 +537,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals if can_be_solidity_func(ir): return convert_to_solidity_func(ir) - # convert library + # convert library or top level function if t in using_for or "*" in using_for: - new_ir = convert_to_library(ir, node, using_for) + new_ir = convert_to_library_or_top_level(ir, node, using_for) if new_ir: return new_ir @@ -534,8 +547,8 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals # UserdefinedType t_type = t.type if isinstance(t_type, Contract): - contract = node.file_scope.get_contract_from_name(t_type.name) - return convert_type_of_high_and_internal_level_call(ir, contract) + # the target contract of the IR is the t_type (the destination of the call) + return convert_type_of_high_and_internal_level_call(ir, t_type) # Convert HighLevelCall to LowLevelCall if (isinstance(t, ElementaryType) and t.name == "address") or ( @@ -544,6 +557,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals # Cannot be a top level function with this. assert isinstance(node_function, FunctionContract) if ir.destination.name == "this": + # the target contract is the contract itself return convert_type_of_high_and_internal_level_call( ir, node_function.contract ) @@ -578,6 +592,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals function_contract = ( func.contract if isinstance(func, FunctionContract) else None ) + # the target contract might be None if its a top level function convert_type_of_high_and_internal_level_call(ir, function_contract) return_type = ir.function.return_type if return_type: @@ -651,7 +666,24 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals if ir.variable_right == "selector" and isinstance(ir.variable_left, (CustomError)): assignment = Assignment( ir.lvalue, - Constant(str(get_function_id(ir.variable_left.solidity_signature))), + Constant( + str(get_function_id(ir.variable_left.solidity_signature)), + ElementaryType("bytes4"), + ), + ElementaryType("bytes4"), + ) + assignment.set_expression(ir.expression) + assignment.set_node(ir.node) + assignment.lvalue.set_type(ElementaryType("bytes4")) + return assignment + + if isinstance(ir.variable_right, (CustomError)): + assignment = Assignment( + ir.lvalue, + Constant( + str(get_function_id(ir.variable_left.solidity_signature)), + ElementaryType("bytes4"), + ), ElementaryType("bytes4"), ) assignment.set_expression(ir.expression) @@ -673,7 +705,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals if isinstance(ir.variable_left, TemporaryVariable) and isinstance( ir.variable_left.type, TypeInformation ): - return _convert_type_contract(ir, node.function.compilation_unit) + return _convert_type_contract(ir) left = ir.variable_left t = None ir_func = ir.function @@ -723,7 +755,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals if f: ir.lvalue.set_type(f) else: - # Allow propgation for variable access through contract's nale + # Allow propgation for variable access through contract's name # like Base_contract.my_variable v = next( ( @@ -875,7 +907,9 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis # } node_func = ins.node.function using_for = ( - node_func.contract.using_for if isinstance(node_func, FunctionContract) else {} + node_func.contract.using_for_complete + if isinstance(node_func, FunctionContract) + else {} ) targeted_libraries = ( @@ -888,10 +922,14 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis lib_contract_type.type, Contract ): continue - lib_contract = lib_contract_type.type - for lib_func in lib_contract.functions: - if lib_func.name == ins.ori.variable_right: - candidates.append(lib_func) + if isinstance(lib_contract_type, FunctionContract): + # Using for with list of functions, this is the function called + candidates.append(lib_contract_type) + else: + lib_contract = lib_contract_type.type + for lib_func in lib_contract.functions: + if lib_func.name == ins.ori.variable_right: + candidates.append(lib_func) if len(candidates) == 1: lib_func = candidates[0] @@ -1320,9 +1358,32 @@ def convert_to_pop(ir, node): return ret -def look_for_library(contract, ir, using_for, t): +def look_for_library_or_top_level(contract, ir, using_for, t): for destination in using_for[t]: - lib_contract = contract.file_scope.get_contract_from_name(str(destination)) + if isinstance(destination, FunctionTopLevel) and destination.name == ir.function_name: + arguments = [ir.destination] + ir.arguments + if ( + len(destination.parameters) == len(arguments) + and _find_function_from_parameter(arguments, [destination], True) is not None + ): + internalcall = InternalCall(destination, ir.nbr_arguments, ir.lvalue, ir.type_call) + internalcall.set_expression(ir.expression) + internalcall.set_node(ir.node) + internalcall.arguments = [ir.destination] + ir.arguments + return_type = internalcall.function.return_type + if return_type: + if len(return_type) == 1: + internalcall.lvalue.set_type(return_type[0]) + elif len(return_type) > 1: + internalcall.lvalue.set_type(return_type) + else: + internalcall.lvalue = None + return internalcall + + if isinstance(destination, FunctionContract) and destination.contract.is_library: + lib_contract = destination.contract + else: + lib_contract = contract.file_scope.get_contract_from_name(str(destination)) if lib_contract: lib_call = LibraryCall( lib_contract, @@ -1342,19 +1403,19 @@ def look_for_library(contract, ir, using_for, t): return None -def convert_to_library(ir, node, using_for): +def convert_to_library_or_top_level(ir, node, using_for): # We use contract_declarer, because Solidity resolve the library # before resolving the inheritance. # Though we could use .contract as libraries cannot be shadowed contract = node.function.contract_declarer t = ir.destination.type if t in using_for: - new_ir = look_for_library(contract, ir, using_for, t) + new_ir = look_for_library_or_top_level(contract, ir, using_for, t) if new_ir: return new_ir if "*" in using_for: - new_ir = look_for_library(contract, ir, using_for, "*") + new_ir = look_for_library_or_top_level(contract, ir, using_for, "*") if new_ir: return new_ir @@ -1400,7 +1461,7 @@ def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract): # TODO: handle collision with multiple state variables/functions func = lib_contract.get_state_variable_from_name(ir.function_name) if func is None and candidates: - func = _find_function_from_parameter(ir, candidates) + func = _find_function_from_parameter(ir.arguments, candidates, False) # In case of multiple binding to the same type # TODO: this part might not be needed with _find_function_from_parameter @@ -1470,7 +1531,19 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]: return [return_type.type] -def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Optional[Contract]): +def convert_type_of_high_and_internal_level_call( + ir: Operation, contract: Optional[Contract] +) -> Optional[Operation]: + """ + Convert the IR type based on heuristic + + Args: + ir: target + contract: optional contract. This should be the target of the IR. It will be used to look up potential functions + + Returns: + Potential new IR + """ func = None if isinstance(ir, InternalCall): candidates: List[Function] @@ -1496,7 +1569,7 @@ def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Option if f.name == ir.function_name and len(f.parameters) == len(ir.arguments) ] - func = _find_function_from_parameter(ir, candidates) + func = _find_function_from_parameter(ir.arguments, candidates, False) if not func: assert contract @@ -1504,7 +1577,6 @@ def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Option else: assert isinstance(ir, HighLevelCall) assert contract - candidates = [ f for f in contract.functions @@ -1512,14 +1584,13 @@ def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Option and not f.is_shadowed and len(f.parameters) == len(ir.arguments) ] - if len(candidates) == 1: func = candidates[0] if func is None: # TODO: handle collision with multiple state variables/functions func = contract.get_state_variable_from_name(ir.function_name) if func is None and candidates: - func = _find_function_from_parameter(ir, candidates) + func = _find_function_from_parameter(ir.arguments, candidates, False) # lowlelvel lookup needs to be done at last step if not func: @@ -1777,7 +1848,7 @@ def _find_source_mapping_references(irs: List[Operation]): ################################################################################### -def apply_ir_heuristics(irs, node): +def apply_ir_heuristics(irs: List[Operation], node: "Node"): """ Apply a set of heuristic to improve slithIR """ diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index c509258e9..0ffd24131 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -6,7 +6,7 @@ from slither.core.declarations import Modifier, Event, EnumContract, StructureCo from slither.core.declarations.contract import Contract from slither.core.declarations.custom_error_contract import CustomErrorContract from slither.core.declarations.function_contract import FunctionContract -from slither.core.solidity_types import ElementaryType, TypeAliasContract +from slither.core.solidity_types import ElementaryType, TypeAliasContract, Type from slither.core.variables.state_variable import StateVariable from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.declarations.custom_error import CustomErrorSolc @@ -326,7 +326,13 @@ class ContractSolc(CallerContextExpression): def parse_state_variables(self): for father in self._contract.inheritance_reverse: - self._contract.variables_as_dict.update(father.variables_as_dict) + self._contract.variables_as_dict.update( + { + name: v + for name, v in father.variables_as_dict.items() + if v.visibility != "private" + } + ) self._contract.add_variables_ordered( [ var @@ -391,11 +397,11 @@ class ContractSolc(CallerContextExpression): ################################################################################### ################################################################################### - def log_incorrect_parsing(self, error): + def log_incorrect_parsing(self, error: str) -> None: if self._contract.compilation_unit.core.disallow_partial: raise ParsingError(error) LOGGER.error(error) - self._contract.is_incorrectly_parsed = True + self._contract.is_incorrectly_constructed = True def analyze_content_modifiers(self): try: @@ -577,21 +583,26 @@ class ContractSolc(CallerContextExpression): except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing state variable {e}") - def analyze_using_for(self): + def analyze_using_for(self): # pylint: disable=too-many-branches try: for father in self._contract.inheritance: self._contract.using_for.update(father.using_for) - if self.is_compact_ast: for using_for in self._usingForNotParsed: - lib_name = parse_type(using_for["libraryName"], self) if "typeName" in using_for and using_for["typeName"]: type_name = parse_type(using_for["typeName"], self) else: type_name = "*" if type_name not in self._contract.using_for: self._contract.using_for[type_name] = [] - self._contract.using_for[type_name].append(lib_name) + + if "libraryName" in using_for: + self._contract.using_for[type_name].append( + parse_type(using_for["libraryName"], self) + ) + else: + # We have a list of functions. A function can be topLevel or a library function + self._analyze_function_list(using_for["functionList"], type_name) else: for using_for in self._usingForNotParsed: children = using_for[self.get_children()] @@ -609,6 +620,59 @@ class ContractSolc(CallerContextExpression): except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing using for {e}") + def _analyze_function_list(self, function_list: List, type_name: Type): + for f in function_list: + full_name_split = f["function"]["name"].split(".") + if len(full_name_split) == 1: + # Top level function + function_name = full_name_split[0] + self._analyze_top_level_function(function_name, type_name) + elif len(full_name_split) == 2: + # It can be a top level function behind an aliased import + # or a library function + first_part = full_name_split[0] + function_name = full_name_split[1] + self._check_aliased_import(first_part, function_name, type_name) + else: + # MyImport.MyLib.a we don't care of the alias + library_name = full_name_split[1] + function_name = full_name_split[2] + self._analyze_library_function(library_name, function_name, type_name) + + def _check_aliased_import(self, first_part: str, function_name: str, type_name: Type): + # We check if the first part appear as alias for an import + # if it is then function_name must be a top level function + # otherwise it's a library function + for i in self._contract.file_scope.imports: + if i.alias == first_part: + self._analyze_top_level_function(function_name, type_name) + return + self._analyze_library_function(first_part, function_name, type_name) + + def _analyze_top_level_function(self, function_name: str, type_name: Type): + for tl_function in self.compilation_unit.functions_top_level: + if tl_function.name == function_name: + self._contract.using_for[type_name].append(tl_function) + + def _analyze_library_function( + self, library_name: str, function_name: str, type_name: Type + ) -> None: + # Get the library function + found = False + for c in self.compilation_unit.contracts: + if found: + break + if c.name == library_name: + for f in c.functions: + if f.name == function_name: + self._contract.using_for[type_name].append(f) + found = True + break + if not found: + self.log_incorrect_parsing( + f"Contract level using for: Library {library_name} - function {function_name} not found" + ) + def analyze_enums(self): try: for father in self._contract.inheritance: diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 130375211..6b8aca51e 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -91,6 +91,9 @@ class FunctionSolc(CallerContextExpression): Union[LocalVariableSolc, LocalVariableInitFromTupleSolc] ] = [] + if "documentation" in function_data: + function.has_documentation = True + @property def underlying_function(self) -> Function: return self._function @@ -308,7 +311,7 @@ class FunctionSolc(CallerContextExpression): for node_parser in self._node_to_yulobject.values(): node_parser.analyze_expressions() - self._filter_ternary() + self._rewrite_ternary_as_if_else() self._remove_alone_endif() @@ -340,7 +343,6 @@ class FunctionSolc(CallerContextExpression): node, [self._function.name, f"asm_{len(self._node_to_yulobject)}"], scope, - parent_func=self._function, ) self._node_to_yulobject[node] = yul_object return yul_object @@ -1336,7 +1338,7 @@ class FunctionSolc(CallerContextExpression): ################################################################################### ################################################################################### - def _filter_ternary(self) -> bool: + def _rewrite_ternary_as_if_else(self) -> bool: ternary_found = True updated = False while ternary_found: diff --git a/slither/solc_parsing/declarations/modifier.py b/slither/solc_parsing/declarations/modifier.py index a3f07da7f..e55487612 100644 --- a/slither/solc_parsing/declarations/modifier.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -87,7 +87,7 @@ class ModifierSolc(FunctionSolc): for node in self._node_to_nodesolc.values(): node.analyze_expressions(self) - self._filter_ternary() + self._rewrite_ternary_as_if_else() self._remove_alone_endif() # self._analyze_read_write() diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py new file mode 100644 index 000000000..16e3666b0 --- /dev/null +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -0,0 +1,167 @@ +""" + Using For Top Level module +""" +import logging +from typing import TYPE_CHECKING, Dict, Union + +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.core.declarations import ( + StructureTopLevel, + EnumTopLevel, +) +from slither.core.declarations.using_for_top_level import UsingForTopLevel +from slither.core.scope.scope import FileScope +from slither.core.solidity_types import TypeAliasTopLevel +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.solc_parsing.declarations.caller_context import CallerContextExpression +from slither.solc_parsing.solidity_types.type_parsing import parse_type + +if TYPE_CHECKING: + from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc + +LOGGER = logging.getLogger("UsingForTopLevelSolc") + + +class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-public-methods + """ + UsingFor class + """ + + def __init__( + self, + uftl: UsingForTopLevel, + top_level_data: Dict, + slither_parser: "SlitherCompilationUnitSolc", + ) -> None: + self._type_name = top_level_data["typeName"] + self._global = top_level_data["global"] + + if "libraryName" in top_level_data: + self._library_name = top_level_data["libraryName"] + else: + self._functions = top_level_data["functionList"] + self._library_name = None + + self._using_for = uftl + self._slither_parser = slither_parser + + def analyze(self) -> None: + type_name = parse_type(self._type_name, self) + self._using_for.using_for[type_name] = [] + + if self._library_name is not None: + library_name = parse_type(self._library_name, self) + self._using_for.using_for[type_name].append(library_name) + self._propagate_global(type_name) + else: + for f in self._functions: + full_name_split = f["function"]["name"].split(".") + if len(full_name_split) == 1: + # Top level function + function_name: str = full_name_split[0] + self._analyze_top_level_function(function_name, type_name) + elif len(full_name_split) == 2: + # It can be a top level function behind an aliased import + # or a library function + first_part = full_name_split[0] + function_name = full_name_split[1] + self._check_aliased_import(first_part, function_name, type_name) + else: + # MyImport.MyLib.a we don't care of the alias + library_name_str = full_name_split[1] + function_name = full_name_split[2] + self._analyze_library_function(library_name_str, function_name, type_name) + + def _check_aliased_import( + self, + first_part: str, + function_name: str, + type_name: Union[TypeAliasTopLevel, UserDefinedType], + ): + # We check if the first part appear as alias for an import + # if it is then function_name must be a top level function + # otherwise it's a library function + for i in self._using_for.file_scope.imports: + if i.alias == first_part: + self._analyze_top_level_function(function_name, type_name) + return + self._analyze_library_function(first_part, function_name, type_name) + + def _analyze_top_level_function( + self, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType] + ) -> None: + for tl_function in self.compilation_unit.functions_top_level: + if tl_function.name == function_name: + self._using_for.using_for[type_name].append(tl_function) + self._propagate_global(type_name) + break + + def _analyze_library_function( + self, + library_name: str, + function_name: str, + type_name: Union[TypeAliasTopLevel, UserDefinedType], + ) -> None: + found = False + for c in self.compilation_unit.contracts: + if found: + break + if c.name == library_name: + for cf in c.functions: + if cf.name == function_name: + self._using_for.using_for[type_name].append(cf) + self._propagate_global(type_name) + found = True + break + if not found: + LOGGER.warning( + f"Top level using for: Library {library_name} - function {function_name} not found" + ) + + def _propagate_global(self, type_name: Union[TypeAliasTopLevel, UserDefinedType]) -> None: + if self._global: + for scope in self.compilation_unit.scopes.values(): + if isinstance(type_name, TypeAliasTopLevel): + for alias in scope.user_defined_types.values(): + if alias == type_name: + scope.using_for_directives.add(self._using_for) + elif isinstance(type_name, UserDefinedType): + self._propagate_global_UserDefinedType(scope, type_name) + else: + LOGGER.error( + f"Error when propagating global using for {type_name} {type(type_name)}" + ) + + def _propagate_global_UserDefinedType(self, scope: FileScope, type_name: UserDefinedType): + underlying = type_name.type + if isinstance(underlying, StructureTopLevel): + for struct in scope.structures.values(): + if struct == underlying: + scope.using_for_directives.add(self._using_for) + elif isinstance(underlying, EnumTopLevel): + for enum in scope.enums.values(): + if enum == underlying: + scope.using_for_directives.add(self._using_for) + else: + LOGGER.error( + f"Error when propagating global {underlying} {type(underlying)} not a StructTopLevel or EnumTopLevel" + ) + + @property + def is_compact_ast(self) -> bool: + return self._slither_parser.is_compact_ast + + @property + def compilation_unit(self) -> SlitherCompilationUnit: + return self._slither_parser.compilation_unit + + def get_key(self) -> str: + return self._slither_parser.get_key() + + @property + def slither_parser(self) -> "SlitherCompilationUnitSolc": + return self._slither_parser + + @property + def underlying_using_for(self) -> UsingForTopLevel: + return self._using_for diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index d12bda1b4..4f6f9ac5c 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -14,6 +14,7 @@ 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 from slither.core.declarations.structure_top_level import StructureTopLevel +from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.scope.scope import FileScope from slither.core.solidity_types import ElementaryType, TypeAliasTopLevel from slither.core.variables.top_level_variable import TopLevelVariable @@ -22,8 +23,10 @@ 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.structure_top_level import StructureTopLevelSolc +from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.exceptions import VariableNotFound from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc +from slither.solc_parsing.declarations.caller_context import CallerContextExpression logging.basicConfig() logger = logging.getLogger("SlitherSolcParsing") @@ -45,19 +48,25 @@ def _handle_import_aliases( """ for symbol_alias in symbol_aliases: - if ( - "foreign" in symbol_alias - and "name" in symbol_alias["foreign"] - and "local" in symbol_alias - ): - original_name = symbol_alias["foreign"]["name"] - local_name = symbol_alias["local"] - import_directive.renaming[local_name] = original_name - # Assuming that two imports cannot collide in renaming - scope.renaming[local_name] = original_name - - -class SlitherCompilationUnitSolc: + if "foreign" in symbol_alias and "local" in symbol_alias: + if isinstance(symbol_alias["foreign"], dict) and "name" in symbol_alias["foreign"]: + + original_name = symbol_alias["foreign"]["name"] + local_name = symbol_alias["local"] + import_directive.renaming[local_name] = original_name + # Assuming that two imports cannot collide in renaming + scope.renaming[local_name] = original_name + + # This path should only be hit for the malformed AST of solc 0.5.12 where + # the foreign identifier cannot be found but is required to resolve the alias. + # see https://github.com/crytic/slither/issues/1319 + elif symbol_alias["local"]: + raise SlitherException( + "Cannot resolve local alias for import directive due to malformed AST. Please upgrade to solc 0.6.0 or higher." + ) + + +class SlitherCompilationUnitSolc(CallerContextExpression): # pylint: disable=no-self-use,too-many-instance-attributes def __init__(self, compilation_unit: SlitherCompilationUnit): super().__init__() @@ -71,6 +80,7 @@ class SlitherCompilationUnitSolc: self._custom_error_parser: List[CustomErrorSolc] = [] self._variables_top_level_parser: List[TopLevelVariableSolc] = [] self._functions_top_level_parser: List[FunctionSolc] = [] + self._using_for_top_level_parser: List[UsingForTopLevelSolc] = [] self._is_compact_ast = False # self._core: SlitherCore = core @@ -95,6 +105,10 @@ class SlitherCompilationUnitSolc: def underlying_contract_to_parser(self) -> Dict[Contract, ContractSolc]: return self._underlying_contract_to_parser + @property + def slither_parser(self) -> "SlitherCompilationUnitSolc": + return self + ################################################################################### ################################################################################### # region AST @@ -221,6 +235,17 @@ class SlitherCompilationUnitSolc: scope.pragmas.add(pragma) pragma.set_offset(top_level_data["src"], self._compilation_unit) self._compilation_unit.pragma_directives.append(pragma) + + elif top_level_data[self.get_key()] == "UsingForDirective": + scope = self.compilation_unit.get_scope(filename) + usingFor = UsingForTopLevel(scope) + usingFor_parser = UsingForTopLevelSolc(usingFor, top_level_data, self) + usingFor.set_offset(top_level_data["src"], self._compilation_unit) + scope.using_for_directives.add(usingFor) + + self._compilation_unit.using_for_top_level.append(usingFor) + self._using_for_top_level_parser.append(usingFor_parser) + elif top_level_data[self.get_key()] == "ImportDirective": if self.is_compact_ast: import_directive = Import( @@ -491,6 +516,9 @@ Please rename it, this name is reserved for Slither's internals""" # Then we analyse state variables, functions and modifiers self._analyze_third_part(contracts_to_be_analyzed, libraries) + [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()] + + self._analyze_using_for(contracts_to_be_analyzed, libraries) self._parsed = True @@ -602,6 +630,29 @@ Please rename it, this name is reserved for Slither's internals""" else: contracts_to_be_analyzed += [contract] + def _analyze_using_for( + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + ): + self._analyze_top_level_using_for() + + for lib in libraries: + lib.analyze_using_for() + + while contracts_to_be_analyzed: + contract = contracts_to_be_analyzed[0] + + contracts_to_be_analyzed = contracts_to_be_analyzed[1:] + all_father_analyzed = all( + self._underlying_contract_to_parser[father].is_analyzed + for father in contract.underlying_contract.inheritance + ) + + if not contract.underlying_contract.inheritance or all_father_analyzed: + contract.analyze_using_for() + contract.set_is_analyzed(True) + else: + contracts_to_be_analyzed += [contract] + def _analyze_enums(self, contract: ContractSolc): # Enum must be analyzed first contract.analyze_enums() @@ -624,7 +675,6 @@ Please rename it, this name is reserved for Slither's internals""" # Event can refer to struct contract.analyze_events() - contract.analyze_using_for() contract.analyze_custom_errors() contract.set_is_analyzed(True) @@ -648,6 +698,10 @@ Please rename it, this name is reserved for Slither's internals""" func_parser.analyze_params() self._compilation_unit.add_function(func_parser.underlying_function) + def _analyze_top_level_using_for(self): + for using_for in self._using_for_top_level_parser: + using_for.analyze() + def _analyze_params_custom_error(self): for custom_error_parser in self._custom_error_parser: custom_error_parser.analyze_params() @@ -683,12 +737,13 @@ Please rename it, this name is reserved for Slither's internals""" for func in contract.functions + contract.modifiers: try: func.generate_slithir_and_analyze() + except AttributeError as e: # This can happens for example if there is a call to an interface # And the interface is redefined due to contract's name reuse # But the available version misses some functions self._underlying_contract_to_parser[contract].log_incorrect_parsing( - f"Impossible to generate IR for {contract.name}.{func.name}:\n {e}" + f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}" ) contract.convert_expression_to_slithir_ssa() diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 9a8ef5db2..b62f908d1 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -224,6 +224,7 @@ def parse_type( from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc + from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc @@ -259,11 +260,16 @@ def parse_type( all_enums += enums_direct_access contracts = sl.contracts functions = [] - elif isinstance(caller_context, (StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc)): + elif isinstance( + caller_context, + (StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc, UsingForTopLevelSolc), + ): if isinstance(caller_context, StructureTopLevelSolc): scope = caller_context.underlying_structure.file_scope elif isinstance(caller_context, TopLevelVariableSolc): scope = caller_context.underlying_variable.file_scope + elif isinstance(caller_context, UsingForTopLevelSolc): + scope = caller_context.underlying_using_for.file_scope else: assert isinstance(caller_context, CustomErrorSolc) custom_error = caller_context.underlying_custom_error diff --git a/slither/solc_parsing/yul/evm_functions.py b/slither/solc_parsing/yul/evm_functions.py index 0276d4bf7..41c150765 100644 --- a/slither/solc_parsing/yul/evm_functions.py +++ b/slither/solc_parsing/yul/evm_functions.py @@ -264,9 +264,9 @@ binary_ops = { class YulBuiltin: # pylint: disable=too-few-public-methods - def __init__(self, name): + def __init__(self, name: str) -> None: self._name = name @property - def name(self): + def name(self) -> str: return self._name diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 8c9ee427e..f7c9938fc 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -24,6 +24,7 @@ from slither.core.expressions import ( UnaryOperation, ) from slither.core.expressions.expression import Expression +from slither.core.scope.scope import FileScope from slither.core.solidity_types import ElementaryType from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.local_variable import LocalVariable @@ -51,30 +52,35 @@ class YulNode: def underlying_node(self) -> Node: return self._node - def add_unparsed_expression(self, expression: Dict): + def add_unparsed_expression(self, expression: Dict) -> None: assert self._unparsed_expression is None self._unparsed_expression = expression - def analyze_expressions(self): + def analyze_expressions(self) -> None: if self._node.type == NodeType.VARIABLE and not self._node.expression: - self._node.add_expression(self._node.variable_declaration.expression) + expression = self._node.variable_declaration.expression + if expression: + self._node.add_expression(expression) if self._unparsed_expression: expression = parse_yul(self._scope, self, self._unparsed_expression) - self._node.add_expression(expression) + if expression: + self._node.add_expression(expression) if self._node.expression: if self._node.type == NodeType.VARIABLE: # Update the expression to be an assignement to the variable - _expression = AssignmentOperation( - Identifier(self._node.variable_declaration), - self._node.expression, - AssignmentOperationType.ASSIGN, - self._node.variable_declaration.type, - ) - _expression.set_offset( - self._node.expression.source_mapping, self._node.compilation_unit - ) - self._node.add_expression(_expression, bypass_verif_empty=True) + variable_declaration = self._node.variable_declaration + if variable_declaration: + _expression = AssignmentOperation( + Identifier(self._node.variable_declaration), + self._node.expression, + AssignmentOperationType.ASSIGN, + variable_declaration.type, + ) + _expression.set_offset( + self._node.expression.source_mapping, self._node.compilation_unit + ) + self._node.add_expression(_expression, bypass_verif_empty=True) expression = self._node.expression read_var = ReadVar(expression) @@ -122,13 +128,13 @@ class YulScope(metaclass=abc.ABCMeta): ] def __init__( - self, contract: Optional[Contract], yul_id: List[str], parent_func: Function = None - ): + self, contract: Optional[Contract], yul_id: List[str], parent_func: Function + ) -> None: self._contract = contract self._id: List[str] = yul_id self._yul_local_variables: List[YulLocalVariable] = [] self._yul_local_functions: List[YulFunction] = [] - self._parent_func = parent_func + self._parent_func: Function = parent_func @property def id(self) -> List[str]: @@ -155,10 +161,14 @@ class YulScope(metaclass=abc.ABCMeta): def new_node(self, node_type: NodeType, src: Union[str, Dict]) -> YulNode: pass - def add_yul_local_variable(self, var): + @property + def file_scope(self) -> FileScope: + return self._parent_func.file_scope + + def add_yul_local_variable(self, var: "YulLocalVariable") -> None: self._yul_local_variables.append(var) - def get_yul_local_variable_from_name(self, variable_name): + def get_yul_local_variable_from_name(self, variable_name: str) -> Optional["YulLocalVariable"]: return next( ( v @@ -168,10 +178,10 @@ class YulScope(metaclass=abc.ABCMeta): None, ) - def add_yul_local_function(self, func): + def add_yul_local_function(self, func: "YulFunction") -> None: self._yul_local_functions.append(func) - def get_yul_local_function_from_name(self, func_name): + def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulLocalVariable"]: return next( (v for v in self._yul_local_functions if v.underlying.name == func_name), None, @@ -242,7 +252,7 @@ class YulFunction(YulScope): def function(self) -> Function: return self._function - def convert_body(self): + def convert_body(self) -> None: node = self.new_node(NodeType.ENTRYPOINT, self._ast["src"]) link_underlying_nodes(self._entrypoint, node) @@ -258,7 +268,7 @@ class YulFunction(YulScope): convert_yul(self, node, self._ast["body"], self.node_scope) - def parse_body(self): + def parse_body(self) -> None: for node in self._nodes: node.analyze_expressions() @@ -289,9 +299,8 @@ class YulBlock(YulScope): entrypoint: Node, yul_id: List[str], node_scope: Union[Scope, Function], - **kwargs, ): - super().__init__(contract, yul_id, **kwargs) + super().__init__(contract, yul_id, entrypoint.function) self._entrypoint: YulNode = YulNode(entrypoint, self) self._nodes: List[YulNode] = [] @@ -318,7 +327,7 @@ class YulBlock(YulScope): def convert(self, ast: Dict) -> YulNode: return convert_yul(self, self._entrypoint, ast, self.node_scope) - def analyze_expressions(self): + def analyze_expressions(self) -> None: for node in self._nodes: node.analyze_expressions() @@ -361,18 +370,22 @@ def convert_yul_function_definition( while not isinstance(top_node_scope, Function): top_node_scope = top_node_scope.father + func: Union[FunctionTopLevel, FunctionContract] if isinstance(top_node_scope, FunctionTopLevel): - scope = root.contract.file_scope + scope = root.file_scope func = FunctionTopLevel(root.compilation_unit, scope) # Note: we do not add the function in the scope # While its a top level function, it is not accessible outside of the function definition # In practice we should probably have a specific function type for function defined within a function else: func = FunctionContract(root.compilation_unit) + func.function_language = FunctionLanguage.Yul yul_function = YulFunction(func, root, ast, node_scope) - root.contract.add_function(func) + if root.contract: + root.contract.add_function(func) + root.compilation_unit.add_function(func) root.add_yul_local_function(yul_function) @@ -774,14 +787,15 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[ # check function-scoped variables parent_func = root.parent_func if parent_func: - variable = parent_func.get_local_variable_from_name(name) - if variable: - return Identifier(variable) + local_variable = parent_func.get_local_variable_from_name(name) + if local_variable: + return Identifier(local_variable) if isinstance(parent_func, FunctionContract): - variable = parent_func.contract.get_state_variable_from_name(name) - if variable: - return Identifier(variable) + assert parent_func.contract + state_variable = parent_func.contract.get_state_variable_from_name(name) + if state_variable: + return Identifier(state_variable) # check yul-scoped variable variable = root.get_yul_local_variable_from_name(name) @@ -798,7 +812,7 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[ if magic_suffix: return magic_suffix - ret, _ = find_top_level(name, root.contract.file_scope) + ret, _ = find_top_level(name, root.file_scope) if ret: return Identifier(ret) @@ -840,7 +854,7 @@ def parse_yul_unsupported(_root: YulScope, _node: YulNode, ast: Dict) -> Optiona def parse_yul(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - op = parsers.get(ast["nodeType"], parse_yul_unsupported)(root, node, ast) + op: Expression = parsers.get(ast["nodeType"], parse_yul_unsupported)(root, node, ast) if op: op.set_offset(ast["src"], root.compilation_unit) return op diff --git a/slither/tools/doctor/__main__.py b/slither/tools/doctor/__main__.py index 94ae865ec..b9b4c5497 100644 --- a/slither/tools/doctor/__main__.py +++ b/slither/tools/doctor/__main__.py @@ -1,4 +1,6 @@ import argparse +import logging +import sys from crytic_compile import cryticparser @@ -25,6 +27,9 @@ def parse_args() -> argparse.Namespace: def main(): + # log on stdout to keep output in order + logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True) + args = parse_args() kwargs = vars(args) diff --git a/slither/tools/doctor/checks/__init__.py b/slither/tools/doctor/checks/__init__.py index 762c60b5d..8a0741940 100644 --- a/slither/tools/doctor/checks/__init__.py +++ b/slither/tools/doctor/checks/__init__.py @@ -1,6 +1,7 @@ from typing import Callable, List from dataclasses import dataclass +from slither.tools.doctor.checks.paths import check_slither_path from slither.tools.doctor.checks.platform import compile_project, detect_platform from slither.tools.doctor.checks.versions import show_versions @@ -12,6 +13,7 @@ class Check: ALL_CHECKS: List[Check] = [ + Check("PATH configuration", check_slither_path), Check("Software versions", show_versions), Check("Project platform", detect_platform), Check("Project compilation", compile_project), diff --git a/slither/tools/doctor/checks/paths.py b/slither/tools/doctor/checks/paths.py new file mode 100644 index 000000000..d388847ef --- /dev/null +++ b/slither/tools/doctor/checks/paths.py @@ -0,0 +1,85 @@ +from pathlib import Path +from typing import List, Optional, Tuple +import shutil +import sys +import sysconfig + +from slither.utils.colors import yellow, green, red + + +def path_is_relative_to(path: Path, relative_to: Path) -> bool: + """ + Check if a path is relative to another one. + + Compatibility wrapper for Path.is_relative_to + """ + if sys.version_info >= (3, 9, 0): + return path.is_relative_to(relative_to) + + path_parts = path.resolve().parts + relative_to_parts = relative_to.resolve().parts + + if len(path_parts) < len(relative_to_parts): + return False + + for (a, b) in zip(path_parts, relative_to_parts): + if a != b: + return False + + return True + + +def check_path_config(name: str) -> Tuple[bool, Optional[Path], List[Path]]: + """ + Check if a given Python binary/script is in PATH. + :return: Returns if the binary on PATH corresponds to this installation, + its path (if present), and a list of possible paths where this + binary might be found. + """ + binary_path = shutil.which(name) + possible_paths = [] + + for scheme in sysconfig.get_scheme_names(): + script_path = Path(sysconfig.get_path("scripts", scheme)) + purelib_path = Path(sysconfig.get_path("purelib", scheme)) + script_binary_path = shutil.which(name, path=script_path) + if script_binary_path is not None: + possible_paths.append((script_path, purelib_path)) + + binary_here = False + if binary_path is not None: + binary_path = Path(binary_path) + this_code = Path(__file__) + this_binary = list(filter(lambda x: path_is_relative_to(this_code, x[1]), possible_paths)) + binary_here = len(this_binary) > 0 and all( + path_is_relative_to(binary_path, script) for script, _ in this_binary + ) + + return binary_here, binary_path, list(set(script for script, _ in possible_paths)) + + +def check_slither_path(**_kwargs) -> None: + binary_here, binary_path, possible_paths = check_path_config("slither") + show_paths = False + + if binary_path: + print(green(f"`slither` found in PATH at `{binary_path}`.")) + if binary_here: + print(green("Its location matches this slither-doctor installation.")) + else: + print( + yellow( + "This path does not correspond to this slither-doctor installation.\n" + + "Double-check the order of directories in PATH if you have several Slither installations." + ) + ) + show_paths = True + else: + print(red("`slither` was not found in PATH.")) + show_paths = True + + if show_paths: + print() + print("Consider adding one of the following directories to PATH:") + for path in possible_paths: + print(f" * {path}") diff --git a/slither/tools/doctor/checks/versions.py b/slither/tools/doctor/checks/versions.py index 909bccf55..ec7ef1d1f 100644 --- a/slither/tools/doctor/checks/versions.py +++ b/slither/tools/doctor/checks/versions.py @@ -3,19 +3,19 @@ import json from typing import Optional import urllib -from packaging.version import parse, LegacyVersion, Version +from packaging.version import parse, Version from slither.utils.colors import yellow, green -def get_installed_version(name: str) -> Optional[LegacyVersion | Version]: +def get_installed_version(name: str) -> Optional[Version]: try: return parse(metadata.version(name)) except metadata.PackageNotFoundError: return None -def get_github_version(name: str) -> Optional[LegacyVersion | Version]: +def get_github_version(name: str) -> Optional[Version]: try: with urllib.request.urlopen( f"https://api.github.com/repos/crytic/{name}/releases/latest" @@ -45,7 +45,9 @@ def show_versions(**_kwargs) -> None: for name, (installed, latest) in versions.items(): color = yellow if name in outdated else green - print(f"{name + ':':<16}{color(installed or 'N/A'):<16} (latest is {latest or 'Unknown'})") + print( + f"{name + ':':<16}{color(str(installed) or 'N/A'):<16} (latest is {str(latest) or 'Unknown'})" + ) if len(outdated) > 0: print() diff --git a/slither/tools/documentation/README.md b/slither/tools/documentation/README.md new file mode 100644 index 000000000..b4b3e6a76 --- /dev/null +++ b/slither/tools/documentation/README.md @@ -0,0 +1,5 @@ +# slither-documentation + +`slither-documentation` uses [codex](https://beta.openai.com) to generate natspec documenation. + +This tool is experimental. See [solmate documentation](https://github.com/montyly/solmate/pull/1) for an example of usage. diff --git a/slither/tools/documentation/__init__.py b/slither/tools/documentation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/tools/documentation/__main__.py b/slither/tools/documentation/__main__.py new file mode 100644 index 000000000..8e545fb09 --- /dev/null +++ b/slither/tools/documentation/__main__.py @@ -0,0 +1,280 @@ +import argparse +import logging +import uuid +from typing import Optional, Dict, List +from crytic_compile import cryticparser +from slither import Slither +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.core.declarations import Function + +from slither.formatters.utils.patches import create_patch, apply_patch, create_diff +from slither.utils import codex + +logging.basicConfig() +logging.getLogger("Slither").setLevel(logging.INFO) + +logger = logging.getLogger("Slither") + + +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.add_argument("project", help="The target directory/Solidity file.") + + parser.add_argument( + "--overwrite", help="Overwrite the files (be careful).", action="store_true", default=False + ) + + parser.add_argument( + "--force-answer-parsing", + help="Apply heuristics to better parse codex output (might lead to incorrect results)", + action="store_true", + default=False, + ) + + parser.add_argument( + "--retry", + help="Retry failed query (default 1). Each retry increases the temperature by 0.1", + action="store", + default=1, + ) + + # Add default arguments from crytic-compile + cryticparser.init(parser) + + codex.init_parser(parser, always_enable_codex=True) + + return parser.parse_args() + + +def _use_tab(char: str) -> Optional[bool]: + """ + Check if the char is a tab + + Args: + char: + + Returns: + + """ + if char == " ": + return False + if char == "\t": + return True + return None + + +def _post_processesing( + answer: str, starting_column: int, use_tab: Optional[bool], force_and_stopped: bool +) -> Optional[str]: + """ + Clean answers from codex + + Args: + answer: + starting_column: + + Returns: + + """ + if answer.count("/**") != 1: + return None + # Sometimes codex will miss the */, even if it finished properly the request + # In this case, we allow slither-documentation to force the */ + if answer.count("*/") != 1: + if force_and_stopped: + answer += "*/" + else: + return None + if answer.find("/**") > answer.find("*/"): + return None + answer = answer[answer.find("/**") : answer.find("*/") + 2] + answer_lines = answer.splitlines() + # Add indentation to all the lines, aside the first one + + space_char = "\t" if use_tab else " " + + if len(answer_lines) > 0: + answer = ( + answer_lines[0] + + "\n" + + "\n".join( + [space_char * (starting_column - 1) + line for line in answer_lines[1:] if line] + ) + ) + answer += "\n" + space_char * (starting_column - 1) + return answer + return answer_lines[0] + + +def _handle_codex( + answer: Dict, starting_column: int, use_tab: Optional[bool], force: bool +) -> Optional[str]: + if "choices" in answer: + if answer["choices"]: + if "text" in answer["choices"][0]: + has_stopped = answer["choices"][0].get("finish_reason", "") == "stop" + answer_processed = _post_processesing( + answer["choices"][0]["text"], starting_column, use_tab, force and has_stopped + ) + if answer_processed is None: + return None + return answer_processed + return None + + +# pylint: disable=too-many-locals,too-many-arguments +def _handle_function( + function: Function, + overwrite: bool, + all_patches: Dict, + logging_file: Optional[str], + slither: Slither, + retry: int, + force: bool, +) -> bool: + if ( + function.source_mapping.is_dependency + or function.has_documentation + or function.is_constructor_variables + ): + return overwrite + prompt = "Create a natpsec documentation for this solidity code with only notice and dev.\n" + src_mapping = function.source_mapping + content = function.compilation_unit.core.source_code[src_mapping.filename.absolute] + start = src_mapping.start + end = src_mapping.start + src_mapping.length + prompt += content[start:end] + + use_tab = _use_tab(content[start - 1]) + if use_tab is None and src_mapping.starting_column > 1: + logger.info(f"Non standard space indentation found {content[start - 1:end]}") + if overwrite: + logger.info("Disable overwrite to avoid mistakes") + overwrite = False + + openai = codex.openai_module() # type: ignore + if openai is None: + raise ImportError + + if logging_file: + codex.log_codex(logging_file, "Q: " + prompt) + + tentative = 0 + answer_processed: Optional[str] = None + while tentative < retry: + tentative += 1 + + answer = openai.Completion.create( # type: ignore + prompt=prompt, + model=slither.codex_model, + temperature=min(slither.codex_temperature + tentative * 0.1, 1), + max_tokens=slither.codex_max_tokens, + ) + + if logging_file: + codex.log_codex(logging_file, "A: " + str(answer)) + + answer_processed = _handle_codex(answer, src_mapping.starting_column, use_tab, force) + if answer_processed: + break + + logger.info( + f"Codex could not generate a well formatted answer for {function.canonical_name}" + ) + logger.info(answer) + + if not answer_processed: + return overwrite + + create_patch(all_patches, src_mapping.filename.absolute, start, start, "", answer_processed) + + return overwrite + + +def _handle_compilation_unit( + slither: Slither, + compilation_unit: SlitherCompilationUnit, + overwrite: bool, + force: bool, + retry: int, +) -> None: + logging_file: Optional[str] + if slither.codex_log: + logging_file = str(uuid.uuid4()) + else: + logging_file = None + + for scope in compilation_unit.scopes.values(): + + # Dont send tests file + if ( + ".t.sol" in scope.filename.absolute + or "mock" in scope.filename.absolute.lower() + or "test" in scope.filename.absolute.lower() + ): + continue + + functions_target: List[Function] = [] + + for contract in scope.contracts.values(): + functions_target += contract.functions_declared + + functions_target += list(scope.functions) + + all_patches: Dict = {} + + for function in functions_target: + overwrite = _handle_function( + function, overwrite, all_patches, logging_file, slither, retry, force + ) + + # all_patches["patches"] should have only 1 file + if "patches" not in all_patches: + continue + for file in all_patches["patches"]: + original_txt = compilation_unit.core.source_code[file].encode("utf8") + patched_txt = original_txt + + patches = all_patches["patches"][file] + offset = 0 + patches.sort(key=lambda x: x["start"]) + + for patch in patches: + patched_txt, offset = apply_patch(patched_txt, patch, offset) + + if overwrite: + with open(file, "w", encoding="utf8") as f: + f.write(patched_txt.decode("utf8")) + else: + diff = create_diff(compilation_unit, original_txt, patched_txt, file) + with open(f"{file}.patch", "w", encoding="utf8") as f: + f.write(diff) + + +def main() -> None: + args = parse_args() + + logger.info("This tool is a WIP, use it with cautious") + logger.info("Be aware of OpenAI ToS: https://openai.com/api/policies/terms/") + slither = Slither(args.project, **vars(args)) + + try: + for compilation_unit in slither.compilation_units: + _handle_compilation_unit( + slither, + compilation_unit, + args.overwrite, + args.force_answer_parsing, + int(args.retry), + ) + except ImportError: + pass + + +if __name__ == "__main__": + main() diff --git a/slither/tools/slither_format/slither_format.py b/slither/tools/slither_format/slither_format.py index c165b3fbb..3c37313fd 100644 --- a/slither/tools/slither_format/slither_format.py +++ b/slither/tools/slither_format/slither_format.py @@ -9,7 +9,8 @@ from slither.detectors.attributes.incorrect_solc import IncorrectSolc from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.naming_convention.naming_convention import NamingConvention from slither.detectors.functions.external_function import ExternalFunction -from slither.detectors.variables.possible_const_state_variables import ConstCandidateStateVars +from slither.detectors.variables.could_be_constant import CouldBeConstant +from slither.detectors.variables.could_be_immutable import CouldBeImmutable from slither.detectors.attributes.const_functions_asm import ConstantFunctionsAsm from slither.detectors.attributes.const_functions_state import ConstantFunctionsState from slither.utils.colors import yellow @@ -23,7 +24,8 @@ all_detectors: Dict[str, Type[AbstractDetector]] = { "pragma": ConstantPragma, "naming-convention": NamingConvention, "external-function": ExternalFunction, - "constable-states": ConstCandidateStateVars, + "constable-states": CouldBeConstant, + "immutable-states": CouldBeImmutable, "constant-function-asm": ConstantFunctionsAsm, "constant-functions-state": ConstantFunctionsState, } diff --git a/slither/tools/upgradeability/__main__.py b/slither/tools/upgradeability/__main__.py index 414a4c175..56b838b9c 100644 --- a/slither/tools/upgradeability/__main__.py +++ b/slither/tools/upgradeability/__main__.py @@ -14,7 +14,10 @@ from slither.exceptions import SlitherException from slither.utils.colors import red from slither.utils.output import output_to_json from slither.tools.upgradeability.checks import all_checks -from slither.tools.upgradeability.checks.abstract_checks import AbstractCheck +from slither.tools.upgradeability.checks.abstract_checks import ( + AbstractCheck, + CheckClassification, +) from slither.tools.upgradeability.utils.command_line import ( output_detectors_json, output_wiki, @@ -27,12 +30,14 @@ logger: logging.Logger = logging.getLogger("Slither") logger.setLevel(logging.INFO) -def parse_args() -> argparse.Namespace: +def parse_args(check_classes: List[Type[AbstractCheck]]) -> argparse.Namespace: parser = argparse.ArgumentParser( description="Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.", usage="slither-check-upgradeability contract.sol ContractName", ) + group_checks = parser.add_argument_group("Checks") + parser.add_argument("contract.sol", help="Codebase to analyze") parser.add_argument("ContractName", help="Contract name (logic contract)") @@ -51,7 +56,16 @@ def parse_args() -> argparse.Namespace: default=False, ) - parser.add_argument( + group_checks.add_argument( + "--detect", + help="Comma-separated list of detectors, defaults to all, " + f"available detectors: {', '.join(d.ARGUMENT for d in check_classes)}", + action="store", + dest="detectors_to_run", + default="all", + ) + + group_checks.add_argument( "--list-detectors", help="List available detectors", action=ListDetectors, @@ -59,6 +73,42 @@ def parse_args() -> argparse.Namespace: default=False, ) + group_checks.add_argument( + "--exclude", + help="Comma-separated list of detectors that should be excluded", + action="store", + dest="detectors_to_exclude", + default=None, + ) + + group_checks.add_argument( + "--exclude-informational", + help="Exclude informational impact analyses", + action="store_true", + default=False, + ) + + group_checks.add_argument( + "--exclude-low", + help="Exclude low impact analyses", + action="store_true", + default=False, + ) + + group_checks.add_argument( + "--exclude-medium", + help="Exclude medium impact analyses", + action="store_true", + default=False, + ) + + group_checks.add_argument( + "--exclude-high", + help="Exclude high impact analyses", + action="store_true", + default=False, + ) + parser.add_argument( "--markdown-root", help="URL for markdown generation", @@ -104,6 +154,43 @@ def _get_checks() -> List[Type[AbstractCheck]]: return detectors +def choose_checks( + args: argparse.Namespace, all_check_classes: List[Type[AbstractCheck]] +) -> List[Type[AbstractCheck]]: + detectors_to_run = [] + detectors = {d.ARGUMENT: d for d in all_check_classes} + + if args.detectors_to_run == "all": + detectors_to_run = all_check_classes + if args.detectors_to_exclude: + detectors_excluded = args.detectors_to_exclude.split(",") + for detector in detectors: + if detector in detectors_excluded: + detectors_to_run.remove(detectors[detector]) + else: + for detector in args.detectors_to_run.split(","): + if detector in detectors: + detectors_to_run.append(detectors[detector]) + else: + raise Exception(f"Error: {detector} is not a detector") + detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) + return detectors_to_run + + if args.exclude_informational: + detectors_to_run = [ + d for d in detectors_to_run if d.IMPACT != CheckClassification.INFORMATIONAL + ] + if args.exclude_low: + detectors_to_run = [d for d in detectors_to_run if d.IMPACT != CheckClassification.LOW] + if args.exclude_medium: + detectors_to_run = [d for d in detectors_to_run if d.IMPACT != CheckClassification.MEDIUM] + if args.exclude_high: + detectors_to_run = [d for d in detectors_to_run if d.IMPACT != CheckClassification.HIGH] + + # detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) + return detectors_to_run + + class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods def __call__( self, parser: Any, *args: Any, **kwargs: Any @@ -200,11 +287,11 @@ def main() -> None: "detectors": [], } - args = parse_args() - + detectors = _get_checks() + args = parse_args(detectors) + detectors_to_run = choose_checks(args, detectors) v1_filename = vars(args)["contract.sol"] number_detectors_run = 0 - detectors = _get_checks() try: variable1 = Slither(v1_filename, **vars(args)) @@ -219,7 +306,7 @@ def main() -> None: return v1_contract = v1_contracts[0] - detectors_results, number_detectors = _checks_on_contract(detectors, v1_contract) + detectors_results, number_detectors = _checks_on_contract(detectors_to_run, v1_contract) json_results["detectors"] += detectors_results number_detectors_run += number_detectors @@ -242,7 +329,7 @@ def main() -> None: json_results["proxy-present"] = True detectors_results, number_detectors = _checks_on_contract_and_proxy( - detectors, v1_contract, proxy_contract + detectors_to_run, v1_contract, proxy_contract ) json_results["detectors"] += detectors_results number_detectors_run += number_detectors @@ -267,19 +354,19 @@ def main() -> None: if proxy_contract: detectors_results, _ = _checks_on_contract_and_proxy( - detectors, v2_contract, proxy_contract + detectors_to_run, v2_contract, proxy_contract ) json_results["detectors"] += detectors_results detectors_results, number_detectors = _checks_on_contract_update( - detectors, v1_contract, v2_contract + detectors_to_run, v1_contract, v2_contract ) json_results["detectors"] += detectors_results number_detectors_run += number_detectors # If there is a V2, we run the contract-only check on the V2 - detectors_results, number_detectors = _checks_on_contract(detectors, v2_contract) + detectors_results, number_detectors = _checks_on_contract(detectors_to_run, v2_contract) json_results["detectors"] += detectors_results number_detectors_run += number_detectors diff --git a/slither/tools/upgradeability/checks/variable_initialization.py b/slither/tools/upgradeability/checks/variable_initialization.py index a2a2edbb2..e8ae9b26c 100644 --- a/slither/tools/upgradeability/checks/variable_initialization.py +++ b/slither/tools/upgradeability/checks/variable_initialization.py @@ -39,8 +39,8 @@ Using initialize functions to write initial values in state variables. def _check(self): results = [] - for s in self.contract.state_variables: - if s.initialized and not s.is_constant: + for s in self.contract.state_variables_ordered: + if s.initialized and not (s.is_constant or s.is_immutable): info = [s, " is a state variable with an initial value.\n"] json = self.generate_result(info) results.append(json) diff --git a/slither/tools/upgradeability/checks/variables_order.py b/slither/tools/upgradeability/checks/variables_order.py index 8404f1d25..030fb0f65 100644 --- a/slither/tools/upgradeability/checks/variables_order.py +++ b/slither/tools/upgradeability/checks/variables_order.py @@ -48,8 +48,16 @@ Do not change the order of the state variables in the updated contract. def _check(self): contract1 = self.contract contract2 = self.contract_v2 - order1 = [variable for variable in contract1.state_variables if not variable.is_constant] - order2 = [variable for variable in contract2.state_variables if not variable.is_constant] + 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) + ] results = [] for idx, _ in enumerate(order1): @@ -109,8 +117,16 @@ Avoid variables in the proxy. If a variable is in the proxy, ensure it has the s def _check(self): contract1 = self._contract1() contract2 = self._contract2() - order1 = [variable for variable in contract1.state_variables if not variable.is_constant] - order2 = [variable for variable in contract2.state_variables if not variable.is_constant] + 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) + ] results = [] for idx, _ in enumerate(order1): @@ -228,8 +244,16 @@ Avoid variables in the proxy. If a variable is in the proxy, ensure it has the s def _check(self): contract1 = self._contract1() contract2 = self._contract2() - order1 = [variable for variable in contract1.state_variables if not variable.is_constant] - order2 = [variable for variable in contract2.state_variables if not variable.is_constant] + 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) + ] results = [] diff --git a/slither/utils/codex.py b/slither/utils/codex.py index 0040fb03c..3b06efe6f 100644 --- a/slither/utils/codex.py +++ b/slither/utils/codex.py @@ -1,10 +1,70 @@ import logging import os +from argparse import ArgumentParser from pathlib import Path +from slither.utils.command_line import defaults_flag_in_config + logger = logging.getLogger("Slither") +def init_parser(parser: ArgumentParser, always_enable_codex: bool = False) -> None: + """ + Init the cli arg with codex features + + Args: + parser: + always_enable_codex (Optional(bool)): if true, --codex is not enabled + + Returns: + + """ + group_codex = parser.add_argument_group("Codex (https://beta.openai.com/docs/guides/code)") + + if not always_enable_codex: + group_codex.add_argument( + "--codex", + help="Enable codex (require an OpenAI API Key)", + action="store_true", + default=defaults_flag_in_config["codex"], + ) + + group_codex.add_argument( + "--codex-log", + help="Log codex queries (in crytic_export/codex/)", + action="store_true", + default=False, + ) + + group_codex.add_argument( + "--codex-contracts", + help="Comma separated list of contracts to submit to OpenAI Codex", + action="store", + default=defaults_flag_in_config["codex_contracts"], + ) + + group_codex.add_argument( + "--codex-model", + help="Name of the Codex model to use (affects pricing). Defaults to 'text-davinci-003'", + action="store", + default=defaults_flag_in_config["codex_model"], + ) + + group_codex.add_argument( + "--codex-temperature", + help="Temperature to use with Codex. Lower number indicates a more precise answer while higher numbers return more creative answers. Defaults to 0", + action="store", + default=defaults_flag_in_config["codex_temperature"], + ) + + group_codex.add_argument( + "--codex-max-tokens", + help="Maximum amount of tokens to use on the response. This number plus the size of the prompt can be no larger than the limit (4097 for text-davinci-003)", + action="store", + default=defaults_flag_in_config["codex_max_tokens"], + ) + + # TODO: investigate how to set the correct return type # So that the other modules can work with openai def openai_module(): # type: ignore diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 71305c56e..174c2a4b6 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -60,6 +60,7 @@ defaults_flag_in_config = { "zip": None, "zip_type": "lzma", "show_ignored_findings": False, + "no_fail": False, **DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE, } diff --git a/slither/utils/expression_manipulations.py b/slither/utils/expression_manipulations.py index 1a300c39b..a63db9829 100644 --- a/slither/utils/expression_manipulations.py +++ b/slither/utils/expression_manipulations.py @@ -23,20 +23,29 @@ from slither.all_exceptions import SlitherException # pylint: disable=protected-access def f_expressions( - e: AssignmentOperation, x: Union[Identifier, Literal, MemberAccess, IndexAccess] + e: Union[AssignmentOperation, BinaryOperation, TupleExpression], + x: Union[Identifier, Literal, MemberAccess, IndexAccess], ) -> None: e._expressions.append(x) -def f_call(e, x): +def f_call(e: CallExpression, x): e._arguments.append(x) -def f_expression(e, x): +def f_call_value(e: CallExpression, x): + e._value = x + + +def f_call_gas(e: CallExpression, x): + e._gas = x + + +def f_expression(e: Union[TypeConversion, UnaryOperation, MemberAccess], x): e._expression = x -def f_called(e, x): +def f_called(e: CallExpression, x): e._called = x @@ -53,13 +62,20 @@ class SplitTernaryExpression: self.condition = None self.copy_expression(expression, self.true_expression, self.false_expression) - def apply_copy( + def conditional_not_ahead( self, next_expr: Expression, true_expression: Union[AssignmentOperation, MemberAccess], false_expression: Union[AssignmentOperation, MemberAccess], f: Callable, ) -> bool: + # look ahead for parenthetical expression (.. ? .. : ..) + if ( + isinstance(next_expr, TupleExpression) + and len(next_expr.expressions) == 1 + and isinstance(next_expr.expressions[0], ConditionalExpression) + ): + next_expr = next_expr.expressions[0] if isinstance(next_expr, ConditionalExpression): f(true_expression, copy.copy(next_expr.then_expression)) @@ -71,7 +87,6 @@ class SplitTernaryExpression: f(false_expression, copy.copy(next_expr)) return True - # pylint: disable=too-many-branches def copy_expression( self, expression: Expression, true_expression: Expression, false_expression: Expression ) -> None: @@ -87,57 +102,20 @@ class SplitTernaryExpression: ): return - # case of lib - # (.. ? .. : ..).add - if isinstance(expression, MemberAccess): - next_expr = expression.expression - if self.apply_copy(next_expr, true_expression, false_expression, f_expression): - self.copy_expression( - next_expr, true_expression.expression, false_expression.expression - ) - - elif isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)): + if isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)): true_expression._expressions = [] false_expression._expressions = [] - - for next_expr in expression.expressions: - if isinstance(next_expr, IndexAccess): - # create an index access for each branch - if isinstance(next_expr.expression_right, ConditionalExpression): - next_expr = _handle_ternary_access( - next_expr, true_expression, false_expression - ) - if self.apply_copy(next_expr, true_expression, false_expression, f_expressions): - # always on last arguments added - self.copy_expression( - next_expr, - true_expression.expressions[-1], - false_expression.expressions[-1], - ) + self.convert_expressions(expression, true_expression, false_expression) elif isinstance(expression, CallExpression): next_expr = expression.called + self.convert_call_expression(expression, next_expr, true_expression, false_expression) - # case of lib - # (.. ? .. : ..).add - if self.apply_copy(next_expr, true_expression, false_expression, f_called): - self.copy_expression(next_expr, true_expression.called, false_expression.called) - - true_expression._arguments = [] - false_expression._arguments = [] - - for next_expr in expression.arguments: - if self.apply_copy(next_expr, true_expression, false_expression, f_call): - # always on last arguments added - self.copy_expression( - next_expr, - true_expression.arguments[-1], - false_expression.arguments[-1], - ) - - elif isinstance(expression, (TypeConversion, UnaryOperation)): + elif isinstance(expression, (TypeConversion, UnaryOperation, MemberAccess)): next_expr = expression.expression - if self.apply_copy(next_expr, true_expression, false_expression, f_expression): + if self.conditional_not_ahead( + next_expr, true_expression, false_expression, f_expression + ): self.copy_expression( expression.expression, true_expression.expression, @@ -149,34 +127,90 @@ class SplitTernaryExpression: f"Ternary operation not handled {expression}({type(expression)})" ) + def convert_expressions( + self, + expression: Union[AssignmentOperation, BinaryOperation, TupleExpression], + true_expression: Expression, + false_expression: Expression, + ) -> None: + for next_expr in expression.expressions: + # TODO: can we get rid of `NoneType` expressions in `TupleExpression`? + # montyly: this might happen with unnamed tuple (ex: (,,,) = f()), but it needs to be checked + if next_expr: + if isinstance(next_expr, IndexAccess): + self.convert_index_access(next_expr, true_expression, false_expression) + + if self.conditional_not_ahead( + next_expr, true_expression, false_expression, f_expressions + ): + # always on last arguments added + self.copy_expression( + next_expr, + true_expression.expressions[-1], + false_expression.expressions[-1], + ) + + def convert_index_access( + self, next_expr: IndexAccess, true_expression: Expression, false_expression: Expression + ) -> None: + # create an index access for each branch + # x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] } + for expr in next_expr.expressions: + if self.conditional_not_ahead(expr, true_expression, false_expression, f_expressions): + self.copy_expression( + expr, + true_expression.expressions[-1], + false_expression.expressions[-1], + ) -def _handle_ternary_access( - next_expr: IndexAccess, - true_expression: AssignmentOperation, - false_expression: AssignmentOperation, -): - """ - Conditional ternary accesses are split into two accesses, one true and one false - E.g. x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] } - """ - true_index_access = IndexAccess( - next_expr.expression_left, - next_expr.expression_right.then_expression, - next_expr.type, - ) - false_index_access = IndexAccess( - next_expr.expression_left, - next_expr.expression_right.else_expression, - next_expr.type, - ) - - f_expressions( - true_expression, - true_index_access, - ) - f_expressions( - false_expression, - false_index_access, - ) - - return next_expr.expression_right + def convert_call_expression( + self, + expression: CallExpression, + next_expr: Expression, + true_expression: Expression, + false_expression: Expression, + ) -> None: + # case of lib + # (.. ? .. : ..).add + if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_called): + self.copy_expression(next_expr, true_expression.called, false_expression.called) + + # In order to handle ternaries in both call options, gas and value, we return early if the + # conditional is not ahead to rewrite both ternaries (see `_rewrite_ternary_as_if_else`). + if expression.call_gas: + # case of (..).func{gas: .. ? .. : ..}() + next_expr = expression.call_gas + if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_call_gas): + self.copy_expression( + next_expr, + true_expression.call_gas, + false_expression.call_gas, + ) + else: + return + + if expression.call_value: + # case of (..).func{value: .. ? .. : ..}() + next_expr = expression.call_value + if self.conditional_not_ahead( + next_expr, true_expression, false_expression, f_call_value + ): + self.copy_expression( + next_expr, + true_expression.call_value, + false_expression.call_value, + ) + else: + return + + true_expression._arguments = [] + false_expression._arguments = [] + + for expr in expression.arguments: + if self.conditional_not_ahead(expr, true_expression, false_expression, f_call): + # always on last arguments added + self.copy_expression( + expr, + true_expression.arguments[-1], + false_expression.arguments[-1], + ) diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index b324ed842..797d1f46e 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -1,4 +1,12 @@ -from slither.core.expressions import BinaryOperationType, Literal, UnaryOperationType +from fractions import Fraction +from slither.core.expressions import ( + BinaryOperationType, + Literal, + UnaryOperationType, + Identifier, + BinaryOperation, + UnaryOperation, +) from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int from slither.visitors.expression.expression import ExpressionVisitor @@ -27,9 +35,15 @@ class ConstantFolding(ExpressionVisitor): super().__init__(expression) def result(self): - return Literal(int(get_val(self._expression)), self._type) - - def _post_identifier(self, expression): + value = get_val(self._expression) + if isinstance(value, Fraction): + value = int(value) + # emulate 256-bit wrapping + if str(self._type).startswith("uint"): + value = value & (2**256 - 1) + return Literal(value, self._type) + + def _post_identifier(self, expression: Identifier): if not expression.value.is_constant: raise NotConstant expr = expression.value.expression @@ -37,9 +51,10 @@ class ConstantFolding(ExpressionVisitor): if not isinstance(expr, Literal): cf = ConstantFolding(expr, self._type) expr = cf.result() - set_val(expression, convert_string_to_int(expr.value)) + set_val(expression, convert_string_to_int(expr.converted_value)) - def _post_binary_operation(self, expression): + # pylint: disable=too-many-branches + def _post_binary_operation(self, expression: BinaryOperation): left = get_val(expression.expression_left) right = get_val(expression.expression_right) if expression.type == BinaryOperationType.POWER: @@ -53,18 +68,39 @@ class ConstantFolding(ExpressionVisitor): elif expression.type == BinaryOperationType.ADDITION: set_val(expression, left + right) elif expression.type == BinaryOperationType.SUBTRACTION: - if (left - right) < 0: - # Could trigger underflow - raise NotConstant set_val(expression, left - right) + # Convert to int for operations not supported by Fraction elif expression.type == BinaryOperationType.LEFT_SHIFT: - set_val(expression, left << right) + set_val(expression, int(left) << int(right)) elif expression.type == BinaryOperationType.RIGHT_SHIFT: - set_val(expression, left >> right) + set_val(expression, int(left) >> int(right)) + elif expression.type == BinaryOperationType.AND: + set_val(expression, int(left) & int(right)) + elif expression.type == BinaryOperationType.CARET: + set_val(expression, int(left) ^ int(right)) + elif expression.type == BinaryOperationType.OR: + set_val(expression, int(left) | int(right)) + elif expression.type == BinaryOperationType.LESS: + set_val(expression, int(left) < int(right)) + elif expression.type == BinaryOperationType.LESS_EQUAL: + set_val(expression, int(left) <= int(right)) + elif expression.type == BinaryOperationType.GREATER: + set_val(expression, int(left) > int(right)) + elif expression.type == BinaryOperationType.GREATER_EQUAL: + set_val(expression, int(left) >= int(right)) + elif expression.type == BinaryOperationType.EQUAL: + set_val(expression, int(left) == int(right)) + elif expression.type == BinaryOperationType.NOT_EQUAL: + set_val(expression, int(left) != int(right)) + # Convert boolean literals from string to bool + elif expression.type == BinaryOperationType.ANDAND: + set_val(expression, left == "true" and right == "true") + elif expression.type == BinaryOperationType.OROR: + set_val(expression, left == "true" or right == "true") else: raise NotConstant - def _post_unary_operation(self, expression): + def _post_unary_operation(self, expression: UnaryOperation): # Case of uint a = -7; uint[-a] arr; if expression.type == UnaryOperationType.MINUS_PRE: expr = expression.expression @@ -72,15 +108,18 @@ class ConstantFolding(ExpressionVisitor): cf = ConstantFolding(expr, self._type) expr = cf.result() assert isinstance(expr, Literal) - set_val(expression, -convert_string_to_fraction(expr.value)) + set_val(expression, -convert_string_to_fraction(expr.converted_value)) else: raise NotConstant - def _post_literal(self, expression): - try: - set_val(expression, convert_string_to_fraction(expression.value)) - except ValueError as e: - raise NotConstant from e + def _post_literal(self, expression: Literal): + if expression.converted_value in ["true", "false"]: + set_val(expression, expression.converted_value) + else: + try: + set_val(expression, convert_string_to_fraction(expression.converted_value)) + except ValueError as e: + raise NotConstant from e def _post_assignement_operation(self, expression): raise NotConstant @@ -115,9 +154,12 @@ class ConstantFolding(ExpressionVisitor): cf = ConstantFolding(expression.expressions[0], self._type) expr = cf.result() assert isinstance(expr, Literal) - set_val(expression, convert_string_to_fraction(expr.value)) + set_val(expression, convert_string_to_fraction(expr.converted_value)) return raise NotConstant def _post_type_conversion(self, expression): - raise NotConstant + cf = ConstantFolding(expression.expression, self._type) + expr = cf.result() + assert isinstance(expr, Literal) + set_val(expression, convert_string_to_fraction(expr.converted_value)) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 3c4595b92..6b7b4c264 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -282,10 +282,15 @@ class ExpressionToSlithIR(ExpressionVisitor): and expression_called.member_name in ["wrap", "unwrap"] and len(args) == 1 ): + # wrap: underlying_type -> alias + # unwrap: alias -> underlying_type + dest_type = ( + called if expression_called.member_name == "wrap" else called.underlying_type + ) val = TemporaryVariable(self._node) - var = TypeConversion(val, args[0], called) + var = TypeConversion(val, args[0], dest_type) var.set_expression(expression) - val.set_type(called) + val.set_type(dest_type) self._result.append(var) set_val(expression, val) @@ -455,14 +460,18 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, expr) return - # Early lookup to detect user defined types from other contracts definitions - # contract A { type MyInt is int} - # contract B { function f() public{ A.MyInt test = A.MyInt.wrap(1);}} - # The logic is handled by _post_call_expression if isinstance(expr, Contract): + # Early lookup to detect user defined types from other contracts definitions + # contract A { type MyInt is int} + # contract B { function f() public{ A.MyInt test = A.MyInt.wrap(1);}} + # The logic is handled by _post_call_expression if expression.member_name in expr.file_scope.user_defined_types: set_val(expression, expr.file_scope.user_defined_types[expression.member_name]) return + # Lookup errors referred to as member of contract e.g. Test.myError.selector + if expression.member_name in expr.custom_errors_as_dict: + set_val(expression, expr.custom_errors_as_dict[expression.member_name]) + return val = ReferenceVariable(self._node) member = Member(expr, Constant(expression.member_name), val) diff --git a/tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.zip b/tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.zip new file mode 100644 index 000000000..f1b133056 Binary files /dev/null and b/tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.zip differ diff --git a/tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.zip b/tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.zip new file mode 100644 index 000000000..3f457a34d Binary files /dev/null and b/tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.10-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.10-compact.zip new file mode 100644 index 000000000..0d7f3ac0f Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.10-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.11-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.11-compact.zip new file mode 100644 index 000000000..193a3c931 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.11-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.12-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.12-compact.zip new file mode 100644 index 000000000..02d8a6c37 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.12-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.13-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.13-compact.zip new file mode 100644 index 000000000..6225bd2c2 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.13-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.14-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.14-compact.zip new file mode 100644 index 000000000..6c13dbacc Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.14-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.15-compact.zip new file mode 100644 index 000000000..fb3120b43 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.4-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.4-compact.zip new file mode 100644 index 000000000..79aa6ddf6 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.4-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.5-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.5-compact.zip new file mode 100644 index 000000000..3c91ae99a Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.5-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.6-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.6-compact.zip new file mode 100644 index 000000000..0c0de9fb8 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.6-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.7-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.7-compact.zip new file mode 100644 index 000000000..7a3ef5b72 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.7-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.8-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.8-compact.zip new file mode 100644 index 000000000..0f1e60672 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.8-compact.zip differ diff --git a/tests/ast-parsing/compile/custom-error-selector.sol-0.8.9-compact.zip b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.9-compact.zip new file mode 100644 index 000000000..eae6bf9c1 Binary files /dev/null and b/tests/ast-parsing/compile/custom-error-selector.sol-0.8.9-compact.zip differ diff --git a/tests/ast-parsing/compile/top-level-struct-0.8.0.sol-0.8.0-compact.zip b/tests/ast-parsing/compile/top-level-struct-0.8.0.sol-0.8.0-compact.zip new file mode 100644 index 000000000..7cb2fb659 Binary files /dev/null and b/tests/ast-parsing/compile/top-level-struct-0.8.0.sol-0.8.0-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.10-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.10-compact.zip new file mode 100644 index 000000000..2dedfd0a6 Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.10-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.11-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.11-compact.zip new file mode 100644 index 000000000..373ed8650 Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.11-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.12-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.12-compact.zip new file mode 100644 index 000000000..573f4efc0 Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.12-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.13-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.13-compact.zip new file mode 100644 index 000000000..798230e9c Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.13-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.14-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.14-compact.zip new file mode 100644 index 000000000..db90bfcab Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.14-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.15-compact.zip new file mode 100644 index 000000000..3d3a0f0b2 Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.8-compact.zip b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.8-compact.zip new file mode 100644 index 000000000..89d650f85 Binary files /dev/null and b/tests/ast-parsing/compile/user_defined_value_type/using-for-0.8.8.sol-0.8.8-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..6505c61e0 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..7c6a28020 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..e51c777db Binary files /dev/null and b/tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..12452d862 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..c460e057f Binary files /dev/null and b/tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..4a0cfea17 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..6a6064a15 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..33ce99146 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..3944ab586 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..8889d83fb Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..baef95605 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-in-library-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-in-library-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..ca32c7583 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-in-library-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/yul-top-level-0.8.0.sol-0.8.0-compact.zip b/tests/ast-parsing/compile/yul-top-level-0.8.0.sol-0.8.0-compact.zip new file mode 100644 index 000000000..ce81a5c37 Binary files /dev/null and b/tests/ast-parsing/compile/yul-top-level-0.8.0.sol-0.8.0-compact.zip differ diff --git a/tests/ast-parsing/complex_imports/import_aliases_issue_1319/import.sol b/tests/ast-parsing/complex_imports/import_aliases_issue_1319/import.sol new file mode 100644 index 000000000..7cfff4bfa --- /dev/null +++ b/tests/ast-parsing/complex_imports/import_aliases_issue_1319/import.sol @@ -0,0 +1 @@ +contract A {} \ No newline at end of file diff --git a/tests/ast-parsing/complex_imports/import_aliases_issue_1319/test.sol b/tests/ast-parsing/complex_imports/import_aliases_issue_1319/test.sol new file mode 100644 index 000000000..7c5bf1eee --- /dev/null +++ b/tests/ast-parsing/complex_imports/import_aliases_issue_1319/test.sol @@ -0,0 +1,9 @@ +pragma solidity 0.5.12; + +import {A} from "./import.sol"; + +contract Z is A { + function test() public pure returns (uint) { + return 1; + } +} \ No newline at end of file diff --git a/tests/ast-parsing/complex_imports/import_aliases_issue_1319/test_fail.sol b/tests/ast-parsing/complex_imports/import_aliases_issue_1319/test_fail.sol new file mode 100644 index 000000000..eb2ab8af0 --- /dev/null +++ b/tests/ast-parsing/complex_imports/import_aliases_issue_1319/test_fail.sol @@ -0,0 +1,9 @@ +pragma solidity 0.5.12; + +import {A as X, A as Y} from "./import.sol"; + +contract Z is X { + function test() public pure returns (uint) { + return 1; + } +} \ No newline at end of file diff --git a/tests/ast-parsing/custom-error-selector.sol b/tests/ast-parsing/custom-error-selector.sol new file mode 100644 index 000000000..05fe75f99 --- /dev/null +++ b/tests/ast-parsing/custom-error-selector.sol @@ -0,0 +1,13 @@ + contract Test { + error myError(); +} + +interface VM { + function expectRevert(bytes4) external; + function expectRevert(bytes calldata) external; +} +contract A { + function b(address c) public { + VM(c).expectRevert(Test.myError.selector); + } +} diff --git a/tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.json b/tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.json new file mode 100644 index 000000000..4132f73d9 --- /dev/null +++ b/tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.json @@ -0,0 +1,6 @@ +{ + "A": {}, + "Z": { + "test()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.json b/tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.json new file mode 100644 index 000000000..4132f73d9 --- /dev/null +++ b/tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.json @@ -0,0 +1,6 @@ +{ + "A": {}, + "Z": { + "test()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.10-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.10-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.10-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.11-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.11-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.11-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.12-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.12-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.12-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.13-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.13-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.13-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.14-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.14-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.14-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.15-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.15-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.4-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.4-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.4-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.5-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.5-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.5-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.6-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.6-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.6-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.7-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.7-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.7-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.8-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.8-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.8-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/custom-error-selector.sol-0.8.9-compact.json b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.9-compact.json new file mode 100644 index 000000000..d35b14059 --- /dev/null +++ b/tests/ast-parsing/expected/custom-error-selector.sol-0.8.9-compact.json @@ -0,0 +1,10 @@ +{ + "Test": {}, + "VM": { + "expectRevert(bytes4)": "digraph{\n}\n", + "expectRevert(bytes)": "digraph{\n}\n" + }, + "A": { + "b(address)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/top-level-struct-0.8.0.sol-0.8.0-compact.json b/tests/ast-parsing/expected/top-level-struct-0.8.0.sol-0.8.0-compact.json new file mode 100644 index 000000000..65dfd3b51 --- /dev/null +++ b/tests/ast-parsing/expected/top-level-struct-0.8.0.sol-0.8.0-compact.json @@ -0,0 +1,3 @@ +{ + "BaseContract": {} +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.10-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.10-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.10-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.11-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.11-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.11-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.12-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.12-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.12-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.13-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.13-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.13-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.14-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.14-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.14-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.15-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.15-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.15-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.8-compact.json b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.8-compact.json new file mode 100644 index 000000000..58fb20ce1 --- /dev/null +++ b/tests/ast-parsing/expected/user_defined_value_type/using-for-0.8.8.sol-0.8.8-compact.json @@ -0,0 +1,11 @@ +{ + "MyLib": { + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n" + }, + "A": { + "b(MyType)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "c(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..01536efb9 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..01536efb9 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..a3b8987d4 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,8 @@ +{ + "Lib": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..6ff9c9780 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,8 @@ +{ + "Lib": { + "f(St,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "libCall(uint16)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..809fcb486 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,9 @@ +{ + "C": { + "topLevel(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n", + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + }, + "Lib": { + "b(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..d16a34808 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,9 @@ +{ + "Lib": { + "b(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "topLevel(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n", + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..01536efb9 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..a41d2ba18 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "topLevelCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..a41d2ba18 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "topLevelCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..01536efb9 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,10 @@ +{ + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..f2119ecf1 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,11 @@ +{ + "C": { + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n", + "topLevelCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + }, + "L1": { + "a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-in-library-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-in-library-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..4975ab40a --- /dev/null +++ b/tests/ast-parsing/expected/using-for-in-library-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,8 @@ +{ + "A": { + "a(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "B": { + "b(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/top-level-struct-0.8.0.sol b/tests/ast-parsing/top-level-struct-0.8.0.sol new file mode 100644 index 000000000..8a335680c --- /dev/null +++ b/tests/ast-parsing/top-level-struct-0.8.0.sol @@ -0,0 +1,18 @@ +struct my_struct { + uint[][] a; // works fine + uint[][3] b; // works fine + uint[3][] c; // fails + uint[3][3] d; // fails + uint[2**20] e; // works fine +} +contract BaseContract{ + struct my_struct_2 { + uint[][] f; // works fine + uint[][3] g; // works fine + uint[3][] h; // works fine + uint[3][3] i; // works fine + uint[2**20] j; // works fine + } + + uint[3][] k; // works fine +} diff --git a/tests/ast-parsing/user_defined_value_type/using-for-0.8.8.sol b/tests/ast-parsing/user_defined_value_type/using-for-0.8.8.sol new file mode 100644 index 000000000..bc65038ab --- /dev/null +++ b/tests/ast-parsing/user_defined_value_type/using-for-0.8.8.sol @@ -0,0 +1,22 @@ +type MyType is uint256; + +library MyLib { + using A for MyType; + using B for uint; + function a() internal returns(uint){ + MyType myvar = MyType.wrap(4); + return MyType.unwrap(myvar.b()).c(); + } +} + +library A { + function b(MyType e) public returns(MyType){ + return MyType.wrap(3); + } + +} +library B { + function c(uint e) public returns(uint){ + return 345; + } +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-1-0.8.0.sol b/tests/ast-parsing/using-for-1-0.8.0.sol new file mode 100644 index 000000000..201e18527 --- /dev/null +++ b/tests/ast-parsing/using-for-1-0.8.0.sol @@ -0,0 +1,40 @@ + +struct Data { mapping(uint => bool) flags; } +using L1 for Data; + +function d(Data storage self, uint value) returns(bool){ + return true; +} + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + Data knownValues; + + function libCall(uint value) public { + require(knownValues.a(value)); + } + +} diff --git a/tests/ast-parsing/using-for-2-0.8.0.sol b/tests/ast-parsing/using-for-2-0.8.0.sol new file mode 100644 index 000000000..cb0bb8ba1 --- /dev/null +++ b/tests/ast-parsing/using-for-2-0.8.0.sol @@ -0,0 +1,40 @@ + +struct Data { mapping(uint => bool) flags; } + +function d(Data storage self, uint value) returns(bool){ + return true; +} + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + using L1 for Data; + Data knownValues; + + function libCall(uint value) public { + require(knownValues.a(value)); + } + +} diff --git a/tests/ast-parsing/using-for-3-0.8.0.sol b/tests/ast-parsing/using-for-3-0.8.0.sol new file mode 100644 index 000000000..1da4f3dc6 --- /dev/null +++ b/tests/ast-parsing/using-for-3-0.8.0.sol @@ -0,0 +1,27 @@ +using {a} for Data; + +struct Data { mapping(uint => bool) flags; } + +function a(Data storage self, uint value, uint value2) returns(bool){ + return false; +} + +library Lib { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + using Lib for Data; + Data knownValues; + + function libCall(uint value) public { + require(knownValues.a(value)); + } + +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-4-0.8.0.sol b/tests/ast-parsing/using-for-4-0.8.0.sol new file mode 100644 index 000000000..d50e107a4 --- /dev/null +++ b/tests/ast-parsing/using-for-4-0.8.0.sol @@ -0,0 +1,25 @@ +using {f} for St; +struct St { uint field; } + + +function f(St storage self, uint8 v) view returns(uint){ + return 0; +} + + +library Lib { + function f(St storage self, uint256 v) public view returns (uint) { + return 1; + } + +} + +contract C { + using Lib for St; + St st; + + function libCall(uint16 v) public view returns(uint){ + return st.f(v); // return 1 + } + +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-alias-contract-0.8.0.sol b/tests/ast-parsing/using-for-alias-contract-0.8.0.sol new file mode 100644 index 000000000..d6906d5ab --- /dev/null +++ b/tests/ast-parsing/using-for-alias-contract-0.8.0.sol @@ -0,0 +1,14 @@ +import "./using-for-alias-dep1.sol"; + +contract C { + using {T3.a, T3.Lib.b} for uint256; + + function topLevel(uint256 value) public { + value.a(); + } + + function libCall(uint256 value) public { + value.b(); + } + +} diff --git a/tests/ast-parsing/using-for-alias-dep1.sol b/tests/ast-parsing/using-for-alias-dep1.sol new file mode 100644 index 000000000..db28e4a71 --- /dev/null +++ b/tests/ast-parsing/using-for-alias-dep1.sol @@ -0,0 +1,11 @@ +import "./using-for-alias-dep2.sol" as T3; + +function b(uint256 value) returns(bool) { + return true; +} + +library Lib { + function a(uint256 value) public returns(bool) { + return true; + } +} diff --git a/tests/ast-parsing/using-for-alias-dep2.sol b/tests/ast-parsing/using-for-alias-dep2.sol new file mode 100644 index 000000000..17ff96452 --- /dev/null +++ b/tests/ast-parsing/using-for-alias-dep2.sol @@ -0,0 +1,9 @@ +function a(uint256 value) returns(bool) { + return true; +} + +library Lib { + function b(uint256 value) public returns(bool) { + return true; + } +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-alias-top-level-0.8.0.sol b/tests/ast-parsing/using-for-alias-top-level-0.8.0.sol new file mode 100644 index 000000000..ed7e22bac --- /dev/null +++ b/tests/ast-parsing/using-for-alias-top-level-0.8.0.sol @@ -0,0 +1,15 @@ +import "./using-for-alias-dep1.sol"; + +using {T3.a, T3.Lib.b} for uint256; + +contract C { + + function topLevel(uint256 value) public { + value.a(); + } + + function libCall(uint256 value) public { + value.b(); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-1-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-1-0.8.0.sol new file mode 100644 index 000000000..52ed5087e --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-1-0.8.0.sol @@ -0,0 +1,37 @@ + +struct Data { mapping(uint => bool) flags; } + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + + using {L1.a, L1.b} for Data; + Data knownValues; + + function libCall(uint value) public { + require(knownValues.a(value)); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-2-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-2-0.8.0.sol new file mode 100644 index 000000000..d41163dee --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-2-0.8.0.sol @@ -0,0 +1,41 @@ + +struct Data { mapping(uint => bool) flags; } + +function d(Data storage self, uint value) returns(bool){ + return true; +} + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + + using {L1.a, L1.b, d} for Data; + Data knownValues; + + function topLevelCall(uint value) public { + require(knownValues.d(value)); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-3-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-3-0.8.0.sol new file mode 100644 index 000000000..7e5db5776 --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-3-0.8.0.sol @@ -0,0 +1,41 @@ + +struct Data { mapping(uint => bool) flags; } +using {L1.a, L1.b, d} for Data; + +function d(Data storage self, uint value) returns(bool){ + return true; +} + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + + Data knownValues; + + function topLevelCall(uint value) public { + require(knownValues.d(value)); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-4-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-4-0.8.0.sol new file mode 100644 index 000000000..ecce5e764 --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-4-0.8.0.sol @@ -0,0 +1,40 @@ + +struct Data { mapping(uint => bool) flags; } +using {L1.a, L1.b, d} for Data; + +function d(Data storage self, uint value) returns(bool){ + return true; +} + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} + +contract C { + Data knownValues; + + function libCall(uint value) public { + require(knownValues.a(value)); + } + +} diff --git a/tests/ast-parsing/using-for-global-0.8.0.sol b/tests/ast-parsing/using-for-global-0.8.0.sol new file mode 100644 index 000000000..c5b112259 --- /dev/null +++ b/tests/ast-parsing/using-for-global-0.8.0.sol @@ -0,0 +1,15 @@ +import "./using-for-library-0.8.0.sol"; + +contract C { + Data knownValues; + + function libCall(uint value) public { + require(knownValues.a(value)); + } + + function topLevelCall(uint value) public { + require(knownValues.d(value)); + } + + +} diff --git a/tests/ast-parsing/using-for-in-library-0.8.0.sol b/tests/ast-parsing/using-for-in-library-0.8.0.sol new file mode 100644 index 000000000..0e8f6a6b9 --- /dev/null +++ b/tests/ast-parsing/using-for-in-library-0.8.0.sol @@ -0,0 +1,14 @@ + +library A { + using B for uint256; + + function a(uint256 v) public view returns (uint) { + return v.b(); + } +} + +library B { + function b(uint256 v) public view returns (uint) { + return 1; + } +} diff --git a/tests/ast-parsing/using-for-library-0.8.0.sol b/tests/ast-parsing/using-for-library-0.8.0.sol new file mode 100644 index 000000000..97ee39ca6 --- /dev/null +++ b/tests/ast-parsing/using-for-library-0.8.0.sol @@ -0,0 +1,32 @@ + +struct Data { mapping(uint => bool) flags; } +using L1 for Data global; +using {d} for Data global; + +function d(Data storage self, uint value) returns(bool){ + return true; +} + +library L1 { + function a(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function b(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + + function c(Data storage self, uint value) public + view + returns (bool) + { + return true; + } + +} diff --git a/tests/ast-parsing/yul-top-level-0.8.0.sol b/tests/ast-parsing/yul-top-level-0.8.0.sol new file mode 100644 index 000000000..214db9cb4 --- /dev/null +++ b/tests/ast-parsing/yul-top-level-0.8.0.sol @@ -0,0 +1,16 @@ +function top_level_yul(int256 c) pure returns (uint result) { + assembly { + function internal_yul(a) -> b { + b := a + } + + result := internal_yul(c) + } +} + + +contract Test { + function test() public{ + top_level_yul(10); + } +} \ No newline at end of file diff --git a/tests/constant_folding_binop.sol b/tests/constant_folding_binop.sol new file mode 100644 index 000000000..923418ce7 --- /dev/null +++ b/tests/constant_folding_binop.sol @@ -0,0 +1,14 @@ +contract BinOp { + uint a = 1 & 2; + uint b = 1 ^ 2; + uint c = 1 | 2; + bool d = 2 < 1; + bool e = 1 > 2; + bool f = 1 <= 2; + bool g = 1 >= 2; + bool h = 1 == 2; + bool i = 1 != 2; + bool j = true && false; + bool k = true || false; + uint l = uint(1) - uint(2); +} \ No newline at end of file diff --git a/tests/detectors/constable-states/0.4.25/const_state_variables.sol b/tests/detectors/constable-states/0.4.25/const_state_variables.sol index aed05d97f..45f8cc87a 100644 --- a/tests/detectors/constable-states/0.4.25/const_state_variables.sol +++ b/tests/detectors/constable-states/0.4.25/const_state_variables.sol @@ -44,7 +44,12 @@ contract MyConc{ address not_constant = msg.sender; uint not_constant_2 = getNumber(); uint not_constant_3 = 10 + block.number; + uint not_constant_5; + constructor(uint b) public { + not_constant_5 = b; + } + function getNumber() public returns(uint){ return block.number; } diff --git a/tests/detectors/constable-states/0.4.25/const_state_variables.sol.0.4.25.ConstCandidateStateVars.json b/tests/detectors/constable-states/0.4.25/const_state_variables.sol.0.4.25.CouldBeConstant.json similarity index 91% rename from tests/detectors/constable-states/0.4.25/const_state_variables.sol.0.4.25.ConstCandidateStateVars.json rename to tests/detectors/constable-states/0.4.25/const_state_variables.sol.0.4.25.CouldBeConstant.json index 4ab2cfa83..51a485f5b 100644 --- a/tests/detectors/constable-states/0.4.25/const_state_variables.sol.0.4.25.ConstCandidateStateVars.json +++ b/tests/detectors/constable-states/0.4.25/const_state_variables.sol.0.4.25.CouldBeConstant.json @@ -4,19 +4,19 @@ "elements": [ { "type": "variable", - "name": "myFriendsAddress", + "name": "text2", "source_mapping": { - "start": 132, - "length": 76, + "start": 333, + "length": 20, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "is_dependency": false, "lines": [ - 7 + 14 ], "starting_column": 5, - "ending_column": 81 + "ending_column": 25 }, "type_specific_fields": { "parent": { @@ -56,10 +56,10 @@ } } ], - "description": "A.myFriendsAddress (tests/detectors/constable-states/0.4.25/const_state_variables.sol#7) should be constant\n", - "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L7) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L7", - "id": "1454db80653b732bf6acbe54ff0ae4707002207a2a8216708c12d61c88a43e5f", + "description": "A.text2 (tests/detectors/constable-states/0.4.25/const_state_variables.sol#14) should be constant \n", + "markdown": "[A.text2](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L14) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L14", + "id": "2f06e04545cea7e7a8998c65d5419f335bf2579a6ce6a832eac9c87392fd5c1a", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -68,19 +68,79 @@ "elements": [ { "type": "variable", - "name": "test", + "name": "mySistersAddress", "source_mapping": { - "start": 237, - "length": 20, + "start": 496, + "length": 76, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "is_dependency": false, "lines": [ - 10 + 26 ], "starting_column": 5, - "ending_column": 25 + "ending_column": 81 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "B", + "source_mapping": { + "start": 473, + "length": 271, + "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "B.mySistersAddress (tests/detectors/constable-states/0.4.25/const_state_variables.sol#26) should be constant \n", + "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L26) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L26", + "id": "3b5bff93954a48a79387e7981e8c45d78edc575a0988a10f1c7f439b9f930539", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "myFriendsAddress", + "source_mapping": { + "start": 132, + "length": 76, + "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 7 + ], + "starting_column": 5, + "ending_column": 81 }, "type_specific_fields": { "parent": { @@ -120,10 +180,10 @@ } } ], - "description": "A.test (tests/detectors/constable-states/0.4.25/const_state_variables.sol#10) should be constant\n", - "markdown": "[A.test](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L10) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L10", - "id": "5d9e3fb413322b71a93e90f7e89bd8c83cd4884d577d039598c681fe9db38b1d", + "description": "A.myFriendsAddress (tests/detectors/constable-states/0.4.25/const_state_variables.sol#7) should be constant \n", + "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L7) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L7", + "id": "52fd72f6870c4b504d1bcf9fb44249658e2077474d66208a33a47d2668b8db49", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -132,19 +192,19 @@ "elements": [ { "type": "variable", - "name": "should_be_constant_2", + "name": "should_be_constant", "source_mapping": { - "start": 841, - "length": 33, + "start": 793, + "length": 42, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "is_dependency": false, "lines": [ - 43 + 42 ], "starting_column": 5, - "ending_column": 38 + "ending_column": 47 }, "type_specific_fields": { "parent": { @@ -152,7 +212,7 @@ "name": "MyConc", "source_mapping": { "start": 746, - "length": 342, + "length": 423, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", @@ -171,7 +231,12 @@ 49, 50, 51, - 52 + 52, + 53, + 54, + 55, + 56, + 57 ], "starting_column": 1, "ending_column": 2 @@ -180,70 +245,10 @@ } } ], - "description": "MyConc.should_be_constant_2 (tests/detectors/constable-states/0.4.25/const_state_variables.sol#43) should be constant\n", - "markdown": "[MyConc.should_be_constant_2](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L43) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L43", - "id": "9a48a4122de1a6a4774a9f1e0d4917bd0fa08f17b4af41b86ba07689e51bf711", - "check": "constable-states", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [ - { - "type": "variable", - "name": "mySistersAddress", - "source_mapping": { - "start": 496, - "length": 76, - "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", - "filename_absolute": "/GENERIC_PATH", - "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", - "is_dependency": false, - "lines": [ - 26 - ], - "starting_column": 5, - "ending_column": 81 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "B", - "source_mapping": { - "start": 473, - "length": 271, - "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", - "filename_absolute": "/GENERIC_PATH", - "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", - "is_dependency": false, - "lines": [ - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37 - ], - "starting_column": 1, - "ending_column": 2 - } - } - } - } - ], - "description": "B.mySistersAddress (tests/detectors/constable-states/0.4.25/const_state_variables.sol#26) should be constant\n", - "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L26) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L26", - "id": "bee93a722c8eae4a48aade67d8ef537d84c106f48fc9eb738c795fce10d3bc63", + "description": "MyConc.should_be_constant (tests/detectors/constable-states/0.4.25/const_state_variables.sol#42) should be constant \n", + "markdown": "[MyConc.should_be_constant](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L42) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L42", + "id": "8d08797efc8230b480ec669c7e2bf53c3b3d16bc59bf7770934b34fd892934f8", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -252,19 +257,19 @@ "elements": [ { "type": "variable", - "name": "should_be_constant", + "name": "should_be_constant_2", "source_mapping": { - "start": 793, - "length": 42, + "start": 841, + "length": 33, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "is_dependency": false, "lines": [ - 42 + 43 ], "starting_column": 5, - "ending_column": 47 + "ending_column": 38 }, "type_specific_fields": { "parent": { @@ -272,7 +277,7 @@ "name": "MyConc", "source_mapping": { "start": 746, - "length": 342, + "length": 423, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", @@ -291,7 +296,12 @@ 49, 50, 51, - 52 + 52, + 53, + 54, + 55, + 56, + 57 ], "starting_column": 1, "ending_column": 2 @@ -300,10 +310,10 @@ } } ], - "description": "MyConc.should_be_constant (tests/detectors/constable-states/0.4.25/const_state_variables.sol#42) should be constant\n", - "markdown": "[MyConc.should_be_constant](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L42) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L42", - "id": "cbcafa2a3efba4d21ac1b51b4b823e5082d556bc3d6cf3fd2ab3188f9f218fc1", + "description": "MyConc.should_be_constant_2 (tests/detectors/constable-states/0.4.25/const_state_variables.sol#43) should be constant \n", + "markdown": "[MyConc.should_be_constant_2](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L43) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L43", + "id": "d08c6d1e331083b42c45c222691dd1e6d880814c66d114971875337ca61ba9c9", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -312,16 +322,16 @@ "elements": [ { "type": "variable", - "name": "text2", + "name": "test", "source_mapping": { - "start": 333, + "start": 237, "length": 20, "filename_relative": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.4.25/const_state_variables.sol", "is_dependency": false, "lines": [ - 14 + 10 ], "starting_column": 5, "ending_column": 25 @@ -364,10 +374,10 @@ } } ], - "description": "A.text2 (tests/detectors/constable-states/0.4.25/const_state_variables.sol#14) should be constant\n", - "markdown": "[A.text2](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L14) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L14", - "id": "df11e6201c4558a8c5cd90b55b134b9ca8f07203b2264d3aa93bd7745e8cb4ba", + "description": "A.test (tests/detectors/constable-states/0.4.25/const_state_variables.sol#10) should be constant \n", + "markdown": "[A.test](tests/detectors/constable-states/0.4.25/const_state_variables.sol#L10) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.4.25/const_state_variables.sol#L10", + "id": "e407a1b57b4d25949ef7c4e6d97197605857099a94774a9c7a848d7dd3463668", "check": "constable-states", "impact": "Optimization", "confidence": "High" diff --git a/tests/detectors/constable-states/0.5.16/const_state_variables.sol b/tests/detectors/constable-states/0.5.16/const_state_variables.sol index aed05d97f..8fd1ca808 100644 --- a/tests/detectors/constable-states/0.5.16/const_state_variables.sol +++ b/tests/detectors/constable-states/0.5.16/const_state_variables.sol @@ -41,10 +41,16 @@ contract MyConc{ uint constant A = 1; bytes32 should_be_constant = sha256('abc'); uint should_be_constant_2 = A + 1; + B should_be_constant_3 = B(address(0)); address not_constant = msg.sender; uint not_constant_2 = getNumber(); uint not_constant_3 = 10 + block.number; + uint not_constant_5; + constructor(uint b) public { + not_constant_5 = b; + } + function getNumber() public returns(uint){ return block.number; } diff --git a/tests/detectors/constable-states/0.5.16/const_state_variables.sol.0.5.16.ConstCandidateStateVars.json b/tests/detectors/constable-states/0.5.16/const_state_variables.sol.0.5.16.CouldBeConstant.json similarity index 78% rename from tests/detectors/constable-states/0.5.16/const_state_variables.sol.0.5.16.ConstCandidateStateVars.json rename to tests/detectors/constable-states/0.5.16/const_state_variables.sol.0.5.16.CouldBeConstant.json index 9b435a4a8..f9ed17eb1 100644 --- a/tests/detectors/constable-states/0.5.16/const_state_variables.sol.0.5.16.ConstCandidateStateVars.json +++ b/tests/detectors/constable-states/0.5.16/const_state_variables.sol.0.5.16.CouldBeConstant.json @@ -4,50 +4,52 @@ "elements": [ { "type": "variable", - "name": "myFriendsAddress", + "name": "should_be_constant_3", "source_mapping": { - "start": 132, - "length": 76, + "start": 880, + "length": 38, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 7 + 44 ], "starting_column": 5, - "ending_column": 81 + "ending_column": 43 }, "type_specific_fields": { "parent": { "type": "contract", - "name": "A", + "name": "MyConc", "source_mapping": { - "start": 29, - "length": 441, + "start": 746, + "length": 467, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21 + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58 ], "starting_column": 1, "ending_column": 2 @@ -56,10 +58,10 @@ } } ], - "description": "A.myFriendsAddress (tests/detectors/constable-states/0.5.16/const_state_variables.sol#7) should be constant\n", - "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L7) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L7", - "id": "1454db80653b732bf6acbe54ff0ae4707002207a2a8216708c12d61c88a43e5f", + "description": "MyConc.should_be_constant_3 (tests/detectors/constable-states/0.5.16/const_state_variables.sol#44) should be constant \n", + "markdown": "[MyConc.should_be_constant_3](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L44) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L44", + "id": "29247b0a9939e854ad51bf3b2f58705156aa8b7e446e646b1832467d362b5b3e", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -68,16 +70,16 @@ "elements": [ { "type": "variable", - "name": "test", + "name": "text2", "source_mapping": { - "start": 237, + "start": 333, "length": 20, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 10 + 14 ], "starting_column": 5, "ending_column": 25 @@ -120,10 +122,10 @@ } } ], - "description": "A.test (tests/detectors/constable-states/0.5.16/const_state_variables.sol#10) should be constant\n", - "markdown": "[A.test](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L10) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L10", - "id": "5d9e3fb413322b71a93e90f7e89bd8c83cd4884d577d039598c681fe9db38b1d", + "description": "A.text2 (tests/detectors/constable-states/0.5.16/const_state_variables.sol#14) should be constant \n", + "markdown": "[A.text2](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L14) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L14", + "id": "2f06e04545cea7e7a8998c65d5419f335bf2579a6ce6a832eac9c87392fd5c1a", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -132,46 +134,46 @@ "elements": [ { "type": "variable", - "name": "should_be_constant_2", + "name": "mySistersAddress", "source_mapping": { - "start": 841, - "length": 33, + "start": 496, + "length": 76, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 43 + 26 ], "starting_column": 5, - "ending_column": 38 + "ending_column": 81 }, "type_specific_fields": { "parent": { "type": "contract", - "name": "MyConc", + "name": "B", "source_mapping": { - "start": 746, - "length": 342, + "start": 473, + "length": 271, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52 + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37 ], "starting_column": 1, "ending_column": 2 @@ -180,10 +182,10 @@ } } ], - "description": "MyConc.should_be_constant_2 (tests/detectors/constable-states/0.5.16/const_state_variables.sol#43) should be constant\n", - "markdown": "[MyConc.should_be_constant_2](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L43) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L43", - "id": "9a48a4122de1a6a4774a9f1e0d4917bd0fa08f17b4af41b86ba07689e51bf711", + "description": "B.mySistersAddress (tests/detectors/constable-states/0.5.16/const_state_variables.sol#26) should be constant \n", + "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L26) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L26", + "id": "3b5bff93954a48a79387e7981e8c45d78edc575a0988a10f1c7f439b9f930539", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -192,16 +194,16 @@ "elements": [ { "type": "variable", - "name": "mySistersAddress", + "name": "myFriendsAddress", "source_mapping": { - "start": 496, + "start": 132, "length": 76, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 26 + 7 ], "starting_column": 5, "ending_column": 81 @@ -209,29 +211,33 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "B", + "name": "A", "source_mapping": { - "start": 473, - "length": 271, + "start": 29, + "length": 441, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37 + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21 ], "starting_column": 1, "ending_column": 2 @@ -240,10 +246,10 @@ } } ], - "description": "B.mySistersAddress (tests/detectors/constable-states/0.5.16/const_state_variables.sol#26) should be constant\n", - "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L26) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L26", - "id": "bee93a722c8eae4a48aade67d8ef537d84c106f48fc9eb738c795fce10d3bc63", + "description": "A.myFriendsAddress (tests/detectors/constable-states/0.5.16/const_state_variables.sol#7) should be constant \n", + "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L7) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L7", + "id": "52fd72f6870c4b504d1bcf9fb44249658e2077474d66208a33a47d2668b8db49", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -272,7 +278,7 @@ "name": "MyConc", "source_mapping": { "start": 746, - "length": 342, + "length": 467, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", @@ -291,7 +297,13 @@ 49, 50, 51, - 52 + 52, + 53, + 54, + 55, + 56, + 57, + 58 ], "starting_column": 1, "ending_column": 2 @@ -300,10 +312,10 @@ } } ], - "description": "MyConc.should_be_constant (tests/detectors/constable-states/0.5.16/const_state_variables.sol#42) should be constant\n", - "markdown": "[MyConc.should_be_constant](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L42) should be constant\n", + "description": "MyConc.should_be_constant (tests/detectors/constable-states/0.5.16/const_state_variables.sol#42) should be constant \n", + "markdown": "[MyConc.should_be_constant](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L42) should be constant \n", "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L42", - "id": "cbcafa2a3efba4d21ac1b51b4b823e5082d556bc3d6cf3fd2ab3188f9f218fc1", + "id": "8d08797efc8230b480ec669c7e2bf53c3b3d16bc59bf7770934b34fd892934f8", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -312,16 +324,82 @@ "elements": [ { "type": "variable", - "name": "text2", + "name": "should_be_constant_2", "source_mapping": { - "start": 333, + "start": 841, + "length": 33, + "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 43 + ], + "starting_column": 5, + "ending_column": 38 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "MyConc", + "source_mapping": { + "start": 746, + "length": 467, + "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "MyConc.should_be_constant_2 (tests/detectors/constable-states/0.5.16/const_state_variables.sol#43) should be constant \n", + "markdown": "[MyConc.should_be_constant_2](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L43) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L43", + "id": "d08c6d1e331083b42c45c222691dd1e6d880814c66d114971875337ca61ba9c9", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "test", + "source_mapping": { + "start": 237, "length": 20, "filename_relative": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.5.16/const_state_variables.sol", "is_dependency": false, "lines": [ - 14 + 10 ], "starting_column": 5, "ending_column": 25 @@ -364,10 +442,10 @@ } } ], - "description": "A.text2 (tests/detectors/constable-states/0.5.16/const_state_variables.sol#14) should be constant\n", - "markdown": "[A.text2](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L14) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L14", - "id": "df11e6201c4558a8c5cd90b55b134b9ca8f07203b2264d3aa93bd7745e8cb4ba", + "description": "A.test (tests/detectors/constable-states/0.5.16/const_state_variables.sol#10) should be constant \n", + "markdown": "[A.test](tests/detectors/constable-states/0.5.16/const_state_variables.sol#L10) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.5.16/const_state_variables.sol#L10", + "id": "e407a1b57b4d25949ef7c4e6d97197605857099a94774a9c7a848d7dd3463668", "check": "constable-states", "impact": "Optimization", "confidence": "High" diff --git a/tests/detectors/constable-states/0.6.11/const_state_variables.sol b/tests/detectors/constable-states/0.6.11/const_state_variables.sol index 3b0c1a436..17548f46f 100644 --- a/tests/detectors/constable-states/0.6.11/const_state_variables.sol +++ b/tests/detectors/constable-states/0.6.11/const_state_variables.sol @@ -1,5 +1,3 @@ -//pragma solidity ^0.4.24; - contract A { @@ -36,17 +34,46 @@ contract B is A { } } -contract MyConc{ +contract Bad { uint constant A = 1; bytes32 should_be_constant = sha256('abc'); uint should_be_constant_2 = A + 1; - address not_constant = msg.sender; - uint not_constant_2 = getNumber(); - uint not_constant_3 = 10 + block.number; + B should_be_constant_3 = B(address(0)); + address should_be_immutable = msg.sender; + uint should_be_immutable_2 = getNumber(); + uint should_be_immutable_3 = 10 + block.number; + B should_be_immutable_4 = new B(); + uint should_be_immutable_5; + constructor(uint b) public { + should_be_immutable_5 = b; + } + function getNumber() public returns(uint){ return block.number; } } + +contract Good { + + uint constant A = 1; + bytes32 constant should_be_constant = sha256('abc'); + uint constant should_be_constant_2 = A + 1; + B constant should_be_constant_3 = B(address(0)); + address immutable should_be_immutable = msg.sender; + uint immutable should_be_immutable_2 = getNumber(); + uint immutable should_be_immutable_3 = 10 + block.number; + B immutable should_be_immutable_4 = new B(); + uint immutable should_be_immutable_5; + + constructor(uint b) public { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} \ No newline at end of file diff --git a/tests/detectors/constable-states/0.6.11/const_state_variables.sol.0.6.11.ConstCandidateStateVars.json b/tests/detectors/constable-states/0.6.11/const_state_variables.sol.0.6.11.CouldBeConstant.json similarity index 67% rename from tests/detectors/constable-states/0.6.11/const_state_variables.sol.0.6.11.ConstCandidateStateVars.json rename to tests/detectors/constable-states/0.6.11/const_state_variables.sol.0.6.11.CouldBeConstant.json index 3c7812877..ac3608f81 100644 --- a/tests/detectors/constable-states/0.6.11/const_state_variables.sol.0.6.11.ConstCandidateStateVars.json +++ b/tests/detectors/constable-states/0.6.11/const_state_variables.sol.0.6.11.CouldBeConstant.json @@ -4,32 +4,34 @@ "elements": [ { "type": "variable", - "name": "myFriendsAddress", + "name": "text2", "source_mapping": { - "start": 132, - "length": 76, + "start": 305, + "length": 20, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 7 + 12 ], "starting_column": 5, - "ending_column": 81 + "ending_column": 25 }, "type_specific_fields": { "parent": { "type": "contract", "name": "A", "source_mapping": { - "start": 29, + "start": 1, "length": 441, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ + 2, + 3, 4, 5, 6, @@ -45,9 +47,7 @@ 16, 17, 18, - 19, - 20, - 21 + 19 ], "starting_column": 1, "ending_column": 2 @@ -56,10 +56,10 @@ } } ], - "description": "A.myFriendsAddress (tests/detectors/constable-states/0.6.11/const_state_variables.sol#7) should be constant\n", - "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L7) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L7", - "id": "1454db80653b732bf6acbe54ff0ae4707002207a2a8216708c12d61c88a43e5f", + "description": "A.text2 (tests/detectors/constable-states/0.6.11/const_state_variables.sol#12) should be constant \n", + "markdown": "[A.text2](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L12) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L12", + "id": "2f06e04545cea7e7a8998c65d5419f335bf2579a6ce6a832eac9c87392fd5c1a", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -68,50 +68,53 @@ "elements": [ { "type": "variable", - "name": "test", + "name": "should_be_constant_2", "source_mapping": { - "start": 237, - "length": 20, + "start": 811, + "length": 33, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 10 + 41 ], "starting_column": 5, - "ending_column": 25 + "ending_column": 38 }, "type_specific_fields": { "parent": { "type": "contract", - "name": "A", + "name": "Bad", "source_mapping": { - "start": 29, - "length": 441, + "start": 718, + "length": 539, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21 + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 ], "starting_column": 1, "ending_column": 2 @@ -120,10 +123,10 @@ } } ], - "description": "A.test (tests/detectors/constable-states/0.6.11/const_state_variables.sol#10) should be constant\n", - "markdown": "[A.test](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L10) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L10", - "id": "5d9e3fb413322b71a93e90f7e89bd8c83cd4884d577d039598c681fe9db38b1d", + "description": "Bad.should_be_constant_2 (tests/detectors/constable-states/0.6.11/const_state_variables.sol#41) should be constant \n", + "markdown": "[Bad.should_be_constant_2](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L41) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L41", + "id": "3a8b682f7960750cd8228d6cd3d0bb5d7d6f9faaf1a044de2fa7069d8e475af2", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -132,46 +135,46 @@ "elements": [ { "type": "variable", - "name": "should_be_constant_2", + "name": "mySistersAddress", "source_mapping": { - "start": 841, - "length": 33, + "start": 468, + "length": 76, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 43 + 24 ], "starting_column": 5, - "ending_column": 38 + "ending_column": 81 }, "type_specific_fields": { "parent": { "type": "contract", - "name": "MyConc", + "name": "B", "source_mapping": { - "start": 746, - "length": 342, + "start": 445, + "length": 271, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52 + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35 ], "starting_column": 1, "ending_column": 2 @@ -180,10 +183,10 @@ } } ], - "description": "MyConc.should_be_constant_2 (tests/detectors/constable-states/0.6.11/const_state_variables.sol#43) should be constant\n", - "markdown": "[MyConc.should_be_constant_2](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L43) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L43", - "id": "9a48a4122de1a6a4774a9f1e0d4917bd0fa08f17b4af41b86ba07689e51bf711", + "description": "B.mySistersAddress (tests/detectors/constable-states/0.6.11/const_state_variables.sol#24) should be constant \n", + "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L24) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L24", + "id": "3b5bff93954a48a79387e7981e8c45d78edc575a0988a10f1c7f439b9f930539", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -192,16 +195,16 @@ "elements": [ { "type": "variable", - "name": "mySistersAddress", + "name": "myFriendsAddress", "source_mapping": { - "start": 496, + "start": 104, "length": 76, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 26 + 5 ], "starting_column": 5, "ending_column": 81 @@ -209,29 +212,33 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "B", + "name": "A", "source_mapping": { - "start": 473, - "length": 271, + "start": 1, + "length": 441, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37 + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19 ], "starting_column": 1, "ending_column": 2 @@ -240,10 +247,10 @@ } } ], - "description": "B.mySistersAddress (tests/detectors/constable-states/0.6.11/const_state_variables.sol#26) should be constant\n", - "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L26) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L26", - "id": "bee93a722c8eae4a48aade67d8ef537d84c106f48fc9eb738c795fce10d3bc63", + "description": "A.myFriendsAddress (tests/detectors/constable-states/0.6.11/const_state_variables.sol#5) should be constant \n", + "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L5) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L5", + "id": "52fd72f6870c4b504d1bcf9fb44249658e2077474d66208a33a47d2668b8db49", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -254,14 +261,14 @@ "type": "variable", "name": "should_be_constant", "source_mapping": { - "start": 793, + "start": 763, "length": 42, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 42 + 40 ], "starting_column": 5, "ending_column": 47 @@ -269,15 +276,84 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "MyConc", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 539, + "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_constant (tests/detectors/constable-states/0.6.11/const_state_variables.sol#40) should be constant \n", + "markdown": "[Bad.should_be_constant](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L40) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L40", + "id": "87097c03d57b72ad7c15336eb44e5a30054c50f8daff32e08bc4fbd97852961c", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_constant_3", + "source_mapping": { + "start": 850, + "length": 38, + "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 42 + ], + "starting_column": 5, + "ending_column": 43 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", "source_mapping": { - "start": 746, - "length": 342, + "start": 718, + "length": 539, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ + 37, + 38, 39, 40, 41, @@ -291,7 +367,12 @@ 49, 50, 51, - 52 + 52, + 53, + 54, + 55, + 56, + 57 ], "starting_column": 1, "ending_column": 2 @@ -300,10 +381,10 @@ } } ], - "description": "MyConc.should_be_constant (tests/detectors/constable-states/0.6.11/const_state_variables.sol#42) should be constant\n", - "markdown": "[MyConc.should_be_constant](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L42) should be constant\n", + "description": "Bad.should_be_constant_3 (tests/detectors/constable-states/0.6.11/const_state_variables.sol#42) should be constant \n", + "markdown": "[Bad.should_be_constant_3](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L42) should be constant \n", "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L42", - "id": "cbcafa2a3efba4d21ac1b51b4b823e5082d556bc3d6cf3fd2ab3188f9f218fc1", + "id": "8e991c1370b1adb10f01f2d7e48f341dee92a98b91b56ccb291d9149d2da97d0", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -312,16 +393,16 @@ "elements": [ { "type": "variable", - "name": "text2", + "name": "test", "source_mapping": { - "start": 333, + "start": 209, "length": 20, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ - 14 + 8 ], "starting_column": 5, "ending_column": 25 @@ -331,13 +412,15 @@ "type": "contract", "name": "A", "source_mapping": { - "start": 29, + "start": 1, "length": 441, "filename_relative": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.6.11/const_state_variables.sol", "is_dependency": false, "lines": [ + 2, + 3, 4, 5, 6, @@ -353,9 +436,7 @@ 16, 17, 18, - 19, - 20, - 21 + 19 ], "starting_column": 1, "ending_column": 2 @@ -364,10 +445,10 @@ } } ], - "description": "A.text2 (tests/detectors/constable-states/0.6.11/const_state_variables.sol#14) should be constant\n", - "markdown": "[A.text2](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L14) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L14", - "id": "df11e6201c4558a8c5cd90b55b134b9ca8f07203b2264d3aa93bd7745e8cb4ba", + "description": "A.test (tests/detectors/constable-states/0.6.11/const_state_variables.sol#8) should be constant \n", + "markdown": "[A.test](tests/detectors/constable-states/0.6.11/const_state_variables.sol#L8) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.6.11/const_state_variables.sol#L8", + "id": "e407a1b57b4d25949ef7c4e6d97197605857099a94774a9c7a848d7dd3463668", "check": "constable-states", "impact": "Optimization", "confidence": "High" diff --git a/tests/detectors/constable-states/0.7.6/const_state_variables.sol b/tests/detectors/constable-states/0.7.6/const_state_variables.sol index 3b0c1a436..297dd6294 100644 --- a/tests/detectors/constable-states/0.7.6/const_state_variables.sol +++ b/tests/detectors/constable-states/0.7.6/const_state_variables.sol @@ -1,5 +1,3 @@ -//pragma solidity ^0.4.24; - contract A { @@ -36,15 +34,43 @@ contract B is A { } } -contract MyConc{ +contract Bad { uint constant A = 1; bytes32 should_be_constant = sha256('abc'); uint should_be_constant_2 = A + 1; - address not_constant = msg.sender; - uint not_constant_2 = getNumber(); - uint not_constant_3 = 10 + block.number; + B should_be_constant_3 = B(address(0)); + address should_be_immutable = msg.sender; + uint should_be_immutable_2 = getNumber(); + uint should_be_immutable_3 = 10 + block.number; + B should_be_immutable_4 = new B(); + uint should_be_immutable_5; + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } +} + +contract Good { + + uint constant A = 1; + bytes32 constant should_be_constant = sha256('abc'); + uint constant should_be_constant_2 = A + 1; + B constant should_be_constant_3 = B(address(0)); + address immutable should_be_immutable = msg.sender; + uint immutable should_be_immutable_2 = getNumber(); + uint immutable should_be_immutable_3 = 10 + block.number; + B immutable should_be_immutable_4 = new B(); + uint immutable should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + function getNumber() public returns(uint){ return block.number; } diff --git a/tests/detectors/constable-states/0.7.6/const_state_variables.sol.0.7.6.ConstCandidateStateVars.json b/tests/detectors/constable-states/0.7.6/const_state_variables.sol.0.7.6.CouldBeConstant.json similarity index 67% rename from tests/detectors/constable-states/0.7.6/const_state_variables.sol.0.7.6.ConstCandidateStateVars.json rename to tests/detectors/constable-states/0.7.6/const_state_variables.sol.0.7.6.CouldBeConstant.json index 3cb6d44c4..d54beb405 100644 --- a/tests/detectors/constable-states/0.7.6/const_state_variables.sol.0.7.6.ConstCandidateStateVars.json +++ b/tests/detectors/constable-states/0.7.6/const_state_variables.sol.0.7.6.CouldBeConstant.json @@ -4,32 +4,34 @@ "elements": [ { "type": "variable", - "name": "myFriendsAddress", + "name": "text2", "source_mapping": { - "start": 132, - "length": 76, + "start": 305, + "length": 20, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 7 + 12 ], "starting_column": 5, - "ending_column": 81 + "ending_column": 25 }, "type_specific_fields": { "parent": { "type": "contract", "name": "A", "source_mapping": { - "start": 29, + "start": 1, "length": 441, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ + 2, + 3, 4, 5, 6, @@ -45,9 +47,7 @@ 16, 17, 18, - 19, - 20, - 21 + 19 ], "starting_column": 1, "ending_column": 2 @@ -56,10 +56,10 @@ } } ], - "description": "A.myFriendsAddress (tests/detectors/constable-states/0.7.6/const_state_variables.sol#7) should be constant\n", - "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L7) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L7", - "id": "1454db80653b732bf6acbe54ff0ae4707002207a2a8216708c12d61c88a43e5f", + "description": "A.text2 (tests/detectors/constable-states/0.7.6/const_state_variables.sol#12) should be constant \n", + "markdown": "[A.text2](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L12) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L12", + "id": "2f06e04545cea7e7a8998c65d5419f335bf2579a6ce6a832eac9c87392fd5c1a", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -68,50 +68,52 @@ "elements": [ { "type": "variable", - "name": "test", + "name": "should_be_constant_2", "source_mapping": { - "start": 237, - "length": 20, + "start": 811, + "length": 33, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 10 + 41 ], "starting_column": 5, - "ending_column": 25 + "ending_column": 38 }, "type_specific_fields": { "parent": { "type": "contract", - "name": "A", + "name": "Bad", "source_mapping": { - "start": 29, - "length": 441, + "start": 718, + "length": 531, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21 + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 ], "starting_column": 1, "ending_column": 2 @@ -120,10 +122,10 @@ } } ], - "description": "A.test (tests/detectors/constable-states/0.7.6/const_state_variables.sol#10) should be constant\n", - "markdown": "[A.test](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L10) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L10", - "id": "5d9e3fb413322b71a93e90f7e89bd8c83cd4884d577d039598c681fe9db38b1d", + "description": "Bad.should_be_constant_2 (tests/detectors/constable-states/0.7.6/const_state_variables.sol#41) should be constant \n", + "markdown": "[Bad.should_be_constant_2](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L41) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L41", + "id": "3a8b682f7960750cd8228d6cd3d0bb5d7d6f9faaf1a044de2fa7069d8e475af2", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -132,46 +134,46 @@ "elements": [ { "type": "variable", - "name": "should_be_constant_2", + "name": "mySistersAddress", "source_mapping": { - "start": 841, - "length": 33, + "start": 468, + "length": 76, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 43 + 24 ], "starting_column": 5, - "ending_column": 38 + "ending_column": 81 }, "type_specific_fields": { "parent": { "type": "contract", - "name": "MyConc", + "name": "B", "source_mapping": { - "start": 746, - "length": 342, + "start": 445, + "length": 271, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52 + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35 ], "starting_column": 1, "ending_column": 2 @@ -180,10 +182,10 @@ } } ], - "description": "MyConc.should_be_constant_2 (tests/detectors/constable-states/0.7.6/const_state_variables.sol#43) should be constant\n", - "markdown": "[MyConc.should_be_constant_2](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L43) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L43", - "id": "9a48a4122de1a6a4774a9f1e0d4917bd0fa08f17b4af41b86ba07689e51bf711", + "description": "B.mySistersAddress (tests/detectors/constable-states/0.7.6/const_state_variables.sol#24) should be constant \n", + "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L24) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L24", + "id": "3b5bff93954a48a79387e7981e8c45d78edc575a0988a10f1c7f439b9f930539", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -192,16 +194,16 @@ "elements": [ { "type": "variable", - "name": "mySistersAddress", + "name": "myFriendsAddress", "source_mapping": { - "start": 496, + "start": 104, "length": 76, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 26 + 5 ], "starting_column": 5, "ending_column": 81 @@ -209,29 +211,33 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "B", + "name": "A", "source_mapping": { - "start": 473, - "length": 271, + "start": 1, + "length": 441, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37 + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19 ], "starting_column": 1, "ending_column": 2 @@ -240,10 +246,10 @@ } } ], - "description": "B.mySistersAddress (tests/detectors/constable-states/0.7.6/const_state_variables.sol#26) should be constant\n", - "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L26) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L26", - "id": "bee93a722c8eae4a48aade67d8ef537d84c106f48fc9eb738c795fce10d3bc63", + "description": "A.myFriendsAddress (tests/detectors/constable-states/0.7.6/const_state_variables.sol#5) should be constant \n", + "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L5) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L5", + "id": "52fd72f6870c4b504d1bcf9fb44249658e2077474d66208a33a47d2668b8db49", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -254,14 +260,14 @@ "type": "variable", "name": "should_be_constant", "source_mapping": { - "start": 793, + "start": 763, "length": 42, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 42 + 40 ], "starting_column": 5, "ending_column": 47 @@ -269,15 +275,83 @@ "type_specific_fields": { "parent": { "type": "contract", - "name": "MyConc", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 531, + "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_constant (tests/detectors/constable-states/0.7.6/const_state_variables.sol#40) should be constant \n", + "markdown": "[Bad.should_be_constant](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L40) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L40", + "id": "87097c03d57b72ad7c15336eb44e5a30054c50f8daff32e08bc4fbd97852961c", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_constant_3", + "source_mapping": { + "start": 850, + "length": 38, + "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 42 + ], + "starting_column": 5, + "ending_column": 43 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", "source_mapping": { - "start": 746, - "length": 342, + "start": 718, + "length": 531, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ + 37, + 38, 39, 40, 41, @@ -291,7 +365,11 @@ 49, 50, 51, - 52 + 52, + 53, + 54, + 55, + 56 ], "starting_column": 1, "ending_column": 2 @@ -300,10 +378,10 @@ } } ], - "description": "MyConc.should_be_constant (tests/detectors/constable-states/0.7.6/const_state_variables.sol#42) should be constant\n", - "markdown": "[MyConc.should_be_constant](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L42) should be constant\n", + "description": "Bad.should_be_constant_3 (tests/detectors/constable-states/0.7.6/const_state_variables.sol#42) should be constant \n", + "markdown": "[Bad.should_be_constant_3](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L42) should be constant \n", "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L42", - "id": "cbcafa2a3efba4d21ac1b51b4b823e5082d556bc3d6cf3fd2ab3188f9f218fc1", + "id": "8e991c1370b1adb10f01f2d7e48f341dee92a98b91b56ccb291d9149d2da97d0", "check": "constable-states", "impact": "Optimization", "confidence": "High" @@ -312,16 +390,16 @@ "elements": [ { "type": "variable", - "name": "text2", + "name": "test", "source_mapping": { - "start": 333, + "start": 209, "length": 20, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ - 14 + 8 ], "starting_column": 5, "ending_column": 25 @@ -331,13 +409,15 @@ "type": "contract", "name": "A", "source_mapping": { - "start": 29, + "start": 1, "length": 441, "filename_relative": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/constable-states/0.7.6/const_state_variables.sol", "is_dependency": false, "lines": [ + 2, + 3, 4, 5, 6, @@ -353,9 +433,7 @@ 16, 17, 18, - 19, - 20, - 21 + 19 ], "starting_column": 1, "ending_column": 2 @@ -364,10 +442,10 @@ } } ], - "description": "A.text2 (tests/detectors/constable-states/0.7.6/const_state_variables.sol#14) should be constant\n", - "markdown": "[A.text2](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L14) should be constant\n", - "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L14", - "id": "df11e6201c4558a8c5cd90b55b134b9ca8f07203b2264d3aa93bd7745e8cb4ba", + "description": "A.test (tests/detectors/constable-states/0.7.6/const_state_variables.sol#8) should be constant \n", + "markdown": "[A.test](tests/detectors/constable-states/0.7.6/const_state_variables.sol#L8) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.7.6/const_state_variables.sol#L8", + "id": "e407a1b57b4d25949ef7c4e6d97197605857099a94774a9c7a848d7dd3463668", "check": "constable-states", "impact": "Optimization", "confidence": "High" diff --git a/tests/detectors/constable-states/0.8.0/const_state_variables.sol b/tests/detectors/constable-states/0.8.0/const_state_variables.sol new file mode 100644 index 000000000..f405a1587 --- /dev/null +++ b/tests/detectors/constable-states/0.8.0/const_state_variables.sol @@ -0,0 +1,78 @@ + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + fallback () external { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} + +contract Bad { + + uint constant A = 1; + bytes32 should_be_constant = sha256('abc'); + uint should_be_constant_2 = A + 1; + B should_be_constant_3 = B(address(0)); + address should_be_immutable = msg.sender; + uint should_be_immutable_2 = getNumber(); + uint should_be_immutable_3 = 10 + block.number; + uint should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} + +contract Good { + + uint constant A = 1; + bytes32 constant should_be_constant = sha256('abc'); + uint constant should_be_constant_2 = A + 1; + B constant should_be_constant_3 = B(address(0)); + address immutable should_be_immutable = msg.sender; + uint immutable should_be_immutable_2 = getNumber(); + uint immutable should_be_immutable_3 = 10 + block.number; + B immutable should_be_immutable_4 = new B(); + uint immutable should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} \ No newline at end of file diff --git a/tests/detectors/constable-states/0.8.0/const_state_variables.sol.0.8.0.CouldBeConstant.json b/tests/detectors/constable-states/0.8.0/const_state_variables.sol.0.8.0.CouldBeConstant.json new file mode 100644 index 000000000..7febfd637 --- /dev/null +++ b/tests/detectors/constable-states/0.8.0/const_state_variables.sol.0.8.0.CouldBeConstant.json @@ -0,0 +1,454 @@ +[ + [ + { + "elements": [ + { + "type": "variable", + "name": "text2", + "source_mapping": { + "start": 305, + "length": 20, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 12 + ], + "starting_column": 5, + "ending_column": 25 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "A", + "source_mapping": { + "start": 1, + "length": 441, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "A.text2 (tests/detectors/constable-states/0.8.0/const_state_variables.sol#12) should be constant \n", + "markdown": "[A.text2](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L12) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L12", + "id": "2f06e04545cea7e7a8998c65d5419f335bf2579a6ce6a832eac9c87392fd5c1a", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_constant_2", + "source_mapping": { + "start": 811, + "length": 33, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 41 + ], + "starting_column": 5, + "ending_column": 38 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_constant_2 (tests/detectors/constable-states/0.8.0/const_state_variables.sol#41) should be constant \n", + "markdown": "[Bad.should_be_constant_2](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L41) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L41", + "id": "3a8b682f7960750cd8228d6cd3d0bb5d7d6f9faaf1a044de2fa7069d8e475af2", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "mySistersAddress", + "source_mapping": { + "start": 468, + "length": 76, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 24 + ], + "starting_column": 5, + "ending_column": 81 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "B", + "source_mapping": { + "start": 445, + "length": 271, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "B.mySistersAddress (tests/detectors/constable-states/0.8.0/const_state_variables.sol#24) should be constant \n", + "markdown": "[B.mySistersAddress](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L24) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L24", + "id": "3b5bff93954a48a79387e7981e8c45d78edc575a0988a10f1c7f439b9f930539", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "myFriendsAddress", + "source_mapping": { + "start": 104, + "length": 76, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 5 + ], + "starting_column": 5, + "ending_column": 81 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "A", + "source_mapping": { + "start": 1, + "length": 441, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "A.myFriendsAddress (tests/detectors/constable-states/0.8.0/const_state_variables.sol#5) should be constant \n", + "markdown": "[A.myFriendsAddress](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L5) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L5", + "id": "52fd72f6870c4b504d1bcf9fb44249658e2077474d66208a33a47d2668b8db49", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_constant", + "source_mapping": { + "start": 763, + "length": 42, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 40 + ], + "starting_column": 5, + "ending_column": 47 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_constant (tests/detectors/constable-states/0.8.0/const_state_variables.sol#40) should be constant \n", + "markdown": "[Bad.should_be_constant](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L40) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L40", + "id": "87097c03d57b72ad7c15336eb44e5a30054c50f8daff32e08bc4fbd97852961c", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_constant_3", + "source_mapping": { + "start": 850, + "length": 38, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 42 + ], + "starting_column": 5, + "ending_column": 43 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_constant_3 (tests/detectors/constable-states/0.8.0/const_state_variables.sol#42) should be constant \n", + "markdown": "[Bad.should_be_constant_3](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L42) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L42", + "id": "8e991c1370b1adb10f01f2d7e48f341dee92a98b91b56ccb291d9149d2da97d0", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "test", + "source_mapping": { + "start": 209, + "length": 20, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 8 + ], + "starting_column": 5, + "ending_column": 25 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "A", + "source_mapping": { + "start": 1, + "length": 441, + "filename_relative": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/constable-states/0.8.0/const_state_variables.sol", + "is_dependency": false, + "lines": [ + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "A.test (tests/detectors/constable-states/0.8.0/const_state_variables.sol#8) should be constant \n", + "markdown": "[A.test](tests/detectors/constable-states/0.8.0/const_state_variables.sol#L8) should be constant \n", + "first_markdown_element": "tests/detectors/constable-states/0.8.0/const_state_variables.sol#L8", + "id": "e407a1b57b4d25949ef7c4e6d97197605857099a94774a9c7a848d7dd3463668", + "check": "constable-states", + "impact": "Optimization", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/detectors/constable-states/0.8.0/immutable.sol b/tests/detectors/constable-states/0.8.0/immutable.sol deleted file mode 100644 index aeb9fbfa7..000000000 --- a/tests/detectors/constable-states/0.8.0/immutable.sol +++ /dev/null @@ -1,7 +0,0 @@ -contract C{ - uint immutable v; - - constructor() public{ - v = 0; - } -} diff --git a/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol b/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol new file mode 100644 index 000000000..45f8cc87a --- /dev/null +++ b/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol @@ -0,0 +1,57 @@ +//pragma solidity ^0.4.24; + + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + function () external { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} + +contract MyConc{ + + uint constant A = 1; + bytes32 should_be_constant = sha256('abc'); + uint should_be_constant_2 = A + 1; + address not_constant = msg.sender; + uint not_constant_2 = getNumber(); + uint not_constant_3 = 10 + block.number; + uint not_constant_5; + + constructor(uint b) public { + not_constant_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} diff --git a/tests/detectors/constable-states/0.8.0/immutable.sol.0.8.0.ConstCandidateStateVars.json b/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol.0.4.25.CouldBeImmutable.json similarity index 100% rename from tests/detectors/constable-states/0.8.0/immutable.sol.0.8.0.ConstCandidateStateVars.json rename to tests/detectors/immutable-states/0.4.25/immut_state_variables.sol.0.4.25.CouldBeImmutable.json diff --git a/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol b/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol new file mode 100644 index 000000000..8fd1ca808 --- /dev/null +++ b/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol @@ -0,0 +1,58 @@ +//pragma solidity ^0.4.24; + + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + function () external { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} + +contract MyConc{ + + uint constant A = 1; + bytes32 should_be_constant = sha256('abc'); + uint should_be_constant_2 = A + 1; + B should_be_constant_3 = B(address(0)); + address not_constant = msg.sender; + uint not_constant_2 = getNumber(); + uint not_constant_3 = 10 + block.number; + uint not_constant_5; + + constructor(uint b) public { + not_constant_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} diff --git a/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol.0.5.16.CouldBeImmutable.json b/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol.0.5.16.CouldBeImmutable.json new file mode 100644 index 000000000..5825bcacc --- /dev/null +++ b/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol.0.5.16.CouldBeImmutable.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol b/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol new file mode 100644 index 000000000..17548f46f --- /dev/null +++ b/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol @@ -0,0 +1,79 @@ + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + fallback () external { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} + +contract Bad { + + uint constant A = 1; + bytes32 should_be_constant = sha256('abc'); + uint should_be_constant_2 = A + 1; + B should_be_constant_3 = B(address(0)); + address should_be_immutable = msg.sender; + uint should_be_immutable_2 = getNumber(); + uint should_be_immutable_3 = 10 + block.number; + B should_be_immutable_4 = new B(); + uint should_be_immutable_5; + + constructor(uint b) public { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} + +contract Good { + + uint constant A = 1; + bytes32 constant should_be_constant = sha256('abc'); + uint constant should_be_constant_2 = A + 1; + B constant should_be_constant_3 = B(address(0)); + address immutable should_be_immutable = msg.sender; + uint immutable should_be_immutable_2 = getNumber(); + uint immutable should_be_immutable_3 = 10 + block.number; + B immutable should_be_immutable_4 = new B(); + uint immutable should_be_immutable_5; + + constructor(uint b) public { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} \ No newline at end of file diff --git a/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol.0.6.11.CouldBeImmutable.json b/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol.0.6.11.CouldBeImmutable.json new file mode 100644 index 000000000..15064ca99 --- /dev/null +++ b/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol.0.6.11.CouldBeImmutable.json @@ -0,0 +1,339 @@ +[ + [ + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_5", + "source_mapping": { + "start": 1077, + "length": 26, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 47 + ], + "starting_column": 5, + "ending_column": 31 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 539, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_5 (tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#47) should be immutable \n", + "markdown": "[Bad.should_be_immutable_5](tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L47) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L47", + "id": "42d50245236163ceca90dea732165e65c2155934b149a5a1a5c51bddc0b5b02a", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_2", + "source_mapping": { + "start": 940, + "length": 40, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 44 + ], + "starting_column": 5, + "ending_column": 45 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 539, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_2 (tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#44) should be immutable \n", + "markdown": "[Bad.should_be_immutable_2](tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L44) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L44", + "id": "70d57aa51dda92c28444a466db8567fa783c85d484259aa5eee2ebc63f97a200", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_4", + "source_mapping": { + "start": 1038, + "length": 33, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 46 + ], + "starting_column": 5, + "ending_column": 38 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 539, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_4 (tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#46) should be immutable \n", + "markdown": "[Bad.should_be_immutable_4](tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L46) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L46", + "id": "a26d6df4087ac010928bc4bd18aa70ac58a28e584b1288e348d9c255473c300d", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable", + "source_mapping": { + "start": 894, + "length": 40, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 43 + ], + "starting_column": 5, + "ending_column": 45 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 539, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable (tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#43) should be immutable \n", + "markdown": "[Bad.should_be_immutable](tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L43) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L43", + "id": "b163d277f544f7f05ed4bcddda61e444be893e65ba0469688abd7b401a1db222", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_3", + "source_mapping": { + "start": 986, + "length": 46, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 45 + ], + "starting_column": 5, + "ending_column": 51 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 539, + "filename_relative": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_3 (tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#45) should be immutable \n", + "markdown": "[Bad.should_be_immutable_3](tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L45) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.6.11/immut_state_variables.sol#L45", + "id": "f19f7a22a6f17ffd8b5c29021226388aab7548f996b686a8e0b2bc861f72d447", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol b/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol new file mode 100644 index 000000000..297dd6294 --- /dev/null +++ b/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol @@ -0,0 +1,78 @@ + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + fallback () external { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} + +contract Bad { + + uint constant A = 1; + bytes32 should_be_constant = sha256('abc'); + uint should_be_constant_2 = A + 1; + B should_be_constant_3 = B(address(0)); + address should_be_immutable = msg.sender; + uint should_be_immutable_2 = getNumber(); + uint should_be_immutable_3 = 10 + block.number; + B should_be_immutable_4 = new B(); + uint should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } +} + +contract Good { + + uint constant A = 1; + bytes32 constant should_be_constant = sha256('abc'); + uint constant should_be_constant_2 = A + 1; + B constant should_be_constant_3 = B(address(0)); + address immutable should_be_immutable = msg.sender; + uint immutable should_be_immutable_2 = getNumber(); + uint immutable should_be_immutable_3 = 10 + block.number; + B immutable should_be_immutable_4 = new B(); + uint immutable should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} diff --git a/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol.0.7.6.CouldBeImmutable.json b/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol.0.7.6.CouldBeImmutable.json new file mode 100644 index 000000000..40c8b1368 --- /dev/null +++ b/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol.0.7.6.CouldBeImmutable.json @@ -0,0 +1,334 @@ +[ + [ + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_5", + "source_mapping": { + "start": 1077, + "length": 26, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 47 + ], + "starting_column": 5, + "ending_column": 31 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 531, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_5 (tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#47) should be immutable \n", + "markdown": "[Bad.should_be_immutable_5](tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L47) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L47", + "id": "42d50245236163ceca90dea732165e65c2155934b149a5a1a5c51bddc0b5b02a", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_2", + "source_mapping": { + "start": 940, + "length": 40, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 44 + ], + "starting_column": 5, + "ending_column": 45 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 531, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_2 (tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#44) should be immutable \n", + "markdown": "[Bad.should_be_immutable_2](tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L44) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L44", + "id": "70d57aa51dda92c28444a466db8567fa783c85d484259aa5eee2ebc63f97a200", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_4", + "source_mapping": { + "start": 1038, + "length": 33, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 46 + ], + "starting_column": 5, + "ending_column": 38 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 531, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_4 (tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#46) should be immutable \n", + "markdown": "[Bad.should_be_immutable_4](tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L46) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L46", + "id": "a26d6df4087ac010928bc4bd18aa70ac58a28e584b1288e348d9c255473c300d", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable", + "source_mapping": { + "start": 894, + "length": 40, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 43 + ], + "starting_column": 5, + "ending_column": 45 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 531, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable (tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#43) should be immutable \n", + "markdown": "[Bad.should_be_immutable](tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L43) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L43", + "id": "b163d277f544f7f05ed4bcddda61e444be893e65ba0469688abd7b401a1db222", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_3", + "source_mapping": { + "start": 986, + "length": 46, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 45 + ], + "starting_column": 5, + "ending_column": 51 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 531, + "filename_relative": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_3 (tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#45) should be immutable \n", + "markdown": "[Bad.should_be_immutable_3](tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L45) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.7.6/immut_state_variables.sol#L45", + "id": "f19f7a22a6f17ffd8b5c29021226388aab7548f996b686a8e0b2bc861f72d447", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol new file mode 100644 index 000000000..f405a1587 --- /dev/null +++ b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol @@ -0,0 +1,78 @@ + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + fallback () external { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} + +contract Bad { + + uint constant A = 1; + bytes32 should_be_constant = sha256('abc'); + uint should_be_constant_2 = A + 1; + B should_be_constant_3 = B(address(0)); + address should_be_immutable = msg.sender; + uint should_be_immutable_2 = getNumber(); + uint should_be_immutable_3 = 10 + block.number; + uint should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} + +contract Good { + + uint constant A = 1; + bytes32 constant should_be_constant = sha256('abc'); + uint constant should_be_constant_2 = A + 1; + B constant should_be_constant_3 = B(address(0)); + address immutable should_be_immutable = msg.sender; + uint immutable should_be_immutable_2 = getNumber(); + uint immutable should_be_immutable_3 = 10 + block.number; + B immutable should_be_immutable_4 = new B(); + uint immutable should_be_immutable_5; + + constructor(uint b) { + should_be_immutable_5 = b; + } + + function getNumber() public returns(uint){ + return block.number; + } + +} \ No newline at end of file diff --git a/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json new file mode 100644 index 000000000..afa2e3bb2 --- /dev/null +++ b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json @@ -0,0 +1,268 @@ +[ + [ + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_5", + "source_mapping": { + "start": 1038, + "length": 26, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 46 + ], + "starting_column": 5, + "ending_column": 31 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_5 (tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#46) should be immutable \n", + "markdown": "[Bad.should_be_immutable_5](tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L46) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L46", + "id": "42d50245236163ceca90dea732165e65c2155934b149a5a1a5c51bddc0b5b02a", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_2", + "source_mapping": { + "start": 940, + "length": 40, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 44 + ], + "starting_column": 5, + "ending_column": 45 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_2 (tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#44) should be immutable \n", + "markdown": "[Bad.should_be_immutable_2](tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L44) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L44", + "id": "70d57aa51dda92c28444a466db8567fa783c85d484259aa5eee2ebc63f97a200", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable", + "source_mapping": { + "start": 894, + "length": 40, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 43 + ], + "starting_column": 5, + "ending_column": 45 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable (tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#43) should be immutable \n", + "markdown": "[Bad.should_be_immutable](tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L43) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L43", + "id": "b163d277f544f7f05ed4bcddda61e444be893e65ba0469688abd7b401a1db222", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [ + { + "type": "variable", + "name": "should_be_immutable_3", + "source_mapping": { + "start": 986, + "length": 46, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 45 + ], + "starting_column": 5, + "ending_column": 51 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Bad", + "source_mapping": { + "start": 718, + "length": 493, + "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", + "is_dependency": false, + "lines": [ + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "Bad.should_be_immutable_3 (tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#45) should be immutable \n", + "markdown": "[Bad.should_be_immutable_3](tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L45) should be immutable \n", + "first_markdown_element": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol#L45", + "id": "f19f7a22a6f17ffd8b5c29021226388aab7548f996b686a8e0b2bc861f72d447", + "check": "immutable-states", + "impact": "Optimization", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/lookup/private_variable.sol b/tests/lookup/private_variable.sol new file mode 100644 index 000000000..93f4d3dda --- /dev/null +++ b/tests/lookup/private_variable.sol @@ -0,0 +1,13 @@ +contract A{ + uint private v = 10; +} + +contract B{ + uint v = 20; +} + +contract C is B, A{ + function f() public view returns(uint) { + return v; + } +} \ No newline at end of file diff --git a/tests/slithir/ternary_expressions.sol b/tests/slithir/ternary_expressions.sol index c2e50b719..89fdc59d1 100644 --- a/tests/slithir/ternary_expressions.sol +++ b/tests/slithir/ternary_expressions.sol @@ -1,3 +1,7 @@ +interface Test { + function test() external payable returns (uint); + function testTuple() external payable returns (uint, uint); +} contract C { // TODO // 1) support variable declarations @@ -21,4 +25,18 @@ contract C { function d(bool cond, bytes calldata x) external { bytes1 a = x[cond ? 1 : 2]; } + + function e(address one, address two) public { + uint x = Test(one).test{value: msg.sender == two ? 1 : 2, gas: true ? 2 : gasleft()}(); + } + + // Parenthetical expression + function f(address one, address two) public { + uint x = Test(one).test{value: msg.sender == two ? 1 : 2, gas: true ? (1 == 1 ? 1 : 2) : gasleft()}(); + } + + // Unused tuple variable + function g(address one) public { + (, uint x) = Test(one).testTuple(); + } } diff --git a/tests/slithir/test_ternary_expressions.py b/tests/slithir/test_ternary_expressions.py index db5658787..17cac6b2f 100644 --- a/tests/slithir/test_ternary_expressions.py +++ b/tests/slithir/test_ternary_expressions.py @@ -9,10 +9,10 @@ def test_ternary_conversions() -> None: slither = Slither("./tests/slithir/ternary_expressions.sol") for contract in slither.contracts: for function in contract.functions: + vars_declared = 0 + vars_assigned = 0 for node in function.nodes: if node.type in [NodeType.IF, NodeType.IFLOOP]: - vars_declared = 0 - vars_assigned = 0 # Iterate over true and false son for inner_node in node.sons: @@ -31,7 +31,7 @@ def test_ternary_conversions() -> None: if isinstance(ir, Assignment): vars_assigned += 1 - assert vars_declared == vars_assigned + assert vars_declared == vars_assigned if __name__ == "__main__": diff --git a/tests/source_unit/README.md b/tests/source_unit/README.md new file mode 100644 index 000000000..9cf3657e0 --- /dev/null +++ b/tests/source_unit/README.md @@ -0,0 +1,3 @@ +# README + +Before using this project, run `forge init --no-git --no-commit --force` to initialize submodules diff --git a/tests/source_unit/foundry.toml b/tests/source_unit/foundry.toml new file mode 100644 index 000000000..e6810b2b5 --- /dev/null +++ b/tests/source_unit/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/tests/source_unit/script/Counter.s.sol b/tests/source_unit/script/Counter.s.sol new file mode 100644 index 000000000..0e546aba3 --- /dev/null +++ b/tests/source_unit/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; + +contract CounterScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + } +} diff --git a/tests/source_unit/src/Counter.sol b/tests/source_unit/src/Counter.sol new file mode 100644 index 000000000..aded7997b --- /dev/null +++ b/tests/source_unit/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/tests/source_unit/src/Counter2.sol b/tests/source_unit/src/Counter2.sol new file mode 100644 index 000000000..fa830a446 --- /dev/null +++ b/tests/source_unit/src/Counter2.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { +} diff --git a/tests/source_unit/test/Counter.t.sol b/tests/source_unit/test/Counter.t.sol new file mode 100644 index 000000000..30235e8a8 --- /dev/null +++ b/tests/source_unit/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function testIncrement() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testSetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/tests/test_ast_parsing.py b/tests/test_ast_parsing.py index e96a129b8..a61ec9604 100644 --- a/tests/test_ast_parsing.py +++ b/tests/test_ast_parsing.py @@ -328,6 +328,7 @@ ALL_TESTS = [ ALL_VERSIONS, ), Test("custom_error-0.8.4.sol", make_version(8, 4, 15)), + Test("custom-error-selector.sol", make_version(8, 4, 15)), Test( "top-level-0.4.0.sol", VERSIONS_04 + VERSIONS_05 + VERSIONS_06 + ["0.7.0"], @@ -418,13 +419,29 @@ ALL_TESTS = [ Test("user_defined_value_type/constant-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)), Test("user_defined_value_type/erc20-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)), Test("user_defined_value_type/in_parenthesis-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)), + Test("user_defined_value_type/using-for-0.8.8.sol", ["0.8.8"] + make_version(8, 10, 15)), Test("bytes_call.sol", ["0.8.12"]), Test("modifier_identifier_path.sol", VERSIONS_08), Test("free_functions/libraries_from_free.sol", ["0.8.12"]), Test("free_functions/new_operator.sol", ["0.8.12"]), Test("free_functions/library_constant_function_collision.sol", ["0.8.12"]), Test("ternary-with-max.sol", ["0.8.15"]), + Test("using-for-1-0.8.0.sol", ["0.8.15"]), + Test("using-for-2-0.8.0.sol", ["0.8.15"]), + Test("using-for-3-0.8.0.sol", ["0.8.15"]), + Test("using-for-4-0.8.0.sol", ["0.8.15"]), + Test("using-for-in-library-0.8.0.sol", ["0.8.15"]), + Test("using-for-alias-contract-0.8.0.sol", ["0.8.15"]), + Test("using-for-alias-top-level-0.8.0.sol", ["0.8.15"]), + Test("using-for-functions-list-1-0.8.0.sol", ["0.8.15"]), + Test("using-for-functions-list-2-0.8.0.sol", ["0.8.15"]), + Test("using-for-functions-list-3-0.8.0.sol", ["0.8.15"]), + Test("using-for-functions-list-4-0.8.0.sol", ["0.8.15"]), + Test("using-for-global-0.8.0.sol", ["0.8.15"]), Test("library_event-0.8.16.sol", ["0.8.16"]), + Test("top-level-struct-0.8.0.sol", ["0.8.0"]), + Test("yul-top-level-0.8.0.sol", ["0.8.0"]), + Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]), ] # create the output folder if needed try: @@ -433,20 +450,21 @@ except OSError: pass -@pytest.mark.parametrize("test_item", ALL_TESTS, ids=lambda x: x.test_file) -def test_parsing(test_item: Test): - flavors = ["compact"] - if not test_item.disable_legacy: - flavors += ["legacy"] - for version, flavor in test_item.versions_with_flavors: - test_file = os.path.join( - TEST_ROOT, "compile", f"{test_item.test_file}-{version}-{flavor}.zip" - ) - expected_file = os.path.join( - TEST_ROOT, "expected", f"{test_item.test_file}-{version}-{flavor}.json" - ) +def pytest_generate_tests(metafunc): + test_cases = [] + for test_item in ALL_TESTS: + for version, flavor in test_item.versions_with_flavors: + test_cases.append((test_item.test_file, version, flavor)) + metafunc.parametrize("test_file, version, flavor", test_cases) - cc = load_from_zip(test_file)[0] + +class TestASTParsing: + # pylint: disable=no-self-use + def test_parsing(self, test_file, version, flavor): + actual = os.path.join(TEST_ROOT, "compile", f"{test_file}-{version}-{flavor}.zip") + expected = os.path.join(TEST_ROOT, "expected", f"{test_file}-{version}-{flavor}.json") + + cc = load_from_zip(actual)[0] sl = Slither( cc, @@ -458,26 +476,25 @@ def test_parsing(test_item: Test): actual = generate_output(sl) try: - with open(expected_file, "r", encoding="utf8") as f: + with open(expected, "r", encoding="utf8") as f: expected = json.load(f) except OSError: pytest.xfail("the file for this test was not generated") raise diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree") - if diff: for change in diff.get("values_changed", []): path_list = re.findall(r"\['(.*?)'\]", change.path()) path = "_".join(path_list) with open( - f"test_artifacts/{test_item.test_file}_{path}_expected.dot", + f"test_artifacts/{test_file}_{path}_expected.dot", "w", encoding="utf8", ) as f: f.write(change.t1) with open( - f"test_artifacts/{test_item.test_file}_{version}_{flavor}_{path}_actual.dot", + f"test_artifacts/{test_file}_{version}_{flavor}_{path}_actual.dot", "w", encoding="utf8", ) as f: diff --git a/tests/test_constant_folding.py b/tests/test_constant_folding.py index efc3119a8..21517ddc4 100644 --- a/tests/test_constant_folding.py +++ b/tests/test_constant_folding.py @@ -43,3 +43,59 @@ def test_constant_folding_rational(): variable_g = contract.get_state_variable_from_name("g") assert str(variable_g.type) == "int64" assert str(ConstantFolding(variable_g.expression, "int64").result()) == "-7" + + +def test_constant_folding_binary_expressions(): + sl = Slither("./tests/constant_folding_binop.sol") + contract = sl.get_contract_from_name("BinOp")[0] + + variable_a = contract.get_state_variable_from_name("a") + assert str(variable_a.type) == "uint256" + assert str(ConstantFolding(variable_a.expression, "uint256").result()) == "0" + + variable_b = contract.get_state_variable_from_name("b") + assert str(variable_b.type) == "uint256" + assert str(ConstantFolding(variable_b.expression, "uint256").result()) == "3" + + variable_c = contract.get_state_variable_from_name("c") + assert str(variable_c.type) == "uint256" + assert str(ConstantFolding(variable_c.expression, "uint256").result()) == "3" + + variable_d = contract.get_state_variable_from_name("d") + assert str(variable_d.type) == "bool" + assert str(ConstantFolding(variable_d.expression, "bool").result()) == "False" + + variable_e = contract.get_state_variable_from_name("e") + assert str(variable_e.type) == "bool" + assert str(ConstantFolding(variable_e.expression, "bool").result()) == "False" + + variable_f = contract.get_state_variable_from_name("f") + assert str(variable_f.type) == "bool" + assert str(ConstantFolding(variable_f.expression, "bool").result()) == "True" + + variable_g = contract.get_state_variable_from_name("g") + assert str(variable_g.type) == "bool" + assert str(ConstantFolding(variable_g.expression, "bool").result()) == "False" + + variable_h = contract.get_state_variable_from_name("h") + assert str(variable_h.type) == "bool" + assert str(ConstantFolding(variable_h.expression, "bool").result()) == "False" + + variable_i = contract.get_state_variable_from_name("i") + assert str(variable_i.type) == "bool" + assert str(ConstantFolding(variable_i.expression, "bool").result()) == "True" + + variable_j = contract.get_state_variable_from_name("j") + assert str(variable_j.type) == "bool" + assert str(ConstantFolding(variable_j.expression, "bool").result()) == "False" + + variable_k = contract.get_state_variable_from_name("k") + assert str(variable_k.type) == "bool" + assert str(ConstantFolding(variable_k.expression, "bool").result()) == "True" + + variable_l = contract.get_state_variable_from_name("l") + assert str(variable_l.type) == "uint256" + assert ( + str(ConstantFolding(variable_l.expression, "uint256").result()) + == "115792089237316195423570985008687907853269984665640564039457584007913129639935" + ) diff --git a/tests/test_detectors.py b/tests/test_detectors.py index a5ecaae2a..994f87aac 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -52,7 +52,7 @@ def set_solc(test_item: Test): # pylint: disable=too-many-lines def id_test(test_item: Test): - return f"{test_item.detector}: {test_item.solc_ver}/{test_item.test_file}" + return f"{test_item.detector.__name__}-{test_item.solc_ver}-{test_item.test_file}" ALL_TEST_OBJECTS = [ @@ -480,28 +480,53 @@ ALL_TEST_OBJECTS = [ "0.7.6", ), Test( - all_detectors.ConstCandidateStateVars, + all_detectors.CouldBeConstant, "const_state_variables.sol", "0.4.25", ), Test( - all_detectors.ConstCandidateStateVars, + all_detectors.CouldBeConstant, "const_state_variables.sol", "0.5.16", ), Test( - all_detectors.ConstCandidateStateVars, + all_detectors.CouldBeConstant, "const_state_variables.sol", "0.6.11", ), Test( - all_detectors.ConstCandidateStateVars, + all_detectors.CouldBeConstant, "const_state_variables.sol", "0.7.6", ), Test( - all_detectors.ConstCandidateStateVars, - "immutable.sol", + all_detectors.CouldBeConstant, + "const_state_variables.sol", + "0.8.0", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.4.25", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.5.16", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.6.11", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.7.6", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", "0.8.0", ), Test( diff --git a/tests/test_features.py b/tests/test_features.py index 4b4564b50..0303a5760 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -5,8 +5,10 @@ from crytic_compile.platform.solc_standard_json import SolcStandardJson from solc_select import solc_select from slither import Slither +from slither.core.variables.state_variable import StateVariable from slither.detectors import all_detectors from slither.detectors.abstract_detector import AbstractDetector +from slither.slithir.operations import LibraryCall, InternalCall def _run_all_detectors(slither: Slither) -> None: @@ -69,3 +71,82 @@ def test_upgradeable_comments() -> None: v1 = compilation_unit.get_contract_from_name("V1")[0] assert v0.is_upgradeable assert v1.upgradeable_version == "version_1" + + +def test_using_for_top_level_same_name() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-3-0.8.0.sol") + contract_c = slither.get_contract_from_name("C")[0] + libCall = contract_c.get_function_from_full_name("libCall(uint256)") + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "a": + return + assert False + + +def test_using_for_top_level_implicit_conversion() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-4-0.8.0.sol") + contract_c = slither.get_contract_from_name("C")[0] + libCall = contract_c.get_function_from_full_name("libCall(uint16)") + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "f": + return + assert False + + +def test_using_for_alias_top_level() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-alias-top-level-0.8.0.sol") + contract_c = slither.get_contract_from_name("C")[0] + libCall = contract_c.get_function_from_full_name("libCall(uint256)") + ok = False + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "b": + ok = True + if not ok: + assert False + topLevelCall = contract_c.get_function_from_full_name("topLevel(uint256)") + for ir in topLevelCall.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "a": + return + assert False + + +def test_using_for_alias_contract() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-alias-contract-0.8.0.sol") + contract_c = slither.get_contract_from_name("C")[0] + libCall = contract_c.get_function_from_full_name("libCall(uint256)") + ok = False + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "b": + ok = True + if not ok: + assert False + topLevelCall = contract_c.get_function_from_full_name("topLevel(uint256)") + for ir in topLevelCall.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "a": + return + assert False + + +def test_using_for_in_library() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-in-library-0.8.0.sol") + contract_c = slither.get_contract_from_name("A")[0] + libCall = contract_c.get_function_from_full_name("a(uint256)") + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "B" and ir.function_name == "b": + return + assert False + + +def test_private_variable() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/lookup/private_variable.sol") + contract_c = slither.get_contract_from_name("C")[0] + f = contract_c.functions[0] + var_read = f.variables_read[0] + assert isinstance(var_read, StateVariable) + assert str(var_read.contract) == "B" diff --git a/tests/test_source_unit.py b/tests/test_source_unit.py new file mode 100644 index 000000000..73c165016 --- /dev/null +++ b/tests/test_source_unit.py @@ -0,0 +1,43 @@ +from pathlib import Path +import shutil + +import pytest +from slither import Slither + +# NB: read tests/source_unit/README.md for setup before using this test + +foundry_available = shutil.which("forge") is not None +project_ready = Path("./tests/source_unit/lib/forge-std").exists() + + +@pytest.mark.skipif( + not foundry_available or not project_ready, reason="requires Foundry and project setup" +) +def test_contract_info() -> None: + slither = Slither("./tests/source_unit") + + assert len(slither.compilation_units) == 1 + compilation_unit = slither.compilation_units[0] + + for source_unit in compilation_unit.crytic_compile_compilation_unit.source_units.values(): + source_unit.remove_metadata() + + counter_sol = compilation_unit.crytic_compile.filename_lookup( + "tests/source_unit/src/Counter.sol" + ) + assert ( + compilation_unit.scopes[counter_sol].bytecode_init( + compilation_unit.crytic_compile_compilation_unit, "Counter" + ) + == "608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c80633fb5c1cb1460415780638381f58a146053578063d09de08a14606d575b600080fd5b6051604c3660046083565b600055565b005b605b60005481565b60405190815260200160405180910390f35b6051600080549080607c83609b565b9190505550565b600060208284031215609457600080fd5b5035919050565b60006001820160ba57634e487b7160e01b600052601160045260246000fd5b506001019056fe" + ) + + counter2_sol = compilation_unit.crytic_compile.filename_lookup( + "tests/source_unit/src/Counter2.sol" + ) + assert ( + compilation_unit.scopes[counter2_sol].bytecode_init( + compilation_unit.crytic_compile_compilation_unit, "Counter" + ) + == "6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfe" + )