mirror of https://github.com/crytic/slither
commit
9e003eeabe
@ -0,0 +1,6 @@ |
||||
from slither.core.expressions.identifier import Identifier |
||||
|
||||
|
||||
class SelfIdentifier(Identifier): |
||||
def __str__(self): |
||||
return "self." + str(self._value) |
@ -0,0 +1,466 @@ |
||||
from typing import Dict, Callable, List |
||||
from slither.vyper_parsing.ast.types import ( |
||||
ASTNode, |
||||
Module, |
||||
ImportFrom, |
||||
EventDef, |
||||
AnnAssign, |
||||
Name, |
||||
Call, |
||||
StructDef, |
||||
VariableDecl, |
||||
Subscript, |
||||
Index, |
||||
Hex, |
||||
Int, |
||||
Str, |
||||
Tuple, |
||||
FunctionDef, |
||||
Assign, |
||||
Raise, |
||||
Attribute, |
||||
Assert, |
||||
Keyword, |
||||
Arguments, |
||||
Arg, |
||||
UnaryOp, |
||||
BinOp, |
||||
Expr, |
||||
Log, |
||||
Return, |
||||
VyDict, |
||||
VyList, |
||||
NameConstant, |
||||
If, |
||||
Compare, |
||||
For, |
||||
Break, |
||||
Continue, |
||||
Pass, |
||||
InterfaceDef, |
||||
EnumDef, |
||||
Bytes, |
||||
AugAssign, |
||||
BoolOp, |
||||
) |
||||
|
||||
|
||||
class ParsingError(Exception): |
||||
pass |
||||
|
||||
|
||||
def _extract_base_props(raw: Dict) -> Dict: |
||||
return { |
||||
"src": raw["src"], |
||||
"node_id": raw["node_id"], |
||||
} |
||||
|
||||
|
||||
def _extract_decl_props(raw: Dict) -> Dict: |
||||
return { |
||||
"doc_string": parse_doc_str(raw["doc_string"]) if raw["doc_string"] else None, |
||||
**_extract_base_props(raw), |
||||
} |
||||
|
||||
|
||||
def parse_module(raw: Dict) -> Module: |
||||
nodes_parsed: List[ASTNode] = [] |
||||
|
||||
for node in raw["body"]: |
||||
nodes_parsed.append(parse(node)) |
||||
|
||||
return Module(name=raw["name"], body=nodes_parsed, **_extract_decl_props(raw)) |
||||
|
||||
|
||||
def parse_import_from(raw: Dict) -> ImportFrom: |
||||
return ImportFrom( |
||||
module=raw["module"], |
||||
name=raw["name"], |
||||
alias=raw["alias"], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_event_def(raw: Dict) -> EventDef: |
||||
body_parsed: List[ASTNode] = [] |
||||
for node in raw["body"]: |
||||
body_parsed.append(parse(node)) |
||||
|
||||
return EventDef( |
||||
name=raw["name"], |
||||
body=body_parsed, |
||||
*_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_ann_assign(raw: Dict) -> AnnAssign: |
||||
return AnnAssign( |
||||
target=parse(raw["target"]), |
||||
annotation=parse(raw["annotation"]), |
||||
value=parse(raw["value"]) if raw["value"] else None, |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_name(raw: Dict) -> Name: |
||||
return Name( |
||||
id=raw["id"], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_call(raw: Dict) -> Call: |
||||
return Call( |
||||
func=parse(raw["func"]), |
||||
args=[parse(arg) for arg in raw["args"]], |
||||
keyword=parse(raw["keyword"]) if raw["keyword"] else None, |
||||
keywords=[parse(keyword) for keyword in raw["keywords"]], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_struct_def(raw: Dict) -> StructDef: |
||||
body_parsed: List[ASTNode] = [] |
||||
for node in raw["body"]: |
||||
body_parsed.append(parse(node)) |
||||
|
||||
return StructDef( |
||||
name=raw["name"], |
||||
body=body_parsed, |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_variable_decl(raw: Dict) -> VariableDecl: |
||||
return VariableDecl( |
||||
annotation=parse(raw["annotation"]), |
||||
value=parse(raw["value"]) if raw["value"] else None, |
||||
target=parse(raw["target"]), |
||||
is_constant=raw["is_constant"], |
||||
is_immutable=raw["is_immutable"], |
||||
is_public=raw["is_public"], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_subscript(raw: Dict) -> Subscript: |
||||
return Subscript( |
||||
value=parse(raw["value"]), |
||||
slice=parse(raw["slice"]), |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_index(raw: Dict) -> Index: |
||||
return Index(value=parse(raw["value"]), **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_bytes(raw: Dict) -> Bytes: |
||||
return Bytes(value=raw["value"], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_hex(raw: Dict) -> Hex: |
||||
return Hex(value=raw["value"], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_int(raw: Dict) -> Int: |
||||
return Int(value=raw["value"], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_str(raw: Dict) -> Str: |
||||
return Str(value=raw["value"], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_tuple(raw: Dict) -> ASTNode: |
||||
return Tuple(elements=[parse(elem) for elem in raw["elements"]], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_function_def(raw: Dict) -> FunctionDef: |
||||
body_parsed: List[ASTNode] = [] |
||||
for node in raw["body"]: |
||||
body_parsed.append(parse(node)) |
||||
|
||||
decorators_parsed: List[ASTNode] = [] |
||||
for node in raw["decorator_list"]: |
||||
decorators_parsed.append(parse(node)) |
||||
|
||||
return FunctionDef( |
||||
name=raw["name"], |
||||
args=parse_arguments(raw["args"]), |
||||
returns=parse(raw["returns"]) if raw["returns"] else None, |
||||
body=body_parsed, |
||||
pos=raw["pos"], |
||||
decorators=decorators_parsed, |
||||
**_extract_decl_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_assign(raw: Dict) -> Assign: |
||||
return Assign( |
||||
target=parse(raw["target"]), |
||||
value=parse(raw["value"]), |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_attribute(raw: Dict) -> Attribute: |
||||
return Attribute( |
||||
value=parse(raw["value"]), |
||||
attr=raw["attr"], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_arguments(raw: Dict) -> Arguments: |
||||
return Arguments( |
||||
args=[parse_arg(arg) for arg in raw["args"]], |
||||
default=parse(raw["default"]) if raw["default"] else None, |
||||
defaults=[parse(x) for x in raw["defaults"]], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_arg(raw: Dict) -> Arg: |
||||
return Arg(arg=raw["arg"], annotation=parse(raw["annotation"]), **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_assert(raw: Dict) -> Assert: |
||||
return Assert( |
||||
test=parse(raw["test"]), |
||||
msg=parse(raw["msg"]) if raw["msg"] else None, |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_raise(raw: Dict) -> Raise: |
||||
return Raise(exc=parse(raw["exc"]) if raw["exc"] else None, **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_expr(raw: Dict) -> Expr: |
||||
return Expr(value=parse(raw["value"]), **_extract_base_props(raw)) |
||||
|
||||
|
||||
# This is done for convenience so we can call `UnaryOperationType.get_type` during expression parsing. |
||||
unop_ast_type_to_op_symbol = {"Not": "!", "USub": "-"} |
||||
|
||||
|
||||
def parse_unary_op(raw: Dict) -> UnaryOp: |
||||
unop_str = unop_ast_type_to_op_symbol[raw["op"]["ast_type"]] |
||||
return UnaryOp(op=unop_str, operand=parse(raw["operand"]), **_extract_base_props(raw)) |
||||
|
||||
|
||||
# This is done for convenience so we can call `BinaryOperationType.get_type` during expression parsing. |
||||
binop_ast_type_to_op_symbol = { |
||||
"Add": "+", |
||||
"Mult": "*", |
||||
"Sub": "-", |
||||
"Div": "/", |
||||
"Pow": "**", |
||||
"Mod": "%", |
||||
"BitAnd": "&", |
||||
"BitOr": "|", |
||||
"Shr": "<<", |
||||
"Shl": ">>", |
||||
"NotEq": "!=", |
||||
"Eq": "==", |
||||
"LtE": "<=", |
||||
"GtE": ">=", |
||||
"Lt": "<", |
||||
"Gt": ">", |
||||
"In": "In", |
||||
"NotIn": "NotIn", |
||||
} |
||||
|
||||
|
||||
def parse_bin_op(raw: Dict) -> BinOp: |
||||
arith_op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]] |
||||
return BinOp( |
||||
left=parse(raw["left"]), |
||||
op=arith_op_str, |
||||
right=parse(raw["right"]), |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_compare(raw: Dict) -> Compare: |
||||
logical_op_str = binop_ast_type_to_op_symbol[raw["op"]["ast_type"]] |
||||
return Compare( |
||||
left=parse(raw["left"]), |
||||
op=logical_op_str, |
||||
right=parse(raw["right"]), |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_keyword(raw: Dict) -> Keyword: |
||||
return Keyword(arg=raw["arg"], value=parse(raw["value"]), **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_log(raw: Dict) -> Log: |
||||
return Log(value=parse(raw["value"]), **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_return(raw: Dict) -> Return: |
||||
return Return(value=parse(raw["value"]) if raw["value"] else None, **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_dict(raw: Dict) -> ASTNode: |
||||
return VyDict( |
||||
keys=[parse(x) for x in raw["keys"]], |
||||
values=[parse(x) for x in raw["values"]], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_list(raw: Dict) -> VyList: |
||||
return VyList(elements=[parse(x) for x in raw["elements"]], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_name_constant(raw: Dict) -> NameConstant: |
||||
return NameConstant(value=raw["value"], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_doc_str(raw: Dict) -> str: |
||||
assert isinstance(raw["value"], str) |
||||
return raw["value"] |
||||
|
||||
|
||||
def parse_if(raw: Dict) -> ASTNode: |
||||
return If( |
||||
test=parse(raw["test"]), |
||||
body=[parse(x) for x in raw["body"]], |
||||
orelse=[parse(x) for x in raw["orelse"]], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_for(raw: Dict) -> For: |
||||
return For( |
||||
target=parse(raw["target"]), |
||||
iter=parse(raw["iter"]), |
||||
body=[parse(x) for x in raw["body"]], |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_break(raw: Dict) -> Break: |
||||
return Break(**_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_continue(raw: Dict) -> Continue: |
||||
return Continue(**_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_pass(raw: Dict) -> Pass: |
||||
return Pass( |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_interface_def(raw: Dict) -> InterfaceDef: |
||||
nodes_parsed: List[ASTNode] = [] |
||||
|
||||
for node in raw["body"]: |
||||
nodes_parsed.append(parse(node)) |
||||
return InterfaceDef(name=raw["name"], body=nodes_parsed, **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse_enum_def(raw: Dict) -> EnumDef: |
||||
nodes_parsed: List[ASTNode] = [] |
||||
|
||||
for node in raw["body"]: |
||||
nodes_parsed.append(parse(node)) |
||||
|
||||
return EnumDef(name=raw["name"], body=nodes_parsed, **_extract_base_props(raw)) |
||||
|
||||
|
||||
aug_assign_ast_type_to_op_symbol = { |
||||
"Add": "+=", |
||||
"Mult": "*=", |
||||
"Sub": "-=", |
||||
"Div": "-=", |
||||
"Pow": "**=", |
||||
"Mod": "%=", |
||||
"BitAnd": "&=", |
||||
"BitOr": "|=", |
||||
"Shr": "<<=", |
||||
"Shl": ">>=", |
||||
} |
||||
|
||||
|
||||
def parse_aug_assign(raw: Dict) -> AugAssign: |
||||
op_str = aug_assign_ast_type_to_op_symbol[raw["op"]["ast_type"]] |
||||
return AugAssign( |
||||
target=parse(raw["target"]), |
||||
op=op_str, |
||||
value=parse(raw["value"]), |
||||
**_extract_base_props(raw), |
||||
) |
||||
|
||||
|
||||
def parse_unsupported(raw: Dict) -> ASTNode: |
||||
raise ParsingError("unsupported Vyper node", raw["ast_type"], raw.keys(), raw) |
||||
|
||||
|
||||
bool_op_ast_type_to_op_symbol = {"And": "&&", "Or": "||"} |
||||
|
||||
|
||||
def parse_bool_op(raw: Dict) -> BoolOp: |
||||
op_str = bool_op_ast_type_to_op_symbol[raw["op"]["ast_type"]] |
||||
return BoolOp(op=op_str, values=[parse(x) for x in raw["values"]], **_extract_base_props(raw)) |
||||
|
||||
|
||||
def parse(raw: Dict) -> ASTNode: |
||||
try: |
||||
return PARSERS.get(raw["ast_type"], parse_unsupported)(raw) |
||||
except ParsingError as e: |
||||
raise e |
||||
except Exception as e: |
||||
raise e |
||||
# raise ParsingError("failed to parse Vyper node", raw["ast_type"], e, raw.keys(), raw) |
||||
|
||||
|
||||
PARSERS: Dict[str, Callable[[Dict], ASTNode]] = { |
||||
"Module": parse_module, |
||||
"ImportFrom": parse_import_from, |
||||
"EventDef": parse_event_def, |
||||
"AnnAssign": parse_ann_assign, |
||||
"Name": parse_name, |
||||
"Call": parse_call, |
||||
"Pass": parse_pass, |
||||
"StructDef": parse_struct_def, |
||||
"VariableDecl": parse_variable_decl, |
||||
"Subscript": parse_subscript, |
||||
"Index": parse_index, |
||||
"Hex": parse_hex, |
||||
"Int": parse_int, |
||||
"Str": parse_str, |
||||
"DocStr": parse_doc_str, |
||||
"Tuple": parse_tuple, |
||||
"FunctionDef": parse_function_def, |
||||
"Assign": parse_assign, |
||||
"Raise": parse_raise, |
||||
"Attribute": parse_attribute, |
||||
"Assert": parse_assert, |
||||
"keyword": parse_keyword, |
||||
"arguments": parse_arguments, |
||||
"arg": parse_arg, |
||||
"UnaryOp": parse_unary_op, |
||||
"BinOp": parse_bin_op, |
||||
"Expr": parse_expr, |
||||
"Log": parse_log, |
||||
"Return": parse_return, |
||||
"If": parse_if, |
||||
"Dict": parse_dict, |
||||
"List": parse_list, |
||||
"Compare": parse_compare, |
||||
"NameConstant": parse_name_constant, |
||||
"For": parse_for, |
||||
"Break": parse_break, |
||||
"Continue": parse_continue, |
||||
"InterfaceDef": parse_interface_def, |
||||
"EnumDef": parse_enum_def, |
||||
"Bytes": parse_bytes, |
||||
"AugAssign": parse_aug_assign, |
||||
"BoolOp": parse_bool_op, |
||||
} |
@ -0,0 +1,262 @@ |
||||
from __future__ import annotations |
||||
from typing import List, Optional, Union |
||||
from dataclasses import dataclass |
||||
|
||||
|
||||
@dataclass |
||||
class ASTNode: |
||||
src: str |
||||
node_id: int |
||||
|
||||
|
||||
@dataclass |
||||
class Definition(ASTNode): |
||||
doc_string: Optional[str] |
||||
|
||||
|
||||
@dataclass |
||||
class Module(Definition): |
||||
body: List[ASTNode] |
||||
name: str |
||||
|
||||
|
||||
@dataclass |
||||
class ImportFrom(ASTNode): |
||||
module: str |
||||
name: str |
||||
alias: Optional[str] |
||||
|
||||
|
||||
@dataclass |
||||
class EventDef(ASTNode): |
||||
name: str |
||||
body: List[AnnAssign] |
||||
|
||||
|
||||
@dataclass |
||||
class AnnAssign(ASTNode): |
||||
target: Name |
||||
annotation: Union[Subscript, Name, Call] |
||||
value: Optional[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Name(ASTNode): # type or identifier |
||||
id: str |
||||
|
||||
|
||||
@dataclass |
||||
class Call(ASTNode): |
||||
func: ASTNode |
||||
args: List[ASTNode] |
||||
keyword: Optional[ASTNode] |
||||
keywords: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Pass(ASTNode): |
||||
pass |
||||
|
||||
|
||||
@dataclass |
||||
class StructDef(ASTNode): |
||||
name: str |
||||
body: List[AnnAssign] |
||||
|
||||
|
||||
@dataclass |
||||
class VariableDecl(ASTNode): |
||||
annotation: ASTNode |
||||
target: ASTNode |
||||
value: Optional[ASTNode] |
||||
is_constant: bool |
||||
is_immutable: bool |
||||
is_public: bool |
||||
|
||||
|
||||
@dataclass |
||||
class Subscript(ASTNode): |
||||
value: ASTNode |
||||
slice: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Index(ASTNode): |
||||
value: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Bytes(ASTNode): |
||||
value: bytes |
||||
|
||||
|
||||
@dataclass |
||||
class Hex(ASTNode): |
||||
value: str |
||||
|
||||
|
||||
@dataclass |
||||
class Int(ASTNode): |
||||
value: int |
||||
|
||||
|
||||
@dataclass |
||||
class Str(ASTNode): |
||||
value: str |
||||
|
||||
|
||||
@dataclass |
||||
class VyList(ASTNode): |
||||
elements: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class VyDict(ASTNode): |
||||
keys: List[ASTNode] |
||||
values: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Tuple(ASTNode): |
||||
elements: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class FunctionDef(Definition): |
||||
name: str |
||||
args: Optional[Arguments] |
||||
returns: Optional[List[ASTNode]] |
||||
body: List[ASTNode] |
||||
decorators: Optional[List[ASTNode]] |
||||
pos: Optional[any] # not sure what this is |
||||
|
||||
|
||||
@dataclass |
||||
class Assign(ASTNode): |
||||
target: ASTNode |
||||
value: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Attribute(ASTNode): |
||||
value: ASTNode |
||||
attr: str |
||||
|
||||
|
||||
@dataclass |
||||
class Arguments(ASTNode): |
||||
args: List[Arg] |
||||
default: Optional[ASTNode] |
||||
defaults: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Arg(ASTNode): |
||||
arg: str |
||||
annotation: Optional[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Assert(ASTNode): |
||||
test: ASTNode |
||||
msg: Optional[Str] |
||||
|
||||
|
||||
@dataclass |
||||
class Raise(ASTNode): |
||||
exc: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Expr(ASTNode): |
||||
value: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class UnaryOp(ASTNode): |
||||
op: ASTNode |
||||
operand: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class BinOp(ASTNode): |
||||
left: ASTNode |
||||
op: str |
||||
right: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Keyword(ASTNode): |
||||
arg: str |
||||
value: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Log(ASTNode): |
||||
value: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class Return(ASTNode): |
||||
value: Optional[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class If(ASTNode): |
||||
test: ASTNode |
||||
body: List[ASTNode] |
||||
orelse: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Compare(ASTNode): |
||||
left: ASTNode |
||||
op: ASTNode |
||||
right: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class NameConstant(ASTNode): |
||||
value: bool |
||||
|
||||
|
||||
@dataclass |
||||
class For(ASTNode): |
||||
target: ASTNode |
||||
iter: ASTNode |
||||
body: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class Continue(ASTNode): |
||||
pass |
||||
|
||||
|
||||
@dataclass |
||||
class Break(ASTNode): |
||||
pass |
||||
|
||||
|
||||
@dataclass |
||||
class InterfaceDef(ASTNode): |
||||
name: str |
||||
body: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class EnumDef(ASTNode): |
||||
name: str |
||||
body: List[ASTNode] |
||||
|
||||
|
||||
@dataclass |
||||
class AugAssign(ASTNode): |
||||
target: ASTNode |
||||
op: ASTNode |
||||
value: ASTNode |
||||
|
||||
|
||||
@dataclass |
||||
class BoolOp(ASTNode): |
||||
op: ASTNode |
||||
values: List[ASTNode] |
@ -0,0 +1,66 @@ |
||||
from typing import Optional, Dict |
||||
|
||||
from slither.core.cfg.node import Node |
||||
from slither.core.cfg.node import NodeType |
||||
from slither.core.expressions.assignment_operation import ( |
||||
AssignmentOperation, |
||||
AssignmentOperationType, |
||||
) |
||||
from slither.core.expressions.identifier import Identifier |
||||
from slither.vyper_parsing.expressions.expression_parsing import parse_expression |
||||
from slither.visitors.expression.find_calls import FindCalls |
||||
from slither.visitors.expression.read_var import ReadVar |
||||
from slither.visitors.expression.write_var import WriteVar |
||||
|
||||
|
||||
class NodeVyper: |
||||
def __init__(self, node: Node) -> None: |
||||
self._unparsed_expression: Optional[Dict] = None |
||||
self._node = node |
||||
|
||||
@property |
||||
def underlying_node(self) -> Node: |
||||
return self._node |
||||
|
||||
def add_unparsed_expression(self, expression: Dict) -> None: |
||||
assert self._unparsed_expression is None |
||||
self._unparsed_expression = expression |
||||
|
||||
def analyze_expressions(self, caller_context) -> None: |
||||
if self._node.type == NodeType.VARIABLE and not self._node.expression: |
||||
self._node.add_expression(self._node.variable_declaration.expression) |
||||
if self._unparsed_expression: |
||||
expression = parse_expression(self._unparsed_expression, caller_context) |
||||
self._node.add_expression(expression) |
||||
self._unparsed_expression = None |
||||
|
||||
if self._node.expression: |
||||
|
||||
if self._node.type == NodeType.VARIABLE: |
||||
# Update the expression to be an assignement to the variable |
||||
_expression = AssignmentOperation( |
||||
Identifier(self._node.variable_declaration), |
||||
self._node.expression, |
||||
AssignmentOperationType.ASSIGN, |
||||
self._node.variable_declaration.type, |
||||
) |
||||
_expression.set_offset( |
||||
self._node.expression.source_mapping, self._node.compilation_unit |
||||
) |
||||
self._node.add_expression(_expression, bypass_verif_empty=True) |
||||
|
||||
expression = self._node.expression |
||||
read_var = ReadVar(expression) |
||||
self._node.variables_read_as_expression = read_var.result() |
||||
|
||||
write_var = WriteVar(expression) |
||||
self._node.variables_written_as_expression = write_var.result() |
||||
|
||||
find_call = FindCalls(expression) |
||||
self._node.calls_as_expression = find_call.result() |
||||
self._node.external_calls_as_expressions = [ |
||||
c for c in self._node.calls_as_expression if not isinstance(c.called, Identifier) |
||||
] |
||||
self._node.internal_calls_as_expressions = [ |
||||
c for c in self._node.calls_as_expression if isinstance(c.called, Identifier) |
||||
] |
@ -0,0 +1,524 @@ |
||||
from pathlib import Path |
||||
from typing import List, TYPE_CHECKING |
||||
from slither.vyper_parsing.ast.types import ( |
||||
Module, |
||||
FunctionDef, |
||||
EventDef, |
||||
EnumDef, |
||||
StructDef, |
||||
VariableDecl, |
||||
ImportFrom, |
||||
InterfaceDef, |
||||
AnnAssign, |
||||
Expr, |
||||
Name, |
||||
Arguments, |
||||
Index, |
||||
Subscript, |
||||
Int, |
||||
Arg, |
||||
) |
||||
|
||||
from slither.vyper_parsing.declarations.event import EventVyper |
||||
from slither.vyper_parsing.declarations.struct import StructVyper |
||||
from slither.vyper_parsing.variables.state_variable import StateVariableVyper |
||||
from slither.vyper_parsing.declarations.function import FunctionVyper |
||||
from slither.core.declarations.function_contract import FunctionContract |
||||
from slither.core.declarations import Contract, StructureContract, EnumContract, Event |
||||
|
||||
from slither.core.variables.state_variable import StateVariable |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.vyper_parsing.vyper_compilation_unit import VyperCompilationUnit |
||||
|
||||
|
||||
class ContractVyper: # pylint: disable=too-many-instance-attributes |
||||
def __init__( |
||||
self, slither_parser: "VyperCompilationUnit", contract: Contract, module: Module |
||||
) -> None: |
||||
|
||||
self._contract: Contract = contract |
||||
self._slither_parser: "VyperCompilationUnit" = slither_parser |
||||
self._data = module |
||||
# Vyper models only have one contract (aside from interfaces) and the name is the file path |
||||
# We use the stem to make it a more user friendly name that is easy to query via canonical name |
||||
self._contract.name = Path(module.name).stem |
||||
self._contract.id = module.node_id |
||||
self._is_analyzed: bool = False |
||||
|
||||
self._enumsNotParsed: List[EnumDef] = [] |
||||
self._structuresNotParsed: List[StructDef] = [] |
||||
self._variablesNotParsed: List[VariableDecl] = [] |
||||
self._eventsNotParsed: List[EventDef] = [] |
||||
self._functionsNotParsed: List[FunctionDef] = [] |
||||
|
||||
self._structures_parser: List[StructVyper] = [] |
||||
self._variables_parser: List[StateVariableVyper] = [] |
||||
self._events_parser: List[EventVyper] = [] |
||||
self._functions_parser: List[FunctionVyper] = [] |
||||
|
||||
self._parse_contract_items() |
||||
|
||||
@property |
||||
def is_analyzed(self) -> bool: |
||||
return self._is_analyzed |
||||
|
||||
def set_is_analyzed(self, is_analyzed: bool) -> None: |
||||
self._is_analyzed = is_analyzed |
||||
|
||||
@property |
||||
def underlying_contract(self) -> Contract: |
||||
return self._contract |
||||
|
||||
def _parse_contract_items(self) -> None: |
||||
for node in self._data.body: |
||||
if isinstance(node, FunctionDef): |
||||
self._functionsNotParsed.append(node) |
||||
elif isinstance(node, EventDef): |
||||
self._eventsNotParsed.append(node) |
||||
elif isinstance(node, VariableDecl): |
||||
self._variablesNotParsed.append(node) |
||||
elif isinstance(node, EnumDef): |
||||
self._enumsNotParsed.append(node) |
||||
elif isinstance(node, StructDef): |
||||
self._structuresNotParsed.append(node) |
||||
elif isinstance(node, ImportFrom): |
||||
# TOOD aliases |
||||
# We create an `InterfaceDef` sense the compilatuion unit does not contain the actual interface |
||||
# https://github.com/vyperlang/vyper/tree/master/vyper/builtins/interfaces |
||||
if node.module == "vyper.interfaces": |
||||
interfaces = { |
||||
"ERC20Detailed": InterfaceDef( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
name="ERC20Detailed", |
||||
body=[ |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
doc_string=None, |
||||
name="name", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
args=[], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Subscript( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Name(src="-1:-1:-1", node_id=-1, id="String"), |
||||
slice=Index( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Int(src="-1:-1:-1", node_id=-1, value=1), |
||||
), |
||||
), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Name(src="-1:-1:-1", node_id=-1, id="view"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
doc_string=None, |
||||
name="symbol", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
args=[], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Subscript( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Name(src="-1:-1:-1", node_id=-1, id="String"), |
||||
slice=Index( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Int(src="-1:-1:-1", node_id=-1, value=1), |
||||
), |
||||
), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Name(src="-1:-1:-1", node_id=-1, id="view"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
doc_string=None, |
||||
name="decimals", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
args=[], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=-1, id="uint8"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=-1, |
||||
value=Name(src="-1:-1:-1", node_id=-1, id="view"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
], |
||||
), |
||||
"ERC20": InterfaceDef( |
||||
src="-1:-1:-1", |
||||
node_id=1, |
||||
name="ERC20", |
||||
body=[ |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=2, |
||||
doc_string=None, |
||||
name="totalSupply", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=3, |
||||
args=[], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=7, id="uint256"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=4, |
||||
value=Name(src="-1:-1:-1", node_id=5, id="view"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=9, |
||||
doc_string=None, |
||||
name="balanceOf", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=10, |
||||
args=[ |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=11, |
||||
arg="_owner", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=12, id="address" |
||||
), |
||||
) |
||||
], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=17, id="uint256"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=14, |
||||
value=Name(src="-1:-1:-1", node_id=15, id="view"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=19, |
||||
doc_string=None, |
||||
name="allowance", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=20, |
||||
args=[ |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=21, |
||||
arg="_owner", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=22, id="address" |
||||
), |
||||
), |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=24, |
||||
arg="_spender", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=25, id="address" |
||||
), |
||||
), |
||||
], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=30, id="uint256"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=27, |
||||
value=Name(src="-1:-1:-1", node_id=28, id="view"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=32, |
||||
doc_string=None, |
||||
name="transfer", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=33, |
||||
args=[ |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=34, |
||||
arg="_to", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=35, id="address" |
||||
), |
||||
), |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=37, |
||||
arg="_value", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=38, id="uint256" |
||||
), |
||||
), |
||||
], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=43, id="bool"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=40, |
||||
value=Name(src="-1:-1:-1", node_id=41, id="nonpayable"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=45, |
||||
doc_string=None, |
||||
name="transferFrom", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=46, |
||||
args=[ |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=47, |
||||
arg="_from", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=48, id="address" |
||||
), |
||||
), |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=50, |
||||
arg="_to", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=51, id="address" |
||||
), |
||||
), |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=53, |
||||
arg="_value", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=54, id="uint256" |
||||
), |
||||
), |
||||
], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=59, id="bool"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=56, |
||||
value=Name(src="-1:-1:-1", node_id=57, id="nonpayable"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
FunctionDef( |
||||
src="-1:-1:-1", |
||||
node_id=61, |
||||
doc_string=None, |
||||
name="approve", |
||||
args=Arguments( |
||||
src="-1:-1:-1", |
||||
node_id=62, |
||||
args=[ |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=63, |
||||
arg="_spender", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=64, id="address" |
||||
), |
||||
), |
||||
Arg( |
||||
src="-1:-1:-1", |
||||
node_id=66, |
||||
arg="_value", |
||||
annotation=Name( |
||||
src="-1:-1:-1", node_id=67, id="uint256" |
||||
), |
||||
), |
||||
], |
||||
default=None, |
||||
defaults=[], |
||||
), |
||||
returns=Name(src="-1:-1:-1", node_id=72, id="bool"), |
||||
body=[ |
||||
Expr( |
||||
src="-1:-1:-1", |
||||
node_id=69, |
||||
value=Name(src="-1:-1:-1", node_id=70, id="nonpayable"), |
||||
) |
||||
], |
||||
decorators=[], |
||||
pos=None, |
||||
), |
||||
], |
||||
), |
||||
"ERC165": [], |
||||
"ERC721": [], |
||||
"ERC4626": [], |
||||
} |
||||
self._data.body.append(interfaces[node.name]) |
||||
|
||||
elif isinstance(node, InterfaceDef): |
||||
# This needs to be done lazily as interfaces can refer to constant state variables |
||||
contract = Contract(self._contract.compilation_unit, self._contract.file_scope) |
||||
contract.set_offset(node.src, self._contract.compilation_unit) |
||||
contract.is_interface = True |
||||
|
||||
contract_parser = ContractVyper(self._slither_parser, contract, node) |
||||
self._contract.file_scope.contracts[contract.name] = contract |
||||
# pylint: disable=protected-access |
||||
self._slither_parser._underlying_contract_to_parser[contract] = contract_parser |
||||
|
||||
elif isinstance(node, AnnAssign): # implements: ERC20 |
||||
pass # TODO |
||||
else: |
||||
raise ValueError("Unknown contract node: ", node) |
||||
|
||||
def parse_enums(self) -> None: |
||||
for enum in self._enumsNotParsed: |
||||
name = enum.name |
||||
canonicalName = self._contract.name + "." + enum.name |
||||
values = [x.value.id for x in enum.body] |
||||
new_enum = EnumContract(name, canonicalName, values) |
||||
new_enum.set_contract(self._contract) |
||||
new_enum.set_offset(enum.src, self._contract.compilation_unit) |
||||
self._contract.enums_as_dict[name] = new_enum # TODO solidity using canonicalName |
||||
self._enumsNotParsed = [] |
||||
|
||||
def parse_structs(self) -> None: |
||||
for struct in self._structuresNotParsed: |
||||
st = StructureContract(self._contract.compilation_unit) |
||||
st.set_contract(self._contract) |
||||
st.set_offset(struct.src, self._contract.compilation_unit) |
||||
|
||||
st_parser = StructVyper(st, struct) |
||||
self._contract.structures_as_dict[st.name] = st |
||||
self._structures_parser.append(st_parser) |
||||
# Interfaces can refer to struct defs |
||||
self._contract.file_scope.structures[st.name] = st |
||||
|
||||
self._structuresNotParsed = [] |
||||
|
||||
def parse_state_variables(self) -> None: |
||||
for varNotParsed in self._variablesNotParsed: |
||||
var = StateVariable() |
||||
var.set_contract(self._contract) |
||||
var.set_offset(varNotParsed.src, self._contract.compilation_unit) |
||||
|
||||
var_parser = StateVariableVyper(var, varNotParsed) |
||||
self._variables_parser.append(var_parser) |
||||
|
||||
assert var.name |
||||
self._contract.variables_as_dict[var.name] = var |
||||
self._contract.add_variables_ordered([var]) |
||||
# Interfaces can refer to constants |
||||
self._contract.file_scope.variables[var.name] = var |
||||
|
||||
self._variablesNotParsed = [] |
||||
|
||||
def parse_events(self) -> None: |
||||
for event_to_parse in self._eventsNotParsed: |
||||
event = Event() |
||||
event.set_contract(self._contract) |
||||
event.set_offset(event_to_parse.src, self._contract.compilation_unit) |
||||
|
||||
event_parser = EventVyper(event, event_to_parse) |
||||
self._events_parser.append(event_parser) |
||||
self._contract.events_as_dict[event.full_name] = event |
||||
|
||||
def parse_functions(self) -> None: |
||||
|
||||
for function in self._functionsNotParsed: |
||||
func = FunctionContract(self._contract.compilation_unit) |
||||
func.set_offset(function.src, self._contract.compilation_unit) |
||||
func.set_contract(self._contract) |
||||
func.set_contract_declarer(self._contract) |
||||
|
||||
func_parser = FunctionVyper(func, function, self) |
||||
self._contract.add_function(func) |
||||
self._contract.compilation_unit.add_function(func) |
||||
self._functions_parser.append(func_parser) |
||||
|
||||
self._functionsNotParsed = [] |
||||
|
||||
def analyze_state_variables(self): |
||||
# Struct defs can refer to constant state variables |
||||
for var_parser in self._variables_parser: |
||||
var_parser.analyze(self._contract) |
||||
|
||||
def analyze(self) -> None: |
||||
|
||||
for struct_parser in self._structures_parser: |
||||
struct_parser.analyze(self._contract) |
||||
|
||||
for event_parser in self._events_parser: |
||||
event_parser.analyze(self._contract) |
||||
|
||||
for function in self._functions_parser: |
||||
function.analyze_params() |
||||
|
||||
for function in self._functions_parser: |
||||
function.analyze_content() |
||||
|
||||
def __hash__(self) -> int: |
||||
return self._contract.id |
@ -0,0 +1,39 @@ |
||||
""" |
||||
Event module |
||||
""" |
||||
|
||||
from slither.core.variables.event_variable import EventVariable |
||||
from slither.vyper_parsing.variables.event_variable import EventVariableVyper |
||||
from slither.core.declarations.event import Event |
||||
from slither.vyper_parsing.ast.types import AnnAssign, Pass |
||||
|
||||
|
||||
from slither.vyper_parsing.ast.types import EventDef |
||||
|
||||
|
||||
class EventVyper: # pylint: disable=too-few-public-methods |
||||
""" |
||||
Event class |
||||
""" |
||||
|
||||
def __init__(self, event: Event, event_def: EventDef) -> None: |
||||
|
||||
self._event = event |
||||
self._event.name = event_def.name |
||||
self._elemsNotParsed = event_def.body |
||||
|
||||
def analyze(self, contract) -> None: |
||||
for elem_to_parse in self._elemsNotParsed: |
||||
if not isinstance(elem_to_parse, AnnAssign): |
||||
assert isinstance(elem_to_parse, Pass) |
||||
continue |
||||
|
||||
elem = EventVariable() |
||||
|
||||
elem.set_offset(elem_to_parse.src, self._event.contract.compilation_unit) |
||||
event_parser = EventVariableVyper(elem, elem_to_parse) |
||||
event_parser.analyze(contract) |
||||
|
||||
self._event.elems.append(elem) |
||||
|
||||
self._elemsNotParsed = [] |
@ -0,0 +1,563 @@ |
||||
from typing import Dict, Union, List, TYPE_CHECKING |
||||
|
||||
from slither.core.cfg.node import NodeType, link_nodes, Node |
||||
from slither.core.cfg.scope import Scope |
||||
from slither.core.declarations.function import ( |
||||
Function, |
||||
FunctionType, |
||||
) |
||||
from slither.core.declarations.function import ModifierStatements |
||||
from slither.core.declarations.modifier import Modifier |
||||
from slither.core.source_mapping.source_mapping import Source |
||||
from slither.core.variables.local_variable import LocalVariable |
||||
from slither.vyper_parsing.cfg.node import NodeVyper |
||||
from slither.solc_parsing.exceptions import ParsingError |
||||
from slither.vyper_parsing.variables.local_variable import LocalVariableVyper |
||||
from slither.vyper_parsing.ast.types import ( |
||||
Int, |
||||
Call, |
||||
Attribute, |
||||
Name, |
||||
Tuple as TupleVyper, |
||||
ASTNode, |
||||
AnnAssign, |
||||
FunctionDef, |
||||
Return, |
||||
Assert, |
||||
Compare, |
||||
Log, |
||||
Subscript, |
||||
If, |
||||
Pass, |
||||
Assign, |
||||
AugAssign, |
||||
Raise, |
||||
Expr, |
||||
For, |
||||
Index, |
||||
Arg, |
||||
Arguments, |
||||
Continue, |
||||
Break, |
||||
) |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.core.compilation_unit import SlitherCompilationUnit |
||||
from slither.vyper_parsing.declarations.contract import ContractVyper |
||||
|
||||
|
||||
def link_underlying_nodes(node1: NodeVyper, node2: NodeVyper): |
||||
link_nodes(node1.underlying_node, node2.underlying_node) |
||||
|
||||
|
||||
class FunctionVyper: # pylint: disable=too-many-instance-attributes |
||||
def __init__( |
||||
self, |
||||
function: Function, |
||||
function_data: FunctionDef, |
||||
contract_parser: "ContractVyper", |
||||
) -> None: |
||||
|
||||
self._function = function |
||||
self._function.name = function_data.name |
||||
self._function.id = function_data.node_id |
||||
self._functionNotParsed = function_data |
||||
self._decoratorNotParsed = None |
||||
self._local_variables_parser: List[LocalVariableVyper] = [] |
||||
self._variables_renamed = [] |
||||
self._contract_parser = contract_parser |
||||
self._node_to_NodeVyper: Dict[Node, NodeVyper] = {} |
||||
|
||||
for decorator in function_data.decorators: |
||||
if isinstance(decorator, Call): |
||||
# TODO handle multiple |
||||
self._decoratorNotParsed = decorator |
||||
elif isinstance(decorator, Name): |
||||
if decorator.id in ["external", "public", "internal"]: |
||||
self._function.visibility = decorator.id |
||||
elif decorator.id == "view": |
||||
self._function.view = True |
||||
elif decorator.id == "pure": |
||||
self._function.pure = True |
||||
elif decorator.id == "payable": |
||||
self._function.payable = True |
||||
elif decorator.id == "nonpayable": |
||||
self._function.payable = False |
||||
else: |
||||
raise ValueError(f"Unknown decorator {decorator.id}") |
||||
|
||||
# Interfaces do not have decorators and are external |
||||
if self._function._visibility is None: |
||||
self._function.visibility = "external" |
||||
|
||||
self._params_was_analyzed = False |
||||
self._content_was_analyzed = False |
||||
self._counter_scope_local_variables = 0 |
||||
|
||||
if function_data.doc_string is not None: |
||||
function.has_documentation = True |
||||
|
||||
self._analyze_function_type() |
||||
|
||||
@property |
||||
def underlying_function(self) -> Function: |
||||
return self._function |
||||
|
||||
@property |
||||
def compilation_unit(self) -> "SlitherCompilationUnit": |
||||
return self._function.compilation_unit |
||||
|
||||
################################################################################### |
||||
################################################################################### |
||||
# region Variables |
||||
################################################################################### |
||||
################################################################################### |
||||
|
||||
@property |
||||
def variables_renamed( |
||||
self, |
||||
) -> Dict[int, LocalVariableVyper]: |
||||
return self._variables_renamed |
||||
|
||||
def _add_local_variable(self, local_var_parser: LocalVariableVyper) -> None: |
||||
# Ensure variables name are unique for SSA conversion |
||||
# This should not apply to actual Vyper variables currently |
||||
# but is necessary if we have nested loops where we've created artificial variables e.g. counter_var |
||||
if local_var_parser.underlying_variable.name: |
||||
known_variables = [v.name for v in self._function.variables] |
||||
while local_var_parser.underlying_variable.name in known_variables: |
||||
local_var_parser.underlying_variable.name += ( |
||||
f"_scope_{self._counter_scope_local_variables}" |
||||
) |
||||
self._counter_scope_local_variables += 1 |
||||
known_variables = [v.name for v in self._function.variables] |
||||
# TODO no reference ID |
||||
# if local_var_parser.reference_id is not None: |
||||
# self._variables_renamed[local_var_parser.reference_id] = local_var_parser |
||||
self._function.variables_as_dict[ |
||||
local_var_parser.underlying_variable.name |
||||
] = local_var_parser.underlying_variable |
||||
self._local_variables_parser.append(local_var_parser) |
||||
|
||||
# endregion |
||||
################################################################################### |
||||
################################################################################### |
||||
# region Analyses |
||||
################################################################################### |
||||
################################################################################### |
||||
|
||||
@property |
||||
def function_not_parsed(self) -> Dict: |
||||
return self._functionNotParsed |
||||
|
||||
def _analyze_function_type(self) -> None: |
||||
if self._function.name == "__init__": |
||||
self._function.function_type = FunctionType.CONSTRUCTOR |
||||
elif self._function.name == "__default__": |
||||
self._function.function_type = FunctionType.FALLBACK |
||||
else: |
||||
self._function.function_type = FunctionType.NORMAL |
||||
|
||||
def analyze_params(self) -> None: |
||||
if self._params_was_analyzed: |
||||
return |
||||
|
||||
self._params_was_analyzed = True |
||||
|
||||
params = self._functionNotParsed.args |
||||
returns = self._functionNotParsed.returns |
||||
|
||||
if params: |
||||
self._parse_params(params) |
||||
if returns: |
||||
self._parse_returns(returns) |
||||
|
||||
def analyze_content(self) -> None: |
||||
if self._content_was_analyzed: |
||||
return |
||||
|
||||
self._content_was_analyzed = True |
||||
|
||||
body = self._functionNotParsed.body |
||||
|
||||
if body and not isinstance(body[0], Pass): |
||||
self._function.is_implemented = True |
||||
self._function.is_empty = False |
||||
self._parse_cfg(body) |
||||
else: |
||||
self._function.is_implemented = False |
||||
self._function.is_empty = True |
||||
|
||||
for local_var_parser in self._local_variables_parser: |
||||
local_var_parser.analyze(self._function) |
||||
|
||||
for node_parser in self._node_to_NodeVyper.values(): |
||||
node_parser.analyze_expressions(self._function) |
||||
|
||||
self._analyze_decorator() |
||||
|
||||
def _analyze_decorator(self) -> None: |
||||
if not self._decoratorNotParsed: |
||||
return |
||||
|
||||
decorator = self._decoratorNotParsed |
||||
if decorator.args: |
||||
name = f"{decorator.func.id}({decorator.args[0].value})" |
||||
else: |
||||
name = decorator.func.id |
||||
|
||||
contract = self._contract_parser.underlying_contract |
||||
compilation_unit = self._contract_parser.underlying_contract.compilation_unit |
||||
modifier = Modifier(compilation_unit) |
||||
modifier.name = name |
||||
modifier.set_offset(decorator.src, compilation_unit) |
||||
modifier.set_contract(contract) |
||||
modifier.set_contract_declarer(contract) |
||||
latest_entry_point = self._function.entry_point |
||||
self._function.add_modifier( |
||||
ModifierStatements( |
||||
modifier=modifier, |
||||
entry_point=latest_entry_point, |
||||
nodes=[latest_entry_point], |
||||
) |
||||
) |
||||
|
||||
# endregion |
||||
################################################################################### |
||||
################################################################################### |
||||
# region Nodes |
||||
################################################################################### |
||||
################################################################################### |
||||
|
||||
def _new_node( |
||||
self, node_type: NodeType, src: Union[str, Source], scope: Union[Scope, "Function"] |
||||
) -> NodeVyper: |
||||
node = self._function.new_node(node_type, src, scope) |
||||
node_parser = NodeVyper(node) |
||||
self._node_to_NodeVyper[node] = node_parser |
||||
return node_parser |
||||
|
||||
# endregion |
||||
################################################################################### |
||||
################################################################################### |
||||
# region Parsing function |
||||
################################################################################### |
||||
################################################################################### |
||||
|
||||
# pylint: disable=too-many-branches,too-many-statements,protected-access,too-many-locals |
||||
def _parse_cfg(self, cfg: List[ASTNode]) -> None: |
||||
|
||||
entry_node = self._new_node(NodeType.ENTRYPOINT, "-1:-1:-1", self.underlying_function) |
||||
self._function.entry_point = entry_node.underlying_node |
||||
scope = Scope(True, False, self.underlying_function) |
||||
|
||||
def parse_statement( |
||||
curr_node: NodeVyper, |
||||
expr: ASTNode, |
||||
continue_destination=None, |
||||
break_destination=None, |
||||
) -> NodeVyper: |
||||
if isinstance(expr, AnnAssign): |
||||
local_var = LocalVariable() |
||||
local_var.set_function(self._function) |
||||
local_var.set_offset(expr.src, self._function.compilation_unit) |
||||
|
||||
local_var_parser = LocalVariableVyper(local_var, expr) |
||||
self._add_local_variable(local_var_parser) |
||||
|
||||
new_node = self._new_node(NodeType.VARIABLE, expr.src, scope) |
||||
if expr.value is not None: |
||||
local_var.initialized = True |
||||
new_node.add_unparsed_expression(expr.value) |
||||
new_node.underlying_node.add_variable_declaration(local_var) |
||||
link_underlying_nodes(curr_node, new_node) |
||||
|
||||
curr_node = new_node |
||||
|
||||
elif isinstance(expr, (AugAssign, Assign)): |
||||
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) |
||||
new_node.add_unparsed_expression(expr) |
||||
link_underlying_nodes(curr_node, new_node) |
||||
|
||||
curr_node = new_node |
||||
|
||||
elif isinstance(expr, Expr): |
||||
# TODO This is a workaround to handle Vyper putting payable/view in the function body... https://github.com/vyperlang/vyper/issues/3578 |
||||
if not isinstance(expr.value, Name): |
||||
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) |
||||
new_node.add_unparsed_expression(expr.value) |
||||
link_underlying_nodes(curr_node, new_node) |
||||
|
||||
curr_node = new_node |
||||
|
||||
elif isinstance(expr, For): |
||||
|
||||
node_startLoop = self._new_node(NodeType.STARTLOOP, expr.src, scope) |
||||
node_endLoop = self._new_node(NodeType.ENDLOOP, expr.src, scope) |
||||
|
||||
link_underlying_nodes(curr_node, node_startLoop) |
||||
|
||||
local_var = LocalVariable() |
||||
local_var.set_function(self._function) |
||||
local_var.set_offset(expr.src, self._function.compilation_unit) |
||||
|
||||
counter_var = AnnAssign( |
||||
expr.target.src, |
||||
expr.target.node_id, |
||||
target=Name("-1:-1:-1", -1, "counter_var"), |
||||
annotation=Name("-1:-1:-1", -1, "uint256"), |
||||
value=Int("-1:-1:-1", -1, 0), |
||||
) |
||||
local_var_parser = LocalVariableVyper(local_var, counter_var) |
||||
self._add_local_variable(local_var_parser) |
||||
counter_node = self._new_node(NodeType.VARIABLE, expr.src, scope) |
||||
local_var.initialized = True |
||||
counter_node.add_unparsed_expression(counter_var.value) |
||||
counter_node.underlying_node.add_variable_declaration(local_var) |
||||
|
||||
link_underlying_nodes(node_startLoop, counter_node) |
||||
|
||||
node_condition = None |
||||
if isinstance(expr.iter, (Attribute, Name)): |
||||
# HACK |
||||
# The loop variable is not annotated so we infer its type by looking at the type of the iterator |
||||
if isinstance(expr.iter, Attribute): # state variable |
||||
iter_expr = expr.iter |
||||
loop_iterator = list( |
||||
filter( |
||||
lambda x: x._variable.name == iter_expr.attr, |
||||
self._contract_parser._variables_parser, |
||||
) |
||||
)[0] |
||||
|
||||
else: # local variable |
||||
iter_expr = expr.iter |
||||
loop_iterator = list( |
||||
filter( |
||||
lambda x: x._variable.name == iter_expr.id, |
||||
self._local_variables_parser, |
||||
) |
||||
)[0] |
||||
|
||||
# TODO use expr.src instead of -1:-1:1? |
||||
cond_expr = Compare( |
||||
"-1:-1:-1", |
||||
-1, |
||||
left=Name("-1:-1:-1", -1, "counter_var"), |
||||
op="<=", |
||||
right=Call( |
||||
"-1:-1:-1", |
||||
-1, |
||||
func=Name("-1:-1:-1", -1, "len"), |
||||
args=[iter_expr], |
||||
keywords=[], |
||||
keyword=None, |
||||
), |
||||
) |
||||
node_condition = self._new_node(NodeType.IFLOOP, expr.src, scope) |
||||
node_condition.add_unparsed_expression(cond_expr) |
||||
|
||||
if loop_iterator._elem_to_parse.value.id == "DynArray": |
||||
loop_var_annotation = loop_iterator._elem_to_parse.slice.value.elements[0] |
||||
else: |
||||
loop_var_annotation = loop_iterator._elem_to_parse.value |
||||
|
||||
value = Subscript( |
||||
"-1:-1:-1", |
||||
-1, |
||||
value=Name("-1:-1:-1", -1, loop_iterator._variable.name), |
||||
slice=Index("-1:-1:-1", -1, value=Name("-1:-1:-1", -1, "counter_var")), |
||||
) |
||||
loop_var = AnnAssign( |
||||
expr.target.src, |
||||
expr.target.node_id, |
||||
target=expr.target, |
||||
annotation=loop_var_annotation, |
||||
value=value, |
||||
) |
||||
|
||||
elif isinstance(expr.iter, Call): # range |
||||
range_val = expr.iter.args[0] |
||||
cond_expr = Compare( |
||||
"-1:-1:-1", |
||||
-1, |
||||
left=Name("-1:-1:-1", -1, "counter_var"), |
||||
op="<=", |
||||
right=range_val, |
||||
) |
||||
node_condition = self._new_node(NodeType.IFLOOP, expr.src, scope) |
||||
node_condition.add_unparsed_expression(cond_expr) |
||||
loop_var = AnnAssign( |
||||
expr.target.src, |
||||
expr.target.node_id, |
||||
target=expr.target, |
||||
annotation=Name("-1:-1:-1", -1, "uint256"), |
||||
value=Name("-1:-1:-1", -1, "counter_var"), |
||||
) |
||||
|
||||
else: |
||||
raise NotImplementedError |
||||
|
||||
# After creating condition node, we link it declaration of the loop variable |
||||
link_underlying_nodes(counter_node, node_condition) |
||||
|
||||
# Create an expression for the loop increment (counter_var += 1) |
||||
loop_increment = AugAssign( |
||||
"-1:-1:-1", |
||||
-1, |
||||
target=Name("-1:-1:-1", -1, "counter_var"), |
||||
op="+=", |
||||
value=Int("-1:-1:-1", -1, 1), |
||||
) |
||||
node_increment = self._new_node(NodeType.EXPRESSION, expr.src, scope) |
||||
node_increment.add_unparsed_expression(loop_increment) |
||||
link_underlying_nodes(node_increment, node_condition) |
||||
|
||||
continue_destination = node_increment |
||||
break_destination = node_endLoop |
||||
|
||||
# We assign the index variable or range variable in the loop body on each iteration |
||||
expr.body.insert(0, loop_var) |
||||
body_node = None |
||||
new_node = node_condition |
||||
for stmt in expr.body: |
||||
body_node = parse_statement( |
||||
new_node, stmt, continue_destination, break_destination |
||||
) |
||||
new_node = body_node |
||||
|
||||
if body_node is not None: |
||||
link_underlying_nodes(body_node, node_increment) |
||||
|
||||
link_underlying_nodes(node_condition, node_endLoop) |
||||
|
||||
curr_node = node_endLoop |
||||
|
||||
elif isinstance(expr, Continue): |
||||
new_node = self._new_node(NodeType.CONTINUE, expr.src, scope) |
||||
link_underlying_nodes(curr_node, new_node) |
||||
link_underlying_nodes(new_node, continue_destination) |
||||
|
||||
elif isinstance(expr, Break): |
||||
new_node = self._new_node(NodeType.BREAK, expr.src, scope) |
||||
link_underlying_nodes(curr_node, new_node) |
||||
link_underlying_nodes(new_node, break_destination) |
||||
|
||||
elif isinstance(expr, Return): |
||||
new_node = self._new_node(NodeType.RETURN, expr.src, scope) |
||||
if expr.value is not None: |
||||
new_node.add_unparsed_expression(expr.value) |
||||
|
||||
link_underlying_nodes(curr_node, new_node) |
||||
curr_node = new_node |
||||
|
||||
elif isinstance(expr, Assert): |
||||
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) |
||||
new_node.add_unparsed_expression(expr) |
||||
|
||||
link_underlying_nodes(curr_node, new_node) |
||||
curr_node = new_node |
||||
|
||||
elif isinstance(expr, Log): |
||||
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) |
||||
new_node.add_unparsed_expression(expr.value) |
||||
|
||||
link_underlying_nodes(curr_node, new_node) |
||||
curr_node = new_node |
||||
|
||||
elif isinstance(expr, If): |
||||
condition_node = self._new_node(NodeType.IF, expr.test.src, scope) |
||||
condition_node.add_unparsed_expression(expr.test) |
||||
|
||||
endIf_node = self._new_node(NodeType.ENDIF, expr.src, scope) |
||||
|
||||
true_node = None |
||||
new_node = condition_node |
||||
for stmt in expr.body: |
||||
true_node = parse_statement( |
||||
new_node, stmt, continue_destination, break_destination |
||||
) |
||||
new_node = true_node |
||||
|
||||
link_underlying_nodes(true_node, endIf_node) |
||||
|
||||
false_node = None |
||||
new_node = condition_node |
||||
for stmt in expr.orelse: |
||||
false_node = parse_statement( |
||||
new_node, stmt, continue_destination, break_destination |
||||
) |
||||
new_node = false_node |
||||
|
||||
if false_node is not None: |
||||
link_underlying_nodes(false_node, endIf_node) |
||||
|
||||
else: |
||||
link_underlying_nodes(condition_node, endIf_node) |
||||
|
||||
link_underlying_nodes(curr_node, condition_node) |
||||
curr_node = endIf_node |
||||
|
||||
elif isinstance(expr, Pass): |
||||
pass |
||||
elif isinstance(expr, Raise): |
||||
new_node = self._new_node(NodeType.EXPRESSION, expr.src, scope) |
||||
new_node.add_unparsed_expression(expr) |
||||
link_underlying_nodes(curr_node, new_node) |
||||
curr_node = new_node |
||||
|
||||
else: |
||||
raise ParsingError(f"Statement not parsed {expr.__class__.__name__} {expr}") |
||||
|
||||
return curr_node |
||||
|
||||
curr_node = entry_node |
||||
for expr in cfg: |
||||
curr_node = parse_statement(curr_node, expr) |
||||
|
||||
# endregion |
||||
################################################################################### |
||||
################################################################################### |
||||
|
||||
def _add_param(self, param: Arg, initialized: bool = False) -> LocalVariableVyper: |
||||
|
||||
local_var = LocalVariable() |
||||
local_var.set_function(self._function) |
||||
local_var.set_offset(param.src, self._function.compilation_unit) |
||||
local_var_parser = LocalVariableVyper(local_var, param) |
||||
|
||||
if initialized: |
||||
local_var.initialized = True |
||||
|
||||
if local_var.location == "default": |
||||
local_var.set_location("memory") |
||||
|
||||
self._add_local_variable(local_var_parser) |
||||
return local_var_parser |
||||
|
||||
def _parse_params(self, params: Arguments): |
||||
|
||||
self._function.parameters_src().set_offset(params.src, self._function.compilation_unit) |
||||
if params.defaults: |
||||
self._function._default_args_as_expressions = params.defaults |
||||
for param in params.args: |
||||
local_var = self._add_param(param) |
||||
self._function.add_parameters(local_var.underlying_variable) |
||||
|
||||
def _parse_returns(self, returns: Union[Name, TupleVyper, Subscript]): |
||||
|
||||
self._function.returns_src().set_offset(returns.src, self._function.compilation_unit) |
||||
# Only the type of the arg is given, not a name. We create an an `Arg` with an empty name |
||||
# so that the function has the correct return type in its signature but doesn't clash with |
||||
# other identifiers during name resolution (`find_variable`). |
||||
if isinstance(returns, (Name, Subscript)): |
||||
local_var = self._add_param(Arg(returns.src, returns.node_id, "", annotation=returns)) |
||||
self._function.add_return(local_var.underlying_variable) |
||||
else: |
||||
assert isinstance(returns, TupleVyper) |
||||
for ret in returns.elements: |
||||
local_var = self._add_param(Arg(ret.src, ret.node_id, "", annotation=ret)) |
||||
self._function.add_return(local_var.underlying_variable) |
||||
|
||||
################################################################################### |
||||
################################################################################### |
@ -0,0 +1,33 @@ |
||||
from typing import List |
||||
|
||||
from slither.core.declarations.structure import Structure |
||||
from slither.core.variables.structure_variable import StructureVariable |
||||
from slither.vyper_parsing.variables.structure_variable import StructureVariableVyper |
||||
from slither.vyper_parsing.ast.types import StructDef, AnnAssign |
||||
|
||||
|
||||
class StructVyper: # pylint: disable=too-few-public-methods |
||||
def __init__( |
||||
self, |
||||
st: Structure, |
||||
struct: StructDef, |
||||
) -> None: |
||||
|
||||
self._structure = st |
||||
st.name = struct.name |
||||
st.canonical_name = struct.name + self._structure.contract.name |
||||
|
||||
self._elemsNotParsed: List[AnnAssign] = struct.body |
||||
|
||||
def analyze(self, contract) -> None: |
||||
for elem_to_parse in self._elemsNotParsed: |
||||
elem = StructureVariable() |
||||
elem.set_structure(self._structure) |
||||
elem.set_offset(elem_to_parse.src, self._structure.contract.compilation_unit) |
||||
|
||||
elem_parser = StructureVariableVyper(elem, elem_to_parse) |
||||
elem_parser.analyze(contract) |
||||
|
||||
self._structure.elems[elem.name] = elem |
||||
self._structure.add_elem_in_order(elem.name) |
||||
self._elemsNotParsed = [] |
@ -0,0 +1,464 @@ |
||||
from typing import Optional, List, Union, TYPE_CHECKING |
||||
from collections import deque |
||||
from slither.core.declarations.solidity_variables import ( |
||||
SOLIDITY_VARIABLES_COMPOSED, |
||||
SolidityVariableComposed, |
||||
) |
||||
from slither.core.declarations import SolidityFunction, FunctionContract |
||||
from slither.core.variables.state_variable import StateVariable |
||||
from slither.core.expressions import ( |
||||
CallExpression, |
||||
ElementaryTypeNameExpression, |
||||
Identifier, |
||||
IndexAccess, |
||||
Literal, |
||||
MemberAccess, |
||||
SelfIdentifier, |
||||
TupleExpression, |
||||
TypeConversion, |
||||
UnaryOperation, |
||||
UnaryOperationType, |
||||
) |
||||
from slither.core.expressions.assignment_operation import ( |
||||
AssignmentOperation, |
||||
AssignmentOperationType, |
||||
) |
||||
from slither.core.expressions.binary_operation import ( |
||||
BinaryOperation, |
||||
BinaryOperationType, |
||||
) |
||||
from slither.core.solidity_types import ( |
||||
ArrayType, |
||||
ElementaryType, |
||||
UserDefinedType, |
||||
) |
||||
from slither.core.declarations.contract import Contract |
||||
from slither.vyper_parsing.expressions.find_variable import find_variable |
||||
from slither.vyper_parsing.type_parsing import parse_type |
||||
from slither.all_exceptions import ParsingError |
||||
from slither.vyper_parsing.ast.types import ( |
||||
Int, |
||||
Call, |
||||
Attribute, |
||||
Name, |
||||
Tuple, |
||||
Hex, |
||||
BinOp, |
||||
Str, |
||||
Assert, |
||||
Compare, |
||||
UnaryOp, |
||||
Subscript, |
||||
NameConstant, |
||||
VyDict, |
||||
Bytes, |
||||
BoolOp, |
||||
Assign, |
||||
AugAssign, |
||||
VyList, |
||||
Raise, |
||||
ASTNode, |
||||
) |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.core.expressions.expression import Expression |
||||
|
||||
|
||||
def vars_to_typestr(rets: Optional[List["Expression"]]) -> str: |
||||
if rets is None: |
||||
return "tuple()" |
||||
if len(rets) == 1: |
||||
return str(rets[0].type) |
||||
return f"tuple({','.join(str(ret.type) for ret in rets)})" |
||||
|
||||
|
||||
# pylint: disable=too-many-branches,too-many-statements,too-many-locals |
||||
def parse_expression( |
||||
expression: ASTNode, caller_context: Union[FunctionContract, Contract] |
||||
) -> "Expression": |
||||
|
||||
if isinstance(expression, Int): |
||||
literal = Literal(str(expression.value), ElementaryType("uint256")) |
||||
literal.set_offset(expression.src, caller_context.compilation_unit) |
||||
return literal |
||||
|
||||
if isinstance(expression, Hex): |
||||
# TODO this is an implicit conversion and could potentially be bytes20 or other? https://github.com/vyperlang/vyper/issues/3580 |
||||
literal = Literal(str(expression.value), ElementaryType("address")) |
||||
literal.set_offset(expression.src, caller_context.compilation_unit) |
||||
return literal |
||||
|
||||
if isinstance(expression, Str): |
||||
literal = Literal(str(expression.value), ElementaryType("string")) |
||||
literal.set_offset(expression.src, caller_context.compilation_unit) |
||||
return literal |
||||
|
||||
if isinstance(expression, Bytes): |
||||
literal = Literal(str(expression.value), ElementaryType("bytes")) |
||||
literal.set_offset(expression.src, caller_context.compilation_unit) |
||||
return literal |
||||
|
||||
if isinstance(expression, NameConstant): |
||||
assert str(expression.value) in ["True", "False"] |
||||
literal = Literal(str(expression.value), ElementaryType("bool")) |
||||
literal.set_offset(expression.src, caller_context.compilation_unit) |
||||
return literal |
||||
|
||||
if isinstance(expression, Call): |
||||
called = parse_expression(expression.func, caller_context) |
||||
if isinstance(called, Identifier) and isinstance(called.value, SolidityFunction): |
||||
if called.value.name == "empty()": |
||||
type_to = parse_type(expression.args[0], caller_context) |
||||
# TODO figure out how to represent this type argument |
||||
parsed_expr = CallExpression(called, [], str(type_to)) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if called.value.name == "convert()": |
||||
arg = parse_expression(expression.args[0], caller_context) |
||||
type_to = parse_type(expression.args[1], caller_context) |
||||
parsed_expr = TypeConversion(arg, type_to) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if called.value.name == "min_value()": |
||||
type_to = parse_type(expression.args[0], caller_context) |
||||
member_type = str(type_to) |
||||
# TODO return Literal |
||||
parsed_expr = MemberAccess( |
||||
"min", |
||||
member_type, |
||||
CallExpression( |
||||
Identifier(SolidityFunction("type()")), |
||||
[ElementaryTypeNameExpression(type_to)], |
||||
member_type, |
||||
), |
||||
) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if called.value.name == "max_value()": |
||||
type_to = parse_type(expression.args[0], caller_context) |
||||
member_type = str(type_to) |
||||
# TODO return Literal |
||||
parsed_expr = MemberAccess( |
||||
"max", |
||||
member_type, |
||||
CallExpression( |
||||
Identifier(SolidityFunction("type()")), |
||||
[ElementaryTypeNameExpression(type_to)], |
||||
member_type, |
||||
), |
||||
) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if called.value.name == "raw_call()": |
||||
args = [parse_expression(a, caller_context) for a in expression.args] |
||||
# This is treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted |
||||
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity... |
||||
call = CallExpression( |
||||
MemberAccess("raw_call", "tuple(bool,bytes32)", args[0]), |
||||
args[1:], |
||||
"tuple(bool,bytes32)", |
||||
) |
||||
call.set_offset(expression.src, caller_context.compilation_unit) |
||||
call.call_value = next( |
||||
iter( |
||||
parse_expression(x.value, caller_context) |
||||
for x in expression.keywords |
||||
if x.arg == "value" |
||||
), |
||||
None, |
||||
) |
||||
call.call_gas = next( |
||||
iter( |
||||
parse_expression(x.value, caller_context) |
||||
for x in expression.keywords |
||||
if x.arg == "gas" |
||||
), |
||||
None, |
||||
) |
||||
# TODO handle `max_outsize` keyword |
||||
|
||||
return call |
||||
|
||||
if expression.args and isinstance(expression.args[0], VyDict): |
||||
arguments = [] |
||||
for val in expression.args[0].values: |
||||
arguments.append(parse_expression(val, caller_context)) |
||||
else: |
||||
arguments = [parse_expression(a, caller_context) for a in expression.args] |
||||
|
||||
rets = None |
||||
# Since the AST lacks the type of the return values, we recover it. https://github.com/vyperlang/vyper/issues/3581 |
||||
if isinstance(called, Identifier): |
||||
if isinstance(called.value, FunctionContract): |
||||
rets = called.value.returns |
||||
# Default arguments are not represented in the AST, so we recover them as well. |
||||
# pylint: disable=protected-access |
||||
if called.value._default_args_as_expressions and len(arguments) < len( |
||||
called.value.parameters |
||||
): |
||||
arguments.extend( |
||||
[ |
||||
parse_expression(x, caller_context) |
||||
for x in called.value._default_args_as_expressions |
||||
] |
||||
) |
||||
|
||||
elif isinstance(called.value, SolidityFunction): |
||||
rets = called.value.return_type |
||||
|
||||
elif isinstance(called.value, Contract): |
||||
# Type conversions are not explicitly represented in the AST e.g. converting address to contract/ interface, |
||||
# so we infer that a type conversion is occurring if `called` is a `Contract` type. https://github.com/vyperlang/vyper/issues/3580 |
||||
type_to = parse_type(expression.func, caller_context) |
||||
parsed_expr = TypeConversion(arguments[0], type_to) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
elif isinstance(called, MemberAccess) and called.type is not None: |
||||
# (recover_type_2) Propagate the type collected to the `CallExpression` |
||||
# see recover_type_1 |
||||
rets = [called] |
||||
|
||||
type_str = vars_to_typestr(rets) |
||||
parsed_expr = CallExpression(called, arguments, type_str) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, Attribute): |
||||
member_name = expression.attr |
||||
if isinstance(expression.value, Name): |
||||
# TODO this is ambiguous because it could be a state variable or a call to balance https://github.com/vyperlang/vyper/issues/3582 |
||||
if expression.value.id == "self" and member_name != "balance": |
||||
var = find_variable(member_name, caller_context, is_self=True) |
||||
parsed_expr = SelfIdentifier(var) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
var.references.append(parsed_expr.source_mapping) |
||||
return parsed_expr |
||||
|
||||
expr = parse_expression(expression.value, caller_context) |
||||
# TODO this is ambiguous because it could be a type conversion of an interface or a member access |
||||
# see https://github.com/vyperlang/vyper/issues/3580 and ttps://github.com/vyperlang/vyper/issues/3582 |
||||
if expression.attr == "address": |
||||
parsed_expr = TypeConversion(expr, ElementaryType("address")) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
member_access = MemberAccess(member_name, None, expr) |
||||
|
||||
if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: |
||||
parsed_expr = Identifier(SolidityVariableComposed(str(member_access))) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
else: |
||||
expr = parse_expression(expression.value, caller_context) |
||||
member_name_ret_type = None |
||||
# (recover_type_1) This may be a call to an interface and we don't have the return types, |
||||
# so we see if there's a function identifier with `member_name` and propagate the type to |
||||
# its enclosing `CallExpression`. https://github.com/vyperlang/vyper/issues/3581 |
||||
if ( |
||||
isinstance(expr, Identifier) |
||||
and isinstance(expr.value, StateVariable) |
||||
and isinstance(expr.value.type, UserDefinedType) |
||||
and isinstance(expr.value.type.type, Contract) |
||||
): |
||||
# If we access a member of an interface, needs to be interface instead of self namespace |
||||
var = find_variable(member_name, expr.value.type.type) |
||||
if isinstance(var, FunctionContract): |
||||
rets = var.returns |
||||
member_name_ret_type = vars_to_typestr(rets) |
||||
|
||||
if ( |
||||
isinstance(expr, TypeConversion) |
||||
and isinstance(expr.type, UserDefinedType) |
||||
and isinstance(expr.type.type, Contract) |
||||
): |
||||
# If we access a member of an interface, needs to be interface instead of self namespace |
||||
var = find_variable(member_name, expr.type.type) |
||||
if isinstance(var, FunctionContract): |
||||
rets = var.returns |
||||
member_name_ret_type = vars_to_typestr(rets) |
||||
|
||||
member_access = MemberAccess(member_name, member_name_ret_type, expr) |
||||
|
||||
member_access.set_offset(expression.src, caller_context.compilation_unit) |
||||
return member_access |
||||
|
||||
if isinstance(expression, Name): |
||||
var = find_variable(expression.id, caller_context) |
||||
parsed_expr = Identifier(var) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, Assign): |
||||
lhs = parse_expression(expression.target, caller_context) |
||||
rhs = parse_expression(expression.value, caller_context) |
||||
parsed_expr = AssignmentOperation(lhs, rhs, AssignmentOperationType.ASSIGN, None) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, AugAssign): |
||||
lhs = parse_expression(expression.target, caller_context) |
||||
rhs = parse_expression(expression.value, caller_context) |
||||
|
||||
op = AssignmentOperationType.get_type(expression.op) |
||||
parsed_expr = AssignmentOperation(lhs, rhs, op, None) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, (Tuple, VyList)): |
||||
tuple_vars = [parse_expression(x, caller_context) for x in expression.elements] |
||||
parsed_expr = TupleExpression(tuple_vars) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, UnaryOp): |
||||
operand = parse_expression(expression.operand, caller_context) |
||||
op = UnaryOperationType.get_type( |
||||
expression.op, isprefix=True |
||||
) # TODO does vyper have postfix? |
||||
|
||||
parsed_expr = UnaryOperation(operand, op) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, Compare): |
||||
lhs = parse_expression(expression.left, caller_context) |
||||
|
||||
# We assume left operand in membership comparison cannot be Array type |
||||
if expression.op in ["In", "NotIn"]: |
||||
# If we see a membership operator e.g. x in [foo(), bar()], we convert it to logical operations |
||||
# like (x == foo() || x == bar()) or (x != foo() && x != bar()) for "not in" |
||||
# TODO consider rewriting as if-else to accurately represent the precedence of potential side-effects |
||||
|
||||
conditions = deque() |
||||
rhs = parse_expression(expression.right, caller_context) |
||||
is_tuple = isinstance(rhs, TupleExpression) |
||||
is_array = isinstance(rhs, Identifier) and isinstance(rhs.value.type, ArrayType) |
||||
if is_array: |
||||
assert ( |
||||
rhs.value.type.is_fixed_array |
||||
), "Dynamic arrays are not supported in comparison operators" |
||||
if is_tuple or is_array: |
||||
length = len(rhs.expressions) if is_tuple else rhs.value.type.length_value.value |
||||
inner_op = ( |
||||
BinaryOperationType.get_type("!=") |
||||
if expression.op == "NotIn" |
||||
else BinaryOperationType.get_type("==") |
||||
) |
||||
for i in range(length): |
||||
elem_expr = ( |
||||
rhs.expressions[i] |
||||
if is_tuple |
||||
else IndexAccess(rhs, Literal(str(i), ElementaryType("uint256"))) |
||||
) |
||||
elem_expr.set_offset(rhs.source_mapping, caller_context.compilation_unit) |
||||
parsed_expr = BinaryOperation(lhs, elem_expr, inner_op) |
||||
parsed_expr.set_offset(lhs.source_mapping, caller_context.compilation_unit) |
||||
conditions.append(parsed_expr) |
||||
|
||||
outer_op = ( |
||||
BinaryOperationType.get_type("&&") |
||||
if expression.op == "NotIn" |
||||
else BinaryOperationType.get_type("||") |
||||
) |
||||
while len(conditions) > 1: |
||||
lhs = conditions.pop() |
||||
rhs = conditions.pop() |
||||
|
||||
conditions.appendleft(BinaryOperation(lhs, rhs, outer_op)) |
||||
|
||||
return conditions.pop() |
||||
|
||||
# enum type membership check https://docs.vyperlang.org/en/stable/types.html?h#id18 |
||||
is_member_op = ( |
||||
BinaryOperationType.get_type("==") |
||||
if expression.op == "NotIn" |
||||
else BinaryOperationType.get_type("!=") |
||||
) |
||||
# If all bits are cleared, then the lhs is not a member of the enum |
||||
# This allows representing membership in multiple enum members |
||||
# For example, if enum Foo has members A (1), B (2), and C (4), then |
||||
# (x in [Foo.A, Foo.B]) is equivalent to (x & (Foo.A | Foo.B) != 0), |
||||
# where (Foo.A | Foo.B) evaluates to 3. |
||||
# Thus, when x is 3, (x & (Foo.A | Foo.B) != 0) is true. |
||||
enum_bit_mask = BinaryOperation( |
||||
TypeConversion(lhs, ElementaryType("uint256")), |
||||
TypeConversion(rhs, ElementaryType("uint256")), |
||||
BinaryOperationType.get_type("&"), |
||||
) |
||||
membership_check = BinaryOperation( |
||||
enum_bit_mask, Literal("0", ElementaryType("uint256")), is_member_op |
||||
) |
||||
membership_check.set_offset(lhs.source_mapping, caller_context.compilation_unit) |
||||
return membership_check |
||||
|
||||
# a regular logical operator |
||||
rhs = parse_expression(expression.right, caller_context) |
||||
op = BinaryOperationType.get_type(expression.op) |
||||
|
||||
parsed_expr = BinaryOperation(lhs, rhs, op) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, BinOp): |
||||
lhs = parse_expression(expression.left, caller_context) |
||||
rhs = parse_expression(expression.right, caller_context) |
||||
|
||||
op = BinaryOperationType.get_type(expression.op) |
||||
parsed_expr = BinaryOperation(lhs, rhs, op) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, Assert): |
||||
# Treat assert the same as a Solidity `require`. |
||||
# TODO rename from `SolidityFunction` to `Builtin`? |
||||
type_str = "tuple()" |
||||
if expression.msg is None: |
||||
func = SolidityFunction("require(bool)") |
||||
args = [parse_expression(expression.test, caller_context)] |
||||
else: |
||||
func = SolidityFunction("require(bool,string)") |
||||
args = [ |
||||
parse_expression(expression.test, caller_context), |
||||
parse_expression(expression.msg, caller_context), |
||||
] |
||||
|
||||
parsed_expr = CallExpression(Identifier(func), args, type_str) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, Subscript): |
||||
left_expression = parse_expression(expression.value, caller_context) |
||||
right_expression = parse_expression(expression.slice.value, caller_context) |
||||
parsed_expr = IndexAccess(left_expression, right_expression) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, BoolOp): |
||||
lhs = parse_expression(expression.values[0], caller_context) |
||||
rhs = parse_expression(expression.values[1], caller_context) |
||||
|
||||
op = BinaryOperationType.get_type(expression.op) |
||||
parsed_expr = BinaryOperation(lhs, rhs, op) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
if isinstance(expression, Raise): |
||||
type_str = "tuple()" |
||||
func = ( |
||||
SolidityFunction("revert()") |
||||
if expression.exc is None |
||||
else SolidityFunction("revert(string)") |
||||
) |
||||
args = [] if expression.exc is None else [parse_expression(expression.exc, caller_context)] |
||||
|
||||
parsed_expr = CallExpression(Identifier(func), args, type_str) |
||||
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) |
||||
return parsed_expr |
||||
|
||||
raise ParsingError(f"Expression not parsed {expression}") |
@ -0,0 +1,150 @@ |
||||
from typing import TYPE_CHECKING, Optional, Union, Tuple |
||||
|
||||
from slither.core.declarations import Event, Enum, Structure |
||||
from slither.core.declarations.contract import Contract |
||||
from slither.core.declarations.custom_error import CustomError |
||||
from slither.core.declarations.function import Function |
||||
from slither.core.declarations.function_contract import FunctionContract |
||||
from slither.core.declarations.solidity_variables import ( |
||||
SOLIDITY_FUNCTIONS, |
||||
SOLIDITY_VARIABLES, |
||||
SolidityFunction, |
||||
SolidityVariable, |
||||
) |
||||
from slither.core.variables.variable import Variable |
||||
from slither.solc_parsing.exceptions import VariableNotFound |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.vyper_parsing.declarations.function import FunctionVyper |
||||
|
||||
|
||||
def _find_variable_in_function_parser( |
||||
var_name: str, |
||||
function_parser: Optional["FunctionVyper"], |
||||
) -> Optional[Variable]: |
||||
if function_parser is None: |
||||
return None |
||||
func_variables = function_parser.variables_as_dict |
||||
if var_name in func_variables: |
||||
return func_variables[var_name] |
||||
|
||||
return None |
||||
|
||||
|
||||
def _find_in_contract( |
||||
var_name: str, |
||||
contract: Optional[Contract], |
||||
contract_declarer: Optional[Contract], |
||||
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure, CustomError]]: |
||||
if contract is None or contract_declarer is None: |
||||
return None |
||||
|
||||
# variable are looked from the contract declarer |
||||
contract_variables = contract.variables_as_dict |
||||
if var_name in contract_variables: |
||||
return contract_variables[var_name] |
||||
|
||||
functions = {f.name: f for f in contract.functions if not f.is_shadowed} |
||||
if var_name in functions: |
||||
return functions[var_name] |
||||
|
||||
# structures are looked on the contract declarer |
||||
structures = contract.structures_as_dict |
||||
if var_name in structures: |
||||
return structures[var_name] |
||||
|
||||
events = contract.events_as_dict |
||||
if var_name in events: |
||||
return events[var_name] |
||||
|
||||
enums = contract.enums_as_dict |
||||
if var_name in enums: |
||||
return enums[var_name] |
||||
|
||||
# If the enum is referred as its name rather than its canonicalName |
||||
enums = {e.name: e for e in contract.enums} |
||||
if var_name in enums: |
||||
return enums[var_name] |
||||
|
||||
return None |
||||
|
||||
|
||||
def find_variable( |
||||
var_name: str, |
||||
caller_context: Union[FunctionContract, Contract], |
||||
is_self: bool = False, |
||||
) -> Tuple[ |
||||
Union[ |
||||
Variable, |
||||
Function, |
||||
Contract, |
||||
SolidityVariable, |
||||
SolidityFunction, |
||||
Event, |
||||
Enum, |
||||
Structure, |
||||
] |
||||
]: |
||||
""" |
||||
Return the variable found and a boolean indicating if the variable was created |
||||
If the variable was created, it has no source mapping, and it the caller must add it |
||||
|
||||
:param var_name: |
||||
:type var_name: |
||||
:param caller_context: |
||||
:type caller_context: |
||||
:param is_self: |
||||
:type is_self: |
||||
:return: |
||||
:rtype: |
||||
""" |
||||
# pylint: disable=import-outside-toplevel |
||||
from slither.vyper_parsing.declarations.function import ( |
||||
FunctionVyper, |
||||
) |
||||
|
||||
if isinstance(caller_context, Contract): |
||||
current_scope = caller_context.file_scope |
||||
next_context = caller_context |
||||
else: |
||||
current_scope = caller_context.contract.file_scope |
||||
next_context = caller_context.contract |
||||
|
||||
function_parser: Optional[FunctionVyper] = ( |
||||
caller_context if isinstance(caller_context, FunctionContract) else None |
||||
) |
||||
|
||||
# If a local shadows a state variable but the attribute is `self`, we want to |
||||
# return the state variable and not the local. |
||||
if not is_self: |
||||
ret1 = _find_variable_in_function_parser(var_name, function_parser) |
||||
if ret1: |
||||
return ret1 |
||||
|
||||
ret = _find_in_contract(var_name, next_context, caller_context) |
||||
if ret: |
||||
return ret |
||||
|
||||
if var_name in current_scope.variables: |
||||
return current_scope.variables[var_name] |
||||
|
||||
# Could refer to any enum |
||||
all_enumss = [c.enums_as_dict for c in current_scope.contracts.values()] |
||||
all_enums = {k: v for d in all_enumss for k, v in d.items()} |
||||
if var_name in all_enums: |
||||
return all_enums[var_name] |
||||
|
||||
contracts = current_scope.contracts |
||||
if var_name in contracts: |
||||
return contracts[var_name] |
||||
|
||||
if var_name in SOLIDITY_VARIABLES: |
||||
return SolidityVariable(var_name) |
||||
|
||||
if f"{var_name}()" in SOLIDITY_FUNCTIONS: |
||||
return SolidityFunction(f"{var_name}()") |
||||
|
||||
if f"{var_name}()" in next_context.events_as_dict: |
||||
return next_context.events_as_dict[f"{var_name}()"] |
||||
|
||||
raise VariableNotFound(f"Variable not found: {var_name} (context {caller_context})") |
@ -0,0 +1,99 @@ |
||||
from typing import Union |
||||
from slither.core.solidity_types.elementary_type import ( |
||||
ElementaryType, |
||||
ElementaryTypeName, |
||||
) # TODO rename solidity type |
||||
from slither.core.solidity_types.array_type import ArrayType |
||||
from slither.core.solidity_types.mapping_type import MappingType |
||||
from slither.core.solidity_types.user_defined_type import UserDefinedType |
||||
from slither.core.declarations import FunctionContract, Contract |
||||
from slither.vyper_parsing.ast.types import Name, Subscript, Call, Index, Tuple |
||||
from slither.solc_parsing.exceptions import ParsingError |
||||
|
||||
# pylint: disable=too-many-branches,too-many-return-statements,import-outside-toplevel,too-many-locals |
||||
def parse_type( |
||||
annotation: Union[Name, Subscript, Call, Tuple], |
||||
caller_context: Union[FunctionContract, Contract], |
||||
): |
||||
from slither.vyper_parsing.expressions.expression_parsing import parse_expression |
||||
|
||||
if isinstance(caller_context, FunctionContract): |
||||
contract = caller_context.contract |
||||
else: |
||||
contract = caller_context |
||||
|
||||
assert isinstance(annotation, (Name, Subscript, Call, Tuple)) |
||||
|
||||
if isinstance(annotation, Name): |
||||
name = annotation.id |
||||
lname = name.lower() # map `String` to string |
||||
if lname in ElementaryTypeName: |
||||
return ElementaryType(lname) |
||||
|
||||
if name in contract.structures_as_dict: |
||||
return UserDefinedType(contract.structures_as_dict[name]) |
||||
|
||||
if name in contract.enums_as_dict: |
||||
return UserDefinedType(contract.enums_as_dict[name]) |
||||
|
||||
if name in contract.file_scope.contracts: |
||||
return UserDefinedType(contract.file_scope.contracts[name]) |
||||
|
||||
if name in contract.file_scope.structures: |
||||
return UserDefinedType(contract.file_scope.structures[name]) |
||||
elif isinstance(annotation, Subscript): |
||||
assert isinstance(annotation.slice, Index) |
||||
# This is also a strange construct... https://github.com/vyperlang/vyper/issues/3577 |
||||
if isinstance(annotation.slice.value, Tuple): |
||||
assert isinstance(annotation.value, Name) |
||||
if annotation.value.id == "DynArray": |
||||
type_ = parse_type(annotation.slice.value.elements[0], caller_context) |
||||
length = parse_expression(annotation.slice.value.elements[1], caller_context) |
||||
return ArrayType(type_, length) |
||||
if annotation.value.id == "HashMap": |
||||
type_from = parse_type(annotation.slice.value.elements[0], caller_context) |
||||
type_to = parse_type(annotation.slice.value.elements[1], caller_context) |
||||
|
||||
return MappingType(type_from, type_to) |
||||
|
||||
elif isinstance(annotation.value, Subscript): |
||||
type_ = parse_type(annotation.value, caller_context) |
||||
|
||||
elif isinstance(annotation.value, Name): |
||||
# TODO it is weird that the ast_type is `Index` when it's a type annotation and not an expression, so we grab the value. https://github.com/vyperlang/vyper/issues/3577 |
||||
type_ = parse_type(annotation.value, caller_context) |
||||
if annotation.value.id == "String": |
||||
# This is an elementary type |
||||
return type_ |
||||
|
||||
length = parse_expression(annotation.slice.value, caller_context) |
||||
return ArrayType(type_, length) |
||||
|
||||
elif isinstance(annotation, Call): |
||||
# TODO event variable represented as Call https://github.com/vyperlang/vyper/issues/3579 |
||||
return parse_type(annotation.args[0], caller_context) |
||||
|
||||
elif isinstance(annotation, Tuple): |
||||
# Vyper has tuple types like python x = f() where f() -> (y,z) |
||||
# and tuple elements can be unpacked like x[0]: y and x[1]: z. |
||||
# We model these as a struct and unpack each index into a field |
||||
# e.g. accessing the 0th element is translated as x._0 |
||||
from slither.core.declarations.structure import Structure |
||||
from slither.core.variables.structure_variable import StructureVariable |
||||
|
||||
st = Structure(caller_context.compilation_unit) |
||||
st.set_offset("-1:-1:-1", caller_context.compilation_unit) |
||||
st.name = "FAKE_TUPLE" |
||||
for idx, elem_info in enumerate(annotation.elements): |
||||
elem = StructureVariable() |
||||
elem.type = parse_type(elem_info, caller_context) |
||||
elem.name = f"_{idx}" |
||||
elem.set_structure(st) |
||||
elem.set_offset("-1:-1:-1", caller_context.compilation_unit) |
||||
st.elems[elem.name] = elem |
||||
st.add_elem_in_order(elem.name) |
||||
st.name += elem.name |
||||
|
||||
return UserDefinedType(st) |
||||
|
||||
raise ParsingError(f"Type name not found {name} context {caller_context}") |
@ -0,0 +1,24 @@ |
||||
from slither.core.variables.event_variable import EventVariable |
||||
from slither.vyper_parsing.type_parsing import parse_type |
||||
from slither.vyper_parsing.ast.types import AnnAssign, Call |
||||
|
||||
|
||||
class EventVariableVyper: |
||||
def __init__(self, variable: EventVariable, variable_data: AnnAssign): |
||||
self._variable = variable |
||||
self._variable.name = variable_data.target.id |
||||
if ( |
||||
isinstance(variable_data.annotation, Call) |
||||
and variable_data.annotation.func.id == "indexed" |
||||
): |
||||
self._variable.indexed = True |
||||
else: |
||||
self._variable.indexed = False |
||||
self._elem_to_parse = variable_data.annotation |
||||
|
||||
@property |
||||
def underlying_variable(self) -> EventVariable: |
||||
return self._variable |
||||
|
||||
def analyze(self, contract) -> None: |
||||
self._variable.type = parse_type(self._elem_to_parse, contract) |
@ -0,0 +1,34 @@ |
||||
from typing import Union |
||||
|
||||
from slither.core.variables.local_variable import LocalVariable |
||||
from slither.vyper_parsing.ast.types import Arg, Name, AnnAssign, Subscript, Call, Tuple |
||||
from slither.vyper_parsing.type_parsing import parse_type |
||||
|
||||
|
||||
class LocalVariableVyper: |
||||
def __init__(self, variable: LocalVariable, variable_data: Union[Arg, AnnAssign, Name]) -> None: |
||||
self._variable: LocalVariable = variable |
||||
|
||||
if isinstance(variable_data, Arg): |
||||
self._variable.name = variable_data.arg |
||||
self._elem_to_parse = variable_data.annotation |
||||
elif isinstance(variable_data, AnnAssign): |
||||
self._variable.name = variable_data.target.id |
||||
self._elem_to_parse = variable_data.annotation |
||||
else: |
||||
assert isinstance(variable_data, Name) |
||||
self._variable.name = variable_data.id |
||||
self._elem_to_parse = variable_data |
||||
|
||||
assert isinstance(self._elem_to_parse, (Name, Subscript, Call, Tuple)) |
||||
|
||||
# Vyper does not have data locations or storage pointers. |
||||
# If this was left as default, reference types would be considered storage by `LocalVariable.is_storage` |
||||
self._variable.set_location("memory") |
||||
|
||||
@property |
||||
def underlying_variable(self) -> LocalVariable: |
||||
return self._variable |
||||
|
||||
def analyze(self, contract) -> None: |
||||
self._variable.type = parse_type(self._elem_to_parse, contract) |
@ -0,0 +1,29 @@ |
||||
from slither.core.variables.state_variable import StateVariable |
||||
from slither.vyper_parsing.ast.types import VariableDecl |
||||
from slither.vyper_parsing.type_parsing import parse_type |
||||
from slither.vyper_parsing.expressions.expression_parsing import parse_expression |
||||
|
||||
|
||||
class StateVariableVyper: |
||||
def __init__(self, variable: StateVariable, variable_data: VariableDecl) -> None: |
||||
self._variable: StateVariable = variable |
||||
self._variable.name = variable_data.target.id |
||||
self._variable.is_constant = variable_data.is_constant |
||||
self._variable.is_immutable = variable_data.is_immutable |
||||
self._variable.visibility = "public" if variable_data.is_public else "internal" |
||||
self._elem_to_parse = variable_data.annotation |
||||
|
||||
if variable_data.value is not None: |
||||
self._variable.initialized = True |
||||
self._initializedNotParsed = variable_data.value |
||||
|
||||
@property |
||||
def underlying_variable(self) -> StateVariable: |
||||
return self._variable |
||||
|
||||
def analyze(self, contract) -> None: |
||||
self._variable.type = parse_type(self._elem_to_parse, contract) |
||||
|
||||
if self._variable.initialized: |
||||
self._variable.expression = parse_expression(self._initializedNotParsed, contract) |
||||
self._initializedNotParsed = None |
@ -0,0 +1,17 @@ |
||||
from slither.core.variables.structure_variable import StructureVariable |
||||
from slither.vyper_parsing.type_parsing import parse_type |
||||
from slither.vyper_parsing.ast.types import AnnAssign |
||||
|
||||
|
||||
class StructureVariableVyper: |
||||
def __init__(self, variable: StructureVariable, variable_data: AnnAssign): |
||||
self._variable: StructureVariable = variable |
||||
self._variable.name = variable_data.target.id |
||||
self._elem_to_parse = variable_data.annotation |
||||
|
||||
@property |
||||
def underlying_variable(self) -> StructureVariable: |
||||
return self._variable |
||||
|
||||
def analyze(self, contract) -> None: |
||||
self._variable.type = parse_type(self._elem_to_parse, contract) |
@ -0,0 +1,80 @@ |
||||
from typing import Dict |
||||
import os |
||||
import re |
||||
from dataclasses import dataclass, field |
||||
from slither.core.declarations import Contract |
||||
from slither.core.compilation_unit import SlitherCompilationUnit |
||||
from slither.vyper_parsing.declarations.contract import ContractVyper |
||||
from slither.analyses.data_dependency.data_dependency import compute_dependency |
||||
from slither.vyper_parsing.ast.types import Module |
||||
from slither.exceptions import SlitherException |
||||
|
||||
|
||||
@dataclass |
||||
class VyperCompilationUnit: |
||||
_compilation_unit: SlitherCompilationUnit |
||||
_parsed: bool = False |
||||
_analyzed: bool = False |
||||
_underlying_contract_to_parser: Dict[Contract, ContractVyper] = field(default_factory=dict) |
||||
_contracts_by_id: Dict[int, Contract] = field(default_factory=dict) |
||||
|
||||
def parse_module(self, data: Module, filename: str): |
||||
|
||||
sourceUnit_candidates = re.findall("[0-9]*:[0-9]*:([0-9]*)", data.src) |
||||
assert len(sourceUnit_candidates) == 1, "Source unit not found" |
||||
sourceUnit = int(sourceUnit_candidates[0]) |
||||
|
||||
self._compilation_unit.source_units[sourceUnit] = filename |
||||
if os.path.isfile(filename) and filename not in self._compilation_unit.core.source_code: |
||||
self._compilation_unit.core.add_source_code(filename) |
||||
|
||||
scope = self._compilation_unit.get_scope(filename) |
||||
contract = Contract(self._compilation_unit, scope) |
||||
contract_parser = ContractVyper(self, contract, data) |
||||
contract.set_offset(data.src, self._compilation_unit) |
||||
|
||||
self._underlying_contract_to_parser[contract] = contract_parser |
||||
|
||||
def parse_contracts(self): |
||||
for contract, contract_parser in self._underlying_contract_to_parser.items(): |
||||
self._contracts_by_id[contract.id] = contract |
||||
self._compilation_unit.contracts.append(contract) |
||||
|
||||
contract_parser.parse_enums() |
||||
contract_parser.parse_structs() |
||||
contract_parser.parse_state_variables() |
||||
contract_parser.parse_events() |
||||
contract_parser.parse_functions() |
||||
|
||||
self._parsed = True |
||||
|
||||
def analyze_contracts(self) -> None: |
||||
if not self._parsed: |
||||
raise SlitherException("Parse the contract before running analyses") |
||||
|
||||
for contract_parser in self._underlying_contract_to_parser.values(): |
||||
# State variables are analyzed for all contracts because interfaces may |
||||
# reference them, specifically, constants. |
||||
contract_parser.analyze_state_variables() |
||||
|
||||
for contract_parser in self._underlying_contract_to_parser.values(): |
||||
contract_parser.analyze() |
||||
|
||||
self._convert_to_slithir() |
||||
|
||||
compute_dependency(self._compilation_unit) |
||||
|
||||
self._analyzed = True |
||||
|
||||
def _convert_to_slithir(self) -> None: |
||||
for contract in self._compilation_unit.contracts: |
||||
contract.add_constructor_variables() |
||||
for func in contract.functions: |
||||
func.generate_slithir_and_analyze() |
||||
|
||||
contract.convert_expression_to_slithir_ssa() |
||||
|
||||
self._compilation_unit.propagate_function_calls() |
||||
for contract in self._compilation_unit.contracts: |
||||
contract.fix_phi() |
||||
contract.update_read_write_using_ssa() |
@ -0,0 +1,38 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
user_shares = () |
||||
|
||||
IRs: |
||||
user_shares(uint256[10]) = []"]; |
||||
1->2; |
||||
2[label="Node Type: EXPRESSION 2 |
||||
|
||||
EXPRESSION: |
||||
user_shares.append(1) |
||||
|
||||
IRs: |
||||
REF_1 -> LENGTH user_shares |
||||
TMP_3(uint256) := REF_1(uint256) |
||||
TMP_4(uint256) = TMP_3 (c)+ 1 |
||||
REF_1(uint256) (->user_shares) := TMP_4(uint256) |
||||
REF_2(uint256) -> user_shares[TMP_3] |
||||
REF_2(uint256) (->user_shares) := 1(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
user_shares.pop() |
||||
|
||||
IRs: |
||||
REF_4 -> LENGTH user_shares |
||||
TMP_6(uint256) = REF_4 (c)- 1 |
||||
REF_5(uint256) -> user_shares[TMP_6] |
||||
REF_5 = delete REF_5 |
||||
REF_6 -> LENGTH user_shares |
||||
REF_6(uint256) (->user_shares) := TMP_6(uint256)"]; |
||||
} |
@ -0,0 +1,118 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = block.coinbase |
||||
|
||||
IRs: |
||||
a(address) := block.coinbase(address)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = block.difficulty |
||||
|
||||
IRs: |
||||
b(uint256) := block.difficulty(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
c = block.prevrandao |
||||
|
||||
IRs: |
||||
c(uint256) := block.prevrandao(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
d = block.number |
||||
|
||||
IRs: |
||||
d(uint256) := block.number(uint256)"]; |
||||
4->5; |
||||
5[label="Node Type: NEW VARIABLE 5 |
||||
|
||||
EXPRESSION: |
||||
e = block.prevhash |
||||
|
||||
IRs: |
||||
e(bytes32) := block.prevhash(bytes32)"]; |
||||
5->6; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
f = block.timestamp |
||||
|
||||
IRs: |
||||
f(uint256) := block.timestamp(uint256)"]; |
||||
6->7; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
h = bytes32(chain.id) |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT chain.id to bytes32 |
||||
h(bytes32) := TMP_0(bytes32)"]; |
||||
7->8; |
||||
8[label="Node Type: NEW VARIABLE 8 |
||||
|
||||
EXPRESSION: |
||||
i = slice()(msg.data,0,32) |
||||
|
||||
IRs: |
||||
TMP_1(None) = SOLIDITY_CALL slice()(msg.data,0,32) |
||||
i(bytes[32]) = ['TMP_1(None)']"]; |
||||
8->9; |
||||
9[label="Node Type: NEW VARIABLE 9 |
||||
|
||||
EXPRESSION: |
||||
j = msg.gas |
||||
|
||||
IRs: |
||||
j(uint256) := msg.gas(uint256)"]; |
||||
9->10; |
||||
10[label="Node Type: NEW VARIABLE 10 |
||||
|
||||
EXPRESSION: |
||||
k = msg.sender |
||||
|
||||
IRs: |
||||
k(address) := msg.sender(address)"]; |
||||
10->11; |
||||
11[label="Node Type: NEW VARIABLE 11 |
||||
|
||||
EXPRESSION: |
||||
l = msg.value |
||||
|
||||
IRs: |
||||
l(uint256) := msg.value(uint256)"]; |
||||
11->12; |
||||
12[label="Node Type: NEW VARIABLE 12 |
||||
|
||||
EXPRESSION: |
||||
m = tx.origin |
||||
|
||||
IRs: |
||||
m(address) := tx.origin(address)"]; |
||||
12->13; |
||||
13[label="Node Type: NEW VARIABLE 13 |
||||
|
||||
EXPRESSION: |
||||
n = tx.gasprice |
||||
|
||||
IRs: |
||||
n(uint256) := tx.gasprice(uint256)"]; |
||||
13->14; |
||||
14[label="Node Type: NEW VARIABLE 14 |
||||
|
||||
EXPRESSION: |
||||
x = self.balance |
||||
|
||||
IRs: |
||||
x(uint256) := self.balance(uint256)"]; |
||||
} |
@ -0,0 +1,28 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: EXPRESSION 1 |
||||
|
||||
EXPRESSION: |
||||
self.b(x,True) |
||||
|
||||
IRs: |
||||
INTERNAL_CALL, default_args.b()(x,True)"]; |
||||
1->2; |
||||
2[label="Node Type: EXPRESSION 2 |
||||
|
||||
EXPRESSION: |
||||
self.b(1,self.config) |
||||
|
||||
IRs: |
||||
INTERNAL_CALL, default_args.b()(1,config)"]; |
||||
2->3; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
self.b(1,z) |
||||
|
||||
IRs: |
||||
INTERNAL_CALL, default_args.b()(1,z)"]; |
||||
} |
@ -0,0 +1,24 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: IF 1 |
||||
|
||||
EXPRESSION: |
||||
config |
||||
|
||||
IRs: |
||||
CONDITION config"]; |
||||
1->3[label="True"]; |
||||
1->2[label="False"]; |
||||
2[label="Node Type: END_IF 2 |
||||
"]; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
self.counter = y |
||||
|
||||
IRs: |
||||
counter(uint256) := y(uint256)"]; |
||||
3->2; |
||||
} |
@ -0,0 +1,63 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
_strategies = strategies |
||||
|
||||
IRs: |
||||
_strategies(address[3]) = ['strategies(address[3])']"]; |
||||
1->2; |
||||
2[label="Node Type: BEGIN_LOOP 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: END_LOOP 3 |
||||
"]; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
4->5; |
||||
5[label="Node Type: IF_LOOP 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= 10 |
||||
|
||||
IRs: |
||||
TMP_0(bool) = counter_var <= 10 |
||||
CONDITION TMP_0"]; |
||||
5->7[label="True"]; |
||||
5->3[label="False"]; |
||||
6[label="Node Type: EXPRESSION 6 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
6->5; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
i = counter_var |
||||
|
||||
IRs: |
||||
i(uint256) := counter_var(uint256)"]; |
||||
7->8; |
||||
8[label="Node Type: NEW VARIABLE 8 |
||||
|
||||
EXPRESSION: |
||||
max_withdraw = IStrategy(_strategies[i]).maxWithdraw(self) |
||||
|
||||
IRs: |
||||
REF_0(address) -> _strategies[i] |
||||
TMP_1 = CONVERT REF_0 to IStrategy |
||||
TMP_2(uint256) = HIGH_LEVEL_CALL, dest:TMP_1(IStrategy), function:maxWithdraw, arguments:['self'] |
||||
max_withdraw(uint256) := TMP_2(uint256)"]; |
||||
8->6; |
||||
} |
@ -0,0 +1,19 @@ |
||||
digraph{ |
||||
0[label="Node Type: OTHER_ENTRYPOINT 0 |
||||
|
||||
EXPRESSION: |
||||
x = 1 + 1 |
||||
|
||||
IRs: |
||||
TMP_3(uint256) = 1 + 1 |
||||
x(uint256) := TMP_3(uint256)"]; |
||||
0->1; |
||||
1[label="Node Type: OTHER_ENTRYPOINT 1 |
||||
|
||||
EXPRESSION: |
||||
MAX_QUEUE = 1 + x |
||||
|
||||
IRs: |
||||
TMP_4(uint256) = 1 + x |
||||
MAX_QUEUE(uint256) := TMP_4(uint256)"]; |
||||
} |
@ -0,0 +1,62 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
S = 0 |
||||
|
||||
IRs: |
||||
S(uint256) := 0(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: BEGIN_LOOP 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: END_LOOP 3 |
||||
"]; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
4->5; |
||||
5[label="Node Type: IF_LOOP 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= len()(_xp) |
||||
|
||||
IRs: |
||||
TMP_0(uint256) = SOLIDITY_CALL len()(_xp) |
||||
TMP_1(bool) = counter_var <= TMP_0 |
||||
CONDITION TMP_1"]; |
||||
5->7[label="True"]; |
||||
5->3[label="False"]; |
||||
6[label="Node Type: EXPRESSION 6 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
6->5; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
x = _xp[counter_var] |
||||
|
||||
IRs: |
||||
REF_0(uint256) -> _xp[counter_var] |
||||
x(uint256) := REF_0(uint256)"]; |
||||
7->8; |
||||
8[label="Node Type: EXPRESSION 8 |
||||
|
||||
EXPRESSION: |
||||
S += x |
||||
|
||||
IRs: |
||||
S(uint256) = S (c)+ x"]; |
||||
8->6; |
||||
} |
@ -0,0 +1,164 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: BEGIN_LOOP 1 |
||||
"]; |
||||
1->3; |
||||
2[label="Node Type: END_LOOP 2 |
||||
"]; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: IF_LOOP 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= 100 |
||||
|
||||
IRs: |
||||
TMP_0(bool) = counter_var <= 100 |
||||
CONDITION TMP_0"]; |
||||
4->6[label="True"]; |
||||
4->2[label="False"]; |
||||
5[label="Node Type: EXPRESSION 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
5->4; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
i = counter_var |
||||
|
||||
IRs: |
||||
i(uint256) := counter_var(uint256)"]; |
||||
6->7; |
||||
7[label="Node Type: IF 7 |
||||
|
||||
EXPRESSION: |
||||
i > 100 |
||||
|
||||
IRs: |
||||
TMP_1(bool) = i > 100 |
||||
CONDITION TMP_1"]; |
||||
7->9[label="True"]; |
||||
7->8[label="False"]; |
||||
8[label="Node Type: END_IF 8 |
||||
"]; |
||||
8->10; |
||||
9[label="Node Type: BREAK 9 |
||||
"]; |
||||
9->2; |
||||
10[label="Node Type: IF 10 |
||||
|
||||
EXPRESSION: |
||||
i < 3 |
||||
|
||||
IRs: |
||||
TMP_2(bool) = i < 3 |
||||
CONDITION TMP_2"]; |
||||
10->12[label="True"]; |
||||
10->11[label="False"]; |
||||
11[label="Node Type: END_IF 11 |
||||
"]; |
||||
11->13; |
||||
12[label="Node Type: CONTINUE 12 |
||||
"]; |
||||
12->5; |
||||
13[label="Node Type: NEW VARIABLE 13 |
||||
|
||||
EXPRESSION: |
||||
x = 10 |
||||
|
||||
IRs: |
||||
x(uint256) := 10(uint256)"]; |
||||
13->14; |
||||
14[label="Node Type: BEGIN_LOOP 14 |
||||
"]; |
||||
14->16; |
||||
15[label="Node Type: END_LOOP 15 |
||||
"]; |
||||
15->5; |
||||
16[label="Node Type: NEW VARIABLE 16 |
||||
|
||||
EXPRESSION: |
||||
counter_var_scope_0 = 0 |
||||
|
||||
IRs: |
||||
counter_var_scope_0(uint256) := 0(uint256)"]; |
||||
16->17; |
||||
17[label="Node Type: IF_LOOP 17 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= 10 |
||||
|
||||
IRs: |
||||
TMP_3(bool) = counter_var <= 10 |
||||
CONDITION TMP_3"]; |
||||
17->19[label="True"]; |
||||
17->15[label="False"]; |
||||
18[label="Node Type: EXPRESSION 18 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
18->17; |
||||
19[label="Node Type: NEW VARIABLE 19 |
||||
|
||||
EXPRESSION: |
||||
j = counter_var |
||||
|
||||
IRs: |
||||
j(uint256) := counter_var(uint256)"]; |
||||
19->20; |
||||
20[label="Node Type: IF 20 |
||||
|
||||
EXPRESSION: |
||||
j > 10 |
||||
|
||||
IRs: |
||||
TMP_4(bool) = j > 10 |
||||
CONDITION TMP_4"]; |
||||
20->22[label="True"]; |
||||
20->21[label="False"]; |
||||
21[label="Node Type: END_IF 21 |
||||
"]; |
||||
21->23; |
||||
22[label="Node Type: CONTINUE 22 |
||||
"]; |
||||
22->18; |
||||
23[label="Node Type: IF 23 |
||||
|
||||
EXPRESSION: |
||||
j < 3 |
||||
|
||||
IRs: |
||||
TMP_5(bool) = j < 3 |
||||
CONDITION TMP_5"]; |
||||
23->25[label="True"]; |
||||
23->24[label="False"]; |
||||
24[label="Node Type: END_IF 24 |
||||
"]; |
||||
24->26; |
||||
25[label="Node Type: BREAK 25 |
||||
"]; |
||||
25->15; |
||||
26[label="Node Type: EXPRESSION 26 |
||||
|
||||
EXPRESSION: |
||||
x -= 1 |
||||
|
||||
IRs: |
||||
x(uint256) = x (c)- 1"]; |
||||
26->18; |
||||
} |
@ -0,0 +1,56 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: BEGIN_LOOP 1 |
||||
"]; |
||||
1->3; |
||||
2[label="Node Type: END_LOOP 2 |
||||
"]; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: IF_LOOP 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= len()(self.strategies) |
||||
|
||||
IRs: |
||||
TMP_0(uint256) = SOLIDITY_CALL len()(strategies) |
||||
TMP_1(bool) = counter_var <= TMP_0 |
||||
CONDITION TMP_1"]; |
||||
4->6[label="True"]; |
||||
4->2[label="False"]; |
||||
5[label="Node Type: EXPRESSION 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
5->4; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
strategy = strategies[counter_var] |
||||
|
||||
IRs: |
||||
REF_0(address) -> strategies[counter_var] |
||||
strategy(address) := REF_0(address)"]; |
||||
6->7; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
z = IStrategy(strategy).asset() |
||||
|
||||
IRs: |
||||
TMP_2 = CONVERT strategy to IStrategy |
||||
TMP_3(address) = HIGH_LEVEL_CALL, dest:TMP_2(IStrategy), function:asset, arguments:[] |
||||
z(address) := TMP_3(address)"]; |
||||
7->5; |
||||
} |
@ -0,0 +1,19 @@ |
||||
digraph{ |
||||
0[label="Node Type: OTHER_ENTRYPOINT 0 |
||||
|
||||
EXPRESSION: |
||||
x = 1 + 1 |
||||
|
||||
IRs: |
||||
TMP_4(uint256) = 1 + 1 |
||||
x(uint256) := TMP_4(uint256)"]; |
||||
0->1; |
||||
1[label="Node Type: OTHER_ENTRYPOINT 1 |
||||
|
||||
EXPRESSION: |
||||
MAX_QUEUE = 1 + x |
||||
|
||||
IRs: |
||||
TMP_5(uint256) = 1 + x |
||||
MAX_QUEUE(uint256) := TMP_5(uint256)"]; |
||||
} |
@ -0,0 +1,172 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = p |
||||
|
||||
IRs: |
||||
a(uint256) := p(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = 1 |
||||
|
||||
IRs: |
||||
b(uint256) := 1(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
c = 0 |
||||
|
||||
IRs: |
||||
c(uint256) := 0(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: IF 4 |
||||
|
||||
EXPRESSION: |
||||
b > 0 |
||||
|
||||
IRs: |
||||
TMP_0(bool) = b > 0 |
||||
CONDITION TMP_0"]; |
||||
4->6[label="True"]; |
||||
4->5[label="False"]; |
||||
5[label="Node Type: END_IF 5 |
||||
"]; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
old_a = 1 |
||||
|
||||
IRs: |
||||
old_a(uint256) := 1(uint256)"]; |
||||
6->7; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
old_c = 2 |
||||
|
||||
IRs: |
||||
old_c(uint256) := 2(uint256)"]; |
||||
7->8; |
||||
8[label="Node Type: IF 8 |
||||
|
||||
EXPRESSION: |
||||
p > old_a |
||||
|
||||
IRs: |
||||
TMP_1(bool) = p > old_a |
||||
CONDITION TMP_1"]; |
||||
8->10[label="True"]; |
||||
8->15[label="False"]; |
||||
9[label="Node Type: END_IF 9 |
||||
"]; |
||||
9->20; |
||||
10[label="Node Type: EXPRESSION 10 |
||||
|
||||
EXPRESSION: |
||||
c = unsafe_div()(old_a * 10 ** 18,p) |
||||
|
||||
IRs: |
||||
TMP_2(uint256) = 10 (c)** 18 |
||||
TMP_3(uint256) = old_a (c)* TMP_2 |
||||
TMP_4(None) = SOLIDITY_CALL unsafe_div()(TMP_3,p) |
||||
c(uint256) := TMP_4(None)"]; |
||||
10->11; |
||||
11[label="Node Type: IF 11 |
||||
|
||||
EXPRESSION: |
||||
c < 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_5(uint256) = 10 (c)** 36 |
||||
TMP_6(uint256) = TMP_5 (c)/ 1 |
||||
TMP_7(bool) = c < TMP_6 |
||||
CONDITION TMP_7"]; |
||||
11->13[label="True"]; |
||||
11->12[label="False"]; |
||||
12[label="Node Type: END_IF 12 |
||||
"]; |
||||
12->9; |
||||
13[label="Node Type: EXPRESSION 13 |
||||
|
||||
EXPRESSION: |
||||
a = unsafe_div()(old_a * 1,10 ** 18) |
||||
|
||||
IRs: |
||||
TMP_8(uint256) = old_a (c)* 1 |
||||
TMP_9(uint256) = 10 (c)** 18 |
||||
TMP_10(None) = SOLIDITY_CALL unsafe_div()(TMP_8,TMP_9) |
||||
a(uint256) := TMP_10(None)"]; |
||||
13->14; |
||||
14[label="Node Type: EXPRESSION 14 |
||||
|
||||
EXPRESSION: |
||||
c = 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_11(uint256) = 10 (c)** 36 |
||||
TMP_12(uint256) = TMP_11 (c)/ 1 |
||||
c(uint256) := TMP_12(uint256)"]; |
||||
14->12; |
||||
15[label="Node Type: EXPRESSION 15 |
||||
|
||||
EXPRESSION: |
||||
c = unsafe_div()(p * 10 ** 18,old_a) |
||||
|
||||
IRs: |
||||
TMP_13(uint256) = 10 (c)** 18 |
||||
TMP_14(uint256) = p (c)* TMP_13 |
||||
TMP_15(None) = SOLIDITY_CALL unsafe_div()(TMP_14,old_a) |
||||
c(uint256) := TMP_15(None)"]; |
||||
15->16; |
||||
16[label="Node Type: IF 16 |
||||
|
||||
EXPRESSION: |
||||
c < 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_16(uint256) = 10 (c)** 36 |
||||
TMP_17(uint256) = TMP_16 (c)/ 1 |
||||
TMP_18(bool) = c < TMP_17 |
||||
CONDITION TMP_18"]; |
||||
16->18[label="True"]; |
||||
16->17[label="False"]; |
||||
17[label="Node Type: END_IF 17 |
||||
"]; |
||||
17->9; |
||||
18[label="Node Type: EXPRESSION 18 |
||||
|
||||
EXPRESSION: |
||||
a = unsafe_div()(old_a * 10 ** 18,1) |
||||
|
||||
IRs: |
||||
TMP_19(uint256) = 10 (c)** 18 |
||||
TMP_20(uint256) = old_a (c)* TMP_19 |
||||
TMP_21(None) = SOLIDITY_CALL unsafe_div()(TMP_20,1) |
||||
a(uint256) := TMP_21(None)"]; |
||||
18->19; |
||||
19[label="Node Type: EXPRESSION 19 |
||||
|
||||
EXPRESSION: |
||||
c = 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_22(uint256) = 10 (c)** 36 |
||||
TMP_23(uint256) = TMP_22 (c)/ 1 |
||||
c(uint256) := TMP_23(uint256)"]; |
||||
19->17; |
||||
20[label="Node Type: EXPRESSION 20 |
||||
|
||||
EXPRESSION: |
||||
c = 1 |
||||
|
||||
IRs: |
||||
c(uint256) := 1(uint256)"]; |
||||
20->5; |
||||
} |
@ -0,0 +1,62 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: IF 1 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(self.roles[self]) != 0 |
||||
|
||||
IRs: |
||||
TMP_10 = CONVERT x to uint256 |
||||
REF_4(in.Roles) -> roles[self] |
||||
TMP_11 = CONVERT REF_4 to uint256 |
||||
TMP_12(uint256) = TMP_10 & TMP_11 |
||||
TMP_13(bool) = TMP_12 != 0 |
||||
CONDITION TMP_13"]; |
||||
1->3[label="True"]; |
||||
1->2[label="False"]; |
||||
2[label="Node Type: END_IF 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: RETURN 3 |
||||
|
||||
EXPRESSION: |
||||
True |
||||
|
||||
IRs: |
||||
RETURN True"]; |
||||
3->2; |
||||
4[label="Node Type: IF 4 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(self.roles[self]) == 0 |
||||
|
||||
IRs: |
||||
TMP_14 = CONVERT x to uint256 |
||||
REF_5(in.Roles) -> roles[self] |
||||
TMP_15 = CONVERT REF_5 to uint256 |
||||
TMP_16(uint256) = TMP_14 & TMP_15 |
||||
TMP_17(bool) = TMP_16 == 0 |
||||
CONDITION TMP_17"]; |
||||
4->6[label="True"]; |
||||
4->5[label="False"]; |
||||
5[label="Node Type: END_IF 5 |
||||
"]; |
||||
5->7; |
||||
6[label="Node Type: RETURN 6 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
6->5; |
||||
7[label="Node Type: RETURN 7 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
} |
@ -0,0 +1,66 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: IF 1 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(Roles.A | Roles.B) != 0 |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT x to uint256 |
||||
REF_0(in.Roles) -> Roles.A |
||||
REF_1(in.Roles) -> Roles.B |
||||
TMP_1(in.Roles) = REF_0 | REF_1 |
||||
TMP_2 = CONVERT TMP_1 to uint256 |
||||
TMP_3(uint256) = TMP_0 & TMP_2 |
||||
TMP_4(bool) = TMP_3 != 0 |
||||
CONDITION TMP_4"]; |
||||
1->3[label="True"]; |
||||
1->2[label="False"]; |
||||
2[label="Node Type: END_IF 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: RETURN 3 |
||||
|
||||
EXPRESSION: |
||||
True |
||||
|
||||
IRs: |
||||
RETURN True"]; |
||||
3->2; |
||||
4[label="Node Type: IF 4 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(Roles.A | Roles.B) == 0 |
||||
|
||||
IRs: |
||||
TMP_5 = CONVERT x to uint256 |
||||
REF_2(in.Roles) -> Roles.A |
||||
REF_3(in.Roles) -> Roles.B |
||||
TMP_6(in.Roles) = REF_2 | REF_3 |
||||
TMP_7 = CONVERT TMP_6 to uint256 |
||||
TMP_8(uint256) = TMP_5 & TMP_7 |
||||
TMP_9(bool) = TMP_8 == 0 |
||||
CONDITION TMP_9"]; |
||||
4->6[label="True"]; |
||||
4->5[label="False"]; |
||||
5[label="Node Type: END_IF 5 |
||||
"]; |
||||
5->7; |
||||
6[label="Node Type: RETURN 6 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
6->5; |
||||
7[label="Node Type: RETURN 7 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
} |
@ -0,0 +1,74 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = 0 |
||||
|
||||
IRs: |
||||
a(int128) := 0(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = 0 |
||||
|
||||
IRs: |
||||
b(int128) := 0(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: IF 3 |
||||
|
||||
EXPRESSION: |
||||
x == b || x == a |
||||
|
||||
IRs: |
||||
TMP_18(bool) = x == b |
||||
TMP_19(bool) = x == a |
||||
TMP_20(bool) = TMP_18 || TMP_19 |
||||
CONDITION TMP_20"]; |
||||
3->5[label="True"]; |
||||
3->4[label="False"]; |
||||
4[label="Node Type: END_IF 4 |
||||
"]; |
||||
4->6; |
||||
5[label="Node Type: RETURN 5 |
||||
|
||||
EXPRESSION: |
||||
True |
||||
|
||||
IRs: |
||||
RETURN True"]; |
||||
5->4; |
||||
6[label="Node Type: IF 6 |
||||
|
||||
EXPRESSION: |
||||
x != b && x != a |
||||
|
||||
IRs: |
||||
TMP_21(bool) = x != b |
||||
TMP_22(bool) = x != a |
||||
TMP_23(bool) = TMP_21 && TMP_22 |
||||
CONDITION TMP_23"]; |
||||
6->8[label="True"]; |
||||
6->7[label="False"]; |
||||
7[label="Node Type: END_IF 7 |
||||
"]; |
||||
7->9; |
||||
8[label="Node Type: EXPRESSION 8 |
||||
|
||||
EXPRESSION: |
||||
revert(string)(nope) |
||||
|
||||
IRs: |
||||
TMP_24(None) = SOLIDITY_CALL revert(string)(nope)"]; |
||||
8->7; |
||||
9[label="Node Type: RETURN 9 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
} |
@ -0,0 +1,22 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: EXPRESSION 1 |
||||
|
||||
EXPRESSION: |
||||
BORROWED_TOKEN = ERC20(x) |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT x to ERC20 |
||||
BORROWED_TOKEN(ERC20) := TMP_0(ERC20)"]; |
||||
1->2; |
||||
2[label="Node Type: EXPRESSION 2 |
||||
|
||||
EXPRESSION: |
||||
COLLATERAL_TOKEN = ERC20(y) |
||||
|
||||
IRs: |
||||
TMP_1 = CONVERT y to ERC20 |
||||
COLLATERAL_TOKEN(ERC20) := TMP_1(ERC20)"]; |
||||
} |
@ -0,0 +1,16 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: RETURN 1 |
||||
|
||||
EXPRESSION: |
||||
(address(BORROWED_TOKEN),address(COLLATERAL_TOKEN))[i] |
||||
|
||||
IRs: |
||||
TMP_2 = CONVERT BORROWED_TOKEN to address |
||||
TMP_3 = CONVERT COLLATERAL_TOKEN to address |
||||
TMP_4(address[2]) = ['TMP_2(address)', 'TMP_3(address)'] |
||||
REF_0(address) -> TMP_4[i] |
||||
RETURN REF_0"]; |
||||
} |
@ -0,0 +1,9 @@ |
||||
digraph{ |
||||
0[label="Node Type: OTHER_ENTRYPOINT 0 |
||||
|
||||
EXPRESSION: |
||||
MY_CONSTANT = 50 |
||||
|
||||
IRs: |
||||
MY_CONSTANT(uint256) := 50(uint256)"]; |
||||
} |
@ -0,0 +1,57 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = 0 |
||||
|
||||
IRs: |
||||
a(int128) := 0(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = 0 |
||||
|
||||
IRs: |
||||
b(int128) := 0(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
(a,b) = self.foo() |
||||
|
||||
IRs: |
||||
TUPLE_0(int128,int128) = INTERNAL_CALL, interface_conversion.foo()() |
||||
a(int128)= UNPACK TUPLE_0 index: 0 |
||||
b(int128)= UNPACK TUPLE_0 index: 1 "]; |
||||
3->4; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
x = 0x0000000000000000000000000000000000000000 |
||||
|
||||
IRs: |
||||
x(address) := 0(address)"]; |
||||
4->5; |
||||
5[label="Node Type: NEW VARIABLE 5 |
||||
|
||||
EXPRESSION: |
||||
c = 0 |
||||
|
||||
IRs: |
||||
c(uint256) := 0(uint256)"]; |
||||
5->6; |
||||
6[label="Node Type: EXPRESSION 6 |
||||
|
||||
EXPRESSION: |
||||
(a,c) = Test(x).foo() |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT x to Test |
||||
TUPLE_1(int128,uint256) = HIGH_LEVEL_CALL, dest:TMP_0(Test), function:foo, arguments:[] |
||||
a(int128)= UNPACK TUPLE_1 index: 0 |
||||
c(uint256)= UNPACK TUPLE_1 index: 1 "]; |
||||
} |
@ -0,0 +1,56 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = 0 |
||||
|
||||
IRs: |
||||
a(int128) := 0(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = 0 |
||||
|
||||
IRs: |
||||
b(int128) := 0(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
(a,b) = self.foo() |
||||
|
||||
IRs: |
||||
TUPLE_2(int128,int128) = INTERNAL_CALL, interface_conversion.foo()() |
||||
a(int128)= UNPACK TUPLE_2 index: 0 |
||||
b(int128)= UNPACK TUPLE_2 index: 1 "]; |
||||
3->4; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
x = 0x0000000000000000000000000000000000000000 |
||||
|
||||
IRs: |
||||
x(address) := 0(address)"]; |
||||
4->5; |
||||
5[label="Node Type: NEW VARIABLE 5 |
||||
|
||||
EXPRESSION: |
||||
c = 0 |
||||
|
||||
IRs: |
||||
c(uint256) := 0(uint256)"]; |
||||
5->6; |
||||
6[label="Node Type: EXPRESSION 6 |
||||
|
||||
EXPRESSION: |
||||
(a,c) = self.tester.foo() |
||||
|
||||
IRs: |
||||
TUPLE_3(int128,uint256) = HIGH_LEVEL_CALL, dest:tester(Test), function:foo, arguments:[] |
||||
a(int128)= UNPACK TUPLE_3 index: 0 |
||||
c(uint256)= UNPACK TUPLE_3 index: 1 "]; |
||||
} |
@ -0,0 +1,12 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: RETURN 1 |
||||
|
||||
EXPRESSION: |
||||
(2,3) |
||||
|
||||
IRs: |
||||
RETURN 2,3"]; |
||||
} |
@ -0,0 +1,12 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: RETURN 1 |
||||
|
||||
EXPRESSION: |
||||
1 |
||||
|
||||
IRs: |
||||
RETURN 1"]; |
||||
} |
@ -0,0 +1,12 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: EXPRESSION 1 |
||||
|
||||
EXPRESSION: |
||||
revert()() |
||||
|
||||
IRs: |
||||
TMP_0(None) = SOLIDITY_CALL revert()()"]; |
||||
} |
@ -0,0 +1,17 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: RETURN 1 |
||||
|
||||
EXPRESSION: |
||||
x != self.fb() && x != self.fa() |
||||
|
||||
IRs: |
||||
TMP_1(uint256) = INTERNAL_CALL, precedence.fb()() |
||||
TMP_2(bool) = x != TMP_1 |
||||
TMP_3(uint256) = INTERNAL_CALL, precedence.fa()() |
||||
TMP_4(bool) = x != TMP_3 |
||||
TMP_5(bool) = TMP_2 && TMP_4 |
||||
RETURN TMP_5"]; |
||||
} |
@ -0,0 +1,13 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: RETURN 1 |
||||
|
||||
EXPRESSION: |
||||
X(1) |
||||
|
||||
IRs: |
||||
TMP_0(struct.X) = new X(1) |
||||
RETURN TMP_0"]; |
||||
} |
@ -0,0 +1,28 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
chainlink_lrd = Test(msg.sender).get() |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT msg.sender to Test |
||||
TUPLE_0(uint80,int256,uint256,uint256,uint80) = HIGH_LEVEL_CALL, dest:TMP_0(Test), function:get, arguments:[] |
||||
TMP_1(uint80)= UNPACK TUPLE_0 index: 0 |
||||
TMP_2(int256)= UNPACK TUPLE_0 index: 1 |
||||
TMP_3(uint256)= UNPACK TUPLE_0 index: 2 |
||||
TMP_4(uint256)= UNPACK TUPLE_0 index: 3 |
||||
TMP_5(uint80)= UNPACK TUPLE_0 index: 4 |
||||
chainlink_lrd(FAKE_TUPLE_0_1_2_3_4) = new FAKE_TUPLE_0_1_2_3_4(TMP_1,TMP_2,TMP_3,TMP_4,TMP_5)"]; |
||||
1->2; |
||||
2[label="Node Type: RETURN 2 |
||||
|
||||
EXPRESSION: |
||||
chainlink_lrd[0] |
||||
|
||||
IRs: |
||||
REF_1(uint80) -> chainlink_lrd._0 |
||||
RETURN REF_1"]; |
||||
} |
@ -0,0 +1,9 @@ |
||||
digraph{ |
||||
0[label="Node Type: OTHER_ENTRYPOINT 0 |
||||
|
||||
EXPRESSION: |
||||
MAX_BANDS = 10 |
||||
|
||||
IRs: |
||||
MAX_BANDS(uint256) := 10(uint256)"]; |
||||
} |
@ -0,0 +1,25 @@ |
||||
from pathlib import Path |
||||
from slither import Slither |
||||
|
||||
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" |
||||
|
||||
ALL_TESTS = list(Path(TEST_DATA_DIR).glob("*.vy")) |
||||
|
||||
|
||||
def pytest_generate_tests(metafunc): |
||||
test_cases = [] |
||||
for test_file in ALL_TESTS: |
||||
sl = Slither(test_file.as_posix()) |
||||
for contract in sl.contracts: |
||||
if contract.is_interface: |
||||
continue |
||||
for func_or_modifier in contract.functions: |
||||
test_cases.append( |
||||
(func_or_modifier.canonical_name, func_or_modifier.slithir_cfg_to_dot_str()) |
||||
) |
||||
|
||||
metafunc.parametrize("test_case", test_cases, ids=lambda x: x[0]) |
||||
|
||||
|
||||
def test_vyper_cfgir(test_case, snapshot): |
||||
assert snapshot() == test_case[1] |
@ -0,0 +1,9 @@ |
||||
|
||||
|
||||
interface ERC20: |
||||
def totalSupply() -> uint256: view |
||||
def balanceOf(_owner: address) -> uint256: view |
||||
def allowance(_owner: address, _spender: address) -> uint256: view |
||||
def transfer(_to: address, _value: uint256) -> bool: nonpayable |
||||
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable |
||||
def approve(_spender: address, _value: uint256) -> bool: nonpayable |
@ -0,0 +1,24 @@ |
||||
|
||||
@payable |
||||
@external |
||||
def test_builtins(): |
||||
a: address = block.coinbase |
||||
b: uint256 = block.difficulty |
||||
c: uint256 = block.prevrandao |
||||
d: uint256 = block.number |
||||
e: bytes32 = block.prevhash |
||||
f: uint256 = block.timestamp |
||||
h: bytes32 = convert(chain.id, bytes32) |
||||
i: Bytes[32] = slice(msg.data, 0, 32) |
||||
j: uint256 = msg.gas |
||||
k: address = msg.sender |
||||
l: uint256 = msg.value |
||||
m: address = tx.origin |
||||
n: uint256 = tx.gasprice |
||||
x: uint256 = self.balance |
||||
|
||||
@external |
||||
def c(x: uint256): |
||||
user_shares: DynArray[uint256, 10] = [] |
||||
user_shares.append(1) |
||||
user_shares.pop() |
@ -0,0 +1,12 @@ |
||||
counter: uint256 |
||||
config: bool |
||||
@internal |
||||
def b(y: uint256, config: bool = True): |
||||
if config: |
||||
self.counter = y |
||||
|
||||
@external |
||||
def a(x: uint256, z: bool): |
||||
self.b(x) |
||||
self.b(1, self.config) |
||||
self.b(1, z) |
@ -0,0 +1,29 @@ |
||||
|
||||
|
||||
x: constant(uint256) = 1 + 1 |
||||
MAX_QUEUE: constant(uint256) = 1 + x |
||||
|
||||
interface IStrategy: |
||||
def asset() -> address: view |
||||
def balanceOf(owner: address) -> uint256: view |
||||
def maxDeposit(receiver: address) -> uint256: view |
||||
def maxWithdraw(owner: address) -> uint256: view |
||||
def withdraw(amount: uint256, receiver: address, owner: address) -> uint256: nonpayable |
||||
def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable |
||||
def deposit(assets: uint256, receiver: address) -> uint256: nonpayable |
||||
def totalAssets() -> (uint256): view |
||||
def convertToAssets(shares: uint256) -> uint256: view |
||||
def convertToShares(assets: uint256) -> uint256: view |
||||
def previewWithdraw(assets: uint256) -> uint256: view |
||||
|
||||
struct X: |
||||
y: int8 |
||||
|
||||
strategies: public(DynArray[address, MAX_QUEUE]) |
||||
|
||||
@external |
||||
def for_loop(): |
||||
|
||||
for strategy in self.strategies: |
||||
z: address = IStrategy(strategy).asset() |
||||
|
@ -0,0 +1,26 @@ |
||||
|
||||
|
||||
x: constant(uint256) = 1 + 1 |
||||
MAX_QUEUE: constant(uint256) = 1 + x |
||||
|
||||
interface IStrategy: |
||||
def asset() -> address: view |
||||
def balanceOf(owner: address) -> uint256: view |
||||
def maxDeposit(receiver: address) -> uint256: view |
||||
def maxWithdraw(owner: address) -> uint256: view |
||||
def withdraw(amount: uint256, receiver: address, owner: address) -> uint256: nonpayable |
||||
def redeem(shares: uint256, receiver: address, owner: address) -> uint256: nonpayable |
||||
def deposit(assets: uint256, receiver: address) -> uint256: nonpayable |
||||
def totalAssets() -> (uint256): view |
||||
def convertToAssets(shares: uint256) -> uint256: view |
||||
def convertToShares(assets: uint256) -> uint256: view |
||||
def previewWithdraw(assets: uint256) -> uint256: view |
||||
|
||||
@external |
||||
def for_loop(strategies: DynArray[address, MAX_QUEUE]): |
||||
_strategies: DynArray[address, MAX_QUEUE] = strategies |
||||
|
||||
for i in range(10): |
||||
|
||||
max_withdraw: uint256 = IStrategy(_strategies[i]).maxWithdraw(self) |
||||
|
@ -0,0 +1,6 @@ |
||||
|
||||
@external |
||||
def get_D(_xp: uint256[3], _amp: uint256): |
||||
S: uint256 = 0 |
||||
for x in _xp: |
||||
S += x |
@ -0,0 +1,17 @@ |
||||
@external |
||||
def f(): |
||||
for i in range(100): |
||||
if (i > 100): |
||||
break |
||||
|
||||
if (i < 3): |
||||
continue |
||||
x: uint256 = 10 |
||||
for j in range(10): |
||||
if (j > 10): |
||||
continue |
||||
|
||||
if (j < 3): |
||||
break |
||||
|
||||
x -= 1 |
@ -0,0 +1,22 @@ |
||||
@external |
||||
@view |
||||
def compute(p: uint256): |
||||
a: uint256 = p |
||||
b: uint256 = 1 |
||||
c: uint256 = 0 |
||||
|
||||
if b > 0: |
||||
old_a: uint256 = 1 |
||||
old_c: uint256 = 2 |
||||
if p > old_a: |
||||
c = unsafe_div(old_a * 10**18, p) |
||||
if c < 10**36 / 1: |
||||
a = unsafe_div(old_a * 1, 10**18) |
||||
c = 10**36 / 1 |
||||
else: |
||||
c = unsafe_div(p * 10**18, old_a) |
||||
if c < 10**36 / 1: |
||||
a = unsafe_div(old_a * 10**18, 1) |
||||
c = 10**36 / 1 |
||||
|
||||
c = 1 |
@ -0,0 +1,36 @@ |
||||
enum Roles: |
||||
A |
||||
B |
||||
|
||||
roles: public(HashMap[address, Roles]) |
||||
|
||||
@external |
||||
def baz(x: Roles) -> bool: |
||||
if x in (Roles.A | Roles.B): |
||||
return True |
||||
if x not in (Roles.A | Roles.B): |
||||
return False |
||||
|
||||
return False |
||||
|
||||
@external |
||||
def bar(x: Roles) -> bool: |
||||
|
||||
if x in self.roles[self]: |
||||
return True |
||||
if x not in self.roles[self]: |
||||
return False |
||||
|
||||
return False |
||||
|
||||
@external |
||||
def foo(x: int128) -> bool: |
||||
a: int128 = 0 |
||||
b: int128 = 0 |
||||
|
||||
if x in [a, b]: |
||||
return True |
||||
if x not in [a, b]: |
||||
raise "nope" |
||||
|
||||
return False |
@ -0,0 +1,17 @@ |
||||
interface ERC20: |
||||
def transfer(_to: address, _value: uint256) -> bool: nonpayable |
||||
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: nonpayable |
||||
def approve(_spender: address, _value: uint256) -> bool: nonpayable |
||||
|
||||
BORROWED_TOKEN: immutable(ERC20) |
||||
COLLATERAL_TOKEN: immutable(ERC20) |
||||
|
||||
@external |
||||
def __init__(x: address, y: address): |
||||
BORROWED_TOKEN = ERC20(x) |
||||
COLLATERAL_TOKEN = ERC20(y) |
||||
|
||||
@external |
||||
@pure |
||||
def coins(i: uint256) -> address: |
||||
return [BORROWED_TOKEN.address, COLLATERAL_TOKEN.address][i] |
@ -0,0 +1,7 @@ |
||||
struct MyStruct: |
||||
liquidation_range: address |
||||
MY_CONSTANT: constant(uint256) = 50 |
||||
interface MyInterface: |
||||
def my_func(a: int256, b: DynArray[uint256, MY_CONSTANT]) -> MyStruct: nonpayable |
||||
|
||||
|
@ -0,0 +1,29 @@ |
||||
interface Test: |
||||
def foo() -> (int128, uint256): nonpayable |
||||
|
||||
tester: Test |
||||
|
||||
@internal |
||||
def foo() -> (int128, int128): |
||||
return 2, 3 |
||||
|
||||
@external |
||||
def bar(): |
||||
a: int128 = 0 |
||||
b: int128 = 0 |
||||
(a, b) = self.foo() |
||||
|
||||
x: address = 0x0000000000000000000000000000000000000000 |
||||
c: uint256 = 0 |
||||
a, c = Test(x).foo() |
||||
|
||||
@external |
||||
def baz(): |
||||
a: int128 = 0 |
||||
b: int128 = 0 |
||||
(a, b) = self.foo() |
||||
|
||||
x: address = 0x0000000000000000000000000000000000000000 |
||||
c: uint256 = 0 |
||||
a, c = self.tester.foo() |
||||
|
@ -0,0 +1,13 @@ |
||||
@internal |
||||
def fa() -> uint256: |
||||
return 1 |
||||
|
||||
@internal |
||||
def fb() -> uint256: |
||||
raise |
||||
|
||||
@external |
||||
def foo(x: uint256) -> bool: |
||||
return x not in [self.fa(), self.fb()] |
||||
|
||||
|
@ -0,0 +1,7 @@ |
||||
struct X: |
||||
y: int8 |
||||
|
||||
|
||||
@external |
||||
def test() -> X: |
||||
return X({y: 1}) |
@ -0,0 +1,10 @@ |
||||
interface Test: |
||||
def get() -> (uint80, int256, uint256, uint256, uint80): view |
||||
@external |
||||
def __default__() -> uint80: |
||||
chainlink_lrd: (uint80, int256, uint256, uint256, uint80) = Test(msg.sender).get() |
||||
return chainlink_lrd[0] |
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,16 @@ |
||||
name: public(String[64]) |
||||
symbol: public(String[32]) |
||||
decimals: public(uint256) |
||||
totalSupply: public(uint256) |
||||
|
||||
balances: HashMap[address, uint256] |
||||
allowances: HashMap[address, HashMap[address, uint256]] |
||||
|
||||
MAX_BANDS: constant(uint256) = 10 |
||||
|
||||
x: public(uint256[3][4]) |
||||
y: public(uint256[2]) |
||||
|
||||
struct Loan: |
||||
liquidation_range: DynArray[uint256, MAX_BANDS] |
||||
deposit_amounts: DynArray[uint256, MAX_BANDS] |
@ -0,0 +1,4 @@ |
||||
name: public(String[64]) |
||||
@external |
||||
def __init__(name: String[64]): |
||||
self.name = name |
@ -0,0 +1,99 @@ |
||||
# # pylint: disable=too-many-lines |
||||
|
||||
|
||||
from slither.core.solidity_types import ElementaryType |
||||
from slither.slithir.operations import ( |
||||
Phi, |
||||
InternalCall, |
||||
) |
||||
from slither.slithir.variables import ( |
||||
Constant, |
||||
) |
||||
|
||||
|
||||
def test_interface_conversion_and_call_resolution(slither_from_vyper_source): |
||||
with slither_from_vyper_source( |
||||
""" |
||||
interface Test: |
||||
def foo() -> (int128, uint256): nonpayable |
||||
|
||||
@internal |
||||
def foo() -> (int128, int128): |
||||
return 2, 3 |
||||
|
||||
@external |
||||
def bar(): |
||||
a: int128 = 0 |
||||
b: int128 = 0 |
||||
(a, b) = self.foo() |
||||
|
||||
x: address = 0x0000000000000000000000000000000000000000 |
||||
c: uint256 = 0 |
||||
a, c = Test(x).foo() |
||||
""" |
||||
) as sl: |
||||
interface = next(iter(x for x in sl.contracts if x.is_interface)) |
||||
contract = next(iter(x for x in sl.contracts if not x.is_interface)) |
||||
func = contract.get_function_from_signature("bar()") |
||||
(contract, function) = func.high_level_calls[0] |
||||
assert contract == interface |
||||
assert function.signature_str == "foo() returns(int128,uint256)" |
||||
|
||||
|
||||
def test_phi_entry_point_internal_call(slither_from_vyper_source): |
||||
with slither_from_vyper_source( |
||||
""" |
||||
counter: uint256 |
||||
@internal |
||||
def b(y: uint256): |
||||
self.counter = y |
||||
|
||||
@external |
||||
def a(x: uint256): |
||||
self.b(x) |
||||
self.b(1) |
||||
""" |
||||
) as sl: |
||||
f = sl.contracts[0].get_function_from_signature("b(uint256)") |
||||
assert ( |
||||
len( |
||||
[ |
||||
ssanode |
||||
for node in f.nodes |
||||
for ssanode in node.irs_ssa |
||||
if isinstance(ssanode, Phi) |
||||
] |
||||
) |
||||
== 1 |
||||
) |
||||
|
||||
|
||||
def test_call_with_default_args(slither_from_vyper_source): |
||||
with slither_from_vyper_source( |
||||
""" |
||||
counter: uint256 |
||||
@internal |
||||
def c(y: uint256, config: bool = True): |
||||
self.counter = y |
||||
@external |
||||
def a(x: uint256): |
||||
self.c(x) |
||||
self.c(1) |
||||
@external |
||||
def b(x: uint256): |
||||
self.c(x, False) |
||||
self.c(1, False) |
||||
""" |
||||
) as sl: |
||||
a = sl.contracts[0].get_function_from_signature("a(uint256)") |
||||
for node in a.nodes: |
||||
for op in node.irs_ssa: |
||||
if isinstance(op, InternalCall) and op.function.name == "c": |
||||
assert len(op.arguments) == 2 |
||||
assert op.arguments[1] == Constant("True", ElementaryType("bool")) |
||||
b = sl.contracts[0].get_function_from_signature("b(uint256)") |
||||
for node in b.nodes: |
||||
for op in node.irs_ssa: |
||||
if isinstance(op, InternalCall) and op.function.name == "c": |
||||
assert len(op.arguments) == 2 |
||||
assert op.arguments[1] == Constant("False", ElementaryType("bool")) |
Loading…
Reference in new issue