Merge pull request #2099 from crytic/feat/vyper-support

Feat/vyper support
pull/2170/head
Feist Josselin 1 year ago committed by GitHub
commit 9e003eeabe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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. 24
      slither/detectors/abstract_detector.py
  13. 2
      slither/detectors/attributes/incorrect_solc.py
  14. 2
      slither/detectors/naming_convention/naming_convention.py
  15. 2
      slither/detectors/statements/deprecated_calls.py
  16. 34
      slither/slither.py
  17. 21
      slither/slithir/convert.py
  18. 5
      slither/slithir/operations/type_conversion.py
  19. 2
      slither/slithir/variables/constant.py
  20. 18
      slither/solc_parsing/slither_compilation_unit_solc.py
  21. 49
      slither/visitors/slithir/expression_to_slithir.py
  22. 0
      slither/vyper_parsing/__init__.py
  23. 0
      slither/vyper_parsing/ast/__init__.py
  24. 466
      slither/vyper_parsing/ast/ast.py
  25. 262
      slither/vyper_parsing/ast/types.py
  26. 0
      slither/vyper_parsing/cfg/__init__.py
  27. 66
      slither/vyper_parsing/cfg/node.py
  28. 0
      slither/vyper_parsing/declarations/__init__.py
  29. 524
      slither/vyper_parsing/declarations/contract.py
  30. 39
      slither/vyper_parsing/declarations/event.py
  31. 563
      slither/vyper_parsing/declarations/function.py
  32. 33
      slither/vyper_parsing/declarations/struct.py
  33. 0
      slither/vyper_parsing/expressions/__init__.py
  34. 464
      slither/vyper_parsing/expressions/expression_parsing.py
  35. 150
      slither/vyper_parsing/expressions/find_variable.py
  36. 99
      slither/vyper_parsing/type_parsing.py
  37. 0
      slither/vyper_parsing/variables/__init__.py
  38. 24
      slither/vyper_parsing/variables/event_variable.py
  39. 34
      slither/vyper_parsing/variables/local_variable.py
  40. 29
      slither/vyper_parsing/variables/state_variable.py
  41. 17
      slither/vyper_parsing/variables/structure_variable.py
  42. 80
      slither/vyper_parsing/vyper_compilation_unit.py
  43. 22
      tests/conftest.py
  44. 0
      tests/e2e/vyper_parsing/__init__.py
  45. 38
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt
  46. 118
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt
  47. 28
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt
  48. 24
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt
  49. 63
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt
  50. 19
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_slitherConstructorConstantVariables__0.txt
  51. 62
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt
  52. 164
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_break_continue_f__0.txt
  53. 56
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt
  54. 19
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_slitherConstructorConstantVariables__0.txt
  55. 172
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt
  56. 62
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt
  57. 66
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt
  58. 74
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt
  59. 22
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt
  60. 16
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt
  61. 9
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt
  62. 57
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_bar__0.txt
  63. 56
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_baz__0.txt
  64. 12
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_foo__0.txt
  65. 12
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fa__0.txt
  66. 12
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt
  67. 17
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt
  68. 13
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_struct_test__0.txt
  69. 28
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_struct___default__0.txt
  70. 9
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_types_slitherConstructorConstantVariables__0.txt
  71. 25
      tests/e2e/vyper_parsing/test_ast_parsing.py
  72. 9
      tests/e2e/vyper_parsing/test_data/ERC20.vy
  73. 24
      tests/e2e/vyper_parsing/test_data/builtins.vy
  74. 12
      tests/e2e/vyper_parsing/test_data/default_args.vy
  75. 29
      tests/e2e/vyper_parsing/test_data/for.vy
  76. 26
      tests/e2e/vyper_parsing/test_data/for2.vy
  77. 6
      tests/e2e/vyper_parsing/test_data/for3.vy
  78. 17
      tests/e2e/vyper_parsing/test_data/for_break_continue.vy
  79. 22
      tests/e2e/vyper_parsing/test_data/if.vy
  80. 36
      tests/e2e/vyper_parsing/test_data/in.vy
  81. 17
      tests/e2e/vyper_parsing/test_data/initarry.vy
  82. 7
      tests/e2e/vyper_parsing/test_data/interface_constant.vy
  83. 29
      tests/e2e/vyper_parsing/test_data/interface_conversion.vy
  84. 13
      tests/e2e/vyper_parsing/test_data/precedence.vy
  85. 7
      tests/e2e/vyper_parsing/test_data/struct.vy
  86. 10
      tests/e2e/vyper_parsing/test_data/tuple_struct.vy
  87. 16
      tests/e2e/vyper_parsing/test_data/types.vy
  88. 4
      tests/unit/core/test_data/src_mapping/SelfIdentifier.vy
  89. 94
      tests/unit/core/test_function_declaration.py
  90. 14
      tests/unit/core/test_source_mapping.py
  91. 100
      tests/unit/slithir/test_ssa_generation.py
  92. 0
      tests/unit/slithir/vyper/__init__.py
  93. 99
      tests/unit/slithir/vyper/test_ir_generation.py

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

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

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

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

