From 7b3e0645e33185c3081b26c8cfc9ff159808284f Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 17 Aug 2023 22:22:48 -0500 Subject: [PATCH 01/69] WIP --- slither/__main__.py | 10 +- slither/core/compilation_unit.py | 24 ++ slither/core/declarations/contract.py | 2 +- slither/core/declarations/function.py | 6 +- slither/core/expressions/binary_operation.py | 2 +- slither/core/expressions/identifier.py | 2 +- slither/slither.py | 35 +- .../slither_compilation_unit_solc.py | 18 +- .../visitors/slithir/expression_to_slithir.py | 1 + slither/vyper_parsing/__init__.py | 0 slither/vyper_parsing/ast/__init__.py | 0 slither/vyper_parsing/ast/ast.py | 373 ++++++++++++++++++ slither/vyper_parsing/ast/types.py | 270 +++++++++++++ slither/vyper_parsing/cfg/__init__.py | 0 slither/vyper_parsing/cfg/node.py | 66 ++++ .../vyper_parsing/declarations/__init__.py | 0 .../vyper_parsing/declarations/contract.py | 176 +++++++++ slither/vyper_parsing/declarations/event.py | 43 ++ .../vyper_parsing/declarations/function.py | 340 ++++++++++++++++ .../vyper_parsing/declarations/modifier.py | 107 +++++ slither/vyper_parsing/declarations/struct.py | 35 ++ slither/vyper_parsing/expressions/__init__.py | 0 .../expressions/expression_parsing.py | 308 +++++++++++++++ .../expressions/find_variable.py | 240 +++++++++++ slither/vyper_parsing/type_parsing.py | 55 +++ slither/vyper_parsing/variables/__init__.py | 0 .../vyper_parsing/variables/event_variable.py | 21 + .../variables/function_type_variable.py | 13 + .../vyper_parsing/variables/local_variable.py | 32 ++ .../vyper_parsing/variables/state_variable.py | 30 ++ .../variables/structure_variable.py | 20 + .../variables/variable_declaration.py | 150 +++++++ .../vyper_parsing/vyper_compilation_unit.py | 94 +++++ 33 files changed, 2438 insertions(+), 35 deletions(-) create mode 100644 slither/vyper_parsing/__init__.py create mode 100644 slither/vyper_parsing/ast/__init__.py create mode 100644 slither/vyper_parsing/ast/ast.py create mode 100644 slither/vyper_parsing/ast/types.py create mode 100644 slither/vyper_parsing/cfg/__init__.py create mode 100644 slither/vyper_parsing/cfg/node.py create mode 100644 slither/vyper_parsing/declarations/__init__.py create mode 100644 slither/vyper_parsing/declarations/contract.py create mode 100644 slither/vyper_parsing/declarations/event.py create mode 100644 slither/vyper_parsing/declarations/function.py create mode 100644 slither/vyper_parsing/declarations/modifier.py create mode 100644 slither/vyper_parsing/declarations/struct.py create mode 100644 slither/vyper_parsing/expressions/__init__.py create mode 100644 slither/vyper_parsing/expressions/expression_parsing.py create mode 100644 slither/vyper_parsing/expressions/find_variable.py create mode 100644 slither/vyper_parsing/type_parsing.py create mode 100644 slither/vyper_parsing/variables/__init__.py create mode 100644 slither/vyper_parsing/variables/event_variable.py create mode 100644 slither/vyper_parsing/variables/function_type_variable.py create mode 100644 slither/vyper_parsing/variables/local_variable.py create mode 100644 slither/vyper_parsing/variables/state_variable.py create mode 100644 slither/vyper_parsing/variables/structure_variable.py create mode 100644 slither/vyper_parsing/variables/variable_declaration.py create mode 100644 slither/vyper_parsing/vyper_compilation_unit.py diff --git a/slither/__main__.py b/slither/__main__.py index d9201a90d9..e68f0b67f1 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -870,11 +870,11 @@ 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) + # 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: diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index 6d24786eb1..8e801ea953 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -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 @@ 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 + elif label == "vyper": + return Language.VYPER + else: + 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 @@ def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit 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,13 @@ def source_units(self) -> Dict[int, str]: # region Compiler ################################################################################### ################################################################################### + @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: @@ -259,6 +282,7 @@ def get_scope(self, filename_str: str) -> FileScope: ################################################################################### def compute_storage_layout(self) -> None: + assert self.is_solidity for contract in self.contracts_derived: self._storage_layouts[contract.name] = {} diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 9b1488db31..5e3ab4571e 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -136,7 +136,7 @@ def name(self, name: str) -> None: @property def id(self) -> int: """Unique id.""" - assert self._id + assert self._id is not None return self._id @id.setter diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index e778019617..d549c96159 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -106,7 +106,7 @@ def _filter_state_variables_written(expressions: List["Expression"]): ret.append(expression.expression_left) return ret - +#TODO replace class FunctionLanguage(Enum): Solidity = 0 Yul = 1 @@ -1521,6 +1521,7 @@ def is_reentrant(self) -> bool: def _analyze_read_write(self) -> None: """Compute variables read/written/...""" write_var = [x.variables_written_as_expression for x in self.nodes] + print(write_var) write_var = [x for x in write_var if x] write_var = [item for sublist in write_var for item in sublist] write_var = list(set(write_var)) @@ -1756,6 +1757,9 @@ def fix_phi( node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)] def generate_slithir_and_analyze(self) -> None: + print("generate_slithir_and_analyze") + print(self.nodes) + for node in self.nodes: node.slithir_generation() diff --git a/slither/core/expressions/binary_operation.py b/slither/core/expressions/binary_operation.py index a395d07cf8..271975e775 100644 --- a/slither/core/expressions/binary_operation.py +++ b/slither/core/expressions/binary_operation.py @@ -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 diff --git a/slither/core/expressions/identifier.py b/slither/core/expressions/identifier.py index 8ffabad894..d4c4261008 100644 --- a/slither/core/expressions/identifier.py +++ b/slither/core/expressions/identifier.py @@ -26,7 +26,7 @@ def __init__( ], ) -> None: super().__init__() - + assert value # pylint: disable=import-outside-toplevel from slither.core.declarations import Contract, SolidityVariable, SolidityFunction from slither.solc_parsing.yul.evm_functions import YulBuiltin diff --git a/slither/slither.py b/slither/slither.py index 85f852e1d5..07444c72f8 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -11,6 +11,7 @@ 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 logger = logging.getLogger("Slither") @@ -62,16 +63,6 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None: 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 +99,25 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None: 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(): + from slither.vyper_parsing.ast.ast import parse + + 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 diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index 00ac3a5192..cc61deb1f5 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -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 @@ def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: 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 @@ def parse_top_level_from_json(self, json_data: str) -> bool: 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 @@ def parse_top_level_from_json(self, json_data: str) -> bool: 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 @@ def _parse_enum(self, top_level_data: Dict, filename: str) -> None: 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 @@ def analyzed(self) -> bool: 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!") diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 005ad81a44..a99a6af863 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -434,6 +434,7 @@ def _post_index_access(self, expression: IndexAccess) -> None: def _post_literal(self, expression: Literal) -> None: expression_type = expression.type assert isinstance(expression_type, ElementaryType) + print(expression.value) cst = Constant(expression.value, expression_type, expression.subdenomination) set_val(expression, cst) diff --git a/slither/vyper_parsing/__init__.py b/slither/vyper_parsing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/vyper_parsing/ast/__init__.py b/slither/vyper_parsing/ast/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py new file mode 100644 index 0000000000..b0a687528a --- /dev/null +++ b/slither/vyper_parsing/ast/ast.py @@ -0,0 +1,373 @@ +from typing import Dict, Callable, List +from slither.vyper_parsing.ast.types import ASTNode +from slither.vyper_parsing.ast.types import * + + +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)) + + +def parse_unary_op(raw: Dict) -> UnaryOp: + return UnaryOp(op=raw["op"], operand=parse(raw["operand"]), **_extract_base_props(raw)) + +binop_ast_type_to_op_symbol = {"Add": "+", "Mult": "*", "Sub": "-", "Div": "-", "Pow": "**", "Mod": "%", "BitAnd": "&", "BitOr": "|", "Shr": "<<", "Shl": ">>"} + +def parse_bin_op(raw: Dict) -> BinOp: + op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]] + return BinOp( + left=parse(raw["left"]), op=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_compare(raw: Dict) -> Compare: + return Compare( + left=parse(raw["left"]), op=raw["op"], right=parse(raw["right"]), **_extract_base_props(raw) + ) + + +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)) + + +def parse_aug_assign(raw: Dict) -> AugAssign: + return AugAssign( + target=parse(raw["target"]), + op=raw["op"], + 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) + + +def parse_bool_op(raw: Dict) -> BoolOp: + return BoolOp( + op=raw["op"], 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, +} diff --git a/slither/vyper_parsing/ast/types.py b/slither/vyper_parsing/ast/types.py new file mode 100644 index 0000000000..619df4f147 --- /dev/null +++ b/slither/vyper_parsing/ast/types.py @@ -0,0 +1,270 @@ +from __future__ import annotations +from typing import List, Optional, Dict, Union, ForwardRef +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 + + +# TODO CONSTANT? + + +@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 + + +from enum import Enum + + +@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] diff --git a/slither/vyper_parsing/cfg/__init__.py b/slither/vyper_parsing/cfg/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/vyper_parsing/cfg/node.py b/slither/vyper_parsing/cfg/node.py new file mode 100644 index 0000000000..cf8a8f160f --- /dev/null +++ b/slither/vyper_parsing/cfg/node.py @@ -0,0 +1,66 @@ +from typing import Union, Optional, Dict, TYPE_CHECKING + +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) + ] diff --git a/slither/vyper_parsing/declarations/__init__.py b/slither/vyper_parsing/declarations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py new file mode 100644 index 0000000000..3a01a22f7f --- /dev/null +++ b/slither/vyper_parsing/declarations/contract.py @@ -0,0 +1,176 @@ +import logging +from typing import List, TYPE_CHECKING +from slither.vyper_parsing.ast.types import ( + Module, + FunctionDef, + EventDef, + EnumDef, + StructDef, + VariableDecl, + ImportFrom, + InterfaceDef, + AnnAssign, +) + +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: + def __init__( + self, slither_parser: "VyperCompilationUnit", contract: Contract, module: Module + ) -> None: + + self._contract: Contract = contract + self._slither_parser: "VyperCompilationUnit" = slither_parser + self._data = module + self._contract.name = module.name + 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): + # https://github.com/vyperlang/vyper/tree/master/vyper/builtins/interfaces + if node.module == "vyper.interfaces": + # TODO add functions + contract = Contract(self._contract.compilation_unit, self._contract.file_scope) + contract.set_offset("-1:-1:-1", self._contract.compilation_unit) + + contract.name = node.name + contract.is_interface = True + self._contract.file_scope.contracts[contract.name] = contract + + elif isinstance(node, InterfaceDef): + contract = Contract(self._contract.compilation_unit, self._contract.file_scope) + contract_parser = ContractVyper(self._slither_parser, contract, node) + contract.set_offset(node.src, self._contract.compilation_unit) + self._contract.file_scope.contracts[contract.name] = contract + 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) + + self._structuresNotParsed = [] + + def parse_state_variables(self) -> None: + for varNotParsed in self._variablesNotParsed: + print(varNotParsed) + 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]) + + 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._contract.add_function(func) + self._functions_parser.append(func_parser) + + self._functionsNotParsed = [] + + def analyze(self) -> None: + for p in self._structures_parser: + p.analyze(self._contract) + for p in self._variables_parser: + p.analyze(self._contract) + for p in self._events_parser: + p.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 diff --git a/slither/vyper_parsing/declarations/event.py b/slither/vyper_parsing/declarations/event.py new file mode 100644 index 0000000000..a0152ec0ec --- /dev/null +++ b/slither/vyper_parsing/declarations/event.py @@ -0,0 +1,43 @@ +""" + Event module +""" +from typing import TYPE_CHECKING, Dict + +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: + """ + 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 + print(event_def) + # assert False + # self.analyze() # TODO create `VariableDecl` from `AnnAssign` from `event_def.body` (also for `StructDef`) + + 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 = [] diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py new file mode 100644 index 0000000000..b652efc5fc --- /dev/null +++ b/slither/vyper_parsing/declarations/function.py @@ -0,0 +1,340 @@ +import logging +from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple, Set + +from slither.core.cfg.node import NodeType, link_nodes, insert_node, Node +from slither.core.cfg.scope import Scope +from slither.core.declarations.contract import Contract +from slither.core.declarations.function import ( + Function, + FunctionType, +) +from slither.core.declarations.function_contract import FunctionContract +from slither.core.expressions import AssignmentOperation +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 * + +if TYPE_CHECKING: + from slither.core.compilation_unit import SlitherCompilationUnit + + +def link_underlying_nodes(node1: NodeVyper, node2: NodeVyper): + link_nodes(node1.underlying_node, node2.underlying_node) + + +class FunctionVyper: + def __init__( + self, + function: Function, + function_data: Dict, + ) -> None: + self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} + + self._function = function + print(function_data.name) + print(function_data) + self._function.name = function_data.name + self._function.id = function_data.node_id + + self._local_variables_parser: List = [] + + for decorator in function_data.decorators: + if not hasattr(decorator, "id"): + continue # TODO isinstance 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 + 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._functionNotParsed = function_data + self._params_was_analyzed = False + self._content_was_analyzed = False + + # self._counter_scope_local_variables = 0 + # # variable renamed will map the solc id + # # to the variable. It only works for compact format + # # Later if an expression provides the referencedDeclaration attr + # # we can retrieve the variable + # # It only matters if two variables have the same name in the function + # # which is only possible with solc > 0.5 + # self._variables_renamed: Dict[ + # int, Union[LocalVariableVyper, LocalVariableInitFromTupleSolc] + # ] = {} + + self._analyze_function_type() + + # self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} + # self._node_to_yulobject: Dict[Node, YulBlock] = {} + + # self._local_variables_parser: List[ + # Union[LocalVariableVyper, LocalVariableInitFromTupleSolc] + # ] = [] + + if function_data.doc_string is not None: + function.has_documentation = True + + @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: + # If two local variables have the same name + # We add a suffix to the new variable + # This is done to prevent collision during SSA translation + # Use of while in case of collision + # In the worst case, the name will be really long + # TODO no shadowing? + # 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 + + print(self._functionNotParsed) + if body: + self._function.is_implemented = True + self._parse_cfg(body) + + for local_var_parser in self._local_variables_parser: + local_var_parser.analyze(self._function.contract) + + for node_parser in self._node_to_NodeVyper.values(): + node_parser.analyze_expressions(self._function.contract) + + # 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 + ################################################################################### + ################################################################################### + + def _parse_cfg(self, cfg: Dict) -> None: + + + curr_node = self._new_node(NodeType.ENTRYPOINT, "-1:-1:-1", self.underlying_function) + self._function.entry_point = curr_node.underlying_node + scope = None + + if cfg: + self._function.is_empty = False + for expr in cfg: + def parse_statement(curr_node, expr): + 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: + 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, (Assign, AugAssign)): + new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + new_node.add_unparsed_expression(expr.value) + link_underlying_nodes(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) + + node_condition = self._new_node(NodeType.IFLOOP, expr.iter.src, scope) + node_condition.add_unparsed_expression(expr.iter) + # link_underlying_nodes(node_startLoop, node_condition) + for stmt in expr.body: + parse_statement(curr_node, stmt) + + # link_underlying_nodes(curr_node, new_node) + + elif isinstance(expr, Continue): + pass + elif isinstance(expr, Break): + pass + elif isinstance(expr, Return): + node_parser = self._new_node(NodeType.RETURN, expr.src, scope) + if expr.value is not None: + node_parser.add_unparsed_expression(expr.value) + + pass + elif isinstance(expr, Assert): + print(expr) + assert False + pass + elif isinstance(expr, Log): + new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + new_node.add_unparsed_expression(expr.value) + pass + elif isinstance(expr, If): + new_node = self._new_node(NodeType.IF, expr.test.src, scope) + new_node.add_unparsed_expression(expr.test) + + for stmt in expr.body: + parse_statement(new_node, stmt) + + for stmt in expr.orelse: + parse_statement(new_node, stmt) + + pass + elif isinstance(expr, Expr): + pass + elif isinstance(expr, Pass): + pass + elif isinstance(expr, Raise): + print(expr) + assert False + pass + else: + print(f"isinstance(expr, {expr.__class__.__name__})") + assert False + return curr_node + curr_node = parse_statement(curr_node, expr) + # self._parse_block(cfg, node, self.underlying_function) + else: + self._function.is_empty = True + + # 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) + print("add_param", param) + local_var_parser = LocalVariableVyper(local_var, param) + + if initialized: + local_var.initialized = True + + # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location + 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): + + print(params) + self._function.parameters_src().set_offset(params.src, self._function.compilation_unit) + + 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, Tuple, Subscript]): + + print(returns) + self._function.returns_src().set_offset(returns.src, self._function.compilation_unit) + + if isinstance(returns, (Name, Subscript)): + local_var = self._add_param(returns) + self._function.add_return(local_var.underlying_variable) + else: + assert isinstance(returns, Tuple) + for ret in returns.elements: + local_var = self._add_param(ret) + self._function.add_return(local_var.underlying_variable) + + ################################################################################### + ################################################################################### diff --git a/slither/vyper_parsing/declarations/modifier.py b/slither/vyper_parsing/declarations/modifier.py new file mode 100644 index 0000000000..c4c5c71772 --- /dev/null +++ b/slither/vyper_parsing/declarations/modifier.py @@ -0,0 +1,107 @@ +""" + Event module +""" +from typing import Dict, TYPE_CHECKING, Union + +from slither.core.cfg.node import NodeType +from slither.core.cfg.node import link_nodes +from slither.core.cfg.scope import Scope +from slither.core.declarations.modifier import Modifier +from slither.solc_parsing.cfg.node import NodeSolc +from slither.solc_parsing.declarations.function import FunctionSolc + +if TYPE_CHECKING: + from slither.solc_parsing.declarations.contract import ContractSolc + from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc + from slither.core.declarations import Function + + +class ModifierSolc(FunctionSolc): + def __init__( + self, + modifier: Modifier, + function_data: Dict, + contract_parser: "ContractSolc", + slither_parser: "SlitherCompilationUnitSolc", + ) -> None: + super().__init__(modifier, function_data, contract_parser, slither_parser) + # _modifier is equal to _function, but keep it here to prevent + # confusion for mypy in underlying_function + self._modifier = modifier + + @property + def underlying_function(self) -> Modifier: + return self._modifier + + def analyze_params(self) -> None: + # Can be re-analyzed due to inheritance + if self._params_was_analyzed: + return + + self._params_was_analyzed = True + + self._analyze_attributes() + + if self.is_compact_ast: + params = self._functionNotParsed["parameters"] + else: + children = self._functionNotParsed["children"] + # It uses to be + # params = children[0] + # But from Solidity 0.6.3 to 0.6.10 (included) + # Comment above a function might be added in the children + params = next(child for child in children if child[self.get_key()] == "ParameterList") + + if params: + self._parse_params(params) + + def analyze_content(self) -> None: + if self._content_was_analyzed: + return + + self._content_was_analyzed = True + + if self.is_compact_ast: + body = self._functionNotParsed.get("body", None) + + if body and body[self.get_key()] == "Block": + self._function.is_implemented = True + self._parse_cfg(body) + + else: + children = self._functionNotParsed["children"] + + self._function.is_implemented = False + if len(children) > 1: + # It uses to be + # params = children[1] + # But from Solidity 0.6.3 to 0.6.10 (included) + # Comment above a function might be added in the children + block = next(child for child in children if child[self.get_key()] == "Block") + self._function.is_implemented = True + self._parse_cfg(block) + + for local_var_parser in self._local_variables_parser: + local_var_parser.analyze(self) + + for node in self._node_to_nodesolc.values(): + node.analyze_expressions(self) + + for yul_parser in self._node_to_yulobject.values(): + yul_parser.analyze_expressions() + + self._rewrite_ternary_as_if_else() + self._remove_alone_endif() + + # self._analyze_read_write() + # self._analyze_calls() + + def _parse_statement( + self, statement: Dict, node: NodeSolc, scope: Union[Scope, "Function"] + ) -> NodeSolc: + name = statement[self.get_key()] + if name == "PlaceholderStatement": + placeholder_node = self._new_node(NodeType.PLACEHOLDER, statement["src"], scope) + link_nodes(node.underlying_node, placeholder_node.underlying_node) + return placeholder_node + return super()._parse_statement(statement, node, scope) diff --git a/slither/vyper_parsing/declarations/struct.py b/slither/vyper_parsing/declarations/struct.py new file mode 100644 index 0000000000..0da2c7fed0 --- /dev/null +++ b/slither/vyper_parsing/declarations/struct.py @@ -0,0 +1,35 @@ +from typing import TYPE_CHECKING, Dict, 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: + def __init__( # pylint: disable=too-many-arguments + self, + st: Structure, + struct: StructDef, + ) -> None: + + print(struct) + + self._structure = st + st.name = struct.name + # st.canonical_name = canonicalName + + 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 = [] diff --git a/slither/vyper_parsing/expressions/__init__.py b/slither/vyper_parsing/expressions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py new file mode 100644 index 0000000000..e745c2f7b4 --- /dev/null +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -0,0 +1,308 @@ +import logging +import re +from typing import Union, Dict, TYPE_CHECKING, List, Any + +import slither.core.expressions.type_conversion +from slither.core.declarations.solidity_variables import ( + SOLIDITY_VARIABLES_COMPOSED, + SolidityVariableComposed, +) +from slither.core.expressions import ( + CallExpression, + ConditionalExpression, + ElementaryTypeNameExpression, + Identifier, + IndexAccess, + Literal, + MemberAccess, + NewArray, + NewContract, + NewElementaryType, + SuperCallExpression, + SuperIdentifier, + 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.solc_parsing.declarations.caller_context import CallerContextExpression +from slither.solc_parsing.exceptions import ParsingError, VariableNotFound +from slither.vyper_parsing.expressions.find_variable import find_variable +from slither.solc_parsing.solidity_types.type_parsing import UnknownType, parse_type + + +if TYPE_CHECKING: + from slither.core.expressions.expression import Expression + +logger = logging.getLogger("ExpressionParsing") + +# pylint: disable=anomalous-backslash-in-string,import-outside-toplevel,too-many-branches,too-many-locals + +# region Filtering +################################################################################### +################################################################################### + + +def filter_name(value: str) -> str: + value = value.replace(" memory", "") + value = value.replace(" storage", "") + value = value.replace(" external", "") + value = value.replace(" internal", "") + value = value.replace("struct ", "") + value = value.replace("contract ", "") + value = value.replace("enum ", "") + value = value.replace(" ref", "") + value = value.replace(" pointer", "") + value = value.replace(" pure", "") + value = value.replace(" view", "") + value = value.replace(" constant", "") + value = value.replace(" payable", "") + value = value.replace("function (", "function(") + value = value.replace("returns (", "returns(") + value = value.replace(" calldata", "") + + # remove the text remaining after functio(...) + # which should only be ..returns(...) + # nested parenthesis so we use a system of counter on parenthesis + idx = value.find("(") + if idx: + counter = 1 + max_idx = len(value) + while counter: + assert idx < max_idx + idx = idx + 1 + if value[idx] == "(": + counter += 1 + elif value[idx] == ")": + counter -= 1 + value = value[: idx + 1] + return value + + +# endregion + +################################################################################### +################################################################################### +# region Parsing +################################################################################### +################################################################################### + +# pylint: disable=too-many-statements +def parse_call( + expression: Dict, caller_context +) -> Union[ + slither.core.expressions.call_expression.CallExpression, + slither.core.expressions.type_conversion.TypeConversion, +]: + src = expression["src"] + if caller_context.is_compact_ast: + attributes = expression + type_conversion = expression["kind"] == "typeConversion" + type_return = attributes["typeDescriptions"]["typeString"] + + else: + attributes = expression["attributes"] + type_conversion = attributes["type_conversion"] + type_return = attributes["type"] + + if type_conversion: + type_call = parse_type(UnknownType(type_return), caller_context) + if caller_context.is_compact_ast: + assert len(expression["arguments"]) == 1 + expression_to_parse = expression["arguments"][0] + else: + children = expression["children"] + assert len(children) == 2 + type_info = children[0] + expression_to_parse = children[1] + assert type_info["name"] in [ + "ElementaryTypenameExpression", + "ElementaryTypeNameExpression", + "Identifier", + "TupleExpression", + "IndexAccess", + "MemberAccess", + ] + + expression = parse_expression(expression_to_parse, caller_context) + t = TypeConversion(expression, type_call) + t.set_offset(src, caller_context.compilation_unit) + if isinstance(type_call, UserDefinedType): + type_call.type.references.append(t.source_mapping) + return t + + call_gas = None + call_value = None + call_salt = None + if caller_context.is_compact_ast: + called = parse_expression(expression["expression"], caller_context) + # If the next expression is a FunctionCallOptions + # We can here the gas/value information + # This is only available if the syntax is {gas: , value: } + # For the .gas().value(), the member are considered as function call + # And converted later to the correct info (convert.py) + if expression["expression"][caller_context.get_key()] == "FunctionCallOptions": + call_with_options = expression["expression"] + for idx, name in enumerate(call_with_options.get("names", [])): + option = parse_expression(call_with_options["options"][idx], caller_context) + if name == "value": + call_value = option + if name == "gas": + call_gas = option + if name == "salt": + call_salt = option + arguments = [] + if expression["arguments"]: + arguments = [parse_expression(a, caller_context) for a in expression["arguments"]] + else: + children = expression["children"] + called = parse_expression(children[0], caller_context) + arguments = [parse_expression(a, caller_context) for a in children[1::]] + + if isinstance(called, SuperCallExpression): + sp = SuperCallExpression(called, arguments, type_return) + sp.set_offset(expression["src"], caller_context.compilation_unit) + return sp + call_expression = CallExpression(called, arguments, type_return) + call_expression.set_offset(src, caller_context.compilation_unit) + + # Only available if the syntax {gas:, value:} was used + call_expression.call_gas = call_gas + call_expression.call_value = call_value + call_expression.call_salt = call_salt + return call_expression + + +def parse_super_name(expression: Dict, is_compact_ast: bool) -> str: + if is_compact_ast: + assert expression["nodeType"] == "MemberAccess" + base_name = expression["memberName"] + arguments = expression["typeDescriptions"]["typeString"] + else: + assert expression["name"] == "MemberAccess" + attributes = expression["attributes"] + base_name = attributes["member_name"] + arguments = attributes["type"] + + assert arguments.startswith("function ") + # remove function (...() + arguments = arguments[len("function ") :] + + arguments = filter_name(arguments) + if " " in arguments: + arguments = arguments[: arguments.find(" ")] + + return base_name + arguments + + +def _parse_elementary_type_name_expression( + expression: Dict, is_compact_ast: bool, caller_context: CallerContextExpression +) -> ElementaryTypeNameExpression: + # nop exression + # uint; + if is_compact_ast: + value = expression["typeName"] + else: + if "children" in expression: + value = expression["children"][0]["attributes"]["name"] + else: + value = expression["attributes"]["value"] + if isinstance(value, dict): + t = parse_type(value, caller_context) + else: + t = parse_type(UnknownType(value), caller_context) + e = ElementaryTypeNameExpression(t) + e.set_offset(expression["src"], caller_context.compilation_unit) + return e + + +if TYPE_CHECKING: + pass + + +def _user_defined_op_call( + caller_context: CallerContextExpression, src, function_id: int, args: List[Any], type_call: str +) -> CallExpression: + var, was_created = find_variable(None, caller_context, function_id) + + if was_created: + var.set_offset(src, caller_context.compilation_unit) + + identifier = Identifier(var) + identifier.set_offset(src, caller_context.compilation_unit) + + var.references.append(identifier.source_mapping) + + call = CallExpression(identifier, args, type_call) + call.set_offset(src, caller_context.compilation_unit) + return call + + +from slither.vyper_parsing.ast.types import Int, Call, Attribute, Name, Tuple, Hex, BinOp, Str + +def parse_expression(expression: Dict, caller_context) -> "Expression": + print("parse_expression") + print(expression, "\n") + # assert False + + if isinstance(expression, Int): + return Literal(str(expression.value), ElementaryType("uint256")) + + if isinstance(expression, Hex): + # TODO this is an implicit conversion and could potentially be bytes20 or other? + return Literal(str(expression.value), ElementaryType("address")) + + if isinstance(expression, Str): + return Literal(str(expression.value), ElementaryType("string")) + + + + if isinstance(expression, Call): + called = parse_expression(expression.func, caller_context) + arguments = [parse_expression(a, caller_context) for a in expression.args] + # Since the AST lacks the type of the return values, we recover it. + rets = called.value.returns + def get_type_str(x): + return str(x.type) + type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" + print(type_str) + + + return CallExpression(called, arguments, type_str) + + if isinstance(expression, Attribute): + var, was_created = find_variable(expression.attr, caller_context) + assert var + return Identifier(var) + + if isinstance(expression, Name): + var, was_created = find_variable(expression.id, caller_context) + assert var + return Identifier(var) + + if isinstance(expression, Tuple): + tuple_vars = [parse_expression(x, caller_context) for x in expression.elements] + return TupleExpression(tuple_vars) + + if isinstance(expression, BinOp): + lhs = parse_expression(expression.left, caller_context) + rhs = parse_expression(expression.right, caller_context) + + op = BinaryOperationType.get_type(expression.op) + return BinaryOperation(lhs, rhs, op) + + + raise ParsingError(f"Expression not parsed {expression}") diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py new file mode 100644 index 0000000000..9037af110c --- /dev/null +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -0,0 +1,240 @@ +from typing import TYPE_CHECKING, Optional, Union, List, 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.function_top_level import FunctionTopLevel +from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder +from slither.core.declarations.solidity_variables import ( + SOLIDITY_FUNCTIONS, + SOLIDITY_VARIABLES, + SolidityFunction, + SolidityVariable, +) +from slither.core.scope.scope import FileScope +from slither.core.solidity_types import ( + ArrayType, + FunctionType, + MappingType, + TypeAlias, +) +from slither.core.variables.top_level_variable import TopLevelVariable +from slither.core.variables.variable import Variable +from slither.exceptions import SlitherError +from slither.solc_parsing.declarations.caller_context import CallerContextExpression +from slither.solc_parsing.exceptions import VariableNotFound + +if TYPE_CHECKING: + from slither.solc_parsing.declarations.function import FunctionSolc + from slither.solc_parsing.declarations.contract import ContractSolc + +# pylint: disable=import-outside-toplevel,too-many-branches,too-many-locals + + +# CallerContext =Union["ContractSolc", "FunctionSolc", "CustomErrorSolc", "StructureTopLevelSolc"] + + + +def _find_variable_in_function_parser( + var_name: str, + function_parser: Optional["FunctionSolc"], +) -> Optional[Variable]: + if function_parser is None: + return None + func_variables = function_parser.underlying_function.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 + print(contract) + contract_variables = contract.variables_as_dict + print(contract_variables) + 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} + print(functions) + 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] + + # Note: contract.custom_errors_as_dict uses the name (not the sol sig) as key + # This is because when the dic is populated the underlying object is not yet parsed + # As a result, we need to iterate over all the custom errors here instead of using the dict + custom_errors = contract.custom_errors + try: + for custom_error in custom_errors: + if var_name in [custom_error.solidity_signature, custom_error.full_name]: + return custom_error + except ValueError: + # This can happen as custom error sol signature might not have been built + # when find_variable was called + # TODO refactor find_variable to prevent this from happening + pass + + # If the enum is refered 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_init( + caller_context: CallerContextExpression, +) -> Tuple[List[Contract], List["Function"], FileScope,]: + from slither.vyper_parsing.declarations.contract import ContractVyper + from slither.vyper_parsing.declarations.function import FunctionVyper + + + direct_contracts: List[Contract] + direct_functions_parser: List[Function] + scope: FileScope + + if isinstance(caller_context, FileScope): + direct_contracts = [] + direct_functions_parser = [] + scope = caller_context + elif isinstance(caller_context, ContractVyper): + direct_contracts = [caller_context.underlying_contract] + direct_functions_parser = [ + f.underlying_function + for f in caller_context.functions_parser + caller_context.modifiers_parser + ] + scope = caller_context.underlying_contract.file_scope + elif isinstance(caller_context, FunctionVyper): + + direct_contracts = [caller_context.underlying_contract] + direct_functions_parser = [ + f.underlying_function + for f in caller_context.functions_parser + ] + + + scope = contract.file_scope + else: + raise SlitherError( + f"{type(caller_context)} ({caller_context} is not valid for find_variable" + ) + + return direct_contracts, direct_functions_parser, scope + + +def find_variable( + var_name: str, + caller_context: CallerContextExpression, +) -> Tuple[ + Union[ + Variable, + Function, + Contract, + SolidityVariable, + SolidityFunction, + Event, + Enum, + Structure, + CustomError, + TypeAlias, + ], + bool, +]: + """ + 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 referenced_declaration: + :return: + :rtype: + """ + + # variable are looked from the contract declarer + # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer + # the difference between function and variable come from the fact that an internal call, or an variable access + # in a function does not behave similariy, for example in: + # contract C{ + # function f(){ + # state_var = 1 + # f2() + # } + # state_var will refer to C.state_var, no mater if C is inherited + # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or + # the contract inheriting from C) + # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact + # structure/enums cannot be shadowed + + direct_contracts = [caller_context] + direct_functions = caller_context.functions_declared + print(direct_functions) + current_scope = caller_context.file_scope + + # Only look for reference declaration in the direct contract, see comment at the end + # Reference looked are split between direct and all + # Because functions are copied between contracts, two functions can have the same ref + # So we need to first look with respect to the direct context + + from slither.vyper_parsing.declarations.contract import ContractVyper + from slither.vyper_parsing.declarations.function import FunctionVyper + function_parser: Optional[FunctionSolc] = ( + caller_context if isinstance(caller_context, FunctionVyper) else None + ) + ret1 = _find_variable_in_function_parser(var_name, function_parser) + if ret1: + return ret1, False + + ret = _find_in_contract(var_name, caller_context, caller_context) + if ret: + return ret, False + + # 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], False + + contracts = current_scope.contracts + if var_name in contracts: + return contracts[var_name], False + + if var_name in SOLIDITY_VARIABLES: + return SolidityVariable(var_name), False + + if var_name in SOLIDITY_FUNCTIONS: + return SolidityFunction(var_name), False + + + + raise VariableNotFound(f"Variable not found: {var_name} (context {caller_context})") diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py new file mode 100644 index 0000000000..e9a8555671 --- /dev/null +++ b/slither/vyper_parsing/type_parsing.py @@ -0,0 +1,55 @@ +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.vyper_parsing.expressions.expression_parsing import parse_expression +from slither.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple +from typing import Union +from slither.core.solidity_types.user_defined_type import UserDefinedType + + +def parse_type(annotation: Union[Name, Subscript, Call], contract): + assert isinstance(annotation, (Name, Subscript, Call)) + print(annotation) + if isinstance(annotation, Name): + name = annotation.id + elif isinstance(annotation, Subscript): + assert isinstance(annotation.slice, Index) + + # This is also a strange construct... + if isinstance(annotation.slice.value, Tuple): + type_ = parse_type(annotation.slice.value.elements[0], contract) + length = parse_expression(annotation.slice.value.elements[1], contract) + else: + # 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 + type_ = parse_type(annotation.value, contract) + length = parse_expression(annotation.slice.value, contract) + + # TODO this can also me `HashMaps` + return ArrayType(type_, length) + + elif isinstance(annotation, Call): + return parse_type(annotation.args[0], contract) + + else: + assert False + + lname = name.lower() # todo map String to string + if lname in ElementaryTypeName: + return ElementaryType(lname) + + print(contract.structures_as_dict) + + if name in contract.structures_as_dict: + return UserDefinedType(contract.structures_as_dict[name]) + + print(contract.enums_as_dict) + if name in contract.enums_as_dict: + return UserDefinedType(contract.enums_as_dict[name]) + + print(contract.file_scope.contracts) + if name in contract.file_scope.contracts: + return UserDefinedType(contract.file_scope.contracts[name]) + assert False diff --git a/slither/vyper_parsing/variables/__init__.py b/slither/vyper_parsing/variables/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/vyper_parsing/variables/event_variable.py b/slither/vyper_parsing/variables/event_variable.py new file mode 100644 index 0000000000..10b8aa06cc --- /dev/null +++ b/slither/vyper_parsing/variables/event_variable.py @@ -0,0 +1,21 @@ +from typing import Dict + +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 + + +class EventVariableVyper: + def __init__(self, variable: EventVariable, variable_data: AnnAssign): + print(variable_data) + self._variable = variable + self._variable.name = variable_data.target.id + # TODO self._variable.indexed + 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) diff --git a/slither/vyper_parsing/variables/function_type_variable.py b/slither/vyper_parsing/variables/function_type_variable.py new file mode 100644 index 0000000000..309f4d8b7b --- /dev/null +++ b/slither/vyper_parsing/variables/function_type_variable.py @@ -0,0 +1,13 @@ +from typing import Dict + +from slither.solc_parsing.variables.variable_declaration import VariableDeclarationSolc +from slither.core.variables.function_type_variable import FunctionTypeVariable + + +class FunctionTypeVariableSolc(VariableDeclarationSolc): + def __init__(self, variable: FunctionTypeVariable, variable_data: Dict): + super().__init__(variable, variable_data) + + @property + def underlying_variable(self) -> FunctionTypeVariable: + return self._variable diff --git a/slither/vyper_parsing/variables/local_variable.py b/slither/vyper_parsing/variables/local_variable.py new file mode 100644 index 0000000000..45eb55351a --- /dev/null +++ b/slither/vyper_parsing/variables/local_variable.py @@ -0,0 +1,32 @@ +from typing import Union + +from slither.core.variables.local_variable import LocalVariable +from slither.vyper_parsing.ast.types import Arg, Name, AnnAssign, Subscript, Call +from slither.vyper_parsing.type_parsing import parse_type + + +class LocalVariableVyper: + def __init__(self, variable: LocalVariable, variable_data: Union[Arg, 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: + self._variable.name = "" + self._elem_to_parse = variable_data + + assert isinstance(self._elem_to_parse, (Name, Subscript, Call)) + + self._variable.set_location("default") + + + @property + def underlying_variable(self) -> LocalVariable: + return self._variable + + def analyze(self, contract) -> None: + self._variable.type = parse_type(self._elem_to_parse, contract) diff --git a/slither/vyper_parsing/variables/state_variable.py b/slither/vyper_parsing/variables/state_variable.py new file mode 100644 index 0000000000..f17a4132ef --- /dev/null +++ b/slither/vyper_parsing/variables/state_variable.py @@ -0,0 +1,30 @@ +from typing import Dict + +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 diff --git a/slither/vyper_parsing/variables/structure_variable.py b/slither/vyper_parsing/variables/structure_variable.py new file mode 100644 index 0000000000..eab7a71c4e --- /dev/null +++ b/slither/vyper_parsing/variables/structure_variable.py @@ -0,0 +1,20 @@ +from typing import Dict + + +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) diff --git a/slither/vyper_parsing/variables/variable_declaration.py b/slither/vyper_parsing/variables/variable_declaration.py new file mode 100644 index 0000000000..64878163cb --- /dev/null +++ b/slither/vyper_parsing/variables/variable_declaration.py @@ -0,0 +1,150 @@ +import logging +import re +from typing import Dict, Optional, Union + + +from slither.core.variables.variable import Variable +from slither.core.solidity_types.elementary_type import ( + ElementaryType, + NonElementaryType, +) +from slither.solc_parsing.exceptions import ParsingError + +from slither.vyper_parsing.ast.types import VariableDecl, Name, Subscript, ASTNode, Call, Arg +from slither.vyper_parsing.type_parsing import parse_type + + +class VariableDeclarationVyper: + # pylint: disable=too-many-branches + def __init__(self, variable: Variable, variable_data: VariableDecl) -> None: + """ + A variable can be declared through a statement, or directly. + If it is through a statement, the following children may contain + the init value. + It may be possible that the variable is declared through a statement, + but the init value is declared at the VariableDeclaration children level + """ + + self._variable = variable + if isinstance(variable_data, Arg): + self._variable.name = variable_data.arg + else: + self._variable.name = variable_data.target.id + self._was_analyzed: bool = False + self._initializedNotParsed: Optional[ASTNode] = None + + if isinstance(variable_data.annotation, Subscript): + self._elem_to_parse = variable_data.annotation.value.id + elif isinstance(variable_data.annotation, Name): + self._elem_to_parse = variable_data.annotation.id + else: # Event defs with indexed args + assert isinstance(variable_data.annotation, Call) + self._elem_to_parse = variable_data.annotation.args[0].id + self._init_from_declaration(variable_data) + # self._elem_to_parse: Optional[Union[Dict, UnknownType]] = None + # self._initializedNotParsed: Optional[Dict] = None + + # self._is_compact_ast = False + + # self._reference_id: Optional[int] = None + + @property + def underlying_variable(self) -> Variable: + return self._variable + + def _init_from_declaration(self, var: VariableDecl): + # Only state variables + + pass + + # self._handle_comment(attributes) + # Args do not have intial value + # print(var.value) + # assert var.value is None + # def _init_from_declaration( + # self, var: Dict, init: Optional[Dict] + # ) -> None: # pylint: disable=too-many-branches + # if self._is_compact_ast: + # attributes = var + # self._typeName = attributes["typeDescriptions"]["typeString"] + # else: + # assert len(var["children"]) <= 2 + # assert var["name"] == "VariableDeclaration" + + # attributes = var["attributes"] + # self._typeName = attributes["type"] + + # self._variable.name = attributes["name"] + # # self._arrayDepth = 0 + # # self._isMapping = False + # # self._mappingFrom = None + # # self._mappingTo = False + # # self._initial_expression = None + # # self._type = None + + # # Only for comapct ast format + # # the id can be used later if referencedDeclaration + # # is provided + # if "id" in var: + # self._reference_id = var["id"] + + # if "constant" in attributes: + # self._variable.is_constant = attributes["constant"] + + # if "mutability" in attributes: + # # Note: this checked is not needed if "constant" was already in attribute, but we keep it + # # for completion + # if attributes["mutability"] == "constant": + # self._variable.is_constant = True + # if attributes["mutability"] == "immutable": + # self._variable.is_immutable = True + + # self._analyze_variable_attributes(attributes) + + # if self._is_compact_ast: + # if var["typeName"]: + # self._elem_to_parse = var["typeName"] + # else: + # self._elem_to_parse = UnknownType(var["typeDescriptions"]["typeString"]) + # else: + # if not var["children"]: + # # It happens on variable declared inside loop declaration + # try: + # self._variable.type = ElementaryType(self._typeName) + # self._elem_to_parse = None + # except NonElementaryType: + # self._elem_to_parse = UnknownType(self._typeName) + # else: + # self._elem_to_parse = var["children"][0] + + # if self._is_compact_ast: + # self._initializedNotParsed = init + # if init: + # self._variable.initialized = True + # else: + # if init: # there are two way to init a var local in the AST + # assert len(var["children"]) <= 1 + # self._variable.initialized = True + # self._initializedNotParsed = init + # elif len(var["children"]) in [0, 1]: + # self._variable.initialized = False + # self._initializedNotParsed = None + # else: + # assert len(var["children"]) == 2 + # self._variable.initialized = True + # self._initializedNotParsed = var["children"][1] + + def analyze(self) -> None: + if self._was_analyzed: + return + self._was_analyzed = True + + if self._elem_to_parse is not None: + print(self._elem_to_parse) + # assert False + self._variable.type = parse_type(self._elem_to_parse) + self._elem_to_parse = None + + # if self._variable.initialized is not None: + # self._variable.expression = parse_expression(self._initializedNotParsed) + # self._initializedNotParsed = None diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py new file mode 100644 index 0000000000..78cb837483 --- /dev/null +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -0,0 +1,94 @@ +from typing import Dict +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.declarations.struct import Structure +from slither.core.variables.state_variable import StateVariable + +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: Dict, filename: str): + 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, contract_parser in self._underlying_contract_to_parser.items(): + 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() + + # def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: + + # self._contracts_by_id: Dict[int, ContractSolc] = {} + # self._parsed = False + # self._analyzed = False + + # self._underlying_contract_to_parser: Dict[Contract, ContractSolc] = {} + # self._structures_top_level_parser: List[StructureTopLevelSolc] = [] + # self._custom_error_parser: List[CustomErrorSolc] = [] + # self._variables_top_level_parser: List[TopLevelVariableSolc] = [] + # self._functions_top_level_parser: List[FunctionSolc] = [] + # self._using_for_top_level_parser: List[UsingForTopLevelSolc] = [] + + # self._all_functions_and_modifier_parser: List[FunctionSolc] = [] + + # self._top_level_contracts_counter = 0 + + # @property + # def compilation_unit(self) -> SlitherCompilationUnit: + # return self._compilation_unit + + # @property + # def all_functions_and_modifiers_parser(self) -> List[FunctionSolc]: + # return self._all_functions_and_modifier_parser + + # def add_function_or_modifier_parser(self, f: FunctionSolc) -> None: + # self._all_functions_and_modifier_parser.append(f) + + # @property + # def underlying_contract_to_parser(self) -> Dict[Contract, ContractSolc]: + # return self._underlying_contract_to_parser + + # @property + # def slither_parser(self) -> "SlitherCompilationUnitSolc": + # return self From b264512509fef6f614b373fe98744cb4d4ee8bb0 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 18 Aug 2023 15:20:22 -0500 Subject: [PATCH 02/69] improve parse_types --- slither/vyper_parsing/ast/ast.py | 22 ++++++------ .../vyper_parsing/declarations/contract.py | 20 +++++++---- .../vyper_parsing/declarations/function.py | 9 ++--- .../expressions/expression_parsing.py | 27 ++++++++++++--- .../expressions/find_variable.py | 8 ++++- slither/vyper_parsing/type_parsing.py | 34 ++++++++++++++----- 6 files changed, 86 insertions(+), 34 deletions(-) diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index b0a687528a..1ea4f34106 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -197,16 +197,24 @@ def parse_raise(raw: Dict) -> Raise: def parse_expr(raw: Dict) -> Expr: return Expr(value=parse(raw["value"]), **_extract_base_props(raw)) +unop_ast_type_to_op_symbol = {"Not": "!", "USub": "-"} def parse_unary_op(raw: Dict) -> UnaryOp: - return UnaryOp(op=raw["op"], operand=parse(raw["operand"]), **_extract_base_props(raw)) + 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)) -binop_ast_type_to_op_symbol = {"Add": "+", "Mult": "*", "Sub": "-", "Div": "-", "Pow": "**", "Mod": "%", "BitAnd": "&", "BitOr": "|", "Shr": "<<", "Shl": ">>"} +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: - op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]] + arith_op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]] return BinOp( - left=parse(raw["left"]), op=op_str, right=parse(raw["right"]), **_extract_base_props(raw) + 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) ) @@ -243,12 +251,6 @@ def parse_doc_str(raw: Dict) -> str: return raw["value"] -def parse_compare(raw: Dict) -> Compare: - return Compare( - left=parse(raw["left"]), op=raw["op"], right=parse(raw["right"]), **_extract_base_props(raw) - ) - - def parse_if(raw: Dict) -> ASTNode: return If( test=parse(raw["test"]), diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index 3a01a22f7f..d95ae68559 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -85,6 +85,7 @@ def _parse_contract_items(self) -> None: self._contract.file_scope.contracts[contract.name] = contract elif isinstance(node, InterfaceDef): + # TODO 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_parser = ContractVyper(self._slither_parser, contract, node) contract.set_offset(node.src, self._contract.compilation_unit) @@ -132,6 +133,8 @@ def parse_state_variables(self) -> None: 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 = [] @@ -160,15 +163,20 @@ def parse_functions(self) -> None: self._functionsNotParsed = [] def analyze(self) -> None: - for p in self._structures_parser: - p.analyze(self._contract) - for p in self._variables_parser: - p.analyze(self._contract) - for p in self._events_parser: - p.analyze(self._contract) + print("Analyze", self._contract._name) + # Struct defs can refer to constant state variables + for var_parser in self._variables_parser: + var_parser.analyze(self._contract) + + 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() diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index b652efc5fc..7960e2ed8c 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -168,7 +168,6 @@ def analyze_content(self) -> None: body = self._functionNotParsed.body - print(self._functionNotParsed) if body: self._function.is_implemented = True self._parse_cfg(body) @@ -256,9 +255,11 @@ def parse_statement(curr_node, expr): pass elif isinstance(expr, Assert): - print(expr) - assert False - pass + new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + new_node.add_unparsed_expression(expr) + + + elif isinstance(expr, Log): new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) new_node.add_unparsed_expression(expr.value) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index e745c2f7b4..b874ddb1f1 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -7,6 +7,7 @@ SOLIDITY_VARIABLES_COMPOSED, SolidityVariableComposed, ) +from slither.core.declarations import SolidityFunction from slither.core.expressions import ( CallExpression, ConditionalExpression, @@ -251,7 +252,7 @@ def _user_defined_op_call( return call -from slither.vyper_parsing.ast.types import Int, Call, Attribute, Name, Tuple, Hex, BinOp, Str +from slither.vyper_parsing.ast.types import Int, Call, Attribute, Name, Tuple, Hex, BinOp, Str, Assert, Compare, UnaryOp def parse_expression(expression: Dict, caller_context) -> "Expression": print("parse_expression") @@ -275,11 +276,11 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": arguments = [parse_expression(a, caller_context) for a in expression.args] # Since the AST lacks the type of the return values, we recover it. rets = called.value.returns + def get_type_str(x): return str(x.type) + type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" - print(type_str) - return CallExpression(called, arguments, type_str) @@ -290,6 +291,8 @@ def get_type_str(x): if isinstance(expression, Name): var, was_created = find_variable(expression.id, caller_context) + print(var) + print(var.__class__) assert var return Identifier(var) @@ -297,12 +300,28 @@ def get_type_str(x): tuple_vars = [parse_expression(x, caller_context) for x in expression.elements] return TupleExpression(tuple_vars) - if isinstance(expression, BinOp): + if isinstance(expression, UnaryOp): + operand = parse_expression(expression.operand, caller_context) + op = UnaryOperationType.get_type(expression.op, isprefix=True) #TODO does vyper have postfix? + + return UnaryOperation(operand, op) + + if isinstance(expression, (BinOp, Compare)): lhs = parse_expression(expression.left, caller_context) rhs = parse_expression(expression.right, caller_context) op = BinaryOperationType.get_type(expression.op) return BinaryOperation(lhs, rhs, op) + if isinstance(expression, Assert): + 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)] + return CallExpression(Identifier(func), args, type_str) + raise ParsingError(f"Expression not parsed {expression}") diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index 9037af110c..4e0564a425 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -44,6 +44,7 @@ def _find_variable_in_function_parser( if function_parser is None: return None func_variables = function_parser.underlying_function.variables_as_dict + print("func_variables", func_variables) if var_name in func_variables: return func_variables[var_name] @@ -208,9 +209,10 @@ def find_variable( from slither.vyper_parsing.declarations.contract import ContractVyper from slither.vyper_parsing.declarations.function import FunctionVyper - function_parser: Optional[FunctionSolc] = ( + function_parser: Optional[FunctionVyper] = ( caller_context if isinstance(caller_context, FunctionVyper) else None ) + print("function_parser", function_parser) ret1 = _find_variable_in_function_parser(var_name, function_parser) if ret1: return ret1, False @@ -219,6 +221,10 @@ def find_variable( if ret: return ret, False + print(current_scope.variables) + if var_name in current_scope.variables: + return current_scope.variables[var_name], False + # 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()} diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index e9a8555671..49cfd6b2d3 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -3,6 +3,8 @@ 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.vyper_parsing.expressions.expression_parsing import parse_expression from slither.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple from typing import Union @@ -16,19 +18,33 @@ def parse_type(annotation: Union[Name, Subscript, Call], contract): name = annotation.id elif isinstance(annotation, Subscript): assert isinstance(annotation.slice, Index) - # This is also a strange construct... if isinstance(annotation.slice.value, Tuple): - type_ = parse_type(annotation.slice.value.elements[0], contract) - length = parse_expression(annotation.slice.value.elements[1], contract) - else: - # 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 - type_ = parse_type(annotation.value, contract) - length = parse_expression(annotation.slice.value, contract) + assert isinstance(annotation.value, Name) + if annotation.value.id == "DynArray": + type_ = parse_type(annotation.slice.value.elements[0], contract) + length = parse_expression(annotation.slice.value.elements[1], contract) + return ArrayType(type_, length) + else: + assert annotation.value.id == "HashMap" + type_from = parse_type(annotation.slice.value.elements[0], contract) + type_to = parse_type(annotation.slice.value.elements[1], contract) - # TODO this can also me `HashMaps` + return MappingType(type_from, type_to) + + elif isinstance(annotation.value, Subscript): + type_ = parse_type(annotation.value, contract) + + 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. + # Subscript(src='13:10:0', node_id=7, value=Name(src='13:6:0', node_id=8, id='String'), slice=Index(src='13:10:0', node_id=12, value=Int(src='20:2:0', node_id=10, value=64))) + type_ = parse_type(annotation.value, contract) + if annotation.value.id == "String": + return type_ + + length = parse_expression(annotation.slice.value, contract) return ArrayType(type_, length) + elif isinstance(annotation, Call): return parse_type(annotation.args[0], contract) From 90dc42fafe8a5d03d60f33236fd93b037f165117 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Sat, 19 Aug 2023 19:38:41 -0500 Subject: [PATCH 03/69] fix interface referring to constants, plus comments --- slither/vyper_parsing/ast/ast.py | 2 ++ slither/vyper_parsing/declarations/contract.py | 12 +++++++++--- .../vyper_parsing/expressions/expression_parsing.py | 2 ++ slither/vyper_parsing/vyper_compilation_unit.py | 7 +++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index 1ea4f34106..2c2470c74c 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -197,12 +197,14 @@ def parse_raise(raw: Dict) -> Raise: 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: diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index d95ae68559..d252c5bceb 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -87,8 +87,10 @@ def _parse_contract_items(self) -> None: elif isinstance(node, InterfaceDef): # TODO 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_parser = ContractVyper(self._slither_parser, contract, node) 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 self._slither_parser._underlying_contract_to_parser[contract] = contract_parser @@ -162,12 +164,16 @@ def parse_functions(self) -> None: self._functionsNotParsed = [] - def analyze(self) -> None: - print("Analyze", self._contract._name) + + 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: + print("Analyze", self._contract._name) + for struct_parser in self._structures_parser: struct_parser.analyze(self._contract) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index b874ddb1f1..220e15520d 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -314,6 +314,8 @@ def get_type_str(x): return BinaryOperation(lhs, rhs, op) 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)") diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index 78cb837483..0a3bb7d3ef 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -42,8 +42,15 @@ def parse_contracts(self): def analyze_contracts(self) -> None: if not self._parsed: raise SlitherException("Parse the contract before running analyses") + + for contract, contract_parser in self._underlying_contract_to_parser.items(): + # State variables are analyzed for all contracts because interfaces may + # reference them, specifically, constants. + contract_parser.analyze_state_variables() + for contract, contract_parser in self._underlying_contract_to_parser.items(): contract_parser.analyze() + self._convert_to_slithir() compute_dependency(self._compilation_unit) From b142d236d731f02204198b1fbbf3c6b33a77ef86 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 22 Aug 2023 16:10:53 -0500 Subject: [PATCH 04/69] check in progress on comparison operators and builtins --- .../core/declarations/solidity_variables.py | 17 ++ .../vyper_parsing/declarations/function.py | 18 +- .../expressions/expression_parsing.py | 162 ++++++++++++++++-- .../expressions/find_variable.py | 50 ++++-- slither/vyper_parsing/type_parsing.py | 29 ++-- 5 files changed, 225 insertions(+), 51 deletions(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index f6a0f08390..76c28552ed 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -10,6 +10,7 @@ SOLIDITY_VARIABLES = { "now": "uint256", "this": "address", + "self": "address", "abi": "address", # to simplify the conversion, assume that abi return an address "msg": "", "tx": "", @@ -81,6 +82,22 @@ "balance(address)": ["uint256"], "code(address)": ["bytes"], "codehash(address)": ["bytes32"], + # Vyper + "create_from_blueprint()":[], + "empty()":[], + "convert()":[], # TODO make type conversion + "len()":[], + "method_id()":[], + "unsafe_sub()": [], + "unsafe_add()": [], + "unsafe_div()":[], + "unsafe_mul()":[], + "pow_mod256()":[], + "max_value()":[], + "min_value()":[], + "concat()":[], + "ecrecover()":[], + "isqrt()":[] } diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 7960e2ed8c..cf50fd41a2 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -173,10 +173,10 @@ def analyze_content(self) -> None: self._parse_cfg(body) for local_var_parser in self._local_variables_parser: - local_var_parser.analyze(self._function.contract) + local_var_parser.analyze(self._function) for node_parser in self._node_to_NodeVyper.values(): - node_parser.analyze_expressions(self._function.contract) + node_parser.analyze_expressions(self._function) # endregion ################################################################################### @@ -205,7 +205,7 @@ def _parse_cfg(self, cfg: Dict) -> None: curr_node = self._new_node(NodeType.ENTRYPOINT, "-1:-1:-1", self.underlying_function) self._function.entry_point = curr_node.underlying_node - scope = None + scope = Scope(True, False, self.underlying_function) if cfg: self._function.is_empty = False @@ -227,11 +227,17 @@ def parse_statement(curr_node, expr): curr_node = new_node - elif isinstance(expr, (Assign, AugAssign)): + elif isinstance(expr, (AugAssign, Assign)): new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) - new_node.add_unparsed_expression(expr.value) + new_node.add_unparsed_expression(expr) link_underlying_nodes(curr_node, new_node) + # elif isinstance(expr, Assign): + # new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + # new_node.add_unparsed_expression(expr.target) + # new_node.add_unparsed_expression(expr.value) + # link_underlying_nodes(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) @@ -281,7 +287,7 @@ def parse_statement(curr_node, expr): pass elif isinstance(expr, Raise): print(expr) - assert False + # assert False pass else: print(f"isinstance(expr, {expr.__class__.__name__})") diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 220e15520d..ee6e1c9d18 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -7,7 +7,7 @@ SOLIDITY_VARIABLES_COMPOSED, SolidityVariableComposed, ) -from slither.core.declarations import SolidityFunction +from slither.core.declarations import SolidityFunction, Function from slither.core.expressions import ( CallExpression, ConditionalExpression, @@ -39,10 +39,11 @@ ElementaryType, UserDefinedType, ) +from slither.core.declarations.contract import Contract from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.exceptions import ParsingError, VariableNotFound from slither.vyper_parsing.expressions.find_variable import find_variable -from slither.solc_parsing.solidity_types.type_parsing import UnknownType, parse_type +from slither.vyper_parsing.type_parsing import parse_type if TYPE_CHECKING: @@ -252,7 +253,8 @@ def _user_defined_op_call( return call -from slither.vyper_parsing.ast.types import Int, Call, Attribute, Name, Tuple, Hex, BinOp, Str, Assert, Compare, UnaryOp +from collections import deque +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 def parse_expression(expression: Dict, caller_context) -> "Expression": print("parse_expression") @@ -268,34 +270,111 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": if isinstance(expression, Str): return Literal(str(expression.value), ElementaryType("string")) + + if isinstance(expression, Bytes): + return Literal(str(expression.value), ElementaryType("bytes")) - + if isinstance(expression, NameConstant): + assert str(expression.value) in ["True", "False"] + return Literal(str(expression.value), ElementaryType("bool")) if isinstance(expression, Call): called = parse_expression(expression.func, caller_context) - arguments = [parse_expression(a, caller_context) for a in expression.args] - # Since the AST lacks the type of the return values, we recover it. - rets = called.value.returns + + if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction): + if called.value.name == "convert()": + arg = parse_expression(expression.args[0], caller_context) + type_to = parse_type(expression.args[1], caller_context) + return TypeConversion(arg, type_to) + elif called.value.name== "min_value()": + type_to = parse_type(expression.args[0], caller_context) + member_type = str(type_to) + # TODO return Literal + return MemberAccess("min", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) + elif called.value.name== "max_value()": + type_to = parse_type(expression.args[0], caller_context) + member_type = str(type_to) + x = MemberAccess("max", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) + print(x) + return x + + + 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] + + if isinstance(called, Identifier): + # Since the AST lacks the type of the return values, we recover it. + if isinstance(called.value, Function): + rets = called.value.returns + 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. + type_to = parse_type(expression.func, caller_context) + return TypeConversion(arguments[0], type_to) + + else: + rets = ["tuple()"] + + else: + rets = ["tuple()"] def get_type_str(x): + if isinstance(x, str): + return x return str(x.type) - + type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" return CallExpression(called, arguments, type_str) if isinstance(expression, Attribute): - var, was_created = find_variable(expression.attr, caller_context) - assert var - return Identifier(var) - + member_name = expression.attr + if isinstance(expression.value, Name): + + if expression.value.id == "self": + var, was_created = find_variable(member_name, caller_context) + # TODO replace with self + return SuperIdentifier(var) + + expr = parse_expression(expression.value, caller_context) + member_access = MemberAccess(member_name, None, expr) + # member_access.set_offset(src, caller_context.compilation_unit) + if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: + id_idx = Identifier(SolidityVariableComposed(str(member_access))) + # id_idx.set_offset(src, caller_context.compilation_unit) + return id_idx + + else: + expr = parse_expression(expression.value, caller_context) + + member_access = MemberAccess(member_name, None, expr) + + return member_access + if isinstance(expression, Name): var, was_created = find_variable(expression.id, caller_context) - print(var) - print(var.__class__) + assert var return Identifier(var) + if isinstance(expression, Assign): + lhs = parse_expression(expression.target, caller_context) + rhs = parse_expression(expression.value, caller_context) + return AssignmentOperation(lhs, rhs, AssignmentOperationType.ASSIGN, None) + + if isinstance(expression, AugAssign): + lhs = parse_expression(expression.target, caller_context) + rhs = parse_expression(expression.value, caller_context) + + op = AssignmentOperationType.get_type(expression.op) + return BinaryOperation(lhs, rhs, op) + if isinstance(expression, Tuple): tuple_vars = [parse_expression(x, caller_context) for x in expression.elements] return TupleExpression(tuple_vars) @@ -306,7 +385,46 @@ def get_type_str(x): return UnaryOperation(operand, op) - if isinstance(expression, (BinOp, Compare)): + if isinstance(expression, Compare): + lhs = parse_expression(expression.left, caller_context) + + if expression.op in ["In", "NotIn"]: + # If we see a membership operator e.g. x in [foo(), bar()] we rewrite it as if-else: + # if (x == foo()) { + # return true + # } else { + # if (x == bar()) { + # return true + # } else { + # return false + # } + # } + # We assume left operand in membership comparison cannot be Array type + assert isinstance(expression.right, VyList) + conditions = deque() + inner_op = BinaryOperationType.get_type("!=") if expression.op == "NotIn" else BinaryOperationType.get_type("==") + outer_op = BinaryOperationType.get_type("&&") if expression.op == "NotIn" else BinaryOperationType.get_type("||") + for elem in expression.right.elements: + elem_expr = parse_expression(elem, caller_context) + + conditions.append(BinaryOperation(lhs, elem_expr, inner_op)) + + assert len(conditions) % 2 == 0 + while len(conditions) > 1: + lhs = conditions.pop() + rhs = conditions.pop() + + conditions.appendleft(BinaryOperation(lhs, rhs, outer_op)) + + return conditions.pop() + + else: + rhs = parse_expression(expression.right, caller_context) + + op = BinaryOperationType.get_type(expression.op) + return BinaryOperation(lhs, rhs, op) + + if isinstance(expression, BinOp): lhs = parse_expression(expression.left, caller_context) rhs = parse_expression(expression.right, caller_context) @@ -326,4 +444,18 @@ def get_type_str(x): return CallExpression(Identifier(func), args, type_str) + if isinstance(expression, Subscript): + left_expression = parse_expression(expression.value, caller_context) + right_expression = parse_expression(expression.slice.value, caller_context) + index = IndexAccess(left_expression, right_expression) + # index.set_offset(src, caller_context.compilation_unit) + return index + + 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) TODO update BoolOp AST + return BinaryOperation(lhs, rhs,BinaryOperationType.ANDAND) + raise ParsingError(f"Expression not parsed {expression}") diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index 4e0564a425..daef44df84 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -43,8 +43,9 @@ def _find_variable_in_function_parser( ) -> Optional[Variable]: if function_parser is None: return None - func_variables = function_parser.underlying_function.variables_as_dict - print("func_variables", func_variables) + func_variables = function_parser.variables_as_dict + # print("func_variables", func_variables) + if var_name in func_variables: return func_variables[var_name] @@ -64,14 +65,14 @@ def _find_in_contract( # variable are looked from the contract declarer print(contract) contract_variables = contract.variables_as_dict - print(contract_variables) + # print(contract_variables) 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} - print(functions) + # print(functions) if var_name in functions: return functions[var_name] @@ -196,32 +197,41 @@ def find_variable( # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed - - direct_contracts = [caller_context] - direct_functions = caller_context.functions_declared - print(direct_functions) - current_scope = caller_context.file_scope + from slither.vyper_parsing.declarations.contract import ContractVyper + from slither.vyper_parsing.declarations.function import FunctionVyper + print("caller_context") + print(caller_context) + print(caller_context.__class__.__name__) + if isinstance(caller_context, Contract): + direct_contracts = [caller_context] + direct_functions = caller_context.functions_declared + current_scope = caller_context.file_scope + next_context = caller_context + else: + direct_contracts = [caller_context.contract] + direct_functions = caller_context.contract.functions_declared + current_scope = caller_context.contract.file_scope + next_context = caller_context.contract + # print(direct_functions) # Only look for reference declaration in the direct contract, see comment at the end # Reference looked are split between direct and all # Because functions are copied between contracts, two functions can have the same ref # So we need to first look with respect to the direct context - from slither.vyper_parsing.declarations.contract import ContractVyper - from slither.vyper_parsing.declarations.function import FunctionVyper function_parser: Optional[FunctionVyper] = ( - caller_context if isinstance(caller_context, FunctionVyper) else None + caller_context if isinstance(caller_context, FunctionContract) else None ) - print("function_parser", function_parser) + # print("function_parser", function_parser) ret1 = _find_variable_in_function_parser(var_name, function_parser) if ret1: return ret1, False - ret = _find_in_contract(var_name, caller_context, caller_context) + ret = _find_in_contract(var_name, next_context, caller_context) if ret: return ret, False - print(current_scope.variables) + # print(current_scope.variables) if var_name in current_scope.variables: return current_scope.variables[var_name], False @@ -238,9 +248,11 @@ def find_variable( if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name), False - if var_name in SOLIDITY_FUNCTIONS: - return SolidityFunction(var_name), False - - + if f"{var_name}()" in SOLIDITY_FUNCTIONS: + return SolidityFunction(f"{var_name}()"), False + print(next_context.events_as_dict) + if f"{var_name}()" in next_context.events_as_dict: + return next_context.events_as_dict[f"{var_name}()"], False + raise VariableNotFound(f"Variable not found: {var_name} (context {caller_context})") diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index 49cfd6b2d3..15fc16df61 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -5,13 +5,20 @@ from slither.core.solidity_types.array_type import ArrayType from slither.core.solidity_types.mapping_type import MappingType -from slither.vyper_parsing.expressions.expression_parsing import parse_expression from slither.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple from typing import Union from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.declarations.function_contract import FunctionContract + +def parse_type(annotation: Union[Name, Subscript, Call], caller_context): + from slither.vyper_parsing.expressions.expression_parsing import parse_expression + + if isinstance(caller_context, FunctionContract): + contract = caller_context.contract + else: + contract = caller_context -def parse_type(annotation: Union[Name, Subscript, Call], contract): assert isinstance(annotation, (Name, Subscript, Call)) print(annotation) if isinstance(annotation, Name): @@ -22,32 +29,32 @@ def parse_type(annotation: Union[Name, Subscript, Call], contract): if isinstance(annotation.slice.value, Tuple): assert isinstance(annotation.value, Name) if annotation.value.id == "DynArray": - type_ = parse_type(annotation.slice.value.elements[0], contract) - length = parse_expression(annotation.slice.value.elements[1], contract) + type_ = parse_type(annotation.slice.value.elements[0], caller_context) + length = parse_expression(annotation.slice.value.elements[1], caller_context) return ArrayType(type_, length) else: assert annotation.value.id == "HashMap" - type_from = parse_type(annotation.slice.value.elements[0], contract) - type_to = parse_type(annotation.slice.value.elements[1], contract) + 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, contract) + 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. # Subscript(src='13:10:0', node_id=7, value=Name(src='13:6:0', node_id=8, id='String'), slice=Index(src='13:10:0', node_id=12, value=Int(src='20:2:0', node_id=10, value=64))) - type_ = parse_type(annotation.value, contract) + type_ = parse_type(annotation.value, caller_context) if annotation.value.id == "String": return type_ - length = parse_expression(annotation.slice.value, contract) + length = parse_expression(annotation.slice.value, caller_context) return ArrayType(type_, length) elif isinstance(annotation, Call): - return parse_type(annotation.args[0], contract) + return parse_type(annotation.args[0], caller_context) else: assert False @@ -56,7 +63,7 @@ def parse_type(annotation: Union[Name, Subscript, Call], contract): if lname in ElementaryTypeName: return ElementaryType(lname) - print(contract.structures_as_dict) + if name in contract.structures_as_dict: return UserDefinedType(contract.structures_as_dict[name]) From 6fd82fee5887cc4e17fa796babf79bd5fbb4e893 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 22 Aug 2023 20:34:21 -0500 Subject: [PATCH 05/69] aug assign op --- slither/vyper_parsing/ast/ast.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index 2c2470c74c..f562c7cae3 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -301,11 +301,13 @@ def parse_enum_def(raw: Dict) -> EnumDef: 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=raw["op"], + op=op_str, value=parse(raw["value"]), **_extract_base_props(raw), ) From 75c3389ace991a3c05a5fca6436c0d4d43c4ccdb Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 24 Aug 2023 09:39:18 -0500 Subject: [PATCH 06/69] wip rewrite of for loops --- .../vyper_parsing/declarations/function.py | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index cf50fd41a2..c16bf16b3a 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -178,6 +178,8 @@ def analyze_content(self) -> None: for node_parser in self._node_to_NodeVyper.values(): node_parser.analyze_expressions(self._function) + + # endregion ################################################################################### ################################################################################### @@ -239,11 +241,63 @@ def parse_statement(curr_node, expr): # link_underlying_nodes(curr_node, new_node) elif isinstance(expr, For): + node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope) + + 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) + new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + new_node.add_unparsed_expression(counter_var.value) + new_node.underlying_node.add_variable_declaration(local_var) + + if isinstance(expr.iter, Name): + # 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=[expr.iter], keywords=[], keyword=None)) + node_condition = self._new_node(NodeType.IFLOOP, expr.src, scope) + node_condition.add_unparsed_expression(cond_expr) + + # HACK + # The loop variable is not annotated so we infer its type by looking at the type of the iterator + loop_iterator = list(filter(lambda x: x._variable.name == expr.iter.id, self._local_variables_parser))[0] + # Assumes `Subscript` + # TODO this should go in the body of the loop: expr.body.insert(0, ...) + loop_var_annotation = loop_iterator._elem_to_parse.slice.value.elements[0] + 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) + 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, loop_var) + self._add_local_variable(local_var_parser) + new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + new_node.add_unparsed_expression(loop_var.value) + new_node.underlying_node.add_variable_declaration(local_var) + + elif isinstance(expr.iter, Call): + 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) + 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")) + 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, loop_var) + self._add_local_variable(local_var_parser) + new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + new_node.add_unparsed_expression(loop_var.value) + new_node.underlying_node.add_variable_declaration(local_var) + else: + print(expr) + raise NotImplementedError + # assert False node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) - node_condition = self._new_node(NodeType.IFLOOP, expr.iter.src, scope) - node_condition.add_unparsed_expression(expr.iter) + + # link_underlying_nodes(node_startLoop, node_condition) for stmt in expr.body: parse_statement(curr_node, stmt) From 2f94d1c2517cdd010b2c02d432a8d7c7b11c337b Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 24 Aug 2023 13:57:48 -0500 Subject: [PATCH 07/69] more builtins; support params, state vars in loop iterators --- .../core/declarations/solidity_variables.py | 13 +++++- .../vyper_parsing/declarations/contract.py | 4 +- .../vyper_parsing/declarations/function.py | 26 ++++++++--- .../expressions/expression_parsing.py | 43 ++++++++++++++----- .../vyper_parsing/variables/local_variable.py | 8 +++- 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 76c28552ed..c14efef77b 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -16,6 +16,7 @@ "tx": "", "block": "", "super": "", + "chain": "", } SOLIDITY_VARIABLES_COMPOSED = { @@ -86,7 +87,7 @@ "create_from_blueprint()":[], "empty()":[], "convert()":[], # TODO make type conversion - "len()":[], + "len()":["uint256"], "method_id()":[], "unsafe_sub()": [], "unsafe_add()": [], @@ -97,7 +98,15 @@ "min_value()":[], "concat()":[], "ecrecover()":[], - "isqrt()":[] + "isqrt()":[], + "range()":[], + "min()":[], + "max()":[], + "shift()":[], + "abs()":[], + "raw_call()":[], + "_abi_encode()":[], + "slice()":[], } diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index d252c5bceb..6ad98296e2 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -85,7 +85,7 @@ def _parse_contract_items(self) -> None: self._contract.file_scope.contracts[contract.name] = contract elif isinstance(node, InterfaceDef): - # TODO This needs to be done lazily as interfaces can refer to constant state variables + # 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 @@ -158,7 +158,7 @@ def parse_functions(self) -> None: func.set_contract(self._contract) func.set_contract_declarer(self._contract) - func_parser = FunctionVyper(func, function) + func_parser = FunctionVyper(func, function, self) self._contract.add_function(func) self._functions_parser.append(func_parser) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index c16bf16b3a..5541f86dfb 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -30,6 +30,7 @@ def __init__( self, function: Function, function_data: Dict, + contract_parser: "ContractVyper", ) -> None: self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} @@ -40,6 +41,7 @@ def __init__( self._function.id = function_data.node_id self._local_variables_parser: List = [] + self._contract_parser = contract_parser for decorator in function_data.decorators: if not hasattr(decorator, "id"): @@ -255,18 +257,28 @@ def parse_statement(curr_node, expr): new_node.add_unparsed_expression(counter_var.value) new_node.underlying_node.add_variable_declaration(local_var) - if isinstance(expr.iter, Name): + 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 + iter_expr = expr.iter + loop_iterator = list(filter(lambda x: x._variable.name == iter_expr.attr, self._contract_parser._variables_parser))[0] + + else: # local + 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=[expr.iter], keywords=[], keyword=None)) + 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) - # HACK - # The loop variable is not annotated so we infer its type by looking at the type of the iterator - loop_iterator = list(filter(lambda x: x._variable.name == expr.iter.id, self._local_variables_parser))[0] - # Assumes `Subscript` # TODO this should go in the body of the loop: expr.body.insert(0, ...) - loop_var_annotation = loop_iterator._elem_to_parse.slice.value.elements[0] + 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) local_var = LocalVariable() diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index ee6e1c9d18..544dcbf65f 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -282,7 +282,10 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": called = parse_expression(expression.func, caller_context) if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction): - if called.value.name == "convert()": + if called.value.name == "empty()": + type_to = parse_type(expression.args[0], caller_context) + return CallExpression(called, [], str(type_to)) + elif called.value.name == "convert()": arg = parse_expression(expression.args[0], caller_context) type_to = parse_type(expression.args[1], caller_context) return TypeConversion(arg, type_to) @@ -294,9 +297,8 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": elif called.value.name== "max_value()": type_to = parse_type(expression.args[0], caller_context) member_type = str(type_to) - x = MemberAccess("max", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) - print(x) - return x + # TODO return Literal + return MemberAccess("max", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) if expression.args and isinstance(expression.args[0], VyDict): @@ -320,7 +322,10 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": else: rets = ["tuple()"] - + 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 else: rets = ["tuple()"] @@ -330,7 +335,8 @@ def get_type_str(x): return str(x.type) type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" - + print(CallExpression(called, arguments, type_str)) + print(type_str) return CallExpression(called, arguments, type_str) if isinstance(expression, Attribute): @@ -352,8 +358,25 @@ def get_type_str(x): else: expr = parse_expression(expression.value, caller_context) - - member_access = MemberAccess(member_name, None, expr) + 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` + # try: TODO this is using the wrong caller_context and needs to be interface instead of self namespace + # var, was_created = find_variable(member_name, caller_context) + # if isinstance(var, Function): + # rets = var.returns + # def get_type_str(x): + # if isinstance(x, str): + # return x + # return str(x.type) + + # type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" + # member_name_ret_type = type_str + # except: + # pass + + member_access = MemberAccess(member_name, member_name_ret_type, expr) return member_access @@ -373,9 +396,9 @@ def get_type_str(x): rhs = parse_expression(expression.value, caller_context) op = AssignmentOperationType.get_type(expression.op) - return BinaryOperation(lhs, rhs, op) + return AssignmentOperation(lhs, rhs, op, None) - if isinstance(expression, Tuple): + if isinstance(expression, (Tuple, VyList)): tuple_vars = [parse_expression(x, caller_context) for x in expression.elements] return TupleExpression(tuple_vars) diff --git a/slither/vyper_parsing/variables/local_variable.py b/slither/vyper_parsing/variables/local_variable.py index 45eb55351a..3651b701fd 100644 --- a/slither/vyper_parsing/variables/local_variable.py +++ b/slither/vyper_parsing/variables/local_variable.py @@ -9,13 +9,17 @@ class LocalVariableVyper: def __init__(self, variable: LocalVariable, variable_data: Union[Arg, Name]) -> None: self._variable: LocalVariable = variable - if isinstance(variable_data, (Arg, )): + if isinstance(variable_data, Arg): self._variable.name = variable_data.arg self._elem_to_parse = variable_data.annotation - elif isinstance(variable_data, (AnnAssign, )): + elif isinstance(variable_data, AnnAssign): self._variable.name = variable_data.target.id self._elem_to_parse = variable_data.annotation + elif isinstance(variable_data, Name): + self._variable.name = variable_data.id + self._elem_to_parse = variable_data else: + # param Subscript self._variable.name = "" self._elem_to_parse = variable_data From 872d9b59b0c6a37cdc31c5d87d385a7cb40d0f8d Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 24 Aug 2023 17:10:05 -0500 Subject: [PATCH 08/69] support enum bitwise comparison operations --- .../expressions/expression_parsing.py | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 544dcbf65f..d3c0dd126c 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -423,16 +423,34 @@ def get_type_str(x): # } # } # We assume left operand in membership comparison cannot be Array type - assert isinstance(expression.right, VyList) conditions = deque() - inner_op = BinaryOperationType.get_type("!=") if expression.op == "NotIn" else BinaryOperationType.get_type("==") - outer_op = BinaryOperationType.get_type("&&") if expression.op == "NotIn" else BinaryOperationType.get_type("||") - for elem in expression.right.elements: - elem_expr = parse_expression(elem, caller_context) + if isinstance(expression.right, VyList): + inner_op = BinaryOperationType.get_type("!=") if expression.op == "NotIn" else BinaryOperationType.get_type("==") + outer_op = BinaryOperationType.get_type("&&") if expression.op == "NotIn" else BinaryOperationType.get_type("||") - conditions.append(BinaryOperation(lhs, elem_expr, inner_op)) + for elem in expression.right.elements: + elem_expr = parse_expression(elem, caller_context) + print("elem", repr(elem_expr)) + conditions.append(BinaryOperation(lhs, elem_expr, inner_op)) + else: + inner_op = BinaryOperationType.get_type("|") #if expression.op == "NotIn" else BinaryOperationType.get_type("==") + outer_op = BinaryOperationType.get_type("&") #if expression.op == "NotIn" else BinaryOperationType.get_type("||") - assert len(conditions) % 2 == 0 + x, _ = find_variable(expression.right.value.attr, caller_context) + print(x) + print(x.type.type_to) + print(x.type.type_to.__class__) + enum_members = x.type.type_to.type.values + # for each value, create a literal with value = 2 ^ n (0 indexed) + # and then translate to bitmasking + enum_values = [Literal(2 ** n, ElementaryType("uint256")) for n in range(len(enum_members))] + inner_lhs = enum_values[0] + for expr in enum_values[1:]: + inner_lhs = BinaryOperation(inner_lhs, expr, inner_op) + conditions.append(inner_lhs) + print(conditions) + return BinaryOperation(lhs, conditions[0], outer_op) + while len(conditions) > 1: lhs = conditions.pop() rhs = conditions.pop() From 7549341c66c992e19ad6aae2a6ab23e7657188e2 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 10:53:22 -0500 Subject: [PATCH 09/69] add some builtin variables for vyper --- slither/core/declarations/solidity_variables.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index c14efef77b..85844508eb 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -36,6 +36,10 @@ "msg.value": "uint256", "tx.gasprice": "uint256", "tx.origin": "address", + # Vyper + "chain.id": "uint256", + "block.prevhash": "bytes32", + } SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { From fd7c130d3903918050476bc301a1c1a9f8cfdece Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 10:53:47 -0500 Subject: [PATCH 10/69] allow type conversion to array type for vyper --- slither/slithir/operations/type_conversion.py | 5 +++-- slither/visitors/slithir/expression_to_slithir.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/slither/slithir/operations/type_conversion.py b/slither/slithir/operations/type_conversion.py index e9998bc65b..08b87ab49a 100644 --- a/slither/slithir/operations/type_conversion.py +++ b/slither/slithir/operations/type_conversion.py @@ -4,6 +4,7 @@ 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 @@ def __init__( 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 diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index a99a6af863..5a6c7de598 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -573,7 +573,7 @@ def _post_type_conversion(self, expression: expressions.TypeConversion) -> None: 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) From a8641d244fb58ee5cf1fd246a9fa79de5daa633a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 10:55:47 -0500 Subject: [PATCH 11/69] add anon. local for return signature --- slither/vyper_parsing/declarations/function.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 5541f86dfb..0cce65d045 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -399,14 +399,16 @@ def _parse_returns(self, returns: Union[Name, Tuple, Subscript]): print(returns) 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(returns) + 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, Tuple) for ret in returns.elements: - local_var = self._add_param(ret) + local_var = self._add_param(Arg(ret.src, ret.node_id, "", annotation=ret)) self._function.add_return(local_var.underlying_variable) ################################################################################### From 4c78fe00c4f9367228502f33b3ffadda55c4775b Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 10:57:48 -0500 Subject: [PATCH 12/69] support conversion of interface to address, lookup interface funcs --- .../expressions/expression_parsing.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index d3c0dd126c..86913ea7bf 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -309,6 +309,8 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": arguments = [parse_expression(a, caller_context) for a in expression.args] if isinstance(called, Identifier): + print("called", called) + print("called.value", called.value.__class__.__name__) # Since the AST lacks the type of the return values, we recover it. if isinstance(called.value, Function): rets = called.value.returns @@ -325,7 +327,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": 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 + rets = [called.type] else: rets = ["tuple()"] @@ -333,7 +335,7 @@ def get_type_str(x): if isinstance(x, str): return x return str(x.type) - + print(rets) type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" print(CallExpression(called, arguments, type_str)) print(type_str) @@ -347,8 +349,10 @@ def get_type_str(x): var, was_created = find_variable(member_name, caller_context) # TODO replace with self return SuperIdentifier(var) - 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 + if expression.attr == "address": + return TypeConversion(expr, ElementaryType("address")) member_access = MemberAccess(member_name, None, expr) # member_access.set_offset(src, caller_context.compilation_unit) if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: @@ -362,17 +366,22 @@ def get_type_str(x): # (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` - # try: TODO this is using the wrong caller_context and needs to be interface instead of self namespace - # var, was_created = find_variable(member_name, caller_context) - # if isinstance(var, Function): - # rets = var.returns - # def get_type_str(x): - # if isinstance(x, str): - # return x - # return str(x.type) - - # type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" - # member_name_ret_type = type_str + # TODO this is using the wrong caller_context and needs to be interface instead of self namespace + print(expr) + print(expr.__class__.__name__) + + if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType): + # try: + var, was_created = find_variable(member_name, expr.type.type) + if isinstance(var, Function): + rets = var.returns + def get_type_str(x): + if isinstance(x, str): + return x + return str(x.type) + + type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" + member_name_ret_type = type_str # except: # pass From cff917f99a36585ca96ccb8ae2c21768daa33ebe Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 20:51:26 -0500 Subject: [PATCH 13/69] handle stldib interfaces --- .../vyper_parsing/declarations/contract.py | 119 ++++++++++++++++-- 1 file changed, 110 insertions(+), 9 deletions(-) diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index 6ad98296e2..fde720340a 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -10,6 +10,13 @@ ImportFrom, InterfaceDef, AnnAssign, + Expr, + Name, + Arguments, + Index, + Subscript, + Int, + Arg, ) from slither.vyper_parsing.declarations.event import EventVyper @@ -74,15 +81,111 @@ def _parse_contract_items(self) -> None: 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": - # TODO add functions - contract = Contract(self._contract.compilation_unit, self._contract.file_scope) - contract.set_offset("-1:-1:-1", self._contract.compilation_unit) - - contract.name = node.name - contract.is_interface = True - self._contract.file_scope.contracts[contract.name] = contract + 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 @@ -164,13 +267,11 @@ def parse_functions(self) -> None: 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: print("Analyze", self._contract._name) From 3e461ca59c08eca81f7cd7c35f648a56cd92b0f2 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 20:56:14 -0500 Subject: [PATCH 14/69] initialized vars with expr --- slither/vyper_parsing/declarations/function.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 0cce65d045..d1b075864a 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -225,6 +225,7 @@ def parse_statement(curr_node, expr): 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) @@ -254,6 +255,7 @@ def parse_statement(curr_node, expr): local_var_parser = LocalVariableVyper(local_var, counter_var) self._add_local_variable(local_var_parser) new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + local_var.initialized = True new_node.add_unparsed_expression(counter_var.value) new_node.underlying_node.add_variable_declaration(local_var) @@ -287,6 +289,7 @@ def parse_statement(curr_node, expr): local_var_parser = LocalVariableVyper(local_var, loop_var) self._add_local_variable(local_var_parser) new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + local_var.initialized = True new_node.add_unparsed_expression(loop_var.value) new_node.underlying_node.add_variable_declaration(local_var) @@ -300,6 +303,7 @@ def parse_statement(curr_node, expr): local_var_parser = LocalVariableVyper(local_var, loop_var) self._add_local_variable(local_var_parser) new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + local_var.initialized = True new_node.add_unparsed_expression(loop_var.value) new_node.underlying_node.add_variable_declaration(local_var) else: From 71894bad7ae0458d442b5f03ebda24d36f74909c Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 20:59:54 -0500 Subject: [PATCH 15/69] perform ssa conversion and analysis --- slither/vyper_parsing/vyper_compilation_unit.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index 0a3bb7d3ef..f8a2ca216d 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -58,12 +58,18 @@ def analyze_contracts(self) -> None: 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() + # def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: # self._contracts_by_id: Dict[int, ContractSolc] = {} From b1cb181dea19cb968bf7f1e5fe0cbe28bb4a1b4b Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 28 Aug 2023 21:02:48 -0500 Subject: [PATCH 16/69] remove unused code --- .../vyper_parsing/declarations/modifier.py | 107 ------------- .../expressions/find_variable.py | 50 +----- .../variables/variable_declaration.py | 150 ------------------ 3 files changed, 2 insertions(+), 305 deletions(-) delete mode 100644 slither/vyper_parsing/declarations/modifier.py delete mode 100644 slither/vyper_parsing/variables/variable_declaration.py diff --git a/slither/vyper_parsing/declarations/modifier.py b/slither/vyper_parsing/declarations/modifier.py deleted file mode 100644 index c4c5c71772..0000000000 --- a/slither/vyper_parsing/declarations/modifier.py +++ /dev/null @@ -1,107 +0,0 @@ -""" - Event module -""" -from typing import Dict, TYPE_CHECKING, Union - -from slither.core.cfg.node import NodeType -from slither.core.cfg.node import link_nodes -from slither.core.cfg.scope import Scope -from slither.core.declarations.modifier import Modifier -from slither.solc_parsing.cfg.node import NodeSolc -from slither.solc_parsing.declarations.function import FunctionSolc - -if TYPE_CHECKING: - from slither.solc_parsing.declarations.contract import ContractSolc - from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc - from slither.core.declarations import Function - - -class ModifierSolc(FunctionSolc): - def __init__( - self, - modifier: Modifier, - function_data: Dict, - contract_parser: "ContractSolc", - slither_parser: "SlitherCompilationUnitSolc", - ) -> None: - super().__init__(modifier, function_data, contract_parser, slither_parser) - # _modifier is equal to _function, but keep it here to prevent - # confusion for mypy in underlying_function - self._modifier = modifier - - @property - def underlying_function(self) -> Modifier: - return self._modifier - - def analyze_params(self) -> None: - # Can be re-analyzed due to inheritance - if self._params_was_analyzed: - return - - self._params_was_analyzed = True - - self._analyze_attributes() - - if self.is_compact_ast: - params = self._functionNotParsed["parameters"] - else: - children = self._functionNotParsed["children"] - # It uses to be - # params = children[0] - # But from Solidity 0.6.3 to 0.6.10 (included) - # Comment above a function might be added in the children - params = next(child for child in children if child[self.get_key()] == "ParameterList") - - if params: - self._parse_params(params) - - def analyze_content(self) -> None: - if self._content_was_analyzed: - return - - self._content_was_analyzed = True - - if self.is_compact_ast: - body = self._functionNotParsed.get("body", None) - - if body and body[self.get_key()] == "Block": - self._function.is_implemented = True - self._parse_cfg(body) - - else: - children = self._functionNotParsed["children"] - - self._function.is_implemented = False - if len(children) > 1: - # It uses to be - # params = children[1] - # But from Solidity 0.6.3 to 0.6.10 (included) - # Comment above a function might be added in the children - block = next(child for child in children if child[self.get_key()] == "Block") - self._function.is_implemented = True - self._parse_cfg(block) - - for local_var_parser in self._local_variables_parser: - local_var_parser.analyze(self) - - for node in self._node_to_nodesolc.values(): - node.analyze_expressions(self) - - for yul_parser in self._node_to_yulobject.values(): - yul_parser.analyze_expressions() - - self._rewrite_ternary_as_if_else() - self._remove_alone_endif() - - # self._analyze_read_write() - # self._analyze_calls() - - def _parse_statement( - self, statement: Dict, node: NodeSolc, scope: Union[Scope, "Function"] - ) -> NodeSolc: - name = statement[self.get_key()] - if name == "PlaceholderStatement": - placeholder_node = self._new_node(NodeType.PLACEHOLDER, statement["src"], scope) - link_nodes(node.underlying_node, placeholder_node.underlying_node) - return placeholder_node - return super()._parse_statement(statement, node, scope) diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index daef44df84..b75b688db9 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -44,8 +44,7 @@ def _find_variable_in_function_parser( if function_parser is None: return None func_variables = function_parser.variables_as_dict - # print("func_variables", func_variables) - + print("func_variables", func_variables) if var_name in func_variables: return func_variables[var_name] @@ -65,7 +64,6 @@ def _find_in_contract( # variable are looked from the contract declarer print(contract) contract_variables = contract.variables_as_dict - # print(contract_variables) if var_name in contract_variables: return contract_variables[var_name] @@ -112,46 +110,6 @@ def _find_in_contract( return None -def _find_variable_init( - caller_context: CallerContextExpression, -) -> Tuple[List[Contract], List["Function"], FileScope,]: - from slither.vyper_parsing.declarations.contract import ContractVyper - from slither.vyper_parsing.declarations.function import FunctionVyper - - - direct_contracts: List[Contract] - direct_functions_parser: List[Function] - scope: FileScope - - if isinstance(caller_context, FileScope): - direct_contracts = [] - direct_functions_parser = [] - scope = caller_context - elif isinstance(caller_context, ContractVyper): - direct_contracts = [caller_context.underlying_contract] - direct_functions_parser = [ - f.underlying_function - for f in caller_context.functions_parser + caller_context.modifiers_parser - ] - scope = caller_context.underlying_contract.file_scope - elif isinstance(caller_context, FunctionVyper): - - direct_contracts = [caller_context.underlying_contract] - direct_functions_parser = [ - f.underlying_function - for f in caller_context.functions_parser - ] - - - scope = contract.file_scope - else: - raise SlitherError( - f"{type(caller_context)} ({caller_context} is not valid for find_variable" - ) - - return direct_contracts, direct_functions_parser, scope - - def find_variable( var_name: str, caller_context: CallerContextExpression, @@ -202,6 +160,7 @@ def find_variable( print("caller_context") print(caller_context) print(caller_context.__class__.__name__) + print("var", var_name) if isinstance(caller_context, Contract): direct_contracts = [caller_context] direct_functions = caller_context.functions_declared @@ -214,11 +173,6 @@ def find_variable( next_context = caller_context.contract # print(direct_functions) - # Only look for reference declaration in the direct contract, see comment at the end - # Reference looked are split between direct and all - # Because functions are copied between contracts, two functions can have the same ref - # So we need to first look with respect to the direct context - function_parser: Optional[FunctionVyper] = ( caller_context if isinstance(caller_context, FunctionContract) else None ) diff --git a/slither/vyper_parsing/variables/variable_declaration.py b/slither/vyper_parsing/variables/variable_declaration.py deleted file mode 100644 index 64878163cb..0000000000 --- a/slither/vyper_parsing/variables/variable_declaration.py +++ /dev/null @@ -1,150 +0,0 @@ -import logging -import re -from typing import Dict, Optional, Union - - -from slither.core.variables.variable import Variable -from slither.core.solidity_types.elementary_type import ( - ElementaryType, - NonElementaryType, -) -from slither.solc_parsing.exceptions import ParsingError - -from slither.vyper_parsing.ast.types import VariableDecl, Name, Subscript, ASTNode, Call, Arg -from slither.vyper_parsing.type_parsing import parse_type - - -class VariableDeclarationVyper: - # pylint: disable=too-many-branches - def __init__(self, variable: Variable, variable_data: VariableDecl) -> None: - """ - A variable can be declared through a statement, or directly. - If it is through a statement, the following children may contain - the init value. - It may be possible that the variable is declared through a statement, - but the init value is declared at the VariableDeclaration children level - """ - - self._variable = variable - if isinstance(variable_data, Arg): - self._variable.name = variable_data.arg - else: - self._variable.name = variable_data.target.id - self._was_analyzed: bool = False - self._initializedNotParsed: Optional[ASTNode] = None - - if isinstance(variable_data.annotation, Subscript): - self._elem_to_parse = variable_data.annotation.value.id - elif isinstance(variable_data.annotation, Name): - self._elem_to_parse = variable_data.annotation.id - else: # Event defs with indexed args - assert isinstance(variable_data.annotation, Call) - self._elem_to_parse = variable_data.annotation.args[0].id - self._init_from_declaration(variable_data) - # self._elem_to_parse: Optional[Union[Dict, UnknownType]] = None - # self._initializedNotParsed: Optional[Dict] = None - - # self._is_compact_ast = False - - # self._reference_id: Optional[int] = None - - @property - def underlying_variable(self) -> Variable: - return self._variable - - def _init_from_declaration(self, var: VariableDecl): - # Only state variables - - pass - - # self._handle_comment(attributes) - # Args do not have intial value - # print(var.value) - # assert var.value is None - # def _init_from_declaration( - # self, var: Dict, init: Optional[Dict] - # ) -> None: # pylint: disable=too-many-branches - # if self._is_compact_ast: - # attributes = var - # self._typeName = attributes["typeDescriptions"]["typeString"] - # else: - # assert len(var["children"]) <= 2 - # assert var["name"] == "VariableDeclaration" - - # attributes = var["attributes"] - # self._typeName = attributes["type"] - - # self._variable.name = attributes["name"] - # # self._arrayDepth = 0 - # # self._isMapping = False - # # self._mappingFrom = None - # # self._mappingTo = False - # # self._initial_expression = None - # # self._type = None - - # # Only for comapct ast format - # # the id can be used later if referencedDeclaration - # # is provided - # if "id" in var: - # self._reference_id = var["id"] - - # if "constant" in attributes: - # self._variable.is_constant = attributes["constant"] - - # if "mutability" in attributes: - # # Note: this checked is not needed if "constant" was already in attribute, but we keep it - # # for completion - # if attributes["mutability"] == "constant": - # self._variable.is_constant = True - # if attributes["mutability"] == "immutable": - # self._variable.is_immutable = True - - # self._analyze_variable_attributes(attributes) - - # if self._is_compact_ast: - # if var["typeName"]: - # self._elem_to_parse = var["typeName"] - # else: - # self._elem_to_parse = UnknownType(var["typeDescriptions"]["typeString"]) - # else: - # if not var["children"]: - # # It happens on variable declared inside loop declaration - # try: - # self._variable.type = ElementaryType(self._typeName) - # self._elem_to_parse = None - # except NonElementaryType: - # self._elem_to_parse = UnknownType(self._typeName) - # else: - # self._elem_to_parse = var["children"][0] - - # if self._is_compact_ast: - # self._initializedNotParsed = init - # if init: - # self._variable.initialized = True - # else: - # if init: # there are two way to init a var local in the AST - # assert len(var["children"]) <= 1 - # self._variable.initialized = True - # self._initializedNotParsed = init - # elif len(var["children"]) in [0, 1]: - # self._variable.initialized = False - # self._initializedNotParsed = None - # else: - # assert len(var["children"]) == 2 - # self._variable.initialized = True - # self._initializedNotParsed = var["children"][1] - - def analyze(self) -> None: - if self._was_analyzed: - return - self._was_analyzed = True - - if self._elem_to_parse is not None: - print(self._elem_to_parse) - # assert False - self._variable.type = parse_type(self._elem_to_parse) - self._elem_to_parse = None - - # if self._variable.initialized is not None: - # self._variable.expression = parse_expression(self._initializedNotParsed) - # self._initializedNotParsed = None From b537b70ef724d8af8c93bc9d62ff84a872ca6c77 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 15:31:24 -0500 Subject: [PATCH 17/69] fix type --- slither/slithir/variables/constant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 5321e52500..19aaaa893e 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -11,7 +11,7 @@ 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 From 8a3fb6ad99a5ddd474d89dcd20e9e3c927062a2e Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 15:31:41 -0500 Subject: [PATCH 18/69] fix div symbol --- slither/vyper_parsing/ast/ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index f562c7cae3..ca748954a1 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -205,7 +205,7 @@ def parse_unary_op(raw: Dict) -> UnaryOp: 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"} +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"]] From 472efb9cda4e9a81ee9eae9de837310bf6ba3ac0 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 15:33:02 -0500 Subject: [PATCH 19/69] improve comparison operator support, set_offset of expressions --- slither/vyper_parsing/cfg/node.py | 8 +- .../expressions/expression_parsing.py | 186 +++++++++++++----- 2 files changed, 137 insertions(+), 57 deletions(-) diff --git a/slither/vyper_parsing/cfg/node.py b/slither/vyper_parsing/cfg/node.py index cf8a8f160f..3d5ffee91e 100644 --- a/slither/vyper_parsing/cfg/node.py +++ b/slither/vyper_parsing/cfg/node.py @@ -32,7 +32,7 @@ def analyze_expressions(self, caller_context) -> None: if self._unparsed_expression: expression = parse_expression(self._unparsed_expression, caller_context) self._node.add_expression(expression) - # self._unparsed_expression = None + self._unparsed_expression = None if self._node.expression: @@ -44,9 +44,9 @@ def analyze_expressions(self, caller_context) -> None: AssignmentOperationType.ASSIGN, self._node.variable_declaration.type, ) - # _expression.set_offset( - # self._node.expression.source_mapping, self._node.compilation_unit - # ) + _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 diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 86913ea7bf..8ce7577326 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -262,21 +262,31 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # assert False if isinstance(expression, Int): - return Literal(str(expression.value), ElementaryType("uint256")) + 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? - return Literal(str(expression.value), ElementaryType("address")) + literal = Literal(str(expression.value), ElementaryType("address")) + literal.set_offset(expression.src, caller_context.compilation_unit) + return literal if isinstance(expression, Str): - return Literal(str(expression.value), ElementaryType("string")) + literal = Literal(str(expression.value), ElementaryType("string")) + literal.set_offset(expression.src, caller_context.compilation_unit) + return literal if isinstance(expression, Bytes): - return Literal(str(expression.value), ElementaryType("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"] - return Literal(str(expression.value), ElementaryType("bool")) + 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) @@ -284,21 +294,30 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction): if called.value.name == "empty()": type_to = parse_type(expression.args[0], caller_context) - return CallExpression(called, [], str(type_to)) + parsed_expr = CallExpression(called, [], str(type_to)) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) + return parsed_expr + elif called.value.name == "convert()": arg = parse_expression(expression.args[0], caller_context) type_to = parse_type(expression.args[1], caller_context) - return TypeConversion(arg, type_to) + parsed_expr = TypeConversion(arg, type_to) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) + return parsed_expr + elif called.value.name== "min_value()": type_to = parse_type(expression.args[0], caller_context) member_type = str(type_to) # TODO return Literal - return MemberAccess("min", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) + parsed_expr = MemberAccess("min", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) + return parsed_expr + elif called.value.name== "max_value()": type_to = parse_type(expression.args[0], caller_context) member_type = str(type_to) # TODO return Literal - return MemberAccess("max", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) + parsed_expr = MemberAccess("max", member_type, CallExpression(Identifier(SolidityFunction("type()")), [ElementaryTypeNameExpression(type_to)], member_type)) + return parsed_expr if expression.args and isinstance(expression.args[0], VyDict): @@ -320,10 +339,13 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # 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. type_to = parse_type(expression.func, caller_context) - return TypeConversion(arguments[0], type_to) + parsed_expr = TypeConversion(arguments[0], type_to) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) + return parsed_expr else: rets = ["tuple()"] + elif isinstance(called, MemberAccess) and called.type is not None: # (recover_type_2) Propagate the type collected to the `CallExpression` # see recover_type_1 @@ -337,9 +359,10 @@ def get_type_str(x): return str(x.type) print(rets) type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" - print(CallExpression(called, arguments, type_str)) - print(type_str) - return CallExpression(called, arguments, type_str) + + 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 @@ -348,17 +371,25 @@ def get_type_str(x): if expression.value.id == "self": var, was_created = find_variable(member_name, caller_context) # TODO replace with self - return SuperIdentifier(var) + if was_created: + var.set_offset(expression.src, caller_context.compilation_unit) + parsed_expr = SuperIdentifier(var) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) + 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 if expression.attr == "address": - return TypeConversion(expr, ElementaryType("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) - # member_access.set_offset(src, caller_context.compilation_unit) + if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: - id_idx = Identifier(SolidityVariableComposed(str(member_access))) - # id_idx.set_offset(src, caller_context.compilation_unit) - return id_idx + 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) @@ -387,35 +418,46 @@ def get_type_str(x): 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, was_created = find_variable(expression.id, caller_context) - - assert var - return Identifier(var) + if was_created: + var.set_offset(expression.src, caller_context.compilation_unit) + 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) - return AssignmentOperation(lhs, rhs, AssignmentOperationType.ASSIGN, None) + 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) - return AssignmentOperation(lhs, rhs, op, None) + 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] - return TupleExpression(tuple_vars) + 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? - return UnaryOperation(operand, op) + 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) @@ -440,25 +482,55 @@ def get_type_str(x): for elem in expression.right.elements: elem_expr = parse_expression(elem, caller_context) print("elem", repr(elem_expr)) - conditions.append(BinaryOperation(lhs, elem_expr, inner_op)) + parsed_expr = BinaryOperation(lhs, elem_expr, inner_op) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) + conditions.append(parsed_expr) else: - inner_op = BinaryOperationType.get_type("|") #if expression.op == "NotIn" else BinaryOperationType.get_type("==") - outer_op = BinaryOperationType.get_type("&") #if expression.op == "NotIn" else BinaryOperationType.get_type("||") - - x, _ = find_variable(expression.right.value.attr, caller_context) - print(x) - print(x.type.type_to) - print(x.type.type_to.__class__) - enum_members = x.type.type_to.type.values - # for each value, create a literal with value = 2 ^ n (0 indexed) - # and then translate to bitmasking - enum_values = [Literal(2 ** n, ElementaryType("uint256")) for n in range(len(enum_members))] - inner_lhs = enum_values[0] - for expr in enum_values[1:]: - inner_lhs = BinaryOperation(inner_lhs, expr, inner_op) - conditions.append(inner_lhs) - print(conditions) - return BinaryOperation(lhs, conditions[0], outer_op) + rhs = parse_expression(expression.right, caller_context) + print(rhs) + print(rhs.__class__.__name__) + if isinstance(rhs, Identifier): + if isinstance(rhs.value.type, ArrayType): + inner_op = BinaryOperationType.get_type("!=") if expression.op == "NotIn" else BinaryOperationType.get_type("==") + outer_op = BinaryOperationType.get_type("&&") if expression.op == "NotIn" else BinaryOperationType.get_type("||") + + enum_members = rhs.value.type.length_value.value + for i in range(enum_members): + elem_expr = 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) + # elif isinstance(rhs.value.type, UserDefinedType): + + else: + assert False + else: + # This is an indexaccess like hashmap[address, Roles] + inner_op = BinaryOperationType.get_type("|") #if expression.op == "NotIn" else BinaryOperationType.get_type("==") + outer_op = BinaryOperationType.get_type("&") #if expression.op == "NotIn" else BinaryOperationType.get_type("||") + + # x, _ = find_variable(expression.right.value.attr, caller_context) + # print(x) + # print(x.type.type_to) + # print(x.type.type_to.__class__) + print(repr(rhs)) + print(rhs) + + enum_members = rhs.expression_left.value.type.type_to.type.values + # for each value, create a literal with value = 2 ^ n (0 indexed) + # and then translate to bitmasking + enum_values = [Literal(str(2 ** n), ElementaryType("uint256")) for n in range(len(enum_members))] + inner_lhs = enum_values[0] + for expr in enum_values[1:]: + inner_lhs = BinaryOperation(inner_lhs, expr, inner_op) + conditions.append(inner_lhs) + + parsed_expr = BinaryOperation(lhs, conditions[0], outer_op) + parsed_expr.set_offset(lhs.source_mapping, caller_context.compilation_unit) + return parsed_expr + + while len(conditions) > 1: lhs = conditions.pop() @@ -470,16 +542,20 @@ def get_type_str(x): else: rhs = parse_expression(expression.right, caller_context) - - op = BinaryOperationType.get_type(expression.op) - return BinaryOperation(lhs, rhs, op) + 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) - return BinaryOperation(lhs, rhs, 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`. @@ -492,20 +568,24 @@ def get_type_str(x): func = SolidityFunction("require(bool,string)") args = [parse_expression(expression.test, caller_context), parse_expression(expression.msg, caller_context)] - return CallExpression(Identifier(func), args, type_str) + 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) - index = IndexAccess(left_expression, right_expression) - # index.set_offset(src, caller_context.compilation_unit) - return index + 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) TODO update BoolOp AST - return BinaryOperation(lhs, rhs,BinaryOperationType.ANDAND) + parsed_expr = BinaryOperation(lhs, rhs,BinaryOperationType.ANDAND) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) + return parsed_expr raise ParsingError(f"Expression not parsed {expression}") From 011c03a3de1f3b69437c9d2ea7e81ec96e4f4371 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 15:33:59 -0500 Subject: [PATCH 20/69] control flow graph (loops and ifs) --- .../vyper_parsing/declarations/function.py | 147 +++++++++++------- 1 file changed, 90 insertions(+), 57 deletions(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index d1b075864a..db32ac6adb 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -63,7 +63,7 @@ def __init__( self._params_was_analyzed = False self._content_was_analyzed = False - # self._counter_scope_local_variables = 0 + self._counter_scope_local_variables = 0 # # variable renamed will map the solc id # # to the variable. It only works for compact format # # Later if an expression provides the referencedDeclaration attr @@ -113,14 +113,14 @@ def _add_local_variable(self, local_var_parser: LocalVariableVyper) -> None: # Use of while in case of collision # In the worst case, the name will be really long # TODO no shadowing? - # 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] + 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 @@ -207,12 +207,13 @@ def _new_node( def _parse_cfg(self, cfg: Dict) -> None: - curr_node = self._new_node(NodeType.ENTRYPOINT, "-1:-1:-1", self.underlying_function) - self._function.entry_point = curr_node.underlying_node + 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) if cfg: self._function.is_empty = False + curr_node = entry_node for expr in cfg: def parse_statement(curr_node, expr): if isinstance(expr, AnnAssign): @@ -237,6 +238,9 @@ def parse_statement(curr_node, expr): new_node.add_unparsed_expression(expr) link_underlying_nodes(curr_node, new_node) + curr_node = new_node + + # elif isinstance(expr, Assign): # new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) # new_node.add_unparsed_expression(expr.target) @@ -246,6 +250,7 @@ def parse_statement(curr_node, expr): elif isinstance(expr, For): node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope) + link_underlying_nodes(curr_node, node_startLoop) local_var = LocalVariable() local_var.set_function(self._function) @@ -254,19 +259,22 @@ def parse_statement(curr_node, expr): 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) - new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) + counter_node = self._new_node(NodeType.VARIABLE, expr.src, scope) local_var.initialized = True - new_node.add_unparsed_expression(counter_var.value) - new_node.underlying_node.add_variable_declaration(local_var) + 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 + 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 + else: # local variable iter_expr = expr.iter loop_iterator = list(filter(lambda x: x._variable.name == iter_expr.id, self._local_variables_parser))[0] @@ -275,7 +283,7 @@ def parse_statement(curr_node, expr): node_condition = self._new_node(NodeType.IFLOOP, expr.src, scope) node_condition.add_unparsed_expression(cond_expr) - # TODO this should go in the body of the loop: expr.body.insert(0, ...) + if loop_iterator._elem_to_parse.value.id == "DynArray": loop_var_annotation = loop_iterator._elem_to_parse.slice.value.elements[0] else: @@ -283,74 +291,99 @@ def parse_statement(curr_node, expr): 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) - 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, loop_var) - self._add_local_variable(local_var_parser) - new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) - local_var.initialized = True - new_node.add_unparsed_expression(loop_var.value) - new_node.underlying_node.add_variable_declaration(local_var) - elif isinstance(expr.iter, Call): + 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")) - 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, loop_var) - self._add_local_variable(local_var_parser) - new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) - local_var.initialized = True - new_node.add_unparsed_expression(loop_var.value) - new_node.underlying_node.add_variable_declaration(local_var) + else: - print(expr) raise NotImplementedError - # assert False - node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) - - - - # link_underlying_nodes(node_startLoop, node_condition) + + # link + link_underlying_nodes(counter_node, node_condition) + + + # We update the index variable or range variable in the loop body + expr.body.insert(0, loop_var) + body_node = None + new_node = node_condition for stmt in expr.body: - parse_statement(curr_node, stmt) - - # link_underlying_nodes(curr_node, new_node) + body_node = parse_statement(new_node, stmt) + new_node = body_node + + node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) + + 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) + + 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): pass elif isinstance(expr, Break): pass elif isinstance(expr, Return): - node_parser = self._new_node(NodeType.RETURN, expr.src, scope) + new_node = self._new_node(NodeType.RETURN, expr.src, scope) if expr.value is not None: - node_parser.add_unparsed_expression(expr.value) + new_node.add_unparsed_expression(expr.value) + + link_underlying_nodes(curr_node, new_node) + curr_node = new_node - pass 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) - pass + + link_underlying_nodes(curr_node, new_node) + curr_node = new_node + elif isinstance(expr, If): - new_node = self._new_node(NodeType.IF, expr.test.src, scope) - new_node.add_unparsed_expression(expr.test) + 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: - parse_statement(new_node, stmt) - + true_node = parse_statement(new_node, stmt) + new_node = true_node + # link_underlying_nodes(condition_node, true_node) + link_underlying_nodes(true_node, endIf_node) + + false_node = None + new_node = condition_node for stmt in expr.orelse: - parse_statement(new_node, stmt) + false_node = parse_statement(new_node, stmt) + new_node = false_node + + if false_node is not None: + # link_underlying_nodes(condition_node, false_node) + 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 - pass elif isinstance(expr, Expr): pass elif isinstance(expr, Pass): From 6bd52fac0edfa1fa406c563e3bb92335c14f13f6 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 15:34:25 -0500 Subject: [PATCH 21/69] more builtins --- slither/core/declarations/solidity_variables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 85844508eb..ec8321ee5f 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -108,9 +108,10 @@ "max()":[], "shift()":[], "abs()":[], - "raw_call()":[], + "raw_call()":["bool", "bytes32"], "_abi_encode()":[], "slice()":[], + "uint2str()":["string"], } From 5443132c2b3151c39d0e602fa24af0a607cee38a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 16:20:21 -0500 Subject: [PATCH 22/69] fix source mapping, set source code and source unit --- slither/vyper_parsing/vyper_compilation_unit.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index f8a2ca216d..f43d85d00b 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -1,12 +1,12 @@ 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.declarations.struct import Structure -from slither.core.variables.state_variable import StateVariable - +from slither.vyper_parsing.ast.types import Module from slither.exceptions import SlitherException @@ -18,7 +18,16 @@ class VyperCompilationUnit: _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: Dict, filename: str): + 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 not filename 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) From a61ca34226cc14148a6eb647283f818e02f26acd Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 16:45:01 -0500 Subject: [PATCH 23/69] parse bool op symbol --- slither/vyper_parsing/ast/ast.py | 4 +++- slither/vyper_parsing/expressions/expression_parsing.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index ca748954a1..5c7eb24897 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -316,10 +316,12 @@ def parse_aug_assign(raw: Dict) -> AugAssign: 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=raw["op"], values=[parse(x) for x in raw["values"]], **_extract_base_props(raw) + op=op_str, values=[parse(x) for x in raw["values"]], **_extract_base_props(raw) ) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 8ce7577326..a84eacb2f5 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -583,8 +583,8 @@ def get_type_str(x): lhs = parse_expression(expression.values[0], caller_context) rhs = parse_expression(expression.values[1], caller_context) - # op = BinaryOperationType.get_type(expression.op) TODO update BoolOp AST - parsed_expr = BinaryOperation(lhs, rhs,BinaryOperationType.ANDAND) + op = BinaryOperationType.get_type(expression.op) + parsed_expr = BinaryOperation(lhs, op) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr From 4b07cbbc56ce0483cd5047ef489b19a7897fff37 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 20:07:57 -0500 Subject: [PATCH 24/69] add support for self.balance --- slither/core/declarations/solidity_variables.py | 1 + slither/vyper_parsing/expressions/expression_parsing.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index ec8321ee5f..03fd81f041 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -39,6 +39,7 @@ # Vyper "chain.id": "uint256", "block.prevhash": "bytes32", + "self.balance": "uint256", } diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index a84eacb2f5..441c078eeb 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -368,7 +368,7 @@ def get_type_str(x): member_name = expression.attr if isinstance(expression.value, Name): - if expression.value.id == "self": + if expression.value.id == "self" and member_name != "balance": var, was_created = find_variable(member_name, caller_context) # TODO replace with self if was_created: From db88cd5855b1dd7e97b9ea6374be9942c4723403 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 20:15:17 -0500 Subject: [PATCH 25/69] fmt --- slither/vyper_parsing/ast/ast.py | 57 ++++- slither/vyper_parsing/ast/types.py | 2 - .../vyper_parsing/declarations/contract.py | 231 +++++++++++++++++- .../vyper_parsing/declarations/function.py | 130 +++++++--- .../expressions/expression_parsing.py | 174 +++++++++---- .../expressions/find_variable.py | 9 +- slither/vyper_parsing/type_parsing.py | 10 +- .../vyper_parsing/variables/local_variable.py | 1 - .../vyper_parsing/variables/state_variable.py | 5 +- .../vyper_parsing/vyper_compilation_unit.py | 6 +- 10 files changed, 511 insertions(+), 114 deletions(-) diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index 5c7eb24897..228805c895 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -197,26 +197,56 @@ def parse_raise(raw: Dict) -> Raise: 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"} +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) + 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) + left=parse(raw["left"]), + op=logical_op_str, + right=parse(raw["right"]), + **_extract_base_props(raw), ) @@ -301,7 +331,20 @@ def parse_enum_def(raw: Dict) -> EnumDef: 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": ">>="} + +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"]] @@ -316,13 +359,13 @@ def parse_aug_assign(raw: Dict) -> AugAssign: 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) - ) + return BoolOp(op=op_str, values=[parse(x) for x in raw["values"]], **_extract_base_props(raw)) def parse(raw: Dict) -> ASTNode: diff --git a/slither/vyper_parsing/ast/types.py b/slither/vyper_parsing/ast/types.py index 619df4f147..e07e6d2132 100644 --- a/slither/vyper_parsing/ast/types.py +++ b/slither/vyper_parsing/ast/types.py @@ -184,8 +184,6 @@ class UnaryOp(ASTNode): operand: ASTNode - - @dataclass class BinOp(ASTNode): left: ASTNode diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index fde720340a..ed61bda1b7 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -180,7 +180,236 @@ def _parse_contract_items(self) -> 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)]), + "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": [], diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index db32ac6adb..9e57b6c66d 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -180,8 +180,6 @@ def analyze_content(self) -> None: for node_parser in self._node_to_NodeVyper.values(): node_parser.analyze_expressions(self._function) - - # endregion ################################################################################### ################################################################################### @@ -206,7 +204,6 @@ def _new_node( def _parse_cfg(self, cfg: Dict) -> 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) @@ -215,6 +212,7 @@ def _parse_cfg(self, cfg: Dict) -> None: self._function.is_empty = False curr_node = entry_node for expr in cfg: + def parse_statement(curr_node, expr): if isinstance(expr, AnnAssign): local_var = LocalVariable() @@ -240,7 +238,6 @@ def parse_statement(curr_node, expr): curr_node = new_node - # elif isinstance(expr, Assign): # new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) # new_node.add_unparsed_expression(expr.target) @@ -256,7 +253,13 @@ def parse_statement(curr_node, expr): 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)) + 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) @@ -267,45 +270,93 @@ def parse_statement(curr_node, expr): link_underlying_nodes(node_startLoop, counter_node) node_condition = None - if isinstance(expr.iter, (Attribute,Name)): - # HACK + 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 + 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 + 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] + 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)) + 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] + 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 + 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) + 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")) - + 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 - - # link + + # link link_underlying_nodes(counter_node, node_condition) - # We update the index variable or range variable in the loop body expr.body.insert(0, loop_var) body_node = None @@ -313,17 +364,23 @@ def parse_statement(curr_node, expr): for stmt in expr.body: body_node = parse_statement(new_node, stmt) new_node = body_node - + node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) - - loop_increment = AugAssign("-1:-1:-1", -1, target=Name("-1:-1:-1", -1, "counter_var"), op="+=", value=Int("-1:-1:-1", -1, 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) - + if body_node is not None: link_underlying_nodes(body_node, node_increment) - + link_underlying_nodes(node_condition, node_endLoop) curr_node = node_endLoop @@ -357,8 +414,8 @@ def parse_statement(curr_node, expr): 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) + + endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope) true_node = None new_node = condition_node @@ -367,20 +424,20 @@ def parse_statement(curr_node, expr): new_node = true_node # link_underlying_nodes(condition_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) new_node = false_node - + if false_node is not None: # link_underlying_nodes(condition_node, false_node) 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 @@ -396,6 +453,7 @@ def parse_statement(curr_node, expr): print(f"isinstance(expr, {expr.__class__.__name__})") assert False return curr_node + curr_node = parse_statement(curr_node, expr) # self._parse_block(cfg, node, self.underlying_function) else: @@ -437,7 +495,7 @@ def _parse_returns(self, returns: Union[Name, Tuple, Subscript]): print(returns) 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 + # 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)) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 441c078eeb..326fe2c754 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -254,7 +254,28 @@ def _user_defined_op_call( from collections import deque -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 +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, +) + def parse_expression(expression: Dict, caller_context) -> "Expression": print("parse_expression") @@ -265,18 +286,18 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": 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? - literal = Literal(str(expression.value), ElementaryType("address")) + 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 = 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) @@ -287,7 +308,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": 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) @@ -297,29 +318,44 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": parsed_expr = CallExpression(called, [], str(type_to)) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr - + elif called.value.name == "convert()": - arg = parse_expression(expression.args[0], caller_context) + 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 - elif called.value.name== "min_value()": + elif called.value.name == "min_value()": type_to = parse_type(expression.args[0], caller_context) - member_type = str(type_to) + 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 = MemberAccess( + "min", + member_type, + CallExpression( + Identifier(SolidityFunction("type()")), + [ElementaryTypeNameExpression(type_to)], + member_type, + ), + ) return parsed_expr - - elif called.value.name== "max_value()": + + elif called.value.name == "max_value()": type_to = parse_type(expression.args[0], caller_context) - member_type = str(type_to) + 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 = MemberAccess( + "max", + member_type, + CallExpression( + Identifier(SolidityFunction("type()")), + [ElementaryTypeNameExpression(type_to)], + member_type, + ), + ) return parsed_expr - if expression.args and isinstance(expression.args[0], VyDict): arguments = [] for val in expression.args[0].values: @@ -342,7 +378,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": parsed_expr = TypeConversion(arguments[0], type_to) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr - + else: rets = ["tuple()"] @@ -352,23 +388,28 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": rets = [called.type] else: rets = ["tuple()"] - + def get_type_str(x): if isinstance(x, str): return x return str(x.type) + print(rets) - type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" + type_str = ( + get_type_str(rets[0]) + if len(rets) == 1 + else f"tuple({','.join(map(get_type_str, 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): - if expression.value.id == "self" and member_name != "balance": + if expression.value.id == "self" and member_name != "balance": var, was_created = find_variable(member_name, caller_context) # TODO replace with self if was_created: @@ -376,7 +417,7 @@ def get_type_str(x): parsed_expr = SuperIdentifier(var) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) 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 if expression.attr == "address": @@ -385,7 +426,7 @@ def get_type_str(x): 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) @@ -395,23 +436,28 @@ def get_type_str(x): 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 + # so we see if there's a function identifier with `member_name` and propagate the type to # its enclosing `CallExpression` # TODO this is using the wrong caller_context and needs to be interface instead of self namespace print(expr) print(expr.__class__.__name__) if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType): - # try: + # try: var, was_created = find_variable(member_name, expr.type.type) if isinstance(var, Function): rets = var.returns + def get_type_str(x): if isinstance(x, str): return x return str(x.type) - type_str = get_type_str(rets[0]) if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" + type_str = ( + get_type_str(rets[0]) + if len(rets) == 1 + else f"tuple({','.join(map(get_type_str, rets))})" + ) member_name_ret_type = type_str # except: # pass @@ -428,14 +474,14 @@ def get_type_str(x): 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) @@ -450,10 +496,12 @@ def get_type_str(x): 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? + 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) @@ -476,9 +524,17 @@ def get_type_str(x): # We assume left operand in membership comparison cannot be Array type conditions = deque() if isinstance(expression.right, VyList): - inner_op = BinaryOperationType.get_type("!=") if expression.op == "NotIn" else BinaryOperationType.get_type("==") - outer_op = BinaryOperationType.get_type("&&") if expression.op == "NotIn" else BinaryOperationType.get_type("||") - + inner_op = ( + BinaryOperationType.get_type("!=") + if expression.op == "NotIn" + else BinaryOperationType.get_type("==") + ) + outer_op = ( + BinaryOperationType.get_type("&&") + if expression.op == "NotIn" + else BinaryOperationType.get_type("||") + ) + for elem in expression.right.elements: elem_expr = parse_expression(elem, caller_context) print("elem", repr(elem_expr)) @@ -491,15 +547,27 @@ def get_type_str(x): print(rhs.__class__.__name__) if isinstance(rhs, Identifier): if isinstance(rhs.value.type, ArrayType): - inner_op = BinaryOperationType.get_type("!=") if expression.op == "NotIn" else BinaryOperationType.get_type("==") - outer_op = BinaryOperationType.get_type("&&") if expression.op == "NotIn" else BinaryOperationType.get_type("||") - + inner_op = ( + BinaryOperationType.get_type("!=") + if expression.op == "NotIn" + else BinaryOperationType.get_type("==") + ) + outer_op = ( + BinaryOperationType.get_type("&&") + if expression.op == "NotIn" + else BinaryOperationType.get_type("||") + ) + enum_members = rhs.value.type.length_value.value for i in range(enum_members): elem_expr = IndexAccess(rhs, Literal(str(i), ElementaryType("uint256"))) - elem_expr.set_offset(rhs.source_mapping, caller_context.compilation_unit) + 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) + parsed_expr.set_offset( + lhs.source_mapping, caller_context.compilation_unit + ) conditions.append(parsed_expr) # elif isinstance(rhs.value.type, UserDefinedType): @@ -507,8 +575,12 @@ def get_type_str(x): assert False else: # This is an indexaccess like hashmap[address, Roles] - inner_op = BinaryOperationType.get_type("|") #if expression.op == "NotIn" else BinaryOperationType.get_type("==") - outer_op = BinaryOperationType.get_type("&") #if expression.op == "NotIn" else BinaryOperationType.get_type("||") + inner_op = BinaryOperationType.get_type( + "|" + ) # if expression.op == "NotIn" else BinaryOperationType.get_type("==") + outer_op = BinaryOperationType.get_type( + "&" + ) # if expression.op == "NotIn" else BinaryOperationType.get_type("||") # x, _ = find_variable(expression.right.value.attr, caller_context) # print(x) @@ -520,7 +592,10 @@ def get_type_str(x): enum_members = rhs.expression_left.value.type.type_to.type.values # for each value, create a literal with value = 2 ^ n (0 indexed) # and then translate to bitmasking - enum_values = [Literal(str(2 ** n), ElementaryType("uint256")) for n in range(len(enum_members))] + enum_values = [ + Literal(str(2**n), ElementaryType("uint256")) + for n in range(len(enum_members)) + ] inner_lhs = enum_values[0] for expr in enum_values[1:]: inner_lhs = BinaryOperation(inner_lhs, expr, inner_op) @@ -530,8 +605,6 @@ def get_type_str(x): parsed_expr.set_offset(lhs.source_mapping, caller_context.compilation_unit) return parsed_expr - - while len(conditions) > 1: lhs = conditions.pop() rhs = conditions.pop() @@ -543,7 +616,7 @@ def get_type_str(x): else: 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 @@ -556,7 +629,7 @@ def get_type_str(x): 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`? @@ -566,19 +639,22 @@ def get_type_str(x): 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)] + 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) diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index b75b688db9..1c6058c1a1 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -36,7 +36,6 @@ # CallerContext =Union["ContractSolc", "FunctionSolc", "CustomErrorSolc", "StructureTopLevelSolc"] - def _find_variable_in_function_parser( var_name: str, function_parser: Optional["FunctionSolc"], @@ -51,8 +50,6 @@ def _find_variable_in_function_parser( return None - - def _find_in_contract( var_name: str, contract: Optional[Contract], @@ -67,8 +64,6 @@ def _find_in_contract( 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} # print(functions) if var_name in functions: @@ -106,7 +101,6 @@ def _find_in_contract( if var_name in enums: return enums[var_name] - return None @@ -157,6 +151,7 @@ def find_variable( # structure/enums cannot be shadowed from slither.vyper_parsing.declarations.contract import ContractVyper from slither.vyper_parsing.declarations.function import FunctionVyper + print("caller_context") print(caller_context) print(caller_context.__class__.__name__) @@ -208,5 +203,5 @@ def find_variable( print(next_context.events_as_dict) if f"{var_name}()" in next_context.events_as_dict: return next_context.events_as_dict[f"{var_name}()"], False - + raise VariableNotFound(f"Variable not found: {var_name} (context {caller_context})") diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index 15fc16df61..a7d83240ea 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -11,11 +11,12 @@ from slither.core.declarations.function_contract import FunctionContract + def parse_type(annotation: Union[Name, Subscript, Call], caller_context): from slither.vyper_parsing.expressions.expression_parsing import parse_expression if isinstance(caller_context, FunctionContract): - contract = caller_context.contract + contract = caller_context.contract else: contract = caller_context @@ -41,17 +42,16 @@ def parse_type(annotation: Union[Name, Subscript, Call], caller_context): 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. # Subscript(src='13:10:0', node_id=7, value=Name(src='13:6:0', node_id=8, id='String'), slice=Index(src='13:10:0', node_id=12, value=Int(src='20:2:0', node_id=10, value=64))) type_ = parse_type(annotation.value, caller_context) if annotation.value.id == "String": return type_ - + length = parse_expression(annotation.slice.value, caller_context) return ArrayType(type_, length) - elif isinstance(annotation, Call): return parse_type(annotation.args[0], caller_context) @@ -63,8 +63,6 @@ def parse_type(annotation: Union[Name, Subscript, Call], caller_context): if lname in ElementaryTypeName: return ElementaryType(lname) - - if name in contract.structures_as_dict: return UserDefinedType(contract.structures_as_dict[name]) diff --git a/slither/vyper_parsing/variables/local_variable.py b/slither/vyper_parsing/variables/local_variable.py index 3651b701fd..d3bd0b0557 100644 --- a/slither/vyper_parsing/variables/local_variable.py +++ b/slither/vyper_parsing/variables/local_variable.py @@ -27,7 +27,6 @@ def __init__(self, variable: LocalVariable, variable_data: Union[Arg, Name]) -> self._variable.set_location("default") - @property def underlying_variable(self) -> LocalVariable: return self._variable diff --git a/slither/vyper_parsing/variables/state_variable.py b/slither/vyper_parsing/variables/state_variable.py index f17a4132ef..a2e925a6ea 100644 --- a/slither/vyper_parsing/variables/state_variable.py +++ b/slither/vyper_parsing/variables/state_variable.py @@ -5,6 +5,7 @@ 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 @@ -16,7 +17,7 @@ def __init__(self, variable: StateVariable, variable_data: VariableDecl) -> None if variable_data.value is not None: self._variable.initialized = True - self._initializedNotParsed = variable_data.value + self._initializedNotParsed = variable_data.value @property def underlying_variable(self) -> StateVariable: @@ -24,7 +25,7 @@ def underlying_variable(self) -> StateVariable: 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 diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index f43d85d00b..88ff43d1e1 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -51,9 +51,9 @@ def parse_contracts(self): def analyze_contracts(self) -> None: if not self._parsed: raise SlitherException("Parse the contract before running analyses") - + for contract, contract_parser in self._underlying_contract_to_parser.items(): - # State variables are analyzed for all contracts because interfaces may + # State variables are analyzed for all contracts because interfaces may # reference them, specifically, constants. contract_parser.analyze_state_variables() @@ -73,7 +73,7 @@ def _convert_to_slithir(self) -> None: 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() From 85e63c6ba5772a6c3aced61a5d95669c61edc320 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 20:21:40 -0500 Subject: [PATCH 26/69] rename fixture --- tests/conftest.py | 2 +- tests/unit/slithir/test_ssa_generation.py | 96 +++++++++++------------ 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 63fccfa120..1b9f44c52b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -59,7 +59,7 @@ def inner(version): @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. diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 3c7e84973f..6b1a1d1023 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -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] From 55ab580a758ddcf73f137e4dbf14591a8a7d69fa Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 22:23:34 -0500 Subject: [PATCH 27/69] convert raw_call to LowLevelCall --- slither/slithir/convert.py | 7 +++++-- .../expressions/expression_parsing.py | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index d40715c4f3..d1eaafdfac 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1131,6 +1131,7 @@ def can_be_low_level(ir: HighLevelCall) -> bool: "delegatecall", "callcode", "staticcall", + "raw_call", ] @@ -1159,13 +1160,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: @@ -1916,7 +1918,8 @@ 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) + # TODO refine only for Solidity + # convert_constant_types(irs) convert_delete(irs) _find_source_mapping_references(irs) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 326fe2c754..c5da11fbec 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -311,10 +311,10 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": 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 @@ -355,7 +355,19 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": ), ) return parsed_expr - + + elif 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: From 4c1ad519d037374705fbda32eefcb6f8648bdb50 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 22:30:01 -0500 Subject: [PATCH 28/69] handle edge case for vyper Expr nodes --- slither/vyper_parsing/declarations/function.py | 15 ++++++++------- .../expressions/expression_parsing.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 9e57b6c66d..98c3728080 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -238,11 +238,14 @@ def parse_statement(curr_node, expr): curr_node = new_node - # elif isinstance(expr, Assign): - # new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) - # new_node.add_unparsed_expression(expr.target) - # new_node.add_unparsed_expression(expr.value) - # link_underlying_nodes(curr_node, new_node) + elif isinstance(expr, Expr): + # TODO This is a workaround to handle Vyper putting payable/view in the function body... + 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): @@ -441,8 +444,6 @@ def parse_statement(curr_node, expr): link_underlying_nodes(curr_node, condition_node) curr_node = endIf_node - elif isinstance(expr, Expr): - pass elif isinstance(expr, Pass): pass elif isinstance(expr, Raise): diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index c5da11fbec..09154d6b94 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -672,7 +672,7 @@ def get_type_str(x): rhs = parse_expression(expression.values[1], caller_context) op = BinaryOperationType.get_type(expression.op) - parsed_expr = BinaryOperation(lhs, op) + parsed_expr = BinaryOperation(lhs, rhs, op) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr From 2bfe8290b9cd5f88002a8821a9292e9590455201 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 23:05:19 -0500 Subject: [PATCH 29/69] add ability to perform filtering by language to AbstractDetector --- slither/detectors/abstract_detector.py | 24 +++++++++++++++---- .../naming_convention/naming_convention.py | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 7bb8eb93fb..b6fe49d30e 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -3,7 +3,7 @@ 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 @@ def __init__( 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 @@ def _log(self, info: str) -> None: 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 == self.LANGUAGE return True @abc.abstractmethod @@ -179,7 +195,7 @@ def detect(self) -> List[Dict]: 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 diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 02deb719e7..c456f62a30 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -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" From 77637d90f0132433bc9daa13168b9797aa53ac64 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 23:13:36 -0500 Subject: [PATCH 30/69] delete unused code, misc. notes --- slither/vyper_parsing/declarations/event.py | 3 - .../expressions/expression_parsing.py | 233 ++---------------- .../vyper_parsing/vyper_compilation_unit.py | 36 --- 3 files changed, 24 insertions(+), 248 deletions(-) diff --git a/slither/vyper_parsing/declarations/event.py b/slither/vyper_parsing/declarations/event.py index a0152ec0ec..e6ed5a12e3 100644 --- a/slither/vyper_parsing/declarations/event.py +++ b/slither/vyper_parsing/declarations/event.py @@ -22,9 +22,6 @@ def __init__(self, event: Event, event_def: EventDef) -> None: self._event = event self._event.name = event_def.name self._elemsNotParsed = event_def.body - print(event_def) - # assert False - # self.analyze() # TODO create `VariableDecl` from `AnnAssign` from `event_def.body` (also for `StructDef`) def analyze(self, contract) -> None: for elem_to_parse in self._elemsNotParsed: diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 09154d6b94..36c7c3ee5b 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -49,209 +49,6 @@ if TYPE_CHECKING: from slither.core.expressions.expression import Expression -logger = logging.getLogger("ExpressionParsing") - -# pylint: disable=anomalous-backslash-in-string,import-outside-toplevel,too-many-branches,too-many-locals - -# region Filtering -################################################################################### -################################################################################### - - -def filter_name(value: str) -> str: - value = value.replace(" memory", "") - value = value.replace(" storage", "") - value = value.replace(" external", "") - value = value.replace(" internal", "") - value = value.replace("struct ", "") - value = value.replace("contract ", "") - value = value.replace("enum ", "") - value = value.replace(" ref", "") - value = value.replace(" pointer", "") - value = value.replace(" pure", "") - value = value.replace(" view", "") - value = value.replace(" constant", "") - value = value.replace(" payable", "") - value = value.replace("function (", "function(") - value = value.replace("returns (", "returns(") - value = value.replace(" calldata", "") - - # remove the text remaining after functio(...) - # which should only be ..returns(...) - # nested parenthesis so we use a system of counter on parenthesis - idx = value.find("(") - if idx: - counter = 1 - max_idx = len(value) - while counter: - assert idx < max_idx - idx = idx + 1 - if value[idx] == "(": - counter += 1 - elif value[idx] == ")": - counter -= 1 - value = value[: idx + 1] - return value - - -# endregion - -################################################################################### -################################################################################### -# region Parsing -################################################################################### -################################################################################### - -# pylint: disable=too-many-statements -def parse_call( - expression: Dict, caller_context -) -> Union[ - slither.core.expressions.call_expression.CallExpression, - slither.core.expressions.type_conversion.TypeConversion, -]: - src = expression["src"] - if caller_context.is_compact_ast: - attributes = expression - type_conversion = expression["kind"] == "typeConversion" - type_return = attributes["typeDescriptions"]["typeString"] - - else: - attributes = expression["attributes"] - type_conversion = attributes["type_conversion"] - type_return = attributes["type"] - - if type_conversion: - type_call = parse_type(UnknownType(type_return), caller_context) - if caller_context.is_compact_ast: - assert len(expression["arguments"]) == 1 - expression_to_parse = expression["arguments"][0] - else: - children = expression["children"] - assert len(children) == 2 - type_info = children[0] - expression_to_parse = children[1] - assert type_info["name"] in [ - "ElementaryTypenameExpression", - "ElementaryTypeNameExpression", - "Identifier", - "TupleExpression", - "IndexAccess", - "MemberAccess", - ] - - expression = parse_expression(expression_to_parse, caller_context) - t = TypeConversion(expression, type_call) - t.set_offset(src, caller_context.compilation_unit) - if isinstance(type_call, UserDefinedType): - type_call.type.references.append(t.source_mapping) - return t - - call_gas = None - call_value = None - call_salt = None - if caller_context.is_compact_ast: - called = parse_expression(expression["expression"], caller_context) - # If the next expression is a FunctionCallOptions - # We can here the gas/value information - # This is only available if the syntax is {gas: , value: } - # For the .gas().value(), the member are considered as function call - # And converted later to the correct info (convert.py) - if expression["expression"][caller_context.get_key()] == "FunctionCallOptions": - call_with_options = expression["expression"] - for idx, name in enumerate(call_with_options.get("names", [])): - option = parse_expression(call_with_options["options"][idx], caller_context) - if name == "value": - call_value = option - if name == "gas": - call_gas = option - if name == "salt": - call_salt = option - arguments = [] - if expression["arguments"]: - arguments = [parse_expression(a, caller_context) for a in expression["arguments"]] - else: - children = expression["children"] - called = parse_expression(children[0], caller_context) - arguments = [parse_expression(a, caller_context) for a in children[1::]] - - if isinstance(called, SuperCallExpression): - sp = SuperCallExpression(called, arguments, type_return) - sp.set_offset(expression["src"], caller_context.compilation_unit) - return sp - call_expression = CallExpression(called, arguments, type_return) - call_expression.set_offset(src, caller_context.compilation_unit) - - # Only available if the syntax {gas:, value:} was used - call_expression.call_gas = call_gas - call_expression.call_value = call_value - call_expression.call_salt = call_salt - return call_expression - - -def parse_super_name(expression: Dict, is_compact_ast: bool) -> str: - if is_compact_ast: - assert expression["nodeType"] == "MemberAccess" - base_name = expression["memberName"] - arguments = expression["typeDescriptions"]["typeString"] - else: - assert expression["name"] == "MemberAccess" - attributes = expression["attributes"] - base_name = attributes["member_name"] - arguments = attributes["type"] - - assert arguments.startswith("function ") - # remove function (...() - arguments = arguments[len("function ") :] - - arguments = filter_name(arguments) - if " " in arguments: - arguments = arguments[: arguments.find(" ")] - - return base_name + arguments - - -def _parse_elementary_type_name_expression( - expression: Dict, is_compact_ast: bool, caller_context: CallerContextExpression -) -> ElementaryTypeNameExpression: - # nop exression - # uint; - if is_compact_ast: - value = expression["typeName"] - else: - if "children" in expression: - value = expression["children"][0]["attributes"]["name"] - else: - value = expression["attributes"]["value"] - if isinstance(value, dict): - t = parse_type(value, caller_context) - else: - t = parse_type(UnknownType(value), caller_context) - e = ElementaryTypeNameExpression(t) - e.set_offset(expression["src"], caller_context.compilation_unit) - return e - - -if TYPE_CHECKING: - pass - - -def _user_defined_op_call( - caller_context: CallerContextExpression, src, function_id: int, args: List[Any], type_call: str -) -> CallExpression: - var, was_created = find_variable(None, caller_context, function_id) - - if was_created: - var.set_offset(src, caller_context.compilation_unit) - - identifier = Identifier(var) - identifier.set_offset(src, caller_context.compilation_unit) - - var.references.append(identifier.source_mapping) - - call = CallExpression(identifier, args, type_call) - call.set_offset(src, caller_context.compilation_unit) - return call - from collections import deque from slither.vyper_parsing.ast.types import ( @@ -355,19 +152,37 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": ), ) return parsed_expr - + elif 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 = 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) + 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 - + return call + if expression.args and isinstance(expression.args[0], VyDict): arguments = [] for val in expression.args[0].values: diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index 88ff43d1e1..2650ffe8e7 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -78,39 +78,3 @@ def _convert_to_slithir(self) -> None: for contract in self._compilation_unit.contracts: contract.fix_phi() contract.update_read_write_using_ssa() - - # def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: - - # self._contracts_by_id: Dict[int, ContractSolc] = {} - # self._parsed = False - # self._analyzed = False - - # self._underlying_contract_to_parser: Dict[Contract, ContractSolc] = {} - # self._structures_top_level_parser: List[StructureTopLevelSolc] = [] - # self._custom_error_parser: List[CustomErrorSolc] = [] - # self._variables_top_level_parser: List[TopLevelVariableSolc] = [] - # self._functions_top_level_parser: List[FunctionSolc] = [] - # self._using_for_top_level_parser: List[UsingForTopLevelSolc] = [] - - # self._all_functions_and_modifier_parser: List[FunctionSolc] = [] - - # self._top_level_contracts_counter = 0 - - # @property - # def compilation_unit(self) -> SlitherCompilationUnit: - # return self._compilation_unit - - # @property - # def all_functions_and_modifiers_parser(self) -> List[FunctionSolc]: - # return self._all_functions_and_modifier_parser - - # def add_function_or_modifier_parser(self, f: FunctionSolc) -> None: - # self._all_functions_and_modifier_parser.append(f) - - # @property - # def underlying_contract_to_parser(self) -> Dict[Contract, ContractSolc]: - # return self._underlying_contract_to_parser - - # @property - # def slither_parser(self) -> "SlitherCompilationUnitSolc": - # return self From 372579221d01580a45e7ff7babf307f1ae2afc58 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 23:16:02 -0500 Subject: [PATCH 31/69] delete more unused code --- .../expressions/find_variable.py | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index 1c6058c1a1..5b84570ce8 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -30,11 +30,6 @@ from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.contract import ContractSolc -# pylint: disable=import-outside-toplevel,too-many-branches,too-many-locals - - -# CallerContext =Union["ContractSolc", "FunctionSolc", "CustomErrorSolc", "StructureTopLevelSolc"] - def _find_variable_in_function_parser( var_name: str, @@ -82,20 +77,6 @@ def _find_in_contract( if var_name in enums: return enums[var_name] - # Note: contract.custom_errors_as_dict uses the name (not the sol sig) as key - # This is because when the dic is populated the underlying object is not yet parsed - # As a result, we need to iterate over all the custom errors here instead of using the dict - custom_errors = contract.custom_errors - try: - for custom_error in custom_errors: - if var_name in [custom_error.solidity_signature, custom_error.full_name]: - return custom_error - except ValueError: - # This can happen as custom error sol signature might not have been built - # when find_variable was called - # TODO refactor find_variable to prevent this from happening - pass - # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: @@ -130,25 +111,10 @@ def find_variable( :type var_name: :param caller_context: :type caller_context: - :param referenced_declaration: :return: :rtype: """ - # variable are looked from the contract declarer - # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer - # the difference between function and variable come from the fact that an internal call, or an variable access - # in a function does not behave similariy, for example in: - # contract C{ - # function f(){ - # state_var = 1 - # f2() - # } - # state_var will refer to C.state_var, no mater if C is inherited - # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or - # the contract inheriting from C) - # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact - # structure/enums cannot be shadowed from slither.vyper_parsing.declarations.contract import ContractVyper from slither.vyper_parsing.declarations.function import FunctionVyper From 917c5d6cd6c28719ff8813b89e9c466a3a6b14a6 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 29 Aug 2023 23:21:35 -0500 Subject: [PATCH 32/69] add IncorrectSolc detector to solidity-only detectors --- slither/detectors/attributes/incorrect_solc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index eaf40bf21f..ad38b17843 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -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" From 5197b157a84190a8575d982276bda0aea50cb289 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 08:36:04 -0500 Subject: [PATCH 33/69] fix filtering to use string value of enum --- slither/detectors/abstract_detector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index b6fe49d30e..c3f661a112 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -182,7 +182,7 @@ def _is_applicable_detector(self) -> bool: and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS ) if self.LANGUAGE: - return self.compilation_unit._language == self.LANGUAGE + return self.compilation_unit._language.value == self.LANGUAGE return True @abc.abstractmethod From baa3c980c29a77c688d5c8bd0f293e00f48069b7 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 11:07:37 -0500 Subject: [PATCH 34/69] install vyper in CI --- .github/workflows/test.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3754bfd78..51ea634f11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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" + wget "$URL" -q -O "$INSTALLDIR/$FILENAME" + chmod 755 "$INSTALLDIR/$FILENAME" + echo "$INSTALLDIR" >> "$GITHUB_PATH" - name: Run ${{ matrix.type }} tests env: TEST_TYPE: ${{ matrix.type }} From ae45f461e32e72bc64af793d4fb3a1da8ccb9fab Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 11:13:15 -0500 Subject: [PATCH 35/69] initial tests --- slither/core/declarations/function.py | 23 ++--- tests/conftest.py | 20 +++++ tests/unit/core/test_function_declaration.py | 85 +++++++++++++++++++ tests/unit/slithir/vyper/__init__.py | 0 .../unit/slithir/vyper/test_ir_generation.py | 65 ++++++++++++++ 5 files changed, 182 insertions(+), 11 deletions(-) create mode 100644 tests/unit/slithir/vyper/__init__.py create mode 100644 tests/unit/slithir/vyper/test_ir_generation.py diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index d549c96159..0f1955a923 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -106,7 +106,8 @@ def _filter_state_variables_written(expressions: List["Expression"]): ret.append(expression.expression_left) return ret -#TODO replace + +# TODO replace class FunctionLanguage(Enum): Solidity = 0 Yul = 1 @@ -238,7 +239,7 @@ def name(self) -> str: """ 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 +986,15 @@ def signature(self) -> Tuple[str, List[str], List[str]]: (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: @@ -1758,7 +1760,6 @@ def fix_phi( def generate_slithir_and_analyze(self) -> None: print("generate_slithir_and_analyze") - print(self.nodes) for node in self.nodes: node.slithir_generation() diff --git a/tests/conftest.py b/tests/conftest.py index 1b9f44c52b..5c77dceca5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,3 +77,23 @@ def inner(source_code: str, solc_version: str = "0.8.19"): 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 diff --git a/tests/unit/core/test_function_declaration.py b/tests/unit/core/test_function_declaration.py index 651f449de5..739a113bc5 100644 --- a/tests/unit/core/test_function_declaration.py +++ b/tests/unit/core/test_function_declaration.py @@ -9,6 +9,7 @@ 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,87 @@ def test_public_variable(solc_binary_path) -> None: assert var.signature_str == "info() returns(bytes32)" assert var.visibility == "public" assert var.type == ElementaryType("bytes32") + + +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 + + f = functions["__default__()"] + assert f.function_type == FunctionType.FALLBACK + assert f.visibility == "external" + assert f.payable + assert not f.view + assert not f.pure + + 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() + + # f = functions["withdraw_locked()"] + # assert not f.can_reenter() + + 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")), + ) diff --git a/tests/unit/slithir/vyper/__init__.py b/tests/unit/slithir/vyper/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/slithir/vyper/test_ir_generation.py b/tests/unit/slithir/vyper/test_ir_generation.py new file mode 100644 index 0000000000..3ea5ea9fcd --- /dev/null +++ b/tests/unit/slithir/vyper/test_ir_generation.py @@ -0,0 +1,65 @@ +# # pylint: disable=too-many-lines +import pathlib +from argparse import ArgumentTypeError +from collections import defaultdict +from inspect import getsourcefile +from typing import Union, List, Dict, Callable + +import pytest + +from slither import Slither +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations import Function, Contract +from slither.core.solidity_types import ArrayType +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable +from slither.slithir.operations import ( + OperationWithLValue, + Phi, + Assignment, + HighLevelCall, + Return, + Operation, + Binary, + BinaryType, + InternalCall, + Index, + InitArray, +) +from slither.slithir.utils.ssa import is_used_later +from slither.slithir.variables import ( + Constant, + ReferenceVariable, + LocalIRVariable, + StateIRVariable, + TemporaryVariableSSA, +) + + +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)" From b5778ce5df4b5e4a65e8bbc38e730773f432be39 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 11:22:36 -0500 Subject: [PATCH 36/69] use curl to support windows --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 51ea634f11..ef10a19a54 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: exit 1 fi mkdir -p "$INSTALLDIR" - wget "$URL" -q -O "$INSTALLDIR/$FILENAME" + curl "$URL" -o "$INSTALLDIR/$FILENAME" chmod 755 "$INSTALLDIR/$FILENAME" echo "$INSTALLDIR" >> "$GITHUB_PATH" - name: Run ${{ matrix.type }} tests From 0fd1ed73088714ba824e1dc4258951d347df7fb0 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 13:21:48 -0500 Subject: [PATCH 37/69] follow redirects --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef10a19a54..533940328a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: exit 1 fi mkdir -p "$INSTALLDIR" - curl "$URL" -o "$INSTALLDIR/$FILENAME" + curl "$URL" -o "$INSTALLDIR/$FILENAME" -L chmod 755 "$INSTALLDIR/$FILENAME" echo "$INSTALLDIR" >> "$GITHUB_PATH" - name: Run ${{ matrix.type }} tests From ce6d3c91c8c6bdca8b05a635d9111cfb9520948f Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 13:27:35 -0500 Subject: [PATCH 38/69] point at vyper crytic-compile branch --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 70d4f71fd4..4d3a15c8f2 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ "prettytable>=3.3.0", "pycryptodome>=3.4.6", # "crytic-compile>=0.3.1,<0.4.0", - "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile", + "crytic-compile@git+https://github.com/crytic/crytic-compile.git@feat/vyper-standard-json#egg=crytic-compile", "web3>=6.0.0", "eth-abi>=4.0.0", "eth-typing>=3.0.0", From 929c2af08842c13d3011f31140515a595d94f927 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 14:53:58 -0500 Subject: [PATCH 39/69] add deprecated calls to solidity only detectors --- slither/detectors/statements/deprecated_calls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/statements/deprecated_calls.py b/slither/detectors/statements/deprecated_calls.py index e59d254bb1..5e066c751e 100644 --- a/slither/detectors/statements/deprecated_calls.py +++ b/slither/detectors/statements/deprecated_calls.py @@ -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" From eadcd462c39923b2e7618f2f6da3cac1d32d9d43 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 30 Aug 2023 15:57:43 -0500 Subject: [PATCH 40/69] remove solc imports from vyper parsing --- .../vyper_parsing/expressions/expression_parsing.py | 8 ++++---- slither/vyper_parsing/expressions/find_variable.py | 10 ++++------ .../variables/function_type_variable.py | 13 ------------- 3 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 slither/vyper_parsing/variables/function_type_variable.py diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 36c7c3ee5b..60d5fe2467 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -40,8 +40,6 @@ UserDefinedType, ) from slither.core.declarations.contract import Contract -from slither.solc_parsing.declarations.caller_context import CallerContextExpression -from slither.solc_parsing.exceptions import ParsingError, VariableNotFound from slither.vyper_parsing.expressions.find_variable import find_variable from slither.vyper_parsing.type_parsing import parse_type @@ -107,6 +105,8 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": return literal if isinstance(expression, Call): + print("Call") + print(expression) called = parse_expression(expression.func, caller_context) if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction): if called.value.name == "empty()": @@ -227,7 +227,7 @@ def get_type_str(x): if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" ) - + print(arguments) parsed_expr = CallExpression(called, arguments, type_str) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr @@ -235,7 +235,7 @@ def get_type_str(x): if isinstance(expression, Attribute): member_name = expression.attr if isinstance(expression.value, Name): - + print(expression) if expression.value.id == "self" and member_name != "balance": var, was_created = find_variable(member_name, caller_context) # TODO replace with self diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index 5b84570ce8..1b9f0de873 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -20,20 +20,18 @@ MappingType, TypeAlias, ) -from slither.core.variables.top_level_variable import TopLevelVariable from slither.core.variables.variable import Variable from slither.exceptions import SlitherError -from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.exceptions import VariableNotFound if TYPE_CHECKING: - from slither.solc_parsing.declarations.function import FunctionSolc - from slither.solc_parsing.declarations.contract import ContractSolc + from slither.vyper_parsing.declarations.function import FunctionVyper + def _find_variable_in_function_parser( var_name: str, - function_parser: Optional["FunctionSolc"], + function_parser: Optional["FunctionVyper"], ) -> Optional[Variable]: if function_parser is None: return None @@ -87,7 +85,7 @@ def _find_in_contract( def find_variable( var_name: str, - caller_context: CallerContextExpression, + caller_context, ) -> Tuple[ Union[ Variable, diff --git a/slither/vyper_parsing/variables/function_type_variable.py b/slither/vyper_parsing/variables/function_type_variable.py deleted file mode 100644 index 309f4d8b7b..0000000000 --- a/slither/vyper_parsing/variables/function_type_variable.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Dict - -from slither.solc_parsing.variables.variable_declaration import VariableDeclarationSolc -from slither.core.variables.function_type_variable import FunctionTypeVariable - - -class FunctionTypeVariableSolc(VariableDeclarationSolc): - def __init__(self, variable: FunctionTypeVariable, variable_data: Dict): - super().__init__(variable, variable_data) - - @property - def underlying_variable(self) -> FunctionTypeVariable: - return self._variable From d3686764b83931a734b60fbd8f7ac8200248d0c5 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 07:41:22 -0500 Subject: [PATCH 41/69] fix missing call to set_offset --- slither/vyper_parsing/expressions/expression_parsing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 60d5fe2467..bebbf3b9ee 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -136,6 +136,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": member_type, ), ) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr elif called.value.name == "max_value()": @@ -151,6 +152,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": member_type, ), ) + parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr elif called.value.name == "raw_call()": From 36c60a93b0de84afa68e8ce71b3563c96545500d Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 10:30:00 -0500 Subject: [PATCH 42/69] add failing phi test --- tests/unit/slithir/vyper/test_ir_generation.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit/slithir/vyper/test_ir_generation.py b/tests/unit/slithir/vyper/test_ir_generation.py index 3ea5ea9fcd..704e0d27a6 100644 --- a/tests/unit/slithir/vyper/test_ir_generation.py +++ b/tests/unit/slithir/vyper/test_ir_generation.py @@ -63,3 +63,20 @@ def 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 # tainted by x, 1 + +@external +def a(x: uint256): + self.b(x) + self.b(1) +""" + ) as sl: + b = sl.contracts[0].get_function_from_signature("b(uint256)") + assert len(list(filter(lambda x: isinstance(x, Phi), b.all_slithir_operations()))) == 1 From 0efef8babf8be6169f62ad20d73c3bd4377e126c Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 12:30:21 -0500 Subject: [PATCH 43/69] add test for parsing and cfgir --- tests/e2e/vyper_parsing/__init__.py | 0 ..._vyper_cfgir_builtins_test_builtins__0.txt | 117 ++++++++++++ ...ast_parsing__vyper_cfgir_chain_test__0.txt | 13 ++ ..._parsing__vyper_cfgir_for2_for_loop__0.txt | 63 +++++++ ...slitherConstructorConstantVariables__0.txt | 19 ++ ...ast_parsing__vyper_cfgir_for3_get_D__0.txt | 62 +++++++ ...t_parsing__vyper_cfgir_for_for_loop__0.txt | 56 ++++++ ...slitherConstructorConstantVariables__0.txt | 19 ++ ...t_parsing__vyper_cfgir_if_limit_p_o__0.txt | 172 ++++++++++++++++++ .../ast_parsing__vyper_cfgir_in_bar__0.txt | 50 +++++ .../ast_parsing__vyper_cfgir_in_foo__0.txt | 51 ++++++ ...arsing__vyper_cfgir_initarry___init__0.txt | 22 +++ ...parsing__vyper_cfgir_initarry_coins__0.txt | 16 ++ ...slitherConstructorConstantVariables__0.txt | 9 + ..._parsing__vyper_cfgir_precedence_fa__0.txt | 12 ++ ..._parsing__vyper_cfgir_precedence_fb__0.txt | 4 + ...parsing__vyper_cfgir_precedence_foo__0.txt | 17 ++ ...st_parsing__vyper_cfgir_struct_test__0.txt | 13 ++ ...slitherConstructorConstantVariables__0.txt | 9 + .../ast_parsing__vyper_cfgir_tuple_bar__0.txt | 57 ++++++ .../ast_parsing__vyper_cfgir_tuple_foo__0.txt | 12 ++ tests/e2e/vyper_parsing/test_ast_parsing.py | 26 +++ tests/e2e/vyper_parsing/test_data/ERC20.vy | 22 +++ tests/e2e/vyper_parsing/test_data/builtins.vy | 31 ++++ tests/e2e/vyper_parsing/test_data/chain.vy | 4 + tests/e2e/vyper_parsing/test_data/for.vy | 32 ++++ tests/e2e/vyper_parsing/test_data/for2.vy | 26 +++ tests/e2e/vyper_parsing/test_data/for3.vy | 6 + tests/e2e/vyper_parsing/test_data/if.vy | 25 +++ tests/e2e/vyper_parsing/test_data/in.vy | 23 +++ tests/e2e/vyper_parsing/test_data/initarry.vy | 17 ++ tests/e2e/vyper_parsing/test_data/literal.vy | 20 ++ .../e2e/vyper_parsing/test_data/precedence.vy | 13 ++ tests/e2e/vyper_parsing/test_data/struct.vy | 7 + tests/e2e/vyper_parsing/test_data/tricky.vy | 15 ++ tests/e2e/vyper_parsing/test_data/tuple.vy | 19 ++ 36 files changed, 1079 insertions(+) create mode 100644 tests/e2e/vyper_parsing/__init__.py create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_slitherConstructorConstantVariables__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_slitherConstructorConstantVariables__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fa__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_struct_test__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt create mode 100644 tests/e2e/vyper_parsing/test_ast_parsing.py create mode 100644 tests/e2e/vyper_parsing/test_data/ERC20.vy create mode 100644 tests/e2e/vyper_parsing/test_data/builtins.vy create mode 100644 tests/e2e/vyper_parsing/test_data/chain.vy create mode 100644 tests/e2e/vyper_parsing/test_data/for.vy create mode 100644 tests/e2e/vyper_parsing/test_data/for2.vy create mode 100644 tests/e2e/vyper_parsing/test_data/for3.vy create mode 100644 tests/e2e/vyper_parsing/test_data/if.vy create mode 100644 tests/e2e/vyper_parsing/test_data/in.vy create mode 100644 tests/e2e/vyper_parsing/test_data/initarry.vy create mode 100644 tests/e2e/vyper_parsing/test_data/literal.vy create mode 100644 tests/e2e/vyper_parsing/test_data/precedence.vy create mode 100644 tests/e2e/vyper_parsing/test_data/struct.vy create mode 100644 tests/e2e/vyper_parsing/test_data/tricky.vy create mode 100644 tests/e2e/vyper_parsing/test_data/tuple.vy diff --git a/tests/e2e/vyper_parsing/__init__.py b/tests/e2e/vyper_parsing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt new file mode 100644 index 0000000000..07141a2b12 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt @@ -0,0 +1,117 @@ +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 = chain.id + +IRs: +h(uint256) := chain.id(uint256)"]; +7->8; +8[label="Node Type: NEW VARIABLE 8 + +EXPRESSION: +i = slice()(msg.data,0,32) + +IRs: +TMP_0(None) = SOLIDITY_CALL slice()(msg.data,0,32) +i(bytes[32]) = ['TMP_0(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)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt new file mode 100644 index 0000000000..6f12cb5302 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt @@ -0,0 +1,13 @@ +digraph{ +0[label="Node Type: ENTRY_POINT 0 +"]; +0->1; +1[label="Node Type: NEW VARIABLE 1 + +EXPRESSION: +x = bytes32(chain.id) + +IRs: +TMP_0 = CONVERT chain.id to bytes32 +x(bytes32) := TMP_0(bytes32)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt new file mode 100644 index 0000000000..949e814fff --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt @@ -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->3; +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 <= 10 + +IRs: +TMP_0(bool) = counter_var <= 10 +CONDITION TMP_0"]; +4->5[label="True"]; +4->7[label="False"]; +5[label="Node Type: NEW VARIABLE 5 + +EXPRESSION: +i = counter_var + +IRs: +i(uint256) := counter_var(uint256)"]; +5->6; +6[label="Node Type: NEW VARIABLE 6 + +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)"]; +6->8; +7[label="Node Type: END_LOOP 7 +"]; +8[label="Node Type: EXPRESSION 8 + +EXPRESSION: +counter_var += 1 + +IRs: +counter_var(uint256) = counter_var (c)+ 1"]; +8->4; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_slitherConstructorConstantVariables__0.txt new file mode 100644 index 0000000000..781b8e4859 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_slitherConstructorConstantVariables__0.txt @@ -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)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt new file mode 100644 index 0000000000..ae6d397b05 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt @@ -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->3; +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()(_xp) + +IRs: +TMP_0(uint256) = SOLIDITY_CALL len()(_xp) +TMP_1(bool) = counter_var <= TMP_0 +CONDITION TMP_1"]; +4->5[label="True"]; +4->7[label="False"]; +5[label="Node Type: NEW VARIABLE 5 + +EXPRESSION: +x = _xp[counter_var] + +IRs: +REF_0(uint256) -> _xp[counter_var] +x(uint256) := REF_0(uint256)"]; +5->6; +6[label="Node Type: EXPRESSION 6 + +EXPRESSION: +S += x + +IRs: +S(uint256) = S (c)+ x"]; +6->8; +7[label="Node Type: END_LOOP 7 +"]; +8[label="Node Type: EXPRESSION 8 + +EXPRESSION: +counter_var += 1 + +IRs: +counter_var(uint256) = counter_var (c)+ 1"]; +8->4; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt new file mode 100644 index 0000000000..210c5cec56 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt @@ -0,0 +1,56 @@ +digraph{ +0[label="Node Type: ENTRY_POINT 0 +"]; +0->1; +1[label="Node Type: BEGIN_LOOP 1 +"]; +1->2; +2[label="Node Type: NEW VARIABLE 2 + +EXPRESSION: +counter_var = 0 + +IRs: +counter_var(uint256) := 0(uint256)"]; +2->3; +3[label="Node Type: IF_LOOP 3 + +EXPRESSION: +counter_var <= len()(super.strategies) + +IRs: +TMP_0(uint256) = SOLIDITY_CALL len()(strategies) +TMP_1(bool) = counter_var <= TMP_0 +CONDITION TMP_1"]; +3->4[label="True"]; +3->6[label="False"]; +4[label="Node Type: NEW VARIABLE 4 + +EXPRESSION: +strategy = strategies[counter_var] + +IRs: +REF_0(address) -> strategies[counter_var] +strategy(address) := REF_0(address)"]; +4->5; +5[label="Node Type: NEW VARIABLE 5 + +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)"]; +5->7; +6[label="Node Type: END_LOOP 6 +"]; +7[label="Node Type: EXPRESSION 7 + +EXPRESSION: +counter_var += 1 + +IRs: +counter_var(uint256) = counter_var (c)+ 1"]; +7->3; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_slitherConstructorConstantVariables__0.txt new file mode 100644 index 0000000000..a5e0a06f74 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_slitherConstructorConstantVariables__0.txt @@ -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)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt new file mode 100644 index 0000000000..0c7ae7d7ad --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt @@ -0,0 +1,172 @@ +digraph{ +0[label="Node Type: ENTRY_POINT 0 +"]; +0->1; +1[label="Node Type: NEW VARIABLE 1 + +EXPRESSION: +p_new = p + +IRs: +p_new(uint256) := p(uint256)"]; +1->2; +2[label="Node Type: NEW VARIABLE 2 + +EXPRESSION: +dt = 1 + +IRs: +dt(uint256) := 1(uint256)"]; +2->3; +3[label="Node Type: NEW VARIABLE 3 + +EXPRESSION: +ratio = 0 + +IRs: +ratio(uint256) := 0(uint256)"]; +3->4; +4[label="Node Type: IF 4 + +EXPRESSION: +dt > 0 + +IRs: +TMP_0(bool) = dt > 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_p_o = 1 + +IRs: +old_p_o(uint256) := 1(uint256)"]; +6->7; +7[label="Node Type: NEW VARIABLE 7 + +EXPRESSION: +old_ratio = 2 + +IRs: +old_ratio(uint256) := 2(uint256)"]; +7->8; +8[label="Node Type: IF 8 + +EXPRESSION: +p > old_p_o + +IRs: +TMP_1(bool) = p > old_p_o +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: +ratio = unsafe_div()(old_p_o * 10 ** 18,p) + +IRs: +TMP_2(uint256) = 10 (c)** 18 +TMP_3(uint256) = old_p_o (c)* TMP_2 +TMP_4(None) = SOLIDITY_CALL unsafe_div()(TMP_3,p) +ratio(uint256) := TMP_4(None)"]; +10->11; +11[label="Node Type: IF 11 + +EXPRESSION: +ratio < 10 ** 36 / 1 + +IRs: +TMP_5(uint256) = 10 (c)** 36 +TMP_6(uint256) = TMP_5 (c)/ 1 +TMP_7(bool) = ratio < 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: +p_new = unsafe_div()(old_p_o * 1,10 ** 18) + +IRs: +TMP_8(uint256) = old_p_o (c)* 1 +TMP_9(uint256) = 10 (c)** 18 +TMP_10(None) = SOLIDITY_CALL unsafe_div()(TMP_8,TMP_9) +p_new(uint256) := TMP_10(None)"]; +13->14; +14[label="Node Type: EXPRESSION 14 + +EXPRESSION: +ratio = 10 ** 36 / 1 + +IRs: +TMP_11(uint256) = 10 (c)** 36 +TMP_12(uint256) = TMP_11 (c)/ 1 +ratio(uint256) := TMP_12(uint256)"]; +14->12; +15[label="Node Type: EXPRESSION 15 + +EXPRESSION: +ratio = unsafe_div()(p * 10 ** 18,old_p_o) + +IRs: +TMP_13(uint256) = 10 (c)** 18 +TMP_14(uint256) = p (c)* TMP_13 +TMP_15(None) = SOLIDITY_CALL unsafe_div()(TMP_14,old_p_o) +ratio(uint256) := TMP_15(None)"]; +15->16; +16[label="Node Type: IF 16 + +EXPRESSION: +ratio < 10 ** 36 / 1 + +IRs: +TMP_16(uint256) = 10 (c)** 36 +TMP_17(uint256) = TMP_16 (c)/ 1 +TMP_18(bool) = ratio < 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: +p_new = unsafe_div()(old_p_o * 10 ** 18,1) + +IRs: +TMP_19(uint256) = 10 (c)** 18 +TMP_20(uint256) = old_p_o (c)* TMP_19 +TMP_21(None) = SOLIDITY_CALL unsafe_div()(TMP_20,1) +p_new(uint256) := TMP_21(None)"]; +18->19; +19[label="Node Type: EXPRESSION 19 + +EXPRESSION: +ratio = 10 ** 36 / 1 + +IRs: +TMP_22(uint256) = 10 (c)** 36 +TMP_23(uint256) = TMP_22 (c)/ 1 +ratio(uint256) := TMP_23(uint256)"]; +19->17; +20[label="Node Type: EXPRESSION 20 + +EXPRESSION: +ratio = 1 + +IRs: +ratio(uint256) := 1(uint256)"]; +20->5; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt new file mode 100644 index 0000000000..7bf9052b8c --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt @@ -0,0 +1,50 @@ +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 & 1 | 2 + +IRs: +TMP_0(uint256) = 1 | 2 +TMP_1(in.Roles) = x & TMP_0 +CONDITION TMP_1"]; +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: RETURN 6 + +EXPRESSION: +False + +IRs: +RETURN False"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt new file mode 100644 index 0000000000..b9d3da07ea --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt @@ -0,0 +1,51 @@ +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_2(bool) = x == b +TMP_3(bool) = x == a +TMP_4(bool) = TMP_2 || TMP_3 +CONDITION TMP_4"]; +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: RETURN 6 + +EXPRESSION: +False + +IRs: +RETURN False"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt new file mode 100644 index 0000000000..e3417a8942 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry___init__0.txt @@ -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)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt new file mode 100644 index 0000000000..15a091f260 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt new file mode 100644 index 0000000000..b53263a8d7 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt @@ -0,0 +1,9 @@ +digraph{ +0[label="Node Type: OTHER_ENTRYPOINT 0 + +EXPRESSION: +MAX_BANDS = 10 + +IRs: +MAX_BANDS(uint256) := 10(uint256)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fa__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fa__0.txt new file mode 100644 index 0000000000..9d3526f54e --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fa__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt new file mode 100644 index 0000000000..b799f07d43 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt @@ -0,0 +1,4 @@ +digraph{ +0[label="Node Type: ENTRY_POINT 0 +"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt new file mode 100644 index 0000000000..802e03ad17 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt @@ -0,0 +1,17 @@ +digraph{ +0[label="Node Type: ENTRY_POINT 0 +"]; +0->1; +1[label="Node Type: RETURN 1 + +EXPRESSION: +x != super.fb() && x != super.fa() + +IRs: +TMP_0(uint256) = INTERNAL_CALL, precedence.fb()() +TMP_1(bool) = x != TMP_0 +TMP_2(uint256) = INTERNAL_CALL, precedence.fa()() +TMP_3(bool) = x != TMP_2 +TMP_4(bool) = TMP_1 && TMP_3 +RETURN TMP_4"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_struct_test__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_struct_test__0.txt new file mode 100644 index 0000000000..d8c540f213 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_struct_test__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt new file mode 100644 index 0000000000..753f5a9385 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt @@ -0,0 +1,9 @@ +digraph{ +0[label="Node Type: OTHER_ENTRYPOINT 0 + +EXPRESSION: +MAX_TICKS_UINT = 50 + +IRs: +MAX_TICKS_UINT(uint256) := 50(uint256)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt new file mode 100644 index 0000000000..35b37282c0 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt @@ -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) = super.foo() + +IRs: +TUPLE_0(int128,int128) = INTERNAL_CALL, tuple.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 "]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt new file mode 100644 index 0000000000..8d1c1166b2 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/test_ast_parsing.py b/tests/e2e/vyper_parsing/test_ast_parsing.py new file mode 100644 index 0000000000..8529f3e1c0 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_ast_parsing.py @@ -0,0 +1,26 @@ +from typing import Dict +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] diff --git a/tests/e2e/vyper_parsing/test_data/ERC20.vy b/tests/e2e/vyper_parsing/test_data/ERC20.vy new file mode 100644 index 0000000000..682b8cf268 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/ERC20.vy @@ -0,0 +1,22 @@ + + +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 diff --git a/tests/e2e/vyper_parsing/test_data/builtins.vy b/tests/e2e/vyper_parsing/test_data/builtins.vy new file mode 100644 index 0000000000..4fb908a094 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/builtins.vy @@ -0,0 +1,31 @@ + +@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: uint256 = chain.id + + 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 diff --git a/tests/e2e/vyper_parsing/test_data/chain.vy b/tests/e2e/vyper_parsing/test_data/chain.vy new file mode 100644 index 0000000000..81a7704794 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/chain.vy @@ -0,0 +1,4 @@ + +@external +def test(): + x: bytes32 = convert(chain.id, bytes32) \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/for.vy b/tests/e2e/vyper_parsing/test_data/for.vy new file mode 100644 index 0000000000..b9f09bf6f6 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/for.vy @@ -0,0 +1,32 @@ + + +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() + diff --git a/tests/e2e/vyper_parsing/test_data/for2.vy b/tests/e2e/vyper_parsing/test_data/for2.vy new file mode 100644 index 0000000000..f129e56b72 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/for2.vy @@ -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) + diff --git a/tests/e2e/vyper_parsing/test_data/for3.vy b/tests/e2e/vyper_parsing/test_data/for3.vy new file mode 100644 index 0000000000..4b55b6970a --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/for3.vy @@ -0,0 +1,6 @@ + +@external +def get_D(_xp: uint256[3], _amp: uint256): + S: uint256 = 0 + for x in _xp: + S += x \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/if.vy b/tests/e2e/vyper_parsing/test_data/if.vy new file mode 100644 index 0000000000..23483ca455 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/if.vy @@ -0,0 +1,25 @@ +@external +@view +def limit_p_o(p: uint256): + p_new: uint256 = p + dt: uint256 = 1 + ratio: uint256 = 0 + + if dt > 0: + old_p_o: uint256 = 1 + old_ratio: uint256 = 2 + # ratio = p_o_min / p_o_max + if p > old_p_o: + ratio = unsafe_div(old_p_o * 10**18, p) + if ratio < 10**36 / 1: + p_new = unsafe_div(old_p_o * 1, 10**18) + ratio = 10**36 / 1 + else: + ratio = unsafe_div(p * 10**18, old_p_o) + if ratio < 10**36 / 1: + p_new = unsafe_div(old_p_o * 10**18, 1) + ratio = 10**36 / 1 + + # ratio is guaranteed to be less than 1e18 + # Also guaranteed to be limited, therefore can have all ops unsafe + ratio = 1 diff --git a/tests/e2e/vyper_parsing/test_data/in.vy b/tests/e2e/vyper_parsing/test_data/in.vy new file mode 100644 index 0000000000..e08f71a749 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/in.vy @@ -0,0 +1,23 @@ +enum Roles: + A + B + +roles: public(HashMap[address, Roles]) + +@external +def bar(x: Roles) -> bool: + a: int128 = 0 + b: int128 = 0 + + if x in self.roles[self]: + return True + return False + +@external +def foo(x: int128) -> bool: + a: int128 = 0 + b: int128 = 0 + + if x in [a, b]: + return True + return False \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/initarry.vy b/tests/e2e/vyper_parsing/test_data/initarry.vy new file mode 100644 index 0000000000..bfbd29a27f --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/initarry.vy @@ -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) # x +COLLATERAL_TOKEN: immutable(ERC20) # x + +@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] \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/literal.vy b/tests/e2e/vyper_parsing/test_data/literal.vy new file mode 100644 index 0000000000..e0686301e7 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/literal.vy @@ -0,0 +1,20 @@ +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] \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/precedence.vy b/tests/e2e/vyper_parsing/test_data/precedence.vy new file mode 100644 index 0000000000..ec86186632 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/precedence.vy @@ -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()] + + diff --git a/tests/e2e/vyper_parsing/test_data/struct.vy b/tests/e2e/vyper_parsing/test_data/struct.vy new file mode 100644 index 0000000000..97c6f5589e --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/struct.vy @@ -0,0 +1,7 @@ +struct X: + y: int8 + + +@external +def test() -> X: + return X({y: 1}) diff --git a/tests/e2e/vyper_parsing/test_data/tricky.vy b/tests/e2e/vyper_parsing/test_data/tricky.vy new file mode 100644 index 0000000000..83b19cb895 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/tricky.vy @@ -0,0 +1,15 @@ +interface LMGauge: + def callback_collateral_shares(n: int256, collateral_per_share: DynArray[uint256, MAX_TICKS_UINT]): nonpayable + def callback_user_shares(user: address, n: int256, user_shares: DynArray[uint256, MAX_TICKS_UINT]): nonpayable + + +MAX_TICKS_UINT: constant(uint256) = 50 + + +struct Loan: + liquidation_range: LMGauge + +x: public(Loan) + + +# TODO Will this overly complicate analyzing AST https://github.com/vyperlang/vyper/pull/3411 \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/tuple.vy b/tests/e2e/vyper_parsing/test_data/tuple.vy new file mode 100644 index 0000000000..f0c7e66fe8 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/tuple.vy @@ -0,0 +1,19 @@ + + +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() + From 08af1ee9e5edf1ccb8392bc415626b9dc8a9c762 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 12:42:02 -0500 Subject: [PATCH 44/69] cleanup FunctionVyper --- .../vyper_parsing/declarations/function.py | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 98c3728080..17bbf6910c 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -19,6 +19,7 @@ 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): @@ -40,8 +41,9 @@ def __init__( self._function.name = function_data.name self._function.id = function_data.node_id - self._local_variables_parser: List = [] + self._local_variables_parser: List[LocalVariableVyper] = [] self._contract_parser = contract_parser + self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} for decorator in function_data.decorators: if not hasattr(decorator, "id"): @@ -54,6 +56,8 @@ def __init__( 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 @@ -64,24 +68,9 @@ def __init__( self._content_was_analyzed = False self._counter_scope_local_variables = 0 - # # variable renamed will map the solc id - # # to the variable. It only works for compact format - # # Later if an expression provides the referencedDeclaration attr - # # we can retrieve the variable - # # It only matters if two variables have the same name in the function - # # which is only possible with solc > 0.5 - # self._variables_renamed: Dict[ - # int, Union[LocalVariableVyper, LocalVariableInitFromTupleSolc] - # ] = {} self._analyze_function_type() - # self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} - # self._node_to_yulobject: Dict[Node, YulBlock] = {} - - # self._local_variables_parser: List[ - # Union[LocalVariableVyper, LocalVariableInitFromTupleSolc] - # ] = [] if function_data.doc_string is not None: function.has_documentation = True @@ -107,12 +96,9 @@ def variables_renamed( return self._variables_renamed def _add_local_variable(self, local_var_parser: LocalVariableVyper) -> None: - # If two local variables have the same name - # We add a suffix to the new variable - # This is done to prevent collision during SSA translation - # Use of while in case of collision - # In the worst case, the name will be really long - # TODO no shadowing? + # 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: From f9633ca67a25a0b5850a66fdbc3330b91babb271 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 13:34:16 -0500 Subject: [PATCH 45/69] fix name resolution for shadowed state variable --- slither/core/expressions/__init__.py | 1 + slither/core/expressions/self_identifier.py | 6 ++++++ .../expressions/expression_parsing.py | 17 +++++++---------- .../vyper_parsing/expressions/find_variable.py | 13 +++++++------ ...ast_parsing__vyper_cfgir_for_for_loop__0.txt | 2 +- ...t_parsing__vyper_cfgir_precedence_foo__0.txt | 2 +- .../ast_parsing__vyper_cfgir_tuple_bar__0.txt | 2 +- .../test_data/src_mapping/SelfIdentifier.vy | 4 ++++ tests/unit/core/test_source_mapping.py | 13 +++++++++++++ 9 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 slither/core/expressions/self_identifier.py create mode 100644 tests/unit/core/test_data/src_mapping/SelfIdentifier.vy diff --git a/slither/core/expressions/__init__.py b/slither/core/expressions/__init__.py index 42554bf0bc..b162481ba2 100644 --- a/slither/core/expressions/__init__.py +++ b/slither/core/expressions/__init__.py @@ -12,6 +12,7 @@ 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 diff --git a/slither/core/expressions/self_identifier.py b/slither/core/expressions/self_identifier.py new file mode 100644 index 0000000000..b86d70baf4 --- /dev/null +++ b/slither/core/expressions/self_identifier.py @@ -0,0 +1,6 @@ +from slither.core.expressions.identifier import Identifier + + +class SelfIdentifier(Identifier): + def __str__(self): + return "self." + str(self._value) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index bebbf3b9ee..9996df8a8e 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -20,7 +20,7 @@ NewContract, NewElementaryType, SuperCallExpression, - SuperIdentifier, + SelfIdentifier, TupleExpression, TypeConversion, UnaryOperation, @@ -42,7 +42,7 @@ 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 if TYPE_CHECKING: from slither.core.expressions.expression import Expression @@ -237,14 +237,14 @@ def get_type_str(x): if isinstance(expression, Attribute): member_name = expression.attr if isinstance(expression.value, Name): - print(expression) + # TODO this is ambiguous because it could be a state variable or a call to balance if expression.value.id == "self" and member_name != "balance": - var, was_created = find_variable(member_name, caller_context) - # TODO replace with self + var, was_created = find_variable(member_name, caller_context, is_self=True) if was_created: var.set_offset(expression.src, caller_context.compilation_unit) - parsed_expr = SuperIdentifier(var) + 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) @@ -267,12 +267,11 @@ def get_type_str(x): # (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` - # TODO this is using the wrong caller_context and needs to be interface instead of self namespace print(expr) print(expr.__class__.__name__) if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType): - # try: + # If we access a member of an interface, needs to be interface instead of self namespace var, was_created = find_variable(member_name, expr.type.type) if isinstance(var, Function): rets = var.returns @@ -288,8 +287,6 @@ def get_type_str(x): else f"tuple({','.join(map(get_type_str, rets))})" ) member_name_ret_type = type_str - # except: - # pass member_access = MemberAccess(member_name, member_name_ret_type, expr) diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index 1b9f0de873..e888aa0148 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -28,7 +28,6 @@ from slither.vyper_parsing.declarations.function import FunctionVyper - def _find_variable_in_function_parser( var_name: str, function_parser: Optional["FunctionVyper"], @@ -86,6 +85,7 @@ def _find_in_contract( def find_variable( var_name: str, caller_context, + is_self: bool = False, ) -> Tuple[ Union[ Variable, @@ -96,8 +96,6 @@ def find_variable( Event, Enum, Structure, - CustomError, - TypeAlias, ], bool, ]: @@ -136,9 +134,12 @@ def find_variable( caller_context if isinstance(caller_context, FunctionContract) else None ) # print("function_parser", function_parser) - ret1 = _find_variable_in_function_parser(var_name, function_parser) - if ret1: - return ret1, False + # 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, False ret = _find_in_contract(var_name, next_context, caller_context) if ret: diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt index 210c5cec56..5c4123662f 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt @@ -16,7 +16,7 @@ counter_var(uint256) := 0(uint256)"]; 3[label="Node Type: IF_LOOP 3 EXPRESSION: -counter_var <= len()(super.strategies) +counter_var <= len()(self.strategies) IRs: TMP_0(uint256) = SOLIDITY_CALL len()(strategies) diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt index 802e03ad17..2355fd513a 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt @@ -5,7 +5,7 @@ digraph{ 1[label="Node Type: RETURN 1 EXPRESSION: -x != super.fb() && x != super.fa() +x != self.fb() && x != self.fa() IRs: TMP_0(uint256) = INTERNAL_CALL, precedence.fb()() diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt index 35b37282c0..0d2540498a 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt @@ -21,7 +21,7 @@ b(int128) := 0(uint256)"]; 3[label="Node Type: EXPRESSION 3 EXPRESSION: -(a,b) = super.foo() +(a,b) = self.foo() IRs: TUPLE_0(int128,int128) = INTERNAL_CALL, tuple.foo()() diff --git a/tests/unit/core/test_data/src_mapping/SelfIdentifier.vy b/tests/unit/core/test_data/src_mapping/SelfIdentifier.vy new file mode 100644 index 0000000000..5607fb9438 --- /dev/null +++ b/tests/unit/core/test_data/src_mapping/SelfIdentifier.vy @@ -0,0 +1,4 @@ +name: public(String[64]) +@external +def __init__(name: String[64]): + self.name = name diff --git a/tests/unit/core/test_source_mapping.py b/tests/unit/core/test_source_mapping.py index fe53359777..16a26215b6 100644 --- a/tests/unit/core/test_source_mapping.py +++ b/tests/unit/core/test_source_mapping.py @@ -113,3 +113,16 @@ 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] \ No newline at end of file From 2dbb912bb72278c5e15047f95bccfb219e310396 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 13:46:26 -0500 Subject: [PATCH 46/69] correctly set indexed attribute for event variables --- slither/vyper_parsing/variables/event_variable.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/slither/vyper_parsing/variables/event_variable.py b/slither/vyper_parsing/variables/event_variable.py index 10b8aa06cc..2dc5db5441 100644 --- a/slither/vyper_parsing/variables/event_variable.py +++ b/slither/vyper_parsing/variables/event_variable.py @@ -2,7 +2,7 @@ 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 +from slither.vyper_parsing.ast.types import AnnAssign, Call class EventVariableVyper: @@ -10,7 +10,10 @@ def __init__(self, variable: EventVariable, variable_data: AnnAssign): print(variable_data) self._variable = variable self._variable.name = variable_data.target.id - # TODO self._variable.indexed + 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 From f7ef48401ed8d8c51956dcaaa1842c953b511a8a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 15:53:03 -0500 Subject: [PATCH 47/69] very simplistic support for reentrancy lock --- slither/core/declarations/function.py | 4 +- .../vyper_parsing/declarations/function.py | 72 +++++++++++++------ tests/unit/core/test_function_declaration.py | 4 +- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 0f1955a923..e9f9552e94 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -1499,7 +1499,9 @@ def is_reentrant(self) -> bool: 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"]: diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 17bbf6910c..850ff08b63 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -8,7 +8,8 @@ Function, FunctionType, ) -from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.function import ModifierStatements +from slither.core.declarations.modifier import Modifier from slither.core.expressions import AssignmentOperation from slither.core.source_mapping.source_mapping import Source from slither.core.variables.local_variable import LocalVariable @@ -33,48 +34,47 @@ def __init__( function_data: Dict, contract_parser: "ContractVyper", ) -> None: - self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} self._function = function - print(function_data.name) - print(function_data) 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._contract_parser = contract_parser self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} for decorator in function_data.decorators: - if not hasattr(decorator, "id"): - continue # TODO isinstance 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 + 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._functionNotParsed = function_data + self._params_was_analyzed = False self._content_was_analyzed = False - self._counter_scope_local_variables = 0 - self._analyze_function_type() - - 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 @@ -166,6 +166,34 @@ def analyze_content(self) -> None: 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 ################################################################################### ################################################################################### diff --git a/tests/unit/core/test_function_declaration.py b/tests/unit/core/test_function_declaration.py index 739a113bc5..2dc8192a1f 100644 --- a/tests/unit/core/test_function_declaration.py +++ b/tests/unit/core/test_function_declaration.py @@ -368,8 +368,8 @@ def __default__(): assert f.can_send_eth() assert f.can_reenter() - # f = functions["withdraw_locked()"] - # assert not f.can_reenter() + f = functions["withdraw_locked()"] + assert not f.is_reentrant var = contract.get_state_variable_from_name("balances") assert var From 4b10a0fa18e7604aed6a5e8076a056e4977caafc Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 15:54:26 -0500 Subject: [PATCH 48/69] delete commented out code --- slither/__main__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index e68f0b67f1..ab15c72d87 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -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: From e561d33eb18e086fe4da751cd86cfa1c1e1ebf7b Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 31 Aug 2023 17:37:30 -0500 Subject: [PATCH 49/69] try removing trailing comment --- tests/e2e/vyper_parsing/test_data/tricky.vy | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/e2e/vyper_parsing/test_data/tricky.vy b/tests/e2e/vyper_parsing/test_data/tricky.vy index 83b19cb895..4a5b718518 100644 --- a/tests/e2e/vyper_parsing/test_data/tricky.vy +++ b/tests/e2e/vyper_parsing/test_data/tricky.vy @@ -9,7 +9,4 @@ MAX_TICKS_UINT: constant(uint256) = 50 struct Loan: liquidation_range: LMGauge -x: public(Loan) - - -# TODO Will this overly complicate analyzing AST https://github.com/vyperlang/vyper/pull/3411 \ No newline at end of file +x: public(Loan) \ No newline at end of file From 3ca9d0f2287742bc8f57afbbaa1f6977966710dd Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 1 Sep 2023 19:50:56 -0500 Subject: [PATCH 50/69] convert tuple on lhs and index access to struct and field access --- .../visitors/slithir/expression_to_slithir.py | 46 +++++++++++++++++++ slither/vyper_parsing/type_parsing.py | 28 ++++++++++- .../vyper_parsing/variables/local_variable.py | 4 +- 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 5a6c7de598..fa8cef8e3b 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -11,6 +11,7 @@ EnumContract, EnumTopLevel, Enum, + Structure, ) from slither.core.expressions import ( AssignmentOperation, @@ -233,6 +234,35 @@ def _post_assignement_operation(self, expression: AssignmentOperation) -> None: 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 @@ -417,6 +447,21 @@ def _post_index_access(self, expression: IndexAccess) -> None: 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): @@ -426,6 +471,7 @@ def _post_index_access(self, expression: IndexAccess) -> None: 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) diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index a7d83240ea..fc45a144c7 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -20,7 +20,7 @@ def parse_type(annotation: Union[Name, Subscript, Call], caller_context): else: contract = caller_context - assert isinstance(annotation, (Name, Subscript, Call)) + assert isinstance(annotation, (Name, Subscript, Call, Tuple)) print(annotation) if isinstance(annotation, Name): name = annotation.id @@ -54,12 +54,36 @@ def parse_type(annotation: Union[Name, Subscript, Call], caller_context): return ArrayType(type_, length) elif isinstance(annotation, Call): + # TODO event variable represented as Call 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) + else: assert False - lname = name.lower() # todo map String to string + lname = name.lower() # TODO map String to string if lname in ElementaryTypeName: return ElementaryType(lname) diff --git a/slither/vyper_parsing/variables/local_variable.py b/slither/vyper_parsing/variables/local_variable.py index d3bd0b0557..918ec344cb 100644 --- a/slither/vyper_parsing/variables/local_variable.py +++ b/slither/vyper_parsing/variables/local_variable.py @@ -1,7 +1,7 @@ from typing import Union from slither.core.variables.local_variable import LocalVariable -from slither.vyper_parsing.ast.types import Arg, Name, AnnAssign, Subscript, Call +from slither.vyper_parsing.ast.types import Arg, Name, AnnAssign, Subscript, Call, Tuple from slither.vyper_parsing.type_parsing import parse_type @@ -23,7 +23,7 @@ def __init__(self, variable: LocalVariable, variable_data: Union[Arg, Name]) -> self._variable.name = "" self._elem_to_parse = variable_data - assert isinstance(self._elem_to_parse, (Name, Subscript, Call)) + assert isinstance(self._elem_to_parse, (Name, Subscript, Call, Tuple)) self._variable.set_location("default") From 47e274c907bf5dfcb174b9caf01f6e169a972b5b Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 1 Sep 2023 19:57:11 -0500 Subject: [PATCH 51/69] consider function unimplemented if body is only pass --- slither/vyper_parsing/declarations/function.py | 5 ++++- tests/unit/core/test_function_declaration.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 850ff08b63..c79b7a9e4e 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -156,9 +156,11 @@ def analyze_content(self) -> None: body = self._functionNotParsed.body - if body: + if body and not isinstance(body[0], Pass): self._function.is_implemented = True self._parse_cfg(body) + else: + self._function.is_implemented = False for local_var_parser in self._local_variables_parser: local_var_parser.analyze(self._function) @@ -462,6 +464,7 @@ def parse_statement(curr_node, expr): pass elif isinstance(expr, Raise): print(expr) + # TODO # assert False pass else: diff --git a/tests/unit/core/test_function_declaration.py b/tests/unit/core/test_function_declaration.py index 2dc8192a1f..c4844074ef 100644 --- a/tests/unit/core/test_function_declaration.py +++ b/tests/unit/core/test_function_declaration.py @@ -351,6 +351,7 @@ def __default__(): assert not f.payable assert not f.view assert not f.pure + assert not f.is_implemented f = functions["__default__()"] assert f.function_type == FunctionType.FALLBACK @@ -358,6 +359,7 @@ def __default__(): assert f.payable assert not f.view assert not f.pure + assert not f.is_implemented f = functions["withdraw()"] assert f.function_type == FunctionType.NORMAL @@ -367,9 +369,11 @@ def __default__(): assert not f.pure assert f.can_send_eth() assert f.can_reenter() + assert f.is_implemented f = functions["withdraw_locked()"] assert not f.is_reentrant + assert f.is_implemented var = contract.get_state_variable_from_name("balances") assert var From 66def310e7717a3c665fd9b13aa9fe5ee0e42b68 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 1 Sep 2023 22:15:29 -0500 Subject: [PATCH 52/69] do not considered local variable that are reference types as `in_storage` --- slither/slithir/convert.py | 4 +++- slither/vyper_parsing/variables/local_variable.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index d1eaafdfac..8bb20412bf 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -576,7 +576,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) diff --git a/slither/vyper_parsing/variables/local_variable.py b/slither/vyper_parsing/variables/local_variable.py index 918ec344cb..b50dea44b1 100644 --- a/slither/vyper_parsing/variables/local_variable.py +++ b/slither/vyper_parsing/variables/local_variable.py @@ -25,7 +25,9 @@ def __init__(self, variable: LocalVariable, variable_data: Union[Arg, Name]) -> assert isinstance(self._elem_to_parse, (Name, Subscript, Call, Tuple)) - self._variable.set_location("default") + # 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: From e287b2f905b2b3abb536e93e8ea79f9b030f145c Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 1 Sep 2023 23:10:13 -0500 Subject: [PATCH 53/69] fix phi placement by calling SlitherCompilationUnit.add_function --- slither/core/compilation_unit.py | 4 ++++ slither/vyper_parsing/declarations/contract.py | 6 +++++- tests/unit/slithir/vyper/test_ir_generation.py | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index 8e801ea953..556c6c7da8 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -189,6 +189,10 @@ def functions_and_modifiers(self) -> List[Function]: 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: diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index ed61bda1b7..0e2140fa1c 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -1,4 +1,5 @@ import logging +from pathlib import Path from typing import List, TYPE_CHECKING from slither.vyper_parsing.ast.types import ( Module, @@ -40,7 +41,9 @@ def __init__( self._contract: Contract = contract self._slither_parser: "VyperCompilationUnit" = slither_parser self._data = module - self._contract.name = module.name + # 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 @@ -492,6 +495,7 @@ def parse_functions(self) -> None: 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 = [] diff --git a/tests/unit/slithir/vyper/test_ir_generation.py b/tests/unit/slithir/vyper/test_ir_generation.py index 704e0d27a6..2f7c59fd37 100644 --- a/tests/unit/slithir/vyper/test_ir_generation.py +++ b/tests/unit/slithir/vyper/test_ir_generation.py @@ -64,6 +64,7 @@ def bar(): 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( """ @@ -78,5 +79,15 @@ def a(x: uint256): self.b(1) """ ) as sl: - b = sl.contracts[0].get_function_from_signature("b(uint256)") - assert len(list(filter(lambda x: isinstance(x, Phi), b.all_slithir_operations()))) == 1 + 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 + ) From 81cb124e2dad5e0397209835186d7614901eb8ff Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 4 Sep 2023 13:04:52 -0500 Subject: [PATCH 54/69] support default args in calls --- slither/core/declarations/function.py | 2 ++ .../vyper_parsing/declarations/function.py | 5 +-- .../expressions/expression_parsing.py | 11 ++++++ .../unit/slithir/vyper/test_ir_generation.py | 35 +++++++++++++++++-- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index e9f9552e94..a4a172b974 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -138,6 +138,8 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None: 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() diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index c79b7a9e4e..9ef3a547ad 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -31,7 +31,7 @@ class FunctionVyper: def __init__( self, function: Function, - function_data: Dict, + function_data: FunctionDef, contract_parser: "ContractVyper", ) -> None: @@ -503,7 +503,8 @@ def _parse_params(self, params: Arguments): print(params) 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) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 9996df8a8e..3d3d3e4f5e 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -198,6 +198,17 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # Since the AST lacks the type of the return values, we recover it. if isinstance(called.value, Function): rets = called.value.returns + # Default arguments are not represented in the AST, so we recover them as well. + 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): diff --git a/tests/unit/slithir/vyper/test_ir_generation.py b/tests/unit/slithir/vyper/test_ir_generation.py index 2f7c59fd37..6bcdaab10e 100644 --- a/tests/unit/slithir/vyper/test_ir_generation.py +++ b/tests/unit/slithir/vyper/test_ir_generation.py @@ -10,7 +10,7 @@ from slither import Slither from slither.core.cfg.node import Node, NodeType from slither.core.declarations import Function, Contract -from slither.core.solidity_types import ArrayType +from slither.core.solidity_types import ArrayType, ElementaryType from slither.core.variables.local_variable import LocalVariable from slither.core.variables.state_variable import StateVariable from slither.slithir.operations import ( @@ -71,7 +71,7 @@ def test_phi_entry_point_internal_call(slither_from_vyper_source): counter: uint256 @internal def b(y: uint256): - self.counter = y # tainted by x, 1 + self.counter = y @external def a(x: uint256): @@ -91,3 +91,34 @@ def a(x: uint256): ) == 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")) From 4845a3bd261db99c77aacdbdbc6845fab9f120a8 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 4 Sep 2023 13:49:05 -0500 Subject: [PATCH 55/69] fix and refactor comparison operator, add support for raise --- .../vyper_parsing/declarations/function.py | 429 +++++++++--------- .../expressions/expression_parsing.py | 167 ++++--- .../ast_parsing__vyper_cfgir_in_bar__0.txt | 56 ++- .../ast_parsing__vyper_cfgir_in_baz__0.txt | 66 +++ .../ast_parsing__vyper_cfgir_in_foo__0.txt | 33 +- ..._parsing__vyper_cfgir_precedence_fb__0.txt | 8 + ...parsing__vyper_cfgir_precedence_foo__0.txt | 12 +- tests/e2e/vyper_parsing/test_data/in.vy | 17 +- 8 files changed, 448 insertions(+), 340 deletions(-) create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 9ef3a547ad..f023953faa 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -158,9 +158,11 @@ def analyze_content(self) -> None: 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) @@ -218,264 +220,257 @@ def _new_node( ################################################################################### ################################################################################### - def _parse_cfg(self, cfg: Dict) -> None: + 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) - if cfg: - self._function.is_empty = False - curr_node = entry_node - for expr in cfg: - - def parse_statement(curr_node, expr): - 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 = entry_node + for expr in cfg: - curr_node = new_node + def parse_statement(curr_node, expr): + if isinstance(expr, AnnAssign): + local_var = LocalVariable() + local_var.set_function(self._function) + local_var.set_offset(expr.src, self._function.compilation_unit) - 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) + local_var_parser = LocalVariableVyper(local_var, expr) + self._add_local_variable(local_var_parser) - curr_node = new_node + 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) - elif isinstance(expr, Expr): - # TODO This is a workaround to handle Vyper putting payable/view in the function body... - 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 - 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) - elif isinstance(expr, For): + curr_node = new_node - node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope) - link_underlying_nodes(curr_node, node_startLoop) + elif isinstance(expr, Expr): + # TODO This is a workaround to handle Vyper putting payable/view in the function body... + 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) - local_var = LocalVariable() - local_var.set_function(self._function) - local_var.set_offset(expr.src, self._function.compilation_unit) + curr_node = new_node - 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] + elif isinstance(expr, For): + + node_startLoop = self._new_node(NodeType.STARTLOOP, 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, ) - else: - loop_var_annotation = loop_iterator._elem_to_parse.value + )[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] - 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( + # 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, - 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"), - ) + 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: - raise NotImplementedError - - # link - link_underlying_nodes(counter_node, node_condition) + loop_var_annotation = loop_iterator._elem_to_parse.value - # We update the index variable or range variable in the loop body - 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) - new_node = body_node - - node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) + 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, + ) - loop_increment = AugAssign( + elif isinstance(expr.iter, Call): # range + range_val = expr.iter.args[0] + cond_expr = Compare( "-1:-1:-1", -1, - target=Name("-1:-1:-1", -1, "counter_var"), - op="+=", - value=Int("-1:-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"), ) - node_increment = self._new_node(NodeType.EXPRESSION, expr.src, scope) - node_increment.add_unparsed_expression(loop_increment) - link_underlying_nodes(node_increment, node_condition) - if body_node is not None: - link_underlying_nodes(body_node, node_increment) + else: + raise NotImplementedError + + link_underlying_nodes(counter_node, node_condition) + + # We update the index variable or range variable in the loop body + 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) + new_node = body_node + + node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) + + 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) + + 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): + pass + elif isinstance(expr, Break): + pass + 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(node_condition, node_endLoop) + link_underlying_nodes(curr_node, new_node) + curr_node = new_node - curr_node = node_endLoop + elif isinstance(expr, Assert): + new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + new_node.add_unparsed_expression(expr) - elif isinstance(expr, Continue): - pass - elif isinstance(expr, Break): - pass - 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 - 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) - 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 - 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) - elif isinstance(expr, Log): - new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) - new_node.add_unparsed_expression(expr.value) + endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope) - link_underlying_nodes(curr_node, new_node) - curr_node = new_node + true_node = None + new_node = condition_node + for stmt in expr.body: + true_node = parse_statement(new_node, stmt) + new_node = true_node + # link_underlying_nodes(condition_node, true_node) + link_underlying_nodes(true_node, endIf_node) - elif isinstance(expr, If): - condition_node = self._new_node(NodeType.IF, expr.test.src, scope) - condition_node.add_unparsed_expression(expr.test) + false_node = None + new_node = condition_node + for stmt in expr.orelse: + false_node = parse_statement(new_node, stmt) + new_node = false_node - endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope) + if false_node is not None: + # link_underlying_nodes(condition_node, false_node) + link_underlying_nodes(false_node, endIf_node) - true_node = None - new_node = condition_node - for stmt in expr.body: - true_node = parse_statement(new_node, stmt) - new_node = true_node - # link_underlying_nodes(condition_node, true_node) - link_underlying_nodes(true_node, endIf_node) + else: + link_underlying_nodes(condition_node, endIf_node) - false_node = None - new_node = condition_node - for stmt in expr.orelse: - false_node = parse_statement(new_node, stmt) - new_node = false_node + link_underlying_nodes(curr_node, condition_node) + curr_node = endIf_node - if false_node is not None: - # link_underlying_nodes(condition_node, false_node) - link_underlying_nodes(false_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: - 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): - print(expr) - # TODO - # assert False - pass - else: - print(f"isinstance(expr, {expr.__class__.__name__})") - assert False - return curr_node + else: + raise ParsingError(f"Statement not parsed {expr.__class__.__name__} {expr}") - curr_node = parse_statement(curr_node, expr) - # self._parse_block(cfg, node, self.underlying_function) - else: - self._function.is_empty = True + return curr_node + + curr_node = parse_statement(curr_node, expr) # endregion ################################################################################### diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 3d3d3e4f5e..3f76ea2814 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -69,6 +69,7 @@ Assign, AugAssign, VyList, + Raise, ) @@ -192,6 +193,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": else: arguments = [parse_expression(a, caller_context) for a in expression.args] + rets = None if isinstance(called, Identifier): print("called", called) print("called.value", called.value.__class__.__name__) @@ -211,6 +213,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": 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. @@ -219,14 +222,12 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr - else: - rets = ["tuple()"] - 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] - else: + + if rets is None: rets = ["tuple()"] def get_type_str(x): @@ -235,6 +236,13 @@ def get_type_str(x): return str(x.type) print(rets) + # def vars_to_typestr(rets: List[Expression]) -> str: + # if len(rets) == 0: + # return "" + # if len(rets) == 1: + # return str(rets[0].type) + # return f"tuple({','.join(str(ret.type) for ret in rets)})" + type_str = ( get_type_str(rets[0]) if len(rets) == 1 @@ -347,30 +355,48 @@ def get_type_str(x): 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 rewrite it as if-else: - # if (x == foo()) { - # return true - # } else { - # if (x == bar()) { - # return true - # } else { - # return false - # } - # } - # We assume left operand in membership comparison cannot be Array type + # 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() - if isinstance(expression.right, VyList): + 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 + 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() for elem in expression.right.elements: elem_expr = parse_expression(elem, caller_context) @@ -378,79 +404,31 @@ def get_type_str(x): parsed_expr = BinaryOperation(lhs, elem_expr, inner_op) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) conditions.append(parsed_expr) - else: - rhs = parse_expression(expression.right, caller_context) - print(rhs) - print(rhs.__class__.__name__) - if isinstance(rhs, Identifier): - if isinstance(rhs.value.type, ArrayType): - inner_op = ( - BinaryOperationType.get_type("!=") - if expression.op == "NotIn" - else BinaryOperationType.get_type("==") - ) - outer_op = ( - BinaryOperationType.get_type("&&") - if expression.op == "NotIn" - else BinaryOperationType.get_type("||") - ) - - enum_members = rhs.value.type.length_value.value - for i in range(enum_members): - elem_expr = 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) - # elif isinstance(rhs.value.type, UserDefinedType): - - else: - assert False - else: - # This is an indexaccess like hashmap[address, Roles] - inner_op = BinaryOperationType.get_type( - "|" - ) # if expression.op == "NotIn" else BinaryOperationType.get_type("==") - outer_op = BinaryOperationType.get_type( - "&" - ) # if expression.op == "NotIn" else BinaryOperationType.get_type("||") - - # x, _ = find_variable(expression.right.value.attr, caller_context) - # print(x) - # print(x.type.type_to) - # print(x.type.type_to.__class__) - print(repr(rhs)) - print(rhs) - - enum_members = rhs.expression_left.value.type.type_to.type.values - # for each value, create a literal with value = 2 ^ n (0 indexed) - # and then translate to bitmasking - enum_values = [ - Literal(str(2**n), ElementaryType("uint256")) - for n in range(len(enum_members)) - ] - inner_lhs = enum_values[0] - for expr in enum_values[1:]: - inner_lhs = BinaryOperation(inner_lhs, expr, inner_op) - conditions.append(inner_lhs) - - parsed_expr = BinaryOperation(lhs, conditions[0], outer_op) - parsed_expr.set_offset(lhs.source_mapping, caller_context.compilation_unit) - return parsed_expr - - while len(conditions) > 1: - lhs = conditions.pop() - rhs = conditions.pop() - - conditions.appendleft(BinaryOperation(lhs, rhs, outer_op)) - - return conditions.pop() + else: # 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 - else: + else: # a regular logical operator rhs = parse_expression(expression.right, caller_context) op = BinaryOperationType.get_type(expression.op) @@ -501,4 +479,17 @@ def get_type_str(x): 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}") diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt index 7bf9052b8c..49552d27f9 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_bar__0.txt @@ -2,45 +2,57 @@ digraph{ 0[label="Node Type: ENTRY_POINT 0 "]; 0->1; -1[label="Node Type: NEW VARIABLE 1 +1[label="Node Type: IF 1 EXPRESSION: -a = 0 +uint256(x) & uint256(self.roles[self]) != 0 IRs: -a(int128) := 0(uint256)"]; -1->2; -2[label="Node Type: NEW VARIABLE 2 +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: -b = 0 +True IRs: -b(int128) := 0(uint256)"]; -2->3; -3[label="Node Type: IF 3 +RETURN True"]; +3->2; +4[label="Node Type: IF 4 EXPRESSION: -x & 1 | 2 +uint256(x) & uint256(self.roles[self]) == 0 IRs: -TMP_0(uint256) = 1 | 2 -TMP_1(in.Roles) = x & TMP_0 -CONDITION TMP_1"]; -3->5[label="True"]; -3->4[label="False"]; -4[label="Node Type: END_IF 4 +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 "]; -4->6; -5[label="Node Type: RETURN 5 +5->7; +6[label="Node Type: RETURN 6 EXPRESSION: -True +False IRs: -RETURN True"]; -5->4; -6[label="Node Type: RETURN 6 +RETURN False"]; +6->5; +7[label="Node Type: RETURN 7 EXPRESSION: False diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt new file mode 100644 index 0000000000..95328dad99 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_baz__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt index b9d3da07ea..cd1e34bf1a 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_in_foo__0.txt @@ -24,10 +24,10 @@ EXPRESSION: x == b || x == a IRs: -TMP_2(bool) = x == b -TMP_3(bool) = x == a -TMP_4(bool) = TMP_2 || TMP_3 -CONDITION TMP_4"]; +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 @@ -41,7 +41,30 @@ True IRs: RETURN True"]; 5->4; -6[label="Node Type: RETURN 6 +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 diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt index b799f07d43..0c204c9fa2 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_fb__0.txt @@ -1,4 +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()()"]; } diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt index 2355fd513a..2180c6eb12 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_precedence_foo__0.txt @@ -8,10 +8,10 @@ EXPRESSION: x != self.fb() && x != self.fa() IRs: -TMP_0(uint256) = INTERNAL_CALL, precedence.fb()() -TMP_1(bool) = x != TMP_0 -TMP_2(uint256) = INTERNAL_CALL, precedence.fa()() -TMP_3(bool) = x != TMP_2 -TMP_4(bool) = TMP_1 && TMP_3 -RETURN TMP_4"]; +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"]; } diff --git a/tests/e2e/vyper_parsing/test_data/in.vy b/tests/e2e/vyper_parsing/test_data/in.vy index e08f71a749..5d4827ca1d 100644 --- a/tests/e2e/vyper_parsing/test_data/in.vy +++ b/tests/e2e/vyper_parsing/test_data/in.vy @@ -4,13 +4,23 @@ enum Roles: 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: - a: int128 = 0 - b: int128 = 0 if x in self.roles[self]: return True + if x not in self.roles[self]: + return False + return False @external @@ -20,4 +30,7 @@ def foo(x: int128) -> bool: if x in [a, b]: return True + if x not in [a, b]: + raise "nope" + return False \ No newline at end of file From 9fdc7dc22878a1acda4c16f25fe59d80e414d2ce Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 4 Sep 2023 13:54:52 -0500 Subject: [PATCH 56/69] conditionally apply refinements to Solidity --- slither/slithir/convert.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 8bb20412bf..91fe30da7f 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -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]: @@ -1910,7 +1910,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 """ @@ -1920,9 +1920,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) - # TODO refine only for Solidity - # 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) From 16140f29c854e6db7ba43f94a3c0c9cd640944e1 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 4 Sep 2023 13:55:28 -0500 Subject: [PATCH 57/69] cleanup and formatting --- .../core/declarations/solidity_variables.py | 45 +++++++++---------- slither/vyper_parsing/ast/types.py | 3 -- slither/vyper_parsing/declarations/struct.py | 6 +-- .../vyper_parsing/variables/event_variable.py | 7 ++- .../vyper_parsing/vyper_compilation_unit.py | 4 +- tests/unit/core/test_source_mapping.py | 3 +- 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 03fd81f041..d5aec009f7 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -40,7 +40,6 @@ "chain.id": "uint256", "block.prevhash": "bytes32", "self.balance": "uint256", - } SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { @@ -89,30 +88,30 @@ "code(address)": ["bytes"], "codehash(address)": ["bytes32"], # Vyper - "create_from_blueprint()":[], - "empty()":[], - "convert()":[], # TODO make type conversion - "len()":["uint256"], - "method_id()":[], + "create_from_blueprint()": [], + "empty()": [], + "convert()": [], # TODO make type conversion + "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"], + "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"], } diff --git a/slither/vyper_parsing/ast/types.py b/slither/vyper_parsing/ast/types.py index e07e6d2132..6eff6d2527 100644 --- a/slither/vyper_parsing/ast/types.py +++ b/slither/vyper_parsing/ast/types.py @@ -85,9 +85,6 @@ class Index(ASTNode): value: ASTNode -# TODO CONSTANT? - - @dataclass class Bytes(ASTNode): value: bytes diff --git a/slither/vyper_parsing/declarations/struct.py b/slither/vyper_parsing/declarations/struct.py index 0da2c7fed0..70a6bd7b72 100644 --- a/slither/vyper_parsing/declarations/struct.py +++ b/slither/vyper_parsing/declarations/struct.py @@ -7,17 +7,15 @@ class StructVyper: - def __init__( # pylint: disable=too-many-arguments + def __init__( self, st: Structure, struct: StructDef, ) -> None: - print(struct) - self._structure = st st.name = struct.name - # st.canonical_name = canonicalName + st.canonical_name = struct.name + self._structure.contract.name self._elemsNotParsed: List[AnnAssign] = struct.body diff --git a/slither/vyper_parsing/variables/event_variable.py b/slither/vyper_parsing/variables/event_variable.py index 2dc5db5441..5167610a80 100644 --- a/slither/vyper_parsing/variables/event_variable.py +++ b/slither/vyper_parsing/variables/event_variable.py @@ -10,8 +10,11 @@ def __init__(self, variable: EventVariable, variable_data: AnnAssign): print(variable_data) 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 + 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 diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index 2650ffe8e7..d4ebf8415a 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -52,12 +52,12 @@ def analyze_contracts(self) -> None: if not self._parsed: raise SlitherException("Parse the contract before running analyses") - for contract, contract_parser in self._underlying_contract_to_parser.items(): + 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, contract_parser in self._underlying_contract_to_parser.items(): + for contract_parser in self._underlying_contract_to_parser.values(): contract_parser.analyze() self._convert_to_slithir() diff --git a/tests/unit/core/test_source_mapping.py b/tests/unit/core/test_source_mapping.py index 16a26215b6..552c08dc95 100644 --- a/tests/unit/core/test_source_mapping.py +++ b/tests/unit/core/test_source_mapping.py @@ -114,6 +114,7 @@ def test_references_user_defined_types_when_casting(solc_binary_path): 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. @@ -125,4 +126,4 @@ def test_references_self_identifier(): a = contracts[0].state_variables[0] assert len(a.references) == 1 lines = _sort_references_lines(a.references) - assert lines == [4] \ No newline at end of file + assert lines == [4] From 9c4bc505d39be9d4d7083caf7eda26ab2ffe3c2c Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 07:44:03 -0500 Subject: [PATCH 58/69] handle break/continue --- .../vyper_parsing/declarations/function.py | 65 ++++--- ..._parsing__vyper_cfgir_for2_for_loop__0.txt | 40 ++--- ...ast_parsing__vyper_cfgir_for3_get_D__0.txt | 40 ++--- ...g__vyper_cfgir_for_break_continue_f__0.txt | 164 ++++++++++++++++++ ...t_parsing__vyper_cfgir_for_for_loop__0.txt | 40 ++--- 5 files changed, 268 insertions(+), 81 deletions(-) create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_break_continue_f__0.txt diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index f023953faa..9932a7c226 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -229,7 +229,12 @@ def _parse_cfg(self, cfg: List[ASTNode]) -> None: curr_node = entry_node for expr in cfg: - def parse_statement(curr_node, expr): + 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) @@ -266,6 +271,8 @@ def parse_statement(curr_node, expr): 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() @@ -371,18 +378,10 @@ def parse_statement(curr_node, expr): else: raise NotImplementedError + # After creating condition node, we link it declaration of the loop variable link_underlying_nodes(counter_node, node_condition) - # We update the index variable or range variable in the loop body - 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) - new_node = body_node - - node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) - + # Create an expression for the loop increment (counter_var += 1) loop_increment = AugAssign( "-1:-1:-1", -1, @@ -394,6 +393,25 @@ def parse_statement(curr_node, expr): node_increment.add_unparsed_expression(loop_increment) link_underlying_nodes(node_increment, node_condition) + prev_continue_destination = continue_destination + prev_break_destination = break_destination + 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 + + # Reset to previous jump destinations for nested loops + continue_destination = prev_continue_destination + break_destination = prev_break_destination + if body_node is not None: link_underlying_nodes(body_node, node_increment) @@ -402,9 +420,15 @@ def parse_statement(curr_node, expr): curr_node = node_endLoop elif isinstance(expr, Continue): - pass + 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): - pass + 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: @@ -436,19 +460,22 @@ def parse_statement(curr_node, expr): true_node = None new_node = condition_node for stmt in expr.body: - true_node = parse_statement(new_node, stmt) + true_node = parse_statement( + new_node, stmt, continue_destination, break_destination + ) new_node = true_node - # link_underlying_nodes(condition_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) + false_node = parse_statement( + new_node, stmt, continue_destination, break_destination + ) new_node = false_node if false_node is not None: - # link_underlying_nodes(condition_node, false_node) link_underlying_nodes(false_node, endIf_node) else: @@ -481,13 +508,11 @@ def _add_param(self, param: Arg, initialized: bool = False) -> LocalVariableVype local_var = LocalVariable() local_var.set_function(self._function) local_var.set_offset(param.src, self._function.compilation_unit) - print("add_param", param) local_var_parser = LocalVariableVyper(local_var, param) if initialized: local_var.initialized = True - # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location if local_var.location == "default": local_var.set_location("memory") @@ -496,7 +521,6 @@ def _add_param(self, param: Arg, initialized: bool = False) -> LocalVariableVype def _parse_params(self, params: Arguments): - print(params) self._function.parameters_src().set_offset(params.src, self._function.compilation_unit) if params.defaults: self._function._default_args_as_expressions = params.defaults @@ -506,7 +530,6 @@ def _parse_params(self, params: Arguments): def _parse_returns(self, returns: Union[Name, Tuple, Subscript]): - print(returns) 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 diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt index 949e814fff..c1f5f2f13d 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt @@ -12,16 +12,18 @@ _strategies(address[3]) = ['strategies(address[3])']"]; 1->2; 2[label="Node Type: BEGIN_LOOP 2 "]; -2->3; -3[label="Node Type: NEW VARIABLE 3 +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)"]; -3->4; -4[label="Node Type: IF_LOOP 4 +4->5; +5[label="Node Type: IF_LOOP 5 EXPRESSION: counter_var <= 10 @@ -29,17 +31,25 @@ counter_var <= 10 IRs: TMP_0(bool) = counter_var <= 10 CONDITION TMP_0"]; -4->5[label="True"]; -4->7[label="False"]; -5[label="Node Type: NEW VARIABLE 5 +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)"]; -5->6; -6[label="Node Type: NEW VARIABLE 6 +7->8; +8[label="Node Type: NEW VARIABLE 8 EXPRESSION: max_withdraw = IStrategy(_strategies[i]).maxWithdraw(self) @@ -49,15 +59,5 @@ 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)"]; -6->8; -7[label="Node Type: END_LOOP 7 -"]; -8[label="Node Type: EXPRESSION 8 - -EXPRESSION: -counter_var += 1 - -IRs: -counter_var(uint256) = counter_var (c)+ 1"]; -8->4; +8->6; } diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt index ae6d397b05..f8ab6cef9a 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for3_get_D__0.txt @@ -12,16 +12,18 @@ S(uint256) := 0(uint256)"]; 1->2; 2[label="Node Type: BEGIN_LOOP 2 "]; -2->3; -3[label="Node Type: NEW VARIABLE 3 +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)"]; -3->4; -4[label="Node Type: IF_LOOP 4 +4->5; +5[label="Node Type: IF_LOOP 5 EXPRESSION: counter_var <= len()(_xp) @@ -30,33 +32,31 @@ IRs: TMP_0(uint256) = SOLIDITY_CALL len()(_xp) TMP_1(bool) = counter_var <= TMP_0 CONDITION TMP_1"]; -4->5[label="True"]; -4->7[label="False"]; -5[label="Node Type: NEW VARIABLE 5 +5->7[label="True"]; +5->3[label="False"]; +6[label="Node Type: EXPRESSION 6 EXPRESSION: -x = _xp[counter_var] +counter_var += 1 IRs: -REF_0(uint256) -> _xp[counter_var] -x(uint256) := REF_0(uint256)"]; -5->6; -6[label="Node Type: EXPRESSION 6 +counter_var(uint256) = counter_var (c)+ 1"]; +6->5; +7[label="Node Type: NEW VARIABLE 7 EXPRESSION: -S += x +x = _xp[counter_var] IRs: -S(uint256) = S (c)+ x"]; -6->8; -7[label="Node Type: END_LOOP 7 -"]; +REF_0(uint256) -> _xp[counter_var] +x(uint256) := REF_0(uint256)"]; +7->8; 8[label="Node Type: EXPRESSION 8 EXPRESSION: -counter_var += 1 +S += x IRs: -counter_var(uint256) = counter_var (c)+ 1"]; -8->4; +S(uint256) = S (c)+ x"]; +8->6; } diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_break_continue_f__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_break_continue_f__0.txt new file mode 100644 index 0000000000..b35bdaa944 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_break_continue_f__0.txt @@ -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; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt index 5c4123662f..575f0d55f3 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for_for_loop__0.txt @@ -4,16 +4,18 @@ digraph{ 0->1; 1[label="Node Type: BEGIN_LOOP 1 "]; -1->2; -2[label="Node Type: NEW VARIABLE 2 +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)"]; -2->3; -3[label="Node Type: IF_LOOP 3 +3->4; +4[label="Node Type: IF_LOOP 4 EXPRESSION: counter_var <= len()(self.strategies) @@ -22,9 +24,17 @@ IRs: TMP_0(uint256) = SOLIDITY_CALL len()(strategies) TMP_1(bool) = counter_var <= TMP_0 CONDITION TMP_1"]; -3->4[label="True"]; -3->6[label="False"]; -4[label="Node Type: NEW VARIABLE 4 +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] @@ -32,8 +42,8 @@ strategy = strategies[counter_var] IRs: REF_0(address) -> strategies[counter_var] strategy(address) := REF_0(address)"]; -4->5; -5[label="Node Type: NEW VARIABLE 5 +6->7; +7[label="Node Type: NEW VARIABLE 7 EXPRESSION: z = IStrategy(strategy).asset() @@ -42,15 +52,5 @@ 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)"]; -5->7; -6[label="Node Type: END_LOOP 6 -"]; -7[label="Node Type: EXPRESSION 7 - -EXPRESSION: -counter_var += 1 - -IRs: -counter_var(uint256) = counter_var (c)+ 1"]; -7->3; +7->5; } From 9df54e9c3a52859c42195ae24890e52f42079f3f Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 07:44:39 -0500 Subject: [PATCH 59/69] update tests --- ...ast_parsing__vyper_cfgir_builtins_c__0.txt | 38 +++++++++++++ ..._vyper_cfgir_builtins_test_builtins__0.txt | 9 +-- ...parsing__vyper_cfgir_default_args_a__0.txt | 28 +++++++++ ...parsing__vyper_cfgir_default_args_b__0.txt | 24 ++++++++ ...slitherConstructorConstantVariables__0.txt | 9 +++ ...yper_cfgir_interface_conversion_bar__0.txt | 57 +++++++++++++++++++ ...yper_cfgir_interface_conversion_foo__0.txt | 12 ++++ ..._vyper_cfgir_tuple_struct___default__0.txt | 28 +++++++++ ...slitherConstructorConstantVariables__0.txt | 9 +++ tests/e2e/vyper_parsing/test_data/ERC20.vy | 13 ----- tests/e2e/vyper_parsing/test_data/builtins.vy | 21 +++---- tests/e2e/vyper_parsing/test_data/chain.vy | 4 -- .../vyper_parsing/test_data/default_args.vy | 12 ++++ tests/e2e/vyper_parsing/test_data/for.vy | 3 - .../test_data/for_break_continue.vy | 17 ++++++ tests/e2e/vyper_parsing/test_data/initarry.vy | 4 +- .../{tricky.vy => interface_constant.vy} | 0 .../{tuple.vy => interface_conversion.vy} | 2 - .../vyper_parsing/test_data/tuple_struct.vy | 10 ++++ .../test_data/{literal.vy => types.vy} | 4 -- 20 files changed, 258 insertions(+), 46 deletions(-) create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_bar__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_foo__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_struct___default__0.txt create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_types_slitherConstructorConstantVariables__0.txt delete mode 100644 tests/e2e/vyper_parsing/test_data/chain.vy create mode 100644 tests/e2e/vyper_parsing/test_data/default_args.vy create mode 100644 tests/e2e/vyper_parsing/test_data/for_break_continue.vy rename tests/e2e/vyper_parsing/test_data/{tricky.vy => interface_constant.vy} (100%) rename tests/e2e/vyper_parsing/test_data/{tuple.vy => interface_conversion.vy} (99%) create mode 100644 tests/e2e/vyper_parsing/test_data/tuple_struct.vy rename tests/e2e/vyper_parsing/test_data/{literal.vy => types.vy} (99%) diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt new file mode 100644 index 0000000000..3c6eaec3e1 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt @@ -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)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt index 07141a2b12..9b7768a6b5 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt @@ -53,10 +53,11 @@ f(uint256) := block.timestamp(uint256)"]; 7[label="Node Type: NEW VARIABLE 7 EXPRESSION: -h = chain.id +h = bytes32(chain.id) IRs: -h(uint256) := chain.id(uint256)"]; +TMP_0 = CONVERT chain.id to bytes32 +h(bytes32) := TMP_0(bytes32)"]; 7->8; 8[label="Node Type: NEW VARIABLE 8 @@ -64,8 +65,8 @@ EXPRESSION: i = slice()(msg.data,0,32) IRs: -TMP_0(None) = SOLIDITY_CALL slice()(msg.data,0,32) -i(bytes[32]) = ['TMP_0(None)']"]; +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 diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt new file mode 100644 index 0000000000..2f0451a520 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_a__0.txt @@ -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)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt new file mode 100644 index 0000000000..23126acaec --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_default_args_b__0.txt @@ -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; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt new file mode 100644 index 0000000000..753f5a9385 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt @@ -0,0 +1,9 @@ +digraph{ +0[label="Node Type: OTHER_ENTRYPOINT 0 + +EXPRESSION: +MAX_TICKS_UINT = 50 + +IRs: +MAX_TICKS_UINT(uint256) := 50(uint256)"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_bar__0.txt new file mode 100644 index 0000000000..1a19c56145 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_bar__0.txt @@ -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 "]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_foo__0.txt new file mode 100644 index 0000000000..8d1c1166b2 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_foo__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_struct___default__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_struct___default__0.txt new file mode 100644 index 0000000000..7587cdfa2d --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_struct___default__0.txt @@ -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"]; +} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_types_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_types_slitherConstructorConstantVariables__0.txt new file mode 100644 index 0000000000..b53263a8d7 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_types_slitherConstructorConstantVariables__0.txt @@ -0,0 +1,9 @@ +digraph{ +0[label="Node Type: OTHER_ENTRYPOINT 0 + +EXPRESSION: +MAX_BANDS = 10 + +IRs: +MAX_BANDS(uint256) := 10(uint256)"]; +} diff --git a/tests/e2e/vyper_parsing/test_data/ERC20.vy b/tests/e2e/vyper_parsing/test_data/ERC20.vy index 682b8cf268..a3bb62694b 100644 --- a/tests/e2e/vyper_parsing/test_data/ERC20.vy +++ b/tests/e2e/vyper_parsing/test_data/ERC20.vy @@ -1,22 +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 diff --git a/tests/e2e/vyper_parsing/test_data/builtins.vy b/tests/e2e/vyper_parsing/test_data/builtins.vy index 4fb908a094..4c4a72927c 100644 --- a/tests/e2e/vyper_parsing/test_data/builtins.vy +++ b/tests/e2e/vyper_parsing/test_data/builtins.vy @@ -3,29 +3,22 @@ @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: uint256 = chain.id - + 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() diff --git a/tests/e2e/vyper_parsing/test_data/chain.vy b/tests/e2e/vyper_parsing/test_data/chain.vy deleted file mode 100644 index 81a7704794..0000000000 --- a/tests/e2e/vyper_parsing/test_data/chain.vy +++ /dev/null @@ -1,4 +0,0 @@ - -@external -def test(): - x: bytes32 = convert(chain.id, bytes32) \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/default_args.vy b/tests/e2e/vyper_parsing/test_data/default_args.vy new file mode 100644 index 0000000000..10115363b4 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/default_args.vy @@ -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) \ No newline at end of file diff --git a/tests/e2e/vyper_parsing/test_data/for.vy b/tests/e2e/vyper_parsing/test_data/for.vy index b9f09bf6f6..7df83bbbb7 100644 --- a/tests/e2e/vyper_parsing/test_data/for.vy +++ b/tests/e2e/vyper_parsing/test_data/for.vy @@ -16,9 +16,6 @@ interface IStrategy: def convertToShares(assets: uint256) -> uint256: view def previewWithdraw(assets: uint256) -> uint256: view - - - struct X: y: int8 diff --git a/tests/e2e/vyper_parsing/test_data/for_break_continue.vy b/tests/e2e/vyper_parsing/test_data/for_break_continue.vy new file mode 100644 index 0000000000..3ea8e6cbd1 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/for_break_continue.vy @@ -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 diff --git a/tests/e2e/vyper_parsing/test_data/initarry.vy b/tests/e2e/vyper_parsing/test_data/initarry.vy index bfbd29a27f..35c3c06937 100644 --- a/tests/e2e/vyper_parsing/test_data/initarry.vy +++ b/tests/e2e/vyper_parsing/test_data/initarry.vy @@ -3,8 +3,8 @@ interface ERC20: def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable def approve(_spender: address, _value: uint256) -> bool: nonpayable -BORROWED_TOKEN: immutable(ERC20) # x -COLLATERAL_TOKEN: immutable(ERC20) # x +BORROWED_TOKEN: immutable(ERC20) +COLLATERAL_TOKEN: immutable(ERC20) @external def __init__(x: address, y: address): diff --git a/tests/e2e/vyper_parsing/test_data/tricky.vy b/tests/e2e/vyper_parsing/test_data/interface_constant.vy similarity index 100% rename from tests/e2e/vyper_parsing/test_data/tricky.vy rename to tests/e2e/vyper_parsing/test_data/interface_constant.vy diff --git a/tests/e2e/vyper_parsing/test_data/tuple.vy b/tests/e2e/vyper_parsing/test_data/interface_conversion.vy similarity index 99% rename from tests/e2e/vyper_parsing/test_data/tuple.vy rename to tests/e2e/vyper_parsing/test_data/interface_conversion.vy index f0c7e66fe8..4bdbd72773 100644 --- a/tests/e2e/vyper_parsing/test_data/tuple.vy +++ b/tests/e2e/vyper_parsing/test_data/interface_conversion.vy @@ -1,5 +1,3 @@ - - interface Test: def foo() -> (int128, uint256): nonpayable diff --git a/tests/e2e/vyper_parsing/test_data/tuple_struct.vy b/tests/e2e/vyper_parsing/test_data/tuple_struct.vy new file mode 100644 index 0000000000..1bcb57b850 --- /dev/null +++ b/tests/e2e/vyper_parsing/test_data/tuple_struct.vy @@ -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] + + + + diff --git a/tests/e2e/vyper_parsing/test_data/literal.vy b/tests/e2e/vyper_parsing/test_data/types.vy similarity index 99% rename from tests/e2e/vyper_parsing/test_data/literal.vy rename to tests/e2e/vyper_parsing/test_data/types.vy index e0686301e7..02f18fe5ae 100644 --- a/tests/e2e/vyper_parsing/test_data/literal.vy +++ b/tests/e2e/vyper_parsing/test_data/types.vy @@ -3,13 +3,9 @@ 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]) From a6209dfe5315b5ceb01582d834e379905b315aa9 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 07:45:12 -0500 Subject: [PATCH 60/69] cleanup --- .../vyper_parsing/declarations/contract.py | 2 - .../expressions/expression_parsing.py | 28 ++----------- .../expressions/find_variable.py | 41 +++++++------------ slither/vyper_parsing/type_parsing.py | 6 +-- .../vyper_parsing/variables/event_variable.py | 1 - 5 files changed, 19 insertions(+), 59 deletions(-) diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index 0e2140fa1c..a4e098942a 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -459,7 +459,6 @@ def parse_structs(self) -> None: def parse_state_variables(self) -> None: for varNotParsed in self._variablesNotParsed: - print(varNotParsed) var = StateVariable() var.set_contract(self._contract) var.set_offset(varNotParsed.src, self._contract.compilation_unit) @@ -506,7 +505,6 @@ def analyze_state_variables(self): var_parser.analyze(self._contract) def analyze(self) -> None: - print("Analyze", self._contract._name) for struct_parser in self._structures_parser: struct_parser.analyze(self._contract) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 3f76ea2814..aec07143cc 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -74,9 +74,6 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": - print("parse_expression") - print(expression, "\n") - # assert False if isinstance(expression, Int): literal = Literal(str(expression.value), ElementaryType("uint256")) @@ -106,8 +103,6 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": return literal if isinstance(expression, Call): - print("Call") - print(expression) called = parse_expression(expression.func, caller_context) if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction): if called.value.name == "empty()": @@ -195,8 +190,6 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": rets = None if isinstance(called, Identifier): - print("called", called) - print("called.value", called.value.__class__.__name__) # Since the AST lacks the type of the return values, we recover it. if isinstance(called.value, Function): rets = called.value.returns @@ -235,7 +228,6 @@ def get_type_str(x): return x return str(x.type) - print(rets) # def vars_to_typestr(rets: List[Expression]) -> str: # if len(rets) == 0: # return "" @@ -248,7 +240,6 @@ def get_type_str(x): if len(rets) == 1 else f"tuple({','.join(map(get_type_str, rets))})" ) - print(arguments) parsed_expr = CallExpression(called, arguments, type_str) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr @@ -258,9 +249,7 @@ def get_type_str(x): if isinstance(expression.value, Name): # TODO this is ambiguous because it could be a state variable or a call to balance if expression.value.id == "self" and member_name != "balance": - var, was_created = find_variable(member_name, caller_context, is_self=True) - if was_created: - var.set_offset(expression.src, caller_context.compilation_unit) + 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) @@ -286,12 +275,9 @@ def get_type_str(x): # (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` - print(expr) - print(expr.__class__.__name__) - if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType): # If we access a member of an interface, needs to be interface instead of self namespace - var, was_created = find_variable(member_name, expr.type.type) + var = find_variable(member_name, expr.type.type) if isinstance(var, Function): rets = var.returns @@ -313,9 +299,7 @@ def get_type_str(x): return member_access if isinstance(expression, Name): - var, was_created = find_variable(expression.id, caller_context) - if was_created: - var.set_offset(expression.src, caller_context.compilation_unit) + var = find_variable(expression.id, caller_context) parsed_expr = Identifier(var) parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr @@ -398,12 +382,6 @@ def get_type_str(x): return conditions.pop() - for elem in expression.right.elements: - elem_expr = parse_expression(elem, caller_context) - print("elem", repr(elem_expr)) - parsed_expr = BinaryOperation(lhs, elem_expr, inner_op) - parsed_expr.set_offset(expression.src, caller_context.compilation_unit) - conditions.append(parsed_expr) else: # enum type membership check https://docs.vyperlang.org/en/stable/types.html?h#id18 is_member_op = ( BinaryOperationType.get_type("==") diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index e888aa0148..06fcce995d 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -35,7 +35,6 @@ def _find_variable_in_function_parser( if function_parser is None: return None func_variables = function_parser.variables_as_dict - print("func_variables", func_variables) if var_name in func_variables: return func_variables[var_name] @@ -51,13 +50,11 @@ def _find_in_contract( return None # variable are looked from the contract declarer - print(contract) 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} - # print(functions) if var_name in functions: return functions[var_name] @@ -74,7 +71,7 @@ def _find_in_contract( if var_name in enums: return enums[var_name] - # If the enum is refered as its name rather than its canonicalName + # 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] @@ -84,7 +81,7 @@ def _find_in_contract( def find_variable( var_name: str, - caller_context, + caller_context: Union[FunctionContract, Contract], is_self: bool = False, ) -> Tuple[ Union[ @@ -96,8 +93,7 @@ def find_variable( Event, Enum, Structure, - ], - bool, + ] ]: """ Return the variable found and a boolean indicating if the variable was created @@ -107,6 +103,8 @@ def find_variable( :type var_name: :param caller_context: :type caller_context: + :param is_self: + :type is_self: :return: :rtype: """ @@ -114,59 +112,48 @@ def find_variable( from slither.vyper_parsing.declarations.contract import ContractVyper from slither.vyper_parsing.declarations.function import FunctionVyper - print("caller_context") - print(caller_context) - print(caller_context.__class__.__name__) - print("var", var_name) if isinstance(caller_context, Contract): - direct_contracts = [caller_context] - direct_functions = caller_context.functions_declared current_scope = caller_context.file_scope next_context = caller_context else: - direct_contracts = [caller_context.contract] - direct_functions = caller_context.contract.functions_declared current_scope = caller_context.contract.file_scope next_context = caller_context.contract - # print(direct_functions) function_parser: Optional[FunctionVyper] = ( caller_context if isinstance(caller_context, FunctionContract) else None ) - # print("function_parser", function_parser) + # 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, False + return ret1 ret = _find_in_contract(var_name, next_context, caller_context) if ret: - return ret, False + return ret - # print(current_scope.variables) if var_name in current_scope.variables: - return current_scope.variables[var_name], False + 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], False + return all_enums[var_name] contracts = current_scope.contracts if var_name in contracts: - return contracts[var_name], False + return contracts[var_name] if var_name in SOLIDITY_VARIABLES: - return SolidityVariable(var_name), False + return SolidityVariable(var_name) if f"{var_name}()" in SOLIDITY_FUNCTIONS: - return SolidityFunction(f"{var_name}()"), False + return SolidityFunction(f"{var_name}()") - print(next_context.events_as_dict) if f"{var_name}()" in next_context.events_as_dict: - return next_context.events_as_dict[f"{var_name}()"], False + return next_context.events_as_dict[f"{var_name}()"] raise VariableNotFound(f"Variable not found: {var_name} (context {caller_context})") diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index fc45a144c7..b8f9b468e2 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -12,7 +12,7 @@ from slither.core.declarations.function_contract import FunctionContract -def parse_type(annotation: Union[Name, Subscript, Call], caller_context): +def parse_type(annotation: Union[Name, Subscript, Call, Tuple], caller_context): from slither.vyper_parsing.expressions.expression_parsing import parse_expression if isinstance(caller_context, FunctionContract): @@ -21,7 +21,7 @@ def parse_type(annotation: Union[Name, Subscript, Call], caller_context): contract = caller_context assert isinstance(annotation, (Name, Subscript, Call, Tuple)) - print(annotation) + if isinstance(annotation, Name): name = annotation.id elif isinstance(annotation, Subscript): @@ -90,11 +90,9 @@ def parse_type(annotation: Union[Name, Subscript, Call], caller_context): if name in contract.structures_as_dict: return UserDefinedType(contract.structures_as_dict[name]) - print(contract.enums_as_dict) if name in contract.enums_as_dict: return UserDefinedType(contract.enums_as_dict[name]) - print(contract.file_scope.contracts) if name in contract.file_scope.contracts: return UserDefinedType(contract.file_scope.contracts[name]) assert False diff --git a/slither/vyper_parsing/variables/event_variable.py b/slither/vyper_parsing/variables/event_variable.py index 5167610a80..2f7fe7c850 100644 --- a/slither/vyper_parsing/variables/event_variable.py +++ b/slither/vyper_parsing/variables/event_variable.py @@ -7,7 +7,6 @@ class EventVariableVyper: def __init__(self, variable: EventVariable, variable_data: AnnAssign): - print(variable_data) self._variable = variable self._variable.name = variable_data.target.id if ( From c21798021c3aebbb39348898ba1ed611370d4d28 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 10:11:00 -0500 Subject: [PATCH 61/69] fix some lint warnings --- slither/core/compilation_unit.py | 6 +- slither/slither.py | 3 +- .../visitors/slithir/expression_to_slithir.py | 4 +- slither/vyper_parsing/ast/types.py | 5 +- slither/vyper_parsing/cfg/node.py | 2 +- .../vyper_parsing/declarations/contract.py | 1 - slither/vyper_parsing/declarations/event.py | 1 - .../vyper_parsing/declarations/function.py | 457 +++++++++--------- slither/vyper_parsing/declarations/struct.py | 2 +- .../expressions/expression_parsing.py | 23 +- .../expressions/find_variable.py | 17 +- slither/vyper_parsing/type_parsing.py | 7 +- .../vyper_parsing/variables/event_variable.py | 2 - .../vyper_parsing/variables/state_variable.py | 2 - .../variables/structure_variable.py | 3 - .../vyper_parsing/vyper_compilation_unit.py | 2 +- tests/e2e/vyper_parsing/test_ast_parsing.py | 1 - .../unit/slithir/vyper/test_ir_generation.py | 27 +- 18 files changed, 249 insertions(+), 316 deletions(-) diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index 556c6c7da8..c1ce98d37e 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -38,10 +38,10 @@ class Language(Enum): def from_str(label: str): if label == "solc": return Language.SOLIDITY - elif label == "vyper": + if label == "vyper": return Language.VYPER - else: - raise ValueError(f"Unknown language: {label}") + + raise ValueError(f"Unknown language: {label}") # pylint: disable=too-many-instance-attributes,too-many-public-methods diff --git a/slither/slither.py b/slither/slither.py index 07444c72f8..b434cfccb5 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -13,6 +13,7 @@ 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() @@ -103,8 +104,6 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None: if compilation_unit_slither.is_vyper: vyper_parser = VyperCompilationUnit(compilation_unit_slither) for path, ast in compilation_unit.asts.items(): - from slither.vyper_parsing.ast.ast import parse - ast_nodes = parse(ast["ast"]) vyper_parser.parse_module(ast_nodes, path) self._parsers.append(vyper_parser) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index fa8cef8e3b..c43b2507f2 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -171,7 +171,9 @@ def __init__(self, expression: Expression, node: "Node") -> None: def result(self) -> List[Operation]: return self._result - def _post_assignement_operation(self, expression: AssignmentOperation) -> None: + def _post_assignement_operation( + self, expression: AssignmentOperation + ) -> None: # pylint: disable=too-many-branches,too-many-statements left = get(expression.expression_left) right = get(expression.expression_right) operation: Operation diff --git a/slither/vyper_parsing/ast/types.py b/slither/vyper_parsing/ast/types.py index 6eff6d2527..d62bf6fb45 100644 --- a/slither/vyper_parsing/ast/types.py +++ b/slither/vyper_parsing/ast/types.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import List, Optional, Dict, Union, ForwardRef +from typing import List, Optional, Union from dataclasses import dataclass @@ -167,9 +167,6 @@ class Raise(ASTNode): exc: ASTNode -from enum import Enum - - @dataclass class Expr(ASTNode): value: ASTNode diff --git a/slither/vyper_parsing/cfg/node.py b/slither/vyper_parsing/cfg/node.py index 3d5ffee91e..5a584fe164 100644 --- a/slither/vyper_parsing/cfg/node.py +++ b/slither/vyper_parsing/cfg/node.py @@ -1,4 +1,4 @@ -from typing import Union, Optional, Dict, TYPE_CHECKING +from typing import Optional, Dict from slither.core.cfg.node import Node from slither.core.cfg.node import NodeType diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index a4e098942a..2cd14f1d0b 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -1,4 +1,3 @@ -import logging from pathlib import Path from typing import List, TYPE_CHECKING from slither.vyper_parsing.ast.types import ( diff --git a/slither/vyper_parsing/declarations/event.py b/slither/vyper_parsing/declarations/event.py index e6ed5a12e3..b73e462113 100644 --- a/slither/vyper_parsing/declarations/event.py +++ b/slither/vyper_parsing/declarations/event.py @@ -1,7 +1,6 @@ """ Event module """ -from typing import TYPE_CHECKING, Dict from slither.core.variables.event_variable import EventVariable from slither.vyper_parsing.variables.event_variable import EventVariableVyper diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 9932a7c226..17d77cfb94 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -1,16 +1,13 @@ -import logging -from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple, Set +from typing import Dict, Union, List, TYPE_CHECKING, Tuple -from slither.core.cfg.node import NodeType, link_nodes, insert_node, Node +from slither.core.cfg.node import NodeType, link_nodes, Node from slither.core.cfg.scope import Scope -from slither.core.declarations.contract import Contract 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.expressions import AssignmentOperation 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 @@ -41,6 +38,7 @@ def __init__( 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] = {} @@ -226,277 +224,268 @@ def _parse_cfg(self, cfg: List[ASTNode]) -> None: self._function.entry_point = entry_node.underlying_node scope = Scope(True, False, self.underlying_function) - curr_node = entry_node - for expr in cfg: + 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) - 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 - 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) - elif isinstance(expr, (AugAssign, Assign)): + curr_node = new_node + + elif isinstance(expr, Expr): + # TODO This is a workaround to handle Vyper putting payable/view in the function body... + if not isinstance(expr.value, Name): new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) - new_node.add_unparsed_expression(expr) + new_node.add_unparsed_expression(expr.value) 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... - 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) + elif isinstance(expr, For): - curr_node = new_node + node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope) + node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) - elif isinstance(expr, For): + link_underlying_nodes(curr_node, node_startLoop) - node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope) - node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) + local_var = LocalVariable() + local_var.set_function(self._function) + local_var.set_offset(expr.src, self._function.compilation_unit) - 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( + 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, - 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"), - ) + 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: - raise NotImplementedError + loop_var_annotation = loop_iterator._elem_to_parse.value - # After creating condition node, we link it declaration of the loop variable - link_underlying_nodes(counter_node, node_condition) + 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, + ) - # Create an expression for the loop increment (counter_var += 1) - loop_increment = AugAssign( + elif isinstance(expr.iter, Call): # range + range_val = expr.iter.args[0] + cond_expr = Compare( "-1:-1:-1", -1, - target=Name("-1:-1:-1", -1, "counter_var"), - op="+=", - value=Int("-1:-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"), ) - node_increment = self._new_node(NodeType.EXPRESSION, expr.src, scope) - node_increment.add_unparsed_expression(loop_increment) - link_underlying_nodes(node_increment, node_condition) - - prev_continue_destination = continue_destination - prev_break_destination = break_destination - 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 - - # Reset to previous jump destinations for nested loops - continue_destination = prev_continue_destination - break_destination = prev_break_destination - - 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) + 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 - 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) + if body_node is not None: + link_underlying_nodes(body_node, node_increment) - link_underlying_nodes(curr_node, new_node) - curr_node = new_node + link_underlying_nodes(node_condition, node_endLoop) - elif isinstance(expr, Assert): - new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) - new_node.add_unparsed_expression(expr) + curr_node = node_endLoop - link_underlying_nodes(curr_node, new_node) - curr_node = new_node + 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, Log): - new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + 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 + 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) + elif isinstance(expr, Assert): + new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + new_node.add_unparsed_expression(expr) - endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope) + link_underlying_nodes(curr_node, new_node) + curr_node = new_node - 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 + elif isinstance(expr, Log): + new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) + new_node.add_unparsed_expression(expr.value) - link_underlying_nodes(true_node, endIf_node) + link_underlying_nodes(curr_node, new_node) + curr_node = new_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 + elif isinstance(expr, If): + condition_node = self._new_node(NodeType.IF, expr.test.src, scope) + condition_node.add_unparsed_expression(expr.test) - if false_node is not None: - link_underlying_nodes(false_node, endIf_node) + endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope) - else: - link_underlying_nodes(condition_node, endIf_node) + 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(curr_node, condition_node) - curr_node = endIf_node + link_underlying_nodes(true_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 + 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: - raise ParsingError(f"Statement not parsed {expr.__class__.__name__} {expr}") + link_underlying_nodes(condition_node, endIf_node) + + link_underlying_nodes(curr_node, condition_node) + curr_node = endIf_node - return curr_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 diff --git a/slither/vyper_parsing/declarations/struct.py b/slither/vyper_parsing/declarations/struct.py index 70a6bd7b72..308dbcb2bd 100644 --- a/slither/vyper_parsing/declarations/struct.py +++ b/slither/vyper_parsing/declarations/struct.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict, List +from typing import List from slither.core.declarations.structure import Structure from slither.core.variables.structure_variable import StructureVariable diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index aec07143cc..a5a031ecfe 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -1,8 +1,5 @@ -import logging -import re -from typing import Union, Dict, TYPE_CHECKING, List, Any - -import slither.core.expressions.type_conversion +from typing import Dict, TYPE_CHECKING +from collections import deque from slither.core.declarations.solidity_variables import ( SOLIDITY_VARIABLES_COMPOSED, SolidityVariableComposed, @@ -10,16 +7,11 @@ from slither.core.declarations import SolidityFunction, Function from slither.core.expressions import ( CallExpression, - ConditionalExpression, ElementaryTypeNameExpression, Identifier, IndexAccess, Literal, MemberAccess, - NewArray, - NewContract, - NewElementaryType, - SuperCallExpression, SelfIdentifier, TupleExpression, TypeConversion, @@ -43,12 +35,6 @@ 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 - -if TYPE_CHECKING: - from slither.core.expressions.expression import Expression - - -from collections import deque from slither.vyper_parsing.ast.types import ( Int, Call, @@ -72,8 +58,11 @@ Raise, ) +if TYPE_CHECKING: + from slither.core.expressions.expression import Expression + -def parse_expression(expression: Dict, caller_context) -> "Expression": +def parse_expression(expression: Dict, caller_context) -> "Expression": # pylint if isinstance(expression, Int): literal = Literal(str(expression.value), ElementaryType("uint256")) diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index 06fcce995d..cc6a48ae74 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -1,27 +1,17 @@ -from typing import TYPE_CHECKING, Optional, Union, List, Tuple +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.function_top_level import FunctionTopLevel -from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder from slither.core.declarations.solidity_variables import ( SOLIDITY_FUNCTIONS, SOLIDITY_VARIABLES, SolidityFunction, SolidityVariable, ) -from slither.core.scope.scope import FileScope -from slither.core.solidity_types import ( - ArrayType, - FunctionType, - MappingType, - TypeAlias, -) from slither.core.variables.variable import Variable -from slither.exceptions import SlitherError from slither.solc_parsing.exceptions import VariableNotFound if TYPE_CHECKING: @@ -109,8 +99,9 @@ def find_variable( :rtype: """ - from slither.vyper_parsing.declarations.contract import ContractVyper - from slither.vyper_parsing.declarations.function import FunctionVyper + from slither.vyper_parsing.declarations.function import ( + FunctionVyper, + ) # pylint: disable=import-outside-toplevel if isinstance(caller_context, Contract): current_scope = caller_context.file_scope diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index b8f9b468e2..21d91e513c 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -12,7 +12,9 @@ from slither.core.declarations.function_contract import FunctionContract -def parse_type(annotation: Union[Name, Subscript, Call, Tuple], caller_context): +def parse_type( + annotation: Union[Name, Subscript, Call, Tuple], caller_context +): # pylint disable=too-many-branches,too-many-return-statements,import-outside-toplevel from slither.vyper_parsing.expressions.expression_parsing import parse_expression if isinstance(caller_context, FunctionContract): @@ -33,8 +35,7 @@ def parse_type(annotation: Union[Name, Subscript, Call, Tuple], caller_context): type_ = parse_type(annotation.slice.value.elements[0], caller_context) length = parse_expression(annotation.slice.value.elements[1], caller_context) return ArrayType(type_, length) - else: - assert annotation.value.id == "HashMap" + 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) diff --git a/slither/vyper_parsing/variables/event_variable.py b/slither/vyper_parsing/variables/event_variable.py index 2f7fe7c850..507c17665e 100644 --- a/slither/vyper_parsing/variables/event_variable.py +++ b/slither/vyper_parsing/variables/event_variable.py @@ -1,5 +1,3 @@ -from typing import Dict - 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 diff --git a/slither/vyper_parsing/variables/state_variable.py b/slither/vyper_parsing/variables/state_variable.py index a2e925a6ea..361bbaba6e 100644 --- a/slither/vyper_parsing/variables/state_variable.py +++ b/slither/vyper_parsing/variables/state_variable.py @@ -1,5 +1,3 @@ -from typing import Dict - 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 diff --git a/slither/vyper_parsing/variables/structure_variable.py b/slither/vyper_parsing/variables/structure_variable.py index eab7a71c4e..7bef8712ea 100644 --- a/slither/vyper_parsing/variables/structure_variable.py +++ b/slither/vyper_parsing/variables/structure_variable.py @@ -1,6 +1,3 @@ -from typing import Dict - - 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 diff --git a/slither/vyper_parsing/vyper_compilation_unit.py b/slither/vyper_parsing/vyper_compilation_unit.py index d4ebf8415a..2a47d9864d 100644 --- a/slither/vyper_parsing/vyper_compilation_unit.py +++ b/slither/vyper_parsing/vyper_compilation_unit.py @@ -25,7 +25,7 @@ def parse_module(self, data: Module, filename: str): sourceUnit = int(sourceUnit_candidates[0]) self._compilation_unit.source_units[sourceUnit] = filename - if os.path.isfile(filename) and not filename in self._compilation_unit.core.source_code: + 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) diff --git a/tests/e2e/vyper_parsing/test_ast_parsing.py b/tests/e2e/vyper_parsing/test_ast_parsing.py index 8529f3e1c0..7ca8184364 100644 --- a/tests/e2e/vyper_parsing/test_ast_parsing.py +++ b/tests/e2e/vyper_parsing/test_ast_parsing.py @@ -1,4 +1,3 @@ -from typing import Dict from pathlib import Path from slither import Slither diff --git a/tests/unit/slithir/vyper/test_ir_generation.py b/tests/unit/slithir/vyper/test_ir_generation.py index 6bcdaab10e..73c9b5e70b 100644 --- a/tests/unit/slithir/vyper/test_ir_generation.py +++ b/tests/unit/slithir/vyper/test_ir_generation.py @@ -1,38 +1,13 @@ # # pylint: disable=too-many-lines -import pathlib -from argparse import ArgumentTypeError -from collections import defaultdict -from inspect import getsourcefile -from typing import Union, List, Dict, Callable -import pytest -from slither import Slither -from slither.core.cfg.node import Node, NodeType -from slither.core.declarations import Function, Contract -from slither.core.solidity_types import ArrayType, ElementaryType -from slither.core.variables.local_variable import LocalVariable -from slither.core.variables.state_variable import StateVariable +from slither.core.solidity_types import ElementaryType from slither.slithir.operations import ( - OperationWithLValue, Phi, - Assignment, - HighLevelCall, - Return, - Operation, - Binary, - BinaryType, InternalCall, - Index, - InitArray, ) -from slither.slithir.utils.ssa import is_used_later from slither.slithir.variables import ( Constant, - ReferenceVariable, - LocalIRVariable, - StateIRVariable, - TemporaryVariableSSA, ) From e80238d8e94b5adce5447d39767fbce85ce0a411 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 13:09:49 -0500 Subject: [PATCH 62/69] add struct defs to file scope --- slither/vyper_parsing/declarations/contract.py | 2 ++ slither/vyper_parsing/type_parsing.py | 3 +++ ...ant_slitherConstructorConstantVariables__0.txt | 4 ++-- .../vyper_parsing/test_data/interface_constant.vy | 15 +++++---------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index 2cd14f1d0b..e1b8ffd671 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -453,6 +453,8 @@ def parse_structs(self) -> None: 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 = [] diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index 21d91e513c..e11a4a9f79 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -96,4 +96,7 @@ def parse_type( 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]) assert False diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt index 753f5a9385..31ff6d4085 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_constant_slitherConstructorConstantVariables__0.txt @@ -2,8 +2,8 @@ digraph{ 0[label="Node Type: OTHER_ENTRYPOINT 0 EXPRESSION: -MAX_TICKS_UINT = 50 +MY_CONSTANT = 50 IRs: -MAX_TICKS_UINT(uint256) := 50(uint256)"]; +MY_CONSTANT(uint256) := 50(uint256)"]; } diff --git a/tests/e2e/vyper_parsing/test_data/interface_constant.vy b/tests/e2e/vyper_parsing/test_data/interface_constant.vy index 4a5b718518..7e6612c68f 100644 --- a/tests/e2e/vyper_parsing/test_data/interface_constant.vy +++ b/tests/e2e/vyper_parsing/test_data/interface_constant.vy @@ -1,12 +1,7 @@ -interface LMGauge: - def callback_collateral_shares(n: int256, collateral_per_share: DynArray[uint256, MAX_TICKS_UINT]): nonpayable - def callback_user_shares(user: address, n: int256, user_shares: DynArray[uint256, MAX_TICKS_UINT]): nonpayable +struct MyStruct: + liquidation_range: address +MY_CONSTANT: constant(uint256) = 50 +interface MyInterface: + def my_func(a: int256, b: DynArray[uint256, MY_CONSTANT]) -> MyStruct: nonpayable -MAX_TICKS_UINT: constant(uint256) = 50 - - -struct Loan: - liquidation_range: LMGauge - -x: public(Loan) \ No newline at end of file From 239369c853d5e8817f2fc9ed3f806450fb77340e Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 13:14:54 -0500 Subject: [PATCH 63/69] propagate return type on call to state variable with interface type --- .../expressions/expression_parsing.py | 24 +++++++- ...yper_cfgir_interface_conversion_baz__0.txt | 56 +++++++++++++++++++ .../test_data/interface_conversion.vy | 12 ++++ 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_baz__0.txt diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index a5a031ecfe..50dd05cf26 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -5,6 +5,7 @@ SolidityVariableComposed, ) from slither.core.declarations import SolidityFunction, Function +from slither.core.variables.state_variable import StateVariable from slither.core.expressions import ( CallExpression, ElementaryTypeNameExpression, @@ -63,6 +64,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylint + print("parse_expression", expression) if isinstance(expression, Int): literal = Literal(str(expression.value), ElementaryType("uint256")) @@ -178,8 +180,8 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin 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. if isinstance(called, Identifier): - # Since the AST lacks the type of the return values, we recover it. if isinstance(called.value, Function): rets = called.value.returns # Default arguments are not represented in the AST, so we recover them as well. @@ -264,7 +266,25 @@ def get_type_str(x): # (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` - if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType): + 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, Function): + rets = var.returns + + def get_type_str(x): + if isinstance(x, str): + return x + return str(x.type) + + type_str = ( + get_type_str(rets[0]) + if len(rets) == 1 + else f"tuple({','.join(map(get_type_str, rets))})" + ) + member_name_ret_type = type_str + + 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, Function): diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_baz__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_baz__0.txt new file mode 100644 index 0000000000..4280229d00 --- /dev/null +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_interface_conversion_baz__0.txt @@ -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 "]; +} diff --git a/tests/e2e/vyper_parsing/test_data/interface_conversion.vy b/tests/e2e/vyper_parsing/test_data/interface_conversion.vy index 4bdbd72773..ad30f0ebfd 100644 --- a/tests/e2e/vyper_parsing/test_data/interface_conversion.vy +++ b/tests/e2e/vyper_parsing/test_data/interface_conversion.vy @@ -1,6 +1,8 @@ interface Test: def foo() -> (int128, uint256): nonpayable +tester: Test + @internal def foo() -> (int128, int128): return 2, 3 @@ -15,3 +17,13 @@ def bar(): 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() + From e8fa8b85fa039dc303628fe545c618f39db8b5a5 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 13:15:41 -0500 Subject: [PATCH 64/69] more cleanup --- slither/core/compilation_unit.py | 4 ++ slither/core/declarations/function.py | 8 +-- .../core/declarations/solidity_variables.py | 4 +- slither/core/expressions/identifier.py | 1 - slither/detectors/abstract_detector.py | 2 +- .../visitors/slithir/expression_to_slithir.py | 1 - ...ast_parsing__vyper_cfgir_chain_test__0.txt | 13 ---- ...st_parsing__vyper_cfgir_if_compute__0.txt} | 72 +++++++++---------- ...slitherConstructorConstantVariables__0.txt | 9 --- ...slitherConstructorConstantVariables__0.txt | 9 --- .../ast_parsing__vyper_cfgir_tuple_bar__0.txt | 57 --------------- .../ast_parsing__vyper_cfgir_tuple_foo__0.txt | 12 ---- tests/e2e/vyper_parsing/test_data/if.vy | 37 +++++----- 13 files changed, 64 insertions(+), 165 deletions(-) delete mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt rename tests/e2e/vyper_parsing/snapshots/{ast_parsing__vyper_cfgir_if_limit_p_o__0.txt => ast_parsing__vyper_cfgir_if_compute__0.txt} (65%) delete mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt delete mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt delete mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt delete mode 100644 tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index c1ce98d37e..23387e6fc9 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -97,6 +97,10 @@ def source_units(self) -> Dict[int, str]: # region Compiler ################################################################################### ################################################################################### + @property + def language(self) -> Language: + return self._language + @property def is_vyper(self) -> bool: return self._language == Language.VYPER diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index a4a172b974..e803154d00 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -107,7 +107,6 @@ def _filter_state_variables_written(expressions: List["Expression"]): return ret -# TODO replace class FunctionLanguage(Enum): Solidity = 0 Yul = 1 @@ -220,8 +219,9 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None: 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 @@ -1527,7 +1527,6 @@ def is_reentrant(self) -> bool: def _analyze_read_write(self) -> None: """Compute variables read/written/...""" write_var = [x.variables_written_as_expression for x in self.nodes] - print(write_var) write_var = [x for x in write_var if x] write_var = [item for sublist in write_var for item in sublist] write_var = list(set(write_var)) @@ -1763,7 +1762,6 @@ def fix_phi( node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)] def generate_slithir_and_analyze(self) -> None: - print("generate_slithir_and_analyze") for node in self.nodes: node.slithir_generation() diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index d5aec009f7..7c81266bf6 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -17,6 +17,7 @@ "block": "", "super": "", "chain": "", + "ZERO_ADDRESS": "address", } SOLIDITY_VARIABLES_COMPOSED = { @@ -89,8 +90,9 @@ "codehash(address)": ["bytes32"], # Vyper "create_from_blueprint()": [], + "create_minimal_proxy_to()": [], "empty()": [], - "convert()": [], # TODO make type conversion + "convert()": [], "len()": ["uint256"], "method_id()": [], "unsafe_sub()": [], diff --git a/slither/core/expressions/identifier.py b/slither/core/expressions/identifier.py index d4c4261008..5cd29a9f5d 100644 --- a/slither/core/expressions/identifier.py +++ b/slither/core/expressions/identifier.py @@ -26,7 +26,6 @@ def __init__( ], ) -> None: super().__init__() - assert value # pylint: disable=import-outside-toplevel from slither.core.declarations import Contract, SolidityVariable, SolidityFunction from slither.solc_parsing.yul.evm_functions import YulBuiltin diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index c3f661a112..8baf9bb3c7 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -182,7 +182,7 @@ def _is_applicable_detector(self) -> bool: and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS ) if self.LANGUAGE: - return self.compilation_unit._language.value == self.LANGUAGE + return self.compilation_unit.language.value == self.LANGUAGE return True @abc.abstractmethod diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index c43b2507f2..ff26166a99 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -482,7 +482,6 @@ def _post_index_access(self, expression: IndexAccess) -> None: def _post_literal(self, expression: Literal) -> None: expression_type = expression.type assert isinstance(expression_type, ElementaryType) - print(expression.value) cst = Constant(expression.value, expression_type, expression.subdenomination) set_val(expression, cst) diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt deleted file mode 100644 index 6f12cb5302..0000000000 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_chain_test__0.txt +++ /dev/null @@ -1,13 +0,0 @@ -digraph{ -0[label="Node Type: ENTRY_POINT 0 -"]; -0->1; -1[label="Node Type: NEW VARIABLE 1 - -EXPRESSION: -x = bytes32(chain.id) - -IRs: -TMP_0 = CONVERT chain.id to bytes32 -x(bytes32) := TMP_0(bytes32)"]; -} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt similarity index 65% rename from tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt rename to tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt index 0c7ae7d7ad..b623aa188e 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_limit_p_o__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_if_compute__0.txt @@ -5,34 +5,34 @@ digraph{ 1[label="Node Type: NEW VARIABLE 1 EXPRESSION: -p_new = p +a = p IRs: -p_new(uint256) := p(uint256)"]; +a(uint256) := p(uint256)"]; 1->2; 2[label="Node Type: NEW VARIABLE 2 EXPRESSION: -dt = 1 +b = 1 IRs: -dt(uint256) := 1(uint256)"]; +b(uint256) := 1(uint256)"]; 2->3; 3[label="Node Type: NEW VARIABLE 3 EXPRESSION: -ratio = 0 +c = 0 IRs: -ratio(uint256) := 0(uint256)"]; +c(uint256) := 0(uint256)"]; 3->4; 4[label="Node Type: IF 4 EXPRESSION: -dt > 0 +b > 0 IRs: -TMP_0(bool) = dt > 0 +TMP_0(bool) = b > 0 CONDITION TMP_0"]; 4->6[label="True"]; 4->5[label="False"]; @@ -41,26 +41,26 @@ CONDITION TMP_0"]; 6[label="Node Type: NEW VARIABLE 6 EXPRESSION: -old_p_o = 1 +old_a = 1 IRs: -old_p_o(uint256) := 1(uint256)"]; +old_a(uint256) := 1(uint256)"]; 6->7; 7[label="Node Type: NEW VARIABLE 7 EXPRESSION: -old_ratio = 2 +old_c = 2 IRs: -old_ratio(uint256) := 2(uint256)"]; +old_c(uint256) := 2(uint256)"]; 7->8; 8[label="Node Type: IF 8 EXPRESSION: -p > old_p_o +p > old_a IRs: -TMP_1(bool) = p > old_p_o +TMP_1(bool) = p > old_a CONDITION TMP_1"]; 8->10[label="True"]; 8->15[label="False"]; @@ -70,23 +70,23 @@ CONDITION TMP_1"]; 10[label="Node Type: EXPRESSION 10 EXPRESSION: -ratio = unsafe_div()(old_p_o * 10 ** 18,p) +c = unsafe_div()(old_a * 10 ** 18,p) IRs: TMP_2(uint256) = 10 (c)** 18 -TMP_3(uint256) = old_p_o (c)* TMP_2 +TMP_3(uint256) = old_a (c)* TMP_2 TMP_4(None) = SOLIDITY_CALL unsafe_div()(TMP_3,p) -ratio(uint256) := TMP_4(None)"]; +c(uint256) := TMP_4(None)"]; 10->11; 11[label="Node Type: IF 11 EXPRESSION: -ratio < 10 ** 36 / 1 +c < 10 ** 36 / 1 IRs: TMP_5(uint256) = 10 (c)** 36 TMP_6(uint256) = TMP_5 (c)/ 1 -TMP_7(bool) = ratio < TMP_6 +TMP_7(bool) = c < TMP_6 CONDITION TMP_7"]; 11->13[label="True"]; 11->12[label="False"]; @@ -96,44 +96,44 @@ CONDITION TMP_7"]; 13[label="Node Type: EXPRESSION 13 EXPRESSION: -p_new = unsafe_div()(old_p_o * 1,10 ** 18) +a = unsafe_div()(old_a * 1,10 ** 18) IRs: -TMP_8(uint256) = old_p_o (c)* 1 +TMP_8(uint256) = old_a (c)* 1 TMP_9(uint256) = 10 (c)** 18 TMP_10(None) = SOLIDITY_CALL unsafe_div()(TMP_8,TMP_9) -p_new(uint256) := TMP_10(None)"]; +a(uint256) := TMP_10(None)"]; 13->14; 14[label="Node Type: EXPRESSION 14 EXPRESSION: -ratio = 10 ** 36 / 1 +c = 10 ** 36 / 1 IRs: TMP_11(uint256) = 10 (c)** 36 TMP_12(uint256) = TMP_11 (c)/ 1 -ratio(uint256) := TMP_12(uint256)"]; +c(uint256) := TMP_12(uint256)"]; 14->12; 15[label="Node Type: EXPRESSION 15 EXPRESSION: -ratio = unsafe_div()(p * 10 ** 18,old_p_o) +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_p_o) -ratio(uint256) := TMP_15(None)"]; +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: -ratio < 10 ** 36 / 1 +c < 10 ** 36 / 1 IRs: TMP_16(uint256) = 10 (c)** 36 TMP_17(uint256) = TMP_16 (c)/ 1 -TMP_18(bool) = ratio < TMP_17 +TMP_18(bool) = c < TMP_17 CONDITION TMP_18"]; 16->18[label="True"]; 16->17[label="False"]; @@ -143,30 +143,30 @@ CONDITION TMP_18"]; 18[label="Node Type: EXPRESSION 18 EXPRESSION: -p_new = unsafe_div()(old_p_o * 10 ** 18,1) +a = unsafe_div()(old_a * 10 ** 18,1) IRs: TMP_19(uint256) = 10 (c)** 18 -TMP_20(uint256) = old_p_o (c)* TMP_19 +TMP_20(uint256) = old_a (c)* TMP_19 TMP_21(None) = SOLIDITY_CALL unsafe_div()(TMP_20,1) -p_new(uint256) := TMP_21(None)"]; +a(uint256) := TMP_21(None)"]; 18->19; 19[label="Node Type: EXPRESSION 19 EXPRESSION: -ratio = 10 ** 36 / 1 +c = 10 ** 36 / 1 IRs: TMP_22(uint256) = 10 (c)** 36 TMP_23(uint256) = TMP_22 (c)/ 1 -ratio(uint256) := TMP_23(uint256)"]; +c(uint256) := TMP_23(uint256)"]; 19->17; 20[label="Node Type: EXPRESSION 20 EXPRESSION: -ratio = 1 +c = 1 IRs: -ratio(uint256) := 1(uint256)"]; +c(uint256) := 1(uint256)"]; 20->5; } diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt deleted file mode 100644 index b53263a8d7..0000000000 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_literal_slitherConstructorConstantVariables__0.txt +++ /dev/null @@ -1,9 +0,0 @@ -digraph{ -0[label="Node Type: OTHER_ENTRYPOINT 0 - -EXPRESSION: -MAX_BANDS = 10 - -IRs: -MAX_BANDS(uint256) := 10(uint256)"]; -} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt deleted file mode 100644 index 753f5a9385..0000000000 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tricky_slitherConstructorConstantVariables__0.txt +++ /dev/null @@ -1,9 +0,0 @@ -digraph{ -0[label="Node Type: OTHER_ENTRYPOINT 0 - -EXPRESSION: -MAX_TICKS_UINT = 50 - -IRs: -MAX_TICKS_UINT(uint256) := 50(uint256)"]; -} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt deleted file mode 100644 index 0d2540498a..0000000000 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_bar__0.txt +++ /dev/null @@ -1,57 +0,0 @@ -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, tuple.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 "]; -} diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt deleted file mode 100644 index 8d1c1166b2..0000000000 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_tuple_foo__0.txt +++ /dev/null @@ -1,12 +0,0 @@ -digraph{ -0[label="Node Type: ENTRY_POINT 0 -"]; -0->1; -1[label="Node Type: RETURN 1 - -EXPRESSION: -(2,3) - -IRs: -RETURN 2,3"]; -} diff --git a/tests/e2e/vyper_parsing/test_data/if.vy b/tests/e2e/vyper_parsing/test_data/if.vy index 23483ca455..e706b9c0e1 100644 --- a/tests/e2e/vyper_parsing/test_data/if.vy +++ b/tests/e2e/vyper_parsing/test_data/if.vy @@ -1,25 +1,22 @@ @external @view -def limit_p_o(p: uint256): - p_new: uint256 = p - dt: uint256 = 1 - ratio: uint256 = 0 +def compute(p: uint256): + a: uint256 = p + b: uint256 = 1 + c: uint256 = 0 - if dt > 0: - old_p_o: uint256 = 1 - old_ratio: uint256 = 2 - # ratio = p_o_min / p_o_max - if p > old_p_o: - ratio = unsafe_div(old_p_o * 10**18, p) - if ratio < 10**36 / 1: - p_new = unsafe_div(old_p_o * 1, 10**18) - ratio = 10**36 / 1 + 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: - ratio = unsafe_div(p * 10**18, old_p_o) - if ratio < 10**36 / 1: - p_new = unsafe_div(old_p_o * 10**18, 1) - ratio = 10**36 / 1 + 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 - # ratio is guaranteed to be less than 1e18 - # Also guaranteed to be limited, therefore can have all ops unsafe - ratio = 1 + c = 1 From 961db4563ffd4a0e30707b28355e869fa32f815a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 13:50:50 -0500 Subject: [PATCH 65/69] address lints and improve typing --- .../visitors/slithir/expression_to_slithir.py | 5 +- slither/vyper_parsing/ast/ast.py | 46 ++++- .../vyper_parsing/declarations/contract.py | 3 +- slither/vyper_parsing/declarations/event.py | 2 +- .../vyper_parsing/declarations/function.py | 39 ++++- slither/vyper_parsing/declarations/struct.py | 2 +- .../expressions/expression_parsing.py | 161 ++++++++---------- .../expressions/find_variable.py | 4 +- slither/vyper_parsing/type_parsing.py | 13 +- 9 files changed, 162 insertions(+), 113 deletions(-) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index ff26166a99..1a2a85b2d2 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -171,9 +171,8 @@ def __init__(self, expression: Expression, node: "Node") -> None: def result(self) -> List[Operation]: return self._result - def _post_assignement_operation( - self, expression: AssignmentOperation - ) -> None: # pylint: disable=too-many-branches,too-many-statements + # 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) operation: Operation diff --git a/slither/vyper_parsing/ast/ast.py b/slither/vyper_parsing/ast/ast.py index 228805c895..f05a167dc2 100644 --- a/slither/vyper_parsing/ast/ast.py +++ b/slither/vyper_parsing/ast/ast.py @@ -1,6 +1,48 @@ from typing import Dict, Callable, List -from slither.vyper_parsing.ast.types import ASTNode -from slither.vyper_parsing.ast.types import * +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): diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index e1b8ffd671..6ca9c6557f 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -32,7 +32,7 @@ from slither.vyper_parsing.vyper_compilation_unit import VyperCompilationUnit -class ContractVyper: +class ContractVyper: # pylint: disable=too-many-instance-attributes def __init__( self, slither_parser: "VyperCompilationUnit", contract: Contract, module: Module ) -> None: @@ -426,6 +426,7 @@ def _parse_contract_items(self) -> None: 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 diff --git a/slither/vyper_parsing/declarations/event.py b/slither/vyper_parsing/declarations/event.py index b73e462113..43d7814394 100644 --- a/slither/vyper_parsing/declarations/event.py +++ b/slither/vyper_parsing/declarations/event.py @@ -11,7 +11,7 @@ from slither.vyper_parsing.ast.types import EventDef -class EventVyper: +class EventVyper: # pylint: disable=too-few-public-methods """ Event class """ diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 17d77cfb94..5a898aaf2e 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -1,4 +1,4 @@ -from typing import Dict, Union, List, TYPE_CHECKING, Tuple +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 @@ -13,7 +13,33 @@ 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 * +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 @@ -24,7 +50,7 @@ def link_underlying_nodes(node1: NodeVyper, node2: NodeVyper): link_nodes(node1.underlying_node, node2.underlying_node) -class FunctionVyper: +class FunctionVyper: # pylint: disable=too-many-instance-attributes def __init__( self, function: Function, @@ -183,7 +209,7 @@ def _analyze_decorator(self) -> None: contract = self._contract_parser.underlying_contract compilation_unit = self._contract_parser.underlying_contract.compilation_unit modifier = Modifier(compilation_unit) - modifier._name = name + modifier.name = name modifier.set_offset(decorator.src, compilation_unit) modifier.set_contract(contract) modifier.set_contract_declarer(contract) @@ -218,6 +244,7 @@ def _new_node( ################################################################################### ################################################################################### + # 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) @@ -517,7 +544,7 @@ def _parse_params(self, params: Arguments): local_var = self._add_param(param) self._function.add_parameters(local_var.underlying_variable) - def _parse_returns(self, returns: Union[Name, Tuple, Subscript]): + 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 @@ -527,7 +554,7 @@ def _parse_returns(self, returns: Union[Name, Tuple, 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, Tuple) + 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) diff --git a/slither/vyper_parsing/declarations/struct.py b/slither/vyper_parsing/declarations/struct.py index 308dbcb2bd..3a3ccf7b87 100644 --- a/slither/vyper_parsing/declarations/struct.py +++ b/slither/vyper_parsing/declarations/struct.py @@ -6,7 +6,7 @@ from slither.vyper_parsing.ast.types import StructDef, AnnAssign -class StructVyper: +class StructVyper: # pylint: disable=too-few-public-methods def __init__( self, st: Structure, diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 50dd05cf26..6edc46f74a 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -1,10 +1,10 @@ -from typing import Dict, TYPE_CHECKING +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, Function +from slither.core.declarations import SolidityFunction, FunctionContract from slither.core.variables.state_variable import StateVariable from slither.core.expressions import ( CallExpression, @@ -57,14 +57,25 @@ AugAssign, VyList, Raise, + ASTNode, ) if TYPE_CHECKING: from slither.core.expressions.expression import Expression -def parse_expression(expression: Dict, caller_context) -> "Expression": # pylint - print("parse_expression", 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")) @@ -103,14 +114,14 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr - elif called.value.name == "convert()": + 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 - elif called.value.name == "min_value()": + if called.value.name == "min_value()": type_to = parse_type(expression.args[0], caller_context) member_type = str(type_to) # TODO return Literal @@ -126,7 +137,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr - elif called.value.name == "max_value()": + if called.value.name == "max_value()": type_to = parse_type(expression.args[0], caller_context) member_type = str(type_to) # TODO return Literal @@ -142,7 +153,7 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin parsed_expr.set_offset(expression.src, caller_context.compilation_unit) return parsed_expr - elif called.value.name == "raw_call()": + 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... @@ -182,9 +193,10 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin rets = None # Since the AST lacks the type of the return values, we recover it. if isinstance(called, Identifier): - if isinstance(called.value, Function): + 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 ): @@ -209,28 +221,9 @@ def parse_expression(expression: Dict, caller_context) -> "Expression": # pylin 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] - - if rets is None: - rets = ["tuple()"] - - def get_type_str(x): - if isinstance(x, str): - return x - return str(x.type) - - # def vars_to_typestr(rets: List[Expression]) -> str: - # if len(rets) == 0: - # return "" - # if len(rets) == 1: - # return str(rets[0].type) - # return f"tuple({','.join(str(ret.type) for ret in rets)})" - - type_str = ( - get_type_str(rets[0]) - if len(rets) == 1 - else f"tuple({','.join(map(get_type_str, rets))})" - ) + 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 @@ -266,41 +259,28 @@ def get_type_str(x): # (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` - if isinstance(expr, Identifier) and isinstance(expr.value, StateVariable) and isinstance(expr.value.type, UserDefinedType) and isinstance(expr.value.type.type, Contract): + 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, Function): + if isinstance(var, FunctionContract): rets = var.returns + member_name_ret_type = vars_to_typestr(rets) - def get_type_str(x): - if isinstance(x, str): - return x - return str(x.type) - - type_str = ( - get_type_str(rets[0]) - if len(rets) == 1 - else f"tuple({','.join(map(get_type_str, rets))})" - ) - member_name_ret_type = type_str - - if isinstance(expr, TypeConversion) and isinstance(expr.type, UserDefinedType) and isinstance(expr.type.type, Contract): + 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, Function): + if isinstance(var, FunctionContract): rets = var.returns - - def get_type_str(x): - if isinstance(x, str): - return x - return str(x.type) - - type_str = ( - get_type_str(rets[0]) - if len(rets) == 1 - else f"tuple({','.join(map(get_type_str, rets))})" - ) - member_name_ret_type = type_str + member_name_ret_type = vars_to_typestr(rets) member_access = MemberAccess(member_name, member_name_ret_type, expr) @@ -359,7 +339,9 @@ def get_type_str(x): 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 + 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 = ( @@ -391,37 +373,36 @@ def get_type_str(x): return conditions.pop() - else: # 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 - - else: # a regular logical operator - rhs = parse_expression(expression.right, caller_context) - op = BinaryOperationType.get_type(expression.op) + # 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 + 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) diff --git a/slither/vyper_parsing/expressions/find_variable.py b/slither/vyper_parsing/expressions/find_variable.py index cc6a48ae74..0509a29d73 100644 --- a/slither/vyper_parsing/expressions/find_variable.py +++ b/slither/vyper_parsing/expressions/find_variable.py @@ -98,10 +98,10 @@ def find_variable( :return: :rtype: """ - + # pylint: disable=import-outside-toplevel from slither.vyper_parsing.declarations.function import ( FunctionVyper, - ) # pylint: disable=import-outside-toplevel + ) if isinstance(caller_context, Contract): current_scope = caller_context.file_scope diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index e11a4a9f79..9156118669 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -1,20 +1,19 @@ +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.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple -from typing import Union from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.declarations import FunctionContract, Contract -from slither.core.declarations.function_contract import FunctionContract - - +# 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 -): # pylint disable=too-many-branches,too-many-return-statements,import-outside-toplevel + 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): From 404914cdfa574ce51b88c0faa04b9d71d1b979ec Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 5 Sep 2023 14:22:41 -0500 Subject: [PATCH 66/69] link issues for TODO comments, lint --- .../vyper_parsing/declarations/function.py | 2 +- .../expressions/expression_parsing.py | 11 +++-- slither/vyper_parsing/type_parsing.py | 46 +++++++++---------- .../vyper_parsing/variables/local_variable.py | 9 ++-- tests/unit/core/test_function_declaration.py | 5 ++ 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index 5a898aaf2e..70e04c8e51 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -282,7 +282,7 @@ def parse_statement( curr_node = new_node elif isinstance(expr, Expr): - # TODO This is a workaround to handle Vyper putting payable/view in the function body... + # 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) diff --git a/slither/vyper_parsing/expressions/expression_parsing.py b/slither/vyper_parsing/expressions/expression_parsing.py index 6edc46f74a..d51467fab4 100644 --- a/slither/vyper_parsing/expressions/expression_parsing.py +++ b/slither/vyper_parsing/expressions/expression_parsing.py @@ -83,7 +83,7 @@ def parse_expression( return literal if isinstance(expression, Hex): - # TODO this is an implicit conversion and could potentially be bytes20 or other? + # 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 @@ -191,7 +191,7 @@ def parse_expression( 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. + # 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 @@ -212,7 +212,7 @@ def parse_expression( 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. + # 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) @@ -231,7 +231,7 @@ def parse_expression( 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 + # 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) @@ -241,6 +241,7 @@ def parse_expression( 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) @@ -258,7 +259,7 @@ def parse_expression( 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` + # its enclosing `CallExpression`. https://github.com/vyperlang/vyper/issues/3581 if ( isinstance(expr, Identifier) and isinstance(expr.value, StateVariable) diff --git a/slither/vyper_parsing/type_parsing.py b/slither/vyper_parsing/type_parsing.py index 9156118669..34c76cc6e3 100644 --- a/slither/vyper_parsing/type_parsing.py +++ b/slither/vyper_parsing/type_parsing.py @@ -5,9 +5,10 @@ ) # TODO rename solidity type from slither.core.solidity_types.array_type import ArrayType from slither.core.solidity_types.mapping_type import MappingType -from slither.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple 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( @@ -25,9 +26,24 @@ def parse_type( 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... + # 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": @@ -44,17 +60,17 @@ def parse_type( 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. - # Subscript(src='13:10:0', node_id=7, value=Name(src='13:6:0', node_id=8, id='String'), slice=Index(src='13:10:0', node_id=12, value=Int(src='20:2:0', node_id=10, value=64))) + # 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 + # 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): @@ -80,22 +96,4 @@ def parse_type( return UserDefinedType(st) - else: - assert False - - lname = name.lower() # TODO 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]) - assert False + raise ParsingError(f"Type name not found {name} context {caller_context}") diff --git a/slither/vyper_parsing/variables/local_variable.py b/slither/vyper_parsing/variables/local_variable.py index b50dea44b1..1195743e17 100644 --- a/slither/vyper_parsing/variables/local_variable.py +++ b/slither/vyper_parsing/variables/local_variable.py @@ -6,7 +6,7 @@ class LocalVariableVyper: - def __init__(self, variable: LocalVariable, variable_data: Union[Arg, Name]) -> None: + def __init__(self, variable: LocalVariable, variable_data: Union[Arg, AnnAssign, Name]) -> None: self._variable: LocalVariable = variable if isinstance(variable_data, Arg): @@ -15,12 +15,9 @@ def __init__(self, variable: LocalVariable, variable_data: Union[Arg, Name]) -> elif isinstance(variable_data, AnnAssign): self._variable.name = variable_data.target.id self._elem_to_parse = variable_data.annotation - elif isinstance(variable_data, Name): - self._variable.name = variable_data.id - self._elem_to_parse = variable_data else: - # param Subscript - self._variable.name = "" + 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)) diff --git a/tests/unit/core/test_function_declaration.py b/tests/unit/core/test_function_declaration.py index c4844074ef..cea207613a 100644 --- a/tests/unit/core/test_function_declaration.py +++ b/tests/unit/core/test_function_declaration.py @@ -305,6 +305,7 @@ def test_public_variable(solc_binary_path) -> None: assert var.type == ElementaryType("bytes32") +# pylint: disable=too-many-statements def test_vyper_functions(slither_from_vyper_source) -> None: with slither_from_vyper_source( """ @@ -352,6 +353,7 @@ def __default__(): 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 @@ -360,6 +362,7 @@ def __default__(): 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 @@ -370,10 +373,12 @@ def __default__(): 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 From 37d714bb0cf798d91c497a07e0ad2bbade5e33c9 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Thu, 12 Oct 2023 15:52:55 +0200 Subject: [PATCH 67/69] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 905470920f..16aa805680 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ "prettytable>=3.3.0", "pycryptodome>=3.4.6", # "crytic-compile>=0.3.1,<0.4.0", - "crytic-compile@git+https://github.com/crytic/crytic-compile.git@feat/vyper-standard-json#egg=crytic-compile", + "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", From 4a8385813f0cf9df945fc8519090d8e8923cf9f4 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Thu, 12 Oct 2023 16:11:21 +0200 Subject: [PATCH 68/69] fix CI --- tests/unit/slithir/test_ssa_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 688c0a5fb1..1ecf82a2dd 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -1118,7 +1118,7 @@ def test_issue_1846_ternary_in_ternary(slither_from_solidity_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 From 2a7e514a5d2db8276c6bef675e137a3cbf92a780 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Thu, 12 Oct 2023 16:24:41 +0200 Subject: [PATCH 69/69] Fix snapshot --- .../snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt | 2 +- .../ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt | 2 +- .../snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt | 2 +- .../snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt index 3c6eaec3e1..1f973fcdb6 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt @@ -8,7 +8,7 @@ EXPRESSION: user_shares = () IRs: -user_shares(uint256[10]) = []"]; +user_shares(uint256[10]) = []"]; 1->2; 2[label="Node Type: EXPRESSION 2 diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt index 9b7768a6b5..4719d99269 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt @@ -66,7 +66,7 @@ i = slice()(msg.data,0,32) IRs: TMP_1(None) = SOLIDITY_CALL slice()(msg.data,0,32) -i(bytes[32]) = ['TMP_1(None)']"]; +i(bytes[32]) = ['TMP_1(None)']"]; 8->9; 9[label="Node Type: NEW VARIABLE 9 diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt index c1f5f2f13d..9e35f147e3 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_for2_for_loop__0.txt @@ -8,7 +8,7 @@ EXPRESSION: _strategies = strategies IRs: -_strategies(address[3]) = ['strategies(address[3])']"]; +_strategies(address[3]) = ['strategies(address[3])']"]; 1->2; 2[label="Node Type: BEGIN_LOOP 2 "]; diff --git a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt index 15a091f260..ac49178228 100644 --- a/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt +++ b/tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_initarry_coins__0.txt @@ -10,7 +10,7 @@ EXPRESSION: 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)'] +TMP_4(address[2]) = ['TMP_2(address)', 'TMP_3(address)'] REF_0(address) -> TMP_4[i] RETURN REF_0"]; }