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] 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 76c28552e..c14efef77 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -16,6 +16,7 @@ SOLIDITY_VARIABLES = { "tx": "", "block": "", "super": "", + "chain": "", } SOLIDITY_VARIABLES_COMPOSED = { @@ -86,7 +87,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { "create_from_blueprint()":[], "empty()":[], "convert()":[], # TODO make type conversion - "len()":[], + "len()":["uint256"], "method_id()":[], "unsafe_sub()": [], "unsafe_add()": [], @@ -97,7 +98,15 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { "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 d252c5bce..6ad98296e 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -85,7 +85,7 @@ class ContractVyper: 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 @@ class ContractVyper: 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 c16bf16b3..5541f86df 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -30,6 +30,7 @@ class FunctionVyper: self, function: Function, function_data: Dict, + contract_parser: "ContractVyper", ) -> None: self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} @@ -40,6 +41,7 @@ class FunctionVyper: 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 @@ class FunctionVyper: 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 ee6e1c9d1..544dcbf65 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 parse_expression(expression: Dict, caller_context) -> "Expression": 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 parse_expression(expression: Dict, caller_context) -> "Expression": 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 parse_expression(expression: Dict, caller_context) -> "Expression": 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 45eb55351..3651b701f 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