@ -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:
@ -1996,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
"""
@ -2006,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

@ -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,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"];
}

@ -0,0 +1,74 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
a = 0
IRs:
a(int128) := 0(uint256)"];
1->2;
2[label="Node Type: NEW VARIABLE 2
EXPRESSION:
b = 0
IRs:
b(int128) := 0(uint256)"];
2->3;
3[label="Node Type: IF 3
EXPRESSION:
x == b || x == a
IRs:
TMP_18(bool) = x == b
TMP_19(bool) = x == a
TMP_20(bool) = TMP_18 || TMP_19
CONDITION TMP_20"];
3->5[label="True"];
3->4[label="False"];
4[label="Node Type: END_IF 4
"];
4->6;
5[label="Node Type: RETURN 5
EXPRESSION:
True
IRs:
RETURN True"];
5->4;
6[label="Node Type: IF 6
EXPRESSION:
x != b && x != a
IRs:
TMP_21(bool) = x != b
TMP_22(bool) = x != a
TMP_23(bool) = TMP_21 && TMP_22
CONDITION TMP_23"];
6->8[label="True"];
6->7[label="False"];
7[label="Node Type: END_IF 7
"];
7->9;
8[label="Node Type: EXPRESSION 8
EXPRESSION:
revert(string)(nope)
IRs:
TMP_24(None) = SOLIDITY_CALL revert(string)(nope)"];
8->7;
9[label="Node Type: RETURN 9
EXPRESSION:
False
IRs:
RETURN False"];
}

@ -0,0 +1,22 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: EXPRESSION 1
EXPRESSION:
BORROWED_TOKEN = ERC20(x)
IRs:
TMP_0 = CONVERT x to ERC20
BORROWED_TOKEN(ERC20) := TMP_0(ERC20)"];
1->2;
2[label="Node Type: EXPRESSION 2
EXPRESSION:
COLLATERAL_TOKEN = ERC20(y)
IRs:
TMP_1 = CONVERT y to ERC20
COLLATERAL_TOKEN(ERC20) := TMP_1(ERC20)"];
}

@ -0,0 +1,16 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: RETURN 1
EXPRESSION:
(address(BORROWED_TOKEN),address(COLLATERAL_TOKEN))[i]
IRs:
TMP_2 = CONVERT BORROWED_TOKEN to address
TMP_3 = CONVERT COLLATERAL_TOKEN to address
TMP_4(address[2]) = ['TMP_2(address)', 'TMP_3(address)']
REF_0(address) -> TMP_4[i]
RETURN REF_0"];
}

@ -0,0 +1,9 @@
digraph{
0[label="Node Type: OTHER_ENTRYPOINT 0
EXPRESSION:
MY_CONSTANT = 50
IRs:
MY_CONSTANT(uint256) := 50(uint256)"];
}

@ -0,0 +1,57 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
a = 0
IRs:
a(int128) := 0(uint256)"];
1->2;
2[label="Node Type: NEW VARIABLE 2
EXPRESSION:
b = 0
IRs:
b(int128) := 0(uint256)"];
2->3;
3[label="Node Type: EXPRESSION 3
EXPRESSION:
(a,b) = self.foo()
IRs:
TUPLE_0(int128,int128) = INTERNAL_CALL, interface_conversion.foo()()
a(int128)= UNPACK TUPLE_0 index: 0
b(int128)= UNPACK TUPLE_0 index: 1 "];
3->4;
4[label="Node Type: NEW VARIABLE 4
EXPRESSION:
x = 0x0000000000000000000000000000000000000000
IRs:
x(address) := 0(address)"];
4->5;
5[label="Node Type: NEW VARIABLE 5
EXPRESSION:
c = 0
IRs:
c(uint256) := 0(uint256)"];
5->6;
6[label="Node Type: EXPRESSION 6
EXPRESSION:
(a,c) = Test(x).foo()
IRs:
TMP_0 = CONVERT x to Test
TUPLE_1(int128,uint256) = HIGH_LEVEL_CALL, dest:TMP_0(Test), function:foo, arguments:[]
a(int128)= UNPACK TUPLE_1 index: 0
c(uint256)= UNPACK TUPLE_1 index: 1 "];
}

@ -0,0 +1,56 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
a = 0
IRs:
a(int128) := 0(uint256)"];
1->2;
2[label="Node Type: NEW VARIABLE 2
EXPRESSION:
b = 0
IRs:
b(int128) := 0(uint256)"];
2->3;
3[label="Node Type: EXPRESSION 3
EXPRESSION:
(a,b) = self.foo()
IRs:
TUPLE_2(int128,int128) = INTERNAL_CALL, interface_conversion.foo()()
a(int128)= UNPACK TUPLE_2 index: 0
b(int128)= UNPACK TUPLE_2 index: 1 "];
3->4;
4[label="Node Type: NEW VARIABLE 4
EXPRESSION:
x = 0x0000000000000000000000000000000000000000
IRs:
x(address) := 0(address)"];
4->5;
5[label="Node Type: NEW VARIABLE 5
EXPRESSION:
c = 0
IRs:
c(uint256) := 0(uint256)"];
5->6;
6[label="Node Type: EXPRESSION 6
EXPRESSION:
(a,c) = self.tester.foo()
IRs:
TUPLE_3(int128,uint256) = HIGH_LEVEL_CALL, dest:tester(Test), function:foo, arguments:[]
a(int128)= UNPACK TUPLE_3 index: 0
c(uint256)= UNPACK TUPLE_3 index: 1 "];
}

@ -0,0 +1,12 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: RETURN 1
EXPRESSION:
(2,3)
IRs:
RETURN 2,3"];
}

@ -0,0 +1,12 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: RETURN 1
EXPRESSION:
1
IRs:
RETURN 1"];
}

@ -0,0 +1,12 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: EXPRESSION 1
EXPRESSION:
revert()()
IRs:
TMP_0(None) = SOLIDITY_CALL revert()()"];
}

@ -0,0 +1,17 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: RETURN 1
EXPRESSION:
x != self.fb() && x != self.fa()
IRs:
TMP_1(uint256) = INTERNAL_CALL, precedence.fb()()
TMP_2(bool) = x != TMP_1
TMP_3(uint256) = INTERNAL_CALL, precedence.fa()()
TMP_4(bool) = x != TMP_3
TMP_5(bool) = TMP_2 && TMP_4
RETURN TMP_5"];
}

@ -0,0 +1,13 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: RETURN 1
EXPRESSION:
X(1)
IRs:
TMP_0(struct.X) = new X(1)
RETURN TMP_0"];
}

@ -0,0 +1,28 @@
digraph{
0[label="Node Type: ENTRY_POINT 0
"];
0->1;
1[label="Node Type: NEW VARIABLE 1
EXPRESSION:
chainlink_lrd = Test(msg.sender).get()
IRs:
TMP_0 = CONVERT msg.sender to Test
TUPLE_0(uint80,int256,uint256,uint256,uint80) = HIGH_LEVEL_CALL, dest:TMP_0(Test), function:get, arguments:[]
TMP_1(uint80)= UNPACK TUPLE_0 index: 0
TMP_2(int256)= UNPACK TUPLE_0 index: 1
TMP_3(uint256)= UNPACK TUPLE_0 index: 2
TMP_4(uint256)= UNPACK TUPLE_0 index: 3
TMP_5(uint80)= UNPACK TUPLE_0 index: 4
chainlink_lrd(FAKE_TUPLE_0_1_2_3_4) = new FAKE_TUPLE_0_1_2_3_4(TMP_1,TMP_2,TMP_3,TMP_4,TMP_5)"];
1->2;
2[label="Node Type: RETURN 2
EXPRESSION:
chainlink_lrd[0]
IRs:
REF_1(uint80) -> chainlink_lrd._0
RETURN REF_1"];
}

@ -0,0 +1,9 @@
digraph{
0[label="Node Type: OTHER_ENTRYPOINT 0
EXPRESSION:
MAX_BANDS = 10
IRs:
MAX_BANDS(uint256) := 10(uint256)"];
}

@ -0,0 +1,25 @@
from pathlib import Path
from slither import Slither
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
ALL_TESTS = list(Path(TEST_DATA_DIR).glob("*.vy"))
def pytest_generate_tests(metafunc):
test_cases = []
for test_file in ALL_TESTS:
sl = Slither(test_file.as_posix())
for contract in sl.contracts:
if contract.is_interface:
continue
for func_or_modifier in contract.functions:
test_cases.append(
(func_or_modifier.canonical_name, func_or_modifier.slithir_cfg_to_dot_str())
)
metafunc.parametrize("test_case", test_cases, ids=lambda x: x[0])
def test_vyper_cfgir(test_case, snapshot):
assert snapshot() == test_case[1]

@ -0,0 +1,9 @@
interface ERC20:
def totalSupply() -> uint256: view
def balanceOf(_owner: address) -> uint256: view
def allowance(_owner: address, _spender: address) -> uint256: view
def transfer(_to: address, _value: uint256) -> bool: nonpayable
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable
def approve(_spender: address, _value: uint256) -> bool: nonpayable

@ -0,0 +1,24 @@
@payable
@external
def test_builtins():
a: address = block.coinbase
b: uint256 = block.difficulty
c: uint256 = block.prevrandao
d: uint256 = block.number
e: bytes32 = block.prevhash
f: uint256 = block.timestamp
h: bytes32 = convert(chain.id, bytes32)
i: Bytes[32] = slice(msg.data, 0, 32)
j: uint256 = msg.gas
k: address = msg.sender
l: uint256 = msg.value
m: address = tx.origin
n: uint256 = tx.gasprice
x: uint256 = self.balance
@external
def c(x: uint256):
user_shares: DynArray[uint256, 10] = []
user_shares.append(1)
user_shares.pop()

@ -0,0 +1,12 @@
counter: uint256
config: bool
@internal
def b(y: uint256, config: bool = True):
if config:
self.counter = y
@external
def a(x: uint256, z: bool):
self.b(x)
self.b(1, self.config)
self.b(1, z)

@ -0,0 +1,29 @@
x: constant(uint256) = 1 + 1
MAX_QUEUE: constant(uint256) = 1 + x
interface IStrategy:
def asset() -> address: view
def balanceOf(owner: address) -> uint256: view
def maxDeposit(receiver: address) -> uint256: view
def maxWithdraw(owner: address) -> uint256: view
def withdraw(amount: uint256, receiver: address, owner: address) -> uint256: nonpayable
def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable
def deposit(assets: uint256, receiver: address) -> uint256: nonpayable
def totalAssets() -> (uint256): view
def convertToAssets(shares: uint256) -> uint256: view
def convertToShares(assets: uint256) -> uint256: view
def previewWithdraw(assets: uint256) -> uint256: view
struct X:
y: int8
strategies: public(DynArray[address, MAX_QUEUE])
@external
def for_loop():
for strategy in self.strategies:
z: address = IStrategy(strategy).asset()

@ -0,0 +1,26 @@
x: constant(uint256) = 1 + 1
MAX_QUEUE: constant(uint256) = 1 + x
interface IStrategy:
def asset() -> address: view
def balanceOf(owner: address) -> uint256: view
def maxDeposit(receiver: address) -> uint256: view
def maxWithdraw(owner: address) -> uint256: view
def withdraw(amount: uint256, receiver: address, owner: address) -> uint256: nonpayable
def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable
def deposit(assets: uint256, receiver: address) -> uint256: nonpayable
def totalAssets() -> (uint256): view
def convertToAssets(shares: uint256) -> uint256: view
def convertToShares(assets: uint256) -> uint256: view
def previewWithdraw(assets: uint256) -> uint256: view
@external
def for_loop(strategies: DynArray[address, MAX_QUEUE]):
_strategies: DynArray[address, MAX_QUEUE] = strategies
for i in range(10):
max_withdraw: uint256 = IStrategy(_strategies[i]).maxWithdraw(self)

@ -0,0 +1,6 @@
@external
def get_D(_xp: uint256[3], _amp: uint256):
S: uint256 = 0
for x in _xp:
S += x

@ -0,0 +1,17 @@
@external
def f():
for i in range(100):
if (i > 100):
break
if (i < 3):
continue
x: uint256 = 10
for j in range(10):
if (j > 10):
continue
if (j < 3):
break
x -= 1

@ -0,0 +1,22 @@
@external
@view
def compute(p: uint256):
a: uint256 = p
b: uint256 = 1
c: uint256 = 0
if b > 0:
old_a: uint256 = 1
old_c: uint256 = 2
if p > old_a:
c = unsafe_div(old_a * 10**18, p)
if c < 10**36 / 1:
a = unsafe_div(old_a * 1, 10**18)
c = 10**36 / 1
else:
c = unsafe_div(p * 10**18, old_a)
if c < 10**36 / 1:
a = unsafe_div(old_a * 10**18, 1)
c = 10**36 / 1
c = 1

@ -0,0 +1,36 @@
enum Roles:
A
B
roles: public(HashMap[address, Roles])
@external
def baz(x: Roles) -> bool:
if x in (Roles.A | Roles.B):
return True
if x not in (Roles.A | Roles.B):
return False
return False
@external
def bar(x: Roles) -> bool:
if x in self.roles[self]:
return True
if x not in self.roles[self]:
return False
return False
@external
def foo(x: int128) -> bool:
a: int128 = 0
b: int128 = 0
if x in [a, b]:
return True
if x not in [a, b]:
raise "nope"
return False

@ -0,0 +1,17 @@
interface ERC20:
def transfer(_to: address, _value: uint256) -> bool: nonpayable
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable
def approve(_spender: address, _value: uint256) -> bool: nonpayable
BORROWED_TOKEN: immutable(ERC20)
COLLATERAL_TOKEN: immutable(ERC20)
@external
def __init__(x: address, y: address):
BORROWED_TOKEN = ERC20(x)
COLLATERAL_TOKEN = ERC20(y)
@external
@pure
def coins(i: uint256) -> address:
return [BORROWED_TOKEN.address, COLLATERAL_TOKEN.address][i]

@ -0,0 +1,7 @@
struct MyStruct:
liquidation_range: address
MY_CONSTANT: constant(uint256) = 50
interface MyInterface:
def my_func(a: int256, b: DynArray[uint256, MY_CONSTANT]) -> MyStruct: nonpayable

@ -0,0 +1,29 @@
interface Test:
def foo() -> (int128, uint256): nonpayable
tester: Test
@internal
def foo() -> (int128, int128):
return 2, 3
@external
def bar():
a: int128 = 0
b: int128 = 0
(a, b) = self.foo()
x: address = 0x0000000000000000000000000000000000000000
c: uint256 = 0
a, c = Test(x).foo()
@external
def baz():
a: int128 = 0
b: int128 = 0
(a, b) = self.foo()
x: address = 0x0000000000000000000000000000000000000000
c: uint256 = 0
a, c = self.tester.foo()

@ -0,0 +1,13 @@
@internal
def fa() -> uint256:
return 1
@internal
def fb() -> uint256:
raise
@external
def foo(x: uint256) -> bool:
return x not in [self.fa(), self.fb()]

@ -0,0 +1,7 @@
struct X:
y: int8
@external
def test() -> X:
return X({y: 1})

@ -0,0 +1,10 @@
interface Test:
def get() -> (uint80, int256, uint256, uint256, uint80): view
@external
def __default__() -> uint80:
chainlink_lrd: (uint80, int256, uint256, uint256, uint80) = Test(msg.sender).get()
return chainlink_lrd[0]

@ -0,0 +1,16 @@
name: public(String[64])
symbol: public(String[32])
decimals: public(uint256)
totalSupply: public(uint256)
balances: HashMap[address, uint256]
allowances: HashMap[address, HashMap[address, uint256]]
MAX_BANDS: constant(uint256) = 10
x: public(uint256[3][4])
y: public(uint256[2])
struct Loan:
liquidation_range: DynArray[uint256, MAX_BANDS]
deposit_amounts: DynArray[uint256, MAX_BANDS]

@ -0,0 +1,4 @@
name: public(String[64])
@external
def __init__(name: String[64]):
self.name = name

@ -9,6 +9,7 @@ from pathlib import Path
from slither import Slither
from slither.core.declarations.function import FunctionType
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.mapping_type import MappingType
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
FUNC_DELC_TEST_ROOT = Path(TEST_DATA_DIR, "function_declaration")
@ -302,3 +303,96 @@ def test_public_variable(solc_binary_path) -> None:
assert var.signature_str == "info() returns(bytes32)"
assert var.visibility == "public"
assert var.type == ElementaryType("bytes32")
# pylint: disable=too-many-statements
def test_vyper_functions(slither_from_vyper_source) -> None:
with slither_from_vyper_source(
"""
balances: public(HashMap[address, uint256])
allowances: HashMap[address, HashMap[address, uint256]]
@pure
@internal
def add(x: int128, y: int128) -> int128:
return x + y
@external
def __init__():
pass
@external
def withdraw():
raw_call(msg.sender, b"", value= self.balances[msg.sender])
@external
@nonreentrant("lock")
def withdraw_locked():
raw_call(msg.sender, b"", value= self.balances[msg.sender])
@payable
@external
def __default__():
pass
"""
) as sl:
contract = sl.contracts[0]
functions = contract.available_functions_as_dict()
f = functions["add(int128,int128)"]
assert f.function_type == FunctionType.NORMAL
assert f.visibility == "internal"
assert not f.payable
assert f.view is False
assert f.pure is True
assert f.parameters[0].name == "x"
assert f.parameters[0].type == ElementaryType("int128")
assert f.parameters[1].name == "y"
assert f.parameters[1].type == ElementaryType("int128")
assert f.return_type[0] == ElementaryType("int128")
f = functions["__init__()"]
assert f.function_type == FunctionType.CONSTRUCTOR
assert f.visibility == "external"
assert not f.payable
assert not f.view
assert not f.pure
assert not f.is_implemented
assert f.is_empty
f = functions["__default__()"]
assert f.function_type == FunctionType.FALLBACK
assert f.visibility == "external"
assert f.payable
assert not f.view
assert not f.pure
assert not f.is_implemented
assert f.is_empty
f = functions["withdraw()"]
assert f.function_type == FunctionType.NORMAL
assert f.visibility == "external"
assert not f.payable
assert not f.view
assert not f.pure
assert f.can_send_eth()
assert f.can_reenter()
assert f.is_implemented
assert not f.is_empty
f = functions["withdraw_locked()"]
assert not f.is_reentrant
assert f.is_implemented
assert not f.is_empty
var = contract.get_state_variable_from_name("balances")
assert var
assert var.solidity_signature == "balances(address)"
assert var.signature_str == "balances(address) returns(uint256)"
assert var.visibility == "public"
assert var.type == MappingType(ElementaryType("address"), ElementaryType("uint256"))
var = contract.get_state_variable_from_name("allowances")
assert var
assert var.solidity_signature == "allowances(address,address)"
assert var.signature_str == "allowances(address,address) returns(uint256)"
assert var.visibility == "internal"
assert var.type == MappingType(
ElementaryType("address"),
MappingType(ElementaryType("address"), ElementaryType("uint256")),
)

@ -111,3 +111,17 @@ def test_references_user_defined_types_when_casting(solc_binary_path):
assert len(a.references) == 2
lines = _sort_references_lines(a.references)
assert lines == [12, 18]
def test_references_self_identifier():
"""
Tests that shadowing state variables with local variables does not affect references.
"""
file = Path(SRC_MAPPING_TEST_ROOT, "SelfIdentifier.vy").as_posix()
slither = Slither(file)
contracts = slither.compilation_units[0].contracts
a = contracts[0].state_variables[0]
assert len(a.references) == 1
lines = _sort_references_lines(a.references)
assert lines == [4]

@ -283,7 +283,7 @@ def get_ssa_of_type(f: Union[Function, Node], ssatype) -> List[Operation]:
return get_filtered_ssa(f, lambda ssanode: isinstance(ssanode, ssatype))
def test_multi_write(slither_from_source) -> None:
def test_multi_write(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
@ -293,11 +293,11 @@ def test_multi_write(slither_from_source) -> None:
val = 3;
}
}"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_single_branch_phi(slither_from_source) -> None:
def test_single_branch_phi(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
@ -309,11 +309,11 @@ def test_single_branch_phi(slither_from_source) -> None:
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_basic_phi(slither_from_source) -> None:
def test_basic_phi(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
@ -327,11 +327,11 @@ def test_basic_phi(slither_from_source) -> None:
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_basic_loop_phi(slither_from_source) -> None:
def test_basic_loop_phi(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract Test {
@ -343,12 +343,12 @@ def test_basic_loop_phi(slither_from_source) -> None:
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_phi_propagation_loop(slither_from_source):
def test_phi_propagation_loop(slither_from_solidity_source):
source = """
pragma solidity ^0.8.11;
contract Test {
@ -365,12 +365,12 @@ def test_phi_propagation_loop(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_free_function_properties(slither_from_source):
def test_free_function_properties(slither_from_solidity_source):
source = """
pragma solidity ^0.8.11;
@ -388,11 +388,11 @@ def test_free_function_properties(slither_from_source):
contract Test {}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
verify_properties_hold(slither)
def test_ssa_inter_transactional(slither_from_source) -> None:
def test_ssa_inter_transactional(slither_from_solidity_source) -> None:
source = """
pragma solidity ^0.8.11;
contract A {
@ -412,7 +412,7 @@ def test_ssa_inter_transactional(slither_from_source) -> None:
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.contracts[0]
variables = c.variables_as_dict
funcs = c.available_functions_as_dict()
@ -435,7 +435,7 @@ def test_ssa_inter_transactional(slither_from_source) -> None:
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_ssa_phi_callbacks(slither_from_source):
def test_ssa_phi_callbacks(slither_from_solidity_source):
source = """
pragma solidity ^0.8.11;
contract A {
@ -463,7 +463,7 @@ def test_ssa_phi_callbacks(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("A")[0]
_dump_functions(c)
f = [x for x in c.functions if x.name == "use_a"][0]
@ -494,7 +494,7 @@ def test_ssa_phi_callbacks(slither_from_source):
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_storage_refers_to(slither_from_source):
def test_storage_refers_to(slither_from_solidity_source):
"""Test the storage aspects of the SSA IR
When declaring a var as being storage, start tracking what storage it refers_to.
@ -523,7 +523,7 @@ def test_storage_refers_to(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.contracts[0]
f = c.functions[0]
@ -563,7 +563,7 @@ def test_storage_refers_to(slither_from_source):
@pytest.mark.skipif(
not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform"
)
def test_initial_version_exists_for_locals(slither_from_source):
def test_initial_version_exists_for_locals(slither_from_solidity_source):
"""
In solidity you can write statements such as
uint a = a + 1, this test ensures that can be handled for local variables.
@ -575,7 +575,7 @@ def test_initial_version_exists_for_locals(slither_from_source):
}
}
"""
with slither_from_source(src, "0.4.0") as slither:
with slither_from_solidity_source(src, "0.4.0") as slither:
verify_properties_hold(slither)
c = slither.contracts[0]
f = c.functions[0]
@ -600,7 +600,7 @@ def test_initial_version_exists_for_locals(slither_from_source):
@pytest.mark.skipif(
not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform"
)
def test_initial_version_exists_for_state_variables(slither_from_source):
def test_initial_version_exists_for_state_variables(slither_from_solidity_source):
"""
In solidity you can write statements such as
uint a = a + 1, this test ensures that can be handled for state variables.
@ -610,7 +610,7 @@ def test_initial_version_exists_for_state_variables(slither_from_source):
uint a = a + 1;
}
"""
with slither_from_source(src, "0.4.0") as slither:
with slither_from_solidity_source(src, "0.4.0") as slither:
verify_properties_hold(slither)
c = slither.contracts[0]
f = c.functions[0] # There will be one artificial ctor function for the state vars
@ -637,7 +637,7 @@ def test_initial_version_exists_for_state_variables(slither_from_source):
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_initial_version_exists_for_state_variables_function_assign(slither_from_source):
def test_initial_version_exists_for_state_variables_function_assign(slither_from_solidity_source):
"""
In solidity you can write statements such as
uint a = a + 1, this test ensures that can be handled for local variables.
@ -652,7 +652,7 @@ def test_initial_version_exists_for_state_variables_function_assign(slither_from
}
}
"""
with slither_from_source(src) as slither:
with slither_from_solidity_source(src) as slither:
verify_properties_hold(slither)
c = slither.contracts[0]
f, ctor = c.functions
@ -679,7 +679,7 @@ def test_initial_version_exists_for_state_variables_function_assign(slither_from
@pytest.mark.skipif(
not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform"
)
def test_return_local_before_assign(slither_from_source):
def test_return_local_before_assign(slither_from_solidity_source):
src = """
// this require solidity < 0.5
// a variable can be returned before declared. Ensure it can be
@ -694,7 +694,7 @@ def test_return_local_before_assign(slither_from_source):
}
}
"""
with slither_from_source(src, "0.4.0") as slither:
with slither_from_solidity_source(src, "0.4.0") as slither:
f = slither.contracts[0].functions[0]
ret = get_ssa_of_type(f, Return)[0]
@ -709,7 +709,7 @@ def test_return_local_before_assign(slither_from_source):
@pytest.mark.skipif(
not valid_version("0.5.0"), reason="Solidity version 0.5.0 not available on this platform"
)
def test_shadow_local(slither_from_source):
def test_shadow_local(slither_from_solidity_source):
src = """
contract A {
// this require solidity 0.5
@ -724,7 +724,7 @@ def test_shadow_local(slither_from_source):
}
}
"""
with slither_from_source(src, "0.5.0") as slither:
with slither_from_solidity_source(src, "0.5.0") as slither:
_dump_functions(slither.contracts[0])
f = slither.contracts[0].functions[0]
@ -734,7 +734,7 @@ def test_shadow_local(slither_from_source):
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_multiple_named_args_returns(slither_from_source):
def test_multiple_named_args_returns(slither_from_solidity_source):
"""Verifies that named arguments and return values have correct versions
Each arg/ret have an initial version, version 0, and is written once and should
@ -749,7 +749,7 @@ def test_multiple_named_args_returns(slither_from_source):
ret2 = arg2 + 4;
}
}"""
with slither_from_source(src) as slither:
with slither_from_solidity_source(src) as slither:
verify_properties_hold(slither)
f = slither.contracts[0].functions[0]
@ -763,7 +763,7 @@ def test_multiple_named_args_returns(slither_from_source):
@pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True)
def test_memory_array(slither_from_source):
def test_memory_array(slither_from_solidity_source):
src = """
contract MemArray {
struct A {
@ -798,7 +798,7 @@ def test_memory_array(slither_from_source):
return arg + 1;
}
}"""
with slither_from_source(src) as slither:
with slither_from_solidity_source(src) as slither:
c = slither.contracts[0]
ftest_array, faccept, fb = c.functions
@ -829,7 +829,7 @@ def test_memory_array(slither_from_source):
@pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True)
def test_storage_array(slither_from_source):
def test_storage_array(slither_from_solidity_source):
src = """
contract StorageArray {
struct A {
@ -865,7 +865,7 @@ def test_storage_array(slither_from_source):
return value + 1;
}
}"""
with slither_from_source(src) as slither:
with slither_from_solidity_source(src) as slither:
c = slither.contracts[0]
_dump_functions(c)
ftest, faccept, fb = c.functions
@ -884,7 +884,7 @@ def test_storage_array(slither_from_source):
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_issue_468(slither_from_source):
def test_issue_468(slither_from_solidity_source):
"""
Ensure issue 468 is corrected as per
https://github.com/crytic/slither/issues/468#issuecomment-620974151
@ -905,7 +905,7 @@ def test_issue_468(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("State")[0]
f = [x for x in c.functions if x.name == "f"][0]
@ -938,7 +938,7 @@ def test_issue_468(slither_from_source):
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_issue_434(slither_from_source):
def test_issue_434(slither_from_solidity_source):
source = """
contract Contract {
int public a;
@ -956,7 +956,7 @@ def test_issue_434(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
e = [x for x in c.functions if x.name == "e"][0]
@ -992,7 +992,7 @@ def test_issue_434(slither_from_source):
@pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.")
def test_issue_473(slither_from_source):
def test_issue_473(slither_from_solidity_source):
source = """
contract Contract {
function f() public returns (int) {
@ -1007,7 +1007,7 @@ def test_issue_473(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
f = c.functions[0]
@ -1035,7 +1035,7 @@ def test_issue_473(slither_from_source):
assert second_phi.lvalue in return_value.values
def test_issue_1748(slither_from_source):
def test_issue_1748(slither_from_solidity_source):
source = """
contract Contract {
uint[] arr;
@ -1044,7 +1044,7 @@ def test_issue_1748(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
f = c.functions[0]
operations = f.slithir_operations
@ -1052,7 +1052,7 @@ def test_issue_1748(slither_from_source):
assert isinstance(assign_op, InitArray)
def test_issue_1776(slither_from_source):
def test_issue_1776(slither_from_solidity_source):
source = """
contract Contract {
function foo() public returns (uint) {
@ -1061,7 +1061,7 @@ def test_issue_1776(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
f = c.functions[0]
operations = f.slithir_operations
@ -1080,7 +1080,7 @@ def test_issue_1776(slither_from_source):
assert lvalue_type2.length_value.value == "5"
def test_issue_1846_ternary_in_if(slither_from_source):
def test_issue_1846_ternary_in_if(slither_from_solidity_source):
source = """
contract Contract {
function foo(uint x) public returns (uint y) {
@ -1092,7 +1092,7 @@ def test_issue_1846_ternary_in_if(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
f = c.functions[0]
node = f.nodes[1]
@ -1101,7 +1101,7 @@ def test_issue_1846_ternary_in_if(slither_from_source):
assert node.son_false.type == NodeType.EXPRESSION
def test_issue_1846_ternary_in_ternary(slither_from_source):
def test_issue_1846_ternary_in_ternary(slither_from_solidity_source):
source = """
contract Contract {
function foo(uint x) public returns (uint y) {
@ -1109,7 +1109,7 @@ def test_issue_1846_ternary_in_ternary(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
f = c.functions[0]
node = f.nodes[1]
@ -1118,7 +1118,7 @@ def test_issue_1846_ternary_in_ternary(slither_from_source):
assert node.son_false.type == NodeType.EXPRESSION
def test_issue_2016(slither_from_source):
def test_issue_2016(slither_from_solidity_source):
source = """
contract Contract {
function test() external {
@ -1126,7 +1126,7 @@ def test_issue_2016(slither_from_source):
}
}
"""
with slither_from_source(source) as slither:
with slither_from_solidity_source(source) as slither:
c = slither.get_contract_from_name("Contract")[0]
f = c.functions[0]
operations = f.slithir_operations

@ -0,0 +1,99 @@
# # pylint: disable=too-many-lines
from slither.core.solidity_types import ElementaryType
from slither.slithir.operations import (
Phi,
InternalCall,
)
from slither.slithir.variables import (
Constant,
)
def test_interface_conversion_and_call_resolution(slither_from_vyper_source):
with slither_from_vyper_source(
"""
interface Test:
def foo() -> (int128, uint256): nonpayable
@internal
def foo() -> (int128, int128):
return 2, 3
@external
def bar():
a: int128 = 0
b: int128 = 0
(a, b) = self.foo()
x: address = 0x0000000000000000000000000000000000000000
c: uint256 = 0
a, c = Test(x).foo()
"""
) as sl:
interface = next(iter(x for x in sl.contracts if x.is_interface))
contract = next(iter(x for x in sl.contracts if not x.is_interface))
func = contract.get_function_from_signature("bar()")
(contract, function) = func.high_level_calls[0]
assert contract == interface
assert function.signature_str == "foo() returns(int128,uint256)"
def test_phi_entry_point_internal_call(slither_from_vyper_source):
with slither_from_vyper_source(
"""
counter: uint256
@internal
def b(y: uint256):
self.counter = y
@external
def a(x: uint256):
self.b(x)
self.b(1)
"""
) as sl:
f = sl.contracts[0].get_function_from_signature("b(uint256)")
assert (
len(
[
ssanode
for node in f.nodes
for ssanode in node.irs_ssa
if isinstance(ssanode, Phi)
]
)
== 1
)
def test_call_with_default_args(slither_from_vyper_source):
with slither_from_vyper_source(
"""
counter: uint256
@internal
def c(y: uint256, config: bool = True):
self.counter = y
@external
def a(x: uint256):
self.c(x)
self.c(1)
@external
def b(x: uint256):
self.c(x, False)
self.c(1, False)
"""
) as sl:
a = sl.contracts[0].get_function_from_signature("a(uint256)")
for node in a.nodes:
for op in node.irs_ssa:
if isinstance(op, InternalCall) and op.function.name == "c":
assert len(op.arguments) == 2
assert op.arguments[1] == Constant("True", ElementaryType("bool"))
b = sl.contracts[0].get_function_from_signature("b(uint256)")
for node in b.nodes:
for op in node.irs_ssa:
if isinstance(op, InternalCall) and op.function.name == "c":
assert len(op.arguments) == 2
assert op.arguments[1] == Constant("False", ElementaryType("bool"))
Loading…
Cancel
Save