mirror of https://github.com/crytic/slither
Merge branch 'dev' of https://github.com/dokzai/slither into issue-1654
commit
00bd1d4f2e
@ -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,91 @@ |
||||
from typing import List, Optional |
||||
|
||||
from slither.core.declarations import SolidityFunction, Function |
||||
from slither.detectors.abstract_detector import ( |
||||
AbstractDetector, |
||||
DetectorClassification, |
||||
DETECTOR_INFO, |
||||
) |
||||
from slither.slithir.operations import SolidityCall |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
def _assembly_node(function: Function) -> Optional[SolidityCall]: |
||||
""" |
||||
Check if there is a node that use return in assembly |
||||
|
||||
Args: |
||||
function: |
||||
|
||||
Returns: |
||||
|
||||
""" |
||||
|
||||
for ir in function.all_slithir_operations(): |
||||
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( |
||||
"return(uint256,uint256)" |
||||
): |
||||
return ir |
||||
return None |
||||
|
||||
|
||||
class IncorrectReturn(AbstractDetector): |
||||
""" |
||||
Check for cases where a return(a,b) is used in an assembly function |
||||
""" |
||||
|
||||
ARGUMENT = "incorrect-return" |
||||
HELP = "If a `return` is incorrectly used in assembly mode." |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return" |
||||
|
||||
WIKI_TITLE = "Incorrect return in assembly" |
||||
WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution." |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
contract C { |
||||
function f() internal returns (uint a, uint b) { |
||||
assembly { |
||||
return (5, 6) |
||||
} |
||||
} |
||||
|
||||
function g() returns (bool){ |
||||
f(); |
||||
return true; |
||||
} |
||||
} |
||||
``` |
||||
The return statement in `f` will cause execution in `g` to halt. |
||||
The function will return 6 bytes starting from offset 5, instead of returning a boolean.""" |
||||
|
||||
WIKI_RECOMMENDATION = "Use the `leave` statement." |
||||
|
||||
# pylint: disable=too-many-nested-blocks |
||||
def _detect(self) -> List[Output]: |
||||
results: List[Output] = [] |
||||
for c in self.contracts: |
||||
for f in c.functions_and_modifiers_declared: |
||||
|
||||
for node in f.nodes: |
||||
if node.sons: |
||||
for function_called in node.internal_calls: |
||||
if isinstance(function_called, Function): |
||||
found = _assembly_node(function_called) |
||||
if found: |
||||
|
||||
info: DETECTOR_INFO = [ |
||||
f, |
||||
" calls ", |
||||
function_called, |
||||
" which halt the execution ", |
||||
found.node, |
||||
"\n", |
||||
] |
||||
json = self.generate_result(info) |
||||
|
||||
results.append(json) |
||||
|
||||
return results |
@ -0,0 +1,68 @@ |
||||
from typing import List |
||||
|
||||
from slither.core.declarations import SolidityFunction, Function |
||||
from slither.detectors.abstract_detector import ( |
||||
AbstractDetector, |
||||
DetectorClassification, |
||||
DETECTOR_INFO, |
||||
) |
||||
from slither.slithir.operations import SolidityCall |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
class ReturnInsteadOfLeave(AbstractDetector): |
||||
""" |
||||
Check for cases where a return(a,b) is used in an assembly function that also returns two variables |
||||
""" |
||||
|
||||
ARGUMENT = "return-leave" |
||||
HELP = "If a `return` is used instead of a `leave`." |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return" |
||||
|
||||
WIKI_TITLE = "Return instead of leave in assembly" |
||||
WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used." |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
contract C { |
||||
function f() internal returns (uint a, uint b) { |
||||
assembly { |
||||
return (5, 6) |
||||
} |
||||
} |
||||
|
||||
} |
||||
``` |
||||
The function will halt the execution, instead of returning a two uint.""" |
||||
|
||||
WIKI_RECOMMENDATION = "Use the `leave` statement." |
||||
|
||||
def _check_function(self, f: Function) -> List[Output]: |
||||
results: List[Output] = [] |
||||
|
||||
for node in f.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( |
||||
"return(uint256,uint256)" |
||||
): |
||||
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"] |
||||
json = self.generate_result(info) |
||||
|
||||
results.append(json) |
||||
return results |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
results: List[Output] = [] |
||||
for c in self.contracts: |
||||
for f in c.functions_declared: |
||||
|
||||
if ( |
||||
len(f.returns) == 2 |
||||
and f.contains_assembly |
||||
and f.visibility not in ["public", "external"] |
||||
): |
||||
results += self._check_function(f) |
||||
|
||||
return results |
@ -0,0 +1,93 @@ |
||||
""" |
||||
Module detecting incorrect operator usage for exponentiation where bitwise xor '^' is used instead of '**' |
||||
""" |
||||
from typing import Tuple, List, Union |
||||
|
||||
from slither.core.cfg.node import Node |
||||
from slither.core.declarations import Contract, Function |
||||
from slither.detectors.abstract_detector import ( |
||||
AbstractDetector, |
||||
DetectorClassification, |
||||
DETECTOR_INFO, |
||||
) |
||||
from slither.slithir.operations import Binary, BinaryType, Operation |
||||
from slither.slithir.utils.utils import RVALUE |
||||
from slither.slithir.variables.constant import Constant |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
def _is_constant_candidate(var: Union[RVALUE, Function]) -> bool: |
||||
""" |
||||
Check if the variable is a constant. |
||||
Do not consider variable that are expressed with hexadecimal. |
||||
Something like 2^0xf is likely to be a correct bitwise operator |
||||
:param var: |
||||
:return: |
||||
""" |
||||
return isinstance(var, Constant) and not var.original_value.startswith("0x") |
||||
|
||||
|
||||
def _is_bitwise_xor_on_constant(ir: Operation) -> bool: |
||||
return ( |
||||
isinstance(ir, Binary) |
||||
and ir.type == BinaryType.CARET |
||||
and (_is_constant_candidate(ir.variable_left) or _is_constant_candidate(ir.variable_right)) |
||||
) |
||||
|
||||
|
||||
def _detect_incorrect_operator(contract: Contract) -> List[Tuple[Function, Node]]: |
||||
ret: List[Tuple[Function, Node]] = [] |
||||
f: Function |
||||
for f in contract.functions + contract.modifiers: # type:ignore |
||||
# Heuristic: look for binary expressions with ^ operator where at least one of the operands is a constant, and |
||||
# the constant is not in hex, because hex typically is used with bitwise xor and not exponentiation |
||||
nodes = [node for node in f.nodes for ir in node.irs if _is_bitwise_xor_on_constant(ir)] |
||||
for node in nodes: |
||||
ret.append((f, node)) |
||||
return ret |
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods |
||||
class IncorrectOperatorExponentiation(AbstractDetector): |
||||
""" |
||||
Incorrect operator usage of bitwise xor mistaking it for exponentiation |
||||
""" |
||||
|
||||
ARGUMENT = "incorrect-exp" |
||||
HELP = "Incorrect exponentiation" |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation" |
||||
|
||||
WIKI_TITLE = "Incorrect exponentiation" |
||||
WIKI_DESCRIPTION = "Detect use of bitwise `xor ^` instead of exponential `**`" |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
contract Bug{ |
||||
uint UINT_MAX = 2^256 - 1; |
||||
... |
||||
} |
||||
``` |
||||
Alice deploys a contract in which `UINT_MAX` incorrectly uses `^` operator instead of `**` for exponentiation""" |
||||
|
||||
WIKI_RECOMMENDATION = "Use the correct operator `**` for exponentiation." |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
"""Detect the incorrect operator usage for exponentiation where bitwise xor ^ is used instead of ** |
||||
|
||||
Returns: |
||||
list: (function, node) |
||||
""" |
||||
results: List[Output] = [] |
||||
for c in self.compilation_unit.contracts_derived: |
||||
res = _detect_incorrect_operator(c) |
||||
for (func, node) in res: |
||||
info: DETECTOR_INFO = [ |
||||
func, |
||||
" has bitwise-xor operator ^ instead of the exponentiation operator **: \n", |
||||
] |
||||
info += ["\t - ", node, "\n"] |
||||
results.append(self.generate_result(info)) |
||||
|
||||
return results |
@ -0,0 +1,123 @@ |
||||
from typing import List |
||||
|
||||
from slither.core.cfg.node import Node |
||||
from slither.core.declarations import Contract |
||||
from slither.core.declarations.function import Function |
||||
from slither.core.solidity_types import Type |
||||
from slither.detectors.abstract_detector import ( |
||||
AbstractDetector, |
||||
DetectorClassification, |
||||
DETECTOR_INFO, |
||||
) |
||||
from slither.slithir.operations import LowLevelCall, HighLevelCall |
||||
from slither.analyses.data_dependency.data_dependency import is_tainted |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
class ReturnBomb(AbstractDetector): |
||||
|
||||
ARGUMENT = "return-bomb" |
||||
HELP = "A low level callee may consume all callers gas unexpectedly." |
||||
IMPACT = DetectorClassification.LOW |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb" |
||||
|
||||
WIKI_TITLE = "Return Bomb" |
||||
WIKI_DESCRIPTION = "A low level callee may consume all callers gas unexpectedly." |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
//Modified from https://github.com/nomad-xyz/ExcessivelySafeCall |
||||
contract BadGuy { |
||||
function youveActivateMyTrapCard() external pure returns (bytes memory) { |
||||
assembly{ |
||||
revert(0, 1000000) |
||||
} |
||||
} |
||||
} |
||||
|
||||
contract Mark { |
||||
function oops(address badGuy) public{ |
||||
bool success; |
||||
bytes memory ret; |
||||
|
||||
// Mark pays a lot of gas for this copy |
||||
//(success, ret) = badGuy.call{gas:10000}( |
||||
(success, ret) = badGuy.call( |
||||
abi.encodeWithSelector( |
||||
BadGuy.youveActivateMyTrapCard.selector |
||||
) |
||||
); |
||||
|
||||
// Mark may OOG here, preventing local state changes |
||||
//importantCleanup(); |
||||
} |
||||
} |
||||
|
||||
``` |
||||
After Mark calls BadGuy bytes are copied from returndata to memory, the memory expansion cost is paid. This means that when using a standard solidity call, the callee can "returnbomb" the caller, imposing an arbitrary gas cost. |
||||
Callee unexpectedly makes the caller OOG. |
||||
""" |
||||
|
||||
WIKI_RECOMMENDATION = "Avoid unlimited implicit decoding of returndata." |
||||
|
||||
@staticmethod |
||||
def is_dynamic_type(ty: Type) -> bool: |
||||
# ty.is_dynamic ? |
||||
name = str(ty) |
||||
if "[]" in name or name in ("bytes", "string"): |
||||
return True |
||||
return False |
||||
|
||||
def get_nodes_for_function(self, function: Function, contract: Contract) -> List[Node]: |
||||
nodes = [] |
||||
for node in function.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, (HighLevelCall, LowLevelCall)): |
||||
if not is_tainted(ir.destination, contract): # type:ignore |
||||
# Only interested if the target address is controlled/tainted |
||||
continue |
||||
|
||||
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function): |
||||
# in normal highlevel calls return bombs are _possible_ |
||||
# if the return type is dynamic and the caller tries to copy and decode large data |
||||
has_dyn = False |
||||
if ir.function.return_type: |
||||
has_dyn = any( |
||||
self.is_dynamic_type(ty) for ty in ir.function.return_type |
||||
) |
||||
|
||||
if not has_dyn: |
||||
continue |
||||
|
||||
# If a gas budget was specified then the |
||||
# user may not know about the return bomb |
||||
if ir.call_gas is None: |
||||
# if a gas budget was NOT specified then the caller |
||||
# may already suspect the call may spend all gas? |
||||
continue |
||||
|
||||
nodes.append(node) |
||||
# TODO: check that there is some state change after the call |
||||
|
||||
return nodes |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
results = [] |
||||
|
||||
for contract in self.compilation_unit.contracts: |
||||
for function in contract.functions_declared: |
||||
nodes = self.get_nodes_for_function(function, contract) |
||||
if nodes: |
||||
info: DETECTOR_INFO = [ |
||||
function, |
||||
" tries to limit the gas of an external call that controls implicit decoding\n", |
||||
] |
||||
|
||||
for node in sorted(nodes, key=lambda x: x.node_id): |
||||
info += ["\t", node, "\n"] |
||||
|
||||
res = self.generate_result(info) |
||||
results.append(res) |
||||
|
||||
return results |
@ -0,0 +1,69 @@ |
||||
from typing import List |
||||
from slither.detectors.abstract_detector import ( |
||||
AbstractDetector, |
||||
DetectorClassification, |
||||
DETECTOR_INFO, |
||||
) |
||||
from slither.slithir.operations import ( |
||||
Binary, |
||||
BinaryType, |
||||
) |
||||
|
||||
from slither.core.declarations import Function |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
class TautologicalCompare(AbstractDetector): |
||||
""" |
||||
Same variable comparison detector |
||||
""" |
||||
|
||||
ARGUMENT = "tautological-compare" |
||||
HELP = "Comparing a variable to itself always returns true or false, depending on comparison" |
||||
IMPACT = DetectorClassification.MEDIUM |
||||
CONFIDENCE = DetectorClassification.HIGH |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#tautological-compare" |
||||
|
||||
WIKI_TITLE = "Tautological compare" |
||||
WIKI_DESCRIPTION = "A variable compared to itself is probably an error as it will always return `true` for `==`, `>=`, `<=` and always `false` for `<`, `>` and `!=`." |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
function check(uint a) external returns(bool){ |
||||
return (a >= a); |
||||
} |
||||
``` |
||||
`check` always return true.""" |
||||
|
||||
WIKI_RECOMMENDATION = "Remove comparison or compare to different value." |
||||
|
||||
def _check_function(self, f: Function) -> List[Output]: |
||||
affected_nodes = set() |
||||
for node in f.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, Binary): |
||||
if ir.type in [ |
||||
BinaryType.GREATER, |
||||
BinaryType.GREATER_EQUAL, |
||||
BinaryType.LESS, |
||||
BinaryType.LESS_EQUAL, |
||||
BinaryType.EQUAL, |
||||
BinaryType.NOT_EQUAL, |
||||
]: |
||||
if ir.variable_left == ir.variable_right: |
||||
affected_nodes.add(node) |
||||
|
||||
results = [] |
||||
for n in affected_nodes: |
||||
info: DETECTOR_INFO = [f, " compares a variable to itself:\n\t", n, "\n"] |
||||
res = self.generate_result(info) |
||||
results.append(res) |
||||
return results |
||||
|
||||
def _detect(self): |
||||
results = [] |
||||
|
||||
for f in self.compilation_unit.functions_and_modifiers: |
||||
results.extend(self._check_function(f)) |
||||
|
||||
return results |
@ -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,5 @@ |
||||
contract TestSlither { |
||||
function testFunction(uint256 param1, uint256, address param3) public { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
Test.bad1() (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#9-12) has bitwise-xor operator ^ instead of the exponentiation operator **: |
||||
- UINT_MAX = 2 ^ 256 - 1 (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#10) |
||||
|
||||
Test.bad0(uint256) (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#5-7) has bitwise-xor operator ^ instead of the exponentiation operator **: |
||||
- a ^ 2 (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#6) |
||||
|
||||
Derived.slitherConstructorVariables() (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#30) has bitwise-xor operator ^ instead of the exponentiation operator **: |
||||
- my_var = 2 ^ 256 - 1 (tests/e2e/detectors/test_data/incorrect-exp/0.7.6/incorrect_exp.sol#3) |
||||
|
@ -0,0 +1,4 @@ |
||||
C.bad1() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#21-24) calls C.indirect() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#17-19) which halt the execution return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#4) |
||||
|
||||
C.bad0() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#8-11) calls C.internal_return() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#2-6) which halt the execution return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#4) |
||||
|
@ -1,32 +1,36 @@ |
||||
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#14-16) is not in CapWords |
||||
Variable T.s_myStateVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#63) is not in mixedCase |
||||
|
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#70) is not in mixedCase |
||||
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#17-19) is not in CapWords |
||||
|
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#70) is single letter l, O, or I, which should not be used |
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#76) is not in mixedCase |
||||
|
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#69) is not in mixedCase |
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#76) is single letter l, O, or I, which should not be used |
||||
|
||||
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#11) is not in mixedCase |
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#75) is not in mixedCase |
||||
|
||||
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#14) is not in mixedCase |
||||
|
||||
Constant naming.MY_other_CONSTANT (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES |
||||
|
||||
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#3-48) is not in CapWords |
||||
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#3-51) is not in CapWords |
||||
|
||||
Enum naming.numbers (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#6) is not in CapWords |
||||
|
||||
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#60) is not in mixedCase |
||||
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#66) is not in mixedCase |
||||
|
||||
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#62) is not in mixedCase |
||||
|
||||
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#57) is not in mixedCase |
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#75) is single letter l, O, or I, which should not be used |
||||
|
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#69) is single letter l, O, or I, which should not be used |
||||
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#26) is not in CapWords |
||||
|
||||
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#23) is not in CapWords |
||||
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#44-46) is not in mixedCase |
||||
|
||||
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#41-43) is not in mixedCase |
||||
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#33-36) is not in mixedCase |
||||
|
||||
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#30-33) is not in mixedCase |
||||
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#74) is single letter l, O, or I, which should not be used |
||||
|
||||
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#68) is single letter l, O, or I, which should not be used |
||||
Variable naming.i_myImutableVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#11) is not in mixedCase |
||||
|
||||
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#35) is not in mixedCase |
||||
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#38) is not in mixedCase |
||||
|
||||
|
@ -1,32 +1,36 @@ |
||||
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#14-16) is not in CapWords |
||||
Variable T.s_myStateVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#63) is not in mixedCase |
||||
|
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#70) is not in mixedCase |
||||
Struct naming.test (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#17-19) is not in CapWords |
||||
|
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#70) is single letter l, O, or I, which should not be used |
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#76) is not in mixedCase |
||||
|
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#69) is not in mixedCase |
||||
Variable T.I (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#76) is single letter l, O, or I, which should not be used |
||||
|
||||
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#11) is not in mixedCase |
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#75) is not in mixedCase |
||||
|
||||
Variable naming.Var_One (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#14) is not in mixedCase |
||||
|
||||
Constant naming.MY_other_CONSTANT (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#9) is not in UPPER_CASE_WITH_UNDERSCORES |
||||
|
||||
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#3-48) is not in CapWords |
||||
Contract naming (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#3-51) is not in CapWords |
||||
|
||||
Enum naming.numbers (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#6) is not in CapWords |
||||
|
||||
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#60) is not in mixedCase |
||||
Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#66) is not in mixedCase |
||||
|
||||
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#62) is not in mixedCase |
||||
|
||||
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#57) is not in mixedCase |
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#75) is single letter l, O, or I, which should not be used |
||||
|
||||
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#69) is single letter l, O, or I, which should not be used |
||||
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#26) is not in CapWords |
||||
|
||||
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#23) is not in CapWords |
||||
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#44-46) is not in mixedCase |
||||
|
||||
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#41-43) is not in mixedCase |
||||
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#33-36) is not in mixedCase |
||||
|
||||
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#30-33) is not in mixedCase |
||||
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#74) is single letter l, O, or I, which should not be used |
||||
|
||||
Variable T.l (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#68) is single letter l, O, or I, which should not be used |
||||
Variable naming.i_myImutableVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#11) is not in mixedCase |
||||
|
||||
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#35) is not in mixedCase |
||||
Parameter naming.setInt(uint256,uint256).Number2 (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#38) is not in mixedCase |
||||
|
||||
|
@ -0,0 +1,5 @@ |
||||
Mark.oops(address) (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#31-55) tries to limit the gas of an external call that controls implicit decoding |
||||
ret1 = BadGuy(badGuy).fbad{gas: 2000}() (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#42) |
||||
(x,str) = BadGuy(badGuy).fbad1{gas: 2000}() (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#44) |
||||
(success,ret) = badGuy.call{gas: 10000}(abi.encodeWithSelector(BadGuy.llbad.selector)) (tests/e2e/detectors/test_data/return-bomb/0.8.20/return_bomb.sol#47-51) |
||||
|
@ -0,0 +1,2 @@ |
||||
C.f() (tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol#3-7) contains an incorrect call to return: return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol#5) |
||||
|
@ -0,0 +1,3 @@ |
||||
A.check(uint256) (tests/e2e/detectors/test_data/tautological-compare/0.8.20/compare.sol#3-5) compares a variable to itself: |
||||
(a >= a) (tests/e2e/detectors/test_data/tautological-compare/0.8.20/compare.sol#4) |
||||
|
@ -0,0 +1,30 @@ |
||||
contract Test { |
||||
|
||||
uint my_var = 2 ^ 256-1; |
||||
|
||||
function bad0(uint a) internal returns (uint) { |
||||
return a^2; |
||||
} |
||||
|
||||
function bad1() internal returns (uint) { |
||||
uint UINT_MAX = 2^256-1; |
||||
return UINT_MAX; |
||||
} |
||||
|
||||
/* Correct exponentiation operator */ |
||||
function good0(uint a) internal returns (uint) { |
||||
return a**2; |
||||
} |
||||
|
||||
/* Neither operand is a constant */ |
||||
function good1(uint a) internal returns (uint) { |
||||
return a^a; |
||||
} |
||||
|
||||
/* The constant operand 0xff in hex typically means bitwise xor */ |
||||
function good2(uint a) internal returns (uint) { |
||||
return a^0xff; |
||||
} |
||||
} |
||||
|
||||
contract Derived is Test {} |
Binary file not shown.
@ -0,0 +1,36 @@ |
||||
contract C { |
||||
function internal_return() internal{ |
||||
assembly { |
||||
return (5, 6) |
||||
} |
||||
} |
||||
|
||||
function bad0() public returns (bool){ |
||||
internal_return(); |
||||
return true; |
||||
} |
||||
|
||||
function indirect2() internal { |
||||
internal_return(); |
||||
} |
||||
|
||||
function indirect() internal { |
||||
indirect2(); |
||||
} |
||||
|
||||
function bad1() public returns (bool){ |
||||
indirect(); |
||||
return true; |
||||
} |
||||
|
||||
function good0() public{ |
||||
// Dont report if there is no following operation |
||||
internal_return(); |
||||
} |
||||
|
||||
function good1() public returns (uint a, uint b){ |
||||
assembly { |
||||
return (5, 6) |
||||
} |
||||
} |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,57 @@ |
||||
contract BadGuy { |
||||
function llbad() external pure returns (bytes memory) { |
||||
assembly{ |
||||
revert(0, 1000000) |
||||
} |
||||
} |
||||
|
||||
function fgood() external payable returns (uint){ |
||||
assembly{ |
||||
return(0, 1000000) |
||||
} |
||||
} |
||||
|
||||
function fbad() external payable returns (uint[] memory){ |
||||
assembly{ |
||||
return(0, 1000000) |
||||
} |
||||
} |
||||
|
||||
function fbad1() external payable returns (uint, string memory){ |
||||
assembly{ |
||||
return(0, 1000000) |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
contract Mark { |
||||
|
||||
function oops(address badGuy) public{ |
||||
bool success; |
||||
string memory str; |
||||
bytes memory ret; |
||||
uint x; |
||||
uint[] memory ret1; |
||||
|
||||
x = BadGuy(badGuy).fgood{gas:2000}(); |
||||
|
||||
ret1 = BadGuy(badGuy).fbad(); //good (no gas specified) |
||||
|
||||
ret1 = BadGuy(badGuy).fbad{gas:2000}(); |
||||
|
||||
(x, str) = BadGuy(badGuy).fbad1{gas:2000}(); |
||||
|
||||
// Mark pays a lot of gas for this copy 😬😬😬 |
||||
(success, ret) = badGuy.call{gas:10000}( |
||||
abi.encodeWithSelector( |
||||
BadGuy.llbad.selector |
||||
) |
||||
); |
||||
|
||||
// Mark may OOG here, preventing local state changes |
||||
//importantCleanup(); |
||||
} |
||||
} |
||||
|
Binary file not shown.
@ -0,0 +1,8 @@ |
||||
contract C { |
||||
|
||||
function f() internal returns (uint a, uint b){ |
||||
assembly { |
||||
return (5, 6) |
||||
} |
||||
} |
||||
} |
Binary file not shown.
@ -0,0 +1,6 @@ |
||||
|
||||
contract A{ |
||||
function check(uint a) external returns(bool){ |
||||
return (a >= a); |
||||
} |
||||
} |
Binary file not shown.
@ -0,0 +1,38 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
user_shares = () |
||||
|
||||
IRs: |
||||
user_shares(uint256[10]) = []"]; |
||||
1->2; |
||||
2[label="Node Type: EXPRESSION 2 |
||||
|
||||
EXPRESSION: |
||||
user_shares.append(1) |
||||
|
||||
IRs: |
||||
REF_1 -> LENGTH user_shares |
||||
TMP_3(uint256) := REF_1(uint256) |
||||
TMP_4(uint256) = TMP_3 (c)+ 1 |
||||
REF_1(uint256) (->user_shares) := TMP_4(uint256) |
||||
REF_2(uint256) -> user_shares[TMP_3] |
||||
REF_2(uint256) (->user_shares) := 1(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
user_shares.pop() |
||||
|
||||
IRs: |
||||
REF_4 -> LENGTH user_shares |
||||
TMP_6(uint256) = REF_4 (c)- 1 |
||||
REF_5(uint256) -> user_shares[TMP_6] |
||||
REF_5 = delete REF_5 |
||||
REF_6 -> LENGTH user_shares |
||||
REF_6(uint256) (->user_shares) := TMP_6(uint256)"]; |
||||
} |
@ -0,0 +1,118 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = block.coinbase |
||||
|
||||
IRs: |
||||
a(address) := block.coinbase(address)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = block.difficulty |
||||
|
||||
IRs: |
||||
b(uint256) := block.difficulty(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
c = block.prevrandao |
||||
|
||||
IRs: |
||||
c(uint256) := block.prevrandao(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
d = block.number |
||||
|
||||
IRs: |
||||
d(uint256) := block.number(uint256)"]; |
||||
4->5; |
||||
5[label="Node Type: NEW VARIABLE 5 |
||||
|
||||
EXPRESSION: |
||||
e = block.prevhash |
||||
|
||||
IRs: |
||||
e(bytes32) := block.prevhash(bytes32)"]; |
||||
5->6; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
f = block.timestamp |
||||
|
||||
IRs: |
||||
f(uint256) := block.timestamp(uint256)"]; |
||||
6->7; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
h = bytes32(chain.id) |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT chain.id to bytes32 |
||||
h(bytes32) := TMP_0(bytes32)"]; |
||||
7->8; |
||||
8[label="Node Type: NEW VARIABLE 8 |
||||
|
||||
EXPRESSION: |
||||
i = slice()(msg.data,0,32) |
||||
|
||||
IRs: |
||||
TMP_1(None) = SOLIDITY_CALL slice()(msg.data,0,32) |
||||
i(bytes[32]) = ['TMP_1(None)']"]; |
||||
8->9; |
||||
9[label="Node Type: NEW VARIABLE 9 |
||||
|
||||
EXPRESSION: |
||||
j = msg.gas |
||||
|
||||
IRs: |
||||
j(uint256) := msg.gas(uint256)"]; |
||||
9->10; |
||||
10[label="Node Type: NEW VARIABLE 10 |
||||
|
||||
EXPRESSION: |
||||
k = msg.sender |
||||
|
||||
IRs: |
||||
k(address) := msg.sender(address)"]; |
||||
10->11; |
||||
11[label="Node Type: NEW VARIABLE 11 |
||||
|
||||
EXPRESSION: |
||||
l = msg.value |
||||
|
||||
IRs: |
||||
l(uint256) := msg.value(uint256)"]; |
||||
11->12; |
||||
12[label="Node Type: NEW VARIABLE 12 |
||||
|
||||
EXPRESSION: |
||||
m = tx.origin |
||||
|
||||
IRs: |
||||
m(address) := tx.origin(address)"]; |
||||
12->13; |
||||
13[label="Node Type: NEW VARIABLE 13 |
||||
|
||||
EXPRESSION: |
||||
n = tx.gasprice |
||||
|
||||
IRs: |
||||
n(uint256) := tx.gasprice(uint256)"]; |
||||
13->14; |
||||
14[label="Node Type: NEW VARIABLE 14 |
||||
|
||||
EXPRESSION: |
||||
x = self.balance |
||||
|
||||
IRs: |
||||
x(uint256) := self.balance(uint256)"]; |
||||
} |
@ -0,0 +1,28 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: EXPRESSION 1 |
||||
|
||||
EXPRESSION: |
||||
self.b(x,True) |
||||
|
||||
IRs: |
||||
INTERNAL_CALL, default_args.b()(x,True)"]; |
||||
1->2; |
||||
2[label="Node Type: EXPRESSION 2 |
||||
|
||||
EXPRESSION: |
||||
self.b(1,self.config) |
||||
|
||||
IRs: |
||||
INTERNAL_CALL, default_args.b()(1,config)"]; |
||||
2->3; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
self.b(1,z) |
||||
|
||||
IRs: |
||||
INTERNAL_CALL, default_args.b()(1,z)"]; |
||||
} |
@ -0,0 +1,24 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: IF 1 |
||||
|
||||
EXPRESSION: |
||||
config |
||||
|
||||
IRs: |
||||
CONDITION config"]; |
||||
1->3[label="True"]; |
||||
1->2[label="False"]; |
||||
2[label="Node Type: END_IF 2 |
||||
"]; |
||||
3[label="Node Type: EXPRESSION 3 |
||||
|
||||
EXPRESSION: |
||||
self.counter = y |
||||
|
||||
IRs: |
||||
counter(uint256) := y(uint256)"]; |
||||
3->2; |
||||
} |
@ -0,0 +1,63 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
_strategies = strategies |
||||
|
||||
IRs: |
||||
_strategies(address[3]) = ['strategies(address[3])']"]; |
||||
1->2; |
||||
2[label="Node Type: BEGIN_LOOP 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: END_LOOP 3 |
||||
"]; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
4->5; |
||||
5[label="Node Type: IF_LOOP 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= 10 |
||||
|
||||
IRs: |
||||
TMP_0(bool) = counter_var <= 10 |
||||
CONDITION TMP_0"]; |
||||
5->7[label="True"]; |
||||
5->3[label="False"]; |
||||
6[label="Node Type: EXPRESSION 6 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
6->5; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
i = counter_var |
||||
|
||||
IRs: |
||||
i(uint256) := counter_var(uint256)"]; |
||||
7->8; |
||||
8[label="Node Type: NEW VARIABLE 8 |
||||
|
||||
EXPRESSION: |
||||
max_withdraw = IStrategy(_strategies[i]).maxWithdraw(self) |
||||
|
||||
IRs: |
||||
REF_0(address) -> _strategies[i] |
||||
TMP_1 = CONVERT REF_0 to IStrategy |
||||
TMP_2(uint256) = HIGH_LEVEL_CALL, dest:TMP_1(IStrategy), function:maxWithdraw, arguments:['self'] |
||||
max_withdraw(uint256) := TMP_2(uint256)"]; |
||||
8->6; |
||||
} |
@ -0,0 +1,19 @@ |
||||
digraph{ |
||||
0[label="Node Type: OTHER_ENTRYPOINT 0 |
||||
|
||||
EXPRESSION: |
||||
x = 1 + 1 |
||||
|
||||
IRs: |
||||
TMP_3(uint256) = 1 + 1 |
||||
x(uint256) := TMP_3(uint256)"]; |
||||
0->1; |
||||
1[label="Node Type: OTHER_ENTRYPOINT 1 |
||||
|
||||
EXPRESSION: |
||||
MAX_QUEUE = 1 + x |
||||
|
||||
IRs: |
||||
TMP_4(uint256) = 1 + x |
||||
MAX_QUEUE(uint256) := TMP_4(uint256)"]; |
||||
} |
@ -0,0 +1,62 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
S = 0 |
||||
|
||||
IRs: |
||||
S(uint256) := 0(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: BEGIN_LOOP 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: END_LOOP 3 |
||||
"]; |
||||
4[label="Node Type: NEW VARIABLE 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
4->5; |
||||
5[label="Node Type: IF_LOOP 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= len()(_xp) |
||||
|
||||
IRs: |
||||
TMP_0(uint256) = SOLIDITY_CALL len()(_xp) |
||||
TMP_1(bool) = counter_var <= TMP_0 |
||||
CONDITION TMP_1"]; |
||||
5->7[label="True"]; |
||||
5->3[label="False"]; |
||||
6[label="Node Type: EXPRESSION 6 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
6->5; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
x = _xp[counter_var] |
||||
|
||||
IRs: |
||||
REF_0(uint256) -> _xp[counter_var] |
||||
x(uint256) := REF_0(uint256)"]; |
||||
7->8; |
||||
8[label="Node Type: EXPRESSION 8 |
||||
|
||||
EXPRESSION: |
||||
S += x |
||||
|
||||
IRs: |
||||
S(uint256) = S (c)+ x"]; |
||||
8->6; |
||||
} |
@ -0,0 +1,164 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: BEGIN_LOOP 1 |
||||
"]; |
||||
1->3; |
||||
2[label="Node Type: END_LOOP 2 |
||||
"]; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: IF_LOOP 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= 100 |
||||
|
||||
IRs: |
||||
TMP_0(bool) = counter_var <= 100 |
||||
CONDITION TMP_0"]; |
||||
4->6[label="True"]; |
||||
4->2[label="False"]; |
||||
5[label="Node Type: EXPRESSION 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
5->4; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
i = counter_var |
||||
|
||||
IRs: |
||||
i(uint256) := counter_var(uint256)"]; |
||||
6->7; |
||||
7[label="Node Type: IF 7 |
||||
|
||||
EXPRESSION: |
||||
i > 100 |
||||
|
||||
IRs: |
||||
TMP_1(bool) = i > 100 |
||||
CONDITION TMP_1"]; |
||||
7->9[label="True"]; |
||||
7->8[label="False"]; |
||||
8[label="Node Type: END_IF 8 |
||||
"]; |
||||
8->10; |
||||
9[label="Node Type: BREAK 9 |
||||
"]; |
||||
9->2; |
||||
10[label="Node Type: IF 10 |
||||
|
||||
EXPRESSION: |
||||
i < 3 |
||||
|
||||
IRs: |
||||
TMP_2(bool) = i < 3 |
||||
CONDITION TMP_2"]; |
||||
10->12[label="True"]; |
||||
10->11[label="False"]; |
||||
11[label="Node Type: END_IF 11 |
||||
"]; |
||||
11->13; |
||||
12[label="Node Type: CONTINUE 12 |
||||
"]; |
||||
12->5; |
||||
13[label="Node Type: NEW VARIABLE 13 |
||||
|
||||
EXPRESSION: |
||||
x = 10 |
||||
|
||||
IRs: |
||||
x(uint256) := 10(uint256)"]; |
||||
13->14; |
||||
14[label="Node Type: BEGIN_LOOP 14 |
||||
"]; |
||||
14->16; |
||||
15[label="Node Type: END_LOOP 15 |
||||
"]; |
||||
15->5; |
||||
16[label="Node Type: NEW VARIABLE 16 |
||||
|
||||
EXPRESSION: |
||||
counter_var_scope_0 = 0 |
||||
|
||||
IRs: |
||||
counter_var_scope_0(uint256) := 0(uint256)"]; |
||||
16->17; |
||||
17[label="Node Type: IF_LOOP 17 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= 10 |
||||
|
||||
IRs: |
||||
TMP_3(bool) = counter_var <= 10 |
||||
CONDITION TMP_3"]; |
||||
17->19[label="True"]; |
||||
17->15[label="False"]; |
||||
18[label="Node Type: EXPRESSION 18 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
18->17; |
||||
19[label="Node Type: NEW VARIABLE 19 |
||||
|
||||
EXPRESSION: |
||||
j = counter_var |
||||
|
||||
IRs: |
||||
j(uint256) := counter_var(uint256)"]; |
||||
19->20; |
||||
20[label="Node Type: IF 20 |
||||
|
||||
EXPRESSION: |
||||
j > 10 |
||||
|
||||
IRs: |
||||
TMP_4(bool) = j > 10 |
||||
CONDITION TMP_4"]; |
||||
20->22[label="True"]; |
||||
20->21[label="False"]; |
||||
21[label="Node Type: END_IF 21 |
||||
"]; |
||||
21->23; |
||||
22[label="Node Type: CONTINUE 22 |
||||
"]; |
||||
22->18; |
||||
23[label="Node Type: IF 23 |
||||
|
||||
EXPRESSION: |
||||
j < 3 |
||||
|
||||
IRs: |
||||
TMP_5(bool) = j < 3 |
||||
CONDITION TMP_5"]; |
||||
23->25[label="True"]; |
||||
23->24[label="False"]; |
||||
24[label="Node Type: END_IF 24 |
||||
"]; |
||||
24->26; |
||||
25[label="Node Type: BREAK 25 |
||||
"]; |
||||
25->15; |
||||
26[label="Node Type: EXPRESSION 26 |
||||
|
||||
EXPRESSION: |
||||
x -= 1 |
||||
|
||||
IRs: |
||||
x(uint256) = x (c)- 1"]; |
||||
26->18; |
||||
} |
@ -0,0 +1,56 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: BEGIN_LOOP 1 |
||||
"]; |
||||
1->3; |
||||
2[label="Node Type: END_LOOP 2 |
||||
"]; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
counter_var = 0 |
||||
|
||||
IRs: |
||||
counter_var(uint256) := 0(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: IF_LOOP 4 |
||||
|
||||
EXPRESSION: |
||||
counter_var <= len()(self.strategies) |
||||
|
||||
IRs: |
||||
TMP_0(uint256) = SOLIDITY_CALL len()(strategies) |
||||
TMP_1(bool) = counter_var <= TMP_0 |
||||
CONDITION TMP_1"]; |
||||
4->6[label="True"]; |
||||
4->2[label="False"]; |
||||
5[label="Node Type: EXPRESSION 5 |
||||
|
||||
EXPRESSION: |
||||
counter_var += 1 |
||||
|
||||
IRs: |
||||
counter_var(uint256) = counter_var (c)+ 1"]; |
||||
5->4; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
strategy = strategies[counter_var] |
||||
|
||||
IRs: |
||||
REF_0(address) -> strategies[counter_var] |
||||
strategy(address) := REF_0(address)"]; |
||||
6->7; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
z = IStrategy(strategy).asset() |
||||
|
||||
IRs: |
||||
TMP_2 = CONVERT strategy to IStrategy |
||||
TMP_3(address) = HIGH_LEVEL_CALL, dest:TMP_2(IStrategy), function:asset, arguments:[] |
||||
z(address) := TMP_3(address)"]; |
||||
7->5; |
||||
} |
@ -0,0 +1,19 @@ |
||||
digraph{ |
||||
0[label="Node Type: OTHER_ENTRYPOINT 0 |
||||
|
||||
EXPRESSION: |
||||
x = 1 + 1 |
||||
|
||||
IRs: |
||||
TMP_4(uint256) = 1 + 1 |
||||
x(uint256) := TMP_4(uint256)"]; |
||||
0->1; |
||||
1[label="Node Type: OTHER_ENTRYPOINT 1 |
||||
|
||||
EXPRESSION: |
||||
MAX_QUEUE = 1 + x |
||||
|
||||
IRs: |
||||
TMP_5(uint256) = 1 + x |
||||
MAX_QUEUE(uint256) := TMP_5(uint256)"]; |
||||
} |
@ -0,0 +1,172 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: NEW VARIABLE 1 |
||||
|
||||
EXPRESSION: |
||||
a = p |
||||
|
||||
IRs: |
||||
a(uint256) := p(uint256)"]; |
||||
1->2; |
||||
2[label="Node Type: NEW VARIABLE 2 |
||||
|
||||
EXPRESSION: |
||||
b = 1 |
||||
|
||||
IRs: |
||||
b(uint256) := 1(uint256)"]; |
||||
2->3; |
||||
3[label="Node Type: NEW VARIABLE 3 |
||||
|
||||
EXPRESSION: |
||||
c = 0 |
||||
|
||||
IRs: |
||||
c(uint256) := 0(uint256)"]; |
||||
3->4; |
||||
4[label="Node Type: IF 4 |
||||
|
||||
EXPRESSION: |
||||
b > 0 |
||||
|
||||
IRs: |
||||
TMP_0(bool) = b > 0 |
||||
CONDITION TMP_0"]; |
||||
4->6[label="True"]; |
||||
4->5[label="False"]; |
||||
5[label="Node Type: END_IF 5 |
||||
"]; |
||||
6[label="Node Type: NEW VARIABLE 6 |
||||
|
||||
EXPRESSION: |
||||
old_a = 1 |
||||
|
||||
IRs: |
||||
old_a(uint256) := 1(uint256)"]; |
||||
6->7; |
||||
7[label="Node Type: NEW VARIABLE 7 |
||||
|
||||
EXPRESSION: |
||||
old_c = 2 |
||||
|
||||
IRs: |
||||
old_c(uint256) := 2(uint256)"]; |
||||
7->8; |
||||
8[label="Node Type: IF 8 |
||||
|
||||
EXPRESSION: |
||||
p > old_a |
||||
|
||||
IRs: |
||||
TMP_1(bool) = p > old_a |
||||
CONDITION TMP_1"]; |
||||
8->10[label="True"]; |
||||
8->15[label="False"]; |
||||
9[label="Node Type: END_IF 9 |
||||
"]; |
||||
9->20; |
||||
10[label="Node Type: EXPRESSION 10 |
||||
|
||||
EXPRESSION: |
||||
c = unsafe_div()(old_a * 10 ** 18,p) |
||||
|
||||
IRs: |
||||
TMP_2(uint256) = 10 (c)** 18 |
||||
TMP_3(uint256) = old_a (c)* TMP_2 |
||||
TMP_4(None) = SOLIDITY_CALL unsafe_div()(TMP_3,p) |
||||
c(uint256) := TMP_4(None)"]; |
||||
10->11; |
||||
11[label="Node Type: IF 11 |
||||
|
||||
EXPRESSION: |
||||
c < 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_5(uint256) = 10 (c)** 36 |
||||
TMP_6(uint256) = TMP_5 (c)/ 1 |
||||
TMP_7(bool) = c < TMP_6 |
||||
CONDITION TMP_7"]; |
||||
11->13[label="True"]; |
||||
11->12[label="False"]; |
||||
12[label="Node Type: END_IF 12 |
||||
"]; |
||||
12->9; |
||||
13[label="Node Type: EXPRESSION 13 |
||||
|
||||
EXPRESSION: |
||||
a = unsafe_div()(old_a * 1,10 ** 18) |
||||
|
||||
IRs: |
||||
TMP_8(uint256) = old_a (c)* 1 |
||||
TMP_9(uint256) = 10 (c)** 18 |
||||
TMP_10(None) = SOLIDITY_CALL unsafe_div()(TMP_8,TMP_9) |
||||
a(uint256) := TMP_10(None)"]; |
||||
13->14; |
||||
14[label="Node Type: EXPRESSION 14 |
||||
|
||||
EXPRESSION: |
||||
c = 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_11(uint256) = 10 (c)** 36 |
||||
TMP_12(uint256) = TMP_11 (c)/ 1 |
||||
c(uint256) := TMP_12(uint256)"]; |
||||
14->12; |
||||
15[label="Node Type: EXPRESSION 15 |
||||
|
||||
EXPRESSION: |
||||
c = unsafe_div()(p * 10 ** 18,old_a) |
||||
|
||||
IRs: |
||||
TMP_13(uint256) = 10 (c)** 18 |
||||
TMP_14(uint256) = p (c)* TMP_13 |
||||
TMP_15(None) = SOLIDITY_CALL unsafe_div()(TMP_14,old_a) |
||||
c(uint256) := TMP_15(None)"]; |
||||
15->16; |
||||
16[label="Node Type: IF 16 |
||||
|
||||
EXPRESSION: |
||||
c < 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_16(uint256) = 10 (c)** 36 |
||||
TMP_17(uint256) = TMP_16 (c)/ 1 |
||||
TMP_18(bool) = c < TMP_17 |
||||
CONDITION TMP_18"]; |
||||
16->18[label="True"]; |
||||
16->17[label="False"]; |
||||
17[label="Node Type: END_IF 17 |
||||
"]; |
||||
17->9; |
||||
18[label="Node Type: EXPRESSION 18 |
||||
|
||||
EXPRESSION: |
||||
a = unsafe_div()(old_a * 10 ** 18,1) |
||||
|
||||
IRs: |
||||
TMP_19(uint256) = 10 (c)** 18 |
||||
TMP_20(uint256) = old_a (c)* TMP_19 |
||||
TMP_21(None) = SOLIDITY_CALL unsafe_div()(TMP_20,1) |
||||
a(uint256) := TMP_21(None)"]; |
||||
18->19; |
||||
19[label="Node Type: EXPRESSION 19 |
||||
|
||||
EXPRESSION: |
||||
c = 10 ** 36 / 1 |
||||
|
||||
IRs: |
||||
TMP_22(uint256) = 10 (c)** 36 |
||||
TMP_23(uint256) = TMP_22 (c)/ 1 |
||||
c(uint256) := TMP_23(uint256)"]; |
||||
19->17; |
||||
20[label="Node Type: EXPRESSION 20 |
||||
|
||||
EXPRESSION: |
||||
c = 1 |
||||
|
||||
IRs: |
||||
c(uint256) := 1(uint256)"]; |
||||
20->5; |
||||
} |
@ -0,0 +1,62 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: IF 1 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(self.roles[self]) != 0 |
||||
|
||||
IRs: |
||||
TMP_10 = CONVERT x to uint256 |
||||
REF_4(in.Roles) -> roles[self] |
||||
TMP_11 = CONVERT REF_4 to uint256 |
||||
TMP_12(uint256) = TMP_10 & TMP_11 |
||||
TMP_13(bool) = TMP_12 != 0 |
||||
CONDITION TMP_13"]; |
||||
1->3[label="True"]; |
||||
1->2[label="False"]; |
||||
2[label="Node Type: END_IF 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: RETURN 3 |
||||
|
||||
EXPRESSION: |
||||
True |
||||
|
||||
IRs: |
||||
RETURN True"]; |
||||
3->2; |
||||
4[label="Node Type: IF 4 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(self.roles[self]) == 0 |
||||
|
||||
IRs: |
||||
TMP_14 = CONVERT x to uint256 |
||||
REF_5(in.Roles) -> roles[self] |
||||
TMP_15 = CONVERT REF_5 to uint256 |
||||
TMP_16(uint256) = TMP_14 & TMP_15 |
||||
TMP_17(bool) = TMP_16 == 0 |
||||
CONDITION TMP_17"]; |
||||
4->6[label="True"]; |
||||
4->5[label="False"]; |
||||
5[label="Node Type: END_IF 5 |
||||
"]; |
||||
5->7; |
||||
6[label="Node Type: RETURN 6 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
6->5; |
||||
7[label="Node Type: RETURN 7 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
} |
@ -0,0 +1,66 @@ |
||||
digraph{ |
||||
0[label="Node Type: ENTRY_POINT 0 |
||||
"]; |
||||
0->1; |
||||
1[label="Node Type: IF 1 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(Roles.A | Roles.B) != 0 |
||||
|
||||
IRs: |
||||
TMP_0 = CONVERT x to uint256 |
||||
REF_0(in.Roles) -> Roles.A |
||||
REF_1(in.Roles) -> Roles.B |
||||
TMP_1(in.Roles) = REF_0 | REF_1 |
||||
TMP_2 = CONVERT TMP_1 to uint256 |
||||
TMP_3(uint256) = TMP_0 & TMP_2 |
||||
TMP_4(bool) = TMP_3 != 0 |
||||
CONDITION TMP_4"]; |
||||
1->3[label="True"]; |
||||
1->2[label="False"]; |
||||
2[label="Node Type: END_IF 2 |
||||
"]; |
||||
2->4; |
||||
3[label="Node Type: RETURN 3 |
||||
|
||||
EXPRESSION: |
||||
True |
||||
|
||||
IRs: |
||||
RETURN True"]; |
||||
3->2; |
||||
4[label="Node Type: IF 4 |
||||
|
||||
EXPRESSION: |
||||
uint256(x) & uint256(Roles.A | Roles.B) == 0 |
||||
|
||||
IRs: |
||||
TMP_5 = CONVERT x to uint256 |
||||
REF_2(in.Roles) -> Roles.A |
||||
REF_3(in.Roles) -> Roles.B |
||||
TMP_6(in.Roles) = REF_2 | REF_3 |
||||
TMP_7 = CONVERT TMP_6 to uint256 |
||||
TMP_8(uint256) = TMP_5 & TMP_7 |
||||
TMP_9(bool) = TMP_8 == 0 |
||||
CONDITION TMP_9"]; |
||||
4->6[label="True"]; |
||||
4->5[label="False"]; |
||||
5[label="Node Type: END_IF 5 |
||||
"]; |
||||
5->7; |
||||
6[label="Node Type: RETURN 6 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
6->5; |
||||
7[label="Node Type: RETURN 7 |
||||
|
||||
EXPRESSION: |
||||
False |
||||
|
||||
IRs: |
||||
RETURN False"]; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue