Merge branch 'dev' of https://github.com/dokzai/slither into issue-1654

pull/2168/head
Judy Wu 1 year ago
commit 00bd1d4f2e
  1. 18
      .github/workflows/test.yml
  2. 4
      setup.py
  3. 6
      slither/__main__.py
  4. 32
      slither/core/compilation_unit.py
  5. 2
      slither/core/declarations/contract.py
  6. 31
      slither/core/declarations/function.py
  7. 33
      slither/core/declarations/solidity_variables.py
  8. 1
      slither/core/expressions/__init__.py
  9. 2
      slither/core/expressions/binary_operation.py
  10. 1
      slither/core/expressions/identifier.py
  11. 6
      slither/core/expressions/self_identifier.py
  12. 3
      slither/core/variables/variable.py
  13. 24
      slither/detectors/abstract_detector.py
  14. 5
      slither/detectors/all_detectors.py
  15. 91
      slither/detectors/assembly/incorrect_return.py
  16. 68
      slither/detectors/assembly/return_instead_of_leave.py
  17. 2
      slither/detectors/attributes/incorrect_solc.py
  18. 19
      slither/detectors/naming_convention/naming_convention.py
  19. 93
      slither/detectors/operations/incorrect_exp.py
  20. 2
      slither/detectors/statements/deprecated_calls.py
  21. 123
      slither/detectors/statements/return_bomb.py
  22. 69
      slither/detectors/statements/tautological_compare.py
  23. 34
      slither/slither.py
  24. 28
      slither/slithir/convert.py
  25. 5
      slither/slithir/operations/type_conversion.py
  26. 2
      slither/slithir/variables/constant.py
  27. 2
      slither/solc_parsing/expressions/expression_parsing.py
  28. 18
      slither/solc_parsing/slither_compilation_unit_solc.py
  29. 49
      slither/visitors/slithir/expression_to_slithir.py
  30. 0
      slither/vyper_parsing/__init__.py
  31. 0
      slither/vyper_parsing/ast/__init__.py
  32. 466
      slither/vyper_parsing/ast/ast.py
  33. 262
      slither/vyper_parsing/ast/types.py
  34. 0
      slither/vyper_parsing/cfg/__init__.py
  35. 66
      slither/vyper_parsing/cfg/node.py
  36. 0
      slither/vyper_parsing/declarations/__init__.py
  37. 524
      slither/vyper_parsing/declarations/contract.py
  38. 39
      slither/vyper_parsing/declarations/event.py
  39. 563
      slither/vyper_parsing/declarations/function.py
  40. 33
      slither/vyper_parsing/declarations/struct.py
  41. 0
      slither/vyper_parsing/expressions/__init__.py
  42. 464
      slither/vyper_parsing/expressions/expression_parsing.py
  43. 150
      slither/vyper_parsing/expressions/find_variable.py
  44. 99
      slither/vyper_parsing/type_parsing.py
  45. 0
      slither/vyper_parsing/variables/__init__.py
  46. 24
      slither/vyper_parsing/variables/event_variable.py
  47. 34
      slither/vyper_parsing/variables/local_variable.py
  48. 29
      slither/vyper_parsing/variables/state_variable.py
  49. 17
      slither/vyper_parsing/variables/structure_variable.py
  50. 80
      slither/vyper_parsing/vyper_compilation_unit.py
  51. 22
      tests/conftest.py
  52. 5
      tests/e2e/compilation/test_data/test_contract_data/test_contract_data.sol
  53. 17
      tests/e2e/compilation/test_resolution.py
  54. 9
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectOperatorExponentiation_0_7_6_incorrect_exp_sol__0.txt
  55. 4
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectReturn_0_8_10_incorrect_return_sol__0.txt
  56. 16
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_4_25_naming_convention_sol__0.txt
  57. 16
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_5_16_naming_convention_sol__0.txt
  58. 32
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_6_11_naming_convention_sol__0.txt
  59. 32
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_7_6_naming_convention_sol__0.txt
  60. 5
      tests/e2e/detectors/snapshots/detectors__detector_ReturnBomb_0_8_20_return_bomb_sol__0.txt
  61. 2
      tests/e2e/detectors/snapshots/detectors__detector_ReturnInsteadOfLeave_0_8_10_incorrect_return_sol__0.txt
  62. 3
      tests/e2e/detectors/snapshots/detectors__detector_TautologicalCompare_0_8_20_compare_sol__0.txt
  63. 30
      tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol
  64. BIN
      tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol-0.7.6.zip
  65. 36
      tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol
  66. BIN
      tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol-0.8.10.zip
  67. 5
      tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol
  68. BIN
      tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol-0.4.25.zip
  69. BIN
      tests/e2e/detectors/test_data/naming-convention/0.4.25/no_warning_for_public_constants.sol-0.4.25.zip
  70. 5
      tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol
  71. BIN
      tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol-0.5.16.zip
  72. BIN
      tests/e2e/detectors/test_data/naming-convention/0.5.16/no_warning_for_public_constants.sol-0.5.16.zip
  73. 8
      tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol
  74. BIN
      tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol-0.6.11.zip
  75. BIN
      tests/e2e/detectors/test_data/naming-convention/0.6.11/no_warning_for_public_constants.sol-0.6.11.zip
  76. 8
      tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol
  77. BIN
      tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol-0.7.6.zip
  78. BIN
      tests/e2e/detectors/test_data/naming-convention/0.7.6/no_warning_for_public_constants.sol-0.7.6.zip
  79. 57
      tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol
  80. BIN
      tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol-0.8.20.zip
  81. 8
      tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol
  82. BIN
      tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol-0.8.10.zip
  83. 6
      tests/e2e/detectors/test_data/tautological-compare/0.8.20/compare.sol
  84. BIN
      tests/e2e/detectors/test_data/tautological-compare/0.8.20/compare.sol-0.8.20.zip
  85. 25
      tests/e2e/detectors/test_detectors.py
  86. 8
      tests/e2e/solc_parsing/test_ast_parsing.py
  87. 0
      tests/e2e/vyper_parsing/__init__.py
  88. 38
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt
  89. 118
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt
  90. 28
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt
  91. 24
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt
  92. 63
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt
  93. 19
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_slitherConstructorConstantVariables__0.txt
  94. 62
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt
  95. 164
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_break_continue_f__0.txt
  96. 56
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt
  97. 19
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_slitherConstructorConstantVariables__0.txt
  98. 172
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt
  99. 62
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt
  100. 66
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -57,7 +57,23 @@ jobs:
npm install hardhat
popd || exit
fi
- name: Install Vyper
run: |
INSTALLDIR="$RUNNER_TEMP/vyper-install"
if [[ "$RUNNER_OS" = "Windows" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.windows.exe"
FILENAME="vyper.exe"
elif [[ "$RUNNER_OS" = "Linux" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.linux"
FILENAME="vyper"
else
echo "Unknown OS"
exit 1
fi
mkdir -p "$INSTALLDIR"
curl "$URL" -o "$INSTALLDIR/$FILENAME" -L
chmod 755 "$INSTALLDIR/$FILENAME"
echo "$INSTALLDIR" >> "$GITHUB_PATH"
- name: Run ${{ matrix.type }} tests
env:
TEST_TYPE: ${{ matrix.type }}

@ -15,8 +15,8 @@ setup(
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.3,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",

@ -870,12 +870,6 @@ def main_impl(
logging.error(red(output_error))
logging.error("Please report an issue to https://github.com/crytic/slither/issues")
except Exception: # pylint: disable=broad-except
output_error = traceback.format_exc()
traceback.print_exc()
logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
logging.error(output_error)
# If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
if outputting_json:
if "console" in args.json_types:

@ -1,4 +1,5 @@
import math
from enum import Enum
from typing import Optional, Dict, List, Set, Union, TYPE_CHECKING, Tuple
from crytic_compile import CompilationUnit, CryticCompile
@ -29,6 +30,20 @@ if TYPE_CHECKING:
from slither.core.slither_core import SlitherCore
class Language(Enum):
SOLIDITY = "solidity"
VYPER = "vyper"
@staticmethod
def from_str(label: str):
if label == "solc":
return Language.SOLIDITY
if label == "vyper":
return Language.VYPER
raise ValueError(f"Unknown language: {label}")
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherCompilationUnit(Context):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit) -> None:
@ -36,6 +51,7 @@ class SlitherCompilationUnit(Context):
self._core = core
self._crytic_compile_compilation_unit = crytic_compilation_unit
self._language = Language.from_str(crytic_compilation_unit.compiler_version.compiler)
# Top level object
self.contracts: List[Contract] = []
@ -81,6 +97,17 @@ class SlitherCompilationUnit(Context):
# region Compiler
###################################################################################
###################################################################################
@property
def language(self) -> Language:
return self._language
@property
def is_vyper(self) -> bool:
return self._language == Language.VYPER
@property
def is_solidity(self) -> bool:
return self._language == Language.SOLIDITY
@property
def compiler_version(self) -> CompilerVersion:
@ -166,6 +193,10 @@ class SlitherCompilationUnit(Context):
return self.functions + list(self.modifiers)
def propagate_function_calls(self) -> None:
"""This info is used to compute the rvalues of Phi operations in `fix_phi` and ultimately
is responsible for the `read` property of Phi operations which is vital to
propagating taints inter-procedurally
"""
for f in self.functions_and_modifiers:
for node in f.nodes:
for ir in node.irs_ssa:
@ -259,6 +290,7 @@ class SlitherCompilationUnit(Context):
###################################################################################
def compute_storage_layout(self) -> None:
assert self.is_solidity
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}

@ -138,7 +138,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def id(self) -> int:
"""Unique id."""
assert self._id
assert self._id is not None
return self._id
@id.setter

@ -137,6 +137,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._parameters: List["LocalVariable"] = []
self._parameters_ssa: List["LocalIRVariable"] = []
self._parameters_src: SourceMapping = SourceMapping()
# This is used for vyper calls with default arguments
self._default_args_as_expressions: List["Expression"] = []
self._returns: List["LocalVariable"] = []
self._returns_ssa: List["LocalIRVariable"] = []
self._returns_src: SourceMapping = SourceMapping()
@ -217,8 +219,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidity by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self.function_language: FunctionLanguage = (
FunctionLanguage.Solidity if compilation_unit.is_solidity else FunctionLanguage.Vyper
)
self._id: Optional[str] = None
@ -238,7 +241,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR:
return "constructor"
if self._function_type == FunctionType.FALLBACK:
if self._name == "" and self._function_type == FunctionType.FALLBACK:
return "fallback"
if self._function_type == FunctionType.RECEIVE:
return "receive"
@ -985,14 +988,15 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
(str, list(str), list(str)): Function signature as
(name, list parameters type, list return values type)
"""
if self._signature is None:
signature = (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
self._signature = signature
return self._signature
# FIXME memoizing this function is not working properly for vyper
# if self._signature is None:
return (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
# self._signature = signature
# return self._signature
@property
def signature_str(self) -> str:
@ -1497,7 +1501,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
Determine if the function can be re-entered
"""
# TODO: compare with hash of known nonReentrant modifier instead of the name
if "nonReentrant" in [m.name for m in self.modifiers]:
if "nonReentrant" in [m.name for m in self.modifiers] or "nonreentrant(lock)" in [
m.name for m in self.modifiers
]:
return False
if self.visibility in ["public", "external"]:
@ -1756,6 +1762,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]
def generate_slithir_and_analyze(self) -> None:
for node in self.nodes:
node.slithir_generation()

@ -10,11 +10,14 @@ from slither.exceptions import SlitherException
SOLIDITY_VARIABLES = {
"now": "uint256",
"this": "address",
"self": "address",
"abi": "address", # to simplify the conversion, assume that abi return an address
"msg": "",
"tx": "",
"block": "",
"super": "",
"chain": "",
"ZERO_ADDRESS": "address",
}
SOLIDITY_VARIABLES_COMPOSED = {
@ -34,6 +37,10 @@ SOLIDITY_VARIABLES_COMPOSED = {
"msg.value": "uint256",
"tx.gasprice": "uint256",
"tx.origin": "address",
# Vyper
"chain.id": "uint256",
"block.prevhash": "bytes32",
"self.balance": "uint256",
}
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
@ -81,6 +88,32 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"balance(address)": ["uint256"],
"code(address)": ["bytes"],
"codehash(address)": ["bytes32"],
# Vyper
"create_from_blueprint()": [],
"create_minimal_proxy_to()": [],
"empty()": [],
"convert()": [],
"len()": ["uint256"],
"method_id()": [],
"unsafe_sub()": [],
"unsafe_add()": [],
"unsafe_div()": [],
"unsafe_mul()": [],
"pow_mod256()": [],
"max_value()": [],
"min_value()": [],
"concat()": [],
"ecrecover()": [],
"isqrt()": [],
"range()": [],
"min()": [],
"max()": [],
"shift()": [],
"abs()": [],
"raw_call()": ["bool", "bytes32"],
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
}

@ -12,6 +12,7 @@ from .new_contract import NewContract
from .new_elementary_type import NewElementaryType
from .super_call_expression import SuperCallExpression
from .super_identifier import SuperIdentifier
from .self_identifier import SelfIdentifier
from .tuple_expression import TupleExpression
from .type_conversion import TypeConversion
from .unary_operation import UnaryOperation, UnaryOperationType

@ -42,7 +42,7 @@ class BinaryOperationType(Enum):
# pylint: disable=too-many-branches
@staticmethod
def get_type(
operation_type: "BinaryOperation",
operation_type: "str",
) -> "BinaryOperationType":
if operation_type == "**":
return BinaryOperationType.POWER

@ -26,7 +26,6 @@ class Identifier(Expression):
],
) -> None:
super().__init__()
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin

@ -0,0 +1,6 @@
from slither.core.expressions.identifier import Identifier
class SelfIdentifier(Identifier):
def __str__(self):
return "self." + str(self._value)

@ -179,5 +179,6 @@ class Variable(SourceMapping):
return f'{name}({",".join(parameters)})'
def __str__(self) -> str:
assert self._name
if self._name is None:
return ""
return self._name

@ -3,7 +3,7 @@ import re
from logging import Logger
from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.compilation_unit import SlitherCompilationUnit, Language
from slither.core.declarations import Contract
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
@ -80,6 +80,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
# list of vulnerable solc versions as strings (e.g. ["0.4.25", "0.5.0"])
# If the detector is meant to run on all versions, use None
VULNERABLE_SOLC_VERSIONS: Optional[List[str]] = None
# If the detector is meant to run on all languages, use None
# Otherwise, use `solidity` or `vyper`
LANGUAGE: Optional[str] = None
def __init__(
self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
@ -133,6 +136,14 @@ class AbstractDetector(metaclass=abc.ABCMeta):
f"VULNERABLE_SOLC_VERSIONS should not be an empty list {self.__class__.__name__}"
)
if self.LANGUAGE is not None and self.LANGUAGE not in [
Language.SOLIDITY.value,
Language.VYPER.value,
]:
raise IncorrectDetectorInitialization(
f"LANGUAGE should not be either 'solidity' or 'vyper' {self.__class__.__name__}"
)
if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization(
f"ARGUMENT has illegal character {self.__class__.__name__}"
@ -164,9 +175,14 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if self.logger:
self.logger.info(self.color(info))
def _uses_vulnerable_solc_version(self) -> bool:
def _is_applicable_detector(self) -> bool:
if self.VULNERABLE_SOLC_VERSIONS:
return self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
return (
self.compilation_unit.is_solidity
and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
)
if self.LANGUAGE:
return self.compilation_unit.language.value == self.LANGUAGE
return True
@abc.abstractmethod
@ -179,7 +195,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
results: List[Dict] = []
# check solc version
if not self._uses_vulnerable_solc_version():
if not self._is_applicable_detector():
return results
# only keep valid result, and remove duplicate

@ -92,3 +92,8 @@ from .functions.cyclomatic_complexity import CyclomaticComplexity
from .operations.cache_array_length import CacheArrayLength
from .statements.incorrect_using_for import IncorrectUsingFor
from .operations.encode_packed import EncodePackedCollision
from .assembly.incorrect_return import IncorrectReturn
from .assembly.return_instead_of_leave import ReturnInsteadOfLeave
from .operations.incorrect_exp import IncorrectOperatorExponentiation
from .statements.tautological_compare import TautologicalCompare
from .statements.return_bomb import ReturnBomb

@ -0,0 +1,91 @@
from typing import List, Optional
from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
def _assembly_node(function: Function) -> Optional[SolidityCall]:
"""
Check if there is a node that use return in assembly
Args:
function:
Returns:
"""
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
return ir
return None
class IncorrectReturn(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function
"""
ARGUMENT = "incorrect-return"
HELP = "If a `return` is incorrectly used in assembly mode."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return"
WIKI_TITLE = "Incorrect return in assembly"
WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
function g() returns (bool){
f();
return true;
}
}
```
The return statement in `f` will cause execution in `g` to halt.
The function will return 6 bytes starting from offset 5, instead of returning a boolean."""
WIKI_RECOMMENDATION = "Use the `leave` statement."
# pylint: disable=too-many-nested-blocks
def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_and_modifiers_declared:
for node in f.nodes:
if node.sons:
for function_called in node.internal_calls:
if isinstance(function_called, Function):
found = _assembly_node(function_called)
if found:
info: DETECTOR_INFO = [
f,
" calls ",
function_called,
" which halt the execution ",
found.node,
"\n",
]
json = self.generate_result(info)
results.append(json)
return results

@ -0,0 +1,68 @@
from typing import List
from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
class ReturnInsteadOfLeave(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function that also returns two variables
"""
ARGUMENT = "return-leave"
HELP = "If a `return` is used instead of a `leave`."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return"
WIKI_TITLE = "Return instead of leave in assembly"
WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
}
```
The function will halt the execution, instead of returning a two uint."""
WIKI_RECOMMENDATION = "Use the `leave` statement."
def _check_function(self, f: Function) -> List[Output]:
results: List[Output] = []
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"]
json = self.generate_result(info)
results.append(json)
return results
def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_declared:
if (
len(f.returns) == 2
and f.contains_assembly
and f.visibility not in ["public", "external"]
):
results += self._check_function(f)
return results

@ -33,7 +33,7 @@ class IncorrectSolc(AbstractDetector):
HELP = "Incorrect Solidity version"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity"
WIKI_TITLE = "Incorrect versions of Solidity"

@ -24,7 +24,7 @@ class NamingConvention(AbstractDetector):
HELP = "Conformity to Solidity naming conventions"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions"
WIKI_TITLE = "Conformance to Solidity naming conventions"
@ -45,6 +45,14 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
def is_cap_words(name: str) -> bool:
return re.search("^[A-Z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_immutable_naming(name: str) -> bool:
return re.search("^i_[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_state_naming(name: str) -> bool:
return re.search("^s_[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_mixed_case(name: str) -> bool:
return re.search("^[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@ -168,9 +176,16 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
else:
if var.visibility in ["private", "internal"]:
correct_naming = self.is_mixed_case_with_underscore(var.name)
correct_naming = self.is_mixed_case_with_underscore(
var.name
) or self.is_state_naming(var.name)
if not correct_naming and var.is_immutable:
correct_naming = self.is_immutable_naming(var.name)
else:
correct_naming = self.is_mixed_case(var.name)
if not correct_naming:
info = ["Variable ", var, " is not in mixedCase\n"]

@ -0,0 +1,93 @@
"""
Module detecting incorrect operator usage for exponentiation where bitwise xor '^' is used instead of '**'
"""
from typing import Tuple, List, Union
from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import Binary, BinaryType, Operation
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables.constant import Constant
from slither.utils.output import Output
def _is_constant_candidate(var: Union[RVALUE, Function]) -> bool:
"""
Check if the variable is a constant.
Do not consider variable that are expressed with hexadecimal.
Something like 2^0xf is likely to be a correct bitwise operator
:param var:
:return:
"""
return isinstance(var, Constant) and not var.original_value.startswith("0x")
def _is_bitwise_xor_on_constant(ir: Operation) -> bool:
return (
isinstance(ir, Binary)
and ir.type == BinaryType.CARET
and (_is_constant_candidate(ir.variable_left) or _is_constant_candidate(ir.variable_right))
)
def _detect_incorrect_operator(contract: Contract) -> List[Tuple[Function, Node]]:
ret: List[Tuple[Function, Node]] = []
f: Function
for f in contract.functions + contract.modifiers: # type:ignore
# Heuristic: look for binary expressions with ^ operator where at least one of the operands is a constant, and
# the constant is not in hex, because hex typically is used with bitwise xor and not exponentiation
nodes = [node for node in f.nodes for ir in node.irs if _is_bitwise_xor_on_constant(ir)]
for node in nodes:
ret.append((f, node))
return ret
# pylint: disable=too-few-public-methods
class IncorrectOperatorExponentiation(AbstractDetector):
"""
Incorrect operator usage of bitwise xor mistaking it for exponentiation
"""
ARGUMENT = "incorrect-exp"
HELP = "Incorrect exponentiation"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation"
WIKI_TITLE = "Incorrect exponentiation"
WIKI_DESCRIPTION = "Detect use of bitwise `xor ^` instead of exponential `**`"
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Bug{
uint UINT_MAX = 2^256 - 1;
...
}
```
Alice deploys a contract in which `UINT_MAX` incorrectly uses `^` operator instead of `**` for exponentiation"""
WIKI_RECOMMENDATION = "Use the correct operator `**` for exponentiation."
def _detect(self) -> List[Output]:
"""Detect the incorrect operator usage for exponentiation where bitwise xor ^ is used instead of **
Returns:
list: (function, node)
"""
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
res = _detect_incorrect_operator(c)
for (func, node) in res:
info: DETECTOR_INFO = [
func,
" has bitwise-xor operator ^ instead of the exponentiation operator **: \n",
]
info += ["\t - ", node, "\n"]
results.append(self.generate_result(info))
return results

@ -31,7 +31,7 @@ class DeprecatedStandards(AbstractDetector):
HELP = "Deprecated Solidity Standards"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards"
WIKI_TITLE = "Deprecated standards"

@ -0,0 +1,123 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Contract
from slither.core.declarations.function import Function
from slither.core.solidity_types import Type
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import LowLevelCall, HighLevelCall
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.utils.output import Output
class ReturnBomb(AbstractDetector):
ARGUMENT = "return-bomb"
HELP = "A low level callee may consume all callers gas unexpectedly."
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb"
WIKI_TITLE = "Return Bomb"
WIKI_DESCRIPTION = "A low level callee may consume all callers gas unexpectedly."
WIKI_EXPLOIT_SCENARIO = """
```solidity
//Modified from https://github.com/nomad-xyz/ExcessivelySafeCall
contract BadGuy {
function youveActivateMyTrapCard() external pure returns (bytes memory) {
assembly{
revert(0, 1000000)
}
}
}
contract Mark {
function oops(address badGuy) public{
bool success;
bytes memory ret;
// Mark pays a lot of gas for this copy
//(success, ret) = badGuy.call{gas:10000}(
(success, ret) = badGuy.call(
abi.encodeWithSelector(
BadGuy.youveActivateMyTrapCard.selector
)
);
// Mark may OOG here, preventing local state changes
//importantCleanup();
}
}
```
After Mark calls BadGuy bytes are copied from returndata to memory, the memory expansion cost is paid. This means that when using a standard solidity call, the callee can "returnbomb" the caller, imposing an arbitrary gas cost.
Callee unexpectedly makes the caller OOG.
"""
WIKI_RECOMMENDATION = "Avoid unlimited implicit decoding of returndata."
@staticmethod
def is_dynamic_type(ty: Type) -> bool:
# ty.is_dynamic ?
name = str(ty)
if "[]" in name or name in ("bytes", "string"):
return True
return False
def get_nodes_for_function(self, function: Function, contract: Contract) -> List[Node]:
nodes = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, (HighLevelCall, LowLevelCall)):
if not is_tainted(ir.destination, contract): # type:ignore
# Only interested if the target address is controlled/tainted
continue
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function):
# in normal highlevel calls return bombs are _possible_
# if the return type is dynamic and the caller tries to copy and decode large data
has_dyn = False
if ir.function.return_type:
has_dyn = any(
self.is_dynamic_type(ty) for ty in ir.function.return_type
)
if not has_dyn:
continue
# If a gas budget was specified then the
# user may not know about the return bomb
if ir.call_gas is None:
# if a gas budget was NOT specified then the caller
# may already suspect the call may spend all gas?
continue
nodes.append(node)
# TODO: check that there is some state change after the call
return nodes
def _detect(self) -> List[Output]:
results = []
for contract in self.compilation_unit.contracts:
for function in contract.functions_declared:
nodes = self.get_nodes_for_function(function, contract)
if nodes:
info: DETECTOR_INFO = [
function,
" tries to limit the gas of an external call that controls implicit decoding\n",
]
for node in sorted(nodes, key=lambda x: x.node_id):
info += ["\t", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,69 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import (
Binary,
BinaryType,
)
from slither.core.declarations import Function
from slither.utils.output import Output
class TautologicalCompare(AbstractDetector):
"""
Same variable comparison detector
"""
ARGUMENT = "tautological-compare"
HELP = "Comparing a variable to itself always returns true or false, depending on comparison"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#tautological-compare"
WIKI_TITLE = "Tautological compare"
WIKI_DESCRIPTION = "A variable compared to itself is probably an error as it will always return `true` for `==`, `>=`, `<=` and always `false` for `<`, `>` and `!=`."
WIKI_EXPLOIT_SCENARIO = """
```solidity
function check(uint a) external returns(bool){
return (a >= a);
}
```
`check` always return true."""
WIKI_RECOMMENDATION = "Remove comparison or compare to different value."
def _check_function(self, f: Function) -> List[Output]:
affected_nodes = set()
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, Binary):
if ir.type in [
BinaryType.GREATER,
BinaryType.GREATER_EQUAL,
BinaryType.LESS,
BinaryType.LESS_EQUAL,
BinaryType.EQUAL,
BinaryType.NOT_EQUAL,
]:
if ir.variable_left == ir.variable_right:
affected_nodes.add(node)
results = []
for n in affected_nodes:
info: DETECTOR_INFO = [f, " compares a variable to itself:\n\t", n, "\n"]
res = self.generate_result(info)
results.append(res)
return results
def _detect(self):
results = []
for f in self.compilation_unit.functions_and_modifiers:
results.extend(self._check_function(f))
return results

@ -11,7 +11,9 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.exceptions import SlitherError
from slither.printers.abstract_printer import AbstractPrinter
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.vyper_parsing.vyper_compilation_unit import VyperCompilationUnit
from slither.utils.output import Output
from slither.vyper_parsing.ast.ast import parse
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -62,16 +64,6 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
triage_mode (bool): if true, switch to triage mode (default false)
exclude_dependencies (bool): if true, exclude results that are only related to dependencies
generate_patches (bool): if true, patches are generated (json output only)
truffle_ignore (bool): ignore truffle.js presence (default false)
truffle_build_directory (str): build truffle directory (default 'build/contracts')
truffle_ignore_compile (bool): do not run truffle compile (default False)
truffle_version (str): use a specific truffle version (default None)
embark_ignore (bool): ignore embark.js presence (default false)
embark_ignore_compile (bool): do not run embark build (default False)
embark_overwrite_config (bool): overwrite original config file (default false)
change_line_prefix (str): Change the line prefix (default #)
for the displayed source codes (i.e. file.sol#1).
@ -108,13 +100,23 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
for compilation_unit in crytic_compile.compilation_units.values():
compilation_unit_slither = SlitherCompilationUnit(self, compilation_unit)
self._compilation_units.append(compilation_unit_slither)
parser = SlitherCompilationUnitSolc(compilation_unit_slither)
self._parsers.append(parser)
for path, ast in compilation_unit.asts.items():
parser.parse_top_level_from_loaded_json(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
if compilation_unit_slither.is_vyper:
vyper_parser = VyperCompilationUnit(compilation_unit_slither)
for path, ast in compilation_unit.asts.items():
ast_nodes = parse(ast["ast"])
vyper_parser.parse_module(ast_nodes, path)
self._parsers.append(vyper_parser)
else:
# Solidity specific
assert compilation_unit_slither.is_solidity
sol_parser = SlitherCompilationUnitSolc(compilation_unit_slither)
self._parsers.append(sol_parser)
for path, ast in compilation_unit.asts.items():
sol_parser.parse_top_level_items(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
if kwargs.get("generate_patches", False):
self.generate_patches = True

@ -114,8 +114,8 @@ def convert_expression(expression: Expression, node: "Node") -> List[Operation]:
visitor = ExpressionToSlithIR(expression, node)
result = visitor.result()
result = apply_ir_heuristics(result, node)
is_solidity = node.compilation_unit.is_solidity
result = apply_ir_heuristics(result, node, is_solidity)
if result:
if node.type in [NodeType.IF, NodeType.IFLOOP]:
@ -657,7 +657,9 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if isinstance(t, ArrayType) or (
isinstance(t, ElementaryType) and t.type == "bytes"
):
if ir.function_name == "push" and len(ir.arguments) <= 1:
# Solidity uses push
# Vyper uses append
if ir.function_name in ["push", "append"] and len(ir.arguments) <= 1:
return convert_to_push(ir, node)
if ir.function_name == "pop" and len(ir.arguments) == 0:
return convert_to_pop(ir, node)
@ -1215,6 +1217,7 @@ def can_be_low_level(ir: HighLevelCall) -> bool:
"delegatecall",
"callcode",
"staticcall",
"raw_call",
]
@ -1243,13 +1246,14 @@ def convert_to_low_level(
ir.set_node(prev_ir.node)
ir.lvalue.set_type(ElementaryType("bool"))
return ir
if ir.function_name in ["call", "delegatecall", "callcode", "staticcall"]:
if ir.function_name in ["call", "delegatecall", "callcode", "staticcall", "raw_call"]:
new_ir = LowLevelCall(
ir.destination, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call
)
new_ir.call_gas = ir.call_gas
new_ir.call_value = ir.call_value
new_ir.arguments = ir.arguments
# TODO fix this for Vyper
if ir.node.compilation_unit.solc_version >= "0.5":
new_ir.lvalue.set_type([ElementaryType("bool"), ElementaryType("bytes")])
else:
@ -1294,7 +1298,12 @@ def convert_to_solidity_func(
and len(new_ir.arguments) == 2
and isinstance(new_ir.arguments[1], list)
):
types = list(new_ir.arguments[1])
types = []
for arg_type in new_ir.arguments[1]:
decode_type = arg_type
if isinstance(decode_type, (Structure, Enum, Contract)):
decode_type = UserDefinedType(decode_type)
types.append(decode_type)
new_ir.lvalue.set_type(types)
# abi.decode where the type to decode is a singleton
# abi.decode(a, (uint))
@ -1991,7 +2000,7 @@ def _find_source_mapping_references(irs: List[Operation]) -> None:
###################################################################################
def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]:
def apply_ir_heuristics(irs: List[Operation], node: "Node", is_solidity: bool) -> List[Operation]:
"""
Apply a set of heuristic to improve slithIR
"""
@ -2001,8 +2010,11 @@ def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]:
irs = propagate_type_and_convert_call(irs, node)
irs = remove_unused(irs)
find_references_origin(irs)
convert_constant_types(irs)
convert_delete(irs)
# These are heuristics that are only applied to Solidity
if is_solidity:
convert_constant_types(irs)
convert_delete(irs)
_find_source_mapping_references(irs)

@ -4,6 +4,7 @@ from slither.core.declarations import Contract
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAlias
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
@ -21,10 +22,10 @@ class TypeConversion(OperationWithLValue):
super().__init__()
assert is_valid_rvalue(variable) or isinstance(variable, Contract)
assert is_valid_lvalue(result)
assert isinstance(variable_type, (TypeAlias, UserDefinedType, ElementaryType))
assert isinstance(variable_type, (TypeAlias, UserDefinedType, ElementaryType, ArrayType))
self._variable = variable
self._type: Union[TypeAlias, UserDefinedType, ElementaryType] = variable_type
self._type: Union[TypeAlias, UserDefinedType, ElementaryType, ArrayType] = variable_type
self._lvalue = result
@property

@ -11,7 +11,7 @@ from slither.utils.integer_conversion import convert_string_to_int
class Constant(SlithIRVariable):
def __init__(
self,
val: Union[int, str],
val: str,
constant_type: Optional[ElementaryType] = None,
subdenomination: Optional[str] = None,
) -> None: # pylint: disable=too-many-branches

@ -175,7 +175,7 @@ def parse_call(
called = parse_expression(children[0], caller_context)
arguments = [parse_expression(a, caller_context) for a in children[1::]]
if isinstance(called, SuperCallExpression):
if isinstance(called, SuperIdentifier):
sp = SuperCallExpression(called, arguments, type_return)
sp.set_offset(expression["src"], caller_context.compilation_unit)
return sp

@ -75,9 +75,12 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
def __init__(self, compilation_unit: SlitherCompilationUnit) -> None:
super().__init__()
self._compilation_unit: SlitherCompilationUnit = compilation_unit
self._contracts_by_id: Dict[int, ContractSolc] = {}
self._parsed = False
self._analyzed = False
self._is_compact_ast = False
self._underlying_contract_to_parser: Dict[Contract, ContractSolc] = {}
self._structures_top_level_parser: List[StructureTopLevelSolc] = []
@ -85,11 +88,6 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
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
self._compilation_unit = compilation_unit
self._all_functions_and_modifier_parser: List[FunctionSolc] = []
self._top_level_contracts_counter = 0
@ -145,14 +143,14 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
data_loaded = json.loads(json_data)
# Truffle AST
if "ast" in data_loaded:
self.parse_top_level_from_loaded_json(data_loaded["ast"], data_loaded["sourcePath"])
self.parse_top_level_items(data_loaded["ast"], data_loaded["sourcePath"])
return True
# solc AST, where the non-json text was removed
if "attributes" in data_loaded:
filename = data_loaded["attributes"]["absolutePath"]
else:
filename = data_loaded["absolutePath"]
self.parse_top_level_from_loaded_json(data_loaded, filename)
self.parse_top_level_items(data_loaded, filename)
return True
except ValueError:
@ -163,7 +161,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
json_data = json_data[first:last]
data_loaded = json.loads(json_data)
self.parse_top_level_from_loaded_json(data_loaded, filename)
self.parse_top_level_items(data_loaded, filename)
return True
return False
@ -197,7 +195,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.enums_top_level.append(enum)
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
def parse_top_level_from_loaded_json(self, data_loaded: Dict, filename: str) -> None:
def parse_top_level_items(self, data_loaded: Dict, filename: str) -> None:
if not data_loaded or data_loaded is None:
logger.error(
"crytic-compile returned an empty AST. "
@ -405,7 +403,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
def parse_contracts(self) -> None: # pylint: disable=too-many-statements,too-many-branches
if not self._underlying_contract_to_parser:
logger.info(
f"No contract were found in {self._compilation_unit.core.filename}, check the correct compilation"
f"No contracts were found in {self._compilation_unit.core.filename}, check the correct compilation"
)
if self._parsed:
raise Exception("Contract analysis can be run only once!")

@ -11,6 +11,7 @@ from slither.core.declarations import (
EnumContract,
EnumTopLevel,
Enum,
Structure,
)
from slither.core.expressions import (
AssignmentOperation,
@ -178,6 +179,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
def result(self) -> List[Operation]:
return self._result
# pylint: disable=too-many-branches,too-many-statements
def _post_assignement_operation(self, expression: AssignmentOperation) -> None:
left = get(expression.expression_left)
right = get(expression.expression_right)
@ -241,6 +243,35 @@ class ExpressionToSlithIR(ExpressionVisitor):
operation.set_expression(expression)
self._result.append(operation)
set_val(expression, left)
elif (
isinstance(left.type, UserDefinedType)
and isinstance(left.type.type, Structure)
and isinstance(right, TupleVariable)
):
# This will result in a `NewStructure` operation where
# each field is assigned the value unpacked from the tuple
# (see `slither.vyper_parsing.type_parsing.parse_type`)
args = []
for idx, elem in enumerate(left.type.type.elems.values()):
temp = TemporaryVariable(self._node)
temp.type = elem.type
args.append(temp)
operation = Unpack(temp, right, idx)
operation.set_expression(expression)
self._result.append(operation)
for arg in args:
op = Argument(arg)
op.set_expression(expression)
self._result.append(op)
operation = TmpCall(
left.type.type, len(left.type.type.elems), left, left.type.type.name
)
operation.set_expression(expression)
self._result.append(operation)
else:
operation = convert_assignment(
left, right, expression.type, expression.expression_return_type
@ -429,6 +460,21 @@ class ExpressionToSlithIR(ExpressionVisitor):
set_val(expression, t)
return
val = ReferenceVariable(self._node)
if (
isinstance(left, LocalVariable)
and isinstance(left.type, UserDefinedType)
and isinstance(left.type.type, Structure)
):
# We rewrite the index access to a tuple variable as
# an access to its field i.e. the 0th element is the field "_0"
# (see `slither.vyper_parsing.type_parsing.parse_type`)
operation = Member(left, Constant("_" + str(right)), val)
operation.set_expression(expression)
self._result.append(operation)
set_val(expression, val)
return
# access to anonymous array
# such as [0,1][x]
if isinstance(left, list):
@ -438,6 +484,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
operation = InitArray(init_array_right, init_array_val)
operation.set_expression(expression)
self._result.append(operation)
operation = Index(val, left, right)
operation.set_expression(expression)
self._result.append(operation)
@ -601,7 +648,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
expr = get(expression.expression)
val = TemporaryVariable(self._node)
expression_type = expression.type
assert isinstance(expression_type, (TypeAlias, UserDefinedType, ElementaryType))
assert isinstance(expression_type, (TypeAlias, UserDefinedType, ElementaryType, ArrayType))
operation = TypeConversion(val, expr, expression_type)
val.set_type(expression.type)
operation.set_expression(expression)

@ -0,0 +1,466 @@
from typing import Dict, Callable, List
from slither.vyper_parsing.ast.types import (
ASTNode,
Module,
ImportFrom,
EventDef,
AnnAssign,
Name,
Call,
StructDef,
VariableDecl,
Subscript,
Index,
Hex,
Int,
Str,
Tuple,
FunctionDef,
Assign,
Raise,
Attribute,
Assert,
Keyword,
Arguments,
Arg,
UnaryOp,
BinOp,
Expr,
Log,
Return,
VyDict,
VyList,
NameConstant,
If,
Compare,
For,
Break,
Continue,
Pass,
InterfaceDef,
EnumDef,
Bytes,
AugAssign,
BoolOp,
)
class ParsingError(Exception):
pass
def _extract_base_props(raw: Dict) -> Dict:
return {
"src": raw["src"],
"node_id": raw["node_id"],
}
def _extract_decl_props(raw: Dict) -> Dict:
return {
"doc_string": parse_doc_str(raw["doc_string"]) if raw["doc_string"] else None,
**_extract_base_props(raw),
}
def parse_module(raw: Dict) -> Module:
nodes_parsed: List[ASTNode] = []
for node in raw["body"]:
nodes_parsed.append(parse(node))
return Module(name=raw["name"], body=nodes_parsed, **_extract_decl_props(raw))
def parse_import_from(raw: Dict) -> ImportFrom:
return ImportFrom(
module=raw["module"],
name=raw["name"],
alias=raw["alias"],
**_extract_base_props(raw),
)
def parse_event_def(raw: Dict) -> EventDef:
body_parsed: List[ASTNode] = []
for node in raw["body"]:
body_parsed.append(parse(node))
return EventDef(
name=raw["name"],
body=body_parsed,
*_extract_base_props(raw),
)
def parse_ann_assign(raw: Dict) -> AnnAssign:
return AnnAssign(
target=parse(raw["target"]),
annotation=parse(raw["annotation"]),
value=parse(raw["value"]) if raw["value"] else None,
**_extract_base_props(raw),
)
def parse_name(raw: Dict) -> Name:
return Name(
id=raw["id"],
**_extract_base_props(raw),
)
def parse_call(raw: Dict) -> Call:
return Call(
func=parse(raw["func"]),
args=[parse(arg) for arg in raw["args"]],
keyword=parse(raw["keyword"]) if raw["keyword"] else None,
keywords=[parse(keyword) for keyword in raw["keywords"]],
**_extract_base_props(raw),
)
def parse_struct_def(raw: Dict) -> StructDef:
body_parsed: List[ASTNode] = []
for node in raw["body"]:
body_parsed.append(parse(node))
return StructDef(
name=raw["name"],
body=body_parsed,
**_extract_base_props(raw),
)
def parse_variable_decl(raw: Dict) -> VariableDecl:
return VariableDecl(
annotation=parse(raw["annotation"]),
value=parse(raw["value"]) if raw["value"] else None,
target=parse(raw["target"]),
is_constant=raw["is_constant"],
is_immutable=raw["is_immutable"],
is_public=raw["is_public"],
**_extract_base_props(raw),
)
def parse_subscript(raw: Dict) -> Subscript:
return Subscript(
value=parse(raw["value"]),
slice=parse(raw["slice"]),
**_extract_base_props(raw),
)
def parse_index(raw: Dict) -> Index:
return Index(value=parse(raw["value"]), **_extract_base_props(raw))
def parse_bytes(raw: Dict) -> Bytes:
return Bytes(value=raw["value"], **_extract_base_props(raw))
def parse_hex(raw: Dict) -> Hex:
return Hex(value=raw["value"], **_extract_base_props(raw))
def parse_int(raw: Dict) -> Int:
return Int(value=raw["value"], **_extract_base_props(raw))
def parse_str(raw: Dict) -> Str:
return Str(value=raw["value"], **_extract_base_props(raw))
def parse_tuple(raw: Dict) -> ASTNode:
return Tuple(elements=[parse(elem) for elem in raw["elements"]], **_extract_base_props(raw))
def parse_function_def(raw: Dict) -> FunctionDef:
body_parsed: List[ASTNode] = []
for node in raw["body"]:
body_parsed.append(parse(node))
decorators_parsed: List[ASTNode] = []
for node in raw["decorator_list"]:
decorators_parsed.append(parse(node))
return FunctionDef(
name=raw["name"],
args=parse_arguments(raw["args"]),
returns=parse(raw["returns"]) if raw["returns"] else None,
body=body_parsed,
pos=raw["pos"],
decorators=decorators_parsed,
**_extract_decl_props(raw),
)
def parse_assign(raw: Dict) -> Assign:
return Assign(
target=parse(raw["target"]),
value=parse(raw["value"]),
**_extract_base_props(raw),
)
def parse_attribute(raw: Dict) -> Attribute:
return Attribute(
value=parse(raw["value"]),
attr=raw["attr"],
**_extract_base_props(raw),
)
def parse_arguments(raw: Dict) -> Arguments:
return Arguments(
args=[parse_arg(arg) for arg in raw["args"]],
default=parse(raw["default"]) if raw["default"] else None,
defaults=[parse(x) for x in raw["defaults"]],
**_extract_base_props(raw),
)
def parse_arg(raw: Dict) -> Arg:
return Arg(arg=raw["arg"], annotation=parse(raw["annotation"]), **_extract_base_props(raw))
def parse_assert(raw: Dict) -> Assert:
return Assert(
test=parse(raw["test"]),
msg=parse(raw["msg"]) if raw["msg"] else None,
**_extract_base_props(raw),
)
def parse_raise(raw: Dict) -> Raise:
return Raise(exc=parse(raw["exc"]) if raw["exc"] else None, **_extract_base_props(raw))
def parse_expr(raw: Dict) -> Expr:
return Expr(value=parse(raw["value"]), **_extract_base_props(raw))
# This is done for convenience so we can call `UnaryOperationType.get_type` during expression parsing.
unop_ast_type_to_op_symbol = {"Not": "!", "USub": "-"}
def parse_unary_op(raw: Dict) -> UnaryOp:
unop_str = unop_ast_type_to_op_symbol[raw["op"]["ast_type"]]
return UnaryOp(op=unop_str, operand=parse(raw["operand"]), **_extract_base_props(raw))
# This is done for convenience so we can call `BinaryOperationType.get_type` during expression parsing.
binop_ast_type_to_op_symbol = {
"Add": "+",
"Mult": "*",
"Sub": "-",
"Div": "/",
"Pow": "**",
"Mod": "%",
"BitAnd": "&",
"BitOr": "|",
"Shr": "<<",
"Shl": ">>",
"NotEq": "!=",
"Eq": "==",
"LtE": "<=",
"GtE": ">=",
"Lt": "<",
"Gt": ">",
"In": "In",
"NotIn": "NotIn",
}
def parse_bin_op(raw: Dict) -> BinOp:
arith_op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]]
return BinOp(
left=parse(raw["left"]),
op=arith_op_str,
right=parse(raw["right"]),
**_extract_base_props(raw),
)
def parse_compare(raw: Dict) -> Compare:
logical_op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]]
return Compare(
left=parse(raw["left"]),
op=logical_op_str,
right=parse(raw["right"]),
**_extract_base_props(raw),
)
def parse_keyword(raw: Dict) -> Keyword:
return Keyword(arg=raw["arg"], value=parse(raw["value"]), **_extract_base_props(raw))
def parse_log(raw: Dict) -> Log:
return Log(value=parse(raw["value"]), **_extract_base_props(raw))
def parse_return(raw: Dict) -> Return:
return Return(value=parse(raw["value"]) if raw["value"] else None, **_extract_base_props(raw))
def parse_dict(raw: Dict) -> ASTNode:
return VyDict(
keys=[parse(x) for x in raw["keys"]],
values=[parse(x) for x in raw["values"]],
**_extract_base_props(raw),
)
def parse_list(raw: Dict) -> VyList:
return VyList(elements=[parse(x) for x in raw["elements"]], **_extract_base_props(raw))
def parse_name_constant(raw: Dict) -> NameConstant:
return NameConstant(value=raw["value"], **_extract_base_props(raw))
def parse_doc_str(raw: Dict) -> str:
assert isinstance(raw["value"], str)
return raw["value"]
def parse_if(raw: Dict) -> ASTNode:
return If(
test=parse(raw["test"]),
body=[parse(x) for x in raw["body"]],
orelse=[parse(x) for x in raw["orelse"]],
**_extract_base_props(raw),
)
def parse_for(raw: Dict) -> For:
return For(
target=parse(raw["target"]),
iter=parse(raw["iter"]),
body=[parse(x) for x in raw["body"]],
**_extract_base_props(raw),
)
def parse_break(raw: Dict) -> Break:
return Break(**_extract_base_props(raw))
def parse_continue(raw: Dict) -> Continue:
return Continue(**_extract_base_props(raw))
def parse_pass(raw: Dict) -> Pass:
return Pass(
**_extract_base_props(raw),
)
def parse_interface_def(raw: Dict) -> InterfaceDef:
nodes_parsed: List[ASTNode] = []
for node in raw["body"]:
nodes_parsed.append(parse(node))
return InterfaceDef(name=raw["name"], body=nodes_parsed, **_extract_base_props(raw))
def parse_enum_def(raw: Dict) -> EnumDef:
nodes_parsed: List[ASTNode] = []
for node in raw["body"]:
nodes_parsed.append(parse(node))
return EnumDef(name=raw["name"], body=nodes_parsed, **_extract_base_props(raw))
aug_assign_ast_type_to_op_symbol = {
"Add": "+=",
"Mult": "*=",
"Sub": "-=",
"Div": "-=",
"Pow": "**=",
"Mod": "%=",
"BitAnd": "&=",
"BitOr": "|=",
"Shr": "<<=",
"Shl": ">>=",
}
def parse_aug_assign(raw: Dict) -> AugAssign:
op_str = aug_assign_ast_type_to_op_symbol[raw["op"]["ast_type"]]
return AugAssign(
target=parse(raw["target"]),
op=op_str,
value=parse(raw["value"]),
**_extract_base_props(raw),
)
def parse_unsupported(raw: Dict) -> ASTNode:
raise ParsingError("unsupported Vyper node", raw["ast_type"], raw.keys(), raw)
bool_op_ast_type_to_op_symbol = {"And": "&&", "Or": "||"}
def parse_bool_op(raw: Dict) -> BoolOp:
op_str = bool_op_ast_type_to_op_symbol[raw["op"]["ast_type"]]
return BoolOp(op=op_str, values=[parse(x) for x in raw["values"]], **_extract_base_props(raw))
def parse(raw: Dict) -> ASTNode:
try:
return PARSERS.get(raw["ast_type"], parse_unsupported)(raw)
except ParsingError as e:
raise e
except Exception as e:
raise e
# raise ParsingError("failed to parse Vyper node", raw["ast_type"], e, raw.keys(), raw)
PARSERS: Dict[str, Callable[[Dict], ASTNode]] = {
"Module": parse_module,
"ImportFrom": parse_import_from,
"EventDef": parse_event_def,
"AnnAssign": parse_ann_assign,
"Name": parse_name,
"Call": parse_call,
"Pass": parse_pass,
"StructDef": parse_struct_def,
"VariableDecl": parse_variable_decl,
"Subscript": parse_subscript,
"Index": parse_index,
"Hex": parse_hex,
"Int": parse_int,
"Str": parse_str,
"DocStr": parse_doc_str,
"Tuple": parse_tuple,
"FunctionDef": parse_function_def,
"Assign": parse_assign,
"Raise": parse_raise,
"Attribute": parse_attribute,
"Assert": parse_assert,
"keyword": parse_keyword,
"arguments": parse_arguments,
"arg": parse_arg,
"UnaryOp": parse_unary_op,
"BinOp": parse_bin_op,
"Expr": parse_expr,
"Log": parse_log,
"Return": parse_return,
"If": parse_if,
"Dict": parse_dict,
"List": parse_list,
"Compare": parse_compare,
"NameConstant": parse_name_constant,
"For": parse_for,
"Break": parse_break,
"Continue": parse_continue,
"InterfaceDef": parse_interface_def,
"EnumDef": parse_enum_def,
"Bytes": parse_bytes,
"AugAssign": parse_aug_assign,
"BoolOp": parse_bool_op,
}

@ -0,0 +1,262 @@
from __future__ import annotations
from typing import List, Optional, Union
from dataclasses import dataclass
@dataclass
class ASTNode:
src: str
node_id: int
@dataclass
class Definition(ASTNode):
doc_string: Optional[str]
@dataclass
class Module(Definition):
body: List[ASTNode]
name: str
@dataclass
class ImportFrom(ASTNode):
module: str
name: str
alias: Optional[str]
@dataclass
class EventDef(ASTNode):
name: str
body: List[AnnAssign]
@dataclass
class AnnAssign(ASTNode):
target: Name
annotation: Union[Subscript, Name, Call]
value: Optional[ASTNode]
@dataclass
class Name(ASTNode): # type or identifier
id: str
@dataclass
class Call(ASTNode):
func: ASTNode
args: List[ASTNode]
keyword: Optional[ASTNode]
keywords: List[ASTNode]
@dataclass
class Pass(ASTNode):
pass
@dataclass
class StructDef(ASTNode):
name: str
body: List[AnnAssign]
@dataclass
class VariableDecl(ASTNode):
annotation: ASTNode
target: ASTNode
value: Optional[ASTNode]
is_constant: bool
is_immutable: bool
is_public: bool
@dataclass
class Subscript(ASTNode):
value: ASTNode
slice: ASTNode
@dataclass
class Index(ASTNode):
value: ASTNode
@dataclass
class Bytes(ASTNode):
value: bytes
@dataclass
class Hex(ASTNode):
value: str
@dataclass
class Int(ASTNode):
value: int
@dataclass
class Str(ASTNode):
value: str
@dataclass
class VyList(ASTNode):
elements: List[ASTNode]
@dataclass
class VyDict(ASTNode):
keys: List[ASTNode]
values: List[ASTNode]
@dataclass
class Tuple(ASTNode):
elements: List[ASTNode]
@dataclass
class FunctionDef(Definition):
name: str
args: Optional[Arguments]
returns: Optional[List[ASTNode]]
body: List[ASTNode]
decorators: Optional[List[ASTNode]]
pos: Optional[any] # not sure what this is
@dataclass
class Assign(ASTNode):
target: ASTNode
value: ASTNode
@dataclass
class Attribute(ASTNode):
value: ASTNode
attr: str
@dataclass
class Arguments(ASTNode):
args: List[Arg]
default: Optional[ASTNode]
defaults: List[ASTNode]
@dataclass
class Arg(ASTNode):
arg: str
annotation: Optional[ASTNode]
@dataclass
class Assert(ASTNode):
test: ASTNode
msg: Optional[Str]
@dataclass
class Raise(ASTNode):
exc: ASTNode
@dataclass
class Expr(ASTNode):
value: ASTNode
@dataclass
class UnaryOp(ASTNode):
op: ASTNode
operand: ASTNode
@dataclass
class BinOp(ASTNode):
left: ASTNode
op: str
right: ASTNode
@dataclass
class Keyword(ASTNode):
arg: str
value: ASTNode
@dataclass
class Log(ASTNode):
value: ASTNode
@dataclass
class Return(ASTNode):
value: Optional[ASTNode]
@dataclass
class If(ASTNode):
test: ASTNode
body: List[ASTNode]
orelse: List[ASTNode]
@dataclass
class Compare(ASTNode):
left: ASTNode
op: ASTNode
right: ASTNode
@dataclass
class NameConstant(ASTNode):
value: bool
@dataclass
class For(ASTNode):
target: ASTNode
iter: ASTNode
body: List[ASTNode]
@dataclass
class Continue(ASTNode):
pass
@dataclass
class Break(ASTNode):
pass
@dataclass
class InterfaceDef(ASTNode):
name: str
body: List[ASTNode]
@dataclass
class EnumDef(ASTNode):
name: str
body: List[ASTNode]
@dataclass
class AugAssign(ASTNode):
target: ASTNode
op: ASTNode
value: ASTNode
@dataclass
class BoolOp(ASTNode):
op: ASTNode
values: List[ASTNode]

@ -0,0 +1,66 @@
from typing import Optional, Dict
from slither.core.cfg.node import Node
from slither.core.cfg.node import NodeType
from slither.core.expressions.assignment_operation import (
AssignmentOperation,
AssignmentOperationType,
)
from slither.core.expressions.identifier import Identifier
from slither.vyper_parsing.expressions.expression_parsing import parse_expression
from slither.visitors.expression.find_calls import FindCalls
from slither.visitors.expression.read_var import ReadVar
from slither.visitors.expression.write_var import WriteVar
class NodeVyper:
def __init__(self, node: Node) -> None:
self._unparsed_expression: Optional[Dict] = None
self._node = node
@property
def underlying_node(self) -> Node:
return self._node
def add_unparsed_expression(self, expression: Dict) -> None:
assert self._unparsed_expression is None
self._unparsed_expression = expression
def analyze_expressions(self, caller_context) -> None:
if self._node.type == NodeType.VARIABLE and not self._node.expression:
self._node.add_expression(self._node.variable_declaration.expression)
if self._unparsed_expression:
expression = parse_expression(self._unparsed_expression, caller_context)
self._node.add_expression(expression)
self._unparsed_expression = None
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)
expression = self._node.expression
read_var = ReadVar(expression)
self._node.variables_read_as_expression = read_var.result()
write_var = WriteVar(expression)
self._node.variables_written_as_expression = write_var.result()
find_call = FindCalls(expression)
self._node.calls_as_expression = find_call.result()
self._node.external_calls_as_expressions = [
c for c in self._node.calls_as_expression if not isinstance(c.called, Identifier)
]
self._node.internal_calls_as_expressions = [
c for c in self._node.calls_as_expression if isinstance(c.called, Identifier)
]

@ -0,0 +1,524 @@
from pathlib import Path
from typing import List, TYPE_CHECKING
from slither.vyper_parsing.ast.types import (
Module,
FunctionDef,
EventDef,
EnumDef,
StructDef,
VariableDecl,
ImportFrom,
InterfaceDef,
AnnAssign,
Expr,
Name,
Arguments,
Index,
Subscript,
Int,
Arg,
)
from slither.vyper_parsing.declarations.event import EventVyper
from slither.vyper_parsing.declarations.struct import StructVyper
from slither.vyper_parsing.variables.state_variable import StateVariableVyper
from slither.vyper_parsing.declarations.function import FunctionVyper
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations import Contract, StructureContract, EnumContract, Event
from slither.core.variables.state_variable import StateVariable
if TYPE_CHECKING:
from slither.vyper_parsing.vyper_compilation_unit import VyperCompilationUnit
class ContractVyper: # pylint: disable=too-many-instance-attributes
def __init__(
self, slither_parser: "VyperCompilationUnit", contract: Contract, module: Module
) -> None:
self._contract: Contract = contract
self._slither_parser: "VyperCompilationUnit" = slither_parser
self._data = module
# Vyper models only have one contract (aside from interfaces) and the name is the file path
# We use the stem to make it a more user friendly name that is easy to query via canonical name
self._contract.name = Path(module.name).stem
self._contract.id = module.node_id
self._is_analyzed: bool = False
self._enumsNotParsed: List[EnumDef] = []
self._structuresNotParsed: List[StructDef] = []
self._variablesNotParsed: List[VariableDecl] = []
self._eventsNotParsed: List[EventDef] = []
self._functionsNotParsed: List[FunctionDef] = []
self._structures_parser: List[StructVyper] = []
self._variables_parser: List[StateVariableVyper] = []
self._events_parser: List[EventVyper] = []
self._functions_parser: List[FunctionVyper] = []
self._parse_contract_items()
@property
def is_analyzed(self) -> bool:
return self._is_analyzed
def set_is_analyzed(self, is_analyzed: bool) -> None:
self._is_analyzed = is_analyzed
@property
def underlying_contract(self) -> Contract:
return self._contract
def _parse_contract_items(self) -> None:
for node in self._data.body:
if isinstance(node, FunctionDef):
self._functionsNotParsed.append(node)
elif isinstance(node, EventDef):
self._eventsNotParsed.append(node)
elif isinstance(node, VariableDecl):
self._variablesNotParsed.append(node)
elif isinstance(node, EnumDef):
self._enumsNotParsed.append(node)
elif isinstance(node, StructDef):
self._structuresNotParsed.append(node)
elif isinstance(node, ImportFrom):
# TOOD aliases
# We create an `InterfaceDef` sense the compilatuion unit does not contain the actual interface
# https://github.com/vyperlang/vyper/tree/master/vyper/builtins/interfaces
if node.module == "vyper.interfaces":
interfaces = {
"ERC20Detailed": InterfaceDef(
src="-1:-1:-1",
node_id=-1,
name="ERC20Detailed",
body=[
FunctionDef(
src="-1:-1:-1",
node_id=-1,
doc_string=None,
name="name",
args=Arguments(
src="-1:-1:-1",
node_id=-1,
args=[],
default=None,
defaults=[],
),
returns=Subscript(
src="-1:-1:-1",
node_id=-1,
value=Name(src="-1:-1:-1", node_id=-1, id="String"),
slice=Index(
src="-1:-1:-1",
node_id=-1,
value=Int(src="-1:-1:-1", node_id=-1, value=1),
),
),
body=[
Expr(
src="-1:-1:-1",
node_id=-1,
value=Name(src="-1:-1:-1", node_id=-1, id="view"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=-1,
doc_string=None,
name="symbol",
args=Arguments(
src="-1:-1:-1",
node_id=-1,
args=[],
default=None,
defaults=[],
),
returns=Subscript(
src="-1:-1:-1",
node_id=-1,
value=Name(src="-1:-1:-1", node_id=-1, id="String"),
slice=Index(
src="-1:-1:-1",
node_id=-1,
value=Int(src="-1:-1:-1", node_id=-1, value=1),
),
),
body=[
Expr(
src="-1:-1:-1",
node_id=-1,
value=Name(src="-1:-1:-1", node_id=-1, id="view"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=-1,
doc_string=None,
name="decimals",
args=Arguments(
src="-1:-1:-1",
node_id=-1,
args=[],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=-1, id="uint8"),
body=[
Expr(
src="-1:-1:-1",
node_id=-1,
value=Name(src="-1:-1:-1", node_id=-1, id="view"),
)
],
decorators=[],
pos=None,
),
],
),
"ERC20": InterfaceDef(
src="-1:-1:-1",
node_id=1,
name="ERC20",
body=[
FunctionDef(
src="-1:-1:-1",
node_id=2,
doc_string=None,
name="totalSupply",
args=Arguments(
src="-1:-1:-1",
node_id=3,
args=[],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=7, id="uint256"),
body=[
Expr(
src="-1:-1:-1",
node_id=4,
value=Name(src="-1:-1:-1", node_id=5, id="view"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=9,
doc_string=None,
name="balanceOf",
args=Arguments(
src="-1:-1:-1",
node_id=10,
args=[
Arg(
src="-1:-1:-1",
node_id=11,
arg="_owner",
annotation=Name(
src="-1:-1:-1", node_id=12, id="address"
),
)
],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=17, id="uint256"),
body=[
Expr(
src="-1:-1:-1",
node_id=14,
value=Name(src="-1:-1:-1", node_id=15, id="view"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=19,
doc_string=None,
name="allowance",
args=Arguments(
src="-1:-1:-1",
node_id=20,
args=[
Arg(
src="-1:-1:-1",
node_id=21,
arg="_owner",
annotation=Name(
src="-1:-1:-1", node_id=22, id="address"
),
),
Arg(
src="-1:-1:-1",
node_id=24,
arg="_spender",
annotation=Name(
src="-1:-1:-1", node_id=25, id="address"
),
),
],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=30, id="uint256"),
body=[
Expr(
src="-1:-1:-1",
node_id=27,
value=Name(src="-1:-1:-1", node_id=28, id="view"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=32,
doc_string=None,
name="transfer",
args=Arguments(
src="-1:-1:-1",
node_id=33,
args=[
Arg(
src="-1:-1:-1",
node_id=34,
arg="_to",
annotation=Name(
src="-1:-1:-1", node_id=35, id="address"
),
),
Arg(
src="-1:-1:-1",
node_id=37,
arg="_value",
annotation=Name(
src="-1:-1:-1", node_id=38, id="uint256"
),
),
],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=43, id="bool"),
body=[
Expr(
src="-1:-1:-1",
node_id=40,
value=Name(src="-1:-1:-1", node_id=41, id="nonpayable"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=45,
doc_string=None,
name="transferFrom",
args=Arguments(
src="-1:-1:-1",
node_id=46,
args=[
Arg(
src="-1:-1:-1",
node_id=47,
arg="_from",
annotation=Name(
src="-1:-1:-1", node_id=48, id="address"
),
),
Arg(
src="-1:-1:-1",
node_id=50,
arg="_to",
annotation=Name(
src="-1:-1:-1", node_id=51, id="address"
),
),
Arg(
src="-1:-1:-1",
node_id=53,
arg="_value",
annotation=Name(
src="-1:-1:-1", node_id=54, id="uint256"
),
),
],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=59, id="bool"),
body=[
Expr(
src="-1:-1:-1",
node_id=56,
value=Name(src="-1:-1:-1", node_id=57, id="nonpayable"),
)
],
decorators=[],
pos=None,
),
FunctionDef(
src="-1:-1:-1",
node_id=61,
doc_string=None,
name="approve",
args=Arguments(
src="-1:-1:-1",
node_id=62,
args=[
Arg(
src="-1:-1:-1",
node_id=63,
arg="_spender",
annotation=Name(
src="-1:-1:-1", node_id=64, id="address"
),
),
Arg(
src="-1:-1:-1",
node_id=66,
arg="_value",
annotation=Name(
src="-1:-1:-1", node_id=67, id="uint256"
),
),
],
default=None,
defaults=[],
),
returns=Name(src="-1:-1:-1", node_id=72, id="bool"),
body=[
Expr(
src="-1:-1:-1",
node_id=69,
value=Name(src="-1:-1:-1", node_id=70, id="nonpayable"),
)
],
decorators=[],
pos=None,
),
],
),
"ERC165": [],
"ERC721": [],
"ERC4626": [],
}
self._data.body.append(interfaces[node.name])
elif isinstance(node, InterfaceDef):
# This needs to be done lazily as interfaces can refer to constant state variables
contract = Contract(self._contract.compilation_unit, self._contract.file_scope)
contract.set_offset(node.src, self._contract.compilation_unit)
contract.is_interface = True
contract_parser = ContractVyper(self._slither_parser, contract, node)
self._contract.file_scope.contracts[contract.name] = contract
# pylint: disable=protected-access
self._slither_parser._underlying_contract_to_parser[contract] = contract_parser
elif isinstance(node, AnnAssign): # implements: ERC20
pass # TODO
else:
raise ValueError("Unknown contract node: ", node)
def parse_enums(self) -> None:
for enum in self._enumsNotParsed:
name = enum.name
canonicalName = self._contract.name + "." + enum.name
values = [x.value.id for x in enum.body]
new_enum = EnumContract(name, canonicalName, values)
new_enum.set_contract(self._contract)
new_enum.set_offset(enum.src, self._contract.compilation_unit)
self._contract.enums_as_dict[name] = new_enum # TODO solidity using canonicalName
self._enumsNotParsed = []
def parse_structs(self) -> None:
for struct in self._structuresNotParsed:
st = StructureContract(self._contract.compilation_unit)
st.set_contract(self._contract)
st.set_offset(struct.src, self._contract.compilation_unit)
st_parser = StructVyper(st, struct)
self._contract.structures_as_dict[st.name] = st
self._structures_parser.append(st_parser)
# Interfaces can refer to struct defs
self._contract.file_scope.structures[st.name] = st
self._structuresNotParsed = []
def parse_state_variables(self) -> None:
for varNotParsed in self._variablesNotParsed:
var = StateVariable()
var.set_contract(self._contract)
var.set_offset(varNotParsed.src, self._contract.compilation_unit)
var_parser = StateVariableVyper(var, varNotParsed)
self._variables_parser.append(var_parser)
assert var.name
self._contract.variables_as_dict[var.name] = var
self._contract.add_variables_ordered([var])
# Interfaces can refer to constants
self._contract.file_scope.variables[var.name] = var
self._variablesNotParsed = []
def parse_events(self) -> None:
for event_to_parse in self._eventsNotParsed:
event = Event()
event.set_contract(self._contract)
event.set_offset(event_to_parse.src, self._contract.compilation_unit)
event_parser = EventVyper(event, event_to_parse)
self._events_parser.append(event_parser)
self._contract.events_as_dict[event.full_name] = event
def parse_functions(self) -> None:
for function in self._functionsNotParsed:
func = FunctionContract(self._contract.compilation_unit)
func.set_offset(function.src, self._contract.compilation_unit)
func.set_contract(self._contract)
func.set_contract_declarer(self._contract)
func_parser = FunctionVyper(func, function, self)
self._contract.add_function(func)
self._contract.compilation_unit.add_function(func)
self._functions_parser.append(func_parser)
self._functionsNotParsed = []
def analyze_state_variables(self):
# Struct defs can refer to constant state variables
for var_parser in self._variables_parser:
var_parser.analyze(self._contract)
def analyze(self) -> None:
for struct_parser in self._structures_parser:
struct_parser.analyze(self._contract)
for event_parser in self._events_parser:
event_parser.analyze(self._contract)
for function in self._functions_parser:
function.analyze_params()
for function in self._functions_parser:
function.analyze_content()
def __hash__(self) -> int:
return self._contract.id

@ -0,0 +1,39 @@
"""
Event module
"""
from slither.core.variables.event_variable import EventVariable
from slither.vyper_parsing.variables.event_variable import EventVariableVyper
from slither.core.declarations.event import Event
from slither.vyper_parsing.ast.types import AnnAssign, Pass
from slither.vyper_parsing.ast.types import EventDef
class EventVyper: # pylint: disable=too-few-public-methods
"""
Event class
"""
def __init__(self, event: Event, event_def: EventDef) -> None:
self._event = event
self._event.name = event_def.name
self._elemsNotParsed = event_def.body
def analyze(self, contract) -> None:
for elem_to_parse in self._elemsNotParsed:
if not isinstance(elem_to_parse, AnnAssign):
assert isinstance(elem_to_parse, Pass)
continue
elem = EventVariable()
elem.set_offset(elem_to_parse.src, self._event.contract.compilation_unit)
event_parser = EventVariableVyper(elem, elem_to_parse)
event_parser.analyze(contract)
self._event.elems.append(elem)
self._elemsNotParsed = []

@ -0,0 +1,563 @@
from typing import Dict, Union, List, TYPE_CHECKING
from slither.core.cfg.node import NodeType, link_nodes, Node
from slither.core.cfg.scope import Scope
from slither.core.declarations.function import (
Function,
FunctionType,
)
from slither.core.declarations.function import ModifierStatements
from slither.core.declarations.modifier import Modifier
from slither.core.source_mapping.source_mapping import Source
from slither.core.variables.local_variable import LocalVariable
from slither.vyper_parsing.cfg.node import NodeVyper
from slither.solc_parsing.exceptions import ParsingError
from slither.vyper_parsing.variables.local_variable import LocalVariableVyper
from slither.vyper_parsing.ast.types import (
Int,
Call,
Attribute,
Name,
Tuple as TupleVyper,
ASTNode,
AnnAssign,
FunctionDef,
Return,
Assert,
Compare,
Log,
Subscript,
If,
Pass,
Assign,
AugAssign,
Raise,
Expr,
For,
Index,
Arg,
Arguments,
Continue,
Break,
)
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.vyper_parsing.declarations.contract import ContractVyper
def link_underlying_nodes(node1: NodeVyper, node2: NodeVyper):
link_nodes(node1.underlying_node, node2.underlying_node)
class FunctionVyper: # pylint: disable=too-many-instance-attributes
def __init__(
self,
function: Function,
function_data: FunctionDef,
contract_parser: "ContractVyper",
) -> None:
self._function = function
self._function.name = function_data.name
self._function.id = function_data.node_id
self._functionNotParsed = function_data
self._decoratorNotParsed = None
self._local_variables_parser: List[LocalVariableVyper] = []
self._variables_renamed = []
self._contract_parser = contract_parser
self._node_to_NodeVyper: Dict[Node, NodeVyper] = {}
for decorator in function_data.decorators:
if isinstance(decorator, Call):
# TODO handle multiple
self._decoratorNotParsed = decorator
elif isinstance(decorator, Name):
if decorator.id in ["external", "public", "internal"]:
self._function.visibility = decorator.id
elif decorator.id == "view":
self._function.view = True
elif decorator.id == "pure":
self._function.pure = True
elif decorator.id == "payable":
self._function.payable = True
elif decorator.id == "nonpayable":
self._function.payable = False
else:
raise ValueError(f"Unknown decorator {decorator.id}")
# Interfaces do not have decorators and are external
if self._function._visibility is None:
self._function.visibility = "external"
self._params_was_analyzed = False
self._content_was_analyzed = False
self._counter_scope_local_variables = 0
if function_data.doc_string is not None:
function.has_documentation = True
self._analyze_function_type()
@property
def underlying_function(self) -> Function:
return self._function
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self._function.compilation_unit
###################################################################################
###################################################################################
# region Variables
###################################################################################
###################################################################################
@property
def variables_renamed(
self,
) -> Dict[int, LocalVariableVyper]:
return self._variables_renamed
def _add_local_variable(self, local_var_parser: LocalVariableVyper) -> None:
# Ensure variables name are unique for SSA conversion
# This should not apply to actual Vyper variables currently
# but is necessary if we have nested loops where we've created artificial variables e.g. counter_var
if local_var_parser.underlying_variable.name:
known_variables = [v.name for v in self._function.variables]
while local_var_parser.underlying_variable.name in known_variables:
local_var_parser.underlying_variable.name += (
f"_scope_{self._counter_scope_local_variables}"
)
self._counter_scope_local_variables += 1
known_variables = [v.name for v in self._function.variables]
# TODO no reference ID
# if local_var_parser.reference_id is not None:
# self._variables_renamed[local_var_parser.reference_id] = local_var_parser
self._function.variables_as_dict[
local_var_parser.underlying_variable.name
] = local_var_parser.underlying_variable
self._local_variables_parser.append(local_var_parser)
# endregion
###################################################################################
###################################################################################
# region Analyses
###################################################################################
###################################################################################
@property
def function_not_parsed(self) -> Dict:
return self._functionNotParsed
def _analyze_function_type(self) -> None:
if self._function.name == "__init__":
self._function.function_type = FunctionType.CONSTRUCTOR
elif self._function.name == "__default__":
self._function.function_type = FunctionType.FALLBACK
else:
self._function.function_type = FunctionType.NORMAL
def analyze_params(self) -> None:
if self._params_was_analyzed:
return
self._params_was_analyzed = True
params = self._functionNotParsed.args
returns = self._functionNotParsed.returns
if params:
self._parse_params(params)
if returns:
self._parse_returns(returns)
def analyze_content(self) -> None:
if self._content_was_analyzed:
return
self._content_was_analyzed = True
body = self._functionNotParsed.body
if body and not isinstance(body[0], Pass):
self._function.is_implemented = True
self._function.is_empty = False
self._parse_cfg(body)
else:
self._function.is_implemented = False
self._function.is_empty = True
for local_var_parser in self._local_variables_parser:
local_var_parser.analyze(self._function)
for node_parser in self._node_to_NodeVyper.values():
node_parser.analyze_expressions(self._function)
self._analyze_decorator()
def _analyze_decorator(self) -> None:
if not self._decoratorNotParsed:
return
decorator = self._decoratorNotParsed
if decorator.args:
name = f"{decorator.func.id}({decorator.args[0].value})"
else:
name = decorator.func.id
contract = self._contract_parser.underlying_contract
compilation_unit = self._contract_parser.underlying_contract.compilation_unit
modifier = Modifier(compilation_unit)
modifier.name = name
modifier.set_offset(decorator.src, compilation_unit)
modifier.set_contract(contract)
modifier.set_contract_declarer(contract)
latest_entry_point = self._function.entry_point
self._function.add_modifier(
ModifierStatements(
modifier=modifier,
entry_point=latest_entry_point,
nodes=[latest_entry_point],
)
)
# endregion
###################################################################################
###################################################################################
# region Nodes
###################################################################################
###################################################################################
def _new_node(
self, node_type: NodeType, src: Union[str, Source], scope: Union[Scope, "Function"]
) -> NodeVyper:
node = self._function.new_node(node_type, src, scope)
node_parser = NodeVyper(node)
self._node_to_NodeVyper[node] = node_parser
return node_parser
# endregion
###################################################################################
###################################################################################
# region Parsing function
###################################################################################
###################################################################################
# pylint: disable=too-many-branches,too-many-statements,protected-access,too-many-locals
def _parse_cfg(self, cfg: List[ASTNode]) -> None:
entry_node = self._new_node(NodeType.ENTRYPOINT, "-1:-1:-1", self.underlying_function)
self._function.entry_point = entry_node.underlying_node
scope = Scope(True, False, self.underlying_function)
def parse_statement(
curr_node: NodeVyper,
expr: ASTNode,
continue_destination=None,
break_destination=None,
) -> NodeVyper:
if isinstance(expr, AnnAssign):
local_var = LocalVariable()
local_var.set_function(self._function)
local_var.set_offset(expr.src, self._function.compilation_unit)
local_var_parser = LocalVariableVyper(local_var, expr)
self._add_local_variable(local_var_parser)
new_node = self._new_node(NodeType.VARIABLE, expr.src, scope)
if expr.value is not None:
local_var.initialized = True
new_node.add_unparsed_expression(expr.value)
new_node.underlying_node.add_variable_declaration(local_var)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
elif isinstance(expr, (AugAssign, Assign)):
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope)
new_node.add_unparsed_expression(expr)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
elif isinstance(expr, Expr):
# TODO This is a workaround to handle Vyper putting payable/view in the function body... https://github.com/vyperlang/vyper/issues/3578
if not isinstance(expr.value, Name):
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope)
new_node.add_unparsed_expression(expr.value)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
elif isinstance(expr, For):
node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope)
node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope)
link_underlying_nodes(curr_node, node_startLoop)
local_var = LocalVariable()
local_var.set_function(self._function)
local_var.set_offset(expr.src, self._function.compilation_unit)
counter_var = AnnAssign(
expr.target.src,
expr.target.node_id,
target=Name("-1:-1:-1", -1, "counter_var"),
annotation=Name("-1:-1:-1", -1, "uint256"),
value=Int("-1:-1:-1", -1, 0),
)
local_var_parser = LocalVariableVyper(local_var, counter_var)
self._add_local_variable(local_var_parser)
counter_node = self._new_node(NodeType.VARIABLE, expr.src, scope)
local_var.initialized = True
counter_node.add_unparsed_expression(counter_var.value)
counter_node.underlying_node.add_variable_declaration(local_var)
link_underlying_nodes(node_startLoop, counter_node)
node_condition = None
if isinstance(expr.iter, (Attribute, Name)):
# HACK
# The loop variable is not annotated so we infer its type by looking at the type of the iterator
if isinstance(expr.iter, Attribute): # state variable
iter_expr = expr.iter
loop_iterator = list(
filter(
lambda x: x._variable.name == iter_expr.attr,
self._contract_parser._variables_parser,
)
)[0]
else: # local variable
iter_expr = expr.iter
loop_iterator = list(
filter(
lambda x: x._variable.name == iter_expr.id,
self._local_variables_parser,
)
)[0]
# TODO use expr.src instead of -1:-1:1?
cond_expr = Compare(
"-1:-1:-1",
-1,
left=Name("-1:-1:-1", -1, "counter_var"),
op="<=",
right=Call(
"-1:-1:-1",
-1,
func=Name("-1:-1:-1", -1, "len"),
args=[iter_expr],
keywords=[],
keyword=None,
),
)
node_condition = self._new_node(NodeType.IFLOOP, expr.src, scope)
node_condition.add_unparsed_expression(cond_expr)
if loop_iterator._elem_to_parse.value.id == "DynArray":
loop_var_annotation = loop_iterator._elem_to_parse.slice.value.elements[0]
else:
loop_var_annotation = loop_iterator._elem_to_parse.value
value = Subscript(
"-1:-1:-1",
-1,
value=Name("-1:-1:-1", -1, loop_iterator._variable.name),
slice=Index("-1:-1:-1", -1, value=Name("-1:-1:-1", -1, "counter_var")),
)
loop_var = AnnAssign(
expr.target.src,
expr.target.node_id,
target=expr.target,
annotation=loop_var_annotation,
value=value,
)
elif isinstance(expr.iter, Call): # range
range_val = expr.iter.args[0]
cond_expr = Compare(
"-1:-1:-1",
-1,
left=Name("-1:-1:-1", -1, "counter_var"),
op="<=",
right=range_val,
)
node_condition = self._new_node(NodeType.IFLOOP, expr.src, scope)
node_condition.add_unparsed_expression(cond_expr)
loop_var = AnnAssign(
expr.target.src,
expr.target.node_id,
target=expr.target,
annotation=Name("-1:-1:-1", -1, "uint256"),
value=Name("-1:-1:-1", -1, "counter_var"),
)
else:
raise NotImplementedError
# After creating condition node, we link it declaration of the loop variable
link_underlying_nodes(counter_node, node_condition)
# Create an expression for the loop increment (counter_var += 1)
loop_increment = AugAssign(
"-1:-1:-1",
-1,
target=Name("-1:-1:-1", -1, "counter_var"),
op="+=",
value=Int("-1:-1:-1", -1, 1),
)
node_increment = self._new_node(NodeType.EXPRESSION, expr.src, scope)
node_increment.add_unparsed_expression(loop_increment)
link_underlying_nodes(node_increment, node_condition)
continue_destination = node_increment
break_destination = node_endLoop
# We assign the index variable or range variable in the loop body on each iteration
expr.body.insert(0, loop_var)
body_node = None
new_node = node_condition
for stmt in expr.body:
body_node = parse_statement(
new_node, stmt, continue_destination, break_destination
)
new_node = body_node
if body_node is not None:
link_underlying_nodes(body_node, node_increment)
link_underlying_nodes(node_condition, node_endLoop)
curr_node = node_endLoop
elif isinstance(expr, Continue):
new_node = self._new_node(NodeType.CONTINUE, expr.src, scope)
link_underlying_nodes(curr_node, new_node)
link_underlying_nodes(new_node, continue_destination)
elif isinstance(expr, Break):
new_node = self._new_node(NodeType.BREAK, expr.src, scope)
link_underlying_nodes(curr_node, new_node)
link_underlying_nodes(new_node, break_destination)
elif isinstance(expr, Return):
new_node = self._new_node(NodeType.RETURN, expr.src, scope)
if expr.value is not None:
new_node.add_unparsed_expression(expr.value)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
elif isinstance(expr, Assert):
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope)
new_node.add_unparsed_expression(expr)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
elif isinstance(expr, Log):
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope)
new_node.add_unparsed_expression(expr.value)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
elif isinstance(expr, If):
condition_node = self._new_node(NodeType.IF, expr.test.src, scope)
condition_node.add_unparsed_expression(expr.test)
endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope)
true_node = None
new_node = condition_node
for stmt in expr.body:
true_node = parse_statement(
new_node, stmt, continue_destination, break_destination
)
new_node = true_node
link_underlying_nodes(true_node, endIf_node)
false_node = None
new_node = condition_node
for stmt in expr.orelse:
false_node = parse_statement(
new_node, stmt, continue_destination, break_destination
)
new_node = false_node
if false_node is not None:
link_underlying_nodes(false_node, endIf_node)
else:
link_underlying_nodes(condition_node, endIf_node)
link_underlying_nodes(curr_node, condition_node)
curr_node = endIf_node
elif isinstance(expr, Pass):
pass
elif isinstance(expr, Raise):
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope)
new_node.add_unparsed_expression(expr)
link_underlying_nodes(curr_node, new_node)
curr_node = new_node
else:
raise ParsingError(f"Statement not parsed {expr.__class__.__name__} {expr}")
return curr_node
curr_node = entry_node
for expr in cfg:
curr_node = parse_statement(curr_node, expr)
# endregion
###################################################################################
###################################################################################
def _add_param(self, param: Arg, initialized: bool = False) -> LocalVariableVyper:
local_var = LocalVariable()
local_var.set_function(self._function)
local_var.set_offset(param.src, self._function.compilation_unit)
local_var_parser = LocalVariableVyper(local_var, param)
if initialized:
local_var.initialized = True
if local_var.location == "default":
local_var.set_location("memory")
self._add_local_variable(local_var_parser)
return local_var_parser
def _parse_params(self, params: Arguments):
self._function.parameters_src().set_offset(params.src, self._function.compilation_unit)
if params.defaults:
self._function._default_args_as_expressions = params.defaults
for param in params.args:
local_var = self._add_param(param)
self._function.add_parameters(local_var.underlying_variable)
def _parse_returns(self, returns: Union[Name, TupleVyper, Subscript]):
self._function.returns_src().set_offset(returns.src, self._function.compilation_unit)
# Only the type of the arg is given, not a name. We create an an `Arg` with an empty name
# so that the function has the correct return type in its signature but doesn't clash with
# other identifiers during name resolution (`find_variable`).
if isinstance(returns, (Name, Subscript)):
local_var = self._add_param(Arg(returns.src, returns.node_id, "", annotation=returns))
self._function.add_return(local_var.underlying_variable)
else:
assert isinstance(returns, TupleVyper)
for ret in returns.elements:
local_var = self._add_param(Arg(ret.src, ret.node_id, "", annotation=ret))
self._function.add_return(local_var.underlying_variable)
###################################################################################
###################################################################################

@ -0,0 +1,33 @@
from typing import List
from slither.core.declarations.structure import Structure
from slither.core.variables.structure_variable import StructureVariable
from slither.vyper_parsing.variables.structure_variable import StructureVariableVyper
from slither.vyper_parsing.ast.types import StructDef, AnnAssign
class StructVyper: # pylint: disable=too-few-public-methods
def __init__(
self,
st: Structure,
struct: StructDef,
) -> None:
self._structure = st
st.name = struct.name
st.canonical_name = struct.name + self._structure.contract.name
self._elemsNotParsed: List[AnnAssign] = struct.body
def analyze(self, contract) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = StructureVariable()
elem.set_structure(self._structure)
elem.set_offset(elem_to_parse.src, self._structure.contract.compilation_unit)
elem_parser = StructureVariableVyper(elem, elem_to_parse)
elem_parser.analyze(contract)
self._structure.elems[elem.name] = elem
self._structure.add_elem_in_order(elem.name)
self._elemsNotParsed = []

@ -0,0 +1,464 @@
from typing import Optional, List, Union, TYPE_CHECKING
from collections import deque
from slither.core.declarations.solidity_variables import (
SOLIDITY_VARIABLES_COMPOSED,
SolidityVariableComposed,
)
from slither.core.declarations import SolidityFunction, FunctionContract
from slither.core.variables.state_variable import StateVariable
from slither.core.expressions import (
CallExpression,
ElementaryTypeNameExpression,
Identifier,
IndexAccess,
Literal,
MemberAccess,
SelfIdentifier,
TupleExpression,
TypeConversion,
UnaryOperation,
UnaryOperationType,
)
from slither.core.expressions.assignment_operation import (
AssignmentOperation,
AssignmentOperationType,
)
from slither.core.expressions.binary_operation import (
BinaryOperation,
BinaryOperationType,
)
from slither.core.solidity_types import (
ArrayType,
ElementaryType,
UserDefinedType,
)
from slither.core.declarations.contract import Contract
from slither.vyper_parsing.expressions.find_variable import find_variable
from slither.vyper_parsing.type_parsing import parse_type
from slither.all_exceptions import ParsingError
from slither.vyper_parsing.ast.types import (
Int,
Call,
Attribute,
Name,
Tuple,
Hex,
BinOp,
Str,
Assert,
Compare,
UnaryOp,
Subscript,
NameConstant,
VyDict,
Bytes,
BoolOp,
Assign,
AugAssign,
VyList,
Raise,
ASTNode,
)
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
def vars_to_typestr(rets: Optional[List["Expression"]]) -> str:
if rets is None:
return "tuple()"
if len(rets) == 1:
return str(rets[0].type)
return f"tuple({','.join(str(ret.type) for ret in rets)})"
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
def parse_expression(
expression: ASTNode, caller_context: Union[FunctionContract, Contract]
) -> "Expression":
if isinstance(expression, Int):
literal = Literal(str(expression.value), ElementaryType("uint256"))
literal.set_offset(expression.src, caller_context.compilation_unit)
return literal
if isinstance(expression, Hex):
# TODO this is an implicit conversion and could potentially be bytes20 or other? https://github.com/vyperlang/vyper/issues/3580
literal = Literal(str(expression.value), ElementaryType("address"))
literal.set_offset(expression.src, caller_context.compilation_unit)
return literal
if isinstance(expression, Str):
literal = Literal(str(expression.value), ElementaryType("string"))
literal.set_offset(expression.src, caller_context.compilation_unit)
return literal
if isinstance(expression, Bytes):
literal = Literal(str(expression.value), ElementaryType("bytes"))
literal.set_offset(expression.src, caller_context.compilation_unit)
return literal
if isinstance(expression, NameConstant):
assert str(expression.value) in ["True", "False"]
literal = Literal(str(expression.value), ElementaryType("bool"))
literal.set_offset(expression.src, caller_context.compilation_unit)
return literal
if isinstance(expression, Call):
called = parse_expression(expression.func, caller_context)
if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction):
if called.value.name == "empty()":
type_to = parse_type(expression.args[0], caller_context)
# TODO figure out how to represent this type argument
parsed_expr = CallExpression(called, [], str(type_to))
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if called.value.name == "convert()":
arg = parse_expression(expression.args[0], caller_context)
type_to = parse_type(expression.args[1], caller_context)
parsed_expr = TypeConversion(arg, type_to)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if called.value.name == "min_value()":
type_to = parse_type(expression.args[0], caller_context)
member_type = str(type_to)
# TODO return Literal
parsed_expr = MemberAccess(
"min",
member_type,
CallExpression(
Identifier(SolidityFunction("type()")),
[ElementaryTypeNameExpression(type_to)],
member_type,
),
)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if called.value.name == "max_value()":
type_to = parse_type(expression.args[0], caller_context)
member_type = str(type_to)
# TODO return Literal
parsed_expr = MemberAccess(
"max",
member_type,
CallExpression(
Identifier(SolidityFunction("type()")),
[ElementaryTypeNameExpression(type_to)],
member_type,
),
)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if called.value.name == "raw_call()":
args = [parse_expression(a, caller_context) for a in expression.args]
# This is treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
call = CallExpression(
MemberAccess("raw_call", "tuple(bool,bytes32)", args[0]),
args[1:],
"tuple(bool,bytes32)",
)
call.set_offset(expression.src, caller_context.compilation_unit)
call.call_value = next(
iter(
parse_expression(x.value, caller_context)
for x in expression.keywords
if x.arg == "value"
),
None,
)
call.call_gas = next(
iter(
parse_expression(x.value, caller_context)
for x in expression.keywords
if x.arg == "gas"
),
None,
)
# TODO handle `max_outsize` keyword
return call
if expression.args and isinstance(expression.args[0], VyDict):
arguments = []
for val in expression.args[0].values:
arguments.append(parse_expression(val, caller_context))
else:
arguments = [parse_expression(a, caller_context) for a in expression.args]
rets = None
# Since the AST lacks the type of the return values, we recover it. https://github.com/vyperlang/vyper/issues/3581
if isinstance(called, Identifier):
if isinstance(called.value, FunctionContract):
rets = called.value.returns
# Default arguments are not represented in the AST, so we recover them as well.
# pylint: disable=protected-access
if called.value._default_args_as_expressions and len(arguments) < len(
called.value.parameters
):
arguments.extend(
[
parse_expression(x, caller_context)
for x in called.value._default_args_as_expressions
]
)
elif isinstance(called.value, SolidityFunction):
rets = called.value.return_type
elif isinstance(called.value, Contract):
# Type conversions are not explicitly represented in the AST e.g. converting address to contract/ interface,
# so we infer that a type conversion is occurring if `called` is a `Contract` type. https://github.com/vyperlang/vyper/issues/3580
type_to = parse_type(expression.func, caller_context)
parsed_expr = TypeConversion(arguments[0], type_to)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
elif isinstance(called, MemberAccess) and called.type is not None:
# (recover_type_2) Propagate the type collected to the `CallExpression`
# see recover_type_1
rets = [called]
type_str = vars_to_typestr(rets)
parsed_expr = CallExpression(called, arguments, type_str)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, Attribute):
member_name = expression.attr
if isinstance(expression.value, Name):
# TODO this is ambiguous because it could be a state variable or a call to balance https://github.com/vyperlang/vyper/issues/3582
if expression.value.id == "self" and member_name != "balance":
var = find_variable(member_name, caller_context, is_self=True)
parsed_expr = SelfIdentifier(var)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
var.references.append(parsed_expr.source_mapping)
return parsed_expr
expr = parse_expression(expression.value, caller_context)
# TODO this is ambiguous because it could be a type conversion of an interface or a member access
# see https://github.com/vyperlang/vyper/issues/3580 and ttps://github.com/vyperlang/vyper/issues/3582
if expression.attr == "address":
parsed_expr = TypeConversion(expr, ElementaryType("address"))
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
member_access = MemberAccess(member_name, None, expr)
if str(member_access) in SOLIDITY_VARIABLES_COMPOSED:
parsed_expr = Identifier(SolidityVariableComposed(str(member_access)))
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
else:
expr = parse_expression(expression.value, caller_context)
member_name_ret_type = None
# (recover_type_1) This may be a call to an interface and we don't have the return types,
# so we see if there's a function identifier with `member_name` and propagate the type to
# its enclosing `CallExpression`. https://github.com/vyperlang/vyper/issues/3581
if (
isinstance(expr, Identifier)
and isinstance(expr.value, StateVariable)
and isinstance(expr.value.type, UserDefinedType)
and isinstance(expr.value.type.type, Contract)
):
# If we access a member of an interface, needs to be interface instead of self namespace
var = find_variable(member_name, expr.value.type.type)
if isinstance(var, FunctionContract):
rets = var.returns
member_name_ret_type = vars_to_typestr(rets)
if (
isinstance(expr, TypeConversion)
and isinstance(expr.type, UserDefinedType)
and isinstance(expr.type.type, Contract)
):
# If we access a member of an interface, needs to be interface instead of self namespace
var = find_variable(member_name, expr.type.type)
if isinstance(var, FunctionContract):
rets = var.returns
member_name_ret_type = vars_to_typestr(rets)
member_access = MemberAccess(member_name, member_name_ret_type, expr)
member_access.set_offset(expression.src, caller_context.compilation_unit)
return member_access
if isinstance(expression, Name):
var = find_variable(expression.id, caller_context)
parsed_expr = Identifier(var)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, Assign):
lhs = parse_expression(expression.target, caller_context)
rhs = parse_expression(expression.value, caller_context)
parsed_expr = AssignmentOperation(lhs, rhs, AssignmentOperationType.ASSIGN, None)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, AugAssign):
lhs = parse_expression(expression.target, caller_context)
rhs = parse_expression(expression.value, caller_context)
op = AssignmentOperationType.get_type(expression.op)
parsed_expr = AssignmentOperation(lhs, rhs, op, None)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, (Tuple, VyList)):
tuple_vars = [parse_expression(x, caller_context) for x in expression.elements]
parsed_expr = TupleExpression(tuple_vars)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, UnaryOp):
operand = parse_expression(expression.operand, caller_context)
op = UnaryOperationType.get_type(
expression.op, isprefix=True
) # TODO does vyper have postfix?
parsed_expr = UnaryOperation(operand, op)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, Compare):
lhs = parse_expression(expression.left, caller_context)
# We assume left operand in membership comparison cannot be Array type
if expression.op in ["In", "NotIn"]:
# If we see a membership operator e.g. x in [foo(), bar()], we convert it to logical operations
# like (x == foo() || x == bar()) or (x != foo() && x != bar()) for "not in"
# TODO consider rewriting as if-else to accurately represent the precedence of potential side-effects
conditions = deque()
rhs = parse_expression(expression.right, caller_context)
is_tuple = isinstance(rhs, TupleExpression)
is_array = isinstance(rhs, Identifier) and isinstance(rhs.value.type, ArrayType)
if is_array:
assert (
rhs.value.type.is_fixed_array
), "Dynamic arrays are not supported in comparison operators"
if is_tuple or is_array:
length = len(rhs.expressions) if is_tuple else rhs.value.type.length_value.value
inner_op = (
BinaryOperationType.get_type("!=")
if expression.op == "NotIn"
else BinaryOperationType.get_type("==")
)
for i in range(length):
elem_expr = (
rhs.expressions[i]
if is_tuple
else IndexAccess(rhs, Literal(str(i), ElementaryType("uint256")))
)
elem_expr.set_offset(rhs.source_mapping, caller_context.compilation_unit)
parsed_expr = BinaryOperation(lhs, elem_expr, inner_op)
parsed_expr.set_offset(lhs.source_mapping, caller_context.compilation_unit)
conditions.append(parsed_expr)
outer_op = (
BinaryOperationType.get_type("&&")
if expression.op == "NotIn"
else BinaryOperationType.get_type("||")
)
while len(conditions) > 1:
lhs = conditions.pop()
rhs = conditions.pop()
conditions.appendleft(BinaryOperation(lhs, rhs, outer_op))
return conditions.pop()
# enum type membership check https://docs.vyperlang.org/en/stable/types.html?h#id18
is_member_op = (
BinaryOperationType.get_type("==")
if expression.op == "NotIn"
else BinaryOperationType.get_type("!=")
)
# If all bits are cleared, then the lhs is not a member of the enum
# This allows representing membership in multiple enum members
# For example, if enum Foo has members A (1), B (2), and C (4), then
# (x in [Foo.A, Foo.B]) is equivalent to (x & (Foo.A | Foo.B) != 0),
# where (Foo.A | Foo.B) evaluates to 3.
# Thus, when x is 3, (x & (Foo.A | Foo.B) != 0) is true.
enum_bit_mask = BinaryOperation(
TypeConversion(lhs, ElementaryType("uint256")),
TypeConversion(rhs, ElementaryType("uint256")),
BinaryOperationType.get_type("&"),
)
membership_check = BinaryOperation(
enum_bit_mask, Literal("0", ElementaryType("uint256")), is_member_op
)
membership_check.set_offset(lhs.source_mapping, caller_context.compilation_unit)
return membership_check
# a regular logical operator
rhs = parse_expression(expression.right, caller_context)
op = BinaryOperationType.get_type(expression.op)
parsed_expr = BinaryOperation(lhs, rhs, op)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, BinOp):
lhs = parse_expression(expression.left, caller_context)
rhs = parse_expression(expression.right, caller_context)
op = BinaryOperationType.get_type(expression.op)
parsed_expr = BinaryOperation(lhs, rhs, op)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, Assert):
# Treat assert the same as a Solidity `require`.
# TODO rename from `SolidityFunction` to `Builtin`?
type_str = "tuple()"
if expression.msg is None:
func = SolidityFunction("require(bool)")
args = [parse_expression(expression.test, caller_context)]
else:
func = SolidityFunction("require(bool,string)")
args = [
parse_expression(expression.test, caller_context),
parse_expression(expression.msg, caller_context),
]
parsed_expr = CallExpression(Identifier(func), args, type_str)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, Subscript):
left_expression = parse_expression(expression.value, caller_context)
right_expression = parse_expression(expression.slice.value, caller_context)
parsed_expr = IndexAccess(left_expression, right_expression)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, BoolOp):
lhs = parse_expression(expression.values[0], caller_context)
rhs = parse_expression(expression.values[1], caller_context)
op = BinaryOperationType.get_type(expression.op)
parsed_expr = BinaryOperation(lhs, rhs, op)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
if isinstance(expression, Raise):
type_str = "tuple()"
func = (
SolidityFunction("revert()")
if expression.exc is None
else SolidityFunction("revert(string)")
)
args = [] if expression.exc is None else [parse_expression(expression.exc, caller_context)]
parsed_expr = CallExpression(Identifier(func), args, type_str)
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
raise ParsingError(f"Expression not parsed {expression}")

@ -0,0 +1,150 @@
from typing import TYPE_CHECKING, Optional, Union, Tuple
from slither.core.declarations import Event, Enum, Structure
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.solidity_variables import (
SOLIDITY_FUNCTIONS,
SOLIDITY_VARIABLES,
SolidityFunction,
SolidityVariable,
)
from slither.core.variables.variable import Variable
from slither.solc_parsing.exceptions import VariableNotFound
if TYPE_CHECKING:
from slither.vyper_parsing.declarations.function import FunctionVyper
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionVyper"],
) -> Optional[Variable]:
if function_parser is None:
return None
func_variables = function_parser.variables_as_dict
if var_name in func_variables:
return func_variables[var_name]
return None
def _find_in_contract(
var_name: str,
contract: Optional[Contract],
contract_declarer: Optional[Contract],
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure, CustomError]]:
if contract is None or contract_declarer is None:
return None
# variable are looked from the contract declarer
contract_variables = contract.variables_as_dict
if var_name in contract_variables:
return contract_variables[var_name]
functions = {f.name: f for f in contract.functions if not f.is_shadowed}
if var_name in functions:
return functions[var_name]
# structures are looked on the contract declarer
structures = contract.structures_as_dict
if var_name in structures:
return structures[var_name]
events = contract.events_as_dict
if var_name in events:
return events[var_name]
enums = contract.enums_as_dict
if var_name in enums:
return enums[var_name]
# If the enum is referred as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums}
if var_name in enums:
return enums[var_name]
return None
def find_variable(
var_name: str,
caller_context: Union[FunctionContract, Contract],
is_self: bool = False,
) -> Tuple[
Union[
Variable,
Function,
Contract,
SolidityVariable,
SolidityFunction,
Event,
Enum,
Structure,
]
]:
"""
Return the variable found and a boolean indicating if the variable was created
If the variable was created, it has no source mapping, and it the caller must add it
:param var_name:
:type var_name:
:param caller_context:
:type caller_context:
:param is_self:
:type is_self:
:return:
:rtype:
"""
# pylint: disable=import-outside-toplevel
from slither.vyper_parsing.declarations.function import (
FunctionVyper,
)
if isinstance(caller_context, Contract):
current_scope = caller_context.file_scope
next_context = caller_context
else:
current_scope = caller_context.contract.file_scope
next_context = caller_context.contract
function_parser: Optional[FunctionVyper] = (
caller_context if isinstance(caller_context, FunctionContract) else None
)
# If a local shadows a state variable but the attribute is `self`, we want to
# return the state variable and not the local.
if not is_self:
ret1 = _find_variable_in_function_parser(var_name, function_parser)
if ret1:
return ret1
ret = _find_in_contract(var_name, next_context, caller_context)
if ret:
return ret
if var_name in current_scope.variables:
return current_scope.variables[var_name]
# Could refer to any enum
all_enumss = [c.enums_as_dict for c in current_scope.contracts.values()]
all_enums = {k: v for d in all_enumss for k, v in d.items()}
if var_name in all_enums:
return all_enums[var_name]
contracts = current_scope.contracts
if var_name in contracts:
return contracts[var_name]
if var_name in SOLIDITY_VARIABLES:
return SolidityVariable(var_name)
if f"{var_name}()" in SOLIDITY_FUNCTIONS:
return SolidityFunction(f"{var_name}()")
if f"{var_name}()" in next_context.events_as_dict:
return next_context.events_as_dict[f"{var_name}()"]
raise VariableNotFound(f"Variable not found: {var_name} (context {caller_context})")

@ -0,0 +1,99 @@
from typing import Union
from slither.core.solidity_types.elementary_type import (
ElementaryType,
ElementaryTypeName,
) # TODO rename solidity type
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.declarations import FunctionContract, Contract
from slither.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple
from slither.solc_parsing.exceptions import ParsingError
# pylint: disable=too-many-branches,too-many-return-statements,import-outside-toplevel,too-many-locals
def parse_type(
annotation: Union[Name, Subscript, Call, Tuple],
caller_context: Union[FunctionContract, Contract],
):
from slither.vyper_parsing.expressions.expression_parsing import parse_expression
if isinstance(caller_context, FunctionContract):
contract = caller_context.contract
else:
contract = caller_context
assert isinstance(annotation, (Name, Subscript, Call, Tuple))
if isinstance(annotation, Name):
name = annotation.id
lname = name.lower() # map `String` to string
if lname in ElementaryTypeName:
return ElementaryType(lname)
if name in contract.structures_as_dict:
return UserDefinedType(contract.structures_as_dict[name])
if name in contract.enums_as_dict:
return UserDefinedType(contract.enums_as_dict[name])
if name in contract.file_scope.contracts:
return UserDefinedType(contract.file_scope.contracts[name])
if name in contract.file_scope.structures:
return UserDefinedType(contract.file_scope.structures[name])
elif isinstance(annotation, Subscript):
assert isinstance(annotation.slice, Index)
# This is also a strange construct... https://github.com/vyperlang/vyper/issues/3577
if isinstance(annotation.slice.value, Tuple):
assert isinstance(annotation.value, Name)
if annotation.value.id == "DynArray":
type_ = parse_type(annotation.slice.value.elements[0], caller_context)
length = parse_expression(annotation.slice.value.elements[1], caller_context)
return ArrayType(type_, length)
if annotation.value.id == "HashMap":
type_from = parse_type(annotation.slice.value.elements[0], caller_context)
type_to = parse_type(annotation.slice.value.elements[1], caller_context)
return MappingType(type_from, type_to)
elif isinstance(annotation.value, Subscript):
type_ = parse_type(annotation.value, caller_context)
elif isinstance(annotation.value, Name):
# TODO it is weird that the ast_type is `Index` when it's a type annotation and not an expression, so we grab the value. https://github.com/vyperlang/vyper/issues/3577
type_ = parse_type(annotation.value, caller_context)
if annotation.value.id == "String":
# This is an elementary type
return type_
length = parse_expression(annotation.slice.value, caller_context)
return ArrayType(type_, length)
elif isinstance(annotation, Call):
# TODO event variable represented as Call https://github.com/vyperlang/vyper/issues/3579
return parse_type(annotation.args[0], caller_context)
elif isinstance(annotation, Tuple):
# Vyper has tuple types like python x = f() where f() -> (y,z)
# and tuple elements can be unpacked like x[0]: y and x[1]: z.
# We model these as a struct and unpack each index into a field
# e.g. accessing the 0th element is translated as x._0
from slither.core.declarations.structure import Structure
from slither.core.variables.structure_variable import StructureVariable
st = Structure(caller_context.compilation_unit)
st.set_offset("-1:-1:-1", caller_context.compilation_unit)
st.name = "FAKE_TUPLE"
for idx, elem_info in enumerate(annotation.elements):
elem = StructureVariable()
elem.type = parse_type(elem_info, caller_context)
elem.name = f"_{idx}"
elem.set_structure(st)
elem.set_offset("-1:-1:-1", caller_context.compilation_unit)
st.elems[elem.name] = elem
st.add_elem_in_order(elem.name)
st.name += elem.name
return UserDefinedType(st)
raise ParsingError(f"Type name not found {name} context {caller_context}")

@ -0,0 +1,24 @@
from slither.core.variables.event_variable import EventVariable
from slither.vyper_parsing.type_parsing import parse_type
from slither.vyper_parsing.ast.types import AnnAssign, Call
class EventVariableVyper:
def __init__(self, variable: EventVariable, variable_data: AnnAssign):
self._variable = variable
self._variable.name = variable_data.target.id
if (
isinstance(variable_data.annotation, Call)
and variable_data.annotation.func.id == "indexed"
):
self._variable.indexed = True
else:
self._variable.indexed = False
self._elem_to_parse = variable_data.annotation
@property
def underlying_variable(self) -> EventVariable:
return self._variable
def analyze(self, contract) -> None:
self._variable.type = parse_type(self._elem_to_parse, contract)

@ -0,0 +1,34 @@
from typing import Union
from slither.core.variables.local_variable import LocalVariable
from slither.vyper_parsing.ast.types import Arg, Name, AnnAssign, Subscript, Call, Tuple
from slither.vyper_parsing.type_parsing import parse_type
class LocalVariableVyper:
def __init__(self, variable: LocalVariable, variable_data: Union[Arg, AnnAssign, Name]) -> None:
self._variable: LocalVariable = variable
if isinstance(variable_data, Arg):
self._variable.name = variable_data.arg
self._elem_to_parse = variable_data.annotation
elif isinstance(variable_data, AnnAssign):
self._variable.name = variable_data.target.id
self._elem_to_parse = variable_data.annotation
else:
assert isinstance(variable_data, Name)
self._variable.name = variable_data.id
self._elem_to_parse = variable_data
assert isinstance(self._elem_to_parse, (Name, Subscript, Call, Tuple))
# Vyper does not have data locations or storage pointers.
# If this was left as default, reference types would be considered storage by `LocalVariable.is_storage`
self._variable.set_location("memory")
@property
def underlying_variable(self) -> LocalVariable:
return self._variable
def analyze(self, contract) -> None:
self._variable.type = parse_type(self._elem_to_parse, contract)

@ -0,0 +1,29 @@
from slither.core.variables.state_variable import StateVariable
from slither.vyper_parsing.ast.types import VariableDecl
from slither.vyper_parsing.type_parsing import parse_type
from slither.vyper_parsing.expressions.expression_parsing import parse_expression
class StateVariableVyper:
def __init__(self, variable: StateVariable, variable_data: VariableDecl) -> None:
self._variable: StateVariable = variable
self._variable.name = variable_data.target.id
self._variable.is_constant = variable_data.is_constant
self._variable.is_immutable = variable_data.is_immutable
self._variable.visibility = "public" if variable_data.is_public else "internal"
self._elem_to_parse = variable_data.annotation
if variable_data.value is not None:
self._variable.initialized = True
self._initializedNotParsed = variable_data.value
@property
def underlying_variable(self) -> StateVariable:
return self._variable
def analyze(self, contract) -> None:
self._variable.type = parse_type(self._elem_to_parse, contract)
if self._variable.initialized:
self._variable.expression = parse_expression(self._initializedNotParsed, contract)
self._initializedNotParsed = None

@ -0,0 +1,17 @@
from slither.core.variables.structure_variable import StructureVariable
from slither.vyper_parsing.type_parsing import parse_type
from slither.vyper_parsing.ast.types import AnnAssign
class StructureVariableVyper:
def __init__(self, variable: StructureVariable, variable_data: AnnAssign):
self._variable: StructureVariable = variable
self._variable.name = variable_data.target.id
self._elem_to_parse = variable_data.annotation
@property
def underlying_variable(self) -> StructureVariable:
return self._variable
def analyze(self, contract) -> None:
self._variable.type = parse_type(self._elem_to_parse, contract)

@ -0,0 +1,80 @@
from typing import Dict
import os
import re
from dataclasses import dataclass, field
from slither.core.declarations import Contract
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.vyper_parsing.declarations.contract import ContractVyper
from slither.analyses.data_dependency.data_dependency import compute_dependency
from slither.vyper_parsing.ast.types import Module
from slither.exceptions import SlitherException
@dataclass
class VyperCompilationUnit:
_compilation_unit: SlitherCompilationUnit
_parsed: bool = False
_analyzed: bool = False
_underlying_contract_to_parser: Dict[Contract, ContractVyper] = field(default_factory=dict)
_contracts_by_id: Dict[int, Contract] = field(default_factory=dict)
def parse_module(self, data: Module, filename: str):
sourceUnit_candidates = re.findall("[0-9]*:[0-9]*:([0-9]*)", data.src)
assert len(sourceUnit_candidates) == 1, "Source unit not found"
sourceUnit = int(sourceUnit_candidates[0])
self._compilation_unit.source_units[sourceUnit] = filename
if os.path.isfile(filename) and filename not in self._compilation_unit.core.source_code:
self._compilation_unit.core.add_source_code(filename)
scope = self._compilation_unit.get_scope(filename)
contract = Contract(self._compilation_unit, scope)
contract_parser = ContractVyper(self, contract, data)
contract.set_offset(data.src, self._compilation_unit)
self._underlying_contract_to_parser[contract] = contract_parser
def parse_contracts(self):
for contract, contract_parser in self._underlying_contract_to_parser.items():
self._contracts_by_id[contract.id] = contract
self._compilation_unit.contracts.append(contract)
contract_parser.parse_enums()
contract_parser.parse_structs()
contract_parser.parse_state_variables()
contract_parser.parse_events()
contract_parser.parse_functions()
self._parsed = True
def analyze_contracts(self) -> None:
if not self._parsed:
raise SlitherException("Parse the contract before running analyses")
for contract_parser in self._underlying_contract_to_parser.values():
# State variables are analyzed for all contracts because interfaces may
# reference them, specifically, constants.
contract_parser.analyze_state_variables()
for contract_parser in self._underlying_contract_to_parser.values():
contract_parser.analyze()
self._convert_to_slithir()
compute_dependency(self._compilation_unit)
self._analyzed = True
def _convert_to_slithir(self) -> None:
for contract in self._compilation_unit.contracts:
contract.add_constructor_variables()
for func in contract.functions:
func.generate_slithir_and_analyze()
contract.convert_expression_to_slithir_ssa()
self._compilation_unit.propagate_function_calls()
for contract in self._compilation_unit.contracts:
contract.fix_phi()
contract.update_read_write_using_ssa()

@ -59,7 +59,7 @@ def solc_binary_path(shared_directory):
@pytest.fixture
def slither_from_source(solc_binary_path):
def slither_from_solidity_source(solc_binary_path):
@contextmanager
def inner(source_code: str, solc_version: str = "0.8.19"):
"""Yields a Slither instance using source_code string and solc_version.
@ -77,3 +77,23 @@ def slither_from_source(solc_binary_path):
Path(fname).unlink()
return inner
@pytest.fixture
def slither_from_vyper_source():
@contextmanager
def inner(source_code: str):
"""Yields a Slither instance using source_code string.
Creates a temporary file and compiles with vyper.
"""
fname = ""
try:
with tempfile.NamedTemporaryFile(mode="w", suffix=".vy", delete=False) as f:
fname = f.name
f.write(source_code)
yield Slither(fname)
finally:
Path(fname).unlink()
return inner

@ -0,0 +1,5 @@
contract TestSlither {
function testFunction(uint256 param1, uint256, address param3) public {
}
}

@ -43,3 +43,20 @@ def test_cycle(solc_binary_path) -> None:
solc_path = solc_binary_path("0.8.0")
slither = Slither(Path(TEST_DATA_DIR, "test_cyclic_import", "a.sol").as_posix(), solc=solc_path)
_run_all_detectors(slither)
def test_contract_function_parameter(solc_binary_path) -> None:
solc_path = solc_binary_path("0.8.0")
standard_json = SolcStandardJson()
standard_json.add_source_file(
Path(TEST_DATA_DIR, "test_contract_data", "test_contract_data.sol").as_posix()
)
compilation = CryticCompile(standard_json, solc=solc_path)
slither = Slither(compilation)
contract = slither.contracts[0]
function = contract.functions[0]
parameters = function.parameters
assert (parameters[0].name == 'param1')
assert (parameters[1].name == '')
assert (parameters[2].name == 'param3')

@ -0,0 +1,9 @@
Test.bad1() (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#9-12) has bitwise-xor operator ^ instead of the exponentiation operator **:
- UINT_MAX = 2 ^ 256 - 1 (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#10)
Test.bad0(uint256) (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#5-7) has bitwise-xor operator ^ instead of the exponentiation operator **:
- a ^ 2 (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#6)
Derived.slitherConstructorVariables() (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#30) has bitwise-xor operator ^ instead of the exponentiation operator **:
- my_var = 2 ^ 256 - 1 (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#3)

@ -0,0 +1,4 @@
C.bad1() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#21-24) calls C.indirect() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#17-19) which halt the execution return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#4)
C.bad0() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#8-11) calls C.internal_return() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#2-6) which halt the execution return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#4)

@ -1,10 +1,12 @@
Variable T.s_myStateVar (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#60) is not in mixedCase
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#14-16) is not in CapWords
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#70) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#73) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#70) is single letter l, O, or I, which should not be used
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#73) is single letter l, O, or I, which should not be used
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#69) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#72) is not in mixedCase
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#11) is not in mixedCase
@ -14,11 +16,11 @@ Contract naming (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_c
Enum naming.numbers (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#6) is not in CapWords
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#60) is not in mixedCase
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#63) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#57) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#59) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#69) is single letter l, O, or I, which should not be used
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#72) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#23) is not in CapWords
@ -26,7 +28,7 @@ Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.4.25
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#30-33) is not in mixedCase
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#71) is single letter l, O, or I, which should not be used
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#35) is not in mixedCase

@ -1,10 +1,12 @@
Variable T.s_myStateVar (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#60) is not in mixedCase
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#14-16) is not in CapWords
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#70) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#73) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#70) is single letter l, O, or I, which should not be used
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#73) is single letter l, O, or I, which should not be used
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#69) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#72) is not in mixedCase
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#11) is not in mixedCase
@ -14,11 +16,11 @@ Contract naming (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_c
Enum naming.numbers (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#6) is not in CapWords
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#60) is not in mixedCase
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#63) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#57) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#59) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#69) is single letter l, O, or I, which should not be used
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#72) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#23) is not in CapWords
@ -26,7 +28,7 @@ Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.5.16
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#30-33) is not in mixedCase
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#71) is single letter l, O, or I, which should not be used
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#35) is not in mixedCase

@ -1,32 +1,36 @@
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#14-16) is not in CapWords
Variable T.s_myStateVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#63) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#70) is not in mixedCase
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#17-19) is not in CapWords
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#70) is single letter l, O, or I, which should not be used
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#76) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#69) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#76) is single letter l, O, or I, which should not be used
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#11) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#75) is not in mixedCase
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#14) is not in mixedCase
Constant naming.MY_other_CONSTANT (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#3-48) is not in CapWords
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#3-51) is not in CapWords
Enum naming.numbers (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#6) is not in CapWords
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#60) is not in mixedCase
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#66) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#62) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#57) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#75) is single letter l, O, or I, which should not be used
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#69) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#26) is not in CapWords
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#23) is not in CapWords
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#44-46) is not in mixedCase
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#41-43) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#33-36) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#30-33) is not in mixedCase
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#74) is single letter l, O, or I, which should not be used
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Variable naming.i_myImutableVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#11) is not in mixedCase
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#35) is not in mixedCase
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#38) is not in mixedCase

@ -1,32 +1,36 @@
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#14-16) is not in CapWords
Variable T.s_myStateVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#63) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#70) is not in mixedCase
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#17-19) is not in CapWords
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#70) is single letter l, O, or I, which should not be used
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#76) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#69) is not in mixedCase
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#76) is single letter l, O, or I, which should not be used
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#11) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#75) is not in mixedCase
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#14) is not in mixedCase
Constant naming.MY_other_CONSTANT (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#3-48) is not in CapWords
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#3-51) is not in CapWords
Enum naming.numbers (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#6) is not in CapWords
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#60) is not in mixedCase
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#66) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#62) is not in mixedCase
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#57) is not in mixedCase
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#75) is single letter l, O, or I, which should not be used
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#69) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#26) is not in CapWords
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#23) is not in CapWords
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#44-46) is not in mixedCase
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#41-43) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#33-36) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#30-33) is not in mixedCase
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#74) is single letter l, O, or I, which should not be used
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Variable naming.i_myImutableVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#11) is not in mixedCase
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#35) is not in mixedCase
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#38) is not in mixedCase

@ -0,0 +1,5 @@
Mark.oops(address) (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#31-55) tries to limit the gas of an external call that controls implicit decoding
ret1 = BadGuy(badGuy).fbad{gas: 2000}() (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#42)
(x,str) = BadGuy(badGuy).fbad1{gas: 2000}() (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#44)
(success,ret) = badGuy.call{gas: 10000}(abi.encodeWithSelector(BadGuy.llbad.selector)) (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#47-51)

@ -0,0 +1,2 @@
C.f() (tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol#3-7) contains an incorrect call to return: return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol#5)

@ -0,0 +1,3 @@
A.check(uint256) (tests/e2e/detectors/test_data/tautological-compare/0.8.20/compare.sol#3-5) compares a variable to itself:
(a >= a) (tests/e2e/detectors/test_data/tautological-compare/0.8.20/compare.sol#4)

@ -0,0 +1,30 @@
contract Test {
uint my_var = 2 ^ 256-1;
function bad0(uint a) internal returns (uint) {
return a^2;
}
function bad1() internal returns (uint) {
uint UINT_MAX = 2^256-1;
return UINT_MAX;
}
/* Correct exponentiation operator */
function good0(uint a) internal returns (uint) {
return a**2;
}
/* Neither operand is a constant */
function good1(uint a) internal returns (uint) {
return a^a;
}
/* The constant operand 0xff in hex typically means bitwise xor */
function good2(uint a) internal returns (uint) {
return a^0xff;
}
}
contract Derived is Test {}

@ -0,0 +1,36 @@
contract C {
function internal_return() internal{
assembly {
return (5, 6)
}
}
function bad0() public returns (bool){
internal_return();
return true;
}
function indirect2() internal {
internal_return();
}
function indirect() internal {
indirect2();
}
function bad1() public returns (bool){
indirect();
return true;
}
function good0() public{
// Dont report if there is no following operation
internal_return();
}
function good1() public returns (uint a, uint b){
assembly {
return (5, 6)
}
}
}

@ -53,9 +53,12 @@ contract Test {
contract T {
uint private _myPrivateVar;
uint private s_myPrivateVar;
uint internal _myInternalVar;
uint internal s_myInternalVar;
uint public _myPublicVar;
uint public s_myStateVar;
uint public myPublicVar;
function test(uint _unused, uint _used) public returns(uint){
return _used;}

@ -53,9 +53,12 @@ contract Test {
contract T {
uint private _myPrivateVar;
uint private s_myPrivateVar;
uint internal _myInternalVar;
uint internal s_myInternalVar;
uint public _myPublicVar;
uint public s_myStateVar;
uint public myPublicVar;
function test(uint _unused, uint _used) public returns(uint){
return _used;}

@ -8,6 +8,9 @@ contract naming {
uint constant MY_CONSTANT = 1;
uint constant MY_other_CONSTANT = 2;
uint public immutable i_myImutableVar = 1;
uint private immutable i_myPrivateImutableVar = 1;
uint Var_One = 1;
uint varTwo = 2;
@ -53,9 +56,12 @@ contract Test {
contract T {
uint private _myPrivateVar;
uint private s_myPrivateVar;
uint internal _myInternalVar;
uint internal s_myInternalVar;
uint public _myPublicVar;
uint public s_myStateVar;
uint public myPublicVar;
function test(uint _unused, uint _used) public returns(uint){
return _used;}

@ -8,6 +8,9 @@ contract naming {
uint constant MY_CONSTANT = 1;
uint constant MY_other_CONSTANT = 2;
uint public immutable i_myImutableVar = 1;
uint private immutable i_myPrivateImutableVar = 1;
uint Var_One = 1;
uint varTwo = 2;
@ -53,9 +56,12 @@ contract Test {
contract T {
uint private _myPrivateVar;
uint private s_myPrivateVar;
uint internal _myInternalVar;
uint internal s_myInternalVar;
uint public _myPublicVar;
uint public s_myStateVar;
uint public myPublicVar;
function test(uint _unused, uint _used) public returns(uint){
return _used;}

@ -0,0 +1,57 @@
contract BadGuy {
function llbad() external pure returns (bytes memory) {
assembly{
revert(0, 1000000)
}
}
function fgood() external payable returns (uint){
assembly{
return(0, 1000000)
}
}
function fbad() external payable returns (uint[] memory){
assembly{
return(0, 1000000)
}
}
function fbad1() external payable returns (uint, string memory){
assembly{
return(0, 1000000)
}
}
}
contract Mark {
function oops(address badGuy) public{
bool success;
string memory str;
bytes memory ret;
uint x;
uint[] memory ret1;
x = BadGuy(badGuy).fgood{gas:2000}();
ret1 = BadGuy(badGuy).fbad(); //good (no gas specified)
ret1 = BadGuy(badGuy).fbad{gas:2000}();
(x, str) = BadGuy(badGuy).fbad1{gas:2000}();
// Mark pays a lot of gas for this copy 😬😬😬
(success, ret) = badGuy.call{gas:10000}(
abi.encodeWithSelector(
BadGuy.llbad.selector
)
);
// Mark may OOG here, preventing local state changes
//importantCleanup();
}
}

@ -0,0 +1,8 @@
contract C {
function f() internal returns (uint a, uint b){
assembly {
return (5, 6)
}
}
}

@ -0,0 +1,6 @@
contract A{
function check(uint a) external returns(bool){
return (a >= a);
}
}

@ -1654,6 +1654,31 @@ ALL_TESTS = [
"encode_packed_collision.sol",
"0.7.6",
),
Test(
all_detectors.IncorrectReturn,
"incorrect_return.sol",
"0.8.10",
),
Test(
all_detectors.ReturnInsteadOfLeave,
"incorrect_return.sol",
"0.8.10",
),
Test(
all_detectors.IncorrectOperatorExponentiation,
"incorrect_exp.sol",
"0.7.6",
),
Test(
all_detectors.TautologicalCompare,
"compare.sol",
"0.8.20",
),
Test(
all_detectors.ReturnBomb,
"return_bomb.sol",
"0.8.20",
),
]
GENERIC_PATH = "/GENERIC_PATH"

@ -4,13 +4,13 @@ import re
import sys
from pathlib import Path
from typing import List, Dict, Tuple
from packaging.version import parse as parse_version
import pytest
from crytic_compile import CryticCompile, save_to_zip
from crytic_compile.utils.zip import load_from_zip
from deepdiff import DeepDiff
from packaging.version import parse as parse_version
from solc_select.solc_select import install_artifacts as install_solc_versions
from solc_select.solc_select import installed_versions as get_installed_solc_versions
from crytic_compile import CryticCompile, save_to_zip
from crytic_compile.utils.zip import load_from_zip
from slither import Slither
from slither.printers.guidance.echidna import Echidna

@ -0,0 +1,38 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
user_shares = ()
IRs:
user_shares(uint256[10]) = []"];
1->2;
2[label="Node Type: EXPRESSION 2
EXPRESSION:
user_shares.append(1)
IRs:
REF_1 -> LENGTH user_shares
TMP_3(uint256) := REF_1(uint256)
TMP_4(uint256) = TMP_3 (c)+ 1
REF_1(uint256) (->user_shares) := TMP_4(uint256)
REF_2(uint256) -> user_shares[TMP_3]
REF_2(uint256) (->user_shares) := 1(uint256)"];
2->3;
3[label="Node Type: EXPRESSION 3
EXPRESSION:
user_shares.pop()
IRs:
REF_4 -> LENGTH user_shares
TMP_6(uint256) = REF_4 (c)- 1
REF_5(uint256) -> user_shares[TMP_6]
REF_5 = delete REF_5
REF_6 -> LENGTH user_shares
REF_6(uint256) (->user_shares) := TMP_6(uint256)"];
}

@ -0,0 +1,118 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
a = block.coinbase
IRs:
a(address) := block.coinbase(address)"];
1->2;
2[label="Node Type: NEW VARIABLE 2
EXPRESSION:
b = block.difficulty
IRs:
b(uint256) := block.difficulty(uint256)"];
2->3;
3[label="Node Type: NEW VARIABLE 3
EXPRESSION:
c = block.prevrandao
IRs:
c(uint256) := block.prevrandao(uint256)"];
3->4;
4[label="Node Type: NEW VARIABLE 4
EXPRESSION:
d = block.number
IRs:
d(uint256) := block.number(uint256)"];
4->5;
5[label="Node Type: NEW VARIABLE 5
EXPRESSION:
e = block.prevhash
IRs:
e(bytes32) := block.prevhash(bytes32)"];
5->6;
6[label="Node Type: NEW VARIABLE 6
EXPRESSION:
f = block.timestamp
IRs:
f(uint256) := block.timestamp(uint256)"];
6->7;
7[label="Node Type: NEW VARIABLE 7
EXPRESSION:
h = bytes32(chain.id)
IRs:
TMP_0 = CONVERT chain.id to bytes32
h(bytes32) := TMP_0(bytes32)"];
7->8;
8[label="Node Type: NEW VARIABLE 8
EXPRESSION:
i = slice()(msg.data,0,32)
IRs:
TMP_1(None) = SOLIDITY_CALL slice()(msg.data,0,32)
i(bytes[32]) = ['TMP_1(None)']"];
8->9;
9[label="Node Type: NEW VARIABLE 9
EXPRESSION:
j = msg.gas
IRs:
j(uint256) := msg.gas(uint256)"];
9->10;
10[label="Node Type: NEW VARIABLE 10
EXPRESSION:
k = msg.sender
IRs:
k(address) := msg.sender(address)"];
10->11;
11[label="Node Type: NEW VARIABLE 11
EXPRESSION:
l = msg.value
IRs:
l(uint256) := msg.value(uint256)"];
11->12;
12[label="Node Type: NEW VARIABLE 12
EXPRESSION:
m = tx.origin
IRs:
m(address) := tx.origin(address)"];
12->13;
13[label="Node Type: NEW VARIABLE 13
EXPRESSION:
n = tx.gasprice
IRs:
n(uint256) := tx.gasprice(uint256)"];
13->14;
14[label="Node Type: NEW VARIABLE 14
EXPRESSION:
x = self.balance
IRs:
x(uint256) := self.balance(uint256)"];
}

@ -0,0 +1,28 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: EXPRESSION 1
EXPRESSION:
self.b(x,True)
IRs:
INTERNAL_CALL, default_args.b()(x,True)"];
1->2;
2[label="Node Type: EXPRESSION 2
EXPRESSION:
self.b(1,self.config)
IRs:
INTERNAL_CALL, default_args.b()(1,config)"];
2->3;
3[label="Node Type: EXPRESSION 3
EXPRESSION:
self.b(1,z)
IRs:
INTERNAL_CALL, default_args.b()(1,z)"];
}

@ -0,0 +1,24 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: IF 1
EXPRESSION:
config
IRs:
CONDITION config"];
1->3[label="True"];
1->2[label="False"];
2[label="Node Type: END_IF 2
"];
3[label="Node Type: EXPRESSION 3
EXPRESSION:
self.counter = y
IRs:
counter(uint256) := y(uint256)"];
3->2;
}

@ -0,0 +1,63 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
_strategies = strategies
IRs:
_strategies(address[3]) = ['strategies(address[3])']"];
1->2;
2[label="Node Type: BEGIN_LOOP 2
"];
2->4;
3[label="Node Type: END_LOOP 3
"];
4[label="Node Type: NEW VARIABLE 4
EXPRESSION:
counter_var = 0
IRs:
counter_var(uint256) := 0(uint256)"];
4->5;
5[label="Node Type: IF_LOOP 5
EXPRESSION:
counter_var <= 10
IRs:
TMP_0(bool) = counter_var <= 10
CONDITION TMP_0"];
5->7[label="True"];
5->3[label="False"];
6[label="Node Type: EXPRESSION 6
EXPRESSION:
counter_var += 1
IRs:
counter_var(uint256) = counter_var (c)+ 1"];
6->5;
7[label="Node Type: NEW VARIABLE 7
EXPRESSION:
i = counter_var
IRs:
i(uint256) := counter_var(uint256)"];
7->8;
8[label="Node Type: NEW VARIABLE 8
EXPRESSION:
max_withdraw = IStrategy(_strategies[i]).maxWithdraw(self)
IRs:
REF_0(address) -> _strategies[i]
TMP_1 = CONVERT REF_0 to IStrategy
TMP_2(uint256) = HIGH_LEVEL_CALL, dest:TMP_1(IStrategy), function:maxWithdraw, arguments:['self']
max_withdraw(uint256) := TMP_2(uint256)"];
8->6;
}

@ -0,0 +1,19 @@
digraph{
0[label="Node Type: OTHER_ENTRYPOINT 0
EXPRESSION:
x = 1 + 1
IRs:
TMP_3(uint256) = 1 + 1
x(uint256) := TMP_3(uint256)"];
0->1;
1[label="Node Type: OTHER_ENTRYPOINT 1
EXPRESSION:
MAX_QUEUE = 1 + x
IRs:
TMP_4(uint256) = 1 + x
MAX_QUEUE(uint256) := TMP_4(uint256)"];
}

@ -0,0 +1,62 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
S = 0
IRs:
S(uint256) := 0(uint256)"];
1->2;
2[label="Node Type: BEGIN_LOOP 2
"];
2->4;
3[label="Node Type: END_LOOP 3
"];
4[label="Node Type: NEW VARIABLE 4
EXPRESSION:
counter_var = 0
IRs:
counter_var(uint256) := 0(uint256)"];
4->5;
5[label="Node Type: IF_LOOP 5
EXPRESSION:
counter_var <= len()(_xp)
IRs:
TMP_0(uint256) = SOLIDITY_CALL len()(_xp)
TMP_1(bool) = counter_var <= TMP_0
CONDITION TMP_1"];
5->7[label="True"];
5->3[label="False"];
6[label="Node Type: EXPRESSION 6
EXPRESSION:
counter_var += 1
IRs:
counter_var(uint256) = counter_var (c)+ 1"];
6->5;
7[label="Node Type: NEW VARIABLE 7
EXPRESSION:
x = _xp[counter_var]
IRs:
REF_0(uint256) -> _xp[counter_var]
x(uint256) := REF_0(uint256)"];
7->8;
8[label="Node Type: EXPRESSION 8
EXPRESSION:
S += x
IRs:
S(uint256) = S (c)+ x"];
8->6;
}

@ -0,0 +1,164 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: BEGIN_LOOP 1
"];
1->3;
2[label="Node Type: END_LOOP 2
"];
3[label="Node Type: NEW VARIABLE 3
EXPRESSION:
counter_var = 0
IRs:
counter_var(uint256) := 0(uint256)"];
3->4;
4[label="Node Type: IF_LOOP 4
EXPRESSION:
counter_var <= 100
IRs:
TMP_0(bool) = counter_var <= 100
CONDITION TMP_0"];
4->6[label="True"];
4->2[label="False"];
5[label="Node Type: EXPRESSION 5
EXPRESSION:
counter_var += 1
IRs:
counter_var(uint256) = counter_var (c)+ 1"];
5->4;
6[label="Node Type: NEW VARIABLE 6
EXPRESSION:
i = counter_var
IRs:
i(uint256) := counter_var(uint256)"];
6->7;
7[label="Node Type: IF 7
EXPRESSION:
i > 100
IRs:
TMP_1(bool) = i > 100
CONDITION TMP_1"];
7->9[label="True"];
7->8[label="False"];
8[label="Node Type: END_IF 8
"];
8->10;
9[label="Node Type: BREAK 9
"];
9->2;
10[label="Node Type: IF 10
EXPRESSION:
i < 3
IRs:
TMP_2(bool) = i < 3
CONDITION TMP_2"];
10->12[label="True"];
10->11[label="False"];
11[label="Node Type: END_IF 11
"];
11->13;
12[label="Node Type: CONTINUE 12
"];
12->5;
13[label="Node Type: NEW VARIABLE 13
EXPRESSION:
x = 10
IRs:
x(uint256) := 10(uint256)"];
13->14;
14[label="Node Type: BEGIN_LOOP 14
"];
14->16;
15[label="Node Type: END_LOOP 15
"];
15->5;
16[label="Node Type: NEW VARIABLE 16
EXPRESSION:
counter_var_scope_0 = 0
IRs:
counter_var_scope_0(uint256) := 0(uint256)"];
16->17;
17[label="Node Type: IF_LOOP 17
EXPRESSION:
counter_var <= 10
IRs:
TMP_3(bool) = counter_var <= 10
CONDITION TMP_3"];
17->19[label="True"];
17->15[label="False"];
18[label="Node Type: EXPRESSION 18
EXPRESSION:
counter_var += 1
IRs:
counter_var(uint256) = counter_var (c)+ 1"];
18->17;
19[label="Node Type: NEW VARIABLE 19
EXPRESSION:
j = counter_var
IRs:
j(uint256) := counter_var(uint256)"];
19->20;
20[label="Node Type: IF 20
EXPRESSION:
j > 10
IRs:
TMP_4(bool) = j > 10
CONDITION TMP_4"];
20->22[label="True"];
20->21[label="False"];
21[label="Node Type: END_IF 21
"];
21->23;
22[label="Node Type: CONTINUE 22
"];
22->18;
23[label="Node Type: IF 23
EXPRESSION:
j < 3
IRs:
TMP_5(bool) = j < 3
CONDITION TMP_5"];
23->25[label="True"];
23->24[label="False"];
24[label="Node Type: END_IF 24
"];
24->26;
25[label="Node Type: BREAK 25
"];
25->15;
26[label="Node Type: EXPRESSION 26
EXPRESSION:
x -= 1
IRs:
x(uint256) = x (c)- 1"];
26->18;
}

@ -0,0 +1,56 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: BEGIN_LOOP 1
"];
1->3;
2[label="Node Type: END_LOOP 2
"];
3[label="Node Type: NEW VARIABLE 3
EXPRESSION:
counter_var = 0
IRs:
counter_var(uint256) := 0(uint256)"];
3->4;
4[label="Node Type: IF_LOOP 4
EXPRESSION:
counter_var <= len()(self.strategies)
IRs:
TMP_0(uint256) = SOLIDITY_CALL len()(strategies)
TMP_1(bool) = counter_var <= TMP_0
CONDITION TMP_1"];
4->6[label="True"];
4->2[label="False"];
5[label="Node Type: EXPRESSION 5
EXPRESSION:
counter_var += 1
IRs:
counter_var(uint256) = counter_var (c)+ 1"];
5->4;
6[label="Node Type: NEW VARIABLE 6
EXPRESSION:
strategy = strategies[counter_var]
IRs:
REF_0(address) -> strategies[counter_var]
strategy(address) := REF_0(address)"];
6->7;
7[label="Node Type: NEW VARIABLE 7
EXPRESSION:
z = IStrategy(strategy).asset()
IRs:
TMP_2 = CONVERT strategy to IStrategy
TMP_3(address) = HIGH_LEVEL_CALL, dest:TMP_2(IStrategy), function:asset, arguments:[]
z(address) := TMP_3(address)"];
7->5;
}

@ -0,0 +1,19 @@
digraph{
0[label="Node Type: OTHER_ENTRYPOINT 0
EXPRESSION:
x = 1 + 1
IRs:
TMP_4(uint256) = 1 + 1
x(uint256) := TMP_4(uint256)"];
0->1;
1[label="Node Type: OTHER_ENTRYPOINT 1
EXPRESSION:
MAX_QUEUE = 1 + x
IRs:
TMP_5(uint256) = 1 + x
MAX_QUEUE(uint256) := TMP_5(uint256)"];
}

@ -0,0 +1,172 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
a = p
IRs:
a(uint256) := p(uint256)"];
1->2;
2[label="Node Type: NEW VARIABLE 2
EXPRESSION:
b = 1
IRs:
b(uint256) := 1(uint256)"];
2->3;
3[label="Node Type: NEW VARIABLE 3
EXPRESSION:
c = 0
IRs:
c(uint256) := 0(uint256)"];
3->4;
4[label="Node Type: IF 4
EXPRESSION:
b > 0
IRs:
TMP_0(bool) = b > 0
CONDITION TMP_0"];
4->6[label="True"];
4->5[label="False"];
5[label="Node Type: END_IF 5
"];
6[label="Node Type: NEW VARIABLE 6
EXPRESSION:
old_a = 1
IRs:
old_a(uint256) := 1(uint256)"];
6->7;
7[label="Node Type: NEW VARIABLE 7
EXPRESSION:
old_c = 2
IRs:
old_c(uint256) := 2(uint256)"];
7->8;
8[label="Node Type: IF 8
EXPRESSION:
p > old_a
IRs:
TMP_1(bool) = p > old_a
CONDITION TMP_1"];
8->10[label="True"];
8->15[label="False"];
9[label="Node Type: END_IF 9
"];
9->20;
10[label="Node Type: EXPRESSION 10
EXPRESSION:
c = unsafe_div()(old_a * 10 ** 18,p)
IRs:
TMP_2(uint256) = 10 (c)** 18
TMP_3(uint256) = old_a (c)* TMP_2
TMP_4(None) = SOLIDITY_CALL unsafe_div()(TMP_3,p)
c(uint256) := TMP_4(None)"];
10->11;
11[label="Node Type: IF 11
EXPRESSION:
c < 10 ** 36 / 1
IRs:
TMP_5(uint256) = 10 (c)** 36
TMP_6(uint256) = TMP_5 (c)/ 1
TMP_7(bool) = c < TMP_6
CONDITION TMP_7"];
11->13[label="True"];
11->12[label="False"];
12[label="Node Type: END_IF 12
"];
12->9;
13[label="Node Type: EXPRESSION 13
EXPRESSION:
a = unsafe_div()(old_a * 1,10 ** 18)
IRs:
TMP_8(uint256) = old_a (c)* 1
TMP_9(uint256) = 10 (c)** 18
TMP_10(None) = SOLIDITY_CALL unsafe_div()(TMP_8,TMP_9)
a(uint256) := TMP_10(None)"];
13->14;
14[label="Node Type: EXPRESSION 14
EXPRESSION:
c = 10 ** 36 / 1
IRs:
TMP_11(uint256) = 10 (c)** 36
TMP_12(uint256) = TMP_11 (c)/ 1
c(uint256) := TMP_12(uint256)"];
14->12;
15[label="Node Type: EXPRESSION 15
EXPRESSION:
c = unsafe_div()(p * 10 ** 18,old_a)
IRs:
TMP_13(uint256) = 10 (c)** 18
TMP_14(uint256) = p (c)* TMP_13
TMP_15(None) = SOLIDITY_CALL unsafe_div()(TMP_14,old_a)
c(uint256) := TMP_15(None)"];
15->16;
16[label="Node Type: IF 16
EXPRESSION:
c < 10 ** 36 / 1
IRs:
TMP_16(uint256) = 10 (c)** 36
TMP_17(uint256) = TMP_16 (c)/ 1
TMP_18(bool) = c < TMP_17
CONDITION TMP_18"];
16->18[label="True"];
16->17[label="False"];
17[label="Node Type: END_IF 17
"];
17->9;
18[label="Node Type: EXPRESSION 18
EXPRESSION:
a = unsafe_div()(old_a * 10 ** 18,1)
IRs:
TMP_19(uint256) = 10 (c)** 18
TMP_20(uint256) = old_a (c)* TMP_19
TMP_21(None) = SOLIDITY_CALL unsafe_div()(TMP_20,1)
a(uint256) := TMP_21(None)"];
18->19;
19[label="Node Type: EXPRESSION 19
EXPRESSION:
c = 10 ** 36 / 1
IRs:
TMP_22(uint256) = 10 (c)** 36
TMP_23(uint256) = TMP_22 (c)/ 1
c(uint256) := TMP_23(uint256)"];
19->17;
20[label="Node Type: EXPRESSION 20
EXPRESSION:
c = 1
IRs:
c(uint256) := 1(uint256)"];
20->5;
}

@ -0,0 +1,62 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: IF 1
EXPRESSION:
uint256(x) & uint256(self.roles[self]) != 0
IRs:
TMP_10 = CONVERT x to uint256
REF_4(in.Roles) -> roles[self]
TMP_11 = CONVERT REF_4 to uint256
TMP_12(uint256) = TMP_10 & TMP_11
TMP_13(bool) = TMP_12 != 0
CONDITION TMP_13"];
1->3[label="True"];
1->2[label="False"];
2[label="Node Type: END_IF 2
"];
2->4;
3[label="Node Type: RETURN 3
EXPRESSION:
True
IRs:
RETURN True"];
3->2;
4[label="Node Type: IF 4
EXPRESSION:
uint256(x) & uint256(self.roles[self]) == 0
IRs:
TMP_14 = CONVERT x to uint256
REF_5(in.Roles) -> roles[self]
TMP_15 = CONVERT REF_5 to uint256
TMP_16(uint256) = TMP_14 & TMP_15
TMP_17(bool) = TMP_16 == 0
CONDITION TMP_17"];
4->6[label="True"];
4->5[label="False"];
5[label="Node Type: END_IF 5
"];
5->7;
6[label="Node Type: RETURN 6
EXPRESSION:
False
IRs:
RETURN False"];
6->5;
7[label="Node Type: RETURN 7
EXPRESSION:
False
IRs:
RETURN False"];
}

@ -0,0 +1,66 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: IF 1
EXPRESSION:
uint256(x) & uint256(Roles.A | Roles.B) != 0
IRs:
TMP_0 = CONVERT x to uint256
REF_0(in.Roles) -> Roles.A
REF_1(in.Roles) -> Roles.B
TMP_1(in.Roles) = REF_0 | REF_1
TMP_2 = CONVERT TMP_1 to uint256
TMP_3(uint256) = TMP_0 & TMP_2
TMP_4(bool) = TMP_3 != 0
CONDITION TMP_4"];
1->3[label="True"];
1->2[label="False"];
2[label="Node Type: END_IF 2
"];
2->4;
3[label="Node Type: RETURN 3
EXPRESSION:
True
IRs:
RETURN True"];
3->2;
4[label="Node Type: IF 4
EXPRESSION:
uint256(x) & uint256(Roles.A | Roles.B) == 0
IRs:
TMP_5 = CONVERT x to uint256
REF_2(in.Roles) -> Roles.A
REF_3(in.Roles) -> Roles.B
TMP_6(in.Roles) = REF_2 | REF_3
TMP_7 = CONVERT TMP_6 to uint256
TMP_8(uint256) = TMP_5 & TMP_7
TMP_9(bool) = TMP_8 == 0
CONDITION TMP_9"];
4->6[label="True"];
4->5[label="False"];
5[label="Node Type: END_IF 5
"];
5->7;
6[label="Node Type: RETURN 6
EXPRESSION:
False
IRs:
RETURN False"];
6->5;
7[label="Node Type: RETURN 7
EXPRESSION:
False
IRs:
RETURN False"];
}

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

Loading…
Cancel
Save