From aae3a0e64212d2dc7b22a777e52d9c36f778bd30 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 7 Jun 2020 16:15:21 +0200 Subject: [PATCH 01/73] Minimal YUL handling --- slither/core/declarations/function.py | 5 +- .../core/declarations/solidity_variables.py | 9 +- slither/core/expressions/binary_operation.py | 26 + slither/detectors/statements/assembly.py | 2 +- slither/printers/summary/slithir.py | 2 +- slither/slithir/convert.py | 7 + slither/slithir/operations/binary.py | 26 + slither/slithir/operations/codesize.py | 24 + slither/slithir/utils/ssa.py | 5 + slither/solc_parsing/cfg/node.py | 48 ++ slither/solc_parsing/declarations/function.py | 30 +- slither/solc_parsing/yul/__init__.py | 0 slither/solc_parsing/yul/evm_functions.py | 268 +++++++++ slither/solc_parsing/yul/parse_yul.py | 525 ++++++++++++++++++ .../visitors/slithir/expression_to_slithir.py | 80 ++- 15 files changed, 1023 insertions(+), 34 deletions(-) create mode 100644 slither/slithir/operations/codesize.py create mode 100644 slither/solc_parsing/yul/__init__.py create mode 100644 slither/solc_parsing/yul/evm_functions.py create mode 100644 slither/solc_parsing/yul/parse_yul.py diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index d28dabb2c..caf06b74d 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -89,6 +89,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): def __init__(self): super(Function, self).__init__() + self._scope: List[str] = [] self._name: Optional[str] = None self._view: bool = False self._pure: bool = False @@ -207,7 +208,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): Return the function signature without the return values """ name, parameters, _ = self.signature - return name + "(" + ",".join(parameters) + ")" + return ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")" @property def canonical_name(self) -> str: @@ -216,7 +217,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): Return the function signature without the return values """ name, parameters, _ = self.signature - return self.contract_declarer.name + "." + name + "(" + ",".join(parameters) + ")" + return ".".join([self.contract_declarer.name] + self._scope + [name]) + "(" + ",".join(parameters) + ")" @property def contains_assembly(self) -> bool: diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index ff26dd1c7..3f493147d 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -90,7 +90,14 @@ class SolidityVariable(Context): # dev function, will be removed once the code is stable def _check_name(self, name: str): - assert name in SOLIDITY_VARIABLES + assert name in SOLIDITY_VARIABLES or name.endswith("_slot") or name.endswith("_offset") + + @property + def state_variable(self): + if self._name.endswith("_slot"): + return self._name[:-5] + if self._name.endswith("_offset"): + return self._name[:-7] @property def name(self) -> str: diff --git a/slither/core/expressions/binary_operation.py b/slither/core/expressions/binary_operation.py index 0a1c4b7a2..6f8c0f15f 100644 --- a/slither/core/expressions/binary_operation.py +++ b/slither/core/expressions/binary_operation.py @@ -31,6 +31,12 @@ class BinaryOperationType(Enum): ANDAND = 17 # && OROR = 18 # || + DIVISION_SIGNED = 19 + MODULO_SIGNED = 20 + LESS_SIGNED = 21 + GREATER_SIGNED = 22 + RIGHT_SHIFT_ARITHMETIC = 23 + @staticmethod def get_type(operation_type: "BinaryOperation"): if operation_type == "**": @@ -71,6 +77,16 @@ class BinaryOperationType(Enum): return BinaryOperationType.ANDAND if operation_type == "||": return BinaryOperationType.OROR + if operation_type == "/'": + return BinaryOperationType.DIVISION_SIGNED + if operation_type == "%'": + return BinaryOperationType.MODULO_SIGNED + if operation_type == "<'": + return BinaryOperationType.LESS_SIGNED + if operation_type == ">'": + return BinaryOperationType.GREATER_SIGNED + if operation_type == ">>'": + return BinaryOperationType.RIGHT_SHIFT_ARITHMETIC raise SlitherCoreError("get_type: Unknown operation type {})".format(operation_type)) @@ -113,6 +129,16 @@ class BinaryOperationType(Enum): return "&&" if self == BinaryOperationType.OROR: return "||" + if self == BinaryOperationType.DIVISION_SIGNED: + return "/'" + if self == BinaryOperationType.MODULO_SIGNED: + return "%'" + if self == BinaryOperationType.LESS_SIGNED: + return "<'" + if self == BinaryOperationType.GREATER_SIGNED: + return ">'" + if self == BinaryOperationType.RIGHT_SHIFT_ARITHMETIC: + return ">>'" raise SlitherCoreError("str: Unknown operation type {})".format(self)) diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index bf84dab7a..ceed61336 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -30,7 +30,7 @@ class Assembly(AbstractDetector): Returns: (bool) """ - return node.type == NodeType.ASSEMBLY + return node.type == NodeType.ASSEMBLY and len(node.yul_path) == 2 def detect_assembly(self, contract): ret = [] diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index a6ba49a17..7326fa0be 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -20,7 +20,7 @@ class PrinterSlithIR(AbstractPrinter): txt = "" for contract in self.contracts: - txt += 'Contract {}'.format(contract.name) + txt += 'Contract {}\n'.format(contract.name) for function in contract.functions: txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}\n' for node in function.nodes: diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c47739e95..ef8c02432 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -10,6 +10,7 @@ from slither.core.solidity_types import (ArrayType, ElementaryType, from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable +from slither.slithir.operations.codesize import CodeSize from slither.slithir.variables import TupleVariable from slither.slithir.operations import (Assignment, Balance, Binary, BinaryType, Call, Condition, Delete, @@ -512,6 +513,12 @@ def propagate_types(ir, node): b.set_expression(ir.expression) b.set_node(ir.node) return b + if ir.variable_right == 'codesize' and not isinstance(ir.variable_left, Contract) and isinstance( + ir.variable_left.type, ElementaryType): + b = CodeSize(ir.variable_left, ir.lvalue) + b.set_expression(ir.expression) + b.set_node(ir.node) + return b if ir.variable_right == 'selector' and isinstance(ir.variable_left.type, Function): assignment = Assignment(ir.lvalue, Constant(str(get_function_id(ir.variable_left.type.full_name))), diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index cd886197f..fce83aa6d 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -31,6 +31,12 @@ class BinaryType(Enum): ANDAND = 17 # && OROR = 18 # || + DIVISION_SIGNED = 19 + MODULO_SIGNED = 20 + LESS_SIGNED = 21 + GREATER_SIGNED = 22 + RIGHT_SHIFT_ARITHMETIC = 23 + @staticmethod def return_bool(operation_type): return operation_type in [BinaryType.OROR, @@ -82,6 +88,16 @@ class BinaryType(Enum): return BinaryType.ANDAND if operation_type == '||': return BinaryType.OROR + if operation_type == "/'": + return BinaryType.DIVISION_SIGNED + if operation_type == "%'": + return BinaryType.MODULO_SIGNED + if operation_type == "<'": + return BinaryType.LESS_SIGNED + if operation_type == ">'": + return BinaryType.GREATER_SIGNED + if operation_type == ">>'": + return BinaryType.RIGHT_SHIFT_ARITHMETIC raise SlithIRError('get_type: Unknown operation type {})'.format(operation_type)) @@ -124,6 +140,16 @@ class BinaryType(Enum): return "&&" if self == BinaryType.OROR: return "||" + if self == BinaryType.DIVISION_SIGNED: + return "/'" + if self == BinaryType.MODULO_SIGNED: + return "%'" + if self == BinaryType.LESS_SIGNED: + return "<'" + if self == BinaryType.GREATER_SIGNED: + return ">'" + if self == BinaryType.RIGHT_SHIFT_ARITHMETIC: + return ">>'" raise SlithIRError("str: Unknown operation type {} {})".format(self, type(self))) diff --git a/slither/slithir/operations/codesize.py b/slither/slithir/operations/codesize.py new file mode 100644 index 000000000..85289dc38 --- /dev/null +++ b/slither/slithir/operations/codesize.py @@ -0,0 +1,24 @@ +from slither.core.solidity_types import ElementaryType +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + + +class CodeSize(OperationWithLValue): + + def __init__(self, value, lvalue): + assert is_valid_rvalue(value) + assert is_valid_lvalue(lvalue) + self._value = value + self._lvalue = lvalue + lvalue.set_type(ElementaryType('uint256')) + + @property + def read(self): + return [self._value] + + @property + def value(self): + return self._value + + def __str__(self): + return "{} -> CODESIZE {}".format(self.lvalue, self.value) diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index e05069a59..277fb4ea7 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -17,6 +17,7 @@ from slither.slithir.operations import (Assignment, Balance, Binary, Condition, Push, Return, Send, SolidityCall, Transfer, TypeConversion, Unary, Unpack, Nop) +from slither.slithir.operations.codesize import CodeSize from slither.slithir.variables import (Constant, LocalIRVariable, ReferenceVariable, ReferenceVariableSSA, StateIRVariable, TemporaryVariable, @@ -527,6 +528,10 @@ def copy_ir(ir, *instances): variable_right = get_variable(ir, lambda x: x.variable_right, *instances) operation_type = ir.type return Binary(lvalue, variable_left, variable_right, operation_type) + elif isinstance(ir, CodeSize): + lvalue = get_variable(ir, lambda x: x.lvalue, *instances) + value = get_variable(ir, lambda x: x.value, *instances) + return CodeSize(value, lvalue) elif isinstance(ir, Condition): val = get_variable(ir, lambda x: x.value, *instances) return Condition(val) diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 2d2bec0f3..9ed4c950b 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -9,6 +9,7 @@ from slither.core.expressions.assignment_operation import ( from slither.core.expressions.identifier import Identifier from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.visitors.expression.find_calls import FindCalls +from slither.solc_parsing.yul.parse_yul import parse_yul from slither.visitors.expression.read_var import ReadVar from slither.visitors.expression.write_var import WriteVar @@ -17,15 +18,57 @@ class NodeSolc: def __init__(self, node: Node): self._unparsed_expression: Optional[Dict] = None self._node = node + self._unparsed_yul_expression = None + + """ + todo this should really go somewhere else, but until + that happens I'm setting it to None for performance + """ + self._yul_local_variables = None + self._yul_local_functions = None + self._yul_path = None @property def underlying_node(self) -> Node: return self._node + def set_yul_root(self, func): + self._yul_path = [func.name, f"asm_{func._counter_asm_nodes}"] + + def set_yul_child(self, parent, cur): + self._yul_path = parent.yul_path + [cur] + + @property + def yul_path(self): + return self._yul_path + + def format_canonical_yul_name(self, name, off=None): + return ".".join(self._yul_path[:off] + [name]) + + def add_yul_local_variable(self, var): + if not self._yul_local_variables: + self._yul_local_variables = [] + self._yul_local_variables.append(var) + + def get_yul_local_variable_from_name(self, variable_name): + return next((v for v in self._yul_local_variables if v.name == variable_name), None) + + def add_yul_local_function(self, func): + if not self._yul_local_functions: + self._yul_local_functions = [] + self._yul_local_functions.append(func) + + def get_yul_local_function_from_name(self, func_name): + return next((v for v in self._yul_local_functions if v.name == func_name), None) + def add_unparsed_expression(self, expression: Dict): assert self._unparsed_expression is None self._unparsed_expression = expression + def add_unparsed_yul_expression(self, root, expression): + assert self._unparsed_expression is None + self._unparsed_yul_expression = (root, expression) + def analyze_expressions(self, caller_context): if self._node.type == NodeType.VARIABLE and not self._node.expression: self._node.add_expression(self._node.variable_declaration.expression) @@ -34,6 +77,11 @@ class NodeSolc: self._node.add_expression(expression) # self._unparsed_expression = None + if self._unparsed_yul_expression: + expression = parse_yul(self._unparsed_yul_expression[0], self, self._unparsed_yul_expression[1]) + self._expression = expression + self._unparsed_yul_expression = None + if self._node.expression: if self._node.type == NodeType.VARIABLE: diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 4572c6766..0c0f4d66b 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -18,6 +18,7 @@ from slither.solc_parsing.variables.local_variable_init_from_tuple import ( LocalVariableInitFromTupleSolc, ) from slither.solc_parsing.variables.variable_declaration import MultipleVariablesDeclaration +from slither.solc_parsing.yul.parse_yul import convert_yul from slither.utils.expression_manipulations import SplitTernaryExpression from slither.visitors.expression.export_values import ExportValues from slither.visitors.expression.has_conditional import HasConditional @@ -62,6 +63,8 @@ class FunctionSolc: self._functionNotParsed = function_data self._params_was_analyzed = False self._content_was_analyzed = False + self._counter_nodes = 0 + self._counter_asm_nodes = 0 self._counter_scope_local_variables = 0 # variable renamed will map the solc id @@ -312,6 +315,9 @@ class FunctionSolc: self._node_to_nodesolc[node] = node_parser return node_parser + def node_solc(self): + return NodeSolc + # endregion ################################################################################### ################################################################################### @@ -797,13 +803,23 @@ class FunctionSolc: elif name == "Block": node = self._parse_block(statement, node) elif name == "InlineAssembly": - asm_node = self._new_node(NodeType.ASSEMBLY, statement["src"]) - self._function.contains_assembly = True - # Added with solc 0.4.12 - if "operations" in statement: - asm_node.underlying_node.add_inline_asm(statement["operations"]) - link_underlying_nodes(node, asm_node) - node = asm_node + # Added with solc 0.6 - the yul code is an AST + if 'AST' in statement: + self._contains_assembly = True + yul_root = self._new_node(NodeType.ASSEMBLY, statement['src']) + yul_root.set_yul_root(self) + link_underlying_nodes(node, yul_root) + self._counter_asm_nodes += 1 + + node = convert_yul(yul_root, yul_root, statement['AST']) + else: + asm_node = self._new_node(NodeType.ASSEMBLY, statement['src']) + self._function._contains_assembly = True + # Added with solc 0.4.12 + if 'operations' in statement: + asm_node.underlying_node.add_inline_asm(statement['operations']) + link_underlying_nodes(node, asm_node) + node = asm_node elif name == "DoWhileStatement": node = self._parse_dowhile(statement, node) # For Continue / Break / Return / Throw diff --git a/slither/solc_parsing/yul/__init__.py b/slither/solc_parsing/yul/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/solc_parsing/yul/evm_functions.py b/slither/solc_parsing/yul/evm_functions.py new file mode 100644 index 000000000..aa237428b --- /dev/null +++ b/slither/solc_parsing/yul/evm_functions.py @@ -0,0 +1,268 @@ +from slither.core.declarations.solidity_variables import SOLIDITY_FUNCTIONS +from slither.core.expressions import BinaryOperationType, UnaryOperationType + +# taken from https://github.com/ethereum/solidity/blob/356cc91084114f840da66804b2a9fc1ac2846cff/libevmasm/Instruction.cpp#L180 +evm_opcodes = [ + "STOP", + "ADD", + "SUB", + "MUL", + "DIV", + "SDIV", + "MOD", + "SMOD", + "EXP", + "NOT", + "LT", + "GT", + "SLT", + "SGT", + "EQ", + "ISZERO", + "AND", + "OR", + "XOR", + "BYTE", + "SHL", + "SHR", + "SAR", + "ADDMOD", + "MULMOD", + "SIGNEXTEND", + "KECCAK256", + "ADDRESS", + "BALANCE", + "ORIGIN", + "CALLER", + "CALLVALUE", + "CALLDATALOAD", + "CALLDATASIZE", + "CALLDATACOPY", + "CODESIZE", + "CODECOPY", + "GASPRICE", + "EXTCODESIZE", + "EXTCODECOPY", + "RETURNDATASIZE", + "RETURNDATACOPY", + "EXTCODEHASH", + "BLOCKHASH", + "COINBASE", + "TIMESTAMP", + "NUMBER", + "DIFFICULTY", + "GASLIMIT", + "CHAINID", + "SELFBALANCE", + "POP", + "MLOAD", + "MSTORE", + "MSTORE8", + "SLOAD", + "SSTORE", + "JUMP", + "JUMPI", + "PC", + "MSIZE", + "GAS", + "JUMPDEST", + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "PUSH6", + "PUSH7", + "PUSH8", + "PUSH9", + "PUSH10", + "PUSH11", + "PUSH12", + "PUSH13", + "PUSH14", + "PUSH15", + "PUSH16", + "PUSH17", + "PUSH18", + "PUSH19", + "PUSH20", + "PUSH21", + "PUSH22", + "PUSH23", + "PUSH24", + "PUSH25", + "PUSH26", + "PUSH27", + "PUSH28", + "PUSH29", + "PUSH30", + "PUSH31", + "PUSH32", + "DUP1", + "DUP2", + "DUP3", + "DUP4", + "DUP5", + "DUP6", + "DUP7", + "DUP8", + "DUP9", + "DUP10", + "DUP11", + "DUP12", + "DUP13", + "DUP14", + "DUP15", + "DUP16", + "SWAP1", + "SWAP2", + "SWAP3", + "SWAP4", + "SWAP5", + "SWAP6", + "SWAP7", + "SWAP8", + "SWAP9", + "SWAP10", + "SWAP11", + "SWAP12", + "SWAP13", + "SWAP14", + "SWAP15", + "SWAP16", + "LOG0", + "LOG1", + "LOG2", + "LOG3", + "LOG4", + "CREATE", + "CALL", + "CALLCODE", + "STATICCALL", + "RETURN", + "DELEGATECALL", + "CREATE2", + "REVERT", + "INVALID", + "SELFDESTRUCT", +] + +yul_funcs = [ + "datasize", + "dataoffset", + "datacopy", + "setimmutable", + "loadimmutable", +] + +builtins = [x.lower() for x in evm_opcodes if not ( + x.startswith("PUSH") or + x.startswith("SWAP") or + x.startswith("DUP") or + x == "JUMP" or + x == "JUMPI" or + x == "JUMPDEST" +)] + yul_funcs + +function_args = { + 'byte': [2, 1], + 'addmod': [3, 1], + 'mulmod': [3, 1], + 'signextend': [2, 1], + 'keccak256': [2, 1], + 'pc': [0, 1], + 'pop': [1, 0], + 'mload': [1, 1], + 'mstore': [2, 0], + 'mstore8': [2, 0], + 'sload': [1, 1], + 'sstore': [2, 0], + 'msize': [1, 1], + 'gas': [0, 1], + 'address': [0, 1], + 'balance': [1, 1], + 'selfbalance': [0, 1], + 'caller': [0, 1], + 'callvalue': [0, 1], + 'calldataload': [1, 1], + 'calldatasize': [0, 1], + 'calldatacopy': [3, 0], + 'codesize': [0, 1], + 'codecopy': [3, 0], + 'extcodesize': [1, 1], + 'extcodecopy': [4, 0], + 'returndatasize': [0, 1], + 'returndatacopy': [3, 0], + 'extcodehash': [1, 1], + 'create': [3, 1], + 'create2': [4, 1], + 'call': [7, 1], + 'callcode': [7, 1], + 'delegatecall': [6, 1], + 'staticcall': [6, 1], + 'return': [2, 0], + 'revert': [2, 0], + 'selfdestruct': [1, 0], + 'invalid': [0, 0], + 'log0': [2, 0], + 'log1': [3, 0], + 'log2': [4, 0], + 'log3': [5, 0], + 'log4': [6, 0], + 'chainid': [0, 1], + 'origin': [0, 1], + 'gasprice': [0, 1], + 'blockhash': [1, 1], + 'coinbase': [0, 1], + 'timestamp': [0, 1], + 'number': [0, 1], + 'difficulty': [0, 1], + 'gaslimit': [0, 1], +} + + +def format_function_descriptor(name): + if name not in function_args: + return name + "()" + + return name + "(" + ",".join(["uint256"] * function_args[name][0]) + ")" + + +for k, v in function_args.items(): + SOLIDITY_FUNCTIONS[format_function_descriptor(k)] = ['uint256'] * v[1] + +unary_ops = { + 'not': UnaryOperationType.TILD, + 'iszero': UnaryOperationType.BANG, +} + +binary_ops = { + 'add': BinaryOperationType.ADDITION, + 'sub': BinaryOperationType.SUBTRACTION, + 'mul': BinaryOperationType.MULTIPLICATION, + 'div': BinaryOperationType.DIVISION, + 'sdiv': BinaryOperationType.DIVISION_SIGNED, + 'mod': BinaryOperationType.MODULO, + 'smod': BinaryOperationType.MODULO_SIGNED, + 'exp': BinaryOperationType.POWER, + 'lt': BinaryOperationType.LESS, + 'gt': BinaryOperationType.GREATER, + 'slt': BinaryOperationType.LESS_SIGNED, + 'sgt': BinaryOperationType.GREATER_SIGNED, + 'eq': BinaryOperationType.EQUAL, + 'and': BinaryOperationType.AND, + 'or': BinaryOperationType.OR, + 'xor': BinaryOperationType.CARET, + 'shl': BinaryOperationType.LEFT_SHIFT, + 'shr': BinaryOperationType.RIGHT_SHIFT, + 'sar': BinaryOperationType.RIGHT_SHIFT_ARITHMETIC, +} + + +class YulBuiltin: + def __init__(self, name): + self._name = name + + @property + def name(self): + return self._name diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py new file mode 100644 index 000000000..4552a57c8 --- /dev/null +++ b/slither/solc_parsing/yul/parse_yul.py @@ -0,0 +1,525 @@ +import json + +from slither.core.cfg.node import NodeType, link_nodes +from slither.core.declarations import Function, SolidityFunction, SolidityVariable +from slither.core.expressions import ( + Literal, + AssignmentOperation, + AssignmentOperationType, + Identifier, CallExpression, TupleExpression, BinaryOperation, UnaryOperation, +) +from slither.core.solidity_types import ElementaryType +from slither.core.variables.local_variable import LocalVariable +from slither.exceptions import SlitherException +from slither.solc_parsing.yul.evm_functions import * + + +class YulLocalVariable(LocalVariable): + + def __init__(self, ast): + super(LocalVariable, self).__init__() + + assert (ast['nodeType'] == 'YulTypedName') + self._name = ast['name'] + self._type = ElementaryType('uint256') + + self._location = 'memory' + + +class YulFunction(Function): + + def __init__(self, ast, root): + super(YulFunction, self).__init__() + + assert (ast['nodeType'] == 'YulFunctionDefinition') + + self._contract = root.function.contract + self._contract_declarer = root.function.contract_declarer + + self._name = ast['name'] + self._scope = root.yul_path + self._counter_nodes = 0 + + self._is_implemented = True + self._contains_assembly = True + + self._node_solc = root.function.node_solc() + + self._entry_point = self.new_node(NodeType.ASSEMBLY, ast['src']) + self._entry_point.set_yul_child(root, ast['name']) + + self._ast = ast + self.set_offset(ast['src'], root.function.slither) + + def convert_body(self): + node = self.new_node(NodeType.ENTRYPOINT, self._ast['src']) + link_nodes(self.entry_point, node) + + for param in self._ast.get('parameters', []): + node = convert_yul(self.entry_point, node, param) + self._parameters.append(self.entry_point.get_yul_local_variable_from_name(param['name'])) + + for ret in self._ast.get('returnVariables', []): + node = convert_yul(self.entry_point, node, ret) + self._returns.append(self.entry_point.get_yul_local_variable_from_name(ret['name'])) + + convert_yul(self.entry_point, node, self._ast['body']) + + def parse_body(self): + for node in self.nodes: + node.analyze_expressions(self) + + def node_solc(self): + return self._node_solc + + def new_node(self, node_type, src): + node = self._node_solc(node_type, self._counter_nodes) + node.set_offset(src, self.slither) + node.set_function(self) + self._counter_nodes += 1 + self._nodes.append(node) + return node + + +################################################################################### +################################################################################### +# region Block conversion +################################################################################### +################################################################################### + +""" +The functions in this region, at a high level, will extract the control flow +structures and metadata from the input AST. These include things like function +definitions and local variables. + +Each function takes three parameters: + 1) root is a NodeSolc of NodeType.ASSEMBLY, and stores information at the + local scope. In Yul, variables are scoped to the function they're + declared in (except for variables outside the assembly block) + 2) parent is the last node in the CFG. new nodes should be linked against + this node + 3) ast is a dictionary and is the current node in the Yul ast being converted + +Each function must return a single parameter: + 1) A NodeSolc representing the new end of the CFG + +The entrypoint is the function at the end of this region, `convert_yul`, which +dispatches to a specialized function based on a lookup dictionary. +""" + + +def convert_yul_block(root, parent, ast): + for statement in ast["statements"]: + parent = convert_yul(root, parent, statement) + return parent + + +def convert_yul_function_definition(root, parent, ast): + f = YulFunction(ast, root) + + root.function.contract._functions[root.format_canonical_yul_name(f.name)] = f + + f.convert_body() + f.parse_body() + + return parent + + +def convert_yul_variable_declaration(root, parent, ast): + for variable_ast in ast['variables']: + parent = convert_yul(root, parent, variable_ast) + + node = parent.function.new_node(NodeType.EXPRESSION, ast["src"]) + node.add_unparsed_yul_expression(root, ast) + link_nodes(parent, node) + + return node + + +def convert_yul_assignment(root, parent, ast): + node = parent.function.new_node(NodeType.EXPRESSION, ast["src"]) + node.add_unparsed_yul_expression(root, ast) + link_nodes(parent, node) + return node + + +def convert_yul_expression_statement(root, parent, ast): + src = ast['src'] + expression_ast = ast['expression'] + + expression = parent.function.new_node(NodeType.EXPRESSION, src) + expression.add_unparsed_yul_expression(root, expression_ast) + link_nodes(parent, expression) + + return expression + + +def convert_yul_if(root, parent, ast): + # we're cheating and pretending that yul supports if/else so we can convert switch cleaner + + src = ast['src'] + condition_ast = ast['condition'] + true_body_ast = ast['body'] + false_body_ast = ast['false_body'] if 'false_body' in ast else None + + condition = parent.function.new_node(NodeType.IF, src) + end = parent.function.new_node(NodeType.ENDIF, src) + + condition.add_unparsed_yul_expression(root, condition_ast) + + true_body = convert_yul(root, condition, true_body_ast) + + if false_body_ast: + false_body = convert_yul(root, condition, false_body_ast) + link_nodes(false_body, end) + else: + link_nodes(condition, end) + + link_nodes(parent, condition) + link_nodes(true_body, end) + + return end + + +def convert_yul_switch(root, parent, ast): + """ + This is unfortunate. We don't really want a switch in our IR so we're going to + translate it into a series of if/else statements. + """ + cases_ast = ast['cases'] + expression_ast = ast['expression'] + + # this variable stores the result of the expression so we don't accidentally compute it more than once + switch_expr_var = 'switch_expr_{}'.format(ast['src'].replace(':', '_')) + + rewritten_switch = { + 'nodeType': 'YulBlock', + 'src': ast['src'], + 'statements': [ + { + 'nodeType': 'YulVariableDeclaration', + 'src': expression_ast['src'], + 'variables': [ + { + 'nodeType': 'YulTypedName', + 'src': expression_ast['src'], + 'name': switch_expr_var, + 'type': '', + }, + ], + 'value': expression_ast, + }, + ], + } + + last_if = None + + default_ast = None + + for case_ast in cases_ast: + body_ast = case_ast['body'] + value_ast = case_ast['value'] + + if value_ast == 'default': + default_ast = case_ast + continue + + current_if = { + 'nodeType': 'YulIf', + 'src': case_ast['src'], + 'condition': { + 'nodeType': 'YulFunctionCall', + 'src': case_ast['src'], + 'functionName': { + 'nodeType': 'YulIdentifier', + 'src': case_ast['src'], + 'name': 'eq', + }, + 'arguments': [ + { + 'nodeType': 'YulIdentifier', + 'src': case_ast['src'], + 'name': switch_expr_var, + }, + value_ast, + ] + }, + 'body': body_ast, + } + + if last_if: + last_if['false_body'] = current_if + else: + rewritten_switch['statements'].append(current_if) + + last_if = current_if + + if default_ast: + body_ast = default_ast['body'] + + if last_if: + last_if['false_body'] = body_ast + else: + rewritten_switch['statements'].append(body_ast) + + return convert_yul(root, parent, rewritten_switch) + + +def convert_yul_for_loop(root, parent, ast): + pre_ast = ast['pre'] + condition_ast = ast['condition'] + post_ast = ast['post'] + body_ast = ast['body'] + + start_loop = parent.function.new_node(NodeType.STARTLOOP, ast['src']) + end_loop = parent.function.new_node(NodeType.ENDLOOP, ast['src']) + + link_nodes(parent, start_loop) + + pre = convert_yul(root, start_loop, pre_ast) + + condition = parent.function.new_node(NodeType.IFLOOP, condition_ast['src']) + condition.add_unparsed_yul_expression(root, condition_ast) + link_nodes(pre, condition) + + link_nodes(condition, end_loop) + + body = convert_yul(root, condition, body_ast) + + post = convert_yul(root, body, post_ast) + + link_nodes(post, condition) + + return end_loop + + +def convert_yul_break(root, parent, ast): + break_ = parent.function.new_node(NodeType.BREAK, ast['src']) + link_nodes(parent, break_) + return break_ + + +def convert_yul_continue(root, parent, ast): + continue_ = parent.function.new_node(NodeType.CONTINUE, ast['src']) + link_nodes(parent, continue_) + return continue_ + + +def convert_yul_leave(root, parent, ast): + leave = parent.function.new_node(NodeType.RETURN, ast['src']) + link_nodes(parent, leave) + return leave + + +def convert_yul_typed_name(root, parent, ast): + var = YulLocalVariable(ast) + var.set_function(root.function) + var.set_offset(ast['src'], root.slither) + + root.add_yul_local_variable(var) + + node = parent.function.new_node(NodeType.VARIABLE, ast['src']) + node.add_variable_declaration(var) + link_nodes(parent, node) + + return node + + +def convert_yul_unsupported(root, parent, ast): + raise SlitherException(f"no converter available for {ast['nodeType']} {json.dumps(ast, indent=2)}") + + +def convert_yul(root, parent, ast): + return converters.get(ast['nodeType'], convert_yul_unsupported)(root, parent, ast) + + +converters = { + 'YulBlock': convert_yul_block, + 'YulFunctionDefinition': convert_yul_function_definition, + 'YulVariableDeclaration': convert_yul_variable_declaration, + 'YulAssignment': convert_yul_assignment, + 'YulExpressionStatement': convert_yul_expression_statement, + 'YulIf': convert_yul_if, + 'YulSwitch': convert_yul_switch, + 'YulForLoop': convert_yul_for_loop, + 'YulBreak': convert_yul_break, + 'YulContinue': convert_yul_continue, + 'YulLeave': convert_yul_leave, + 'YulTypedName': convert_yul_typed_name, +} + +# endregion +################################################################################### +################################################################################### + +################################################################################### +################################################################################### +# region Expression parsing +################################################################################### +################################################################################### + +""" +The functions in this region parse the AST into expressions. + +Each function takes three parameters: + 1) root is the same root as above + 2) node is the CFG node which stores this expression + 3) ast is the same ast as above + +Each function must return a single parameter: + 1) The operation that was parsed, or None + +The entrypoint is the function at the end of this region, `parse_yul`, which +dispatches to a specialized function based on a lookup dictionary. +""" + + +def _parse_yul_assignment_common(root, node, ast, key): + lhs = [parse_yul(root, node, arg) for arg in ast[key]] + rhs = parse_yul(root, node, ast['value']) + + return AssignmentOperation(vars_to_val(lhs), rhs, AssignmentOperationType.ASSIGN, vars_to_typestr(lhs)) + + +def parse_yul_variable_declaration(root, node, ast): + """ + We already created variables in the conversion phase, so just do + the assignment + """ + + if not ast['value']: + return None + + return _parse_yul_assignment_common(root, node, ast, 'variables') + + +def parse_yul_assignment(root, node, ast): + return _parse_yul_assignment_common(root, node, ast, 'variableNames') + + +def parse_yul_function_call(root, node, ast): + args = [parse_yul(root, node, arg) for arg in ast['arguments']] + ident = parse_yul(root, node, ast['functionName']) + + if isinstance(ident.value, YulBuiltin): + name = ident.value.name + if name in binary_ops: + if name in ['shl', 'shr', 'sar']: + # lmao ok + return BinaryOperation(args[1], args[0], binary_ops[name]) + + return BinaryOperation(args[0], args[1], binary_ops[name]) + + if name in unary_ops: + return UnaryOperation(args[0], unary_ops[name]) + + ident = Identifier(SolidityFunction(format_function_descriptor(ident.value.name))) + + if isinstance(ident.value, Function): + return CallExpression(ident, args, vars_to_typestr(ident.value.returns)) + elif isinstance(ident.value, SolidityFunction): + return CallExpression(ident, args, vars_to_typestr(ident.value.return_type)) + else: + raise SlitherException(f"unexpected function call target type {str(type(ident.value))}") + + +def parse_yul_identifier(root, node, ast): + name = ast['name'] + + if name in builtins: + return Identifier(YulBuiltin(name)) + + # check function-scoped variables + variable = root.function.get_local_variable_from_name(name) + if variable: + return Identifier(variable) + + # check yul-scoped variable + variable = root.get_yul_local_variable_from_name(name) + if variable: + return Identifier(variable) + + # check yul-scoped function + # note that a function can recurse into itself, so we have two canonical names + # to check (but only one of them can be valid) + + functions = root.function.contract_declarer._functions + + canonical_name = root.format_canonical_yul_name(name) + if canonical_name in functions: + return Identifier(functions[canonical_name]) + + canonical_name = root.format_canonical_yul_name(name, -1) + if canonical_name in functions: + return Identifier(functions[canonical_name]) + + # check for magic suffixes + if name.endswith("_slot"): + potential_name = name[:-5] + var = root.function.contract.get_state_variable_from_name(potential_name) + if var: + return Identifier(SolidityVariable(name)) + if name.endswith("_offset"): + potential_name = name[:-7] + var = root.function.contract.get_state_variable_from_name(potential_name) + if var: + return Identifier(SolidityVariable(name)) + + raise SlitherException(f"unresolved reference to identifier {name}") + + +def parse_yul_literal(root, node, ast): + type_ = ast['type'] + value = ast['value'] + + if not type_: + type_ = 'bool' if value in ['true', 'false'] else 'uint256' + + return Literal(value, ElementaryType(type_)) + + +def parse_yul_typed_name(root, node, ast): + var = root.get_yul_local_variable_from_name(ast['name']) + + i = Identifier(var) + i._type = var.type + return i + + +def parse_yul_unsupported(root, node, ast): + raise SlitherException(f"no parser available for {ast['nodeType']} {json.dumps(ast, indent=2)}") + + +def parse_yul(root, node, ast): + op = parsers.get(ast['nodeType'], parse_yul_unsupported)(root, node, ast) + if op: + op.set_offset(ast["src"], root.slither) + return op + + +parsers = { + 'YulVariableDeclaration': parse_yul_variable_declaration, + 'YulAssignment': parse_yul_assignment, + 'YulFunctionCall': parse_yul_function_call, + 'YulIdentifier': parse_yul_identifier, + 'YulTypedName': parse_yul_typed_name, + 'YulLiteral': parse_yul_literal, +} + + +# endregion +################################################################################### +################################################################################### + +def vars_to_typestr(rets): + if len(rets) == 0: + return "" + if len(rets) == 1: + return str(rets[0].type) + return "tuple({})".format(",".join(str(ret.type) for ret in rets)) + + +def vars_to_val(vars): + if len(vars) == 1: + return vars[0] + return TupleExpression(vars) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 69fb2a980..c215084b7 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -1,9 +1,9 @@ import logging -from slither.core.declarations import Function +from slither.core.declarations import Function, SolidityVariable, SolidityVariableComposed from slither.core.expressions import (AssignmentOperationType, UnaryOperationType, BinaryOperationType) -from slither.core.solidity_types import ArrayType +from slither.core.solidity_types import ArrayType, ElementaryType from slither.core.solidity_types.type import Type from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete, Index, InitArray, InternalCall, Member, @@ -167,27 +167,63 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(internal_call) set_val(expression, val) else: - # If tuple - if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': - val = TupleVariable(self._node) - else: + # yul things + if called.name == 'caller()': val = TemporaryVariable(self._node) - - message_call = TmpCall(called, len(args), val, expression.type_call) - message_call.set_expression(expression) - # Gas/value are only accessible here if the syntax {gas: , value: } - # Is used over .gas().value() - if expression.call_gas: - call_gas = get(expression.call_gas) - message_call.call_gas = call_gas - if expression.call_value: - call_value = get(expression.call_value) - message_call.call_value = call_value - if expression.call_salt: - call_salt = get(expression.call_salt) - message_call.call_salt = call_salt - self._result.append(message_call) - set_val(expression, val) + var = Assignment(val, SolidityVariableComposed('msg.sender'), 'uint256') + self._result.append(var) + set_val(expression, val) + elif called.name == 'origin()': + val = TemporaryVariable(self._node) + var = Assignment(val, SolidityVariableComposed('tx.origin'), 'uint256') + self._result.append(var) + set_val(expression, val) + elif called.name == 'extcodesize(uint256)': + val = ReferenceVariable(self._node) + var = Member(args[0], Constant('codesize'), val) + self._result.append(var) + set_val(expression, val) + elif called.name == 'selfbalance()': + val = TemporaryVariable(self._node) + var = TypeConversion(val, SolidityVariable('this'), ElementaryType('address')) + self._result.append(var) + + val1 = ReferenceVariable(self._node) + var1 = Member(val, Constant('balance'), val1) + self._result.append(var1) + set_val(expression, val1) + elif called.name == 'address()': + val = TemporaryVariable(self._node) + var = TypeConversion(val, SolidityVariable('this'), ElementaryType('address')) + self._result.append(var) + set_val(expression, val) + elif called.name == 'callvalue()': + val = TemporaryVariable(self._node) + var = Assignment(val, SolidityVariableComposed('msg.value'), 'uint256') + self._result.append(var) + set_val(expression, val) + else: + # If tuple + if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': + val = TupleVariable(self._node) + else: + val = TemporaryVariable(self._node) + + message_call = TmpCall(called, len(args), val, expression.type_call) + message_call.set_expression(expression) + # Gas/value are only accessible here if the syntax {gas: , value: } + # Is used over .gas().value() + if expression.call_gas: + call_gas = get(expression.call_gas) + message_call.call_gas = call_gas + if expression.call_value: + call_value = get(expression.call_value) + message_call.call_value = call_value + if expression.call_salt: + call_salt = get(expression.call_salt) + message_call.call_salt = call_salt + self._result.append(message_call) + set_val(expression, val) def _post_conditional_expression(self, expression): raise Exception('Ternary operator are not convertible to SlithIR {}'.format(expression)) From 1bf449bec876f22c499bcae48a711764b66290f1 Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 23 Jun 2020 14:16:30 -0400 Subject: [PATCH 02/73] rewrite to handle new parser layout --- slither/solc_parsing/cfg/node.py | 47 -- slither/solc_parsing/declarations/function.py | 29 +- slither/solc_parsing/yul/parse_yul.py | 403 ++++++++++++------ .../visitors/slithir/expression_to_slithir.py | 5 + 4 files changed, 305 insertions(+), 179 deletions(-) diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 9ed4c950b..88ffd75fe 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -18,57 +18,15 @@ class NodeSolc: def __init__(self, node: Node): self._unparsed_expression: Optional[Dict] = None self._node = node - self._unparsed_yul_expression = None - - """ - todo this should really go somewhere else, but until - that happens I'm setting it to None for performance - """ - self._yul_local_variables = None - self._yul_local_functions = None - self._yul_path = None @property def underlying_node(self) -> Node: return self._node - def set_yul_root(self, func): - self._yul_path = [func.name, f"asm_{func._counter_asm_nodes}"] - - def set_yul_child(self, parent, cur): - self._yul_path = parent.yul_path + [cur] - - @property - def yul_path(self): - return self._yul_path - - def format_canonical_yul_name(self, name, off=None): - return ".".join(self._yul_path[:off] + [name]) - - def add_yul_local_variable(self, var): - if not self._yul_local_variables: - self._yul_local_variables = [] - self._yul_local_variables.append(var) - - def get_yul_local_variable_from_name(self, variable_name): - return next((v for v in self._yul_local_variables if v.name == variable_name), None) - - def add_yul_local_function(self, func): - if not self._yul_local_functions: - self._yul_local_functions = [] - self._yul_local_functions.append(func) - - def get_yul_local_function_from_name(self, func_name): - return next((v for v in self._yul_local_functions if v.name == func_name), None) - def add_unparsed_expression(self, expression: Dict): assert self._unparsed_expression is None self._unparsed_expression = expression - def add_unparsed_yul_expression(self, root, expression): - assert self._unparsed_expression is None - self._unparsed_yul_expression = (root, expression) - def analyze_expressions(self, caller_context): if self._node.type == NodeType.VARIABLE and not self._node.expression: self._node.add_expression(self._node.variable_declaration.expression) @@ -77,11 +35,6 @@ class NodeSolc: self._node.add_expression(expression) # self._unparsed_expression = None - if self._unparsed_yul_expression: - expression = parse_yul(self._unparsed_yul_expression[0], self, self._unparsed_yul_expression[1]) - self._expression = expression - self._unparsed_yul_expression = None - if self._node.expression: if self._node.type == NodeType.VARIABLE: diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 0c0f4d66b..d54b63f52 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -18,7 +18,7 @@ from slither.solc_parsing.variables.local_variable_init_from_tuple import ( LocalVariableInitFromTupleSolc, ) from slither.solc_parsing.variables.variable_declaration import MultipleVariablesDeclaration -from slither.solc_parsing.yul.parse_yul import convert_yul +from slither.solc_parsing.yul.parse_yul import YulObject from slither.utils.expression_manipulations import SplitTernaryExpression from slither.visitors.expression.export_values import ExportValues from slither.visitors.expression.has_conditional import HasConditional @@ -63,8 +63,6 @@ class FunctionSolc: self._functionNotParsed = function_data self._params_was_analyzed = False self._content_was_analyzed = False - self._counter_nodes = 0 - self._counter_asm_nodes = 0 self._counter_scope_local_variables = 0 # variable renamed will map the solc id @@ -83,6 +81,7 @@ class FunctionSolc: self.returns_src = SourceMapping() self._node_to_nodesolc: Dict[Node, NodeSolc] = dict() + self._node_to_yulobject: Dict[Node, YulObject] = dict() self._local_variables_parser: List[ Union[LocalVariableSolc, LocalVariableInitFromTupleSolc] @@ -298,6 +297,9 @@ class FunctionSolc: for node_parser in self._node_to_nodesolc.values(): node_parser.analyze_expressions(self) + for node_parser in self._node_to_yulobject.values(): + node_parser.analyze_expressions() + self._filter_ternary() self._remove_alone_endif() @@ -315,8 +317,11 @@ class FunctionSolc: self._node_to_nodesolc[node] = node_parser return node_parser - def node_solc(self): - return NodeSolc + def _new_yul_object(self, src: Union[str, Dict]) -> YulObject: + node = self._function.new_node(NodeType.ASSEMBLY, src) + yul_object = YulObject(self._function.contract, node, [self._function.name, f"asm_{len(self._node_to_yulobject)}"], parent_func=self._function) + self._node_to_yulobject[node] = yul_object + return yul_object # endregion ################################################################################### @@ -806,12 +811,14 @@ class FunctionSolc: # Added with solc 0.6 - the yul code is an AST if 'AST' in statement: self._contains_assembly = True - yul_root = self._new_node(NodeType.ASSEMBLY, statement['src']) - yul_root.set_yul_root(self) - link_underlying_nodes(node, yul_root) - self._counter_asm_nodes += 1 - - node = convert_yul(yul_root, yul_root, statement['AST']) + yul_object = self._new_yul_object(statement['src']) + entrypoint = yul_object.entrypoint + exitpoint = yul_object.convert(statement['AST']) + + # technically, entrypoint and exitpoint are YulNodes and we should be returning a NodeSolc here + # but they both expose an underlying_node so oh well + link_underlying_nodes(node, entrypoint) + node = exitpoint else: asm_node = self._new_node(NodeType.ASSEMBLY, statement['src']) self._function._contains_assembly = True diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 4552a57c8..e4c6662d2 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -1,84 +1,249 @@ +import abc import json +from typing import Optional, Dict, Tuple, List, Union -from slither.core.cfg.node import NodeType, link_nodes -from slither.core.declarations import Function, SolidityFunction, SolidityVariable +from slither.core.cfg.node import NodeType, Node, link_nodes +from slither.core.declarations import Function, SolidityFunction, SolidityVariable, Contract from slither.core.expressions import ( Literal, AssignmentOperation, AssignmentOperationType, Identifier, CallExpression, TupleExpression, BinaryOperation, UnaryOperation, ) +from slither.core.expressions.expression import Expression +from slither.core.slither_core import SlitherCore from slither.core.solidity_types import ElementaryType from slither.core.variables.local_variable import LocalVariable from slither.exceptions import SlitherException from slither.solc_parsing.yul.evm_functions import * +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 YulNode: + def __init__(self, node: Node): + self._unparsed_expression: Optional[Tuple[YulScope, Dict]] = None + self._node = node + @property + def underlying_node(self) -> Node: + return self._node -class YulLocalVariable(LocalVariable): + def add_unparsed_yul_expression(self, root: 'YulScope', expression: Dict): + assert self._unparsed_expression is None + self._unparsed_expression = (root, expression) - def __init__(self, ast): - super(LocalVariable, self).__init__() + def analyze_expressions(self, caller_context): + 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_yul(self._unparsed_expression[0], self, self._unparsed_expression[1]) + self._node.add_expression(expression) + 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.slither) + 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) + ] + + +def link_underlying_nodes(node1: YulNode, node2: YulNode): + link_nodes(node1.underlying_node, node2.underlying_node) + + +class YulScope(metaclass=abc.ABCMeta): + __slots__ = ["_contract", "_id", "_yul_local_variables", "_yul_local_functions", "_parent_func"] + + def __init__(self, contract: Contract, id: List[str], parent_func: Function = None): + self._contract = contract + self._id: List[str] = id + self._yul_local_variables: List[YulLocalVariable] = [] + self._yul_local_functions: List[YulFunction] = [] + self._parent_func = parent_func + + @property + def id(self) -> List[str]: + return self._id + + @property + def contract(self) -> Contract: + return self._contract + + @property + def slither(self) -> SlitherCore: + return self._contract.slither + + @property + def parent_func(self) -> Optional[Function]: + return self._parent_func + + @property + @abc.abstractmethod + def function(self) -> Function: + pass + + @abc.abstractmethod + def new_node(self, node_type: NodeType, src: Union[str, Dict]) -> YulNode: + pass + + def add_yul_local_variable(self, var): + self._yul_local_variables.append(var) + + def get_yul_local_variable_from_name(self, variable_name): + return next((v for v in self._yul_local_variables if v.underlying.name == variable_name), None) + + def add_yul_local_function(self, func): + self._yul_local_functions.append(func) + + def get_yul_local_function_from_name(self, func_name): + return next((v for v in self._yul_local_functions if v.underlying.name == func_name), None) + + +class YulLocalVariable: + __slots__ = ["_variable", "_root"] + + def __init__(self, var: LocalVariable, root: YulScope, ast: Dict): assert (ast['nodeType'] == 'YulTypedName') - self._name = ast['name'] - self._type = ElementaryType('uint256') - self._location = 'memory' + self._variable = var + self._root = root + + # start initializing the underlying variable + var.set_function(root.function) + var.set_offset(ast["src"], root.slither) + + var.name = ast['name'] + var.set_type(ElementaryType('uint256')) + var.set_location('memory') + @property + def underlying(self) -> LocalVariable: + return self._variable -class YulFunction(Function): - def __init__(self, ast, root): - super(YulFunction, self).__init__() +class YulFunction(YulScope): + __slots__ = ["_function", "_root", "_ast", "_nodes", "_entrypoint"] + + def __init__(self, func: Function, root: YulScope, ast: Dict, **kwargs): + super().__init__(root.contract, root.id + [ast['name']], parent_func=root.parent_func) assert (ast['nodeType'] == 'YulFunctionDefinition') - self._contract = root.function.contract - self._contract_declarer = root.function.contract_declarer + self._function: Function = func + self._root: YulScope = root + self._ast: Dict = ast + + # start initializing the underlying function + + func.name = ast['name'] + func.set_visibility('private') + func.set_offset(ast["src"], root.slither) + func.set_contract(root.contract) + func.set_contract_declarer(root.contract) + func._scope = root.id - self._name = ast['name'] - self._scope = root.yul_path - self._counter_nodes = 0 + self._nodes = [] + self._entrypoint = self.new_node(NodeType.ASSEMBLY, ast['src']) - self._is_implemented = True - self._contains_assembly = True + self.add_yul_local_function(self) - self._node_solc = root.function.node_solc() + @property + def underlying(self) -> Function: + return self._function - self._entry_point = self.new_node(NodeType.ASSEMBLY, ast['src']) - self._entry_point.set_yul_child(root, ast['name']) + @property + def slither(self) -> SlitherCore: + return self._entrypoint.underlying_node.slither - self._ast = ast - self.set_offset(ast['src'], root.function.slither) + @property + def function(self) -> Function: + return self._function def convert_body(self): node = self.new_node(NodeType.ENTRYPOINT, self._ast['src']) - link_nodes(self.entry_point, node) + link_underlying_nodes(self._entrypoint, node) for param in self._ast.get('parameters', []): - node = convert_yul(self.entry_point, node, param) - self._parameters.append(self.entry_point.get_yul_local_variable_from_name(param['name'])) + node = convert_yul(self, node, param) + self._function.add_parameters(self.get_yul_local_variable_from_name(param['name']).underlying) for ret in self._ast.get('returnVariables', []): - node = convert_yul(self.entry_point, node, ret) - self._returns.append(self.entry_point.get_yul_local_variable_from_name(ret['name'])) + node = convert_yul(self, node, ret) + self._function.add_return(self.get_yul_local_variable_from_name(ret['name']).underlying) - convert_yul(self.entry_point, node, self._ast['body']) + convert_yul(self, node, self._ast['body']) def parse_body(self): - for node in self.nodes: + for node in self._nodes: node.analyze_expressions(self) - def node_solc(self): - return self._node_solc + def new_node(self, node_type, src) -> YulNode: + if self._function: + node = self._function.new_node(node_type, src) + else: + raise SlitherException("standalone yul objects are not supported yet") + + yul_node = YulNode(node) + self._nodes.append(yul_node) + return yul_node + - def new_node(self, node_type, src): - node = self._node_solc(node_type, self._counter_nodes) - node.set_offset(src, self.slither) - node.set_function(self) - self._counter_nodes += 1 - self._nodes.append(node) - return node +class YulObject(YulScope): + __slots__ = ["_entrypoint", "_parent_func", "_nodes"] + + def __init__(self, contract: Contract, entrypoint: Node, id: List[str], **kwargs): + super().__init__(contract, id, **kwargs) + + self._entrypoint: YulNode = YulNode(entrypoint) + self._nodes: List[YulNode] = [] + + @property + def entrypoint(self) -> YulNode: + return self._entrypoint + + @property + def function(self) -> Function: + return self._parent_func + + def new_node(self, node_type: NodeType, src: Union[str, Dict]) -> YulNode: + if self._parent_func: + node = self._parent_func.new_node(node_type, src) + else: + raise SlitherException("standalone yul objects are not supported yet") + + yul_node = YulNode(node) + self._nodes.append(yul_node) + return yul_node + + def convert(self, ast: Dict) -> YulNode: + return convert_yul(self, self._entrypoint, ast) + + def analyze_expressions(self): + for node in self._nodes: + node.analyze_expressions(self) ################################################################################### @@ -93,68 +258,68 @@ structures and metadata from the input AST. These include things like function definitions and local variables. Each function takes three parameters: - 1) root is a NodeSolc of NodeType.ASSEMBLY, and stores information at the - local scope. In Yul, variables are scoped to the function they're - declared in (except for variables outside the assembly block) - 2) parent is the last node in the CFG. new nodes should be linked against - this node + 1) root is the current YulScope, where you can find things like local variables + 2) parent is the previous YulNode, which you'll have to link to 3) ast is a dictionary and is the current node in the Yul ast being converted Each function must return a single parameter: - 1) A NodeSolc representing the new end of the CFG + 1) the new YulNode that the CFG ends at The entrypoint is the function at the end of this region, `convert_yul`, which dispatches to a specialized function based on a lookup dictionary. """ -def convert_yul_block(root, parent, ast): +def convert_yul_block(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: for statement in ast["statements"]: parent = convert_yul(root, parent, statement) return parent -def convert_yul_function_definition(root, parent, ast): - f = YulFunction(ast, root) +def convert_yul_function_definition(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: + func = Function() + yul_function = YulFunction(func, root, ast) - root.function.contract._functions[root.format_canonical_yul_name(f.name)] = f + root.contract._functions[func.canonical_name] = func + root.slither.add_function(func) + root.add_yul_local_function(yul_function) - f.convert_body() - f.parse_body() + yul_function.convert_body() + yul_function.parse_body() return parent -def convert_yul_variable_declaration(root, parent, ast): +def convert_yul_variable_declaration(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: for variable_ast in ast['variables']: parent = convert_yul(root, parent, variable_ast) - node = parent.function.new_node(NodeType.EXPRESSION, ast["src"]) + node = root.new_node(NodeType.EXPRESSION, ast["src"]) node.add_unparsed_yul_expression(root, ast) - link_nodes(parent, node) + link_underlying_nodes(parent, node) return node -def convert_yul_assignment(root, parent, ast): - node = parent.function.new_node(NodeType.EXPRESSION, ast["src"]) +def convert_yul_assignment(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: + node = root.new_node(NodeType.EXPRESSION, ast["src"]) node.add_unparsed_yul_expression(root, ast) - link_nodes(parent, node) + link_underlying_nodes(parent, node) return node -def convert_yul_expression_statement(root, parent, ast): +def convert_yul_expression_statement(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: src = ast['src'] expression_ast = ast['expression'] - expression = parent.function.new_node(NodeType.EXPRESSION, src) + expression = root.new_node(NodeType.EXPRESSION, src) expression.add_unparsed_yul_expression(root, expression_ast) - link_nodes(parent, expression) + link_underlying_nodes(parent, expression) return expression -def convert_yul_if(root, parent, ast): +def convert_yul_if(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: # we're cheating and pretending that yul supports if/else so we can convert switch cleaner src = ast['src'] @@ -162,8 +327,8 @@ def convert_yul_if(root, parent, ast): true_body_ast = ast['body'] false_body_ast = ast['false_body'] if 'false_body' in ast else None - condition = parent.function.new_node(NodeType.IF, src) - end = parent.function.new_node(NodeType.ENDIF, src) + condition = root.new_node(NodeType.IF, src) + end = root.new_node(NodeType.ENDIF, src) condition.add_unparsed_yul_expression(root, condition_ast) @@ -171,17 +336,17 @@ def convert_yul_if(root, parent, ast): if false_body_ast: false_body = convert_yul(root, condition, false_body_ast) - link_nodes(false_body, end) + link_underlying_nodes(false_body, end) else: - link_nodes(condition, end) + link_underlying_nodes(condition, end) - link_nodes(parent, condition) - link_nodes(true_body, end) + link_underlying_nodes(parent, condition) + link_underlying_nodes(true_body, end) return end -def convert_yul_switch(root, parent, ast): +def convert_yul_switch(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: """ This is unfortunate. We don't really want a switch in our IR so we're going to translate it into a series of if/else statements. @@ -265,71 +430,71 @@ def convert_yul_switch(root, parent, ast): return convert_yul(root, parent, rewritten_switch) -def convert_yul_for_loop(root, parent, ast): +def convert_yul_for_loop(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: pre_ast = ast['pre'] condition_ast = ast['condition'] post_ast = ast['post'] body_ast = ast['body'] - start_loop = parent.function.new_node(NodeType.STARTLOOP, ast['src']) - end_loop = parent.function.new_node(NodeType.ENDLOOP, ast['src']) + start_loop = root.new_node(NodeType.STARTLOOP, ast['src']) + end_loop = root.new_node(NodeType.ENDLOOP, ast['src']) - link_nodes(parent, start_loop) + link_underlying_nodes(parent, start_loop) pre = convert_yul(root, start_loop, pre_ast) - condition = parent.function.new_node(NodeType.IFLOOP, condition_ast['src']) + condition = root.new_node(NodeType.IFLOOP, condition_ast['src']) condition.add_unparsed_yul_expression(root, condition_ast) - link_nodes(pre, condition) + link_underlying_nodes(pre, condition) - link_nodes(condition, end_loop) + link_underlying_nodes(condition, end_loop) body = convert_yul(root, condition, body_ast) post = convert_yul(root, body, post_ast) - link_nodes(post, condition) + link_underlying_nodes(post, condition) return end_loop -def convert_yul_break(root, parent, ast): - break_ = parent.function.new_node(NodeType.BREAK, ast['src']) - link_nodes(parent, break_) +def convert_yul_break(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: + break_ = root.new_node(NodeType.BREAK, ast['src']) + link_underlying_nodes(parent, break_) return break_ -def convert_yul_continue(root, parent, ast): - continue_ = parent.function.new_node(NodeType.CONTINUE, ast['src']) - link_nodes(parent, continue_) +def convert_yul_continue(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: + continue_ = root.new_node(NodeType.CONTINUE, ast['src']) + link_underlying_nodes(parent, continue_) return continue_ -def convert_yul_leave(root, parent, ast): - leave = parent.function.new_node(NodeType.RETURN, ast['src']) - link_nodes(parent, leave) +def convert_yul_leave(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: + leave = root.new_node(NodeType.RETURN, ast['src']) + link_underlying_nodes(parent, leave) return leave -def convert_yul_typed_name(root, parent, ast): - var = YulLocalVariable(ast) - var.set_function(root.function) - var.set_offset(ast['src'], root.slither) +def convert_yul_typed_name(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: + local_var = LocalVariable() + + var = YulLocalVariable(local_var, root, ast) root.add_yul_local_variable(var) - node = parent.function.new_node(NodeType.VARIABLE, ast['src']) - node.add_variable_declaration(var) - link_nodes(parent, node) + node = root.new_node(NodeType.VARIABLE, ast['src']) + node.underlying_node.add_variable_declaration(local_var) + link_underlying_nodes(parent, node) return node -def convert_yul_unsupported(root, parent, ast): +def convert_yul_unsupported(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: raise SlitherException(f"no converter available for {ast['nodeType']} {json.dumps(ast, indent=2)}") -def convert_yul(root, parent, ast): +def convert_yul(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: return converters.get(ast['nodeType'], convert_yul_unsupported)(root, parent, ast) @@ -374,14 +539,14 @@ dispatches to a specialized function based on a lookup dictionary. """ -def _parse_yul_assignment_common(root, node, ast, key): +def _parse_yul_assignment_common(root: YulScope, node: YulNode, ast: Dict, key: str) -> Optional[Expression]: lhs = [parse_yul(root, node, arg) for arg in ast[key]] rhs = parse_yul(root, node, ast['value']) return AssignmentOperation(vars_to_val(lhs), rhs, AssignmentOperationType.ASSIGN, vars_to_typestr(lhs)) -def parse_yul_variable_declaration(root, node, ast): +def parse_yul_variable_declaration(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: """ We already created variables in the conversion phase, so just do the assignment @@ -393,14 +558,17 @@ def parse_yul_variable_declaration(root, node, ast): return _parse_yul_assignment_common(root, node, ast, 'variables') -def parse_yul_assignment(root, node, ast): +def parse_yul_assignment(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: return _parse_yul_assignment_common(root, node, ast, 'variableNames') -def parse_yul_function_call(root, node, ast): +def parse_yul_function_call(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: args = [parse_yul(root, node, arg) for arg in ast['arguments']] ident = parse_yul(root, node, ast['functionName']) + if not isinstance(ident, Identifier): + raise SlitherException("expected identifier from parsing function name") + if isinstance(ident.value, YulBuiltin): name = ident.value.name if name in binary_ops: @@ -423,35 +591,28 @@ def parse_yul_function_call(root, node, ast): raise SlitherException(f"unexpected function call target type {str(type(ident.value))}") -def parse_yul_identifier(root, node, ast): +def parse_yul_identifier(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: name = ast['name'] if name in builtins: return Identifier(YulBuiltin(name)) # check function-scoped variables - variable = root.function.get_local_variable_from_name(name) - if variable: - return Identifier(variable) + if root.parent_func: + variable = root.parent_func.get_local_variable_from_name(name) + if variable: + return Identifier(variable) # check yul-scoped variable variable = root.get_yul_local_variable_from_name(name) if variable: - return Identifier(variable) + return Identifier(variable.underlying) # check yul-scoped function - # note that a function can recurse into itself, so we have two canonical names - # to check (but only one of them can be valid) - - functions = root.function.contract_declarer._functions - - canonical_name = root.format_canonical_yul_name(name) - if canonical_name in functions: - return Identifier(functions[canonical_name]) - canonical_name = root.format_canonical_yul_name(name, -1) - if canonical_name in functions: - return Identifier(functions[canonical_name]) + func = root.get_yul_local_function_from_name(name) + if func: + return Identifier(func.underlying) # check for magic suffixes if name.endswith("_slot"): @@ -468,7 +629,7 @@ def parse_yul_identifier(root, node, ast): raise SlitherException(f"unresolved reference to identifier {name}") -def parse_yul_literal(root, node, ast): +def parse_yul_literal(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: type_ = ast['type'] value = ast['value'] @@ -478,19 +639,19 @@ def parse_yul_literal(root, node, ast): return Literal(value, ElementaryType(type_)) -def parse_yul_typed_name(root, node, ast): +def parse_yul_typed_name(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: var = root.get_yul_local_variable_from_name(ast['name']) - i = Identifier(var) - i._type = var.type + i = Identifier(var.underlying) + i._type = var.underlying.type return i -def parse_yul_unsupported(root, node, ast): +def parse_yul_unsupported(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: raise SlitherException(f"no parser available for {ast['nodeType']} {json.dumps(ast, indent=2)}") -def parse_yul(root, node, ast): +def parse_yul(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: op = parsers.get(ast['nodeType'], parse_yul_unsupported)(root, node, ast) if op: op.set_offset(ast["src"], root.slither) @@ -511,7 +672,7 @@ parsers = { ################################################################################### ################################################################################### -def vars_to_typestr(rets): +def vars_to_typestr(rets: List[Expression]) -> str: if len(rets) == 0: return "" if len(rets) == 1: diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index c215084b7..1fea831a9 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -55,6 +55,11 @@ _binary_to_binary = { BinaryOperationType.NOT_EQUAL: BinaryType.NOT_EQUAL, BinaryOperationType.ANDAND: BinaryType.ANDAND, BinaryOperationType.OROR: BinaryType.OROR, + BinaryOperationType.DIVISION_SIGNED: BinaryType.DIVISION_SIGNED, + BinaryOperationType.MODULO_SIGNED: BinaryType.MODULO_SIGNED, + BinaryOperationType.LESS_SIGNED: BinaryType.LESS_SIGNED, + BinaryOperationType.GREATER_SIGNED: BinaryType.GREATER_SIGNED, + BinaryOperationType.RIGHT_SHIFT_ARITHMETIC: BinaryType.RIGHT_SHIFT_ARITHMETIC, } def convert_assignment(left, right, t, return_type): From c809e83bde35aff474374a0d45b1c2e729cc0ecd Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 23 Jun 2020 17:21:59 -0400 Subject: [PATCH 03/73] remove unused import --- slither/solc_parsing/cfg/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 88ffd75fe..2d2bec0f3 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -9,7 +9,6 @@ from slither.core.expressions.assignment_operation import ( from slither.core.expressions.identifier import Identifier from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.visitors.expression.find_calls import FindCalls -from slither.solc_parsing.yul.parse_yul import parse_yul from slither.visitors.expression.read_var import ReadVar from slither.visitors.expression.write_var import WriteVar From ba4e288b9ab265ba9fdc111b01b07b662307c242 Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 23 Jun 2020 17:32:46 -0400 Subject: [PATCH 04/73] do some cleanup --- slither/core/declarations/function.py | 8 ++++++ slither/solc_parsing/yul/parse_yul.py | 41 ++++++++++++++------------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index caf06b74d..0584343fc 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -201,6 +201,14 @@ class Function(ChildContract, ChildInheritance, SourceMapping): def name(self, new_name: str): self._name = new_name + @property + def scope(self): + return self._scope + + @scope.setter + def scope(self, new_scope: List[str]): + self._scope = new_scope + @property def full_name(self) -> str: """ diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index e4c6662d2..608ae66d7 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -1,6 +1,6 @@ import abc import json -from typing import Optional, Dict, Tuple, List, Union +from typing import Optional, Dict, List, Union from slither.core.cfg.node import NodeType, Node, link_nodes from slither.core.declarations import Function, SolidityFunction, SolidityVariable, Contract @@ -22,23 +22,24 @@ from slither.visitors.expression.write_var import WriteVar class YulNode: - def __init__(self, node: Node): - self._unparsed_expression: Optional[Tuple[YulScope, Dict]] = None + def __init__(self, node: Node, scope: 'YulScope'): self._node = node + self._scope = scope + self._unparsed_expression: Optional[Dict] = None @property def underlying_node(self) -> Node: return self._node - def add_unparsed_yul_expression(self, root: 'YulScope', expression: Dict): + def add_unparsed_expression(self, expression: Dict): assert self._unparsed_expression is None - self._unparsed_expression = (root, expression) + self._unparsed_expression = expression - def analyze_expressions(self, caller_context): + def analyze_expressions(self): 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_yul(self._unparsed_expression[0], self, self._unparsed_expression[1]) + expression = parse_yul(self._scope, self, self._unparsed_expression) self._node.add_expression(expression) if self._node.expression: @@ -147,7 +148,7 @@ class YulLocalVariable: class YulFunction(YulScope): __slots__ = ["_function", "_root", "_ast", "_nodes", "_entrypoint"] - def __init__(self, func: Function, root: YulScope, ast: Dict, **kwargs): + def __init__(self, func: Function, root: YulScope, ast: Dict): super().__init__(root.contract, root.id + [ast['name']], parent_func=root.parent_func) assert (ast['nodeType'] == 'YulFunctionDefinition') @@ -163,9 +164,9 @@ class YulFunction(YulScope): func.set_offset(ast["src"], root.slither) func.set_contract(root.contract) func.set_contract_declarer(root.contract) - func._scope = root.id + func.scope = root.id - self._nodes = [] + self._nodes: List[YulNode] = [] self._entrypoint = self.new_node(NodeType.ASSEMBLY, ast['src']) self.add_yul_local_function(self) @@ -198,7 +199,7 @@ class YulFunction(YulScope): def parse_body(self): for node in self._nodes: - node.analyze_expressions(self) + node.analyze_expressions() def new_node(self, node_type, src) -> YulNode: if self._function: @@ -206,7 +207,7 @@ class YulFunction(YulScope): else: raise SlitherException("standalone yul objects are not supported yet") - yul_node = YulNode(node) + yul_node = YulNode(node, self) self._nodes.append(yul_node) return yul_node @@ -217,7 +218,7 @@ class YulObject(YulScope): def __init__(self, contract: Contract, entrypoint: Node, id: List[str], **kwargs): super().__init__(contract, id, **kwargs) - self._entrypoint: YulNode = YulNode(entrypoint) + self._entrypoint: YulNode = YulNode(entrypoint, self) self._nodes: List[YulNode] = [] @property @@ -234,7 +235,7 @@ class YulObject(YulScope): else: raise SlitherException("standalone yul objects are not supported yet") - yul_node = YulNode(node) + yul_node = YulNode(node, self) self._nodes.append(yul_node) return yul_node @@ -243,7 +244,7 @@ class YulObject(YulScope): def analyze_expressions(self): for node in self._nodes: - node.analyze_expressions(self) + node.analyze_expressions() ################################################################################### @@ -295,7 +296,7 @@ def convert_yul_variable_declaration(root: YulScope, parent: YulNode, ast: Dict) parent = convert_yul(root, parent, variable_ast) node = root.new_node(NodeType.EXPRESSION, ast["src"]) - node.add_unparsed_yul_expression(root, ast) + node.add_unparsed_expression(ast) link_underlying_nodes(parent, node) return node @@ -303,7 +304,7 @@ def convert_yul_variable_declaration(root: YulScope, parent: YulNode, ast: Dict) def convert_yul_assignment(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: node = root.new_node(NodeType.EXPRESSION, ast["src"]) - node.add_unparsed_yul_expression(root, ast) + node.add_unparsed_expression(ast) link_underlying_nodes(parent, node) return node @@ -313,7 +314,7 @@ def convert_yul_expression_statement(root: YulScope, parent: YulNode, ast: Dict) expression_ast = ast['expression'] expression = root.new_node(NodeType.EXPRESSION, src) - expression.add_unparsed_yul_expression(root, expression_ast) + expression.add_unparsed_expression(expression_ast) link_underlying_nodes(parent, expression) return expression @@ -330,7 +331,7 @@ def convert_yul_if(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: condition = root.new_node(NodeType.IF, src) end = root.new_node(NodeType.ENDIF, src) - condition.add_unparsed_yul_expression(root, condition_ast) + condition.add_unparsed_expression(condition_ast) true_body = convert_yul(root, condition, true_body_ast) @@ -444,7 +445,7 @@ def convert_yul_for_loop(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: pre = convert_yul(root, start_loop, pre_ast) condition = root.new_node(NodeType.IFLOOP, condition_ast['src']) - condition.add_unparsed_yul_expression(root, condition_ast) + condition.add_unparsed_expression(condition_ast) link_underlying_nodes(pre, condition) link_underlying_nodes(condition, end_loop) From 66ae66b09c3300df2b306987b678cb209621b0f2 Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 23 Jun 2020 17:52:42 -0400 Subject: [PATCH 05/73] translate signed operations --- slither/slithir/operations/binary.py | 26 ------------ .../visitors/slithir/expression_to_slithir.py | 40 +++++++++++++++---- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index fce83aa6d..cd886197f 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -31,12 +31,6 @@ class BinaryType(Enum): ANDAND = 17 # && OROR = 18 # || - DIVISION_SIGNED = 19 - MODULO_SIGNED = 20 - LESS_SIGNED = 21 - GREATER_SIGNED = 22 - RIGHT_SHIFT_ARITHMETIC = 23 - @staticmethod def return_bool(operation_type): return operation_type in [BinaryType.OROR, @@ -88,16 +82,6 @@ class BinaryType(Enum): return BinaryType.ANDAND if operation_type == '||': return BinaryType.OROR - if operation_type == "/'": - return BinaryType.DIVISION_SIGNED - if operation_type == "%'": - return BinaryType.MODULO_SIGNED - if operation_type == "<'": - return BinaryType.LESS_SIGNED - if operation_type == ">'": - return BinaryType.GREATER_SIGNED - if operation_type == ">>'": - return BinaryType.RIGHT_SHIFT_ARITHMETIC raise SlithIRError('get_type: Unknown operation type {})'.format(operation_type)) @@ -140,16 +124,6 @@ class BinaryType(Enum): return "&&" if self == BinaryType.OROR: return "||" - if self == BinaryType.DIVISION_SIGNED: - return "/'" - if self == BinaryType.MODULO_SIGNED: - return "%'" - if self == BinaryType.LESS_SIGNED: - return "<'" - if self == BinaryType.GREATER_SIGNED: - return ">'" - if self == BinaryType.RIGHT_SHIFT_ARITHMETIC: - return ">>'" raise SlithIRError("str: Unknown operation type {} {})".format(self, type(self))) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 1fea831a9..99d41d3e5 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -55,13 +55,17 @@ _binary_to_binary = { BinaryOperationType.NOT_EQUAL: BinaryType.NOT_EQUAL, BinaryOperationType.ANDAND: BinaryType.ANDAND, BinaryOperationType.OROR: BinaryType.OROR, - BinaryOperationType.DIVISION_SIGNED: BinaryType.DIVISION_SIGNED, - BinaryOperationType.MODULO_SIGNED: BinaryType.MODULO_SIGNED, - BinaryOperationType.LESS_SIGNED: BinaryType.LESS_SIGNED, - BinaryOperationType.GREATER_SIGNED: BinaryType.GREATER_SIGNED, - BinaryOperationType.RIGHT_SHIFT_ARITHMETIC: BinaryType.RIGHT_SHIFT_ARITHMETIC, } +_signed_to_unsigned = { + BinaryOperationType.DIVISION_SIGNED: BinaryType.DIVISION, + BinaryOperationType.MODULO_SIGNED: BinaryType.MODULO, + BinaryOperationType.LESS_SIGNED: BinaryType.LESS, + BinaryOperationType.GREATER_SIGNED: BinaryType.GREATER, + BinaryOperationType.RIGHT_SHIFT_ARITHMETIC: BinaryType.RIGHT_SHIFT, +} + + def convert_assignment(left, right, t, return_type): if t == AssignmentOperationType.ASSIGN: return Assignment(left, right, return_type) @@ -147,9 +151,29 @@ class ExpressionToSlithIR(ExpressionVisitor): right = get(expression.expression_right) val = TemporaryVariable(self._node) - operation = Binary(val, left, right, _binary_to_binary[expression.type]) - operation.set_expression(expression) - self._result.append(operation) + if expression.type in _signed_to_unsigned: + new_left = TemporaryVariable(self._node) + conv_left = TypeConversion(new_left, left, ElementaryType('int256')) + conv_left.set_expression(expression) + self._result.append(conv_left) + + if expression.type != BinaryOperationType.RIGHT_SHIFT_ARITHMETIC: + new_right = TemporaryVariable(self._node) + conv_right = TypeConversion(new_right, right, ElementaryType('int256')) + conv_right.set_expression(expression) + self._result.append(conv_right) + else: + new_right = right + + operation = Binary(val, new_left, new_right, _signed_to_unsigned[expression.type]) + operation.set_expression(expression) + val.set_type(ElementaryType('uint256')) # overwrite the result from Binary() for now + self._result.append(operation) + else: + operation = Binary(val, left, right, _binary_to_binary[expression.type]) + operation.set_expression(expression) + self._result.append(operation) + set_val(expression, val) def _post_call_expression(self, expression): From f3485c5f625754feaa0f51239b7c86c979ccbc9a Mon Sep 17 00:00:00 2001 From: samczsun Date: Mon, 15 Jun 2020 20:13:27 -0400 Subject: [PATCH 06/73] Implement support for calculating storage layout --- slither/core/slither_core.py | 38 +++++++++++++++++++ slither/core/solidity_types/array_type.py | 7 ++++ .../core/solidity_types/elementary_type.py | 7 ++++ slither/core/solidity_types/function_type.py | 4 ++ slither/core/solidity_types/mapping_type.py | 4 ++ slither/core/solidity_types/type.py | 14 ++++++- .../core/solidity_types/user_defined_type.py | 33 ++++++++++++++++ slither/printers/summary/variable_order.py | 5 ++- slither/solc_parsing/declarations/contract.py | 3 +- slither/solc_parsing/slitherSolc.py | 1 + 10 files changed, 111 insertions(+), 5 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index cb4a5fd04..c42503ca9 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -5,6 +5,7 @@ import os import logging import json import re +import math from collections import defaultdict from typing import Optional, Dict, List, Set, Union @@ -56,6 +57,8 @@ class SlitherCore(Context): self._contract_name_collisions = defaultdict(list) self._contract_with_missing_inheritance = set() + self._storage_layouts = {} + ################################################################################### ################################################################################### # region Source code @@ -345,3 +348,38 @@ class SlitherCore(Context): return self._contract_with_missing_inheritance # endregion + ################################################################################### + ################################################################################### + # region Storage Layouts + ################################################################################### + ################################################################################### + + def compute_storage_layout(self): + for contract in self.contracts_derived: + self._storage_layouts[contract.name] = {} + + slot = 0 + offset = 0 + for var in contract.state_variables_ordered: + if var.is_constant: + continue + + size, new_slot = var.type.storage_size + + if new_slot: + if offset > 0: + slot += 1 + offset = 0 + elif size + offset > 32: + slot += 1 + offset = 0 + + self._storage_layouts[contract.name][var.canonical_name] = (slot, offset) + if new_slot: + slot += math.ceil(size / 32) + else: + offset += size + + def storage_layout_of(self, contract, var): + return self._storage_layouts[contract.name][var.canonical_name] + # endregion diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index 7022539a7..aba611b8c 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -37,6 +37,13 @@ class ArrayType(Type): def lenght_value(self) -> Optional[Literal]: return self._length_value + @property + def storage_size(self): + if self._length_value: + elem_size, _ = self._type.storage_size + return elem_size * int(self._length_value.value), True + return 32, True + def __str__(self): if self._length: return str(self._type) + "[{}]".format(str(self._length_value)) diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index 474f2a452..16c25e2f0 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -172,6 +172,13 @@ class ElementaryType(Type): return int(t[len("bytes") :]) return None + @property + def storage_size(self): + if self._type == 'string' or self._type == 'bytes': + return 32, True + + return int(self.size / 8), False + def __str__(self): return self._type diff --git a/slither/core/solidity_types/function_type.py b/slither/core/solidity_types/function_type.py index e1d20d68a..a666a3521 100644 --- a/slither/core/solidity_types/function_type.py +++ b/slither/core/solidity_types/function_type.py @@ -26,6 +26,10 @@ class FunctionType(Type): def return_type(self) -> List[Type]: return [x.type for x in self.return_values] + @property + def storage_size(self): + return 24, False + def __str__(self): # Use x.type # x.name may be empty diff --git a/slither/core/solidity_types/mapping_type.py b/slither/core/solidity_types/mapping_type.py index 7c4b8c2bb..815f2dd78 100644 --- a/slither/core/solidity_types/mapping_type.py +++ b/slither/core/solidity_types/mapping_type.py @@ -17,6 +17,10 @@ class MappingType(Type): def type_to(self) -> Type: return self._to + @property + def storage_size(self): + return 32, True + def __str__(self): return "mapping({} => {})".format(str(self._from), str(self._to)) diff --git a/slither/core/solidity_types/type.py b/slither/core/solidity_types/type.py index e06c7bf0d..8042fcc7b 100644 --- a/slither/core/solidity_types/type.py +++ b/slither/core/solidity_types/type.py @@ -1,5 +1,15 @@ +import abc +from typing import Tuple + from slither.core.source_mapping.source_mapping import SourceMapping +class Type(SourceMapping,metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def storage_size(self) -> Tuple[int, bool]: + """ + Computes and returns storage layout related metadata -class Type(SourceMapping): - pass + :return: (int, bool) - the number of bytes this type will require, and whether it must start in + a new slot regardless of whether the current slot can still fit it + """ diff --git a/slither/core/solidity_types/user_defined_type.py b/slither/core/solidity_types/user_defined_type.py index eeb38b670..dc3c1dd8c 100644 --- a/slither/core/solidity_types/user_defined_type.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -1,4 +1,5 @@ from typing import Union, TYPE_CHECKING +import math from slither.core.solidity_types.type import Type @@ -22,6 +23,38 @@ class UserDefinedType(Type): def type(self) -> Union["Contract", "Enum", "Structure"]: return self._type + @property + def storage_size(self): + from slither.core.declarations.structure import Structure + from slither.core.declarations.enum import Enum + from slither.core.declarations.contract import Contract + + if isinstance(self._type, Contract): + return 20, False + elif isinstance(self._type, Enum): + return int(math.ceil(math.log2(len(self._type.values)) / 8)), False + elif isinstance(self._type, Structure): + # todo there's some duplicate logic here and slither_core, can we refactor this? + slot = 0 + offset = 0 + for elem in self._type.elems_ordered: + size, new_slot = elem.type.storage_size + if new_slot: + if offset > 0: + slot += 1 + offset = 0 + elif size + offset > 32: + slot += 1 + offset = 0 + + if new_slot: + slot += math.ceil(size / 32) + else: + offset += size + if offset > 0: + slot += 1 + return slot * 32, True + def __str__(self): from slither.core.declarations.structure import Structure from slither.core.declarations.enum import Enum diff --git a/slither/printers/summary/variable_order.py b/slither/printers/summary/variable_order.py index 93c04fe49..d00fd7f56 100644 --- a/slither/printers/summary/variable_order.py +++ b/slither/printers/summary/variable_order.py @@ -26,10 +26,11 @@ class VariableOrder(AbstractPrinter): for contract in self.slither.contracts_derived: txt += '\n{}:\n'.format(contract.name) - table = MyPrettyTable(['Name', 'Type']) + table = MyPrettyTable(['Name', 'Type', 'Slot', 'Offset']) for variable in contract.state_variables_ordered: if not variable.is_constant: - table.add_row([variable.canonical_name, str(variable.type)]) + slot, offset = self.slither.storage_layout_of(contract, variable) + table.add_row([variable.canonical_name, str(variable.type), slot, offset]) all_tables.append((contract.name, table)) txt += str(table) + '\n' diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index f7d001fe8..d348cdccc 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -277,7 +277,8 @@ class ContractSolc: def parse_state_variables(self): for father in self._contract.inheritance_reverse: self._contract.variables_as_dict.update(father.variables_as_dict) - self._contract.add_variables_ordered(father.state_variables_ordered) + self._contract.add_variables_ordered( + [var for var in father.state_variables_ordered if var not in self._contract.state_variables_ordered]) for varNotParsed in self._variablesNotParsed: var = StateVariable() diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index d5de1b56a..e6f5d0df4 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -344,6 +344,7 @@ Please rename it, this name is reserved for Slither's internals""" self._convert_to_slithir() compute_dependency(self._core) + self._core.compute_storage_layout() def _analyze_all_enums(self, contracts_to_be_analyzed: List[ContractSolc]): while contracts_to_be_analyzed: From 075db34267c6a20c8e16e98948a6b56a843e0ffe Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 23 Jun 2020 18:16:36 -0400 Subject: [PATCH 07/73] typings --- slither/core/slither_core.py | 6 +++--- slither/core/solidity_types/array_type.py | 4 ++-- slither/core/solidity_types/elementary_type.py | 4 ++-- slither/core/solidity_types/function_type.py | 4 ++-- slither/core/solidity_types/mapping_type.py | 4 +++- slither/core/solidity_types/user_defined_type.py | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index c42503ca9..a67c68bc6 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -7,7 +7,7 @@ import json import re import math from collections import defaultdict -from typing import Optional, Dict, List, Set, Union +from typing import Optional, Dict, List, Set, Union, Tuple from crytic_compile import CryticCompile @@ -57,7 +57,7 @@ class SlitherCore(Context): self._contract_name_collisions = defaultdict(list) self._contract_with_missing_inheritance = set() - self._storage_layouts = {} + self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {} ################################################################################### ################################################################################### @@ -380,6 +380,6 @@ class SlitherCore(Context): else: offset += size - def storage_layout_of(self, contract, var): + def storage_layout_of(self, contract, var) -> Tuple[int, int]: return self._storage_layouts[contract.name][var.canonical_name] # endregion diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index aba611b8c..966d1b537 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Tuple from slither.core.expressions import Literal from slither.core.expressions.expression import Expression @@ -38,7 +38,7 @@ class ArrayType(Type): return self._length_value @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: if self._length_value: elem_size, _ = self._type.storage_size return elem_size * int(self._length_value.value), True diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index 16c25e2f0..6f1d7a8b5 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -1,5 +1,5 @@ import itertools -from typing import Optional +from typing import Optional, Tuple from slither.core.solidity_types.type import Type @@ -173,7 +173,7 @@ class ElementaryType(Type): return None @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: if self._type == 'string' or self._type == 'bytes': return 32, True diff --git a/slither/core/solidity_types/function_type.py b/slither/core/solidity_types/function_type.py index a666a3521..6a96bf368 100644 --- a/slither/core/solidity_types/function_type.py +++ b/slither/core/solidity_types/function_type.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Tuple from slither.core.solidity_types.type import Type from slither.core.variables.function_type_variable import FunctionTypeVariable @@ -27,7 +27,7 @@ class FunctionType(Type): return [x.type for x in self.return_values] @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: return 24, False def __str__(self): diff --git a/slither/core/solidity_types/mapping_type.py b/slither/core/solidity_types/mapping_type.py index 815f2dd78..e2f8ebe30 100644 --- a/slither/core/solidity_types/mapping_type.py +++ b/slither/core/solidity_types/mapping_type.py @@ -1,3 +1,5 @@ +from typing import Tuple + from slither.core.solidity_types.type import Type @@ -18,7 +20,7 @@ class MappingType(Type): return self._to @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: return 32, True def __str__(self): diff --git a/slither/core/solidity_types/user_defined_type.py b/slither/core/solidity_types/user_defined_type.py index dc3c1dd8c..4aed394e8 100644 --- a/slither/core/solidity_types/user_defined_type.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -1,4 +1,4 @@ -from typing import Union, TYPE_CHECKING +from typing import Union, TYPE_CHECKING, Tuple import math from slither.core.solidity_types.type import Type @@ -24,7 +24,7 @@ class UserDefinedType(Type): return self._type @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: from slither.core.declarations.structure import Structure from slither.core.declarations.enum import Enum from slither.core.declarations.contract import Contract From 95c6229a88b30e51b3b7ee87a76a690953ee8744 Mon Sep 17 00:00:00 2001 From: samczsun Date: Wed, 24 Jun 2020 11:20:02 -0400 Subject: [PATCH 08/73] do one final conversion --- slither/visitors/slithir/expression_to_slithir.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 99d41d3e5..19f9fd782 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -165,10 +165,14 @@ class ExpressionToSlithIR(ExpressionVisitor): else: new_right = right - operation = Binary(val, new_left, new_right, _signed_to_unsigned[expression.type]) + new_final = TemporaryVariable(self._node) + operation = Binary(new_final, new_left, new_right, _signed_to_unsigned[expression.type]) operation.set_expression(expression) - val.set_type(ElementaryType('uint256')) # overwrite the result from Binary() for now self._result.append(operation) + + conv_final = TypeConversion(val, new_final, ElementaryType('uint256')) + conv_final.set_expression(expression) + self._result.append(conv_final) else: operation = Binary(val, left, right, _binary_to_binary[expression.type]) operation.set_expression(expression) From 3edef5344bb7a25d58e0c5e8de3d270ec8768db4 Mon Sep 17 00:00:00 2001 From: samczsun Date: Wed, 24 Jun 2020 11:28:13 -0400 Subject: [PATCH 09/73] remove old check --- slither/detectors/statements/assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index ceed61336..bf84dab7a 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -30,7 +30,7 @@ class Assembly(AbstractDetector): Returns: (bool) """ - return node.type == NodeType.ASSEMBLY and len(node.yul_path) == 2 + return node.type == NodeType.ASSEMBLY def detect_assembly(self, contract): ret = [] From a607e6eb57aa86a4ffb60af0f976effef8560784 Mon Sep 17 00:00:00 2001 From: samczsun Date: Wed, 24 Jun 2020 11:30:33 -0400 Subject: [PATCH 10/73] set some more fields --- slither/solc_parsing/yul/parse_yul.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 608ae66d7..e22c0f5b4 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -165,9 +165,11 @@ class YulFunction(YulScope): func.set_contract(root.contract) func.set_contract_declarer(root.contract) func.scope = root.id + func.is_implemented = True self._nodes: List[YulNode] = [] self._entrypoint = self.new_node(NodeType.ASSEMBLY, ast['src']) + func.entry_point = self._entrypoint.underlying_node self.add_yul_local_function(self) From c2cb8dbf6c3c9ee34e03419d80bdb6ec03913bcc Mon Sep 17 00:00:00 2001 From: samczsun Date: Wed, 24 Jun 2020 12:24:16 -0400 Subject: [PATCH 11/73] update the right field --- slither/solc_parsing/declarations/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index d54b63f52..8ef0fbe5a 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -810,7 +810,7 @@ class FunctionSolc: elif name == "InlineAssembly": # Added with solc 0.6 - the yul code is an AST if 'AST' in statement: - self._contains_assembly = True + self._function.contains_assembly = True yul_object = self._new_yul_object(statement['src']) entrypoint = yul_object.entrypoint exitpoint = yul_object.convert(statement['AST']) From 2d256ddd93ccdd83108a10cd4bba4c826dd45666 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jun 2020 19:30:38 +0200 Subject: [PATCH 12/73] Improve support of top-level structures and enums --- slither/core/slither_core.py | 22 +++++++++++++++++-- .../expressions/expression_parsing.py | 10 +++++++++ slither/solc_parsing/slitherSolc.py | 11 +++++----- .../solidity_types/type_parsing.py | 4 ++-- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index cb4a5fd04..c2c8d365f 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -11,7 +11,7 @@ from typing import Optional, Dict, List, Set, Union from crytic_compile import CryticCompile from slither.core.context.context import Context -from slither.core.declarations import Contract, Pragma, Import, Function, Modifier +from slither.core.declarations import Contract, Pragma, Import, Function, Modifier, Structure, Enum from slither.core.variables.state_variable import StateVariable from slither.slithir.operations import InternalCall from slither.slithir.variables import Constant @@ -136,7 +136,7 @@ class SlitherCore(Context): """list(Contract): List of contracts that are derived and not inherited.""" inheritance = (x.inheritance for x in self.contracts) inheritance = [item for sublist in inheritance for item in sublist] - return [c for c in self._contracts.values() if c not in inheritance] + return [c for c in self._contracts.values() if c not in inheritance and not c.is_top_level] @property def contracts_as_dict(self) -> Dict[str, Contract]: @@ -200,6 +200,24 @@ class SlitherCore(Context): self._all_state_variables = set(state_variables) return list(self._all_state_variables) + # endregion + ################################################################################### + ################################################################################### + # region Top level + ################################################################################### + ################################################################################### + + @property + def top_level_structures(self) -> List[Structure]: + top_level_structures = [c.structures for c in self.contracts if c.is_top_level] + return [st for sublist in top_level_structures for st in sublist] + + + @property + def top_level_enums(self) -> List[Enum]: + top_level_enums = [c.enums for c in self.contracts if c.is_top_level] + return [st for sublist in top_level_enums for st in sublist] + # endregion ################################################################################### ################################################################################### diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index d2ee972df..4b78a2a10 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -168,6 +168,11 @@ def find_variable( if var_name in structures: return structures[var_name] + structures_top_level = contract.slither.top_level_structures + for st in structures_top_level: + if st.name == var_name: + return st + events = contract.events_as_dict if var_name in events: return events[var_name] @@ -176,6 +181,11 @@ def find_variable( if var_name in enums: return enums[var_name] + enums_top_level = contract.slither.top_level_enums + for enum in enums_top_level: + if enum.name == var_name: + return enum + # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index d5de1b56a..695a11184 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -163,15 +163,16 @@ class SlitherSolc: assert self._is_compact_ast # Do not support top level definition for legacy AST fake_contract_data = { "name": f"SlitherInternalTopLevelContract{self._top_level_contracts_counter}", - "id": -1000, # TODO: determine if collission possible + "id": -1000 + self._top_level_contracts_counter, # TODO: determine if collission possible "linearizedBaseContracts": [], "fullyImplemented": True, "contractKind": "SLitherInternal", } self._top_level_contracts_counter += 1 - top_level_contract = ContractSolc(self, fake_contract_data) - top_level_contract.is_top_level = True - top_level_contract.set_offset(contract_data["src"], self) + contract = Contract() + top_level_contract = ContractSolc(self, contract, fake_contract_data) + contract.is_top_level = True + contract.set_offset(contract_data["src"], self._core) if contract_data[self.get_key()] == "StructDefinition": top_level_contract._structuresNotParsed.append( @@ -182,7 +183,7 @@ class SlitherSolc: contract_data ) # Todo add proper setters - self._contractsNotParsed.append(top_level_contract) + self._underlying_contract_to_parser[contract] = top_level_contract def _parse_source_unit(self, data: Dict, filename: str): if data[self.get_key()] != "SourceUnit": diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 9d11936ad..4ec8f5112 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -167,8 +167,8 @@ def parse_type(t: Union[Dict, UnknownType], caller_context): else: key = "name" - structures = contract.structures - enums = contract.enums + structures = contract.structures + contract.slither.top_level_structures + enums = contract.enums + contract.slither.top_level_enums contracts = contract.slither.contracts if isinstance(t, UnknownType): From 9c6090a4f95cb782fb54c60e53d6b3b0dc865b0a Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jun 2020 19:38:14 +0200 Subject: [PATCH 13/73] Improve printers --- slither/printers/functions/authorization.py | 2 ++ slither/printers/functions/cfg.py | 2 ++ slither/printers/inheritance/inheritance.py | 4 ++++ slither/printers/inheritance/inheritance_graph.py | 2 ++ slither/printers/summary/contract.py | 2 ++ slither/printers/summary/data_depenency.py | 2 ++ slither/printers/summary/function.py | 2 ++ slither/printers/summary/human_summary.py | 7 ++++--- slither/printers/summary/slithir.py | 2 ++ slither/printers/summary/slithir_ssa.py | 2 ++ 10 files changed, 24 insertions(+), 3 deletions(-) diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index 00dd08b72..70c281a6c 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -37,6 +37,8 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): txt = '' all_tables = [] for contract in self.contracts: + if contract.is_top_level: + continue txt += "\nContract %s\n"%contract.name table = MyPrettyTable(["Function", "State variables written", "Conditions on msg.sender"]) for function in contract.functions: diff --git a/slither/printers/functions/cfg.py b/slither/printers/functions/cfg.py index 0352305d6..74c5c0f1f 100644 --- a/slither/printers/functions/cfg.py +++ b/slither/printers/functions/cfg.py @@ -21,6 +21,8 @@ class CFG(AbstractPrinter): info = '' all_files = [] for contract in self.contracts: + if contract.is_top_level: + continue for function in contract.functions + contract.modifiers: if original_filename: filename = "{}-{}-{}.dot".format(original_filename, contract.name, function.full_name) diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index 62d816f18..ffeb81007 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -39,6 +39,8 @@ class PrinterInheritance(AbstractPrinter): result = {'child_to_base': {}} for child in self.contracts: + if child.is_top_level: + continue info += blue(f'\n+ {child.name}\n') result['child_to_base'][child.name] = {'immediate': [], 'not_immediate': []} @@ -58,6 +60,8 @@ class PrinterInheritance(AbstractPrinter): result['base_to_child'] = {} for base in self.contracts: + if base.is_top_level: + continue info += green(f'\n+ {base.name}') + '\n' children = list(self._get_child_contracts(base)) diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py index 4fb3bf877..e1311d7a3 100644 --- a/slither/printers/inheritance/inheritance_graph.py +++ b/slither/printers/inheritance/inheritance_graph.py @@ -166,6 +166,8 @@ class PrinterInheritanceGraph(AbstractPrinter): content = 'digraph "" {\n' for c in self.contracts: + if c.is_top_level: + continue content += self._summary(c) + '\n' content += '}' diff --git a/slither/printers/summary/contract.py b/slither/printers/summary/contract.py index a32d2c19b..b231c0b36 100644 --- a/slither/printers/summary/contract.py +++ b/slither/printers/summary/contract.py @@ -24,6 +24,8 @@ class ContractSummary(AbstractPrinter): all_contracts = [] for c in self.contracts: + if c.is_top_level: + continue is_upgradeable_proxy = c.is_upgradeable_proxy is_upgradeable = c.is_upgradeable diff --git a/slither/printers/summary/data_depenency.py b/slither/printers/summary/data_depenency.py index 1211a914f..3e33bae57 100644 --- a/slither/printers/summary/data_depenency.py +++ b/slither/printers/summary/data_depenency.py @@ -31,6 +31,8 @@ class DataDependency(AbstractPrinter): txt = '' for c in self.contracts: + if c.is_top_level: + continue txt += "\nContract %s\n"%c.name table = MyPrettyTable(['Variable', 'Dependencies']) for v in c.state_variables: diff --git a/slither/printers/summary/function.py b/slither/printers/summary/function.py index e81b11183..b334e860a 100644 --- a/slither/printers/summary/function.py +++ b/slither/printers/summary/function.py @@ -33,6 +33,8 @@ class FunctionSummary(AbstractPrinter): all_txt = '' for c in self.contracts: + if c.is_top_level: + continue (name, inheritance, var, func_summaries, modif_summaries) = c.get_summary() txt = "\nContract %s"%name txt += '\nContract vars: '+str(var) diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index 37c3475e6..aaad9e324 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -187,9 +187,10 @@ class PrinterHumanSummary(AbstractPrinter): def _number_contracts(self): if self.slither.crytic_compile is None: len(self.slither.contracts), 0 - deps = [c for c in self.slither.contracts if c.is_from_dependency()] - tests = [c for c in self.slither.contracts if c.is_test] - return len(self.slither.contracts) - len(deps) - len(tests), len(deps), len(tests) + contracts = [c for c in self.slither.contracts if not c.is_top_level] + deps = [c for c in contracts if c.is_from_dependency()] + tests = [c for c in contracts if c.is_test] + return len(contracts) - len(deps) - len(tests), len(deps), len(tests) def _standard_libraries(self): libraries = [] diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index a6ba49a17..deb0c7ddb 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -20,6 +20,8 @@ class PrinterSlithIR(AbstractPrinter): txt = "" for contract in self.contracts: + if contract.is_top_level: + continue txt += 'Contract {}'.format(contract.name) for function in contract.functions: txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}\n' diff --git a/slither/printers/summary/slithir_ssa.py b/slither/printers/summary/slithir_ssa.py index 53ac73997..b743dbdf2 100644 --- a/slither/printers/summary/slithir_ssa.py +++ b/slither/printers/summary/slithir_ssa.py @@ -21,6 +21,8 @@ class PrinterSlithIRSSA(AbstractPrinter): txt = "" for contract in self.contracts: + if contract.is_top_level: + continue txt += 'Contract {}'.format(contract.name) + '\n' for function in contract.functions: txt += '\tFunction {}'.format(function.canonical_name) + '\n' From 955ad39d749a4d9cc2cd066d2ecb58bd355d1e91 Mon Sep 17 00:00:00 2001 From: samczsun Date: Thu, 25 Jun 2020 12:01:55 -0400 Subject: [PATCH 14/73] fix ordering of sons when parsing for loop --- slither/core/cfg/node.py | 4 ++-- slither/core/declarations/function.py | 2 +- slither/solc_parsing/declarations/function.py | 20 +++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index f5ccef2db..094b78dc8 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -650,13 +650,13 @@ class Node(SourceMapping, ChildFunction): @property def son_true(self) -> Optional["Node"]: - if self.type == NodeType.IF: + if self.type == NodeType.IF or self.type == NodeType.IFLOOP: return self._sons[0] return None @property def son_false(self) -> Optional["Node"]: - if self.type == NodeType.IF and len(self._sons) >= 1: + if (self.type == NodeType.IF or self.type == NodeType.IFLOOP) and len(self._sons) >= 1: return self._sons[1] return None diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index d28dabb2c..86ce74e56 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -1282,7 +1282,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): if node.irs: label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) content += '{}[label="{}"];\n'.format(node.node_id, label) - if node.type == NodeType.IF: + if node.type == NodeType.IF or node.type == NodeType.IFLOOP: true_node = node.son_true if true_node: content += '{}->{}[label="True"];\n'.format(node.node_id, true_node.node_id) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 4572c6766..fafa4527a 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -398,18 +398,24 @@ class FunctionSolc: node_condition = self._new_node(NodeType.IFLOOP, condition["src"]) node_condition.add_unparsed_expression(condition) link_underlying_nodes(node_startLoop, node_condition) - link_underlying_nodes(node_condition, node_endLoop) + + node_beforeBody = node_condition else: - node_condition = node_startLoop + node_condition = None + + node_beforeBody = node_startLoop + + node_body = self._parse_statement(body, node_beforeBody) - node_body = self._parse_statement(body, node_condition) + if node_condition: + link_underlying_nodes(node_condition, node_endLoop) node_LoopExpression = None if loop_expression: node_LoopExpression = self._parse_statement(loop_expression, node_body) - link_underlying_nodes(node_LoopExpression, node_condition) + link_underlying_nodes(node_LoopExpression, node_beforeBody) else: - link_underlying_nodes(node_body, node_condition) + link_underlying_nodes(node_body, node_beforeBody) if not condition: if not loop_expression: @@ -497,13 +503,15 @@ class FunctionSolc: # expression = parse_expression(candidate, self) node_condition.add_unparsed_expression(expression) link_underlying_nodes(node_startLoop, node_condition) - link_underlying_nodes(node_condition, node_endLoop) hasCondition = True else: hasCondition = False node_statement = self._parse_statement(children[-1], node_condition) + if hasCondition: + link_underlying_nodes(node_condition, node_endLoop) + node_LoopExpression = node_statement if hasLoopExpression: if len(children) > 2: From 22d9c6827ef8a9efae46c61e4a9317a1af3eaa9f Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 10 Jul 2020 13:50:52 +0200 Subject: [PATCH 15/73] Fix slither-erc when checking state variable as function --- slither/tools/erc_conformance/erc/ercs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slither/tools/erc_conformance/erc/ercs.py b/slither/tools/erc_conformance/erc/ercs.py index 5af000357..4ec5a1b35 100644 --- a/slither/tools/erc_conformance/erc/ercs.py +++ b/slither/tools/erc_conformance/erc/ercs.py @@ -47,6 +47,7 @@ def _check_signature(erc_function, contract, ret): return function_return_type = [export_return_type_from_variable(state_variable_as_function)] + function = state_variable_as_function function_view = True else: From fa8deb62147c7bb4c432d7d6299b4382435a32e5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 12 Jul 2020 10:19:45 +0200 Subject: [PATCH 16/73] Detect suicidal external function (fix #527) --- slither/detectors/functions/suicidal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index a3599fcad..999e21705 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -46,7 +46,7 @@ Bob calls `kill` and destructs the contract.''' if func.is_constructor: return False - if func.visibility != 'public': + if func.visibility not in ['public', 'external']: return False calls = [c.name for c in func.internal_calls] @@ -60,7 +60,7 @@ Bob calls `kill` and destructs the contract.''' def detect_suicidal(self, contract): ret = [] - for f in [f for f in contract.functions if f.contract_declarer == contract]: + for f in contract.functions_declared: if self.detect_suicidal_func(f): ret.append(f) return ret From dfe0e657f586082be3768e4b4a0fc16a7da250da Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 12 Jul 2020 10:46:56 +0200 Subject: [PATCH 17/73] Node.son_true/son_false: minor --- slither/core/cfg/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 094b78dc8..f8dbe87ce 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -650,13 +650,13 @@ class Node(SourceMapping, ChildFunction): @property def son_true(self) -> Optional["Node"]: - if self.type == NodeType.IF or self.type == NodeType.IFLOOP: + if self.type in [NodeType.IF, NodeType.IFLOOP]: return self._sons[0] return None @property def son_false(self) -> Optional["Node"]: - if (self.type == NodeType.IF or self.type == NodeType.IFLOOP) and len(self._sons) >= 1: + if self.type in [NodeType.IF, NodeType.IFLOOP] and len(self._sons) >= 1: return self._sons[1] return None From 096915a51a67fe6d3509b54631210f84195eed69 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 12 Jul 2020 10:48:57 +0200 Subject: [PATCH 18/73] declaration.function: minor refactor if IF/IFLOOP detection --- slither/core/declarations/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 86ce74e56..b21fbe933 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -1282,7 +1282,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): if node.irs: label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) content += '{}[label="{}"];\n'.format(node.node_id, label) - if node.type == NodeType.IF or node.type == NodeType.IFLOOP: + if node.type in [NodeType.IF, NodeType.IFLOOP]: true_node = node.son_true if true_node: content += '{}->{}[label="True"];\n'.format(node.node_id, true_node.node_id) From 1b8210159ef2f8b74525df6af82ba5686f4133c8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 12 Jul 2020 11:19:17 +0200 Subject: [PATCH 19/73] Update documentation of state_variables_ordered (breaking change) --- slither/core/declarations/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 992e2d853..18ed5ac10 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -54,7 +54,7 @@ class Contract(ChildSlither, SourceMapping): self._structures: Dict[str, "Structure"] = {} self._events: Dict[str, "Event"] = {} self._variables: Dict[str, "StateVariable"] = {} - self._variables_ordered: List["StateVariable"] = [] # contain also shadowed variables + self._variables_ordered: List["StateVariable"] = [] self._modifiers: Dict[str, "Modifier"] = {} self._functions: Dict[str, "Function"] = {} self._linearizedBaseContracts = List[int] @@ -255,7 +255,7 @@ class Contract(ChildSlither, SourceMapping): @property def state_variables_ordered(self) -> List["StateVariable"]: """ - list(StateVariable): List of the state variables by order of declaration. Contains also shadowed variables + list(StateVariable): List of the state variables by order of declaration. """ return list(self._variables_ordered) From 7f069476013153192abc53bd5b47bdfa2840b3e8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 15 Jul 2020 18:18:31 +0200 Subject: [PATCH 20/73] Prevent infite loop on nested try/catch/if/then/else (fix #533) --- slither/solc_parsing/declarations/function.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index fafa4527a..1bffc1fd7 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -995,7 +995,8 @@ class FunctionSolc: link_nodes(node, end_node) else: for son in node.sons: - self._fix_catch(son, end_node) + if son != end_node: + self._fix_catch(son, end_node) def _add_param(self, param: Dict) -> LocalVariableSolc: From d8ccb645b76a9d2a0dc93a871a6b8975410ca86f Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 15:17:40 +0200 Subject: [PATCH 21/73] Improve type support for tuple (fix #529) --- slither/visitors/slithir/expression_to_slithir.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 69fb2a980..80e36bce0 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -115,11 +115,14 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, None) else: assert isinstance(right, TupleVariable) + tuple_types = [] for idx in range(len(left)): if not left[idx] is None: operation = Unpack(left[idx], right, idx) operation.set_expression(expression) + tuple_types.append(left[idx].type) self._result.append(operation) + right.set_type(tuple_types) set_val(expression, None) else: # Init of array, like From 9fb974c33fed6099a34fef63d3825aa530971b76 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 15:27:13 +0200 Subject: [PATCH 22/73] slither-flat: Add --pragma-solidity flag (fix #531) --- slither/tools/flattening/__main__.py | 5 +++++ slither/tools/flattening/flattening.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/slither/tools/flattening/__main__.py b/slither/tools/flattening/__main__.py index 3486150f4..e1755d73a 100644 --- a/slither/tools/flattening/__main__.py +++ b/slither/tools/flattening/__main__.py @@ -76,6 +76,10 @@ def parse_args(): "--remove-assert", help="Remove call to assert().", action="store_true" ) + group_patching.add_argument( + "--pragma-solidity", help="For a given solidity version.", action="store", default=None + ) + # Add default arguments from crytic-compile cryticparser.init(parser) @@ -96,6 +100,7 @@ def main(): remove_assert=args.remove_assert, private_to_internal=args.convert_private, export_path=args.dir, + pragma_solidity=args.pragma_solidity ) try: diff --git a/slither/tools/flattening/flattening.py b/slither/tools/flattening/flattening.py index 21638b05c..40199d80e 100644 --- a/slither/tools/flattening/flattening.py +++ b/slither/tools/flattening/flattening.py @@ -43,6 +43,7 @@ class Flattening: remove_assert=False, private_to_internal=False, export_path: Optional[str] = None, + pragma_solidity: Optional[str] = None ): self._source_codes: Dict[Contract, str] = {} self._slither = slither @@ -50,6 +51,7 @@ class Flattening: self._remove_assert = remove_assert self._use_abi_encoder_v2 = False self._private_to_internal = private_to_internal + self._pragma_solidity = pragma_solidity self._export_path: Path = DEFAULT_EXPORT_PATH if export_path is None else Path(export_path) @@ -165,8 +167,11 @@ class Flattening: :return: """ ret = "" - if self._slither.solc_version: + if self._pragma_solidity: + ret += f"pragma solidity {self._pragma_solidity};\n" + elif self._slither.solc_version: ret += f"pragma solidity {self._slither.solc_version};\n" + if self._use_abi_encoder_v2: ret += "pragma experimental ABIEncoderV2;\n" return ret From 5620b3bfbdd504eb7e12b564b81d3eff6c4947c4 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 15:29:48 +0200 Subject: [PATCH 23/73] slither-flat: Improve helper --- slither/tools/flattening/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/tools/flattening/__main__.py b/slither/tools/flattening/__main__.py index e1755d73a..aef147d05 100644 --- a/slither/tools/flattening/__main__.py +++ b/slither/tools/flattening/__main__.py @@ -77,7 +77,7 @@ def parse_args(): ) group_patching.add_argument( - "--pragma-solidity", help="For a given solidity version.", action="store", default=None + "--pragma-solidity", help="Set the solidity pragma with a given version.", action="store", default=None ) # Add default arguments from crytic-compile From 697e7bab46fd61c0de375d9188c62bf463430119 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 16:04:31 +0200 Subject: [PATCH 24/73] Improve support for variable name reused (fix #534) --- slither/solc_parsing/declarations/function.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 1bffc1fd7..a61bc2b6c 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -148,11 +148,13 @@ class FunctionSolc: # Use of while in case of collision # In the worst case, the name will be really long if local_var_parser.underlying_variable.name: - while local_var_parser.underlying_variable.name in self._function.variables: + 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 += "_scope_{}".format( self._counter_scope_local_variables ) self._counter_scope_local_variables += 1 + known_variables = [v.name for v in self._function.variables] if local_var_parser.reference_id is not None: self._variables_renamed[local_var_parser.reference_id] = local_var_parser self._function.variables_as_dict[ From c9fdc0b0afce903a7d4d365f3732eab29708d74f Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 17:06:14 +0200 Subject: [PATCH 25/73] Fix incorrect tuple type in case of low level call (fix #529) --- slither/slithir/convert.py | 5 ++++- slither/slithir/operations/low_level_call.py | 7 ++++++- slither/visitors/slithir/expression_to_slithir.py | 3 --- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c47739e95..f37810fd8 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -763,7 +763,10 @@ def convert_to_low_level(ir): new_ir.call_gas = ir.call_gas new_ir.call_value = ir.call_value new_ir.arguments = ir.arguments - new_ir.lvalue.set_type(ElementaryType('bool')) + if ir.slither.solc_version >= "0.5": + new_ir.lvalue.set_type([ElementaryType('bool'), ElementaryType('bytes')]) + else: + new_ir.lvalue.set_type(ElementaryType('bool')) new_ir.set_expression(ir.expression) new_ir.set_node(ir.node) return new_ir diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index 4b092cdc9..1db3ba850 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -95,9 +95,14 @@ class LowLevelCall(Call, OperationWithLValue): arguments = [] if self.arguments: arguments = self.arguments + return_type = self.lvalue.type + + if return_type and isinstance(return_type, list): + return_type = ','.join(str(x) for x in return_type) + txt = '{}({}) = LOW_LEVEL_CALL, dest:{}, function:{}, arguments:{} {} {}' return txt.format(self.lvalue, - self.lvalue.type, + return_type, self.destination, self.function_name, [str(x) for x in arguments], diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 80e36bce0..69fb2a980 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -115,14 +115,11 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, None) else: assert isinstance(right, TupleVariable) - tuple_types = [] for idx in range(len(left)): if not left[idx] is None: operation = Unpack(left[idx], right, idx) operation.set_expression(expression) - tuple_types.append(left[idx].type) self._result.append(operation) - right.set_type(tuple_types) set_val(expression, None) else: # Init of array, like From a7ebc0903b984cbbc3ef75dc31465fd4f65cada3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 17:25:02 +0200 Subject: [PATCH 26/73] Add storage_size for TypeInformation --- slither/core/solidity_types/type_information.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/slither/core/solidity_types/type_information.py b/slither/core/solidity_types/type_information.py index 26dbda55a..a15bdb128 100644 --- a/slither/core/solidity_types/type_information.py +++ b/slither/core/solidity_types/type_information.py @@ -20,6 +20,18 @@ class TypeInformation(Type): def type(self) -> "Contract": return self._type + @property + def storage_size(self) -> Tuple[int, bool]: + """ + 32 is incorrect, as Type(x) return a kind of structure that can contain + an arbitrary number of value + As Type(x) cannot be directly stored, we are assuming that the correct storage size + will be handled by the fields access + + :return: + """ + return 32, True + def __str__(self): return f"type({self.type.name})" From 26cd41c51f7c5b2d423e38331ac87333ed8cbe80 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 19 Jul 2020 17:27:14 +0200 Subject: [PATCH 27/73] Missing import --- slither/core/solidity_types/type_information.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/solidity_types/type_information.py b/slither/core/solidity_types/type_information.py index a15bdb128..71c7ca3c5 100644 --- a/slither/core/solidity_types/type_information.py +++ b/slither/core/solidity_types/type_information.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Tuple from slither.core.solidity_types.type import Type From 2ee8aad7a6bf4ec20decb61d3d13cfd8282e5365 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 20 Jul 2020 09:48:36 +0200 Subject: [PATCH 28/73] Convert structure types to list of types if the lvalue is a tuple (fix #529 second issue) --- slither/slithir/convert.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index f37810fd8..cf678cde2 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,4 +1,5 @@ import logging +from typing import List from slither.core.declarations import (Contract, Enum, Event, Function, SolidityFunction, SolidityVariable, @@ -8,6 +9,7 @@ from slither.core.solidity_types import (ArrayType, ElementaryType, FunctionType, MappingType, UserDefinedType, TypeInformation) from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt +from slither.core.solidity_types.type import Type from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable from slither.slithir.variables import TupleVariable @@ -1005,6 +1007,22 @@ def convert_type_library_call(ir, lib_contract): return ir +def _convert_to_structure_to_list(return_type: Type) -> List[Type]: + """ + Convert structure elements types to a list of types + Recursive function + + :param return_type: + :return: + """ + if isinstance(return_type, UserDefinedType) and isinstance(return_type.type, Structure): + ret = [] + for v in return_type.type.elems_ordered: + ret += _convert_to_structure_to_list(v.type) + return ret + return [return_type.type] + + def convert_type_of_high_and_internal_level_call(ir, contract): func = None if isinstance(ir, InternalCall): @@ -1068,6 +1086,15 @@ def convert_type_of_high_and_internal_level_call(ir, contract): else: return_type = func.type if return_type: + + # If the return type is a structure, but the lvalue is a tuple + # We convert the type of the structure to a list of element + # TODO: explore to replace all tuple variables by structures + if (isinstance(ir.lvalue, TupleVariable) and + isinstance(return_type, UserDefinedType) and + isinstance(return_type.type, Structure)): + return_type = _convert_to_structure_to_list(return_type) + ir.lvalue.set_type(return_type) else: ir.lvalue = None From 945ff10aefee59ef0586f1b7aff8c688dd31d3e7 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 20 Jul 2020 21:00:29 +0200 Subject: [PATCH 29/73] Yul: minor modifs - Rename YulObject to YulBlock - Add some comments --- slither/core/declarations/function.py | 8 +++++++- slither/core/expressions/binary_operation.py | 3 +++ slither/solc_parsing/declarations/function.py | 10 +++++----- slither/solc_parsing/yul/parse_yul.py | 7 ++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 016f8af41..1dc3538b3 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -202,7 +202,13 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._name = new_name @property - def scope(self): + def scope(self) -> List[str]: + """ + Return a list of name representing the scope of the function + This is used to model nested functions declared in YUL + + :return: + """ return self._scope @scope.setter diff --git a/slither/core/expressions/binary_operation.py b/slither/core/expressions/binary_operation.py index 6f8c0f15f..591319d29 100644 --- a/slither/core/expressions/binary_operation.py +++ b/slither/core/expressions/binary_operation.py @@ -31,6 +31,9 @@ class BinaryOperationType(Enum): ANDAND = 17 # && OROR = 18 # || + # YUL specific operators + # TODO: investigate if we can remove these + # Find the types earlier on, and do the conversion DIVISION_SIGNED = 19 MODULO_SIGNED = 20 LESS_SIGNED = 21 diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index d87964ac0..a674c5975 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -18,7 +18,7 @@ from slither.solc_parsing.variables.local_variable_init_from_tuple import ( LocalVariableInitFromTupleSolc, ) from slither.solc_parsing.variables.variable_declaration import MultipleVariablesDeclaration -from slither.solc_parsing.yul.parse_yul import YulObject +from slither.solc_parsing.yul.parse_yul import YulBlock from slither.utils.expression_manipulations import SplitTernaryExpression from slither.visitors.expression.export_values import ExportValues from slither.visitors.expression.has_conditional import HasConditional @@ -81,7 +81,7 @@ class FunctionSolc: self.returns_src = SourceMapping() self._node_to_nodesolc: Dict[Node, NodeSolc] = dict() - self._node_to_yulobject: Dict[Node, YulObject] = dict() + self._node_to_yulobject: Dict[Node, YulBlock] = dict() self._local_variables_parser: List[ Union[LocalVariableSolc, LocalVariableInitFromTupleSolc] @@ -319,9 +319,9 @@ class FunctionSolc: self._node_to_nodesolc[node] = node_parser return node_parser - def _new_yul_object(self, src: Union[str, Dict]) -> YulObject: + def _new_yul_block(self, src: Union[str, Dict]) -> YulBlock: node = self._function.new_node(NodeType.ASSEMBLY, src) - yul_object = YulObject(self._function.contract, node, [self._function.name, f"asm_{len(self._node_to_yulobject)}"], parent_func=self._function) + yul_object = YulBlock(self._function.contract, node, [self._function.name, f"asm_{len(self._node_to_yulobject)}"], parent_func=self._function) self._node_to_yulobject[node] = yul_object return yul_object @@ -821,7 +821,7 @@ class FunctionSolc: # Added with solc 0.6 - the yul code is an AST if 'AST' in statement: self._function.contains_assembly = True - yul_object = self._new_yul_object(statement['src']) + yul_object = self._new_yul_block(statement['src']) entrypoint = yul_object.entrypoint exitpoint = yul_object.convert(statement['AST']) diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index e22c0f5b4..7812b92d0 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -214,7 +214,12 @@ class YulFunction(YulScope): return yul_node -class YulObject(YulScope): +class YulBlock(YulScope): + """ + A YulBlock represents a standalone yul component. + For example an inline assembly block + + """ __slots__ = ["_entrypoint", "_parent_func", "_nodes"] def __init__(self, contract: Contract, entrypoint: Node, id: List[str], **kwargs): From a474896e508ecc8eaca537958d90292f5543ec92 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 21 Jul 2020 21:28:44 +0200 Subject: [PATCH 30/73] Add padding on function id printer (fix #500) --- slither/printers/summary/function_ids.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 75c3e046a..2ebbc1bdf 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -27,11 +27,13 @@ class FunctionIds(AbstractPrinter): table = MyPrettyTable(['Name', 'ID']) for function in contract.functions: if function.visibility in ['public', 'external']: - table.add_row([function.solidity_signature, hex(get_function_id(function.solidity_signature))]) + function_id = get_function_id(function.solidity_signature) + table.add_row([function.solidity_signature, f"{function_id:#0{10}x}"]) for variable in contract.state_variables: if variable.visibility in ['public']: sig = variable.function_name - table.add_row([sig, hex(get_function_id(sig))]) + function_id = get_function_id(sig) + table.add_row([sig, f"{function_id:#0{10}x}"]) txt += str(table) + '\n' all_tables.append((contract.name, table)) From 53dc37388e2ab09b2d0619f3d41a88ee4ffed446 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 22 Jul 2020 21:06:17 +0200 Subject: [PATCH 31/73] Several improvements in tuples and abi.decode support abi.decode: - Better IR output - Propagate types (fix #529 third cases) - Correct parsing in abi.decode parameters in case of arrays Tuples: - Improve support of tuple singleton (the generated IR was missing an Unpack operation) --- .../elementary_type_name_expression.py | 5 +++++ slither/slithir/convert.py | 6 ++++++ slither/slithir/operations/solidity_call.py | 19 ++++++++++++++----- slither/solc_parsing/declarations/function.py | 2 ++ .../expressions/expression_parsing.py | 8 ++++++-- .../visitors/slithir/expression_to_slithir.py | 9 +++++++++ 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/slither/core/expressions/elementary_type_name_expression.py b/slither/core/expressions/elementary_type_name_expression.py index 6c10acd5b..217217fbc 100644 --- a/slither/core/expressions/elementary_type_name_expression.py +++ b/slither/core/expressions/elementary_type_name_expression.py @@ -15,5 +15,10 @@ class ElementaryTypeNameExpression(Expression): def type(self) -> Type: return self._type + @type.setter + def type(self, new_type: Type): + assert isinstance(new_type, Type) + self._type = new_type + def __str__(self): return str(self._type) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 810b0e878..7458854f1 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -803,6 +803,12 @@ def convert_to_solidity_func(ir): new_ir.set_node(ir.node) if isinstance(call.return_type, list) and len(call.return_type) == 1: new_ir.lvalue.set_type(call.return_type[0]) + elif (isinstance(new_ir.lvalue, TupleVariable) and + call == SolidityFunction("abi.decode()") and + len(new_ir.arguments) == 2 and + isinstance(new_ir.arguments[1], list)): + types = [x for x in new_ir.arguments[1]] + new_ir.lvalue.set_type(types) else: new_ir.lvalue.set_type(call.return_type) return new_ir diff --git a/slither/slithir/operations/solidity_call.py b/slither/slithir/operations/solidity_call.py index a0ec01637..4d4ea7592 100644 --- a/slither/slithir/operations/solidity_call.py +++ b/slither/slithir/operations/solidity_call.py @@ -30,8 +30,17 @@ class SolidityCall(Call, OperationWithLValue): return self._type_call def __str__(self): - args = [str(a) for a in self.arguments] - return str(self.lvalue) +' = SOLIDITY_CALL {}({})'.format(self.function.full_name, ','.join(args)) - # return str(self.lvalue) +' = INTERNALCALL {} (arg {})'.format(self.function, - # self.nbr_arguments) - + if (self.function == SolidityFunction("abi.decode()") and + len(self.arguments) == 2 and + isinstance(self.arguments[1], list)): + args = str(self.arguments[0]) + '(' + ','.join([str(a) for a in self.arguments[1]]) + ')' + else: + args = ','.join([str(a) for a in self.arguments]) + + lvalue = '' + if self.lvalue: + if isinstance(self.lvalue.type, (list,)): + lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + else: + lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) + return lvalue + 'SOLIDITY_CALL {}({})'.format(self.function.full_name, args) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index a674c5975..b564bfb93 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -631,6 +631,8 @@ class FunctionSolc: i = 0 new_node = node for variable in variables: + if variable is None: + continue init = inits[i] src = variable["src"] i = i + 1 diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 4b78a2a10..fb1764dcc 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -421,7 +421,6 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres # | Expression '?' Expression ':' Expression # | Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression # | PrimaryExpression - # The AST naming does not follow the spec name = expression[caller_context.get_key()] is_compact_ast = caller_context.is_compact_ast @@ -646,7 +645,12 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres # if abi.decode is used # For example, abi.decode(data, ...(uint[]) ) if right is None: - return parse_expression(left, caller_context) + ret = parse_expression(left, caller_context) + # Nested array are not yet available in abi.decode + if isinstance(ret, ElementaryTypeNameExpression): + old_type = ret.type + ret.type = ArrayType(old_type, None) + return ret left_expression = parse_expression(left, caller_context) right_expression = parse_expression(right, caller_context) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 19f9fd782..3606dce4b 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -5,6 +5,7 @@ from slither.core.expressions import (AssignmentOperationType, UnaryOperationType, BinaryOperationType) from slither.core.solidity_types import ArrayType, ElementaryType from slither.core.solidity_types.type import Type +from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete, Index, InitArray, InternalCall, Member, NewArray, NewContract, @@ -130,6 +131,14 @@ class ExpressionToSlithIR(ExpressionVisitor): operation.set_expression(expression) self._result.append(operation) set_val(expression, None) + # Tuple with only one element. We need to convert the assignment to a Unpack + # Ex: + # (uint a,,) = g() + elif isinstance(left, LocalVariableInitFromTuple) and left.tuple_index: + operation = Unpack(left, right, left.tuple_index) + operation.set_expression(expression) + self._result.append(operation) + set_val(expression, None) else: # Init of array, like # uint8[2] var = [1,2]; From 26ca40b0aeb6e6b68097a445b41f916f4150bb93 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 22 Jul 2020 21:58:36 +0200 Subject: [PATCH 32/73] Add partial support of array slices (fix #545) --- slither/solc_parsing/expressions/expression_parsing.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index fb1764dcc..7d14a0026 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -763,4 +763,13 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres call.set_offset(src, caller_context.slither) return call + elif name == "IndexRangeAccess": + # For now, we convert array slices to a direct array access + # As a result the generated IR will lose the slices information + # As far as I understand, array slice are only used in abi.decode + # https://solidity.readthedocs.io/en/v0.6.12/types.html + # TODO: Investigate array slices usage and implication for the IR + base = parse_expression(expression["baseExpression"], caller_context) + return base + raise ParsingError("Expression not parsed %s" % name) From 650e3ae99bd8106e11dc0689c2b8812c3d8b398c Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 22 Jul 2020 22:13:08 +0200 Subject: [PATCH 33/73] Improve abi.decode support in case of single type to convert --- slither/slithir/convert.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 7458854f1..57248fc2a 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -809,6 +809,10 @@ def convert_to_solidity_func(ir): isinstance(new_ir.arguments[1], list)): types = [x for x in new_ir.arguments[1]] new_ir.lvalue.set_type(types) + # abi.decode where the type to decode is a singleton + # abi.decode(a, (uint)) + elif call == SolidityFunction("abi.decode()") and len(new_ir.arguments) == 2: + new_ir.lvalue.set_type(new_ir.arguments[1]) else: new_ir.lvalue.set_type(call.return_type) return new_ir From e9f4283323380459b288d31be4eb74e565bb5c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 22 Jul 2020 20:19:55 -0300 Subject: [PATCH 34/73] linting: Solve Markdown linting errors --- CONTRIBUTING.md | 12 ++++++------ README.md | 18 +++++++++--------- plugin_example/README.md | 5 ++--- slither/tools/demo/README.md | 2 +- slither/tools/erc_conformance/README.md | 2 +- tests/check-kspec/safeAdd/spec.md | 1 + 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e767e47c..13bdc5859 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,24 @@ # Contributing to Slither First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements. -If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels. +If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels. -# Bug reports and feature suggestions +## Bug reports and feature suggestions Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead. -# Questions +## Questions Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel). -# Code +## Code Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/). Some pull request guidelines: -- Work from the [`dev`](https://github.com/crytic/slither/tree/dev) branch. We performed extensive tests prior to merging anything to `master`, working from `dev` will allow us to merge your work faster. +- Work from the [`dev`](https://github.com/crytic/slither/tree/dev) branch. We performed extensive tests prior to merging anything to `master`, working from `dev` will allow us to merge your work faster. - Minimize irrelevant changes (formatting, whitespace, etc) to code that would otherwise not be touched by this patch. Save formatting or style corrections for a separate pull request that does not make any semantic changes. - When possible, large changes should be split up into smaller focused pull requests. - Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable. - Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own. -# Development Environment +## Development Environment Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation). diff --git a/README.md b/README.md index d9c9ebe74..0426c250a 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s ## Bugs and Optimizations Detection Run Slither on a Truffle/Embark/Dapp/Etherlime application: -``` +```bash slither . ``` Run Slither on a single file: -``` -$ slither tests/uninitialized.sol +```bash +slither tests/uninitialized.sol ``` For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. @@ -136,15 +136,15 @@ Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), ### Using Pip -``` -$ pip3 install slither-analyzer +```bash +pip3 install slither-analyzer ``` ### Using Git ```bash -$ git clone https://github.com/crytic/slither.git && cd slither -$ python3 setup.py install +git clone https://github.com/crytic/slither.git && cd slither +python3 setup.py install ``` We recommend using an Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git. @@ -153,13 +153,13 @@ We recommend using an Python virtual environment, as detailed in the [Developer Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container. -``` +```bash docker pull trailofbits/eth-security-toolbox ``` To share a directory in the container: -``` +```bash docker run -it -v /home/share:/share trailofbits/eth-security-toolbox ``` diff --git a/plugin_example/README.md b/plugin_example/README.md index fc711f5d1..6f8c4a6c1 100644 --- a/plugin_example/README.md +++ b/plugin_example/README.md @@ -7,13 +7,12 @@ See the [detector documentation](https://github.com/trailofbits/slither/wiki/Add ## Architecture - `setup.py`: Contain the plugin information -- `slither_my_plugin/__init__.py`: Contain `make_plugin()`. The function must return the list of new detectors and printers +- `slither_my_plugin/__init__.py`: Contain `make_plugin()`. The function must return the list of new detectors and printers - `slither_my_plugin/detectors/example.py`: Detector plugin skeleton. Once these files are updated with your plugin, you can install it: -``` +```bash python setup.py develop ``` We recommend to use a Python virtual environment (for example: [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)). - diff --git a/slither/tools/demo/README.md b/slither/tools/demo/README.md index 00bdec0b4..2ed90692c 100644 --- a/slither/tools/demo/README.md +++ b/slither/tools/demo/README.md @@ -1,4 +1,4 @@ -## Demo +# Demo This directory contains an example of Slither utility. diff --git a/slither/tools/erc_conformance/README.md b/slither/tools/erc_conformance/README.md index a04677a65..6b50ce27f 100644 --- a/slither/tools/erc_conformance/README.md +++ b/slither/tools/erc_conformance/README.md @@ -1 +1 @@ -## ERC conformance \ No newline at end of file +# ERC conformance diff --git a/tests/check-kspec/safeAdd/spec.md b/tests/check-kspec/safeAdd/spec.md index 5fa1f516a..433ad25c5 100644 --- a/tests/check-kspec/safeAdd/spec.md +++ b/tests/check-kspec/safeAdd/spec.md @@ -1,3 +1,4 @@ +# spec ```act behaviour add of SafeAdd From 3f2527f227f480b33f823db5f8185d29cc510553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 22 Jul 2020 20:37:56 -0300 Subject: [PATCH 35/73] linting: Solve shell linting errors --- scripts/fix_travis_relative_paths.sh | 1 + scripts/tests_generate_expected_json_4.sh | 6 ++-- scripts/tests_generate_expected_json_5.sh | 6 ++-- scripts/travis_test_4.sh | 6 ++-- scripts/travis_test_5.sh | 15 ++++------ scripts/travis_test_cli.sh | 14 +++------- scripts/travis_test_dapp.sh | 9 +++--- scripts/travis_test_data_dependency.sh | 4 +-- scripts/travis_test_embark.sh | 9 +++--- scripts/travis_test_erc.sh | 5 ++-- scripts/travis_test_etherlime.sh | 7 +++-- scripts/travis_test_etherscan.sh | 10 +++---- scripts/travis_test_find_paths.sh | 4 +-- scripts/travis_test_kspec.sh | 4 +-- scripts/travis_test_printers.sh | 4 +-- scripts/travis_test_prop.sh | 4 +-- scripts/travis_test_simil.sh | 4 +-- scripts/travis_test_slither_config.sh | 6 ++-- scripts/travis_test_truffle.sh | 7 +++-- scripts/travis_test_upgradability.sh | 34 +++++++++++------------ 20 files changed, 74 insertions(+), 85 deletions(-) diff --git a/scripts/fix_travis_relative_paths.sh b/scripts/fix_travis_relative_paths.sh index 7c0a03d38..76de85ed9 100755 --- a/scripts/fix_travis_relative_paths.sh +++ b/scripts/fix_travis_relative_paths.sh @@ -1,3 +1,4 @@ +#!/bin/sh CURRENT_PATH=$(pwd) TRAVIS_PATH='/home/travis/build/crytic/slither' for f in tests/expected_json/*json; do diff --git a/scripts/tests_generate_expected_json_4.sh b/scripts/tests_generate_expected_json_4.sh index 527a8abc3..4e4750cde 100755 --- a/scripts/tests_generate_expected_json_4.sh +++ b/scripts/tests_generate_expected_json_4.sh @@ -9,11 +9,11 @@ generate_expected_json(){ # generate output filename # e.g. file: uninitialized.sol detector: uninitialized-state # ---> uninitialized.uninitialized-state.json - output_filename="$DIR/../tests/expected_json/$(basename $1 .sol).$2.json" - output_filename_txt="$DIR/../tests/expected_json/$(basename $1 .sol).$2.txt" + output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" + output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt" # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" --solc solc-0.4.25 > $output_filename_txt 2>&1 + slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" --solc solc-0.4.25 > "$output_filename_txt" 2>&1 sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i diff --git a/scripts/tests_generate_expected_json_5.sh b/scripts/tests_generate_expected_json_5.sh index 065bb6334..b4f70bbbe 100755 --- a/scripts/tests_generate_expected_json_5.sh +++ b/scripts/tests_generate_expected_json_5.sh @@ -10,11 +10,11 @@ generate_expected_json(){ # generate output filename # e.g. file: uninitialized.sol detector: uninitialized-state # ---> uninitialized.uninitialized-state.json - output_filename="$DIR/../tests/expected_json/$(basename $1 .sol).$2.json" - output_filename_txt="$DIR/../tests/expected_json/$(basename $1 .sol).$2.txt" + output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" + output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt" # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" --solc solc-0.5.1 > $output_filename_txt 2>&1 + slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" --solc solc-0.5.1 > "$output_filename_txt" 2>&1 sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i diff --git a/scripts/travis_test_4.sh b/scripts/travis_test_4.sh index aae98669e..35059d08b 100755 --- a/scripts/travis_test_4.sh +++ b/scripts/travis_test_4.sh @@ -9,14 +9,14 @@ TRAVIS_PATH='/home/travis/build/crytic/slither' # test_slither file.sol detectors test_slither(){ - expected="$DIR/../tests/expected_json/$(basename $1 .sol).$2.json" + expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" # run slither detector on input file and save output as json slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" --solc solc-0.4.25 if [ $? -eq 255 ] then echo "Slither crashed" - exit -1 + exit 255 fi if [ ! -f "$DIR/tmp-test.json" ]; then @@ -44,7 +44,7 @@ test_slither(){ if [ $? -eq 255 ] then echo "Slither crashed" - exit -1 + exit 255 fi if [ ! -f "$DIR/tmp-test.json" ]; then diff --git a/scripts/travis_test_5.sh b/scripts/travis_test_5.sh index 3a58bc1fd..cada6804a 100755 --- a/scripts/travis_test_5.sh +++ b/scripts/travis_test_5.sh @@ -10,14 +10,14 @@ TRAVIS_PATH='/home/travis/build/crytic/slither' # test_slither file.sol detectors test_slither(){ - expected="$DIR/../tests/expected_json/$(basename $1 .sol).$2.json" + expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" # run slither detector on input file and save output as json slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" --solc solc-0.5.1 if [ $? -eq 255 ] then echo "Slither crashed" - exit -1 + exit 255 fi if [ ! -f "$DIR/tmp-test.json" ]; then @@ -44,7 +44,7 @@ test_slither(){ if [ $? -eq 255 ] then echo "Slither crashed" - exit -1 + exit 255 fi if [ ! -f "$DIR/tmp-test.json" ]; then @@ -102,18 +102,15 @@ test_slither tests/too_many_digits.sol "too-many-digits" ### Test scripts -python examples/scripts/functions_called.py examples/scripts/functions_called.sol -if [ $? -ne 0 ]; then +if ! python examples/scripts/functions_called.py examples/scripts/functions_called.sol; then exit 1 fi -python examples/scripts/functions_writing.py examples/scripts/functions_writing.sol -if [ $? -ne 0 ]; then +if ! python examples/scripts/functions_writing.py examples/scripts/functions_writing.sol; then exit 1 fi -python examples/scripts/variable_in_condition.py examples/scripts/variable_in_condition.sol -if [ $? -ne 0 ]; then +if ! python examples/scripts/variable_in_condition.py examples/scripts/variable_in_condition.sol; then exit 1 fi exit 0 diff --git a/scripts/travis_test_cli.sh b/scripts/travis_test_cli.sh index ae0ece063..5bce9008e 100755 --- a/scripts/travis_test_cli.sh +++ b/scripts/travis_test_cli.sh @@ -1,24 +1,18 @@ #!/usr/bin/env bash -### Test +### Test -slither "tests/*.json" --solc-ast --ignore-return-value - -if [ $? -ne 0 ]; then +if ! slither "tests/*.json" --solc-ast --ignore-return-value; then echo "--solc-ast failed" exit 1 fi -slither "tests/*0.5*.sol" --solc-disable-warnings --ignore-return-value - -if [ $? -ne 0 ]; then +if ! slither "tests/*0.5*.sol" --solc-disable-warnings --ignore-return-value; then echo "--solc-disable-warnings failed" exit 1 fi -slither "tests/*0.5*.sol" --disable-color --ignore-return-value - -if [ $? -ne 0 ]; then +if ! slither "tests/*0.5*.sol" --disable-color --ignore-return-value; then echo "--disable-color failed" exit 1 fi diff --git a/scripts/travis_test_dapp.sh b/scripts/travis_test_dapp.sh index 49899c14f..bbe06efc6 100755 --- a/scripts/travis_test_dapp.sh +++ b/scripts/travis_test_dapp.sh @@ -3,18 +3,19 @@ ### Test Dapp integration mkdir test_dapp -cd test_dapp +cd test_dapp || exit 255 # The dapp init process makes a temporary local git repo and needs certain values to be set git config --global user.email "ci@trailofbits.com" git config --global user.name "CI User" curl https://nixos.org/nix/install | sh +# shellcheck disable=SC1090 . "$HOME/.nix-profile/etc/profile.d/nix.sh" nix-env -iA nixpkgs.cachix cachix use dapp -git clone --recursive https://github.com/dapphub/dapptools $HOME/.dapp/dapptools -nix-env -f $HOME/.dapp/dapptools -iA dapp seth solc hevm ethsign +git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools" +nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign dapp init @@ -26,4 +27,4 @@ then fi echo "Truffle test failed" -exit -1 +exit 255 diff --git a/scripts/travis_test_data_dependency.sh b/scripts/travis_test_data_dependency.sh index 4ef96457b..d137f654b 100755 --- a/scripts/travis_test_data_dependency.sh +++ b/scripts/travis_test_data_dependency.sh @@ -2,9 +2,7 @@ ### Test data dependecy -python ./examples/scripts/data_dependency.py ./examples/scripts/data_dependency.sol - -if [ $? -ne 0 ]; then +if ! python ./examples/scripts/data_dependency.py ./examples/scripts/data_dependency.sol; then echo "data dependency failed" exit 1 fi diff --git a/scripts/travis_test_embark.sh b/scripts/travis_test_embark.sh index 118046260..147f7243d 100755 --- a/scripts/travis_test_embark.sh +++ b/scripts/travis_test_embark.sh @@ -3,24 +3,25 @@ ### Test embark integration mkdir test_embark -cd test_embark +cd test_embark || exit 255 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash +# shellcheck disable=SC1090 source ~/.nvm/nvm.sh nvm install 10.17.0 nvm use 10.17.0 npm install -g embark@4.2.0 embark demo -cd embark_demo +cd embark_demo || exit 255 npm install slither . --embark-overwrite-config if [ $? -eq 3 ] -then +then exit 0 fi echo "Embark test failed" -exit -1 +exit 255 diff --git a/scripts/travis_test_erc.sh b/scripts/travis_test_erc.sh index 908760631..12e973137 100755 --- a/scripts/travis_test_erc.sh +++ b/scripts/travis_test_erc.sh @@ -6,15 +6,14 @@ DIR_TESTS="tests/check-erc" slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 --solc solc-0.5.0 > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-erc 1 failed" cat test_1.txt echo "" cat "$DIR_TESTS/test_1.txt" - exit -1 + exit 255 fi rm test_1.txt - diff --git a/scripts/travis_test_etherlime.sh b/scripts/travis_test_etherlime.sh index d0b5b43cc..d7f7adfd9 100755 --- a/scripts/travis_test_etherlime.sh +++ b/scripts/travis_test_etherlime.sh @@ -3,9 +3,10 @@ ### Test etherlime integration mkdir test_etherlime -cd test_etherlime +cd test_etherlime || exit 255 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash +# shellcheck disable=SC1090 source ~/.nvm/nvm.sh nvm install 10.17.0 nvm use 10.17.0 @@ -15,9 +16,9 @@ etherlime init slither . if [ $? -eq 6 ] -then +then exit 0 fi echo "Etherlime test failed" -exit -1 +exit 255 diff --git a/scripts/travis_test_etherscan.sh b/scripts/travis_test_etherscan.sh index 528d8e72e..0398fa48f 100755 --- a/scripts/travis_test_etherscan.sh +++ b/scripts/travis_test_etherscan.sh @@ -3,24 +3,24 @@ ### Test etherscan integration mkdir etherscan -cd etherscan +cd etherscan || exit 255 wget -O solc-0.4.25 https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux chmod +x solc-0.4.25 -slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --solc "./solc-0.4.25" --etherscan-apikey $GITHUB_ETHERSCAN +slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --solc "./solc-0.4.25" --etherscan-apikey "$GITHUB_ETHERSCAN" if [ $? -ne 5 ] then echo "Etherscan test failed" - exit -1 + exit 255 fi -slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --solc "./solc-0.4.25" --etherscan-apikey $GITHUB_ETHERSCAN +slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --solc "./solc-0.4.25" --etherscan-apikey "$GITHUB_ETHERSCAN" if [ $? -ne 70 ] then echo "Etherscan test failed" - exit -1 + exit 255 fi diff --git a/scripts/travis_test_find_paths.sh b/scripts/travis_test_find_paths.sh index 85c49f733..088d02e65 100755 --- a/scripts/travis_test_find_paths.sh +++ b/scripts/travis_test_find_paths.sh @@ -6,11 +6,11 @@ DIR_TESTS="tests/possible_paths" slither-find-paths "$DIR_TESTS/paths.sol" A.destination --solc solc-0.5.0 > test_possible_paths.txt 2>&1 DIFF=$(diff test_possible_paths.txt "$DIR_TESTS/paths.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-find-paths failed" cat test_possible_paths.txt cat "$DIR_TESTS/paths.txt" - exit -1 + exit 255 fi rm test_possible_paths.txt diff --git a/scripts/travis_test_kspec.sh b/scripts/travis_test_kspec.sh index 341af9526..7fa36858b 100755 --- a/scripts/travis_test_kspec.sh +++ b/scripts/travis_test_kspec.sh @@ -4,13 +4,13 @@ DIR_TESTS="tests/check-kspec" slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md" --solc solc-0.5.0 > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-upgradeability 1 failed" cat test_1.txt echo "" cat "$DIR_TESTS/test_1.txt" - exit -1 + exit 255 fi rm test_1.txt diff --git a/scripts/travis_test_printers.sh b/scripts/travis_test_printers.sh index c47b96288..9ff51b0aa 100755 --- a/scripts/travis_test_printers.sh +++ b/scripts/travis_test_printers.sh @@ -5,9 +5,7 @@ # Needed for evm printer pip install evm-cfg-builder -slither "tests/*.json" --print all --json - - -if [ $? -ne 0 ]; then +if ! slither "tests/*.json" --print all --json -; then echo "Printer tests failed" exit 1 fi diff --git a/scripts/travis_test_prop.sh b/scripts/travis_test_prop.sh index 264e075f8..eed4517a4 100755 --- a/scripts/travis_test_prop.sh +++ b/scripts/travis_test_prop.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -### Test slither-prop +### Test slither-prop -cd examples/slither-prop +cd examples/slither-prop || exit 1 slither-prop . --contract ERC20Buggy if [ ! -f contracts/crytic/TestERC20BuggyTransferable.sol ]; then echo "slither-prop failed" diff --git a/scripts/travis_test_simil.sh b/scripts/travis_test_simil.sh index ccf332800..349f3eca9 100755 --- a/scripts/travis_test_simil.sh +++ b/scripts/travis_test_simil.sh @@ -10,12 +10,12 @@ pip3.6 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip DIR_TESTS="tests/simil" slither-simil info "" --filename $DIR_TESTS/../complex_func.sol --fname Complex.complexExternalWrites --solc solc-0.4.25 > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-simil failed" cat test_1.txt cat "$DIR_TESTS/test_1.txt" - exit -1 + exit 255 fi rm test_1.txt diff --git a/scripts/travis_test_slither_config.sh b/scripts/travis_test_slither_config.sh index e5f40afd5..525eb5f7c 100755 --- a/scripts/travis_test_slither_config.sh +++ b/scripts/travis_test_slither_config.sh @@ -1,10 +1,8 @@ #!/usr/bin/env bash -### Test +### Test -slither "tests/*.json" --config "tests/config/slither.config.json" - -if [ $? -ne 0 ]; then +if ! slither "tests/*.json" --config "tests/config/slither.config.json"; then echo "Config failed" exit 1 fi diff --git a/scripts/travis_test_truffle.sh b/scripts/travis_test_truffle.sh index 0e6bb8131..37591e948 100755 --- a/scripts/travis_test_truffle.sh +++ b/scripts/travis_test_truffle.sh @@ -3,9 +3,10 @@ ### Test truffle integration mkdir test_truffle -cd test_truffle +cd test_truffle || exit 255 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash +# shellcheck disable=SC1090 source ~/.nvm/nvm.sh nvm install --lts nvm use --lts @@ -15,9 +16,9 @@ truffle unbox metacoin slither . if [ $? -eq 5 ] -then +then exit 0 fi echo "Truffle test failed" -exit -1 +exit 255 diff --git a/scripts/travis_test_upgradability.sh b/scripts/travis_test_upgradability.sh index 610356c92..cfb30bebe 100755 --- a/scripts/travis_test_upgradability.sh +++ b/scripts/travis_test_upgradability.sh @@ -6,51 +6,51 @@ DIR_TESTS="tests/check-upgradeability" slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-upgradeability 1 failed" cat test_1.txt echo "" cat "$DIR_TESTS/test_1.txt" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2.sol" --new-contract-name ContractV2 > test_2.txt 2>&1 DIFF=$(diff test_2.txt "$DIR_TESTS/test_2.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-upgradeability 2 failed" cat test_2.txt echo "" cat "$DIR_TESTS/test_2.txt" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2_bug.sol" --new-contract-name ContractV2 > test_3.txt 2>&1 DIFF=$(diff test_3.txt "$DIR_TESTS/test_3.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-upgradeability 3 failed" cat test_3.txt echo "" cat "$DIR_TESTS/test_3.txt" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2_bug2.sol" --new-contract-name ContractV2 > test_4.txt 2>&1 DIFF=$(diff test_4.txt "$DIR_TESTS/test_4.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-upgradeability 4 failed" cat test_4.txt echo "" cat "$DIR_TESTS/test_4.txt" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_5.txt 2>&1 DIFF=$(diff test_5.txt "$DIR_TESTS/test_5.txt") -if [ "$DIFF" != "" ] +if [ "$DIFF" != "" ] then echo "slither-check-upgradeability 5 failed" cat test_5.txt @@ -58,7 +58,7 @@ then cat "$DIR_TESTS/test_5.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_5.txt 2>&1 @@ -71,7 +71,7 @@ then cat "$DIR_TESTS/test_5.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi @@ -85,7 +85,7 @@ then cat "$DIR_TESTS/test_6.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi @@ -99,7 +99,7 @@ then cat "$DIR_TESTS/test_7.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_inherits --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_8.txt 2>&1 @@ -112,7 +112,7 @@ then cat "$DIR_TESTS/test_8.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_double_call --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_9.txt 2>&1 @@ -125,7 +125,7 @@ then cat "$DIR_TESTS/test_9.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contract_v2_constant.sol" --new-contract-name ContractV2 > test_10.txt 2>&1 @@ -138,7 +138,7 @@ then cat "$DIR_TESTS/test_10.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi slither-check-upgradeability "$DIR_TESTS/contract_v1_var_init.sol" ContractV1 --solc solc-0.5.0 > test_11.txt 2>&1 @@ -151,7 +151,7 @@ then cat "$DIR_TESTS/test_11.txt" echo "" echo "$DIFF" - exit -1 + exit 255 fi rm test_1.txt From de0aa3f4bae6a5db01a50e0d7fc6ab0a035dfd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 22 Jul 2020 20:41:38 -0300 Subject: [PATCH 36/73] linting: Solve Python linting problems --- slither/core/declarations/function.py | 2 +- slither/core/expressions/call_expression.py | 8 -------- slither/detectors/abstract_detector.py | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 1dc3538b3..7e6873fd0 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -1553,7 +1553,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): if self._entry_point is None: return dict() # node, values - to_explore: List[Tuple[Node, Dict]] = [(self._entry_point, dict())] + to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, dict())] # node -> values explored: Dict = dict() # name -> instances diff --git a/slither/core/expressions/call_expression.py b/slither/core/expressions/call_expression.py index 229b8daf1..848eb221a 100644 --- a/slither/core/expressions/call_expression.py +++ b/slither/core/expressions/call_expression.py @@ -41,14 +41,6 @@ class CallExpression(Expression): def call_salt(self, salt): self._salt = salt - @property - def call_salt(self): - return self._salt - - @call_salt.setter - def call_salt(self, salt): - self._salt = salt - @property def called(self) -> Expression: return self._called diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 765b79d15..9b3138c39 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -150,7 +150,7 @@ class AbstractDetector(metaclass=abc.ABCMeta): if results and self.slither.triage_mode: while True: - indexes = input('Results to hide during next runs: "0,1,..." or "All" (enter to not hide results): '.format(len(results))) + indexes = input('Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format(len(results))) if indexes == 'All': self.slither.save_results_to_hide(results) return [] From 84307e77d723a5abb32736752dee6fa55aeef8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 22 Jul 2020 22:04:29 -0300 Subject: [PATCH 37/73] linting: Solve Dockerfile linting problems --- Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index babd8a56f..8a62e1ea4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ FROM ubuntu:bionic -LABEL name slither -LABEL src "https://github.com/trailofbits/slither" -LABEL creator trailofbits -LABEL dockerfile_maintenance trailofbits -LABEL desc "Static Analyzer for Solidity" +LABEL name=slither +LABEL src="https://github.com/trailofbits/slither" +LABEL creator=trailofbits +LABEL dockerfile_maintenance=trailofbits +LABEL desc="Static Analyzer for Solidity" RUN apt update \ && apt upgrade -y \ From a58d99ac2df5e0a33ef10c40d1fbcd451748253d Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 29 Jul 2020 09:12:11 +0200 Subject: [PATCH 38/73] Add --disallow-partial flag --- slither/__main__.py | 6 ++++++ slither/core/slither_core.py | 15 +++++++++++++++ slither/slither.py | 4 ++++ slither/solc_parsing/declarations/contract.py | 7 +++++-- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index cc15e9c60..d0b043cd9 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -425,6 +425,12 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=False) + # Disable the throw/catch on partial analyses + parser.add_argument('--disallow-partial', + help=argparse.SUPPRESS, + action="store_true", + default=False) + if len(sys.argv) == 1: parser.print_help(sys.stderr) sys.exit(1) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index a4e186772..eb3fa8d72 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -59,6 +59,10 @@ class SlitherCore(Context): self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {} + # If set to true, slither will not catch errors during parsing + self._disallow_partial: bool = False + + ################################################################################### ################################################################################### # region Source code @@ -365,6 +369,17 @@ class SlitherCore(Context): def contracts_with_missing_inheritance(self) -> Set: return self._contract_with_missing_inheritance + + @property + def disallow_partial(self) -> bool: + """ + Return true if partial analyses are disallowed + For example, codebase with duplicate names will lead to partial analyses + + :return: + """ + return self._disallow_partial + # endregion ################################################################################### ################################################################################### diff --git a/slither/slither.py b/slither/slither.py index 2eff55fa7..6d8f35a9d 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -43,6 +43,10 @@ class Slither(SlitherCore): """ super().__init__() self._parser: SlitherSolc # This could be another parser, like SlitherVyper, interface needs to be determined + + + self._disallow_partial: bool = kwargs.get("disallow_partial", False) + # list of files provided (see --splitted option) if isinstance(target, list): self._init_from_list(target) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index d348cdccc..c89f4ff6e 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -337,8 +337,11 @@ class ContractSolc: ################################################################################### def log_incorrect_parsing(self, error): - LOGGER.error(error) - self._contract.is_incorrectly_parsed = True + if self._contract.slither.disallow_partial: + raise ParsingError(error) + else: + LOGGER.error(error) + self._contract.is_incorrectly_parsed = True def analyze_content_modifiers(self): try: From d42f3981e5e46d8d8d474e815ce6c0f6b8f9ed96 Mon Sep 17 00:00:00 2001 From: samczsun Date: Wed, 29 Jul 2020 13:43:17 -0400 Subject: [PATCH 39/73] allow converting library to address --- slither/slithir/operations/type_conversion.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/slithir/operations/type_conversion.py b/slither/slithir/operations/type_conversion.py index 13b053b04..d80802054 100644 --- a/slither/slithir/operations/type_conversion.py +++ b/slither/slithir/operations/type_conversion.py @@ -1,3 +1,4 @@ +from slither.core.declarations import Contract from slither.core.solidity_types.type import Type from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue @@ -7,7 +8,7 @@ class TypeConversion(OperationWithLValue): def __init__(self, result, variable, variable_type): super().__init__() - assert is_valid_rvalue(variable) + assert is_valid_rvalue(variable) or isinstance(variable, Contract) assert is_valid_lvalue(result) assert isinstance(variable_type, Type) From 7dd74dc887cb60769684770b119fff2c63f95f36 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 09:06:30 +0200 Subject: [PATCH 40/73] Fix missing str conversion on reentrancy event --- slither/detectors/reentrancy/reentrancy_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/reentrancy/reentrancy_events.py b/slither/detectors/reentrancy/reentrancy_events.py index fa95dd979..d1e2c9043 100644 --- a/slither/detectors/reentrancy/reentrancy_events.py +++ b/slither/detectors/reentrancy/reentrancy_events.py @@ -78,7 +78,7 @@ If `d.()` reenters, the `Counter` events will be showed in an incorrect order, w for (func, calls, send_eth), events in result_sorted: calls = sorted(list(set(calls)), key=lambda x: x[0].node_id) send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id) - events = sorted(events, key=lambda x: (x.variable.name, x.node.node_id)) + events = sorted(events, key=lambda x: (str(x.variable.name), x.node.node_id)) info = ['Reentrancy in ', func, ':\n'] info += ['\tExternal calls:\n'] From ac27e68f9467dc368e0552d0704be863c310378d Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 09:39:49 +0200 Subject: [PATCH 41/73] Fix incorrect handling of tuple variable re-assignement (bug introduced xith #548) --- slither/visitors/slithir/expression_to_slithir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 3606dce4b..94c2fbffd 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -134,7 +134,7 @@ class ExpressionToSlithIR(ExpressionVisitor): # Tuple with only one element. We need to convert the assignment to a Unpack # Ex: # (uint a,,) = g() - elif isinstance(left, LocalVariableInitFromTuple) and left.tuple_index: + elif isinstance(left, LocalVariableInitFromTuple) and left.tuple_index and isinstance(right, TupleVariable): operation = Unpack(left, right, left.tuple_index) operation.set_expression(expression) self._result.append(operation) From a6a4d8ddc54d2379fa7e6034df2a27d7bd5a3fd4 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 10:03:06 +0200 Subject: [PATCH 42/73] Fix incorrect tuple index (https://github.com/crytic/slither/issues/529#issuecomment-662225727) --- slither/solc_parsing/declarations/function.py | 4 ++-- slither/visitors/slithir/expression_to_slithir.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index b564bfb93..d7845be0b 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -654,7 +654,6 @@ class FunctionSolc: i = 0 new_node = node for variable in statement["declarations"]: - i = i + 1 if variable: src = variable["src"] # Create a fake statement to be consistent @@ -668,6 +667,7 @@ class FunctionSolc: new_node = self._parse_variable_definition_init_tuple( new_statement, i, new_node ) + i = i + 1 var_identifiers = [] # craft of the expression doing the assignement @@ -737,7 +737,6 @@ class FunctionSolc: variables = [] for variable in variables_declaration: src = variable["src"] - i = i + 1 # Create a fake statement to be consistent new_statement = { self.get_key(): "VariableDefinitionStatement", @@ -749,6 +748,7 @@ class FunctionSolc: new_node = self._parse_variable_definition_init_tuple( new_statement, i, new_node ) + i = i + 1 var_identifiers = [] # craft of the expression doing the assignement for v in variables: diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 94c2fbffd..553e955f5 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -127,14 +127,20 @@ class ExpressionToSlithIR(ExpressionVisitor): assert isinstance(right, TupleVariable) for idx in range(len(left)): if not left[idx] is None: - operation = Unpack(left[idx], right, idx) + index = idx + # The following test is probably always true? + if isinstance(left[idx], LocalVariableInitFromTuple) and left[idx].tuple_index: + index = left[idx].tuple_index + operation = Unpack(left[idx], right, index) operation.set_expression(expression) self._result.append(operation) set_val(expression, None) # Tuple with only one element. We need to convert the assignment to a Unpack # Ex: # (uint a,,) = g() - elif isinstance(left, LocalVariableInitFromTuple) and left.tuple_index and isinstance(right, TupleVariable): + elif (isinstance(left, LocalVariableInitFromTuple) and + left.tuple_index is not None and + isinstance(right, TupleVariable)): operation = Unpack(left, right, left.tuple_index) operation.set_expression(expression) self._result.append(operation) From 37d2967e9dbc17057f19c4870606d97d4b65048d Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 10:10:07 +0200 Subject: [PATCH 43/73] Improve test on tuple_index existence --- slither/visitors/slithir/expression_to_slithir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 553e955f5..eb24bba4e 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -129,7 +129,7 @@ class ExpressionToSlithIR(ExpressionVisitor): if not left[idx] is None: index = idx # The following test is probably always true? - if isinstance(left[idx], LocalVariableInitFromTuple) and left[idx].tuple_index: + if isinstance(left[idx], LocalVariableInitFromTuple) and left[idx].tuple_index is not None: index = left[idx].tuple_index operation = Unpack(left[idx], right, index) operation.set_expression(expression) From de7b81fee4097d9c04daa9f49fd20026d35eab25 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 10:28:30 +0200 Subject: [PATCH 44/73] Add total ordering to Constant --- slither/slithir/variables/constant.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 1bee7e3bc..ff38aeb17 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -1,9 +1,11 @@ +from functools import total_ordering from decimal import Decimal from .variable import SlithIRVariable from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint from slither.utils.arithmetic import convert_subdenomination +@total_ordering class Constant(SlithIRVariable): def __init__(self, val, type=None, subdenomination=None): @@ -71,3 +73,12 @@ class Constant(SlithIRVariable): def __eq__(self, other): return self.value == other + + def __ne__(self, other): + return self.value != other + + def __lt__(self, other): + return self.value < other + + def __repr__(self): + return "%s" % (str(self.value)) \ No newline at end of file From bfaaf742f0c37d44d4a615697a91aa78e262686d Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 10:30:02 +0200 Subject: [PATCH 45/73] Add ending line to variables/constant.py --- slither/slithir/variables/constant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index ff38aeb17..5de70d0a9 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -81,4 +81,4 @@ class Constant(SlithIRVariable): return self.value < other def __repr__(self): - return "%s" % (str(self.value)) \ No newline at end of file + return "%s" % (str(self.value)) From d791d105ec3887522fcc59000ca3fa896cb290c3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 3 Aug 2020 11:01:45 +0200 Subject: [PATCH 46/73] Add temporary support for abi.decode on complex type (temporary solution until #566 is fixed) --- slither/slithir/convert.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 57248fc2a..e625a10b2 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -812,7 +812,10 @@ def convert_to_solidity_func(ir): # abi.decode where the type to decode is a singleton # abi.decode(a, (uint)) elif call == SolidityFunction("abi.decode()") and len(new_ir.arguments) == 2: - new_ir.lvalue.set_type(new_ir.arguments[1]) + # If the variable is a referenceVariable, we are lost + # See https://github.com/crytic/slither/issues/566 for potential solutions + if not isinstance(new_ir.arguments[1], ReferenceVariable): + new_ir.lvalue.set_type(new_ir.arguments[1]) else: new_ir.lvalue.set_type(call.return_type) return new_ir From c70f202138da0b889efba2f19e87e019c98ecebd Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 5 Aug 2020 14:42:51 +0200 Subject: [PATCH 47/73] Improve support for type() (fix #547) --- slither/core/declarations/solidity_variables.py | 1 + slither/slithir/convert.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 3f493147d..7a645f5d3 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -66,6 +66,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { # abi.decode returns an a list arbitrary types "abi.decode()": [], "type(address)": [], + "type()": [], # 0.6.8 changed type(address) to type() } diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index e625a10b2..97c8171e7 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -581,7 +581,7 @@ def propagate_types(ir, node): elif isinstance(ir, Send): ir.lvalue.set_type(ElementaryType('bool')) elif isinstance(ir, SolidityCall): - if ir.function.name == 'type(address)': + if ir.function.name in ['type(address)', 'type()']: ir.function.return_type = [TypeInformation(ir.arguments[0])] return_type = ir.function.return_type if len(return_type) == 1: From cd592e292372a0e6678e3f87600e60c1c6fa99fc Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 9 Aug 2020 20:01:01 +0200 Subject: [PATCH 48/73] Copy editing on all public detectors --- slither/detectors/attributes/const_functions_asm.py | 6 +++--- slither/detectors/attributes/const_functions_state.py | 8 ++++---- slither/detectors/attributes/constant_pragma.py | 2 +- slither/detectors/attributes/incorrect_solc.py | 4 ++-- slither/detectors/attributes/locked_ether.py | 6 +++--- slither/detectors/erc/incorrect_erc20_interface.py | 6 +++--- slither/detectors/erc/incorrect_erc721_interface.py | 6 +++--- slither/detectors/erc/unindexed_event_parameters.py | 9 +++++---- slither/detectors/functions/arbitrary_send.py | 8 ++++---- slither/detectors/functions/external_function.py | 6 +++--- .../detectors/naming_convention/naming_convention.py | 10 +++++----- slither/detectors/operations/low_level_calls.py | 2 +- .../operations/unchecked_low_level_return_values.py | 4 ++-- .../operations/unchecked_send_return_value.py | 8 ++++---- slither/detectors/operations/unused_return_values.py | 2 +- slither/detectors/operations/void_constructor.py | 6 +++--- slither/detectors/reentrancy/reentrancy_benign.py | 4 ++-- slither/detectors/reentrancy/reentrancy_eth.py | 8 ++++---- slither/detectors/reentrancy/reentrancy_events.py | 8 ++++---- slither/detectors/reentrancy/reentrancy_no_gas.py | 8 ++++---- .../reentrancy/reentrancy_read_before_write.py | 6 +++--- slither/detectors/shadowing/builtin_symbols.py | 4 ++-- slither/detectors/shadowing/local.py | 6 +++--- slither/detectors/slither/name_reused.py | 8 ++++---- slither/detectors/source/rtlo.py | 4 ++-- slither/detectors/statements/assembly.py | 2 +- .../detectors/statements/boolean_constant_equality.py | 6 +++--- .../detectors/statements/boolean_constant_misuse.py | 3 ++- slither/detectors/statements/calls_in_loop.py | 4 ++-- .../detectors/statements/controlled_delegatecall.py | 4 ++-- slither/detectors/statements/deprecated_calls.py | 4 ++-- slither/detectors/statements/divide_before_multiply.py | 6 +++--- .../detectors/statements/incorrect_strict_equality.py | 5 +++-- slither/detectors/statements/too_many_digits.py | 4 ++-- slither/detectors/statements/tx_origin.py | 2 +- slither/detectors/statements/type_based_tautology.py | 6 +++--- .../variables/possible_const_state_variables.py | 4 ++-- .../variables/uninitialized_local_variables.py | 2 +- .../variables/uninitialized_state_variables.py | 2 +- .../variables/uninitialized_storage_variables.py | 6 +++--- slither/detectors/variables/unused_state_variables.py | 2 +- .../external_function.external-function.txt | 2 +- .../naming_convention.naming-convention.txt | 2 +- 43 files changed, 109 insertions(+), 106 deletions(-) diff --git a/slither/detectors/attributes/const_functions_asm.py b/slither/detectors/attributes/const_functions_asm.py index f42aa337a..e76e97cb1 100644 --- a/slither/detectors/attributes/const_functions_asm.py +++ b/slither/detectors/attributes/const_functions_asm.py @@ -22,7 +22,7 @@ class ConstantFunctionsAsm(AbstractDetector): WIKI_DESCRIPTION = ''' Functions declared as `constant`/`pure`/`view` using assembly code. -`constant`/`pure`/`view` was not enforced prior Solidity 0.5. +`constant`/`pure`/`view` was not enforced prior to Solidity 0.5. Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification. As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).''' @@ -37,10 +37,10 @@ contract Constant{ } } ``` -`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract interacting with `Constant` in Solidity 0.5.0. +`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0. All the calls to `get` revert, breaking Bob's smart contract execution.''' - WIKI_RECOMMENDATION = 'Ensure that the attributes of contracts compiled prior to Solidity 0.5.0 are correct.' + WIKI_RECOMMENDATION = 'Ensure the attributes of contracts compiled prior to Solidity 0.5.0 are correct.' def _detect(self): """ Detect the constant function using assembly code diff --git a/slither/detectors/attributes/const_functions_state.py b/slither/detectors/attributes/const_functions_state.py index fb7f94c7b..f4e96bf85 100644 --- a/slither/detectors/attributes/const_functions_state.py +++ b/slither/detectors/attributes/const_functions_state.py @@ -20,9 +20,9 @@ class ConstantFunctionsState(AbstractDetector): WIKI_TITLE = 'Constant functions changing the state' WIKI_DESCRIPTION = ''' -Functions declared as `constant`/`pure`/`view` changing the state. +Functions declared as `constant`/`pure`/`view` change the state. -`constant`/`pure`/`view` was not enforced prior Solidity 0.5. +`constant`/`pure`/`view` was not enforced prior to Solidity 0.5. Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification. As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).''' @@ -37,10 +37,10 @@ contract Constant{ } } ``` -`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract interacting with `Constant` in Solidity 0.5.0. +`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0. All the calls to `get` revert, breaking Bob's smart contract execution.''' - WIKI_RECOMMENDATION = 'Ensure that the attributes of contracts compiled prior to Solidity 0.5.0 are correct.' + WIKI_RECOMMENDATION = 'Ensure that attributes of contracts compiled prior to Solidity 0.5.0 are correct.' def _detect(self): """ Detect the constant function changing the state diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 161ab985b..876037d76 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -20,7 +20,7 @@ class ConstantPragma(AbstractDetector): WIKI_TITLE = 'Different pragma directives are used' - WIKI_DESCRIPTION = 'Detect if different Solidity versions are used.' + WIKI_DESCRIPTION = 'Detect whether different Solidity versions are used.' WIKI_RECOMMENDATION = 'Use one Solidity version.' def _detect(self): diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index 2b01b69e0..3b676e5f3 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -30,8 +30,8 @@ class IncorrectSolc(AbstractDetector): WIKI_TITLE = 'Incorrect versions of Solidity' WIKI_DESCRIPTION = ''' -Solc frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks. -We recommend avoiding complex pragma statement.''' +`solc` frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks. +We also recommend avoiding complex `pragma` statement.''' WIKI_RECOMMENDATION = ''' Use Solidity 0.4.25 or 0.5.11. Consider using the latest version of Solidity for testing the compilation, and a trusted version for deploying.''' diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 97ffb0db2..343a5b7a2 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -20,8 +20,8 @@ class LockedEther(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether' - WIKI_TITLE = 'Contracts that lock ether' - WIKI_DESCRIPTION = 'Contract with a `payable` function, but without a withdraw capacity.' + WIKI_TITLE = 'Contracts that lock Ether' + WIKI_DESCRIPTION = 'Contract with a `payable` function, but without a withdrawal capacity.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity pragma solidity 0.4.24; @@ -30,7 +30,7 @@ contract Locked{ } } ``` -Every ether sent to `Locked` will be lost.''' +Every Ether sent to `Locked` will be lost.''' WIKI_RECOMMENDATION = 'Remove the payable attribute or add a withdraw function.' diff --git a/slither/detectors/erc/incorrect_erc20_interface.py b/slither/detectors/erc/incorrect_erc20_interface.py index b7782a66a..f50050493 100644 --- a/slither/detectors/erc/incorrect_erc20_interface.py +++ b/slither/detectors/erc/incorrect_erc20_interface.py @@ -18,7 +18,7 @@ class IncorrectERC20InterfaceDetection(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface' WIKI_TITLE = 'Incorrect erc20 interface' - WIKI_DESCRIPTION = 'Incorrect return values for ERC20 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.' + WIKI_DESCRIPTION = 'Incorrect return values for `ERC20` functions. A contract compiled with Solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract Token{ @@ -26,9 +26,9 @@ contract Token{ //... } ``` -`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct ERC20 interface implementation. Alice's contract is unable to interact with Bob's contract.''' +`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC20` interface implementation. Alice's contract is unable to interact with Bob's contract.''' - WIKI_RECOMMENDATION = 'Set the appropriate return values and value-types for the defined ERC20 functions.' + WIKI_RECOMMENDATION = 'Set the appropriate return values and types for the defined `ERC20` functions.' @staticmethod def incorrect_erc20_interface(signature): diff --git a/slither/detectors/erc/incorrect_erc721_interface.py b/slither/detectors/erc/incorrect_erc721_interface.py index e6a484631..fc242b752 100644 --- a/slither/detectors/erc/incorrect_erc721_interface.py +++ b/slither/detectors/erc/incorrect_erc721_interface.py @@ -17,7 +17,7 @@ class IncorrectERC721InterfaceDetection(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface' WIKI_TITLE = 'Incorrect erc721 interface' - WIKI_DESCRIPTION = 'Incorrect return values for ERC721 functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.' + WIKI_DESCRIPTION = 'Incorrect return values for `ERC721` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract Token{ @@ -25,9 +25,9 @@ contract Token{ //... } ``` -`Token.ownerOf` does not return an address as ERC721 expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct ERC721 interface implementation. Alice's contract is unable to interact with Bob's contract.''' +`Token.ownerOf` does not return an address like `ERC721` expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC721` interface implementation. Alice's contract is unable to interact with Bob's contract.''' - WIKI_RECOMMENDATION = 'Set the appropriate return values and value-types for the defined ERC721 functions.' + WIKI_RECOMMENDATION = 'Set the appropriate return values and vtypes for the defined `ERC721` functions.' @staticmethod def incorrect_erc721_interface(signature): diff --git a/slither/detectors/erc/unindexed_event_parameters.py b/slither/detectors/erc/unindexed_event_parameters.py index 096c63f16..d0069ffcc 100644 --- a/slither/detectors/erc/unindexed_event_parameters.py +++ b/slither/detectors/erc/unindexed_event_parameters.py @@ -16,8 +16,8 @@ class UnindexedERC20EventParameters(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters' - WIKI_TITLE = 'Unindexed ERC20 Event Parameters' - WIKI_DESCRIPTION = 'Detects that events defined by the ERC20 specification which are meant to have some parameters as `indexed`, are missing the `indexed` keyword.' + WIKI_TITLE = 'Unindexed ERC20 event oarameters' + WIKI_DESCRIPTION = 'Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract ERC20Bad { @@ -28,9 +28,10 @@ contract ERC20Bad { // ... } ``` -In this case, Transfer and Approval events should have the 'indexed' keyword on their two first parameters, as defined by the ERC20 specification. Failure to include these keywords will not include the parameter data in the transaction/block's bloom filter. This may cause external tooling searching for these parameters to overlook them, and fail to index logs from this token contract.''' +`Transfer` and `Approval` events should have the 'indexed' keyword on their two first parameters, as defined by the `ERC20` specification. +Failure to include these keywords will exclude the parameter data in the transaction/block's bloom filter, so external tooling searching for these parameters may overlook them and fail to index logs from this token contract.''' - WIKI_RECOMMENDATION = 'Add the `indexed` keyword to event parameters which should include it, according to the ERC20 specification.' + WIKI_RECOMMENDATION = 'Add the `indexed` keyword to event parameters that should include it, according to the `ERC20` specification.' STANDARD_JSON = False diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 2c608e73a..8659a4261 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -24,14 +24,14 @@ class ArbitrarySend(AbstractDetector): """ ARGUMENT = 'arbitrary-send' - HELP = 'Functions that send ether to arbitrary destinations' + HELP = 'Functions that send Ether to arbitrary destinations' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations' - WIKI_TITLE = 'Functions that send ether to arbitrary destinations' - WIKI_DESCRIPTION = 'Unprotected call to a function executing sending ethers to an arbitrary address.' + WIKI_TITLE = 'Functions that send Ether to arbitrary destinations' + WIKI_DESCRIPTION = 'Unprotected call to a function sending Ether to an arbitrary address.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract ArbitrarySend{ @@ -47,7 +47,7 @@ contract ArbitrarySend{ ``` Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance.''' - WIKI_RECOMMENDATION = 'Ensure that an arbitrary user cannot withdraw unauthorize funds.' + WIKI_RECOMMENDATION = 'Ensure that an arbitrary user cannot withdraw unauthorized funds.' def arbitrary_send(self, func): """ diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index f5d9aad61..9fd1b132b 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -14,14 +14,14 @@ class ExternalFunction(AbstractDetector): """ ARGUMENT = 'external-function' - HELP = 'Public function that could be declared as external' + HELP = 'Public function that could be declared external' IMPACT = DetectorClassification.OPTIMIZATION CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external' + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external' - WIKI_TITLE = 'Public function that could be declared as external' + WIKI_TITLE = 'Public function that could be declared external' WIKI_DESCRIPTION = '`public` functions that are never called by the contract should be declared `external` to save gas.' WIKI_RECOMMENDATION = 'Use the `external` attribute for functions never called from the contract.' diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 3f07d1cba..ff651a901 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -15,18 +15,18 @@ class NamingConvention(AbstractDetector): """ ARGUMENT = 'naming-convention' - HELP = 'Conformance to Solidity naming conventions' + HELP = 'Conformity to Solidity naming conventions' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions' + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#conformity-to-solidity-naming-conventions' WIKI_TITLE = 'Conformance to Solidity naming conventions' WIKI_DESCRIPTION = ''' Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions) that should be followed. -#### Rules exceptions -- Allow constant variables name/symbol/decimals to be lowercase (ERC20) -- Allow `_` at the beginning of the mixed_case match for private variables and unused parameters.''' +#### Rule exceptions +- Allow constant variable name/symbol/decimals to be lowercase (`ERC20`). +- Allow `_` at the beginning of the `mixed_case` match for private variables and unused parameters.''' WIKI_RECOMMENDATION = 'Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions).' diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 1a46a9909..96320bd89 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -18,7 +18,7 @@ class LowLevelCalls(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls' - WIKI_TITLE = 'Low level calls' + WIKI_TITLE = 'Low-level calls' WIKI_DESCRIPTION = 'The use of low-level calls is error-prone. Low-level calls do not check for [code existence](https://solidity.readthedocs.io/en/v0.4.25/control-structures.html#error-handling-assert-require-revert-and-exceptions) or call success.' WIKI_RECOMMENDATION = 'Avoid low-level calls. Check the call success. If the call is meant for a contract, check for code existence.' diff --git a/slither/detectors/operations/unchecked_low_level_return_values.py b/slither/detectors/operations/unchecked_low_level_return_values.py index 74a049112..a01e5eb8e 100644 --- a/slither/detectors/operations/unchecked_low_level_return_values.py +++ b/slither/detectors/operations/unchecked_low_level_return_values.py @@ -27,11 +27,11 @@ contract MyConc{ } } ``` -The return value of the low-level call is not checked. As a result if the callfailed, the ether will be locked in the contract. +The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract. If the low level is used to prevent blocking operations, consider logging failed calls. ''' - WIKI_RECOMMENDATION = 'Ensure that the return value of low-level call is checked or logged.' + WIKI_RECOMMENDATION = 'Ensure that the return value of a low-level call is checked or logged.' _txt_description = "low-level calls" diff --git a/slither/detectors/operations/unchecked_send_return_value.py b/slither/detectors/operations/unchecked_send_return_value.py index 6c5a59324..3fedc2467 100644 --- a/slither/detectors/operations/unchecked_send_return_value.py +++ b/slither/detectors/operations/unchecked_send_return_value.py @@ -19,7 +19,7 @@ class UncheckedSend(UnusedReturnValues): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send' WIKI_TITLE = 'Unchecked Send' - WIKI_DESCRIPTION = 'The return value of a send is not checked.' + WIKI_DESCRIPTION = 'The return value of a `send` is not checked.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract MyConc{ @@ -28,11 +28,11 @@ contract MyConc{ } } ``` -The return value of `send` is not checked. As a result if the send failed, the ether will be locked in the contract. -If `send` is used to prevent blocking operations, consider logging the failed sent. +The return value of `send` is not checked, so if the send fails, the Ether will be locked in the contract. +If `send` is used to prevent blocking operations, consider logging the failed `send`. ''' - WIKI_RECOMMENDATION = 'Ensure that the return value of send is checked or logged.' + WIKI_RECOMMENDATION = 'Ensure that the return value of `send` is checked or logged.' _txt_description = "send calls" diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py index b1e2b6d08..2aa453c9d 100644 --- a/slither/detectors/operations/unused_return_values.py +++ b/slither/detectors/operations/unused_return_values.py @@ -30,7 +30,7 @@ contract MyConc{ } } ``` -`MyConc` calls `add` of SafeMath, but does not store the result in `a`. As a result, the computation has no effect.''' +`MyConc` calls `add` of `SafeMath`, but does not store the result in `a`. As a result, the computation has no effect.''' WIKI_RECOMMENDATION = 'Ensure that all the return values of the function calls are used.' diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py index 74abd1329..a0a20f1a7 100644 --- a/slither/detectors/operations/void_constructor.py +++ b/slither/detectors/operations/void_constructor.py @@ -12,8 +12,8 @@ class VoidConstructor(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor' - WIKI_TITLE = 'Void Constructor' - WIKI_DESCRIPTION = 'Detect the call to a constructor not implemented' + WIKI_TITLE = 'Void constructor' + WIKI_DESCRIPTION = 'Detect the call to a constructor that is not implemented' WIKI_RECOMMENDATION = 'Remove the constructor call.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity @@ -22,7 +22,7 @@ contract B is A{ constructor() public A(){} } ``` -By reading B's constructor definition, the reader might assume that `A()` initiate the contract, while no code is executed.''' +When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed.''' def _detect(self): diff --git a/slither/detectors/reentrancy/reentrancy_benign.py b/slither/detectors/reentrancy/reentrancy_benign.py index d815927d6..98da21f1b 100644 --- a/slither/detectors/reentrancy/reentrancy_benign.py +++ b/slither/detectors/reentrancy/reentrancy_benign.py @@ -24,7 +24,7 @@ class ReentrancyBenign(Reentrancy): WIKI_TITLE = 'Reentrancy vulnerabilities' WIKI_DESCRIPTION = ''' -Detection of the [re-entrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). +Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentrancy-no-eth`).''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity @@ -38,7 +38,7 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr `callme` contains a reentrancy. The reentrancy is benign because it's exploitation would have the same effect as two consecutive calls.''' - WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' STANDARD_JSON = False diff --git a/slither/detectors/reentrancy/reentrancy_eth.py b/slither/detectors/reentrancy/reentrancy_eth.py index 4b92de9ba..ea70987ce 100644 --- a/slither/detectors/reentrancy/reentrancy_eth.py +++ b/slither/detectors/reentrancy/reentrancy_eth.py @@ -24,12 +24,12 @@ class ReentrancyEth(Reentrancy): WIKI_TITLE = 'Reentrancy vulnerabilities' WIKI_DESCRIPTION = ''' -Detection of the [re-entrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Do not report reentrancies that don't involve ethers (see `reentrancy-no-eth`)''' +Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). +Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity function withdrawBalance(){ - // send userBalance[msg.sender] ethers to msg.sender + // send userBalance[msg.sender] Ether to msg.sender // if mgs.sender is a contract, it will call its fallback function if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){ throw; @@ -40,7 +40,7 @@ Do not report reentrancies that don't involve ethers (see `reentrancy-no-eth`)'' Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw more than its initial deposit to the contract.''' - WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions pattern`](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' STANDARD_JSON = False diff --git a/slither/detectors/reentrancy/reentrancy_events.py b/slither/detectors/reentrancy/reentrancy_events.py index d1e2c9043..f0f9d0db3 100644 --- a/slither/detectors/reentrancy/reentrancy_events.py +++ b/slither/detectors/reentrancy/reentrancy_events.py @@ -23,8 +23,8 @@ class ReentrancyEvent(Reentrancy): WIKI_TITLE = 'Reentrancy vulnerabilities' WIKI_DESCRIPTION = ''' -Detection of the [re-entrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Only report reentrancies leading to out-of-order Events''' +Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). +Only report reentrancies leading to out-of-order events.''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity function bug(Called d){ @@ -34,9 +34,9 @@ Only report reentrancies leading to out-of-order Events''' } ``` -If `d.()` reenters, the `Counter` events will be showed in an incorrect order, which might lead to issues for third-parties.''' +If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties.''' - WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' STANDARD_JSON = False diff --git a/slither/detectors/reentrancy/reentrancy_no_gas.py b/slither/detectors/reentrancy/reentrancy_no_gas.py index d2906f13a..ab1a2e9c2 100644 --- a/slither/detectors/reentrancy/reentrancy_no_gas.py +++ b/slither/detectors/reentrancy/reentrancy_no_gas.py @@ -27,8 +27,8 @@ class ReentrancyNoGas(Reentrancy): WIKI_TITLE = 'Reentrancy vulnerabilities' WIKI_DESCRIPTION = ''' -Detection of the [re-entrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Only report reentrancy that are based on `transfer` or `send`.''' +Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). +Only report reentrancy that is based on `transfer` or `send`.''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity function callme(){ @@ -37,9 +37,9 @@ Only report reentrancy that are based on `transfer` or `send`.''' } ``` -`send` and `transfer` does not protect from reentrancies in case of gas-price change.''' +`send` and `transfer` do not protect from reentrancies in case of gas price changes.''' - WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' @staticmethod def can_callback(ir): diff --git a/slither/detectors/reentrancy/reentrancy_read_before_write.py b/slither/detectors/reentrancy/reentrancy_read_before_write.py index aaaeddc14..944ab2b69 100644 --- a/slither/detectors/reentrancy/reentrancy_read_before_write.py +++ b/slither/detectors/reentrancy/reentrancy_read_before_write.py @@ -24,8 +24,8 @@ class ReentrancyReadBeforeWritten(Reentrancy): WIKI_TITLE = 'Reentrancy vulnerabilities' WIKI_DESCRIPTION = ''' -Detection of the [re-entrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Do not report reentrancies that involve ethers (see `reentrancy-eth`)''' +Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). +Do not report reentrancies that involve Ether (see `reentrancy-eth`).''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity @@ -38,7 +38,7 @@ Do not report reentrancies that involve ethers (see `reentrancy-eth`)''' } ``` ''' - WIKI_RECOMMENDATION = 'Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' STANDARD_JSON = False diff --git a/slither/detectors/shadowing/builtin_symbols.py b/slither/detectors/shadowing/builtin_symbols.py index 95064b1ce..18c010ba4 100644 --- a/slither/detectors/shadowing/builtin_symbols.py +++ b/slither/detectors/shadowing/builtin_symbols.py @@ -19,7 +19,7 @@ class BuiltinSymbolShadowing(AbstractDetector): WIKI_TITLE = 'Builtin Symbol Shadowing' - WIKI_DESCRIPTION = 'Detection of shadowing built-in symbols using local variables/state variables/functions/modifiers/events.' + WIKI_DESCRIPTION = 'Detection of shadowing built-in symbols using local variables, state variables, functions, modifiers, or events.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity pragma solidity ^0.4.24; @@ -38,7 +38,7 @@ contract Bug { ``` `now` is defined as a state variable, and shadows with the built-in symbol `now`. The function `assert` overshadows the built-in `assert` function. Any use of either of these built-in symbols may lead to unexpected results.''' - WIKI_RECOMMENDATION = 'Rename the local variable/state variable/function/modifier/event, so as not to mistakenly overshadow any built-in symbol definitions.' + WIKI_RECOMMENDATION = 'Rename the local variables, state variables, functions, modifiers, and events that shadow a builtin symbol.' SHADOWING_FUNCTION = "function" SHADOWING_MODIFIER = "modifier" diff --git a/slither/detectors/shadowing/local.py b/slither/detectors/shadowing/local.py index 7f00f495d..e1ec20870 100644 --- a/slither/detectors/shadowing/local.py +++ b/slither/detectors/shadowing/local.py @@ -17,7 +17,7 @@ class LocalShadowing(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing' - WIKI_TITLE = 'Local Variable Shadowing' + WIKI_TITLE = 'Local variable shadowing' WIKI_DESCRIPTION = 'Detection of shadowing using local variables.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity @@ -38,9 +38,9 @@ contract Bug { } } ``` -`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` inside `sensitive_function` might be incorrect.''' +`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` in `sensitive_function` might be incorrect.''' - WIKI_RECOMMENDATION = 'Rename the local variable so as not to mistakenly overshadow any state variable/function/modifier/event definitions.' + WIKI_RECOMMENDATION = 'Rename the local variables that shadow another component.' OVERSHADOWED_FUNCTION = "function" OVERSHADOWED_MODIFIER = "modifier" diff --git a/slither/detectors/slither/name_reused.py b/slither/detectors/slither/name_reused.py index 300f2a38b..705d22927 100644 --- a/slither/detectors/slither/name_reused.py +++ b/slither/detectors/slither/name_reused.py @@ -34,11 +34,11 @@ class NameReused(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused' WIKI_TITLE = 'Name reused' - WIKI_DESCRIPTION = '''If a codebase has two contracts with the similar name, the compilation artifacts -will not contain one of the contract with the dupplicate name.''' + WIKI_DESCRIPTION = '''If a codebase has two contracts the similar names, the compilation artifacts +will not contain one of the contracts with the duplicate name.''' WIKI_EXPLOIT_SCENARIO = ''' -Bob's truffle codebase has two contracts named `ERC20`. -When `truffle compile` runs, only one of the two contract will generate artifacts in `build/contracts`. +Bob's `truffle` codebase has two contracts named `ERC20`. +When `truffle compile` runs, only one of the two contracts will generate artifacts in `build/contracts`. As a result, the second contract cannot be analyzed. ''' WIKI_RECOMMENDATION = 'Rename the contract.' diff --git a/slither/detectors/source/rtlo.py b/slither/detectors/source/rtlo.py index dbc976da2..41ab7dc3e 100644 --- a/slither/detectors/source/rtlo.py +++ b/slither/detectors/source/rtlo.py @@ -13,8 +13,8 @@ class RightToLeftOverride(AbstractDetector): CONFIDENCE = DetectorClassification.HIGH WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character' - WIKI_TITLE = 'Right-To-Left-Override character' - WIKI_DESCRIPTION = 'An attacker can manipulate the logic of the contract by using a right-to-left-override character (U+202E)' + WIKI_TITLE = 'Right-to-Left-Override character' + WIKI_DESCRIPTION = 'An attacker can manipulate the logic of the contract by using a right-to-left-override character (`U+202E)`.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract Token diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index bf84dab7a..032787d07 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -21,7 +21,7 @@ class Assembly(AbstractDetector): WIKI_TITLE = 'Assembly usage' WIKI_DESCRIPTION = 'The use of assembly is error-prone and should be avoided.' - WIKI_RECOMMENDATION = 'Do not use evm assembly.' + WIKI_RECOMMENDATION = 'Do not use `evm` assembly.' @staticmethod def _contains_inline_assembly_use(node): diff --git a/slither/detectors/statements/boolean_constant_equality.py b/slither/detectors/statements/boolean_constant_equality.py index 9736603c4..8788325e8 100644 --- a/slither/detectors/statements/boolean_constant_equality.py +++ b/slither/detectors/statements/boolean_constant_equality.py @@ -19,8 +19,8 @@ class BooleanEquality(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality' - WIKI_TITLE = 'Boolean Equality' - WIKI_DESCRIPTION = '''Detects the comparison to boolean constant.''' + WIKI_TITLE = 'Boolean equality' + WIKI_DESCRIPTION = '''Detects the comparison to boolean constants.''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract A { @@ -33,7 +33,7 @@ contract A { } } ``` -Boolean can be used directly and do not need to be compare to `true` or `false`.''' +Boolean constants can be used directly and do not need to be compare to `true` or `false`.''' WIKI_RECOMMENDATION = '''Remove the equality to the boolean constant.''' diff --git a/slither/detectors/statements/boolean_constant_misuse.py b/slither/detectors/statements/boolean_constant_misuse.py index 2d67b1dc1..56a9af1af 100644 --- a/slither/detectors/statements/boolean_constant_misuse.py +++ b/slither/detectors/statements/boolean_constant_misuse.py @@ -40,7 +40,8 @@ contract A { } } ``` -Boolean constants in code have only a few legitimate uses. Other uses (in complex expressions, as conditionals) indicate either an error or (most likely) the persistence of debugging/development code that is likely faulty.''' +Boolean constants in code have only a few legitimate uses. +Other uses (in complex expressions, as conditionals) indicate either an error or, most likely, the persistence of faulty code.''' WIKI_RECOMMENDATION = '''Verify and simplify the condition.''' diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index cf115c858..53df5b7e6 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -20,7 +20,7 @@ class MultipleCallsInLoop(AbstractDetector): WIKI_TITLE = 'Calls inside a loop' - WIKI_DESCRIPTION = 'Calls inside a loop might lead to denial of service attack.' + WIKI_DESCRIPTION = 'Calls inside a loop might lead to a denial-of-service attack.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract CallsInLoop{ @@ -39,7 +39,7 @@ contract CallsInLoop{ } ``` -If one of the destinations has a fallback function which reverts, `bad` will always revert.''' +If one of the destinations has a fallback function that reverts, `bad` will always revert.''' WIKI_RECOMMENDATION = 'Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls.' diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py index 334c6cd41..08751cc7e 100644 --- a/slither/detectors/statements/controlled_delegatecall.py +++ b/slither/detectors/statements/controlled_delegatecall.py @@ -16,7 +16,7 @@ class ControlledDelegateCall(AbstractDetector): WIKI_TITLE = 'Controlled Delegatecall' - WIKI_DESCRIPTION = 'Delegatecall or callcode to an address controlled by the user.' + WIKI_DESCRIPTION = '`Delegatecall` or `callcode` to an address controlled by the user.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract Delegatecall{ @@ -25,7 +25,7 @@ contract Delegatecall{ } } ``` -Bob calls `delegate` and delegates the execution to its malicious contract. As a result, Bob withdraws the funds of the contract and destructs it.''' +Bob calls `delegate` and delegates the execution to his malicious contract. As a result, Bob withdraws the funds of the contract and destructs it.''' WIKI_RECOMMENDATION = 'Avoid using `delegatecall`. Use only trusted destinations.' diff --git a/slither/detectors/statements/deprecated_calls.py b/slither/detectors/statements/deprecated_calls.py index b56a807c5..ade436844 100644 --- a/slither/detectors/statements/deprecated_calls.py +++ b/slither/detectors/statements/deprecated_calls.py @@ -22,8 +22,8 @@ class DeprecatedStandards(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards' - WIKI_TITLE = 'Deprecated Standards' - WIKI_DESCRIPTION = 'Detect the usage of deprecated standards (as defined by SWC-111), excluding only `constant` keyword detection on functions.' + WIKI_TITLE = 'Deprecated standards' + WIKI_DESCRIPTION = 'Detect the usage of deprecated standards.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract ContractWithDeprecatedReferences { diff --git a/slither/detectors/statements/divide_before_multiply.py b/slither/detectors/statements/divide_before_multiply.py index d1d1df511..6fff9ed4c 100644 --- a/slither/detectors/statements/divide_before_multiply.py +++ b/slither/detectors/statements/divide_before_multiply.py @@ -58,7 +58,7 @@ class DivideBeforeMultiply(AbstractDetector): WIKI_TITLE = 'Divide before multiply' WIKI_DESCRIPTION = '''Solidity only supports integers, so division will often truncate; performing a multiply before a divison can sometimes avoid loss of precision.''' - WIKI_DESCRIPTION = '''Solidity integer division might truncate. As a result, performing a multiply before a divison might lead to loss of precision.''' + WIKI_DESCRIPTION = '''Solidity integer division might truncate. As a result, performing multiplication before divison might reduce precision.''' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract A { @@ -69,9 +69,9 @@ contract A { ``` If `n` is greater than `oldSupply`, `coins` will be zero. For example, with `oldSupply = 5; n = 10, interest = 2`, coins will be zero. If `(oldSupply * interest / n)` was used, `coins` would have been `1`. -In general, it's usually a good idea to re-arrange arithmetic to perform multiply before divide, unless the limit of a smaller type makes this dangerous.''' +In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous.''' - WIKI_RECOMMENDATION = '''Consider ordering multiplication prior division.''' + WIKI_RECOMMENDATION = '''Consider ordering multiplication before division.''' def _explore(self, node, explored, f_results, divisions): if node in explored: diff --git a/slither/detectors/statements/incorrect_strict_equality.py b/slither/detectors/statements/incorrect_strict_equality.py index b0b47d025..bd970ca8c 100644 --- a/slither/detectors/statements/incorrect_strict_equality.py +++ b/slither/detectors/statements/incorrect_strict_equality.py @@ -33,9 +33,10 @@ contract Crowdsale{ return this.balance == 100 ether; } ``` -`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. `Crowdsale` reaches 100 ether. Bob sends 0.1 ether. As a result, `fund_reached` is always false and the crowdsale never ends.''' +`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. +`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends.''' - WIKI_RECOMMENDATION = '''Don't use strict equality to determine if an account has enough ethers or tokens.''' + WIKI_RECOMMENDATION = '''Don't use strict equality to determine if an account has enough Ether or tokens.''' sources_taint = [SolidityVariable('now'), SolidityVariableComposed('block.number'), diff --git a/slither/detectors/statements/too_many_digits.py b/slither/detectors/statements/too_many_digits.py index 32d4ab93c..1201815e8 100644 --- a/slither/detectors/statements/too_many_digits.py +++ b/slither/detectors/statements/too_many_digits.py @@ -28,11 +28,11 @@ contract MyContract{ } ``` -While `1_ether` looks like `1 ether`, it is `10 ether`. As a result, its usage is likely to be incorrect. +While `1_ether` looks like `1 ether`, it is `10 ether`. As a result, it's likely to be used incorrectly. ''' WIKI_RECOMMENDATION = ''' Use: -- [Ether suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#ether-units) +- [Ether suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#ether-units), - [Time suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#time-units), or - [The scientific notation](https://solidity.readthedocs.io/en/latest/types.html#rational-and-integer-literals) ''' diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index adc274b10..06eed1016 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -18,7 +18,7 @@ class TxOrigin(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin' WIKI_TITLE = 'Dangerous usage of `tx.origin`' - WIKI_DESCRIPTION = '`tx.origin`-based protection can be abused by malicious contract if a legitimate user interacts with the malicious contract.' + WIKI_DESCRIPTION = '`tx.origin`-based protection can be abused by a malicious contract if a legitimate user interacts with the malicious contract.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract TxOrigin { diff --git a/slither/detectors/statements/type_based_tautology.py b/slither/detectors/statements/type_based_tautology.py index 69ed373af..4f334805e 100644 --- a/slither/detectors/statements/type_based_tautology.py +++ b/slither/detectors/statements/type_based_tautology.py @@ -39,11 +39,11 @@ contract A { } } ``` -`x` is an `uint256`, as a result `x >= 0` will be always true. -`y` is an `uint8`, as a result `y <512` will be always true. +`x` is a `uint256`, so `x >= 0` will be always true. +`y` is a `uint8`, so `y <512` will be always true. ''' - WIKI_RECOMMENDATION = '''Fix the incorrect comparison by chaning the value type or the comparison.''' + WIKI_RECOMMENDATION = '''Fix the incorrect comparison by changing the value type or the comparison.''' def typeRange(self, t): bits = int(t.split("int")[1]) diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 2d6d40b06..ee87cbd38 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -27,8 +27,8 @@ class ConstCandidateStateVars(AbstractDetector): WIKI_TITLE = 'State variables that could be declared constant' - WIKI_DESCRIPTION = 'Constant state variable should be declared constant to save gas.' - WIKI_RECOMMENDATION = 'Add the `constant` attributes to the state variables that never change.' + WIKI_DESCRIPTION = 'Constant state variables should be declared constant to save gas.' + WIKI_RECOMMENDATION = 'Add the `constant` attributes to state variables that never change.' @staticmethod def _valid_candidate(v): diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index 766a091ec..02051f913 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -31,7 +31,7 @@ contract Uninitialized is Owner{ } } ``` -Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and are lost.''' +Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is lost.''' WIKI_RECOMMENDATION = 'Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero.' diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index a9690b231..f4be60bc7 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -38,7 +38,7 @@ contract Uninitialized{ } } ``` -Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and are lost. +Bob calls `transfer`. As a result, the Ether are sent to the address `0x0` and are lost. ''' WIKI_RECOMMENDATION = ''' Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero. diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index a6a704e6b..728597d2e 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -20,7 +20,7 @@ class UninitializedStorageVars(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables' WIKI_TITLE = 'Uninitialized storage variables' - WIKI_DESCRIPTION = 'An uinitialized storage variable will act as a reference to the first state variable, and can override a critical variable.' + WIKI_DESCRIPTION = 'An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable.' WIKI_EXPLOIT_SCENARIO = ''' ```solidity contract Uninitialized{ @@ -36,10 +36,10 @@ contract Uninitialized{ } } ``` -Bob calls `func`. As a result, `owner` is override to 0. +Bob calls `func`. As a result, `owner` is overridden to `0`. ''' - WIKI_RECOMMENDATION = 'Initialize all the storage variables.' + WIKI_RECOMMENDATION = 'Initialize all storage variables.' # node.context[self.key] contains the uninitialized storage variables key = "UNINITIALIZEDSTORAGE" diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index d1b4f2179..195c79aca 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -21,7 +21,7 @@ class UnusedStateVars(AbstractDetector): WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables' - WIKI_TITLE = 'Unused state variables' + WIKI_TITLE = 'Unused state variable' WIKI_DESCRIPTION = 'Unused state variable.' WIKI_EXPLOIT_SCENARIO = '' WIKI_RECOMMENDATION = 'Remove unused state variables.' diff --git a/tests/expected_json/external_function.external-function.txt b/tests/expected_json/external_function.external-function.txt index d30da79de..3bb411274 100644 --- a/tests/expected_json/external_function.external-function.txt +++ b/tests/expected_json/external_function.external-function.txt @@ -9,6 +9,6 @@ funcNotCalled() should be declared external: - ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) parameter_read_ok_for_external(uint256) should be declared external: - FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) -Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external tests/external_function.sol analyzed (6 contracts with 1 detectors), 5 result(s) found Use https://crytic.io/ to get access to additional detectors and Github integration diff --git a/tests/expected_json/naming_convention.naming-convention.txt b/tests/expected_json/naming_convention.naming-convention.txt index 6ec5102e2..6c1a8a2d2 100644 --- a/tests/expected_json/naming_convention.naming-convention.txt +++ b/tests/expected_json/naming_convention.naming-convention.txt @@ -11,6 +11,6 @@ Modifier naming.CantDo() (tests/naming_convention.sol#41-43) is not in mixedCase Parameter T.test(uint256,uint256)._used (tests/naming_convention.sol#59) is not in mixedCase Variable T._myPublicVar (tests/naming_convention.sol#56) is not in mixedCase Variable T.l (tests/naming_convention.sol#67) used l, O, I, which should not be used -Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformity-to-solidity-naming-conventions tests/naming_convention.sol analyzed (4 contracts with 1 detectors), 12 result(s) found Use https://crytic.io/ to get access to additional detectors and Github integration From 66e08ea195d70fd13e7461bb2f203335bce583aa Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 9 Aug 2020 21:21:25 +0200 Subject: [PATCH 49/73] Fix incorrect solc version for json-only --- slither/solc_parsing/slitherSolc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 5bf55e7c9..22ba493f6 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -115,11 +115,11 @@ class SlitherSolc: self._core.add_source_code(sourcePath) if data_loaded[self.get_key()] == "root": - self._solc_version = "0.3" + self._core._solc_version = "0.3" logger.error("solc <0.4 is not supported") return elif data_loaded[self.get_key()] == "SourceUnit": - self._solc_version = "0.4" + self._core._solc_version = "0.4" self._parse_source_unit(data_loaded, filename) else: logger.error("solc version is not supported") From 0f68331a41a9fdc487480f70be53c49da8e7b0b9 Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 11 Aug 2020 12:02:12 -0400 Subject: [PATCH 50/73] resolve state variables properly --- slither/solc_parsing/yul/parse_yul.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 7812b92d0..993d389cb 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -611,6 +611,10 @@ def parse_yul_identifier(root: YulScope, node: YulNode, ast: Dict) -> Optional[E if variable: return Identifier(variable) + variable = root.parent_func.contract.get_state_variable_from_name(name) + if variable: + return Identifier(variable) + # check yul-scoped variable variable = root.get_yul_local_variable_from_name(name) if variable: From 5af174946a65e90397c877fbba14c71834bee0cb Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Aug 2020 13:24:09 +0200 Subject: [PATCH 51/73] Fix incorrect types to list conversion --- slither/slithir/convert.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 97c8171e7..d9ebb07fc 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1040,6 +1040,29 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]: for v in return_type.type.elems_ordered: ret += _convert_to_structure_to_list(v.type) return ret + # Mapping and arrays are not included in external call + # + # contract A{ + # + # struct St{ + # uint a; + # uint b; + # mapping(uint => uint) map; + # uint[] array; + # } + # + # mapping (uint => St) public st; + # + # } + # + # contract B{ + # + # function f(A a) public{ + # (uint a, uint b) = a.st(0); + # } + # } + if isinstance(return_type, (MappingType, ArrayType)): + return [] return [return_type.type] From 799fe16c21806ea1864a5a9892ae61f01fbccd10 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 15 Aug 2020 09:13:41 +0200 Subject: [PATCH 52/73] Update solc-version recommended versions (fix #570) --- slither/detectors/attributes/incorrect_solc.py | 12 +++++++----- ...c_version_incorrect_05.ast.json.solc-version.json | 6 +++--- ...lc_version_incorrect_05.ast.json.solc-version.txt | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index 3b676e5f3..75c130c07 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -33,23 +33,25 @@ class IncorrectSolc(AbstractDetector): `solc` frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks. We also recommend avoiding complex `pragma` statement.''' WIKI_RECOMMENDATION = ''' -Use Solidity 0.4.25 or 0.5.11. Consider using the latest version of Solidity for testing the compilation, and a trusted version for deploying.''' +Use Solidity 0.5.11 - 0.5.13, 0.5.15-0.5.17, or 0.6.8, 0.6.10-0.6.11. +Consider using the latest version of Solidity for testing the compilation, and a trusted version for deploying.''' COMPLEX_PRAGMA_TXT = "is too complex" OLD_VERSION_TXT = "allows old versions" LESS_THAN_TXT = "uses lesser than" - TOO_RECENT_VERSION_TXT = "necessitates versions too recent to be trusted. Consider deploying with 0.5.11" - BUGGY_VERSION_TXT = "is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html)" + TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.11" + BUGGY_VERSION_TXT = "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)" # Indicates the allowed versions. Must be formatted in increasing order. - ALLOWED_VERSIONS = ["0.4.25", "0.4.26", "0.5.11"] + ALLOWED_VERSIONS = ["0.5.11", "0.5.12", "0.5.13", "0.5.15", "0.5.16", "0.5.17", "0.6.8", "0.6.10", "0.6.11"] # Indicates the versions that should not be used. BUGGY_VERSIONS = ["0.4.22", "^0.4.22", "0.5.5", "^0.5.5", "0.5.6", "^0.5.6", - "0.5.14", "^0.5.14"] + "0.5.14", "^0.5.14", + "0.6.9", "^0.6.9"] def _check_version(self, version): op = version[0] diff --git a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json index 6f1701ac7..0809104b8 100644 --- a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json +++ b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.json @@ -30,9 +30,9 @@ } } ], - "description": "Pragma version^0.5.5 (None) is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html)\n", - "markdown": "Pragma version[^0.5.5](None) is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html)\n", - "id": "fa84bcbd40d52d8846dcd54be4cada287e43c5461898c9acbf089ca8a478f6e5", + "description": "Pragma version^0.5.5 (None) is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)\n", + "markdown": "Pragma version[^0.5.5](None) is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)\n", + "id": "b27987b5c734b3ac9a18d3aef5c96a20911b71fb0ffd8d222a1052252fea4151", "check": "solc-version", "impact": "Informational", "confidence": "High" diff --git a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt index 4d4c5e10f..82933723e 100644 --- a/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt +++ b/tests/expected_json/solc_version_incorrect_05.ast.json.solc-version.txt @@ -1,5 +1,5 @@  -Pragma version^0.5.5 (None) is known to contain severe issue (https://solidity.readthedocs.io/en/v0.5.8/bugs.html) +Pragma version^0.5.5 (None) is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) Pragma version0.5.7 (None) allows old versions Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity tests/solc_version_incorrect_05.ast.json analyzed (1 contracts with 1 detectors), 2 result(s) found From ff0d820da6ed116b63f4b565d52933c29c05aaf8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 15 Aug 2020 10:22:25 +0200 Subject: [PATCH 53/73] solc-version: - Add check on exact compiler version sued - Improve truffle test --- scripts/travis_test_truffle.sh | 2 +- .../detectors/attributes/incorrect_solc.py | 19 +++++++++++++++++-- .../old_solc.sol.json.solc-version.txt | 1 - .../solc_version_incorrect.solc-version.json | 9 +++++++++ .../solc_version_incorrect.solc-version.txt | 4 ++-- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/scripts/travis_test_truffle.sh b/scripts/travis_test_truffle.sh index 37591e948..c7f40e442 100755 --- a/scripts/travis_test_truffle.sh +++ b/scripts/travis_test_truffle.sh @@ -15,7 +15,7 @@ npm install -g truffle truffle unbox metacoin slither . -if [ $? -eq 5 ] +if [ $? -eq 8 ] then exit 0 fi diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index 75c130c07..f34581e2d 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -33,8 +33,12 @@ class IncorrectSolc(AbstractDetector): `solc` frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks. We also recommend avoiding complex `pragma` statement.''' WIKI_RECOMMENDATION = ''' -Use Solidity 0.5.11 - 0.5.13, 0.5.15-0.5.17, or 0.6.8, 0.6.10-0.6.11. -Consider using the latest version of Solidity for testing the compilation, and a trusted version for deploying.''' +Deploy with any of the following Solidity versions: +- 0.5.11 - 0.5.13, +- 0.5.15 - 0.5.17, +- 0.6.8, +- 0.6.10 - 0.6.11. +Consider using the latest version of Solidity for testing the compilation.''' COMPLEX_PRAGMA_TXT = "is too complex" OLD_VERSION_TXT = "allows old versions" @@ -112,6 +116,17 @@ Consider using the latest version of Solidity for testing the compilation, and a results.append(json) + if self.slither.crytic_compile: + if self.slither.crytic_compile.compiler_version: + if self.slither.crytic_compile.compiler_version.version not in self.ALLOWED_VERSIONS: + info = ["solc-", + self.slither.crytic_compile.compiler_version.version, + " is not recommended for deployement"] + + json = self.generate_result(info) + + results.append(json) + return results @staticmethod diff --git a/tests/expected_json/old_solc.sol.json.solc-version.txt b/tests/expected_json/old_solc.sol.json.solc-version.txt index d1b4b2faf..83a789c46 100644 --- a/tests/expected_json/old_solc.sol.json.solc-version.txt +++ b/tests/expected_json/old_solc.sol.json.solc-version.txt @@ -3,4 +3,3 @@ Pragma version0.4.21 (None) allows old versions Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity tests/old_solc.sol.json analyzed (1 contracts with 1 detectors), 1 result(s) found Use https://crytic.io/ to get access to additional detectors and Github integration -INFO:Slither:/home/travis/build/crytic/slither/scripts/../tests/expected_json/old_solc.sol.json.solc-version.json exists already, the overwrite is prevented diff --git a/tests/expected_json/solc_version_incorrect.solc-version.json b/tests/expected_json/solc_version_incorrect.solc-version.json index 6b75fd8ff..74a1fba12 100644 --- a/tests/expected_json/solc_version_incorrect.solc-version.json +++ b/tests/expected_json/solc_version_incorrect.solc-version.json @@ -77,6 +77,15 @@ "check": "solc-version", "impact": "Informational", "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.4.25 is not recommended for deployement", + "markdown": "solc-0.4.25 is not recommended for deployement", + "id": "3d2fbcb2fa2b79dac83c4860aea6ba0bfbf9128d2a982f465feb6846a692626b", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] } diff --git a/tests/expected_json/solc_version_incorrect.solc-version.txt b/tests/expected_json/solc_version_incorrect.solc-version.txt index 379e0cc64..4cbae2c59 100644 --- a/tests/expected_json/solc_version_incorrect.solc-version.txt +++ b/tests/expected_json/solc_version_incorrect.solc-version.txt @@ -1,6 +1,6 @@  Pragma version^0.4.23 (tests/solc_version_incorrect.sol#2) allows old versions Pragma version>=0.4.0<0.6.0 (tests/solc_version_incorrect.sol#3) allows old versions -Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity -tests/solc_version_incorrect.sol analyzed (1 contracts with 1 detectors), 2 result(s) found +solc-0.4.25 is not recommended for deployementReference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity +tests/solc_version_incorrect.sol analyzed (1 contracts with 1 detectors), 3 result(s) found Use https://crytic.io/ to get access to additional detectors and Github integration From 0caada35dcf722aea8a972845908725f393c3505 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 15 Aug 2020 19:03:49 +0200 Subject: [PATCH 54/73] solc-version: minor --- slither/detectors/attributes/incorrect_solc.py | 5 +++-- .../expected_json/solc_version_incorrect.solc-version.json | 6 +++--- tests/expected_json/solc_version_incorrect.solc-version.txt | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index f34581e2d..36a3c11e3 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -38,7 +38,8 @@ Deploy with any of the following Solidity versions: - 0.5.15 - 0.5.17, - 0.6.8, - 0.6.10 - 0.6.11. -Consider using the latest version of Solidity for testing the compilation.''' +Use a simple pragma version that allows any of these versions. +Consider using the latest version of Solidity for testing.''' COMPLEX_PRAGMA_TXT = "is too complex" OLD_VERSION_TXT = "allows old versions" @@ -121,7 +122,7 @@ Consider using the latest version of Solidity for testing the compilation.''' if self.slither.crytic_compile.compiler_version.version not in self.ALLOWED_VERSIONS: info = ["solc-", self.slither.crytic_compile.compiler_version.version, - " is not recommended for deployement"] + " is not recommended for deployement\n"] json = self.generate_result(info) diff --git a/tests/expected_json/solc_version_incorrect.solc-version.json b/tests/expected_json/solc_version_incorrect.solc-version.json index 74a1fba12..50855fc27 100644 --- a/tests/expected_json/solc_version_incorrect.solc-version.json +++ b/tests/expected_json/solc_version_incorrect.solc-version.json @@ -80,9 +80,9 @@ }, { "elements": [], - "description": "solc-0.4.25 is not recommended for deployement", - "markdown": "solc-0.4.25 is not recommended for deployement", - "id": "3d2fbcb2fa2b79dac83c4860aea6ba0bfbf9128d2a982f465feb6846a692626b", + "description": "solc-0.4.25 is not recommended for deployement\n", + "markdown": "solc-0.4.25 is not recommended for deployement\n", + "id": "5a4264386059605a5ac36e3f7dbc853e89e337be5bfbd8b05a64964a81d73790", "check": "solc-version", "impact": "Informational", "confidence": "High" diff --git a/tests/expected_json/solc_version_incorrect.solc-version.txt b/tests/expected_json/solc_version_incorrect.solc-version.txt index 4cbae2c59..dc387dd18 100644 --- a/tests/expected_json/solc_version_incorrect.solc-version.txt +++ b/tests/expected_json/solc_version_incorrect.solc-version.txt @@ -1,6 +1,7 @@  Pragma version^0.4.23 (tests/solc_version_incorrect.sol#2) allows old versions Pragma version>=0.4.0<0.6.0 (tests/solc_version_incorrect.sol#3) allows old versions -solc-0.4.25 is not recommended for deployementReference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity +solc-0.4.25 is not recommended for deployement +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity tests/solc_version_incorrect.sol analyzed (1 contracts with 1 detectors), 3 result(s) found Use https://crytic.io/ to get access to additional detectors and Github integration From 8f5658042d2e568347644a11e71469239fdef085 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 15 Aug 2020 19:04:19 +0200 Subject: [PATCH 55/73] solc-version: update embark test --- scripts/travis_test_embark.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test_embark.sh b/scripts/travis_test_embark.sh index 147f7243d..ad34ffa4f 100755 --- a/scripts/travis_test_embark.sh +++ b/scripts/travis_test_embark.sh @@ -17,7 +17,7 @@ cd embark_demo || exit 255 npm install slither . --embark-overwrite-config -if [ $? -eq 3 ] +if [ $? -eq 4 ] then exit 0 fi From 2d615314fd6eb3783efcc798d2de181978921761 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Sat, 15 Aug 2020 19:22:21 +0200 Subject: [PATCH 56/73] Update travis_test_etherlime.sh --- scripts/travis_test_etherlime.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test_etherlime.sh b/scripts/travis_test_etherlime.sh index d7f7adfd9..9a2f24080 100755 --- a/scripts/travis_test_etherlime.sh +++ b/scripts/travis_test_etherlime.sh @@ -15,7 +15,7 @@ npm i -g etherlime etherlime init slither . -if [ $? -eq 6 ] +if [ $? -eq 7 ] then exit 0 fi From ee1d9ce85b9993e76ab69659bf38b822e864b6ff Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 22 Aug 2020 11:42:13 +0200 Subject: [PATCH 57/73] Improve abi-decode support --- slither/slithir/convert.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index d9ebb07fc..1a6414b19 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -815,7 +815,10 @@ def convert_to_solidity_func(ir): # If the variable is a referenceVariable, we are lost # See https://github.com/crytic/slither/issues/566 for potential solutions if not isinstance(new_ir.arguments[1], ReferenceVariable): - new_ir.lvalue.set_type(new_ir.arguments[1]) + decode_type = new_ir.arguments[1] + if isinstance(decode_type, (Structure, Enum, Contract)): + decode_type = UserDefinedType(decode_type) + new_ir.lvalue.set_type(decode_type) else: new_ir.lvalue.set_type(call.return_type) return new_ir From b022b39da0f796a5a9183655d4d27dc67c8cf2fd Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 22 Aug 2020 14:33:16 +0200 Subject: [PATCH 58/73] Increase default stack depth limit --- slither/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/slither/__main__.py b/slither/__main__.py index d0b043cd9..313fd8fb2 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -514,6 +514,9 @@ class FormatterCryticCompile(logging.Formatter): def main(): + # Codebase with complex domninators can lead to a lot of SSA recursive call + sys.setrecursionlimit(1500) + detectors, printers = get_detectors_and_printers() main_impl(all_detector_classes=detectors, all_printer_classes=printers) From de53bc53ff0b26d8b03de2b7952a3be55fab4719 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 22 Aug 2020 15:02:07 +0200 Subject: [PATCH 59/73] Add type propgation for this.function usage (fix #592) --- slither/slithir/convert.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 1a6414b19..e4d7fba18 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -10,6 +10,7 @@ from slither.core.solidity_types import (ArrayType, ElementaryType, UserDefinedType, TypeInformation) from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt from slither.core.solidity_types.type import Type +from slither.core.variables.function_type_variable import FunctionTypeVariable from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable from slither.slithir.operations.codesize import CodeSize @@ -534,7 +535,30 @@ def propagate_types(ir, node): return _convert_type_contract(ir, node.function.slither) left = ir.variable_left t = None - if isinstance(left, (Variable, SolidityVariable)): + # Handling of this.function_name usage + if (left == SolidityVariable("this") and + isinstance(ir.variable_right, Constant) and + str(ir.variable_right) in [x.name for x in ir.function.contract.functions] + ): + # Assumption that this.function_name can only compile if + # And the contract does not have two functions starting with function_name + # Otherwise solc raises: + # Error: Member "f" not unique after argument-dependent lookup in contract + targeted_function = next((x for x in ir.function.contract.functions if + x.name == str(ir.variable_right))) + parameters = [] + returns = [] + for parameter in targeted_function.parameters: + v = FunctionTypeVariable() + v.name = parameter.name + parameters.append(v) + for return_var in targeted_function.returns: + v = FunctionTypeVariable() + v.name = return_var.name + returns.append(v) + t = FunctionType(parameters, returns) + ir.lvalue.set_type(t) + elif isinstance(left, (Variable, SolidityVariable)): t = ir.variable_left.type elif isinstance(left, (Contract, Enum, Structure)): t = UserDefinedType(left) @@ -967,7 +991,6 @@ def convert_to_library(ir, node, using_for): # Though we could use .contract as libraries cannot be shadowed contract = node.function.contract_declarer t = ir.destination.type - if t in using_for: new_ir = look_for_library(contract, ir, node, using_for, t) if new_ir: From 8871dd7e0f8ef2d5582092416a86dc7f2f991c9c Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 22 Aug 2020 15:06:32 +0200 Subject: [PATCH 60/73] IR: Add support for function pointer in RETURN operator (fix #590) --- slither/slithir/operations/return_operation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slither/slithir/operations/return_operation.py b/slither/slithir/operations/return_operation.py index c93128558..2e5ae3b6a 100644 --- a/slither/slithir/operations/return_operation.py +++ b/slither/slithir/operations/return_operation.py @@ -1,3 +1,4 @@ +from slither.core.declarations import Function from slither.slithir.operations.operation import Operation from slither.slithir.variables.tuple import TupleVariable @@ -15,7 +16,7 @@ class Return(Operation): # ex: return call() # where call() dont return if not isinstance(values, list): - assert is_valid_rvalue(values) or isinstance(values, TupleVariable) or values is None + assert is_valid_rvalue(values) or isinstance(values, (TupleVariable, Function)) or values is None if values is None: values = [] else: @@ -34,7 +35,7 @@ class Return(Operation): if isinstance(value, list): assert all(self._valid_value(v) for v in value) else: - assert is_valid_rvalue(value) or isinstance(value, TupleVariable) + assert is_valid_rvalue(value) or isinstance(value, (TupleVariable, Function)) return True @property From 031465bb34583b08a39bc88c064090e2a675f7e5 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Wed, 26 Aug 2020 11:11:50 -0400 Subject: [PATCH 61/73] "slither-erc" -> "slither-check-erc" --- README.md | 2 +- slither/tools/erc_conformance/__main__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0426c250a..d8e99dba3 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d - `slither-check-upgradeability`: [Review `delegatecall`-based upgradeability](https://github.com/crytic/slither/wiki/Upgradeability-Checks) - `slither-prop`: [Automatic unit tests and properties generation](https://github.com/crytic/slither/wiki/Properties-generation) - `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening) -- `slither-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance) +- `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance) - `slither-format`: [Automatic patches generation](https://github.com/crytic/slither/wiki/Slither-format) See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools. diff --git a/slither/tools/erc_conformance/__main__.py b/slither/tools/erc_conformance/__main__.py index 449ae8669..bb98842e2 100644 --- a/slither/tools/erc_conformance/__main__.py +++ b/slither/tools/erc_conformance/__main__.py @@ -31,7 +31,7 @@ def parse_args(): :return: Returns the arguments for the program. """ parser = argparse.ArgumentParser( - description="Check the ERC 20 conformance", usage="slither-erc project contractName" + description="Check the ERC 20 conformance", usage="slither-check-erc project contractName" ) parser.add_argument("project", help="The codebase to be tested.") From 246a4baa0e8f5dcc57400127c483b2c10b039c5d Mon Sep 17 00:00:00 2001 From: Claudia Richoux Date: Thu, 27 Aug 2020 20:22:25 -0400 Subject: [PATCH 62/73] fixing exponent dos by adding limits on size of exponent --- slither/slithir/variables/constant.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 5de70d0a9..9be1bbc59 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -27,10 +27,16 @@ class Constant(SlithIRVariable): else: if 'e' in val: base, expo = val.split('e') - self._val = int(Decimal(base) * (10 ** int(expo))) + expo = int(expo) + if expo > 80: + raise ValueError("exponent is too large to fit in any Solidity integer size") + self._val = int(Decimal(base) * (10 ** expo)) elif 'E' in val: base, expo = val.split('E') - self._val = int(Decimal(base) * (10 ** int(expo))) + expo = int(expo) + if expo > 80: + raise ValueError("exponent is too large to fit in any Solidity integer size") + self._val = int(Decimal(base) * (10 ** expo)) else: self._val = int(Decimal(val)) elif type.type == 'bool': From d06c397a5c600ae7380e7fe9fb3338532c996de8 Mon Sep 17 00:00:00 2001 From: Claudia Richoux Date: Thu, 27 Aug 2020 20:28:55 -0400 Subject: [PATCH 63/73] fixing it and supporting base of zero --- slither/slithir/variables/constant.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 9be1bbc59..dd9df153b 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -25,18 +25,16 @@ class Constant(SlithIRVariable): if val.startswith('0x') or val.startswith('0X'): self._val = int(val, 16) else: - if 'e' in val: - base, expo = val.split('e') - expo = int(expo) + if 'e' in val or 'E' in val: + base, expo = val.split('e') if 'e' in val else val.split('E') + base, expo = Decimal(base), int(expo) if expo > 80: - raise ValueError("exponent is too large to fit in any Solidity integer size") - self._val = int(Decimal(base) * (10 ** expo)) - elif 'E' in val: - base, expo = val.split('E') - expo = int(expo) - if expo > 80: - raise ValueError("exponent is too large to fit in any Solidity integer size") - self._val = int(Decimal(base) * (10 ** expo)) + if base != Decimal(0): + raise ValueError("exponent is too large to fit in any Solidity integer size") + else: + self._val = 0 + else: + self._val = int(Decimal(base) * (10 ** expo)) else: self._val = int(Decimal(val)) elif type.type == 'bool': From 66b98dafea7b37062b12f9c34af87957d9f0b14e Mon Sep 17 00:00:00 2001 From: Claudia Richoux Date: Thu, 27 Aug 2020 20:45:11 -0400 Subject: [PATCH 64/73] fixing 587 while im at it --- slither/slithir/variables/constant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index dd9df153b..f0ab9b4a0 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -34,7 +34,7 @@ class Constant(SlithIRVariable): else: self._val = 0 else: - self._val = int(Decimal(base) * (10 ** expo)) + self._val = int(Decimal(base) * Decimal(10 ** expo)) else: self._val = int(Decimal(val)) elif type.type == 'bool': From fbccffc745f56035cf74a6cf93affabd0e75449d Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 10:03:28 +0200 Subject: [PATCH 65/73] Improve exponent handling on large value - Use 77 as the expo limit - Use SlithIRError instead of value error --- slither/slithir/variables/constant.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index f0ab9b4a0..d1e1a5524 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -4,6 +4,8 @@ from decimal import Decimal from .variable import SlithIRVariable from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint from slither.utils.arithmetic import convert_subdenomination +from ..exceptions import SlithIRError + @total_ordering class Constant(SlithIRVariable): @@ -28,9 +30,13 @@ class Constant(SlithIRVariable): if 'e' in val or 'E' in val: base, expo = val.split('e') if 'e' in val else val.split('E') base, expo = Decimal(base), int(expo) - if expo > 80: + # The resulting number must be < 2**256-1, otherwise solc + # Would not be able to compile it + # 10**77 is the largest exponent that fits + # See https://github.com/ethereum/solidity/blob/9e61f92bd4d19b430cb8cb26f1c7cf79f1dff380/libsolidity/ast/Types.cpp#L1281-L1290 + if expo > 77: if base != Decimal(0): - raise ValueError("exponent is too large to fit in any Solidity integer size") + raise SlithIRError(f"{base}e{expo} is too large to fit in any Solidity integer size") else: self._val = 0 else: From 351de6bd31c1220480aa802a70292a00a53de2ef Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 11:06:35 +0200 Subject: [PATCH 66/73] Refactor regression tests: - Rename travis_ to ci_ - Move shared code to scripts/ci_tests.sh - Rename _4 /_5 to detectors_4 / detectors_5 - Rename generation scripts - Create solidity 6 / 7 scripts (but there is no test for now) - Use solc-select - Update CONTRIBUTING.md guidelines --- .github/workflows/ci.yml | 33 ++++--- CONTRIBUTING.md | 13 +++ ...n_4.sh => ci_generate_test_detectors_4.sh} | 21 +---- scripts/ci_generate_test_detectors_5.sh | 28 ++++++ scripts/ci_generate_test_detectors_6.sh | 11 +++ scripts/ci_generate_test_detectors_7.sh | 11 +++ scripts/ci_test.sh | 86 +++++++++++++++++++ .../{travis_test_cli.sh => ci_test_cli.sh} | 0 .../{travis_test_dapp.sh => ci_test_dapp.sh} | 0 ...pendency.sh => ci_test_data_dependency.sh} | 0 ...ravis_test_4.sh => ci_test_detectors_4.sh} | 70 +-------------- ...ravis_test_5.sh => ci_test_detectors_5.sh} | 71 +-------------- scripts/ci_test_detectors_6.sh | 7 ++ scripts/ci_test_detectors_7.sh | 7 ++ ...ravis_test_embark.sh => ci_test_embark.sh} | 0 .../{travis_test_erc.sh => ci_test_erc.sh} | 3 +- ...test_etherlime.sh => ci_test_etherlime.sh} | 0 scripts/ci_test_etherscan.sh | 23 +++++ ...st_find_paths.sh => ci_test_find_paths.sh} | 3 +- ...{travis_test_kspec.sh => ci_test_kspec.sh} | 3 +- ...s_test_printers.sh => ci_test_printers.sh} | 4 +- .../{travis_test_prop.sh => ci_test_prop.sh} | 0 ...{travis_test_simil.sh => ci_test_simil.sh} | 4 +- ...er_config.sh => ci_test_slither_config.sh} | 0 ...vis_test_truffle.sh => ci_test_truffle.sh} | 0 ...radability.sh => ci_test_upgradability.sh} | 25 +++--- scripts/fix_travis_relative_paths.sh | 6 -- scripts/tests_generate_expected_json_5.sh | 42 --------- scripts/travis_test_etherscan.sh | 26 ------ 29 files changed, 243 insertions(+), 254 deletions(-) rename scripts/{tests_generate_expected_json_4.sh => ci_generate_test_detectors_4.sh} (76%) create mode 100755 scripts/ci_generate_test_detectors_5.sh create mode 100755 scripts/ci_generate_test_detectors_6.sh create mode 100755 scripts/ci_generate_test_detectors_7.sh create mode 100755 scripts/ci_test.sh rename scripts/{travis_test_cli.sh => ci_test_cli.sh} (100%) rename scripts/{travis_test_dapp.sh => ci_test_dapp.sh} (100%) rename scripts/{travis_test_data_dependency.sh => ci_test_data_dependency.sh} (100%) rename scripts/{travis_test_4.sh => ci_test_detectors_4.sh} (54%) rename scripts/{travis_test_5.sh => ci_test_detectors_5.sh} (54%) create mode 100755 scripts/ci_test_detectors_6.sh create mode 100755 scripts/ci_test_detectors_7.sh rename scripts/{travis_test_embark.sh => ci_test_embark.sh} (100%) rename scripts/{travis_test_erc.sh => ci_test_erc.sh} (77%) rename scripts/{travis_test_etherlime.sh => ci_test_etherlime.sh} (100%) create mode 100755 scripts/ci_test_etherscan.sh rename scripts/{travis_test_find_paths.sh => ci_test_find_paths.sh} (75%) rename scripts/{travis_test_kspec.sh => ci_test_kspec.sh} (85%) rename scripts/{travis_test_printers.sh => ci_test_printers.sh} (72%) rename scripts/{travis_test_prop.sh => ci_test_prop.sh} (100%) rename scripts/{travis_test_simil.sh => ci_test_simil.sh} (85%) rename scripts/{travis_test_slither_config.sh => ci_test_slither_config.sh} (100%) rename scripts/{travis_test_truffle.sh => ci_test_truffle.sh} (100%) rename scripts/{travis_test_upgradability.sh => ci_test_upgradability.sh} (79%) delete mode 100755 scripts/fix_travis_relative_paths.sh delete mode 100755 scripts/tests_generate_expected_json_5.sh delete mode 100755 scripts/travis_test_etherscan.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4f079c8d..875c91a89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,23 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - type: ["4", "5", "cli", "data_dependency", "embark", "erc", "etherlime", "find_paths", "kspec", "printers", "simil", "slither_config", "truffle", "upgradability", "prop"] + type: ["detectors_4", + "detectors_5", + "detectors_6", + "detectors_7", + "cli", + "data_dependency", + "embark", + "erc", + "etherlime", + "find_paths", + "kspec", + "printers", + "simil", + "slither_config", + "truffle", + "upgradability", + "prop"] steps: - uses: actions/checkout@v1 - name: Set up Python 3.6 @@ -25,19 +41,16 @@ jobs: - name: Install dependencies run: | python setup.py install - # Used by travis_test.sh + # Used by ci_test.sh pip install deepdiff - sudo wget -O /usr/bin/solc-0.4.25 https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux - sudo chmod +x /usr/bin/solc-0.4.25 - sudo wget -O /usr/bin/solc-0.5.1 https://github.com/ethereum/solidity/releases/download/v0.5.1/solc-static-linux - sudo chmod +x /usr/bin/solc-0.5.1 - sudo wget -O /usr/bin/solc-0.5.0 https://github.com/ethereum/solidity/releases/download/v0.5.0/solc-static-linux - sudo chmod +x /usr/bin/solc-0.5.0 - sudo cp /usr/bin/solc-0.5.1 /usr/bin/solc + git clone https://github.com/crytic/solc-select.git + ./solc-select/scripts/install.sh + solc use 0.5.1 + - name: Run Tests env: TEST_TYPE: ${{ matrix.type }} GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }} run: | - bash scripts/travis_test_${TEST_TYPE}.sh + bash scripts/ci_test_${TEST_TYPE}.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13bdc5859..ec72c9e65 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,3 +22,16 @@ Some pull request guidelines: ## Development Environment Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation). + +## Detectors regression tests + +For each new detector, at least one regression tests must be present. +To generate the following scripts, you must have [`solc-select`](https://github.com/crytic/solc-select) installed. + +- Create a test in `tests` +- Update `script/ci_test_detectors_[solc_version].sh`, and add `generate_expected_json tests/YOUR_FILENAME.sol "DETECTOR_NAME"`. Be sure that all the other lines are commented (otherwise you will regenerate the tests for all the detectores) +- Run `./script/ci_test_detectors_[solc_version].sh`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git. +- Update `scripts/ci_test_detectors_[solc_version].sh` with your new tests. +- Run `scripts/ci_test_detectors_[solc_version].sh` and check that everything worked. + + diff --git a/scripts/tests_generate_expected_json_4.sh b/scripts/ci_generate_test_detectors_4.sh similarity index 76% rename from scripts/tests_generate_expected_json_4.sh rename to scripts/ci_generate_test_detectors_4.sh index 4e4750cde..a5349ca70 100755 --- a/scripts/tests_generate_expected_json_4.sh +++ b/scripts/ci_generate_test_detectors_4.sh @@ -1,24 +1,10 @@ #!/usr/bin/env bash -DIR="$(cd "$(dirname "$0")" && pwd)" -CURRENT_PATH=$(pwd) -TRAVIS_PATH='/home/travis/build/crytic/slither' +source "$(dirname "$0")""/ci_test.sh" -# generate_expected_json file.sol detectors -generate_expected_json(){ - # generate output filename - # e.g. file: uninitialized.sol detector: uninitialized-state - # ---> uninitialized.uninitialized-state.json - output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" - output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt" +solc use "0.4.25" - # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" --solc solc-0.4.25 > "$output_filename_txt" 2>&1 - - - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i -} +# Be sure that only one of the following line is uncommented before running the script #generate_expected_json tests/deprecated_calls.sol "deprecated-standards" @@ -58,3 +44,4 @@ generate_expected_json(){ #generate_expected_json tests/solc_version_incorrect.sol "solc-version" #generate_expected_json tests/right_to_left_override.sol "rtlo" #generate_expected_json tests/unchecked_lowlevel.sol "unchecked-lowlevel" + diff --git a/scripts/ci_generate_test_detectors_5.sh b/scripts/ci_generate_test_detectors_5.sh new file mode 100755 index 000000000..e7e027154 --- /dev/null +++ b/scripts/ci_generate_test_detectors_5.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +source "$(dirname "$0")""/ci_test.sh" + +solc use "0.5.1" + +# Be sure that only one of the following line is uncommented before running the script + +# generate_expected_json tests/void-cst.sol "void-cst" +# generate_expected_json tests/solc_version_incorrect_05.ast.json "solc-version" +# generate_expected_json tests/uninitialized-0.5.1.sol "uninitialized-state" +# generate_expected_json tests/backdoor.sol "backdoor" +# generate_expected_json tests/backdoor.sol "suicidal" +# generate_expected_json tests/old_solc.sol.json "solc-version" +# generate_expected_json tests/reentrancy-0.5.1.sol "reentrancy-eth" +# generate_expected_json tests/reentrancy-0.5.1-events.sol "reentrancy-events" +# generate_expected_json tests/tx_origin-0.5.1.sol "tx-origin" +# generate_expected_json tests/locked_ether-0.5.1.sol "locked-ether" +# generate_expected_json tests/arbitrary_send-0.5.1.sol "arbitrary-send" +# generate_expected_json tests/inline_assembly_contract-0.5.1.sol "assembly" +# generate_expected_json tests/inline_assembly_library-0.5.1.sol "assembly" +# generate_expected_json tests/constant-0.5.1.sol "constant-function-asm" +# generate_expected_json tests/constant-0.5.1.sol "constant-function-state" +# generate_expected_json tests/incorrect_equality.sol "incorrect-equality" +# generate_expected_json tests/too_many_digits.sol "too-many-digits" +# generate_expected_json tests/unchecked_lowlevel-0.5.1.sol "unchecked-lowlevel" +# generate_expected_json tests/unchecked_send-0.5.1.sol "unchecked-send" + diff --git a/scripts/ci_generate_test_detectors_6.sh b/scripts/ci_generate_test_detectors_6.sh new file mode 100755 index 000000000..0d1cc46ab --- /dev/null +++ b/scripts/ci_generate_test_detectors_6.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +source "$(dirname "$0")""/ci_test.sh" + +solc use "0.6.11" + +# Be sure that only one of the following line is uncommented before running the script + + +#generate_expected_json tests/filename.sol "detector_name" + diff --git a/scripts/ci_generate_test_detectors_7.sh b/scripts/ci_generate_test_detectors_7.sh new file mode 100755 index 000000000..cbc6500a9 --- /dev/null +++ b/scripts/ci_generate_test_detectors_7.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +source "$(dirname "$0")""/ci_test.sh" + +solc use "0.7.0" + +# Be sure that only one of the following line is uncommented before running the script + + +#generate_expected_json tests/filename.sol "detector_name" + diff --git a/scripts/ci_test.sh b/scripts/ci_test.sh new file mode 100755 index 000000000..2ef5b0357 --- /dev/null +++ b/scripts/ci_test.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +### Test Detectors + +DIR="$(cd "$(dirname "$0")" && pwd)" + +CURRENT_PATH=$(pwd) +TRAVIS_PATH='/home/travis/build/crytic/slither' + +# test_slither file.sol detectors +test_slither(){ + + expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" + + # run slither detector on input file and save output as json + slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" + if [ $? -eq 255 ] + then + echo "Slither crashed" + exit 255 + fi + + if [ ! -f "$DIR/tmp-test.json" ]; then + echo "" + echo "Missing generated file" + echo "" + exit 1 + fi + sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i + result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json") + + rm "$DIR/tmp-test.json" + if [ "$result" != "{}" ]; then + echo "" + echo "failed test of file: $1, detector: $2" + echo "" + echo "$result" + echo "" + exit 1 + fi + + # run slither detector on input file and save output as json + slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json" + if [ $? -eq 255 ] + then + echo "Slither crashed" + exit 255 + fi + + if [ ! -f "$DIR/tmp-test.json" ]; then + echo "" + echo "Missing generated file" + echo "" + exit 1 + fi + + sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i + result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json") + + rm "$DIR/tmp-test.json" + if [ "$result" != "{}" ]; then + echo "" + echo "failed test of file: $1, detector: $2" + echo "" + echo "$result" + echo "" + exit 1 + fi +} + +# generate_expected_json file.sol detectors +generate_expected_json(){ + # generate output filename + # e.g. file: uninitialized.sol detector: uninitialized-state + # ---> uninitialized.uninitialized-state.json + output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" + output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt" + + # run slither detector on input file and save output as json + slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" > "$output_filename_txt" 2>&1 + + + sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i + sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i +} + diff --git a/scripts/travis_test_cli.sh b/scripts/ci_test_cli.sh similarity index 100% rename from scripts/travis_test_cli.sh rename to scripts/ci_test_cli.sh diff --git a/scripts/travis_test_dapp.sh b/scripts/ci_test_dapp.sh similarity index 100% rename from scripts/travis_test_dapp.sh rename to scripts/ci_test_dapp.sh diff --git a/scripts/travis_test_data_dependency.sh b/scripts/ci_test_data_dependency.sh similarity index 100% rename from scripts/travis_test_data_dependency.sh rename to scripts/ci_test_data_dependency.sh diff --git a/scripts/travis_test_4.sh b/scripts/ci_test_detectors_4.sh similarity index 54% rename from scripts/travis_test_4.sh rename to scripts/ci_test_detectors_4.sh index 35059d08b..c147ba406 100755 --- a/scripts/travis_test_4.sh +++ b/scripts/ci_test_detectors_4.sh @@ -1,73 +1,8 @@ #!/usr/bin/env bash -### Test Detectors - -DIR="$(cd "$(dirname "$0")" && pwd)" - -CURRENT_PATH=$(pwd) -TRAVIS_PATH='/home/travis/build/crytic/slither' -# test_slither file.sol detectors -test_slither(){ - - expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" - - # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" --solc solc-0.4.25 - if [ $? -eq 255 ] - then - echo "Slither crashed" - exit 255 - fi - - if [ ! -f "$DIR/tmp-test.json" ]; then - echo "" - echo "Missing generated file" - echo "" - exit 1 - fi - - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i - result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json") - - rm "$DIR/tmp-test.json" - if [ "$result" != "{}" ]; then - echo "" - echo "failed test of file: $1, detector: $2" - echo "" - echo "$result" - echo "" - exit 1 - fi - - # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json" --solc solc-0.4.25 - if [ $? -eq 255 ] - then - echo "Slither crashed" - exit 255 - fi - - if [ ! -f "$DIR/tmp-test.json" ]; then - echo "" - echo "Missing generated file" - echo "" - exit 1 - fi - - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i - result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json") - - rm "$DIR/tmp-test.json" - if [ "$result" != "{}" ]; then - echo "" - echo "failed test of file: $1, detector: $2" - echo "" - echo "$result" - echo "" - exit 1 - fi -} +source "$(dirname "$0")""/ci_test.sh" +solc use "0.4.25" test_slither tests/unchecked_lowlevel.sol "unchecked-lowlevel" test_slither tests/deprecated_calls.sol "deprecated-standards" @@ -92,7 +27,6 @@ test_slither tests/const_state_variables.sol "constable-states" test_slither tests/external_function.sol "external-function" test_slither tests/external_function_2.sol "external-function" test_slither tests/naming_convention.sol "naming-convention" -#test_slither tests/complex_func.sol "complex-function" test_slither tests/controlled_delegatecall.sol "controlled-delegatecall" test_slither tests/uninitialized_local_variable.sol "uninitialized-local" test_slither tests/constant.sol "constant-function-asm" diff --git a/scripts/travis_test_5.sh b/scripts/ci_test_detectors_5.sh similarity index 54% rename from scripts/travis_test_5.sh rename to scripts/ci_test_detectors_5.sh index cada6804a..ac75177bd 100755 --- a/scripts/travis_test_5.sh +++ b/scripts/ci_test_detectors_5.sh @@ -1,73 +1,8 @@ #!/usr/bin/env bash -### Test Detectors - -DIR="$(cd "$(dirname "$0")" && pwd)" - -CURRENT_PATH=$(pwd) -TRAVIS_PATH='/home/travis/build/crytic/slither' - -# test_slither file.sol detectors -test_slither(){ - - expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" - - # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" --solc solc-0.5.1 - if [ $? -eq 255 ] - then - echo "Slither crashed" - exit 255 - fi - - if [ ! -f "$DIR/tmp-test.json" ]; then - echo "" - echo "Missing generated file" - echo "" - exit 1 - fi - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i - result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json") - - rm "$DIR/tmp-test.json" - if [ "$result" != "{}" ]; then - echo "" - echo "failed test of file: $1, detector: $2" - echo "" - echo "$result" - echo "" - exit 1 - fi - - # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json" --solc solc-0.5.1 - if [ $? -eq 255 ] - then - echo "Slither crashed" - exit 255 - fi - - if [ ! -f "$DIR/tmp-test.json" ]; then - echo "" - echo "Missing generated file" - echo "" - exit 1 - fi - - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i - result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json") - - rm "$DIR/tmp-test.json" - if [ "$result" != "{}" ]; then - echo "" - echo "failed test of file: $1, detector: $2" - echo "" - echo "$result" - echo "" - exit 1 - fi -} - +source "$(dirname "$0")""/ci_test.sh" + +solc use "0.5.1" test_slither tests/void-cst.sol "void-cst" test_slither tests/solc_version_incorrect_05.ast.json "solc-version" diff --git a/scripts/ci_test_detectors_6.sh b/scripts/ci_test_detectors_6.sh new file mode 100755 index 000000000..6af1b8486 --- /dev/null +++ b/scripts/ci_test_detectors_6.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +source "$(dirname "$0")""/ci_test.sh" + +solc use "0.6.11" + +# test_slither tests/filename.sol "detector_name" diff --git a/scripts/ci_test_detectors_7.sh b/scripts/ci_test_detectors_7.sh new file mode 100755 index 000000000..6ad291bb6 --- /dev/null +++ b/scripts/ci_test_detectors_7.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +source "$(dirname "$0")""/ci_test.sh" + +solc use "0.7.0" + +# test_slither tests/filename.sol "detector_name" diff --git a/scripts/travis_test_embark.sh b/scripts/ci_test_embark.sh similarity index 100% rename from scripts/travis_test_embark.sh rename to scripts/ci_test_embark.sh diff --git a/scripts/travis_test_erc.sh b/scripts/ci_test_erc.sh similarity index 77% rename from scripts/travis_test_erc.sh rename to scripts/ci_test_erc.sh index 12e973137..a96a414e1 100755 --- a/scripts/travis_test_erc.sh +++ b/scripts/ci_test_erc.sh @@ -4,7 +4,8 @@ DIR_TESTS="tests/check-erc" -slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 --solc solc-0.5.0 > test_1.txt 2>&1 +solc use 0.5.0 +slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") if [ "$DIFF" != "" ] then diff --git a/scripts/travis_test_etherlime.sh b/scripts/ci_test_etherlime.sh similarity index 100% rename from scripts/travis_test_etherlime.sh rename to scripts/ci_test_etherlime.sh diff --git a/scripts/ci_test_etherscan.sh b/scripts/ci_test_etherscan.sh new file mode 100755 index 000000000..08a160f50 --- /dev/null +++ b/scripts/ci_test_etherscan.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +### Test etherscan integration + +mkdir etherscan +cd etherscan || exit 255 + +slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN" + +if [ $? -ne 5 ] +then + echo "Etherscan test failed" + exit 255 +fi + +slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN" + +if [ $? -ne 70 ] +then + echo "Etherscan test failed" + exit 255 +fi + diff --git a/scripts/travis_test_find_paths.sh b/scripts/ci_test_find_paths.sh similarity index 75% rename from scripts/travis_test_find_paths.sh rename to scripts/ci_test_find_paths.sh index 088d02e65..2707aaa07 100755 --- a/scripts/travis_test_find_paths.sh +++ b/scripts/ci_test_find_paths.sh @@ -4,7 +4,8 @@ DIR_TESTS="tests/possible_paths" -slither-find-paths "$DIR_TESTS/paths.sol" A.destination --solc solc-0.5.0 > test_possible_paths.txt 2>&1 +solc use "0.5.0" +slither-find-paths "$DIR_TESTS/paths.sol" A.destination > test_possible_paths.txt 2>&1 DIFF=$(diff test_possible_paths.txt "$DIR_TESTS/paths.txt") if [ "$DIFF" != "" ] then diff --git a/scripts/travis_test_kspec.sh b/scripts/ci_test_kspec.sh similarity index 85% rename from scripts/travis_test_kspec.sh rename to scripts/ci_test_kspec.sh index 7fa36858b..c80df54e0 100755 --- a/scripts/travis_test_kspec.sh +++ b/scripts/ci_test_kspec.sh @@ -2,7 +2,8 @@ DIR_TESTS="tests/check-kspec" -slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md" --solc solc-0.5.0 > test_1.txt 2>&1 +solc use "0.5.0" +slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md" > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") if [ "$DIFF" != "" ] then diff --git a/scripts/travis_test_printers.sh b/scripts/ci_test_printers.sh similarity index 72% rename from scripts/travis_test_printers.sh rename to scripts/ci_test_printers.sh index 9ff51b0aa..209f4329e 100755 --- a/scripts/travis_test_printers.sh +++ b/scripts/ci_test_printers.sh @@ -10,4 +10,6 @@ if ! slither "tests/*.json" --print all --json -; then exit 1 fi -slither examples/scripts/test_evm_api.sol --print evm --solc solc-0.5.1 +solc use "0.5.1" + +slither examples/scripts/test_evm_api.sol --print evm diff --git a/scripts/travis_test_prop.sh b/scripts/ci_test_prop.sh similarity index 100% rename from scripts/travis_test_prop.sh rename to scripts/ci_test_prop.sh diff --git a/scripts/travis_test_simil.sh b/scripts/ci_test_simil.sh similarity index 85% rename from scripts/travis_test_simil.sh rename to scripts/ci_test_simil.sh index 349f3eca9..5c8a8f70d 100755 --- a/scripts/travis_test_simil.sh +++ b/scripts/ci_test_simil.sh @@ -7,8 +7,10 @@ pip3.6 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip ### Test slither-simil +solc use "0.4.25" + DIR_TESTS="tests/simil" -slither-simil info "" --filename $DIR_TESTS/../complex_func.sol --fname Complex.complexExternalWrites --solc solc-0.4.25 > test_1.txt 2>&1 +slither-simil info "" --filename $DIR_TESTS/../complex_func.sol --fname Complex.complexExternalWrites > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") if [ "$DIFF" != "" ] then diff --git a/scripts/travis_test_slither_config.sh b/scripts/ci_test_slither_config.sh similarity index 100% rename from scripts/travis_test_slither_config.sh rename to scripts/ci_test_slither_config.sh diff --git a/scripts/travis_test_truffle.sh b/scripts/ci_test_truffle.sh similarity index 100% rename from scripts/travis_test_truffle.sh rename to scripts/ci_test_truffle.sh diff --git a/scripts/travis_test_upgradability.sh b/scripts/ci_test_upgradability.sh similarity index 79% rename from scripts/travis_test_upgradability.sh rename to scripts/ci_test_upgradability.sh index cfb30bebe..2dd8f7a52 100755 --- a/scripts/travis_test_upgradability.sh +++ b/scripts/ci_test_upgradability.sh @@ -3,8 +3,9 @@ ### Test slither-check-upgradeability DIR_TESTS="tests/check-upgradeability" +solc use "0.5.0" -slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_1.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_1.txt 2>&1 DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") if [ "$DIFF" != "" ] then @@ -15,7 +16,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2.sol" --new-contract-name ContractV2 > test_2.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --new-contract-filename "$DIR_TESTS/contractV2.sol" --new-contract-name ContractV2 > test_2.txt 2>&1 DIFF=$(diff test_2.txt "$DIR_TESTS/test_2.txt") if [ "$DIFF" != "" ] then @@ -26,7 +27,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2_bug.sol" --new-contract-name ContractV2 > test_3.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --new-contract-filename "$DIR_TESTS/contractV2_bug.sol" --new-contract-name ContractV2 > test_3.txt 2>&1 DIFF=$(diff test_3.txt "$DIR_TESTS/test_3.txt") if [ "$DIFF" != "" ] then @@ -37,7 +38,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contractV2_bug2.sol" --new-contract-name ContractV2 > test_4.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --new-contract-filename "$DIR_TESTS/contractV2_bug2.sol" --new-contract-name ContractV2 > test_4.txt 2>&1 DIFF=$(diff test_4.txt "$DIR_TESTS/test_4.txt") if [ "$DIFF" != "" ] then @@ -48,7 +49,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_5.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_5.txt 2>&1 DIFF=$(diff test_5.txt "$DIR_TESTS/test_5.txt") if [ "$DIFF" != "" ] then @@ -61,7 +62,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_5.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_5.txt 2>&1 DIFF=$(diff test_5.txt "$DIR_TESTS/test_5.txt") if [ "$DIFF" != "" ] then @@ -75,7 +76,7 @@ then fi -slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_lack_to_call_modifier --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_6.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_lack_to_call_modifier --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_6.txt 2>&1 DIFF=$(diff test_6.txt "$DIR_TESTS/test_6.txt") if [ "$DIFF" != "" ] then @@ -89,7 +90,7 @@ then fi -slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_not_called_super_init --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_7.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_not_called_super_init --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_7.txt 2>&1 DIFF=$(diff test_7.txt "$DIR_TESTS/test_7.txt") if [ "$DIFF" != "" ] then @@ -102,7 +103,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_inherits --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_8.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_inherits --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_8.txt 2>&1 DIFF=$(diff test_8.txt "$DIR_TESTS/test_8.txt") if [ "$DIFF" != "" ] then @@ -115,7 +116,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_double_call --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy --solc solc-0.5.0 > test_9.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_double_call --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_9.txt 2>&1 DIFF=$(diff test_9.txt "$DIR_TESTS/test_9.txt") if [ "$DIFF" != "" ] then @@ -128,7 +129,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --solc solc-0.5.0 --new-contract-filename "$DIR_TESTS/contract_v2_constant.sol" --new-contract-name ContractV2 > test_10.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --new-contract-filename "$DIR_TESTS/contract_v2_constant.sol" --new-contract-name ContractV2 > test_10.txt 2>&1 DIFF=$(diff test_10.txt "$DIR_TESTS/test_10.txt") if [ "$DIFF" != "" ] then @@ -141,7 +142,7 @@ then exit 255 fi -slither-check-upgradeability "$DIR_TESTS/contract_v1_var_init.sol" ContractV1 --solc solc-0.5.0 > test_11.txt 2>&1 +slither-check-upgradeability "$DIR_TESTS/contract_v1_var_init.sol" ContractV1 > test_11.txt 2>&1 DIFF=$(diff test_11.txt "$DIR_TESTS/test_11.txt") if [ "$DIFF" != "" ] then diff --git a/scripts/fix_travis_relative_paths.sh b/scripts/fix_travis_relative_paths.sh deleted file mode 100755 index 76de85ed9..000000000 --- a/scripts/fix_travis_relative_paths.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -CURRENT_PATH=$(pwd) -TRAVIS_PATH='/home/travis/build/crytic/slither' -for f in tests/expected_json/*json; do - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$f" -i -done diff --git a/scripts/tests_generate_expected_json_5.sh b/scripts/tests_generate_expected_json_5.sh deleted file mode 100755 index b4f70bbbe..000000000 --- a/scripts/tests_generate_expected_json_5.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -DIR="$(cd "$(dirname "$0")" && pwd)" -CURRENT_PATH=$(pwd) -TRAVIS_PATH='/home/travis/build/crytic/slither' - - -# generate_expected_json file.sol detectors -generate_expected_json(){ - # generate output filename - # e.g. file: uninitialized.sol detector: uninitialized-state - # ---> uninitialized.uninitialized-state.json - output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" - output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt" - - # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" --solc solc-0.5.1 > "$output_filename_txt" 2>&1 - - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i - sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i -} - -generate_expected_json tests/void-cst.sol "void-cst" -generate_expected_json tests/solc_version_incorrect_05.ast.json "solc-version" -generate_expected_json tests/uninitialized-0.5.1.sol "uninitialized-state" -generate_expected_json tests/backdoor.sol "backdoor" -generate_expected_json tests/backdoor.sol "suicidal" -generate_expected_json tests/old_solc.sol.json "solc-version" -generate_expected_json tests/reentrancy-0.5.1.sol "reentrancy-eth" -generate_expected_json tests/reentrancy-0.5.1-events.sol "reentrancy-events" -generate_expected_json tests/tx_origin-0.5.1.sol "tx-origin" -generate_expected_json tests/locked_ether-0.5.1.sol "locked-ether" -generate_expected_json tests/arbitrary_send-0.5.1.sol "arbitrary-send" -generate_expected_json tests/inline_assembly_contract-0.5.1.sol "assembly" -generate_expected_json tests/inline_assembly_library-0.5.1.sol "assembly" -generate_expected_json tests/constant-0.5.1.sol "constant-function-asm" -generate_expected_json tests/constant-0.5.1.sol "constant-function-state" -generate_expected_json tests/incorrect_equality.sol "incorrect-equality" -generate_expected_json tests/too_many_digits.sol "too-many-digits" -generate_expected_json tests/unchecked_lowlevel-0.5.1.sol "unchecked-lowlevel" -generate_expected_json tests/unchecked_send-0.5.1.sol "unchecked-send" - diff --git a/scripts/travis_test_etherscan.sh b/scripts/travis_test_etherscan.sh deleted file mode 100755 index 0398fa48f..000000000 --- a/scripts/travis_test_etherscan.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -### Test etherscan integration - -mkdir etherscan -cd etherscan || exit 255 - -wget -O solc-0.4.25 https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux -chmod +x solc-0.4.25 - -slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --solc "./solc-0.4.25" --etherscan-apikey "$GITHUB_ETHERSCAN" - -if [ $? -ne 5 ] -then - echo "Etherscan test failed" - exit 255 -fi - -slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --solc "./solc-0.4.25" --etherscan-apikey "$GITHUB_ETHERSCAN" - -if [ $? -ne 70 ] -then - echo "Etherscan test failed" - exit 255 -fi - From 93eb08e1baa423ee3764458969f0344da9b6b668 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 11:12:40 +0200 Subject: [PATCH 67/73] Update solc-select installation --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 875c91a89..3dd42a345 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,8 @@ jobs: git clone https://github.com/crytic/solc-select.git ./solc-select/scripts/install.sh + export PATH=/home/runner/.solc-select:$PATH + echo "export PATH=/home/runner/.solc-select:$PATH" >> ~/.bashrc solc use 0.5.1 - name: Run Tests From e77ec257c458506a19403835b22dbf14d60f90e4 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 11:20:27 +0200 Subject: [PATCH 68/73] load bashrc in workflow --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3dd42a345..5737a99b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,10 @@ name: CI +defaults: + run: + # To load bashrc + shell: bash -ieo pipefail {0} + on: push: branches: From 88954ac6d9015fc0b38ac08b90add6be3c36ea8a Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 11:32:33 +0200 Subject: [PATCH 69/73] Run black --- slither/__init__.py | 2 +- slither/__main__.py | 619 ++++++++++-------- slither/all_exceptions.py | 2 +- .../data_dependency/data_dependency.py | 135 ++-- slither/analyses/evm/convert.py | 102 +-- slither/analyses/evm/evm_cfg_builder.py | 4 +- .../analyses/write/are_variables_written.py | 22 +- slither/core/cfg/node.py | 8 +- slither/core/declarations/function.py | 12 +- .../core/declarations/solidity_variables.py | 2 +- slither/core/slither_core.py | 4 +- .../core/solidity_types/elementary_type.py | 2 +- slither/core/solidity_types/type.py | 3 +- slither/detectors/abstract_detector.py | 187 ++++-- slither/detectors/all_detectors.py | 4 +- .../attributes/const_functions_asm.py | 26 +- .../attributes/const_functions_state.py | 28 +- .../detectors/attributes/constant_pragma.py | 13 +- .../detectors/attributes/incorrect_solc.py | 80 ++- slither/detectors/attributes/locked_ether.py | 38 +- .../erc/incorrect_erc20_interface.py | 44 +- .../erc/incorrect_erc721_interface.py | 62 +- .../erc/unindexed_event_parameters.py | 23 +- slither/detectors/examples/backdoor.py | 19 +- slither/detectors/functions/arbitrary_send.py | 54 +- .../detectors/functions/complex_function.py | 47 +- .../detectors/functions/external_function.py | 60 +- slither/detectors/functions/suicidal.py | 21 +- .../naming_convention/naming_convention.py | 95 ++- .../detectors/operations/block_timestamp.py | 35 +- .../detectors/operations/low_level_calls.py | 19 +- .../unchecked_low_level_return_values.py | 21 +- .../operations/unchecked_send_return_value.py | 19 +- .../operations/unused_return_values.py | 19 +- .../detectors/operations/void_constructor.py | 18 +- slither/detectors/reentrancy/reentrancy.py | 64 +- .../detectors/reentrancy/reentrancy_benign.py | 112 ++-- .../detectors/reentrancy/reentrancy_eth.py | 107 +-- .../detectors/reentrancy/reentrancy_events.py | 91 ++- .../detectors/reentrancy/reentrancy_no_gas.py | 146 +++-- .../reentrancy_read_before_write.py | 90 +-- slither/detectors/shadowing/abstract.py | 21 +- .../detectors/shadowing/builtin_symbols.py | 88 ++- slither/detectors/shadowing/local.py | 22 +- slither/detectors/shadowing/state.py | 20 +- slither/detectors/slither/name_reused.py | 50 +- slither/detectors/source/rtlo.py | 32 +- slither/detectors/statements/assembly.py | 16 +- .../statements/boolean_constant_equality.py | 16 +- .../statements/boolean_constant_misuse.py | 34 +- slither/detectors/statements/calls_in_loop.py | 31 +- .../statements/controlled_delegatecall.py | 29 +- .../detectors/statements/deprecated_calls.py | 38 +- .../statements/divide_before_multiply.py | 28 +- .../statements/incorrect_strict_equality.py | 68 +- .../detectors/statements/too_many_digits.py | 28 +- slither/detectors/statements/tx_origin.py | 33 +- .../statements/type_based_tautology.py | 38 +- .../possible_const_state_variables.py | 55 +- .../uninitialized_local_variables.py | 24 +- .../uninitialized_state_variables.py | 41 +- .../uninitialized_storage_variables.py | 23 +- .../variables/unused_state_variables.py | 35 +- slither/exceptions.py | 7 +- .../formatters/attributes/const_functions.py | 51 +- .../formatters/attributes/constant_pragma.py | 48 +- .../formatters/attributes/incorrect_solc.py | 56 +- slither/formatters/exceptions.py | 8 +- .../formatters/functions/external_function.py | 63 +- .../naming_convention/naming_convention.py | 499 +++++++------- slither/formatters/utils/patches.py | 43 +- .../possible_const_state_variables.py | 44 +- .../variables/unused_state_variables.py | 40 +- slither/printers/abstract_printer.py | 21 +- slither/printers/all_printers.py | 2 +- slither/printers/call/call_graph.py | 170 +++-- slither/printers/functions/authorization.py | 34 +- slither/printers/functions/cfg.py | 18 +- slither/printers/guidance/echidna.py | 193 +++--- slither/printers/inheritance/inheritance.py | 48 +- .../printers/inheritance/inheritance_graph.py | 127 ++-- slither/printers/summary/constructor_calls.py | 32 +- slither/printers/summary/contract.py | 53 +- slither/printers/summary/data_depenency.py | 30 +- slither/printers/summary/evm.py | 87 ++- slither/printers/summary/function.py | 73 ++- slither/printers/summary/function_ids.py | 20 +- slither/printers/summary/human_summary.py | 177 ++--- slither/printers/summary/modifier_calls.py | 17 +- slither/printers/summary/require_calls.py | 34 +- slither/printers/summary/slithir.py | 26 +- slither/printers/summary/slithir_ssa.py | 26 +- slither/printers/summary/variable_order.py | 16 +- slither/slither.py | 1 - slither/slithir/convert.py | 461 ++++++++----- slither/slithir/exceptions.py | 4 +- slither/slithir/operations/assignment.py | 12 +- slither/slithir/operations/balance.py | 3 +- slither/slithir/operations/binary.py | 81 +-- slither/slithir/operations/call.py | 9 +- slither/slithir/operations/codesize.py | 3 +- slither/slithir/operations/event_call.py | 3 +- slither/slithir/operations/high_level_call.py | 44 +- slither/slithir/operations/index.py | 9 +- slither/slithir/operations/init_array.py | 2 - slither/slithir/operations/internal_call.py | 15 +- .../operations/internal_dynamic_call.py | 27 +- slither/slithir/operations/length.py | 3 +- slither/slithir/operations/library_call.py | 24 +- slither/slithir/operations/low_level_call.py | 38 +- slither/slithir/operations/lvalue.py | 4 +- slither/slithir/operations/member.py | 7 +- slither/slithir/operations/new_array.py | 5 +- slither/slithir/operations/new_contract.py | 17 +- .../slithir/operations/new_elementary_type.py | 3 +- slither/slithir/operations/new_structure.py | 3 +- slither/slithir/operations/nop.py | 1 - slither/slithir/operations/operation.py | 2 - slither/slithir/operations/phi.py | 7 +- slither/slithir/operations/phi_callback.py | 9 +- slither/slithir/operations/push.py | 1 - .../slithir/operations/return_operation.py | 10 +- slither/slithir/operations/send.py | 7 +- slither/slithir/operations/solidity_call.py | 23 +- slither/slithir/operations/transfer.py | 5 +- slither/slithir/operations/type_conversion.py | 4 +- slither/slithir/operations/unary.py | 7 +- slither/slithir/operations/unpack.py | 8 +- slither/slithir/tmp_operations/argument.py | 15 +- slither/slithir/tmp_operations/tmp_call.py | 24 +- .../slithir/tmp_operations/tmp_new_array.py | 5 +- .../tmp_operations/tmp_new_contract.py | 5 +- .../tmp_operations/tmp_new_elementary_type.py | 4 +- .../tmp_operations/tmp_new_structure.py | 5 +- slither/slithir/utils/ssa.py | 358 ++++++---- slither/slithir/utils/utils.py | 19 +- slither/slithir/variables/constant.py | 29 +- slither/slithir/variables/local_variable.py | 9 +- slither/slithir/variables/reference.py | 9 +- slither/slithir/variables/reference_ssa.py | 6 +- slither/slithir/variables/state_variable.py | 5 +- slither/slithir/variables/temporary.py | 6 +- slither/slithir/variables/temporary_ssa.py | 6 +- slither/slithir/variables/tuple.py | 4 +- slither/slithir/variables/tuple_ssa.py | 8 +- slither/slithir/variables/variable.py | 2 +- slither/solc_parsing/declarations/contract.py | 7 +- slither/solc_parsing/declarations/function.py | 19 +- slither/solc_parsing/slitherSolc.py | 13 +- slither/solc_parsing/yul/evm_functions.py | 170 ++--- slither/solc_parsing/yul/parse_yul.py | 236 +++---- slither/tools/flattening/__main__.py | 7 +- slither/tools/flattening/flattening.py | 2 +- .../visitors/slithir/expression_to_slithir.py | 113 ++-- 154 files changed, 4141 insertions(+), 3099 deletions(-) diff --git a/slither/__init__.py b/slither/__init__.py index 773c3f527..c709b144c 100644 --- a/slither/__init__.py +++ b/slither/__init__.py @@ -1 +1 @@ -from .slither import Slither \ No newline at end of file +from .slither import Slither diff --git a/slither/__main__.py b/slither/__main__.py index 313fd8fb2..198a1f304 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -14,18 +14,26 @@ from crytic_compile import cryticparser from crytic_compile.platform.standard import generate_standard_export from slither.detectors import all_detectors -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.printers import all_printers from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither from slither.utils.output import output_to_json, output_to_zip, ZIP_TYPES_ACCEPTED from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, blue, set_colorization_enabled -from slither.utils.command_line import (output_detectors, output_results_to_markdown, - output_detectors_json, output_printers, output_printers_json, - output_to_markdown, output_wiki, defaults_flag_in_config, - read_config_file, JSON_OUTPUT_TYPES, DEFAULT_JSON_OUTPUT_TYPES) +from slither.utils.command_line import ( + output_detectors, + output_results_to_markdown, + output_detectors_json, + output_printers, + output_printers_json, + output_to_markdown, + output_wiki, + defaults_flag_in_config, + read_config_file, + JSON_OUTPUT_TYPES, + DEFAULT_JSON_OUTPUT_TYPES, +) from crytic_compile import compile_all, is_supported from slither.exceptions import SlitherException @@ -47,12 +55,10 @@ def process_single(target, args, detector_classes, printer_classes): Returns: list(result), int: Result list and number of contracts analyzed """ - ast = '--ast-compact-json' + ast = "--ast-compact-json" if args.legacy_ast: - ast = '--ast-json' - slither = Slither(target, - ast_format=ast, - **vars(args)) + ast = "--ast-json" + slither = Slither(target, ast_format=ast, **vars(args)) return _process(slither, detector_classes, printer_classes) @@ -64,8 +70,12 @@ def process_all(target, args, detector_classes, printer_classes): results_printers = [] analyzed_contracts_count = 0 for compilation in compilations: - (slither, current_results_detectors, current_results_printers, current_analyzed_count) = process_single( - compilation, args, detector_classes, printer_classes) + ( + slither, + current_results_detectors, + current_results_printers, + current_analyzed_count, + ) = process_single(compilation, args, detector_classes, printer_classes) results_detectors.extend(current_results_detectors) results_printers.extend(current_results_printers) slither_instances.append(slither) @@ -103,9 +113,9 @@ def process_from_asts(filenames, args, detector_classes, printer_classes): all_contracts = [] for filename in filenames: - with open(filename, encoding='utf8') as f: + with open(filename, encoding="utf8") as f: contract_loaded = json.load(f) - all_contracts.append(contract_loaded['ast']) + all_contracts.append(contract_loaded["ast"]) return process_single(all_contracts, args, detector_classes, printer_classes) @@ -117,6 +127,7 @@ def process_from_asts(filenames, args, detector_classes, printer_classes): ################################################################################### ################################################################################### + def exit(results): if not results: sys.exit(0) @@ -130,6 +141,7 @@ def exit(results): ################################################################################### ################################################################################### + def get_detectors_and_printers(): """ NOTE: This contains just a few detectors and printers that we made public. @@ -142,16 +154,16 @@ def get_detectors_and_printers(): printers = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)] # Handle plugins! - for entry_point in iter_entry_points(group='slither_analyzer.plugin', name=None): + for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None): make_plugin = entry_point.load() plugin_detectors, plugin_printers = make_plugin() if not all(issubclass(d, AbstractDetector) for d in plugin_detectors): - raise Exception('Error when loading plugin %s, %r is not a detector' % (entry_point, d)) + raise Exception("Error when loading plugin %s, %r is not a detector" % (entry_point, d)) if not all(issubclass(p, AbstractPrinter) for p in plugin_printers): - raise Exception('Error when loading plugin %s, %r is not a printer' % (entry_point, p)) + raise Exception("Error when loading plugin %s, %r is not a printer" % (entry_point, p)) # We convert those to lists in case someone returns a tuple detectors += list(plugin_detectors) @@ -166,41 +178,43 @@ def choose_detectors(args, all_detector_classes): detectors_to_run = [] detectors = {d.ARGUMENT: d for d in all_detector_classes} - if args.detectors_to_run == 'all': + if args.detectors_to_run == "all": detectors_to_run = all_detector_classes if args.detectors_to_exclude: - detectors_excluded = args.detectors_to_exclude.split(',') + detectors_excluded = args.detectors_to_exclude.split(",") for d in detectors: if d in detectors_excluded: detectors_to_run.remove(detectors[d]) else: - for d in args.detectors_to_run.split(','): + for d in args.detectors_to_run.split(","): if d in detectors: detectors_to_run.append(detectors[d]) else: - raise Exception('Error: {} is not a detector'.format(d)) + raise Exception("Error: {} is not a detector".format(d)) detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) return detectors_to_run if args.exclude_optimization: - detectors_to_run = [d for d in detectors_to_run if - d.IMPACT != DetectorClassification.OPTIMIZATION] + detectors_to_run = [ + d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION + ] if args.exclude_informational: - detectors_to_run = [d for d in detectors_to_run if - d.IMPACT != DetectorClassification.INFORMATIONAL] + detectors_to_run = [ + d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL + ] if args.exclude_low: - detectors_to_run = [d for d in detectors_to_run if - d.IMPACT != DetectorClassification.LOW] + detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW] if args.exclude_medium: - detectors_to_run = [d for d in detectors_to_run if - d.IMPACT != DetectorClassification.MEDIUM] + detectors_to_run = [ + d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM + ] if args.exclude_high: - detectors_to_run = [d for d in detectors_to_run if - d.IMPACT != DetectorClassification.HIGH] + detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH] if args.detectors_to_exclude: - detectors_to_run = [d for d in detectors_to_run if - d.ARGUMENT not in args.detectors_to_exclude] + detectors_to_run = [ + d for d in detectors_to_run if d.ARGUMENT not in args.detectors_to_exclude + ] detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) @@ -214,15 +228,15 @@ def choose_printers(args, all_printer_classes): if args.printers_to_run is None: return [] - if args.printers_to_run == 'all': + if args.printers_to_run == "all": return all_printer_classes printers = {p.ARGUMENT: p for p in all_printer_classes} - for p in args.printers_to_run.split(','): + for p in args.printers_to_run.split(","): if p in printers: printers_to_run.append(printers[p]) else: - raise Exception('Error: {} is not a printer'.format(p)) + raise Exception("Error: {} is not a printer".format(p)) return printers_to_run @@ -233,203 +247,236 @@ def choose_printers(args, all_printer_classes): ################################################################################### ################################################################################### + def parse_filter_paths(args): if args.filter_paths: - return args.filter_paths.split(',') + return args.filter_paths.split(",") return [] def parse_args(detector_classes, printer_classes): parser = argparse.ArgumentParser( - description='Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage', - usage="slither.py contract.sol [flag]") + description="Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage", + usage="slither.py contract.sol [flag]", + ) - parser.add_argument('filename', - help='contract.sol') + parser.add_argument("filename", help="contract.sol") cryticparser.init(parser) - parser.add_argument('--version', - help='displays the current version', - version=require('slither-analyzer')[0].version, - action='version') - - group_detector = parser.add_argument_group('Detectors') - group_printer = parser.add_argument_group('Printers') - group_misc = parser.add_argument_group('Additional options') - - group_detector.add_argument('--detect', - help='Comma-separated list of detectors, defaults to all, ' - 'available detectors: {}'.format( - ', '.join(d.ARGUMENT for d in detector_classes)), - action='store', - dest='detectors_to_run', - default=defaults_flag_in_config['detectors_to_run']) - - group_printer.add_argument('--print', - help='Comma-separated list fo contract information printers, ' - 'available printers: {}'.format( - ', '.join(d.ARGUMENT for d in printer_classes)), - action='store', - dest='printers_to_run', - default=defaults_flag_in_config['printers_to_run']) - - group_detector.add_argument('--list-detectors', - help='List available detectors', - action=ListDetectors, - nargs=0, - default=False) - - group_printer.add_argument('--list-printers', - help='List available printers', - action=ListPrinters, - nargs=0, - default=False) - - group_detector.add_argument('--exclude', - help='Comma-separated list of detectors that should be excluded', - action='store', - dest='detectors_to_exclude', - default=defaults_flag_in_config['detectors_to_exclude']) - - group_detector.add_argument('--exclude-dependencies', - help='Exclude results that are only related to dependencies', - action='store_true', - default=defaults_flag_in_config['exclude_dependencies']) - - group_detector.add_argument('--exclude-optimization', - help='Exclude optimization analyses', - action='store_true', - default=defaults_flag_in_config['exclude_optimization']) - - group_detector.add_argument('--exclude-informational', - help='Exclude informational impact analyses', - action='store_true', - default=defaults_flag_in_config['exclude_informational']) - - group_detector.add_argument('--exclude-low', - help='Exclude low impact analyses', - action='store_true', - default=defaults_flag_in_config['exclude_low']) - - group_detector.add_argument('--exclude-medium', - help='Exclude medium impact analyses', - action='store_true', - default=defaults_flag_in_config['exclude_medium']) - - group_detector.add_argument('--exclude-high', - help='Exclude high impact analyses', - action='store_true', - default=defaults_flag_in_config['exclude_high']) - - group_misc.add_argument('--json', - help='Export the results as a JSON file ("--json -" to export to stdout)', - action='store', - default=defaults_flag_in_config['json']) - - group_misc.add_argument('--json-types', - help=f'Comma-separated list of result types to output to JSON, defaults to ' + \ - f'{",".join(output_type for output_type in DEFAULT_JSON_OUTPUT_TYPES)}. ' + \ - f'Available types: {",".join(output_type for output_type in JSON_OUTPUT_TYPES)}', - action='store', - default=defaults_flag_in_config['json-types']) - - group_misc.add_argument('--zip', - help='Export the results as a zipped JSON file', - action='store', - default=defaults_flag_in_config['zip']) - - group_misc.add_argument('--zip-type', - help=f'Zip compression type. One of {",".join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma', - action='store', - default=defaults_flag_in_config['zip_type']) - - group_misc.add_argument('--markdown-root', - help='URL for markdown generation', - action='store', - default="") - - group_misc.add_argument('--disable-color', - help='Disable output colorization', - action='store_true', - default=defaults_flag_in_config['disable_color']) - - group_misc.add_argument('--filter-paths', - help='Comma-separated list of paths for which results will be excluded', - action='store', - dest='filter_paths', - default=defaults_flag_in_config['filter_paths']) - - group_misc.add_argument('--triage-mode', - help='Run triage mode (save results in slither.db.json)', - action='store_true', - dest='triage_mode', - default=False) - - group_misc.add_argument('--config-file', - help='Provide a config file (default: slither.config.json)', - action='store', - dest='config_file', - default='slither.config.json') - - group_misc.add_argument('--solc-ast', - help='Provide the contract as a json AST', - action='store_true', - default=False) - - group_misc.add_argument('--generate-patches', - help='Generate patches (json output only)', - action='store_true', - default=False) + parser.add_argument( + "--version", + help="displays the current version", + version=require("slither-analyzer")[0].version, + action="version", + ) + + group_detector = parser.add_argument_group("Detectors") + group_printer = parser.add_argument_group("Printers") + group_misc = parser.add_argument_group("Additional options") + + group_detector.add_argument( + "--detect", + help="Comma-separated list of detectors, defaults to all, " + "available detectors: {}".format(", ".join(d.ARGUMENT for d in detector_classes)), + action="store", + dest="detectors_to_run", + default=defaults_flag_in_config["detectors_to_run"], + ) + + group_printer.add_argument( + "--print", + help="Comma-separated list fo contract information printers, " + "available printers: {}".format(", ".join(d.ARGUMENT for d in printer_classes)), + action="store", + dest="printers_to_run", + default=defaults_flag_in_config["printers_to_run"], + ) + + group_detector.add_argument( + "--list-detectors", + help="List available detectors", + action=ListDetectors, + nargs=0, + default=False, + ) + + group_printer.add_argument( + "--list-printers", + help="List available printers", + action=ListPrinters, + nargs=0, + default=False, + ) + + group_detector.add_argument( + "--exclude", + help="Comma-separated list of detectors that should be excluded", + action="store", + dest="detectors_to_exclude", + default=defaults_flag_in_config["detectors_to_exclude"], + ) + + group_detector.add_argument( + "--exclude-dependencies", + help="Exclude results that are only related to dependencies", + action="store_true", + default=defaults_flag_in_config["exclude_dependencies"], + ) + + group_detector.add_argument( + "--exclude-optimization", + help="Exclude optimization analyses", + action="store_true", + default=defaults_flag_in_config["exclude_optimization"], + ) + + group_detector.add_argument( + "--exclude-informational", + help="Exclude informational impact analyses", + action="store_true", + default=defaults_flag_in_config["exclude_informational"], + ) + + group_detector.add_argument( + "--exclude-low", + help="Exclude low impact analyses", + action="store_true", + default=defaults_flag_in_config["exclude_low"], + ) + + group_detector.add_argument( + "--exclude-medium", + help="Exclude medium impact analyses", + action="store_true", + default=defaults_flag_in_config["exclude_medium"], + ) + + group_detector.add_argument( + "--exclude-high", + help="Exclude high impact analyses", + action="store_true", + default=defaults_flag_in_config["exclude_high"], + ) + + group_misc.add_argument( + "--json", + help='Export the results as a JSON file ("--json -" to export to stdout)', + action="store", + default=defaults_flag_in_config["json"], + ) + + group_misc.add_argument( + "--json-types", + help=f"Comma-separated list of result types to output to JSON, defaults to " + + f'{",".join(output_type for output_type in DEFAULT_JSON_OUTPUT_TYPES)}. ' + + f'Available types: {",".join(output_type for output_type in JSON_OUTPUT_TYPES)}', + action="store", + default=defaults_flag_in_config["json-types"], + ) + + group_misc.add_argument( + "--zip", + help="Export the results as a zipped JSON file", + action="store", + default=defaults_flag_in_config["zip"], + ) + + group_misc.add_argument( + "--zip-type", + help=f'Zip compression type. One of {",".join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma', + action="store", + default=defaults_flag_in_config["zip_type"], + ) + + group_misc.add_argument( + "--markdown-root", help="URL for markdown generation", action="store", default="" + ) + + group_misc.add_argument( + "--disable-color", + help="Disable output colorization", + action="store_true", + default=defaults_flag_in_config["disable_color"], + ) + + group_misc.add_argument( + "--filter-paths", + help="Comma-separated list of paths for which results will be excluded", + action="store", + dest="filter_paths", + default=defaults_flag_in_config["filter_paths"], + ) + + group_misc.add_argument( + "--triage-mode", + help="Run triage mode (save results in slither.db.json)", + action="store_true", + dest="triage_mode", + default=False, + ) + + group_misc.add_argument( + "--config-file", + help="Provide a config file (default: slither.config.json)", + action="store", + dest="config_file", + default="slither.config.json", + ) + + group_misc.add_argument( + "--solc-ast", help="Provide the contract as a json AST", action="store_true", default=False + ) + + group_misc.add_argument( + "--generate-patches", + help="Generate patches (json output only)", + action="store_true", + default=False, + ) # debugger command - parser.add_argument('--debug', - help=argparse.SUPPRESS, - action="store_true", - default=False) - - parser.add_argument('--markdown', - help=argparse.SUPPRESS, - action=OutputMarkdown, - default=False) - - group_misc.add_argument('--checklist', - help=argparse.SUPPRESS, - action='store_true', - default=False) - - parser.add_argument('--wiki-detectors', - help=argparse.SUPPRESS, - action=OutputWiki, - default=False) - - parser.add_argument('--list-detectors-json', - help=argparse.SUPPRESS, - action=ListDetectorsJson, - nargs=0, - default=False) - - parser.add_argument('--legacy-ast', - help=argparse.SUPPRESS, - action='store_true', - default=defaults_flag_in_config['legacy_ast']) - - parser.add_argument('--ignore-return-value', - help=argparse.SUPPRESS, - action='store_true', - default=defaults_flag_in_config['ignore_return_value']) + parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False) + + parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False) + + group_misc.add_argument( + "--checklist", help=argparse.SUPPRESS, action="store_true", default=False + ) + + parser.add_argument( + "--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False + ) + + parser.add_argument( + "--list-detectors-json", + help=argparse.SUPPRESS, + action=ListDetectorsJson, + nargs=0, + default=False, + ) + + parser.add_argument( + "--legacy-ast", + help=argparse.SUPPRESS, + action="store_true", + default=defaults_flag_in_config["legacy_ast"], + ) + + parser.add_argument( + "--ignore-return-value", + help=argparse.SUPPRESS, + action="store_true", + default=defaults_flag_in_config["ignore_return_value"], + ) # if the json is splitted in different files - parser.add_argument('--splitted', - help=argparse.SUPPRESS, - action='store_true', - default=False) + parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False) # Disable the throw/catch on partial analyses - parser.add_argument('--disallow-partial', - help=argparse.SUPPRESS, - action="store_true", - default=False) + parser.add_argument( + "--disallow-partial", help=argparse.SUPPRESS, action="store_true", default=False + ) if len(sys.argv) == 1: parser.print_help(sys.stderr) @@ -441,10 +488,10 @@ def parse_args(detector_classes, printer_classes): args.filter_paths = parse_filter_paths(args) # Verify our json-type output is valid - args.json_types = set(args.json_types.split(',')) + args.json_types = set(args.json_types.split(",")) for json_type in args.json_types: if json_type not in JSON_OUTPUT_TYPES: - raise Exception(f"Error: \"{json_type}\" is not a valid JSON result output type.") + raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.') return args @@ -496,11 +543,11 @@ class OutputWiki(argparse.Action): class FormatterCryticCompile(logging.Formatter): def format(self, record): # for i, msg in enumerate(record.msg): - if record.msg.startswith('Compilation warnings/errors on '): + if record.msg.startswith("Compilation warnings/errors on "): txt = record.args[1] - txt = txt.split('\n') - txt = [red(x) if 'Error' in x else x for x in txt] - txt = '\n'.join(txt) + txt = txt.split("\n") + txt = [red(x) if "Error" in x else x for x in txt] + txt = "\n".join(txt) record.args = (record.args[0], txt) return super().format(record) @@ -538,10 +585,12 @@ def main_impl(all_detector_classes, all_printer_classes): json_results = {} output_error = None outputting_json = args.json is not None - outputting_json_stdout = args.json == '-' + outputting_json_stdout = args.json == "-" outputting_zip = args.zip is not None if args.zip_type not in ZIP_TYPES_ACCEPTED.keys(): - logger.error(f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}') + logger.error( + f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}' + ) # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout # output. @@ -553,19 +602,20 @@ def main_impl(all_detector_classes, all_printer_classes): default_log = logging.INFO if not args.debug else logging.DEBUG - for (l_name, l_level) in [('Slither', default_log), - ('Contract', default_log), - ('Function', default_log), - ('Node', default_log), - ('Parsing', default_log), - ('Detectors', default_log), - ('FunctionSolc', default_log), - ('ExpressionParsing', default_log), - ('TypeParsing', default_log), - ('SSA_Conversion', default_log), - ('Printers', default_log), - # ('CryticCompile', default_log) - ]: + for (l_name, l_level) in [ + ("Slither", default_log), + ("Contract", default_log), + ("Function", default_log), + ("Node", default_log), + ("Parsing", default_log), + ("Detectors", default_log), + ("FunctionSolc", default_log), + ("ExpressionParsing", default_log), + ("TypeParsing", default_log), + ("SSA_Conversion", default_log), + ("Printers", default_log), + # ('CryticCompile', default_log) + ]: l = logging.getLogger(l_name) l.setLevel(l_level) @@ -574,7 +624,7 @@ def main_impl(all_detector_classes, all_printer_classes): console_handler.setFormatter(FormatterCryticCompile()) - crytic_compile_error = logging.getLogger(('CryticCompile')) + crytic_compile_error = logging.getLogger(("CryticCompile")) crytic_compile_error.addHandler(console_handler) crytic_compile_error.propagate = False crytic_compile_error.setLevel(logging.INFO) @@ -585,7 +635,7 @@ def main_impl(all_detector_classes, all_printer_classes): filename = args.filename # Determine if we are handling ast from solc - if args.solc_ast or (filename.endswith('.json') and not is_supported(filename)): + if args.solc_ast or (filename.endswith(".json") and not is_supported(filename)): globbed_filenames = glob.glob(filename, recursive=True) filenames = glob.glob(os.path.join(filename, "*.json")) if not filenames: @@ -594,15 +644,21 @@ def main_impl(all_detector_classes, all_printer_classes): slither_instances = [] if args.splitted: - (slither_instance, results_detectors, results_printers, number_contracts) = process_from_asts(filenames, - args, - detector_classes, - printer_classes) + ( + slither_instance, + results_detectors, + results_printers, + number_contracts, + ) = process_from_asts(filenames, args, detector_classes, printer_classes) slither_instances.append(slither_instance) else: for filename in filenames: - (slither_instance, results_detectors_tmp, results_printers_tmp, - number_contracts_tmp) = process_single(filename, args, detector_classes, printer_classes) + ( + slither_instance, + results_detectors_tmp, + results_printers_tmp, + number_contracts_tmp, + ) = process_single(filename, args, detector_classes, printer_classes) number_contracts += number_contracts_tmp results_detectors += results_detectors_tmp results_printers += results_printers_tmp @@ -610,36 +666,41 @@ def main_impl(all_detector_classes, all_printer_classes): # Rely on CryticCompile to discern the underlying type of compilations. else: - (slither_instances, results_detectors, results_printers, number_contracts) = process_all(filename, args, - detector_classes, - printer_classes) + ( + slither_instances, + results_detectors, + results_printers, + number_contracts, + ) = process_all(filename, args, detector_classes, printer_classes) # Determine if we are outputting JSON if outputting_json or outputting_zip: # Add our compilation information to JSON - if 'compilations' in args.json_types: + if "compilations" in args.json_types: compilation_results = [] for slither_instance in slither_instances: - compilation_results.append(generate_standard_export(slither_instance.crytic_compile)) - json_results['compilations'] = compilation_results + compilation_results.append( + generate_standard_export(slither_instance.crytic_compile) + ) + json_results["compilations"] = compilation_results # Add our detector results to JSON if desired. - if results_detectors and 'detectors' in args.json_types: - json_results['detectors'] = results_detectors + if results_detectors and "detectors" in args.json_types: + json_results["detectors"] = results_detectors # Add our printer results to JSON if desired. - if results_printers and 'printers' in args.json_types: - json_results['printers'] = results_printers + if results_printers and "printers" in args.json_types: + json_results["printers"] = results_printers # Add our detector types to JSON - if 'list-detectors' in args.json_types: + if "list-detectors" in args.json_types: detectors, _ = get_detectors_and_printers() - json_results['list-detectors'] = output_detectors_json(detectors) + json_results["list-detectors"] = output_detectors_json(detectors) # Add our detector types to JSON - if 'list-printers' in args.json_types: + if "list-printers" in args.json_types: _, printers = get_detectors_and_printers() - json_results['list-printers'] = output_printers_json(printers) + json_results["list-printers"] = output_printers_json(printers) # Output our results to markdown if we wish to compile a checklist. if args.checklist: @@ -647,37 +708,45 @@ def main_impl(all_detector_classes, all_printer_classes): # Dont print the number of result for printers if number_contracts == 0: - logger.warning(red('No contract was analyzed')) + logger.warning(red("No contract was analyzed")) if printer_classes: - logger.info('%s analyzed (%d contracts)', filename, number_contracts) + logger.info("%s analyzed (%d contracts)", filename, number_contracts) else: - logger.info('%s analyzed (%d contracts with %d detectors), %d result(s) found', filename, - number_contracts, len(detector_classes), len(results_detectors)) - - logger.info(blue('Use https://crytic.io/ to get access to additional detectors and Github integration')) + logger.info( + "%s analyzed (%d contracts with %d detectors), %d result(s) found", + filename, + number_contracts, + len(detector_classes), + len(results_detectors), + ) + + logger.info( + blue( + "Use https://crytic.io/ to get access to additional detectors and Github integration" + ) + ) if args.ignore_return_value: return except SlitherException as se: output_error = str(se) traceback.print_exc() - logging.error(red('Error:')) + logging.error(red("Error:")) logging.error(red(output_error)) - logging.error('Please report an issue to https://github.com/crytic/slither/issues') + logging.error("Please report an issue to https://github.com/crytic/slither/issues") except Exception: output_error = traceback.format_exc() logging.error(traceback.print_exc()) - logging.error('Error in %s' % args.filename) + logging.error("Error in %s" % args.filename) logging.error(output_error) - # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. if outputting_json: - if 'console' in args.json_types: - json_results['console'] = { - 'stdout': StandardOutputCapture.get_stdout_output(), - 'stderr': StandardOutputCapture.get_stderr_output() + if "console" in args.json_types: + json_results["console"] = { + "stdout": StandardOutputCapture.get_stdout_output(), + "stderr": StandardOutputCapture.get_stderr_output(), } StandardOutputCapture.disable() output_to_json(None if outputting_json_stdout else args.json, output_error, json_results) @@ -692,7 +761,7 @@ def main_impl(all_detector_classes, all_printer_classes): exit(results_detectors) -if __name__ == '__main__': +if __name__ == "__main__": main() # endregion diff --git a/slither/all_exceptions.py b/slither/all_exceptions.py index eacb8b56f..560078f26 100644 --- a/slither/all_exceptions.py +++ b/slither/all_exceptions.py @@ -4,4 +4,4 @@ This module import all slither exceptions from slither.slithir.exceptions import SlithIRError from slither.solc_parsing.exceptions import ParsingError, VariableNotFound from slither.core.exceptions import SlitherCoreError -from slither.exceptions import SlitherException \ No newline at end of file +from slither.exceptions import SlitherException diff --git a/slither/analyses/data_dependency/data_dependency.py b/slither/analyses/data_dependency/data_dependency.py index 06501dd4f..57b6565fc 100644 --- a/slither/analyses/data_dependency/data_dependency.py +++ b/slither/analyses/data_dependency/data_dependency.py @@ -3,15 +3,26 @@ """ from typing import Union, Set, Dict -from slither.core.declarations import (Contract, Enum, Function, - SolidityFunction, SolidityVariable, - SolidityVariableComposed, Structure) +from slither.core.declarations import ( + Contract, + Enum, + Function, + SolidityFunction, + SolidityVariable, + SolidityVariableComposed, + Structure, +) from slither.core.variables.variable import Variable from slither.slithir.operations import Index, OperationWithLValue, InternalCall -from slither.slithir.variables import (Constant, LocalIRVariable, - ReferenceVariable, ReferenceVariableSSA, - StateIRVariable, - TemporaryVariableSSA, TupleVariableSSA) +from slither.slithir.variables import ( + Constant, + LocalIRVariable, + ReferenceVariable, + ReferenceVariableSSA, + StateIRVariable, + TemporaryVariableSSA, + TupleVariableSSA, +) from slither.core.solidity_types.type import Type @@ -21,8 +32,9 @@ from slither.core.solidity_types.type import Type ################################################################################### ################################################################################### + def is_dependent(variable, source, context, only_unprotected=False): - ''' + """ Args: variable (Variable) source (Variable) @@ -30,7 +42,7 @@ def is_dependent(variable, source, context, only_unprotected=False): only_unprotected (bool): True only unprotected function are considered Returns: bool - ''' + """ assert isinstance(context, (Contract, Function)) if isinstance(variable, Constant): return False @@ -39,12 +51,15 @@ def is_dependent(variable, source, context, only_unprotected=False): context = context.context if only_unprotected: - return variable in context[KEY_NON_SSA_UNPROTECTED] and source in context[KEY_NON_SSA_UNPROTECTED][variable] + return ( + variable in context[KEY_NON_SSA_UNPROTECTED] + and source in context[KEY_NON_SSA_UNPROTECTED][variable] + ) return variable in context[KEY_NON_SSA] and source in context[KEY_NON_SSA][variable] def is_dependent_ssa(variable, source, context, only_unprotected=False): - ''' + """ Args: variable (Variable) taint (Variable) @@ -52,7 +67,7 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False): only_unprotected (bool): True only unprotected function are considered Returns: bool - ''' + """ assert isinstance(context, (Contract, Function)) context = context.context if isinstance(variable, Constant): @@ -60,25 +75,30 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False): if variable == source: return True if only_unprotected: - return variable in context[KEY_SSA_UNPROTECTED] and source in context[KEY_SSA_UNPROTECTED][variable] + return ( + variable in context[KEY_SSA_UNPROTECTED] + and source in context[KEY_SSA_UNPROTECTED][variable] + ) return variable in context[KEY_SSA] and source in context[KEY_SSA][variable] -GENERIC_TAINT = {SolidityVariableComposed('msg.sender'), - SolidityVariableComposed('msg.value'), - SolidityVariableComposed('msg.data'), - SolidityVariableComposed('tx.origin')} +GENERIC_TAINT = { + SolidityVariableComposed("msg.sender"), + SolidityVariableComposed("msg.value"), + SolidityVariableComposed("msg.data"), + SolidityVariableComposed("tx.origin"), +} def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=False): - ''' + """ Args: variable context (Contract|Function) only_unprotected (bool): True only unprotected function are considered Returns: bool - ''' + """ assert isinstance(context, (Contract, Function)) assert isinstance(only_unprotected, bool) if isinstance(variable, Constant): @@ -87,18 +107,20 @@ def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=F taints = slither.context[KEY_INPUT] if not ignore_generic_taint: taints |= GENERIC_TAINT - return variable in taints or any(is_dependent(variable, t, context, only_unprotected) for t in taints) + return variable in taints or any( + is_dependent(variable, t, context, only_unprotected) for t in taints + ) def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_taint=False): - ''' + """ Args: variable context (Contract|Function) only_unprotected (bool): True only unprotected function are considered Returns: bool - ''' + """ assert isinstance(context, (Contract, Function)) assert isinstance(only_unprotected, bool) if isinstance(variable, Constant): @@ -107,11 +129,14 @@ def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_tai taints = slither.context[KEY_INPUT_SSA] if not ignore_generic_taint: taints |= GENERIC_TAINT - return variable in taints or any(is_dependent_ssa(variable, t, context, only_unprotected) for t in taints) + return variable in taints or any( + is_dependent_ssa(variable, t, context, only_unprotected) for t in taints + ) def get_dependencies( - variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False) -> Set[Variable]: + variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False +) -> Set[Variable]: """ Return the variables for which `variable` depends on. @@ -128,7 +153,8 @@ def get_dependencies( def get_all_dependencies( - context: Union[Contract, Function], only_unprotected: bool = False) -> Dict[Variable, Set[Variable]]: + context: Union[Contract, Function], only_unprotected: bool = False +) -> Dict[Variable, Set[Variable]]: """ Return the dictionary of dependencies. @@ -144,7 +170,8 @@ def get_all_dependencies( def get_dependencies_ssa( - variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False) -> Set[Variable]: + variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False +) -> Set[Variable]: """ Return the variables for which `variable` depends on (SSA version). @@ -161,7 +188,8 @@ def get_dependencies_ssa( def get_all_dependencies_ssa( - context: Union[Contract, Function], only_unprotected: bool = False) -> Dict[Variable, Set[Variable]]: + context: Union[Contract, Function], only_unprotected: bool = False +) -> Dict[Variable, Set[Variable]]: """ Return the dictionary of dependencies. @@ -201,19 +229,20 @@ KEY_INPUT_SSA = "DATA_DEPENDENCY_INPUT_SSA" ################################################################################### ################################################################################### + def pprint_dependency(context): - print('#### SSA ####') + print("#### SSA ####") context = context.context for k, values in context[KEY_SSA].items(): - print('{} ({}):'.format(k, id(k))) + print("{} ({}):".format(k, id(k))) for v in values: - print('\t- {}'.format(v)) + print("\t- {}".format(v)) - print('#### NON SSA ####') + print("#### NON SSA ####") for k, values in context[KEY_NON_SSA].items(): - print('{} ({}):'.format(k, hex(id(k)))) + print("{} ({}):".format(k, hex(id(k)))) for v in values: - print('\t- {} ({})'.format(v, hex(id(v)))) + print("\t- {} ({})".format(v, hex(id(v)))) # endregion @@ -223,6 +252,7 @@ def pprint_dependency(context): ################################################################################### ################################################################################### + def compute_dependency(slither): slither.context[KEY_INPUT] = set() slither.context[KEY_INPUT_SSA] = set() @@ -242,12 +272,9 @@ def compute_dependency_contract(contract, slither): compute_dependency_function(function) propagate_function(contract, function, KEY_SSA, KEY_NON_SSA) - propagate_function(contract, - function, - KEY_SSA_UNPROTECTED, - KEY_NON_SSA_UNPROTECTED) + propagate_function(contract, function, KEY_SSA_UNPROTECTED, KEY_NON_SSA_UNPROTECTED) - if function.visibility in ['public', 'external']: + if function.visibility in ["public", "external"]: [slither.context[KEY_INPUT].add(p) for p in function.parameters] [slither.context[KEY_INPUT_SSA].add(p) for p in function.parameters_ssa] @@ -272,7 +299,9 @@ def transitive_close_dependencies(context, context_key, context_key_non_ssa): while changed: changed = False # Need to create new set() as its changed during iteration - data_depencencies = {k: set([v for v in values]) for k, values in context.context[context_key].items()} + data_depencencies = { + k: set([v for v in values]) for k, values in context.context[context_key].items() + } for key, items in data_depencencies.items(): for item in items: if item in data_depencencies: @@ -301,7 +330,11 @@ def add_dependency(lvalue, function, ir, is_protected): read = ir.read [function.context[KEY_SSA][lvalue].add(v) for v in read if not isinstance(v, Constant)] if not is_protected: - [function.context[KEY_SSA_UNPROTECTED][lvalue].add(v) for v in read if not isinstance(v, Constant)] + [ + function.context[KEY_SSA_UNPROTECTED][lvalue].add(v) + for v in read + if not isinstance(v, Constant) + ] def compute_dependency_function(function): @@ -324,13 +357,26 @@ def compute_dependency_function(function): add_dependency(ir.lvalue, function, ir, is_protected) function.context[KEY_NON_SSA] = convert_to_non_ssa(function.context[KEY_SSA]) - function.context[KEY_NON_SSA_UNPROTECTED] = convert_to_non_ssa(function.context[KEY_SSA_UNPROTECTED]) + function.context[KEY_NON_SSA_UNPROTECTED] = convert_to_non_ssa( + function.context[KEY_SSA_UNPROTECTED] + ) def convert_variable_to_non_ssa(v): - if isinstance(v, (LocalIRVariable, StateIRVariable, TemporaryVariableSSA, ReferenceVariableSSA, TupleVariableSSA)): + if isinstance( + v, + ( + LocalIRVariable, + StateIRVariable, + TemporaryVariableSSA, + ReferenceVariableSSA, + TupleVariableSSA, + ), + ): return v.non_ssa_version - assert isinstance(v, (Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type)) + assert isinstance( + v, (Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type) + ) return v @@ -341,7 +387,6 @@ def convert_to_non_ssa(data_depencies): var = convert_variable_to_non_ssa(k) if not var in ret: ret[var] = set() - ret[var] = ret[var].union(set([convert_variable_to_non_ssa(v) for v in - values])) + ret[var] = ret[var].union(set([convert_variable_to_non_ssa(v) for v in values])) return ret diff --git a/slither/analyses/evm/convert.py b/slither/analyses/evm/convert.py index a86a4aaa5..827f4da29 100644 --- a/slither/analyses/evm/convert.py +++ b/slither/analyses/evm/convert.py @@ -1,11 +1,11 @@ import logging -from slither.core.declarations import (Contract, Function) +from slither.core.declarations import Contract, Function from slither.core.cfg.node import Node from slither.utils.function import get_function_id from slither.exceptions import SlitherError from .evm_cfg_builder import load_evm_cfg_builder -logger = logging.getLogger('ConvertToEVM') +logger = logging.getLogger("ConvertToEVM") KEY_EVM_INS = "EVM_INSTRUCTIONS" @@ -20,30 +20,36 @@ def get_evm_instructions(obj): slither = obj.slither if not slither.crytic_compile: - raise SlitherError('EVM features require to compile with crytic-compile') + raise SlitherError("EVM features require to compile with crytic-compile") contract_info = {} function_info = {} node_info = {} if isinstance(obj, Node): - contract_info['contract'] = obj.function.contract + contract_info["contract"] = obj.function.contract elif isinstance(obj, Function): - contract_info['contract'] = obj.contract + contract_info["contract"] = obj.contract else: - contract_info['contract'] = obj + contract_info["contract"] = obj # Get contract runtime bytecode, srcmap and cfg - contract_info['bytecode_runtime'] = slither.crytic_compile.bytecode_runtime( - contract_info['contract'].name) - contract_info['srcmap_runtime'] = slither.crytic_compile.srcmap_runtime( - contract_info['contract'].name) - contract_info['cfg'] = CFG(contract_info['bytecode_runtime']) + contract_info["bytecode_runtime"] = slither.crytic_compile.bytecode_runtime( + contract_info["contract"].name + ) + contract_info["srcmap_runtime"] = slither.crytic_compile.srcmap_runtime( + contract_info["contract"].name + ) + contract_info["cfg"] = CFG(contract_info["bytecode_runtime"]) # Get contract init bytecode, srcmap and cfg - contract_info['bytecode_init'] = slither.crytic_compile.bytecode_init(contract_info['contract'].name) - contract_info['srcmap_init'] = slither.crytic_compile.srcmap_init(contract_info['contract'].name) - contract_info['cfg_init'] = CFG(contract_info['bytecode_init']) + contract_info["bytecode_init"] = slither.crytic_compile.bytecode_init( + contract_info["contract"].name + ) + contract_info["srcmap_init"] = slither.crytic_compile.srcmap_init( + contract_info["contract"].name + ) + contract_info["cfg_init"] = CFG(contract_info["bytecode_init"]) # Get evm instructions if isinstance(obj, Contract): @@ -52,26 +58,26 @@ def get_evm_instructions(obj): elif isinstance(obj, Function): # Get evm instructions for function - function_info['function'] = obj - function_info['contract_info'] = contract_info + function_info["function"] = obj + function_info["contract_info"] = contract_info obj.context[KEY_EVM_INS] = _get_evm_instructions_function(function_info) else: # Get evm instructions for node - node_info['node'] = obj + node_info["node"] = obj # CFG and srcmap depend on function being constructor or not - if node_info['node'].function.is_constructor: - cfg = contract_info['cfg_init'] - srcmap = contract_info['srcmap_init'] + if node_info["node"].function.is_constructor: + cfg = contract_info["cfg_init"] + srcmap = contract_info["srcmap_init"] else: - cfg = contract_info['cfg'] - srcmap = contract_info['srcmap_runtime'] + cfg = contract_info["cfg"] + srcmap = contract_info["srcmap_runtime"] - node_info['cfg'] = cfg - node_info['srcmap'] = srcmap - node_info['contract'] = contract_info['contract'] - node_info['slither'] = slither + node_info["cfg"] = cfg + node_info["srcmap"] = srcmap + node_info["contract"] = contract_info["contract"] + node_info["slither"] = slither obj.context[KEY_EVM_INS] = _get_evm_instructions_node(node_info) @@ -80,15 +86,15 @@ def get_evm_instructions(obj): def _get_evm_instructions_contract(contract_info): # Combine the instructions of constructor and the rest of the contract - return contract_info['cfg_init'].instructions + contract_info['cfg'].instructions + return contract_info["cfg_init"].instructions + contract_info["cfg"].instructions def _get_evm_instructions_function(function_info): - function = function_info['function'] + function = function_info["function"] # CFG depends on function being constructor or not if function.is_constructor: - cfg = function_info['contract_info']['cfg_init'] + cfg = function_info["contract_info"]["cfg_init"] # _dispatcher is the only function recognised by evm-cfg-builder in bytecode_init. # _dispatcher serves the role of the constructor in init code, # given that there are no other functions. @@ -97,7 +103,7 @@ def _get_evm_instructions_function(function_info): name = "_dispatcher" hash = "" else: - cfg = function_info['contract_info']['cfg'] + cfg = function_info["contract_info"]["cfg"] name = function.name # Get first four bytes of function singature's keccak-256 hash used as function selector hash = str(hex(get_function_id(function.full_name))) @@ -117,20 +123,26 @@ def _get_evm_instructions_function(function_info): def _get_evm_instructions_node(node_info): # Get evm instructions for node's contract - contract_pcs = generate_source_to_evm_ins_mapping(node_info['cfg'].instructions, - node_info['srcmap'], - node_info['slither'], - node_info['contract'].source_mapping['filename_absolute']) - contract_file = node_info['slither'].source_code[node_info['contract'].source_mapping['filename_absolute']].encode( - 'utf-8') + contract_pcs = generate_source_to_evm_ins_mapping( + node_info["cfg"].instructions, + node_info["srcmap"], + node_info["slither"], + node_info["contract"].source_mapping["filename_absolute"], + ) + contract_file = ( + node_info["slither"] + .source_code[node_info["contract"].source_mapping["filename_absolute"]] + .encode("utf-8") + ) # Get evm instructions corresponding to node's source line number - node_source_line = contract_file[0:node_info['node'].source_mapping['start']].count("\n".encode("utf-8")) \ - + 1 + node_source_line = ( + contract_file[0 : node_info["node"].source_mapping["start"]].count("\n".encode("utf-8")) + 1 + ) node_pcs = contract_pcs.get(node_source_line, []) node_ins = [] for pc in node_pcs: - node_ins.append(node_info['cfg'].get_instruction_at(pc)) + node_ins.append(node_info["cfg"].get_instruction_at(pc)) return node_ins @@ -141,7 +153,7 @@ def _get_function_evm(cfg, function_name, function_hash): if function_evm.name[:2] == "0x" and function_evm.name == function_hash: return function_evm # Match function name - elif function_evm.name[:2] != "0x" and function_evm.name.split('(')[0] == function_name: + elif function_evm.name[:2] != "0x" and function_evm.name.split("(")[0] == function_name: return function_evm return None @@ -155,7 +167,7 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither """ source_to_evm_mapping = {} - file_source = slither.source_code[filename].encode('utf-8') + file_source = slither.source_code[filename].encode("utf-8") prev_mapping = [] for idx, mapping in enumerate(srcmap_runtime): @@ -165,17 +177,17 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither # If a field is empty, the value of the preceding element is used. # If a : is missing, all following fields are considered empty. - mapping_item = mapping.split(':') - mapping_item += prev_mapping[len(mapping_item):] + mapping_item = mapping.split(":") + mapping_item += prev_mapping[len(mapping_item) :] for i in range(len(mapping_item)): - if mapping_item[i] == '': + if mapping_item[i] == "": mapping_item[i] = int(prev_mapping[i]) offset, length, file_id, _ = mapping_item prev_mapping = mapping_item - if file_id == '-1': + if file_id == "-1": # Internal compiler-generated code snippets to be ignored # See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635 continue diff --git a/slither/analyses/evm/evm_cfg_builder.py b/slither/analyses/evm/evm_cfg_builder.py index 5fc93d3f6..d299a055f 100644 --- a/slither/analyses/evm/evm_cfg_builder.py +++ b/slither/analyses/evm/evm_cfg_builder.py @@ -1,12 +1,14 @@ import logging from slither.exceptions import SlitherError -logger = logging.getLogger('ConvertToEVM') +logger = logging.getLogger("ConvertToEVM") + def load_evm_cfg_builder(): try: # Avoiding the addition of evm_cfg_builder as permanent dependency from evm_cfg_builder.cfg import CFG + return CFG except ImportError: logger.error("To use evm features, you need to install evm-cfg-builder") diff --git a/slither/analyses/write/are_variables_written.py b/slither/analyses/write/are_variables_written.py index 2bed1d649..a0c591711 100644 --- a/slither/analyses/write/are_variables_written.py +++ b/slither/analyses/write/are_variables_written.py @@ -7,13 +7,18 @@ from typing import Dict, Tuple, Set, List, Optional from slither.core.cfg.node import NodeType, Node from slither.core.declarations import SolidityFunction from slither.core.variables.variable import Variable -from slither.slithir.operations import (Index, Member, OperationWithLValue, - SolidityCall, Length, Balance) +from slither.slithir.operations import ( + Index, + Member, + OperationWithLValue, + SolidityCall, + Length, + Balance, +) from slither.slithir.variables import ReferenceVariable, TemporaryVariable class State: - def __init__(self): # Map node -> list of variables set # Were each variables set represents a configuration of a path @@ -28,7 +33,9 @@ class State: self.nodes: Dict[Node, List[Set[Variable]]] = defaultdict(list) -def _visit(node: Node, state: State, variables_written: Set[Variable], variables_to_write: List[Variable]): +def _visit( + node: Node, state: State, variables_written: Set[Variable], variables_to_write: List[Variable] +): """ Explore all the nodes to look for values not written when the node's function return Fixpoint reaches if no new written variables are found @@ -44,8 +51,7 @@ def _visit(node: Node, state: State, variables_written: Set[Variable], variables for ir in node.irs: if isinstance(ir, SolidityCall): # TODO convert the revert to a THROW node - if ir.function in [SolidityFunction('revert(string)'), - SolidityFunction('revert()')]: + if ir.function in [SolidityFunction("revert(string)"), SolidityFunction("revert()")]: return [] if not isinstance(ir, OperationWithLValue): @@ -62,7 +68,9 @@ def _visit(node: Node, state: State, variables_written: Set[Variable], variables while isinstance(lvalue, ReferenceVariable): if lvalue not in refs: break - if refs[lvalue] and not isinstance(refs[lvalue], (TemporaryVariable, ReferenceVariable)): + if refs[lvalue] and not isinstance( + refs[lvalue], (TemporaryVariable, ReferenceVariable) + ): variables_written.add(refs[lvalue]) lvalue = refs[lvalue] diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index f8dbe87ce..a72a1b0dd 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -168,7 +168,7 @@ class Node(SourceMapping, ChildFunction): # key are variable name self._phi_origins_state_variables: Dict[str, Tuple[StateVariable, Set["Node"]]] = {} self._phi_origins_local_variables: Dict[str, Tuple[LocalVariable, Set["Node"]]] = {} - #self._phi_origins_member_variables: Dict[str, Tuple[MemberVariable, Set["Node"]]] = {} + # self._phi_origins_member_variables: Dict[str, Tuple[MemberVariable, Set["Node"]]] = {} self._expression: Optional[Expression] = None self._variable_declaration: Optional[LocalVariable] = None @@ -983,11 +983,11 @@ class Node(SourceMapping, ChildFunction): ################################################################################### def __str__(self): - additional_info = '' + additional_info = "" if self.expression: - additional_info += ' ' + str(self.expression) + additional_info += " " + str(self.expression) elif self.variable_declaration: - additional_info += ' ' + str(self.variable_declaration) + additional_info += " " + str(self.variable_declaration) txt = str(self._node_type) + additional_info return txt diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 7e6873fd0..7fe309bba 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -231,7 +231,12 @@ class Function(ChildContract, ChildInheritance, SourceMapping): Return the function signature without the return values """ name, parameters, _ = self.signature - return ".".join([self.contract_declarer.name] + self._scope + [name]) + "(" + ",".join(parameters) + ")" + return ( + ".".join([self.contract_declarer.name] + self._scope + [name]) + + "(" + + ",".join(parameters) + + ")" + ) @property def contains_assembly(self) -> bool: @@ -1615,8 +1620,9 @@ class Function(ChildContract, ChildInheritance, SourceMapping): return ir.rvalues[0] == ir.lvalue def fix_phi(self, last_state_variables_instances, initial_state_variables_instances): - from slither.slithir.operations import (InternalCall, PhiCallback) - from slither.slithir.variables import (Constant, StateIRVariable) + from slither.slithir.operations import InternalCall, PhiCallback + from slither.slithir.variables import Constant, StateIRVariable + for node in self.nodes: for ir in node.irs_ssa: if node == self.entry_point: diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 7a645f5d3..db64ae801 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -66,7 +66,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { # abi.decode returns an a list arbitrary types "abi.decode()": [], "type(address)": [], - "type()": [], # 0.6.8 changed type(address) to type() + "type()": [], # 0.6.8 changed type(address) to type() } diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index eb3fa8d72..edf2ae9ab 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -62,7 +62,6 @@ class SlitherCore(Context): # If set to true, slither will not catch errors during parsing self._disallow_partial: bool = False - ################################################################################### ################################################################################### # region Source code @@ -219,7 +218,6 @@ class SlitherCore(Context): top_level_structures = [c.structures for c in self.contracts if c.is_top_level] return [st for sublist in top_level_structures for st in sublist] - @property def top_level_enums(self) -> List[Enum]: top_level_enums = [c.enums for c in self.contracts if c.is_top_level] @@ -369,7 +367,6 @@ class SlitherCore(Context): def contracts_with_missing_inheritance(self) -> Set: return self._contract_with_missing_inheritance - @property def disallow_partial(self) -> bool: """ @@ -415,4 +412,5 @@ class SlitherCore(Context): def storage_layout_of(self, contract, var) -> Tuple[int, int]: return self._storage_layouts[contract.name][var.canonical_name] + # endregion diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index 6f1d7a8b5..3fc53dc29 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -174,7 +174,7 @@ class ElementaryType(Type): @property def storage_size(self) -> Tuple[int, bool]: - if self._type == 'string' or self._type == 'bytes': + if self._type == "string" or self._type == "bytes": return 32, True return int(self.size / 8), False diff --git a/slither/core/solidity_types/type.py b/slither/core/solidity_types/type.py index 8042fcc7b..8405578a9 100644 --- a/slither/core/solidity_types/type.py +++ b/slither/core/solidity_types/type.py @@ -3,7 +3,8 @@ from typing import Tuple from slither.core.source_mapping.source_mapping import SourceMapping -class Type(SourceMapping,metaclass=abc.ABCMeta): + +class Type(SourceMapping, metaclass=abc.ABCMeta): @property @abc.abstractmethod def storage_size(self) -> Tuple[int, bool]: diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 9b3138c39..b3008d43c 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -24,30 +24,30 @@ classification_colors = { DetectorClassification.OPTIMIZATION: green, DetectorClassification.LOW: green, DetectorClassification.MEDIUM: yellow, - DetectorClassification.HIGH: red + DetectorClassification.HIGH: red, } classification_txt = { - DetectorClassification.INFORMATIONAL: 'Informational', - DetectorClassification.OPTIMIZATION: 'Optimization', - DetectorClassification.LOW: 'Low', - DetectorClassification.MEDIUM: 'Medium', - DetectorClassification.HIGH: 'High', + DetectorClassification.INFORMATIONAL: "Informational", + DetectorClassification.OPTIMIZATION: "Optimization", + DetectorClassification.LOW: "Low", + DetectorClassification.MEDIUM: "Medium", + DetectorClassification.HIGH: "High", } class AbstractDetector(metaclass=abc.ABCMeta): - ARGUMENT = '' # run the detector with slither.py --ARGUMENT - HELP = '' # help information + ARGUMENT = "" # run the detector with slither.py --ARGUMENT + HELP = "" # help information IMPACT = None CONFIDENCE = None - WIKI = '' + WIKI = "" - WIKI_TITLE = '' - WIKI_DESCRIPTION = '' - WIKI_EXPLOIT_SCENARIO = '' - WIKI_RECOMMENDATION = '' + WIKI_TITLE = "" + WIKI_DESCRIPTION = "" + WIKI_EXPLOIT_SCENARIO = "" + WIKI_RECOMMENDATION = "" STANDARD_JSON = True @@ -58,43 +58,69 @@ class AbstractDetector(metaclass=abc.ABCMeta): self.logger = logger if not self.HELP: - raise IncorrectDetectorInitialization('HELP is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectDetectorInitialization( + "HELP is not initialized {}".format(self.__class__.__name__) + ) if not self.ARGUMENT: - raise IncorrectDetectorInitialization('ARGUMENT is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectDetectorInitialization( + "ARGUMENT is not initialized {}".format(self.__class__.__name__) + ) if not self.WIKI: - raise IncorrectDetectorInitialization('WIKI is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectDetectorInitialization( + "WIKI is not initialized {}".format(self.__class__.__name__) + ) if not self.WIKI_TITLE: - raise IncorrectDetectorInitialization('WIKI_TITLE is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectDetectorInitialization( + "WIKI_TITLE is not initialized {}".format(self.__class__.__name__) + ) if not self.WIKI_DESCRIPTION: - raise IncorrectDetectorInitialization('WIKI_DESCRIPTION is not initialized {}'.format(self.__class__.__name__)) - - if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [DetectorClassification.INFORMATIONAL, - DetectorClassification.OPTIMIZATION]: - raise IncorrectDetectorInitialization('WIKI_EXPLOIT_SCENARIO is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectDetectorInitialization( + "WIKI_DESCRIPTION is not initialized {}".format(self.__class__.__name__) + ) + + if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [ + DetectorClassification.INFORMATIONAL, + DetectorClassification.OPTIMIZATION, + ]: + raise IncorrectDetectorInitialization( + "WIKI_EXPLOIT_SCENARIO is not initialized {}".format(self.__class__.__name__) + ) if not self.WIKI_RECOMMENDATION: - raise IncorrectDetectorInitialization('WIKI_RECOMMENDATION is not initialized {}'.format(self.__class__.__name__)) - - if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None: - raise IncorrectDetectorInitialization('ARGUMENT has illegal character {}'.format(self.__class__.__name__)) - - if self.IMPACT not in [DetectorClassification.LOW, - DetectorClassification.MEDIUM, - DetectorClassification.HIGH, - DetectorClassification.INFORMATIONAL, - DetectorClassification.OPTIMIZATION]: - raise IncorrectDetectorInitialization('IMPACT is not initialized {}'.format(self.__class__.__name__)) - - if self.CONFIDENCE not in [DetectorClassification.LOW, - DetectorClassification.MEDIUM, - DetectorClassification.HIGH, - DetectorClassification.INFORMATIONAL, - DetectorClassification.OPTIMIZATION]: - raise IncorrectDetectorInitialization('CONFIDENCE is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectDetectorInitialization( + "WIKI_RECOMMENDATION is not initialized {}".format(self.__class__.__name__) + ) + + if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None: + raise IncorrectDetectorInitialization( + "ARGUMENT has illegal character {}".format(self.__class__.__name__) + ) + + if self.IMPACT not in [ + DetectorClassification.LOW, + DetectorClassification.MEDIUM, + DetectorClassification.HIGH, + DetectorClassification.INFORMATIONAL, + DetectorClassification.OPTIMIZATION, + ]: + raise IncorrectDetectorInitialization( + "IMPACT is not initialized {}".format(self.__class__.__name__) + ) + + if self.CONFIDENCE not in [ + DetectorClassification.LOW, + DetectorClassification.MEDIUM, + DetectorClassification.HIGH, + DetectorClassification.INFORMATIONAL, + DetectorClassification.OPTIMIZATION, + ]: + raise IncorrectDetectorInitialization( + "CONFIDENCE is not initialized {}".format(self.__class__.__name__) + ) def _log(self, info): if self.logger: @@ -111,61 +137,76 @@ class AbstractDetector(metaclass=abc.ABCMeta): all_results = [r.data for r in all_results] results = [] # only keep valid result, and remove dupplicate - [results.append(r) for r in all_results if self.slither.valid_result(r) and r not in results] + [ + results.append(r) + for r in all_results + if self.slither.valid_result(r) and r not in results + ] if results: if self.logger: - info = '\n' + info = "\n" for idx, result in enumerate(results): if self.slither.triage_mode: - info += '{}: '.format(idx) - info += result['description'] - info += 'Reference: {}'.format(self.WIKI) + info += "{}: ".format(idx) + info += result["description"] + info += "Reference: {}".format(self.WIKI) self._log(info) if self.slither.generate_patches: for result in results: try: self._format(self.slither, result) - if not 'patches' in result: + if not "patches" in result: continue - result['patches_diff'] = dict() - for file in result['patches']: - original_txt = self.slither.source_code[file].encode('utf8') + result["patches_diff"] = dict() + for file in result["patches"]: + original_txt = self.slither.source_code[file].encode("utf8") patched_txt = original_txt offset = 0 - patches = result['patches'][file] - patches.sort(key=lambda x: x['start']) - if not all(patches[i]['end'] <= patches[i + 1]['end'] for i in range(len(patches) - 1)): - self._log(f'Impossible to generate patch; patches collisions: {patches}') + patches = result["patches"][file] + patches.sort(key=lambda x: x["start"]) + if not all( + patches[i]["end"] <= patches[i + 1]["end"] + for i in range(len(patches) - 1) + ): + self._log( + f"Impossible to generate patch; patches collisions: {patches}" + ) continue for patch in patches: patched_txt, offset = apply_patch(patched_txt, patch, offset) diff = create_diff(self.slither, original_txt, patched_txt, file) if not diff: - self._log(f'Impossible to generate patch; empty {result}') + self._log(f"Impossible to generate patch; empty {result}") else: - result['patches_diff'][file] = diff + result["patches_diff"][file] = diff except FormatImpossible as e: - self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{e}') + self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{e}') if results and self.slither.triage_mode: while True: - indexes = input('Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format(len(results))) - if indexes == 'All': + indexes = input( + 'Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format( + len(results) + ) + ) + if indexes == "All": self.slither.save_results_to_hide(results) return [] - if indexes == '': + if indexes == "": return results - if indexes.startswith('['): + if indexes.startswith("["): indexes = indexes[1:] - if indexes.endswith(']'): + if indexes.endswith("]"): indexes = indexes[:-1] try: - indexes = [int(i) for i in indexes.split(',')] - self.slither.save_results_to_hide([r for (idx, r) in enumerate(results) if idx in indexes]) + indexes = [int(i) for i in indexes.split(",")] + self.slither.save_results_to_hide( + [r for (idx, r) in enumerate(results) if idx in indexes] + ) return [r for (idx, r) in enumerate(results) if idx not in indexes] except ValueError: - self.logger.error(yellow('Malformed input. Example of valid input: 0,1,2,3')) + self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3")) return results @property @@ -173,18 +214,20 @@ class AbstractDetector(metaclass=abc.ABCMeta): return classification_colors[self.IMPACT] def generate_result(self, info, additional_fields=None): - output = Output(info, - additional_fields, - standard_format=self.STANDARD_JSON, - markdown_root=self.slither.markdown_root) + output = Output( + info, + additional_fields, + standard_format=self.STANDARD_JSON, + markdown_root=self.slither.markdown_root, + ) - output.data['check'] = self.ARGUMENT - output.data['impact'] = classification_txt[self.IMPACT] - output.data['confidence'] = classification_txt[self.CONFIDENCE] + output.data["check"] = self.ARGUMENT + output.data["impact"] = classification_txt[self.IMPACT] + output.data["confidence"] = classification_txt[self.CONFIDENCE] return output @staticmethod def _format(slither, result): """Implement format""" - return \ No newline at end of file + return diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 78147ebd4..e5088bd62 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -7,7 +7,8 @@ from .attributes.incorrect_solc import IncorrectSolc from .attributes.locked_ether import LockedEther from .functions.arbitrary_send import ArbitrarySend from .functions.suicidal import Suicidal -#from .functions.complex_function import ComplexFunction + +# from .functions.complex_function import ComplexFunction from .reentrancy.reentrancy_benign import ReentrancyBenign from .reentrancy.reentrancy_read_before_write import ReentrancyReadBeforeWritten from .reentrancy.reentrancy_eth import ReentrancyEth @@ -45,5 +46,6 @@ from .statements.boolean_constant_equality import BooleanEquality from .statements.boolean_constant_misuse import BooleanConstantMisuse from .statements.divide_before_multiply import DivideBeforeMultiply from .slither.name_reused import NameReused + # # diff --git a/slither/detectors/attributes/const_functions_asm.py b/slither/detectors/attributes/const_functions_asm.py index e76e97cb1..a72d3cba8 100644 --- a/slither/detectors/attributes/const_functions_asm.py +++ b/slither/detectors/attributes/const_functions_asm.py @@ -11,23 +11,23 @@ class ConstantFunctionsAsm(AbstractDetector): Constant function detector """ - ARGUMENT = 'constant-function-asm' # run the detector with slither.py --ARGUMENT - HELP = 'Constant functions using assembly code' # help information + ARGUMENT = "constant-function-asm" # run the detector with slither.py --ARGUMENT + HELP = "Constant functions using assembly code" # help information IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code" - WIKI_TITLE = 'Constant functions using assembly code' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Constant functions using assembly code" + WIKI_DESCRIPTION = """ Functions declared as `constant`/`pure`/`view` using assembly code. `constant`/`pure`/`view` was not enforced prior to Solidity 0.5. Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification. -As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).''' +As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).""" - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Constant{ uint counter; @@ -38,9 +38,11 @@ contract Constant{ } ``` `Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0. -All the calls to `get` revert, breaking Bob's smart contract execution.''' +All the calls to `get` revert, breaking Bob's smart contract execution.""" - WIKI_RECOMMENDATION = 'Ensure the attributes of contracts compiled prior to Solidity 0.5.0 are correct.' + WIKI_RECOMMENDATION = ( + "Ensure the attributes of contracts compiled prior to Solidity 0.5.0 are correct." + ) def _detect(self): """ Detect the constant function using assembly code @@ -58,10 +60,10 @@ All the calls to `get` revert, breaking Bob's smart contract execution.''' continue if f.view or f.pure: if f.contains_assembly: - attr = 'view' if f.view else 'pure' + attr = "view" if f.view else "pure" - info = [f, f' is declared {attr} but contains assembly code\n'] - res = self.generate_result(info, {'contains_assembly': True}) + info = [f, f" is declared {attr} but contains assembly code\n"] + res = self.generate_result(info, {"contains_assembly": True}) results.append(res) diff --git a/slither/detectors/attributes/const_functions_state.py b/slither/detectors/attributes/const_functions_state.py index f4e96bf85..19b86f465 100644 --- a/slither/detectors/attributes/const_functions_state.py +++ b/slither/detectors/attributes/const_functions_state.py @@ -11,23 +11,23 @@ class ConstantFunctionsState(AbstractDetector): Constant function detector """ - ARGUMENT = 'constant-function-state' # run the detector with slither.py --ARGUMENT - HELP = 'Constant functions changing the state' # help information + ARGUMENT = "constant-function-state" # run the detector with slither.py --ARGUMENT + HELP = "Constant functions changing the state" # help information IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state" - WIKI_TITLE = 'Constant functions changing the state' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Constant functions changing the state" + WIKI_DESCRIPTION = """ Functions declared as `constant`/`pure`/`view` change the state. `constant`/`pure`/`view` was not enforced prior to Solidity 0.5. Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification. -As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).''' +As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).""" - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Constant{ uint counter; @@ -38,9 +38,11 @@ contract Constant{ } ``` `Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0. -All the calls to `get` revert, breaking Bob's smart contract execution.''' +All the calls to `get` revert, breaking Bob's smart contract execution.""" - WIKI_RECOMMENDATION = 'Ensure that attributes of contracts compiled prior to Solidity 0.5.0 are correct.' + WIKI_RECOMMENDATION = ( + "Ensure that attributes of contracts compiled prior to Solidity 0.5.0 are correct." + ) def _detect(self): """ Detect the constant function changing the state @@ -59,14 +61,14 @@ All the calls to `get` revert, breaking Bob's smart contract execution.''' if f.view or f.pure: variables_written = f.all_state_variables_written() if variables_written: - attr = 'view' if f.view else 'pure' + attr = "view" if f.view else "pure" - info = [f, f' is declared {attr} but changes state variables:\n'] + info = [f, f" is declared {attr} but changes state variables:\n"] for variable_written in variables_written: - info += ['\t- ', variable_written, '\n'] + info += ["\t- ", variable_written, "\n"] - res = self.generate_result(info, {'contains_assembly': False}) + res = self.generate_result(info, {"contains_assembly": False}) results.append(res) diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 876037d76..45b40ae04 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -11,17 +11,16 @@ class ConstantPragma(AbstractDetector): Check that the same pragma is used in all the files """ - ARGUMENT = 'pragma' - HELP = 'If different pragma directives are used' + ARGUMENT = "pragma" + HELP = "If different pragma directives are used" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used" - - WIKI_TITLE = 'Different pragma directives are used' - WIKI_DESCRIPTION = 'Detect whether different Solidity versions are used.' - WIKI_RECOMMENDATION = 'Use one Solidity version.' + WIKI_TITLE = "Different pragma directives are used" + WIKI_DESCRIPTION = "Detect whether different Solidity versions are used." + WIKI_RECOMMENDATION = "Use one Solidity version." def _detect(self): results = [] diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index 36a3c11e3..62baeb5a9 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -13,7 +13,7 @@ from slither.formatters.attributes.incorrect_solc import format # 3: version number # 4: version number -PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') +PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)") class IncorrectSolc(AbstractDetector): @@ -21,50 +21,71 @@ class IncorrectSolc(AbstractDetector): Check if an old version of solc is used """ - ARGUMENT = 'solc-version' - HELP = 'Incorrect Solidity version' + ARGUMENT = "solc-version" + HELP = "Incorrect Solidity version" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity" - WIKI_TITLE = 'Incorrect versions of Solidity' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Incorrect versions of Solidity" + WIKI_DESCRIPTION = """ `solc` frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks. -We also recommend avoiding complex `pragma` statement.''' - WIKI_RECOMMENDATION = ''' +We also recommend avoiding complex `pragma` statement.""" + WIKI_RECOMMENDATION = """ Deploy with any of the following Solidity versions: - 0.5.11 - 0.5.13, - 0.5.15 - 0.5.17, - 0.6.8, - 0.6.10 - 0.6.11. Use a simple pragma version that allows any of these versions. -Consider using the latest version of Solidity for testing.''' +Consider using the latest version of Solidity for testing.""" COMPLEX_PRAGMA_TXT = "is too complex" OLD_VERSION_TXT = "allows old versions" LESS_THAN_TXT = "uses lesser than" - TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.11" - BUGGY_VERSION_TXT = "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)" + TOO_RECENT_VERSION_TXT = ( + "necessitates a version too recent to be trusted. Consider deploying with 0.6.11" + ) + BUGGY_VERSION_TXT = ( + "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)" + ) # Indicates the allowed versions. Must be formatted in increasing order. - ALLOWED_VERSIONS = ["0.5.11", "0.5.12", "0.5.13", "0.5.15", "0.5.16", "0.5.17", "0.6.8", "0.6.10", "0.6.11"] + ALLOWED_VERSIONS = [ + "0.5.11", + "0.5.12", + "0.5.13", + "0.5.15", + "0.5.16", + "0.5.17", + "0.6.8", + "0.6.10", + "0.6.11", + ] # Indicates the versions that should not be used. - BUGGY_VERSIONS = ["0.4.22", "^0.4.22", - "0.5.5", "^0.5.5", - "0.5.6", "^0.5.6", - "0.5.14", "^0.5.14", - "0.6.9", "^0.6.9"] + BUGGY_VERSIONS = [ + "0.4.22", + "^0.4.22", + "0.5.5", + "^0.5.5", + "0.5.6", + "^0.5.6", + "0.5.14", + "^0.5.14", + "0.6.9", + "^0.6.9", + ] def _check_version(self, version): op = version[0] - if op and op not in ['>', '>=', '^']: + if op and op not in [">", ">=", "^"]: return self.LESS_THAN_TXT - version_number = '.'.join(version[2:]) + version_number = ".".join(version[2:]) if version_number not in self.ALLOWED_VERSIONS: - if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split('.'))): + if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))): return self.TOO_RECENT_VERSION_TXT return self.OLD_VERSION_TXT return None @@ -81,7 +102,11 @@ Consider using the latest version of Solidity for testing.''' version_right = versions[1] # Only allow two elements if the second one is # <0.5.0 or <0.6.0 - if version_right not in [('<', '', '0', '5', '0'), ('<', '', '0', '6', '0'), ('<', '', '0', '7', '0')]: + if version_right not in [ + ("<", "", "0", "5", "0"), + ("<", "", "0", "6", "0"), + ("<", "", "0", "7", "0"), + ]: return self.COMPLEX_PRAGMA_TXT return self._check_version(version_left) else: @@ -119,10 +144,15 @@ Consider using the latest version of Solidity for testing.''' if self.slither.crytic_compile: if self.slither.crytic_compile.compiler_version: - if self.slither.crytic_compile.compiler_version.version not in self.ALLOWED_VERSIONS: - info = ["solc-", - self.slither.crytic_compile.compiler_version.version, - " is not recommended for deployement\n"] + if ( + self.slither.crytic_compile.compiler_version.version + not in self.ALLOWED_VERSIONS + ): + info = [ + "solc-", + self.slither.crytic_compile.compiler_version.version, + " is not recommended for deployement\n", + ] json = self.generate_result(info) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 343a5b7a2..cba7d63c4 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -2,27 +2,32 @@ Check if ethers are locked in the contract """ -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (HighLevelCall, LowLevelCall, Send, - Transfer, NewContract, LibraryCall, InternalCall) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import ( + HighLevelCall, + LowLevelCall, + Send, + Transfer, + NewContract, + LibraryCall, + InternalCall, +) class LockedEther(AbstractDetector): """ """ - ARGUMENT = 'locked-ether' + ARGUMENT = "locked-ether" HELP = "Contracts that lock ether" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether" - - WIKI_TITLE = 'Contracts that lock Ether' - WIKI_DESCRIPTION = 'Contract with a `payable` function, but without a withdrawal capacity.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Contracts that lock Ether" + WIKI_DESCRIPTION = "Contract with a `payable` function, but without a withdrawal capacity." + WIKI_EXPLOIT_SCENARIO = """ ```solidity pragma solidity 0.4.24; contract Locked{ @@ -30,9 +35,9 @@ contract Locked{ } } ``` -Every Ether sent to `Locked` will be lost.''' +Every Ether sent to `Locked` will be lost.""" - WIKI_RECOMMENDATION = 'Remove the payable attribute or add a withdraw function.' + WIKI_RECOMMENDATION = "Remove the payable attribute or add a withdraw function." @staticmethod def do_no_send_ether(contract): @@ -45,15 +50,17 @@ Every Ether sent to `Locked` will be lost.''' to_explore = [] for function in functions: calls = [c.name for c in function.internal_calls] - if 'suicide(address)' in calls or 'selfdestruct(address)' in calls: + if "suicide(address)" in calls or "selfdestruct(address)" in calls: return False for node in function.nodes: for ir in node.irs: - if isinstance(ir, (Send, Transfer, HighLevelCall, LowLevelCall, NewContract)): + if isinstance( + ir, (Send, Transfer, HighLevelCall, LowLevelCall, NewContract) + ): if ir.call_value and ir.call_value != 0: return False if isinstance(ir, (LowLevelCall)): - if ir.function_name in ['delegatecall', 'callcode']: + if ir.function_name in ["delegatecall", "callcode"]: return False # If a new internal call or librarycall # Add it to the list to explore @@ -64,7 +71,6 @@ Every Ether sent to `Locked` will be lost.''' return True - def _detect(self): results = [] diff --git a/slither/detectors/erc/incorrect_erc20_interface.py b/slither/detectors/erc/incorrect_erc20_interface.py index f50050493..1f8ea7aaa 100644 --- a/slither/detectors/erc/incorrect_erc20_interface.py +++ b/slither/detectors/erc/incorrect_erc20_interface.py @@ -10,46 +10,56 @@ class IncorrectERC20InterfaceDetection(AbstractDetector): Incorrect ERC20 Interface """ - ARGUMENT = 'erc20-interface' - HELP = 'Incorrect ERC20 interfaces' + ARGUMENT = "erc20-interface" + HELP = "Incorrect ERC20 interfaces" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface" - WIKI_TITLE = 'Incorrect erc20 interface' - WIKI_DESCRIPTION = 'Incorrect return values for `ERC20` functions. A contract compiled with Solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Incorrect erc20 interface" + WIKI_DESCRIPTION = "Incorrect return values for `ERC20` functions. A contract compiled with Solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Token{ function transfer(address to, uint value) external; //... } ``` -`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC20` interface implementation. Alice's contract is unable to interact with Bob's contract.''' +`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC20` interface implementation. Alice's contract is unable to interact with Bob's contract.""" - WIKI_RECOMMENDATION = 'Set the appropriate return values and types for the defined `ERC20` functions.' + WIKI_RECOMMENDATION = ( + "Set the appropriate return values and types for the defined `ERC20` functions." + ) @staticmethod def incorrect_erc20_interface(signature): (name, parameters, returnVars) = signature - if name == 'transfer' and parameters == ['address', 'uint256'] and returnVars != ['bool']: + if name == "transfer" and parameters == ["address", "uint256"] and returnVars != ["bool"]: return True - if name == 'transferFrom' and parameters == ['address', 'address', 'uint256'] and returnVars != ['bool']: + if ( + name == "transferFrom" + and parameters == ["address", "address", "uint256"] + and returnVars != ["bool"] + ): return True - if name == 'approve' and parameters == ['address', 'uint256'] and returnVars != ['bool']: + if name == "approve" and parameters == ["address", "uint256"] and returnVars != ["bool"]: return True - if name == 'allowance' and parameters == ['address', 'address'] and returnVars != ['uint256']: + if ( + name == "allowance" + and parameters == ["address", "address"] + and returnVars != ["uint256"] + ): return True - if name == 'balanceOf' and parameters == ['address'] and returnVars != ['uint256']: + if name == "balanceOf" and parameters == ["address"] and returnVars != ["uint256"]: return True - if name == 'totalSupply' and parameters == [] and returnVars != ['uint256']: + if name == "totalSupply" and parameters == [] and returnVars != ["uint256"]: return True return False @@ -72,7 +82,11 @@ contract Token{ return [] funcs = contract.functions - functions = [f for f in funcs if IncorrectERC20InterfaceDetection.incorrect_erc20_interface(f.signature)] + functions = [ + f + for f in funcs + if IncorrectERC20InterfaceDetection.incorrect_erc20_interface(f.signature) + ] return functions diff --git a/slither/detectors/erc/incorrect_erc721_interface.py b/slither/detectors/erc/incorrect_erc721_interface.py index fc242b752..53703a2cf 100644 --- a/slither/detectors/erc/incorrect_erc721_interface.py +++ b/slither/detectors/erc/incorrect_erc721_interface.py @@ -9,52 +9,72 @@ class IncorrectERC721InterfaceDetection(AbstractDetector): Incorrect ERC721 Interface """ - ARGUMENT = 'erc721-interface' - HELP = 'Incorrect ERC721 interfaces' + ARGUMENT = "erc721-interface" + HELP = "Incorrect ERC721 interfaces" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface" + ) - WIKI_TITLE = 'Incorrect erc721 interface' - WIKI_DESCRIPTION = 'Incorrect return values for `ERC721` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Incorrect erc721 interface" + WIKI_DESCRIPTION = "Incorrect return values for `ERC721` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Token{ function ownerOf(uint256 _tokenId) external view returns (bool); //... } ``` -`Token.ownerOf` does not return an address like `ERC721` expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC721` interface implementation. Alice's contract is unable to interact with Bob's contract.''' +`Token.ownerOf` does not return an address like `ERC721` expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC721` interface implementation. Alice's contract is unable to interact with Bob's contract.""" - WIKI_RECOMMENDATION = 'Set the appropriate return values and vtypes for the defined `ERC721` functions.' + WIKI_RECOMMENDATION = ( + "Set the appropriate return values and vtypes for the defined `ERC721` functions." + ) @staticmethod def incorrect_erc721_interface(signature): (name, parameters, returnVars) = signature # ERC721 - if name == 'balanceOf' and parameters == ['address'] and returnVars != ['uint256']: + if name == "balanceOf" and parameters == ["address"] and returnVars != ["uint256"]: return True - if name == 'ownerOf' and parameters == ['uint256'] and returnVars != ['address']: + if name == "ownerOf" and parameters == ["uint256"] and returnVars != ["address"]: return True - if name == 'safeTransferFrom' and parameters == ['address', 'address', 'uint256', 'bytes'] and returnVars != []: + if ( + name == "safeTransferFrom" + and parameters == ["address", "address", "uint256", "bytes"] + and returnVars != [] + ): return True - if name == 'safeTransferFrom' and parameters == ['address', 'address', 'uint256'] and returnVars != []: + if ( + name == "safeTransferFrom" + and parameters == ["address", "address", "uint256"] + and returnVars != [] + ): return True - if name == 'transferFrom' and parameters == ['address', 'address', 'uint256'] and returnVars != []: + if ( + name == "transferFrom" + and parameters == ["address", "address", "uint256"] + and returnVars != [] + ): return True - if name == 'approve' and parameters == ['address', 'uint256'] and returnVars != []: + if name == "approve" and parameters == ["address", "uint256"] and returnVars != []: return True - if name == 'setApprovalForAll' and parameters == ['address', 'bool'] and returnVars != []: + if name == "setApprovalForAll" and parameters == ["address", "bool"] and returnVars != []: return True - if name == 'getApproved' and parameters == ['uint256'] and returnVars != ['address']: + if name == "getApproved" and parameters == ["uint256"] and returnVars != ["address"]: return True - if name == 'isApprovedForAll' and parameters == ['address', 'address'] and returnVars != ['bool']: + if ( + name == "isApprovedForAll" + and parameters == ["address", "address"] + and returnVars != ["bool"] + ): return True # ERC165 (dependency) - if name == 'supportsInterface' and parameters == ['bytes4'] and returnVars != ['bool']: + if name == "supportsInterface" and parameters == ["bytes4"] and returnVars != ["bool"]: return True return False @@ -72,7 +92,11 @@ contract Token{ return [] funcs = contract.functions - functions = [f for f in funcs if IncorrectERC721InterfaceDetection.incorrect_erc721_interface(f.signature)] + functions = [ + f + for f in funcs + if IncorrectERC721InterfaceDetection.incorrect_erc721_interface(f.signature) + ] return functions def _detect(self): diff --git a/slither/detectors/erc/unindexed_event_parameters.py b/slither/detectors/erc/unindexed_event_parameters.py index d0069ffcc..757017946 100644 --- a/slither/detectors/erc/unindexed_event_parameters.py +++ b/slither/detectors/erc/unindexed_event_parameters.py @@ -9,16 +9,16 @@ class UnindexedERC20EventParameters(AbstractDetector): Un-indexed ERC20 event parameters """ - ARGUMENT = 'erc20-indexed' - HELP = 'Un-indexed ERC20 event parameters' + ARGUMENT = "erc20-indexed" + HELP = "Un-indexed ERC20 event parameters" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters" - WIKI_TITLE = 'Unindexed ERC20 event oarameters' - WIKI_DESCRIPTION = 'Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Unindexed ERC20 event oarameters" + WIKI_DESCRIPTION = "Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract ERC20Bad { // ... @@ -29,9 +29,9 @@ contract ERC20Bad { } ``` `Transfer` and `Approval` events should have the 'indexed' keyword on their two first parameters, as defined by the `ERC20` specification. -Failure to include these keywords will exclude the parameter data in the transaction/block's bloom filter, so external tooling searching for these parameters may overlook them and fail to index logs from this token contract.''' +Failure to include these keywords will exclude the parameter data in the transaction/block's bloom filter, so external tooling searching for these parameters may overlook them and fail to index logs from this token contract.""" - WIKI_RECOMMENDATION = 'Add the `indexed` keyword to event parameters that should include it, according to the `ERC20` specification.' + WIKI_RECOMMENDATION = "Add the `indexed` keyword to event parameters that should include it, according to the `ERC20` specification." STANDARD_JSON = False @@ -53,8 +53,10 @@ Failure to include these keywords will exclude the parameter data in the transac for event in contract.events_declared: # If this is transfer/approval events, expect the first two parameters to be indexed. - if event.full_name in ["Transfer(address,address,uint256)", - "Approval(address,address,uint256)"]: + if event.full_name in [ + "Transfer(address,address,uint256)", + "Approval(address,address,uint256)", + ]: if not event.elems[0].indexed: results.append((event, event.elems[0])) if not event.elems[1].indexed: @@ -82,5 +84,4 @@ Failure to include these keywords will exclude the parameter data in the transac res.add(event, {"parameter_name": parameter.name}) results.append(res) - return results diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 76511d018..36d8326e4 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -6,17 +6,16 @@ class Backdoor(AbstractDetector): Detect function named backdoor """ - ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector - HELP = 'Function named backdoor (detector example)' + ARGUMENT = "backdoor" # slither will launch the detector with slither.py --mydetector + HELP = "Function named backdoor (detector example)" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - - WIKI = 'https://github.com/trailofbits/slither/wiki/Adding-a-new-detector' - WIKI_TITLE = 'Backdoor example' - WIKI_DESCRIPTION = 'Plugin example' - WIKI_EXPLOIT_SCENARIO = '..' - WIKI_RECOMMENDATION = '..' + WIKI = "https://github.com/trailofbits/slither/wiki/Adding-a-new-detector" + WIKI_TITLE = "Backdoor example" + WIKI_DESCRIPTION = "Plugin example" + WIKI_EXPLOIT_SCENARIO = ".." + WIKI_RECOMMENDATION = ".." def _detect(self): results = [] @@ -24,9 +23,9 @@ class Backdoor(AbstractDetector): for contract in self.slither.contracts_derived: # Check if a function has 'backdoor' in its name for f in contract.functions: - if 'backdoor' in f.name: + if "backdoor" in f.name: # Info to be printed - info = ['Backdoor function found in ', f, '\n'] + info = ["Backdoor function found in ", f, "\n"] # Add the result in result res = self.generate_result(info) diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 8659a4261..1b63d5e1c 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -11,28 +11,32 @@ """ from slither.core.declarations import Function from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent -from slither.core.declarations.solidity_variables import (SolidityFunction, - SolidityVariableComposed) -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, - Send, SolidityCall, Transfer) +from slither.core.declarations.solidity_variables import SolidityFunction, SolidityVariableComposed +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import ( + HighLevelCall, + Index, + LowLevelCall, + Send, + SolidityCall, + Transfer, +) class ArbitrarySend(AbstractDetector): """ """ - ARGUMENT = 'arbitrary-send' - HELP = 'Functions that send Ether to arbitrary destinations' + ARGUMENT = "arbitrary-send" + HELP = "Functions that send Ether to arbitrary destinations" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations" - WIKI_TITLE = 'Functions that send Ether to arbitrary destinations' - WIKI_DESCRIPTION = 'Unprotected call to a function sending Ether to an arbitrary address.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Functions that send Ether to arbitrary destinations" + WIKI_DESCRIPTION = "Unprotected call to a function sending Ether to an arbitrary address." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract ArbitrarySend{ address destination; @@ -45,9 +49,9 @@ contract ArbitrarySend{ } } ``` -Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance.''' +Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance.""" - WIKI_RECOMMENDATION = 'Ensure that an arbitrary user cannot withdraw unauthorized funds.' + WIKI_RECOMMENDATION = "Ensure that an arbitrary user cannot withdraw unauthorized funds." def arbitrary_send(self, func): """ @@ -59,32 +63,34 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract for node in func.nodes: for ir in node.irs: if isinstance(ir, SolidityCall): - if ir.function == SolidityFunction('ecrecover(bytes32,uint8,bytes32,bytes32)'): + if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"): return False if isinstance(ir, Index): - if ir.variable_right == SolidityVariableComposed('msg.sender'): + if ir.variable_right == SolidityVariableComposed("msg.sender"): return False - if is_dependent(ir.variable_right, SolidityVariableComposed('msg.sender'), func.contract): + if is_dependent( + ir.variable_right, SolidityVariableComposed("msg.sender"), func.contract + ): return False if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): if isinstance(ir, (HighLevelCall)): if isinstance(ir.function, Function): - if ir.function.full_name == 'transferFrom(address,address,uint256)': + if ir.function.full_name == "transferFrom(address,address,uint256)": return False if ir.call_value is None: continue - if ir.call_value == SolidityVariableComposed('msg.value'): + if ir.call_value == SolidityVariableComposed("msg.value"): continue - if is_dependent(ir.call_value, SolidityVariableComposed('msg.value'), func.contract): + if is_dependent( + ir.call_value, SolidityVariableComposed("msg.value"), func.contract + ): continue if is_tainted(ir.destination, func.contract): ret.append(node) - return ret - def detect_arbitrary_send(self, contract): """ Detect arbitrary send @@ -110,9 +116,9 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract for (func, nodes) in arbitrary_send: info = [func, " sends eth to arbitrary user\n"] - info += ['\tDangerous calls:\n'] + info += ["\tDangerous calls:\n"] for node in nodes: - info += ['\t- ', node, '\n'] + info += ["\t- ", node, "\n"] res = self.generate_result(info) diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index 21add9239..c9d7067a9 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -1,10 +1,6 @@ -from slither.core.declarations.solidity_variables import (SolidityFunction, - SolidityVariableComposed) -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (HighLevelCall, - LowLevelCall, - LibraryCall) +from slither.core.declarations.solidity_variables import SolidityFunction, SolidityVariableComposed +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import HighLevelCall, LowLevelCall, LibraryCall from slither.utils.code_complexity import compute_cyclomatic_complexity @@ -17,9 +13,8 @@ class ComplexFunction(AbstractDetector): - numerous external calls """ - - ARGUMENT = 'complex-function' - HELP = 'Complex functions' + ARGUMENT = "complex-function" + HELP = "Complex functions" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.MEDIUM @@ -42,10 +37,7 @@ class ComplexFunction(AbstractDetector): code_complexity = compute_cyclomatic_complexity(func) if code_complexity > ComplexFunction.MAX_CYCLOMATIC_COMPLEXITY: - result.append({ - "func": func, - "cause": ComplexFunction.CAUSE_CYCLOMATIC - }) + result.append({"func": func, "cause": ComplexFunction.CAUSE_CYCLOMATIC}) """Detect the number of external calls in the func shouldn't be greater than 5 @@ -57,19 +49,13 @@ class ComplexFunction(AbstractDetector): count += 1 if count > ComplexFunction.MAX_EXTERNAL_CALLS: - result.append({ - "func": func, - "cause": ComplexFunction.CAUSE_EXTERNAL_CALL - }) + result.append({"func": func, "cause": ComplexFunction.CAUSE_EXTERNAL_CALL}) """Checks the number of the state variables written shouldn't be greater than 10 """ if len(func.state_variables_written) > ComplexFunction.MAX_STATE_VARIABLES: - result.append({ - "func": func, - "cause": ComplexFunction.CAUSE_STATE_VARS - }) + result.append({"func": func, "cause": ComplexFunction.CAUSE_STATE_VARS}) return result @@ -100,19 +86,20 @@ class ComplexFunction(AbstractDetector): if cause == self.CAUSE_STATE_VARS: txt += "\t- Reason: High number of modified state variables" - info = txt.format(func.canonical_name, - func.source_mapping_str) + info = txt.format(func.canonical_name, func.source_mapping_str) info = info + "\n" self.log(info) res = self.generate_result(info) - res.add(func, { - 'high_number_of_external_calls': cause == self.CAUSE_EXTERNAL_CALL, - 'high_number_of_branches': cause == self.CAUSE_CYCLOMATIC, - 'high_number_of_state_variables': cause == self.CAUSE_STATE_VARS - }) + res.add( + func, + { + "high_number_of_external_calls": cause == self.CAUSE_EXTERNAL_CALL, + "high_number_of_branches": cause == self.CAUSE_CYCLOMATIC, + "high_number_of_state_variables": cause == self.CAUSE_STATE_VARS, + }, + ) results.append(res) return results - diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 9fd1b132b..eb815565b 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -1,7 +1,6 @@ -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import SolidityCall -from slither.slithir.operations import (InternalCall, InternalDynamicCall) +from slither.slithir.operations import InternalCall, InternalDynamicCall from slither.formatters.functions.external_function import format @@ -13,17 +12,18 @@ class ExternalFunction(AbstractDetector): https://github.com/trailofbits/slither/pull/53#issuecomment-432809950 """ - ARGUMENT = 'external-function' - HELP = 'Public function that could be declared external' + ARGUMENT = "external-function" + HELP = "Public function that could be declared external" IMPACT = DetectorClassification.OPTIMIZATION CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external" - - WIKI_TITLE = 'Public function that could be declared external' - WIKI_DESCRIPTION = '`public` functions that are never called by the contract should be declared `external` to save gas.' - WIKI_RECOMMENDATION = 'Use the `external` attribute for functions never called from the contract.' + WIKI_TITLE = "Public function that could be declared external" + WIKI_DESCRIPTION = "`public` functions that are never called by the contract should be declared `external` to save gas." + WIKI_RECOMMENDATION = ( + "Use the `external` attribute for functions never called from the contract." + ) @staticmethod def detect_functions_called(contract): @@ -94,9 +94,12 @@ class ExternalFunction(AbstractDetector): """ # We assume the provided function is the base-most function, so we check all derived contracts # for a redefinition - return [base_most_function] + [function for derived_contract in base_most_function.contract.derived_contracts - for function in derived_contract.functions - if function.full_name == base_most_function.full_name] + return [base_most_function] + [ + function + for derived_contract in base_most_function.contract.derived_contracts + for function in derived_contract.functions + if function.full_name == base_most_function.full_name + ] @staticmethod def function_parameters_written(function): @@ -140,25 +143,37 @@ class ExternalFunction(AbstractDetector): # because parameters of external functions will be allocated in calldata region which is immutable if self.function_parameters_written(function): continue - + # Get the base-most function to know our origin of this function. base_most_function = self.get_base_most_function(function) # Get all possible contracts which can call this function (or an override). - all_possible_sources = [base_most_function.contract] + base_most_function.contract.derived_contracts + all_possible_sources = [ + base_most_function.contract + ] + base_most_function.contract.derived_contracts # Get all function signatures (overloaded and not), mark as completed and we process them now. # Note: We mark all function definitions as the same, as they must all share visibility to override. - all_function_definitions = set(self.get_all_function_definitions(base_most_function)) + all_function_definitions = set( + self.get_all_function_definitions(base_most_function) + ) completed_functions = completed_functions.union(all_function_definitions) # Filter false-positives: Determine if any of these sources have dynamic calls, if so, flag all of these # function definitions, and then flag all functions in all contracts that make dynamic calls. sources_with_dynamic_calls = set(all_possible_sources) & dynamic_call_contracts if sources_with_dynamic_calls: - functions_in_dynamic_call_sources = set([f for dyn_contract in sources_with_dynamic_calls - for f in dyn_contract.functions if not f.is_constructor]) - completed_functions = completed_functions.union(functions_in_dynamic_call_sources) + functions_in_dynamic_call_sources = set( + [ + f + for dyn_contract in sources_with_dynamic_calls + for f in dyn_contract.functions + if not f.is_constructor + ] + ) + completed_functions = completed_functions.union( + functions_in_dynamic_call_sources + ) continue # Detect all functions called in each source, if any match our current signature, we skip @@ -176,8 +191,11 @@ class ExternalFunction(AbstractDetector): # As we collect all shadowed functions in get_all_function_definitions # Some function coming from a base might already been declared as external - all_function_definitions = [f for f in all_function_definitions if f.visibility == 'public' and - f.contract == f.contract_declarer] + all_function_definitions = [ + f + for f in all_function_definitions + if f.visibility == "public" and f.contract == f.contract_declarer + ] if all_function_definitions: function_definition = all_function_definitions[0] all_function_definitions = all_function_definitions[1:] diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 999e21705..44c4aa919 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -12,17 +12,16 @@ class Suicidal(AbstractDetector): Unprotected function detector """ - ARGUMENT = 'suicidal' - HELP = 'Functions allowing anyone to destruct the contract' + ARGUMENT = "suicidal" + HELP = "Functions allowing anyone to destruct the contract" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal" - - WIKI_TITLE = 'Suicidal' - WIKI_DESCRIPTION = 'Unprotected call to a function executing `selfdestruct`/`suicide`.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Suicidal" + WIKI_DESCRIPTION = "Unprotected call to a function executing `selfdestruct`/`suicide`." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Suicidal{ function kill() public{ @@ -30,9 +29,9 @@ contract Suicidal{ } } ``` -Bob calls `kill` and destructs the contract.''' +Bob calls `kill` and destructs the contract.""" - WIKI_RECOMMENDATION = 'Protect access to all sensitive functions.' + WIKI_RECOMMENDATION = "Protect access to all sensitive functions." @staticmethod def detect_suicidal_func(func): @@ -46,11 +45,11 @@ Bob calls `kill` and destructs the contract.''' if func.is_constructor: return False - if func.visibility not in ['public', 'external']: + if func.visibility not in ["public", "external"]: return False calls = [c.name for c in func.internal_calls] - if not ('suicide(address)' in calls or 'selfdestruct(address)' in calls): + if not ("suicide(address)" in calls or "selfdestruct(address)" in calls): return False if func.is_protected(): diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index ff651a901..24e944c6b 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -14,45 +14,45 @@ class NamingConvention(AbstractDetector): - Ignore echidna properties (functions with names starting 'echidna_' or 'crytic_' """ - ARGUMENT = 'naming-convention' - HELP = 'Conformity to Solidity naming conventions' + ARGUMENT = "naming-convention" + HELP = "Conformity to Solidity naming conventions" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#conformity-to-solidity-naming-conventions' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformity-to-solidity-naming-conventions" - WIKI_TITLE = 'Conformance to Solidity naming conventions' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Conformance to Solidity naming conventions" + WIKI_DESCRIPTION = """ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions) that should be followed. #### Rule exceptions - Allow constant variable name/symbol/decimals to be lowercase (`ERC20`). -- Allow `_` at the beginning of the `mixed_case` match for private variables and unused parameters.''' +- Allow `_` at the beginning of the `mixed_case` match for private variables and unused parameters.""" - WIKI_RECOMMENDATION = 'Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions).' + WIKI_RECOMMENDATION = "Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions)." STANDARD_JSON = False @staticmethod def is_cap_words(name): - return re.search('^[A-Z]([A-Za-z0-9]+)?_?$', name) is not None + return re.search("^[A-Z]([A-Za-z0-9]+)?_?$", name) is not None @staticmethod def is_mixed_case(name): - return re.search('^[a-z]([A-Za-z0-9]+)?_?$', name) is not None + return re.search("^[a-z]([A-Za-z0-9]+)?_?$", name) is not None @staticmethod def is_mixed_case_with_underscore(name): # Allow _ at the beginning to represent private variable # or unused parameters - return re.search('^[_]?[a-z]([A-Za-z0-9]+)?_?$', name) is not None + return re.search("^[_]?[a-z]([A-Za-z0-9]+)?_?$", name) is not None @staticmethod def is_upper_case_with_underscores(name): - return re.search('^[A-Z0-9_]+_?$', name) is not None + return re.search("^[A-Z0-9_]+_?$", name) is not None @staticmethod def should_avoid_name(name): - return re.search('^[lOI]$', name) is not None + return re.search("^[lOI]$", name) is not None def _detect(self): @@ -63,10 +63,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Contract ", contract, " is not in CapWords\n"] res = self.generate_result(info) - res.add(contract, { - "target": "contract", - "convention": "CapWords" - }) + res.add(contract, {"target": "contract", "convention": "CapWords"}) results.append(res) for struct in contract.structures_declared: @@ -74,10 +71,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Struct ", struct, " is not in CapWords\n"] res = self.generate_result(info) - res.add(struct, { - "target": "structure", - "convention": "CapWords" - }) + res.add(struct, {"target": "structure", "convention": "CapWords"}) results.append(res) for event in contract.events_declared: @@ -85,27 +79,24 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Event ", event, " is not in CapWords\n"] res = self.generate_result(info) - res.add(event, { - "target": "event", - "convention": "CapWords" - }) + res.add(event, {"target": "event", "convention": "CapWords"}) results.append(res) for func in contract.functions_declared: if func.is_constructor: continue if not self.is_mixed_case(func.name): - if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name): + if func.visibility in [ + "internal", + "private", + ] and self.is_mixed_case_with_underscore(func.name): continue if func.name.startswith("echidna_") or func.name.startswith("crytic_"): continue info = ["Function ", func, " is not in mixedCase\n"] res = self.generate_result(info) - res.add(func, { - "target": "function", - "convention": "mixedCase" - }) + res.add(func, {"target": "function", "convention": "mixedCase"}) results.append(res) for argument in func.parameters: @@ -120,41 +111,40 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Parameter ", argument, " is not in mixedCase\n"] res = self.generate_result(info) - res.add(argument, { - "target": "parameter", - "convention": "mixedCase" - }) + res.add(argument, {"target": "parameter", "convention": "mixedCase"}) results.append(res) for var in contract.state_variables_declared: if self.should_avoid_name(var.name): if not self.is_upper_case_with_underscores(var.name): - info = ["Variable ", var," used l, O, I, which should not be used\n"] + info = ["Variable ", var, " used l, O, I, which should not be used\n"] res = self.generate_result(info) - res.add(var, { - "target": "variable", - "convention": "l_O_I_should_not_be_used" - }) + res.add( + var, {"target": "variable", "convention": "l_O_I_should_not_be_used"} + ) results.append(res) if var.is_constant is True: # For ERC20 compatibility - if var.name in ['symbol', 'name', 'decimals']: + if var.name in ["symbol", "name", "decimals"]: continue if not self.is_upper_case_with_underscores(var.name): - info = ["Constant ", var," is not in UPPER_CASE_WITH_UNDERSCORES\n"] + info = ["Constant ", var, " is not in UPPER_CASE_WITH_UNDERSCORES\n"] res = self.generate_result(info) - res.add(var, { - "target": "variable_constant", - "convention": "UPPER_CASE_WITH_UNDERSCORES" - }) + res.add( + var, + { + "target": "variable_constant", + "convention": "UPPER_CASE_WITH_UNDERSCORES", + }, + ) results.append(res) else: - if var.visibility == 'private': + if var.visibility == "private": correct_naming = self.is_mixed_case_with_underscore(var.name) else: correct_naming = self.is_mixed_case(var.name) @@ -162,10 +152,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Variable ", var, " is not in mixedCase\n"] res = self.generate_result(info) - res.add(var, { - "target": "variable", - "convention": "mixedCase" - }) + res.add(var, {"target": "variable", "convention": "mixedCase"}) results.append(res) for enum in contract.enums_declared: @@ -173,10 +160,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Enum ", enum, " is not in CapWords\n"] res = self.generate_result(info) - res.add(enum, { - "target": "enum", - "convention": "CapWords" - }) + res.add(enum, {"target": "enum", "convention": "CapWords"}) results.append(res) for modifier in contract.modifiers_declared: @@ -184,10 +168,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = ["Modifier ", modifier, " is not in mixedCase\n"] res = self.generate_result(info) - res.add(modifier, { - "target": "modifier", - "convention": "mixedCase" - }) + res.add(modifier, {"target": "modifier", "convention": "mixedCase"}) results.append(res) return results diff --git a/slither/detectors/operations/block_timestamp.py b/slither/detectors/operations/block_timestamp.py index fd50571ee..cf65bf5b3 100644 --- a/slither/detectors/operations/block_timestamp.py +++ b/slither/detectors/operations/block_timestamp.py @@ -7,9 +7,8 @@ from typing import List, Tuple from slither.analyses.data_dependency.data_dependency import is_dependent from slither.core.cfg.node import Node from slither.core.declarations import Function, Contract -from slither.core.declarations.solidity_variables import (SolidityVariableComposed, SolidityVariable) -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) +from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Binary, BinaryType @@ -18,16 +17,18 @@ def _timestamp(func: Function) -> List[Node]: for node in func.nodes: if node.contains_require_or_assert(): for var in node.variables_read: - if is_dependent(var, SolidityVariableComposed('block.timestamp'), func.contract): + if is_dependent(var, SolidityVariableComposed("block.timestamp"), func.contract): ret.add(node) - if is_dependent(var, SolidityVariable('now'), func.contract): + if is_dependent(var, SolidityVariable("now"), func.contract): ret.add(node) for ir in node.irs: if isinstance(ir, Binary) and BinaryType.return_bool(ir.type): for var in ir.read: - if is_dependent(var, SolidityVariableComposed('block.timestamp'), func.contract): + if is_dependent( + var, SolidityVariableComposed("block.timestamp"), func.contract + ): ret.add(node) - if is_dependent(var, SolidityVariable('now'), func.contract): + if is_dependent(var, SolidityVariable("now"), func.contract): ret.add(node) return sorted(list(ret), key=lambda x: x.node_id) @@ -51,17 +52,19 @@ class Timestamp(AbstractDetector): """ """ - ARGUMENT = 'timestamp' - HELP = 'Dangerous usage of `block.timestamp`' + ARGUMENT = "timestamp" + HELP = "Dangerous usage of `block.timestamp`" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp" - WIKI_TITLE = 'Block timestamp' - WIKI_DESCRIPTION = 'Dangerous usage of `block.timestamp`. `block.timestamp` can be manipulated by miners.' - WIKI_EXPLOIT_SCENARIO = '''"Bob's contract relies on `block.timestamp` for its randomness. Eve is a miner and manipulates `block.timestamp` to exploit Bob's contract.''' - WIKI_RECOMMENDATION = 'Avoid relying on `block.timestamp`.' + WIKI_TITLE = "Block timestamp" + WIKI_DESCRIPTION = ( + "Dangerous usage of `block.timestamp`. `block.timestamp` can be manipulated by miners." + ) + WIKI_EXPLOIT_SCENARIO = """"Bob's contract relies on `block.timestamp` for its randomness. Eve is a miner and manipulates `block.timestamp` to exploit Bob's contract.""" + WIKI_RECOMMENDATION = "Avoid relying on `block.timestamp`." def _detect(self): """ @@ -74,9 +77,9 @@ class Timestamp(AbstractDetector): info = [func, " uses timestamp for comparisons\n"] - info += ['\tDangerous comparisons:\n'] + info += ["\tDangerous comparisons:\n"] for node in nodes: - info += ['\t- ', node, '\n'] + info += ["\t- ", node, "\n"] res = self.generate_result(info) diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 96320bd89..81dbf5ce1 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -11,16 +11,16 @@ class LowLevelCalls(AbstractDetector): Detect usage of low level calls """ - ARGUMENT = 'low-level-calls' - HELP = 'Low level calls' + ARGUMENT = "low-level-calls" + HELP = "Low level calls" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls" - WIKI_TITLE = 'Low-level calls' - WIKI_DESCRIPTION = 'The use of low-level calls is error-prone. Low-level calls do not check for [code existence](https://solidity.readthedocs.io/en/v0.4.25/control-structures.html#error-handling-assert-require-revert-and-exceptions) or call success.' - WIKI_RECOMMENDATION = 'Avoid low-level calls. Check the call success. If the call is meant for a contract, check for code existence.' + WIKI_TITLE = "Low-level calls" + WIKI_DESCRIPTION = "The use of low-level calls is error-prone. Low-level calls do not check for [code existence](https://solidity.readthedocs.io/en/v0.4.25/control-structures.html#error-handling-assert-require-revert-and-exceptions) or call success." + WIKI_RECOMMENDATION = "Avoid low-level calls. Check the call success. If the call is meant for a contract, check for code existence." @staticmethod def _contains_low_level_calls(node): @@ -35,8 +35,7 @@ class LowLevelCalls(AbstractDetector): ret = [] for f in [f for f in contract.functions if contract == f.contract_declarer]: nodes = f.nodes - assembly_nodes = [n for n in nodes if - self._contains_low_level_calls(n)] + assembly_nodes = [n for n in nodes if self._contains_low_level_calls(n)] if assembly_nodes: ret.append((f, assembly_nodes)) return ret @@ -48,10 +47,10 @@ class LowLevelCalls(AbstractDetector): for c in self.contracts: values = self.detect_low_level_calls(c) for func, nodes in values: - info = ["Low level call in ", func,":\n"] + info = ["Low level call in ", func, ":\n"] for node in nodes: - info += ['\t- ', node, '\n'] + info += ["\t- ", node, "\n"] res = self.generate_result(info) diff --git a/slither/detectors/operations/unchecked_low_level_return_values.py b/slither/detectors/operations/unchecked_low_level_return_values.py index a01e5eb8e..a9c2da38d 100644 --- a/slither/detectors/operations/unchecked_low_level_return_values.py +++ b/slither/detectors/operations/unchecked_low_level_return_values.py @@ -5,21 +5,22 @@ from slither.detectors.abstract_detector import DetectorClassification from .unused_return_values import UnusedReturnValues from slither.slithir.operations import LowLevelCall + class UncheckedLowLevel(UnusedReturnValues): """ If the return value of a send is not checked, it might lead to losing ether """ - ARGUMENT = 'unchecked-lowlevel' - HELP = 'Unchecked low-level calls' + ARGUMENT = "unchecked-lowlevel" + HELP = "Unchecked low-level calls" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls" - WIKI_TITLE = 'Unchecked low-level calls' - WIKI_DESCRIPTION = 'The return value of a low-level call is not checked.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Unchecked low-level calls" + WIKI_DESCRIPTION = "The return value of a low-level call is not checked." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract MyConc{ function my_func(address payable dst) public payable{ @@ -29,15 +30,11 @@ contract MyConc{ ``` The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract. If the low level is used to prevent blocking operations, consider logging failed calls. - ''' + """ - WIKI_RECOMMENDATION = 'Ensure that the return value of a low-level call is checked or logged.' + WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged." _txt_description = "low-level calls" def _is_instance(self, ir): return isinstance(ir, LowLevelCall) - - - - diff --git a/slither/detectors/operations/unchecked_send_return_value.py b/slither/detectors/operations/unchecked_send_return_value.py index 3fedc2467..b2437609a 100644 --- a/slither/detectors/operations/unchecked_send_return_value.py +++ b/slither/detectors/operations/unchecked_send_return_value.py @@ -6,21 +6,22 @@ from slither.detectors.abstract_detector import DetectorClassification from .unused_return_values import UnusedReturnValues from slither.slithir.operations import Send + class UncheckedSend(UnusedReturnValues): """ If the return value of a send is not checked, it might lead to losing ether """ - ARGUMENT = 'unchecked-send' - HELP = 'Unchecked send' + ARGUMENT = "unchecked-send" + HELP = "Unchecked send" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send" - WIKI_TITLE = 'Unchecked Send' - WIKI_DESCRIPTION = 'The return value of a `send` is not checked.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Unchecked Send" + WIKI_DESCRIPTION = "The return value of a `send` is not checked." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract MyConc{ function my_func(address payable dst) public payable{ @@ -30,11 +31,11 @@ contract MyConc{ ``` The return value of `send` is not checked, so if the send fails, the Ether will be locked in the contract. If `send` is used to prevent blocking operations, consider logging the failed `send`. - ''' + """ - WIKI_RECOMMENDATION = 'Ensure that the return value of `send` is checked or logged.' + WIKI_RECOMMENDATION = "Ensure that the return value of `send` is checked or logged." _txt_description = "send calls" def _is_instance(self, ir): - return isinstance(ir, Send) \ No newline at end of file + return isinstance(ir, Send) diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py index 2aa453c9d..a64212b5e 100644 --- a/slither/detectors/operations/unused_return_values.py +++ b/slither/detectors/operations/unused_return_values.py @@ -12,16 +12,18 @@ class UnusedReturnValues(AbstractDetector): If the return value of a function is never used, it's likely to be bug """ - ARGUMENT = 'unused-return' - HELP = 'Unused return values' + ARGUMENT = "unused-return" + HELP = "Unused return values" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return" - WIKI_TITLE = 'Unused return' - WIKI_DESCRIPTION = 'The return value of an external call is not stored in a local or state variable.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Unused return" + WIKI_DESCRIPTION = ( + "The return value of an external call is not stored in a local or state variable." + ) + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract MyConc{ using SafeMath for uint; @@ -30,9 +32,9 @@ contract MyConc{ } } ``` -`MyConc` calls `add` of `SafeMath`, but does not store the result in `a`. As a result, the computation has no effect.''' +`MyConc` calls `add` of `SafeMath`, but does not store the result in `a`. As a result, the computation has no effect.""" - WIKI_RECOMMENDATION = 'Ensure that all the return values of the function calls are used.' + WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used." _txt_description = "external calls" @@ -81,4 +83,3 @@ contract MyConc{ results.append(res) return results - diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py index a0a20f1a7..96922c7a7 100644 --- a/slither/detectors/operations/void_constructor.py +++ b/slither/detectors/operations/void_constructor.py @@ -1,29 +1,27 @@ - from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Nop class VoidConstructor(AbstractDetector): - ARGUMENT = 'void-cst' - HELP = 'Constructor called not implemented' + ARGUMENT = "void-cst" + HELP = "Constructor called not implemented" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor" - WIKI_TITLE = 'Void constructor' - WIKI_DESCRIPTION = 'Detect the call to a constructor that is not implemented' - WIKI_RECOMMENDATION = 'Remove the constructor call.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Void constructor" + WIKI_DESCRIPTION = "Detect the call to a constructor that is not implemented" + WIKI_RECOMMENDATION = "Remove the constructor call." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract A{} contract B is A{ constructor() public A(){} } ``` -When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed.''' - +When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed.""" def _detect(self): """ diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 4164e3d55..eb238ea96 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -26,7 +26,10 @@ def dict_are_equal(d1, d2): return all(set(d1[k]) == set(d2[k]) for k in d1.keys()) -def is_subset(new_info: Dict[Union[Variable, Node], Set[Node]], old_info: Dict[Union[Variable, Node], Set[Node]]): +def is_subset( + new_info: Dict[Union[Variable, Node], Set[Node]], + old_info: Dict[Union[Variable, Node], Set[Node]], +): for k in new_info.keys(): if k not in old_info: return False @@ -36,11 +39,13 @@ def is_subset(new_info: Dict[Union[Variable, Node], Set[Node]], old_info: Dict[U def to_hashable(d: Dict[Node, Set[Node]]): - list_tuple = list(tuple((k, tuple(sorted(values, key=lambda x: x.node_id)))) for k, values in d.items()) + list_tuple = list( + tuple((k, tuple(sorted(values, key=lambda x: x.node_id)))) for k, values in d.items() + ) return tuple(sorted(list_tuple, key=lambda x: x[0].node_id)) -class AbstractState: +class AbstractState: def __init__(self): # send_eth returns the list of calls sending value # calls returns the list of calls that can callback @@ -104,23 +109,36 @@ class AbstractState: def merge_fathers(self, node, skip_father, detector): for father in node.fathers: if detector.KEY in father.context: - self._send_eth = union_dict(self._send_eth, - {key: values for key, values in father.context[detector.KEY].send_eth.items() if - key != skip_father}) - self._calls = union_dict(self._calls, - {key: values for key, values in father.context[detector.KEY].calls.items() if - key != skip_father}) + self._send_eth = union_dict( + self._send_eth, + { + key: values + for key, values in father.context[detector.KEY].send_eth.items() + if key != skip_father + }, + ) + self._calls = union_dict( + self._calls, + { + key: values + for key, values in father.context[detector.KEY].calls.items() + if key != skip_father + }, + ) self._reads = union_dict(self._reads, father.context[detector.KEY].reads) - self._reads_prior_calls = union_dict(self.reads_prior_calls, - father.context[detector.KEY].reads_prior_calls) + self._reads_prior_calls = union_dict( + self.reads_prior_calls, father.context[detector.KEY].reads_prior_calls + ) def analyze_node(self, node, detector): - state_vars_read: Dict[Variable, Set[Node]] = defaultdict(set, - {v: {node} for v in node.state_variables_read}) + state_vars_read: Dict[Variable, Set[Node]] = defaultdict( + set, {v: {node} for v in node.state_variables_read} + ) # All the state variables written - state_vars_written: Dict[Variable, Set[Node]] = defaultdict(set, - {v: {node} for v in node.state_variables_written}) + state_vars_written: Dict[Variable, Set[Node]] = defaultdict( + set, {v: {node} for v in node.state_variables_written} + ) slithir_operations = [] # Add the state variables written in internal calls for internal_call in node.internal_calls: @@ -139,9 +157,11 @@ class AbstractState: for ir in node.irs + slithir_operations: if detector.can_callback(ir): self._calls[node] |= {ir.node} - self._reads_prior_calls[node] = set(self._reads_prior_calls.get(node, set()) | - set(node.context[detector.KEY].reads.keys()) | - set(state_vars_read.keys())) + self._reads_prior_calls[node] = set( + self._reads_prior_calls.get(node, set()) + | set(node.context[detector.KEY].reads.keys()) + | set(state_vars_read.keys()) + ) contains_call = True if detector.can_send_eth(ir): @@ -152,7 +172,6 @@ class AbstractState: self._reads = union_dict(self._reads, state_vars_read) - return contains_call def add(self, fathers): @@ -170,7 +189,7 @@ class AbstractState: class Reentrancy(AbstractDetector): - KEY = 'REENTRANCY' + KEY = "REENTRANCY" # can_callback and can_send_eth are static method # allowing inherited classes to define different behaviors @@ -207,7 +226,10 @@ class Reentrancy(AbstractDetector): This will work only on naive implementation """ - return isinstance(node.expression, UnaryOperation) and node.expression.type == UnaryOperationType.BANG + return ( + isinstance(node.expression, UnaryOperation) + and node.expression.type == UnaryOperationType.BANG + ) def _explore(self, node, visited, skip_father=None): """ diff --git a/slither/detectors/reentrancy/reentrancy_benign.py b/slither/detectors/reentrancy/reentrancy_benign.py index 98da21f1b..05e6dd3f9 100644 --- a/slither/detectors/reentrancy/reentrancy_benign.py +++ b/slither/detectors/reentrancy/reentrancy_benign.py @@ -10,23 +10,25 @@ from typing import List from slither.detectors.abstract_detector import DetectorClassification from .reentrancy import Reentrancy, to_hashable -FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth']) -FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes']) +FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) +FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) class ReentrancyBenign(Reentrancy): - ARGUMENT = 'reentrancy-benign' - HELP = 'Benign reentrancy vulnerabilities' + ARGUMENT = "reentrancy-benign" + HELP = "Benign reentrancy vulnerabilities" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2" + ) - WIKI_TITLE = 'Reentrancy vulnerabilities' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Reentrancy vulnerabilities" + WIKI_DESCRIPTION = """ Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentrancy-no-eth`).''' - WIKI_EXPLOIT_SCENARIO = ''' +Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentrancy-no-eth`).""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity function callme(){ if( ! (msg.sender.call()() ) ){ @@ -36,9 +38,9 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr } ``` -`callme` contains a reentrancy. The reentrancy is benign because it's exploitation would have the same effect as two consecutive calls.''' +`callme` contains a reentrancy. The reentrancy is benign because it's exploitation would have the same effect as two consecutive calls.""" - WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)." STANDARD_JSON = False @@ -55,19 +57,25 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr continue read_then_written = [] for c in node.context[self.KEY].calls: - read_then_written += [v for v in node.context[self.KEY].written - if v in node.context[self.KEY].reads_prior_calls[c]] - not_read_then_written = set([FindingValue(v, - node, - tuple(sorted(nodes, key=lambda x: x.node_id))) - for (v, nodes) - in node.context[self.KEY].written.items() - if v not in read_then_written]) + read_then_written += [ + v + for v in node.context[self.KEY].written + if v in node.context[self.KEY].reads_prior_calls[c] + ] + not_read_then_written = set( + [ + FindingValue(v, node, tuple(sorted(nodes, key=lambda x: x.node_id))) + for (v, nodes) in node.context[self.KEY].written.items() + if v not in read_then_written + ] + ) if not_read_then_written: # calls are ordered - finding_key = FindingKey(function=node.function, - calls=to_hashable(node.context[self.KEY].calls), - send_eth=to_hashable(node.context[self.KEY].send_eth)) + finding_key = FindingKey( + function=node.function, + calls=to_hashable(node.context[self.KEY].calls), + send_eth=to_hashable(node.context[self.KEY].send_eth), + ) result[finding_key] |= not_read_then_written return result @@ -87,27 +95,27 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id) varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id)) - info = ['Reentrancy in ', func, ':\n'] + info = ["Reentrancy in ", func, ":\n"] - info += ['\tExternal calls:\n'] + info += ["\tExternal calls:\n"] for (call_info, calls_list) in calls: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] + info += ["\t\t- ", call_list_info, "\n"] if calls != send_eth and send_eth: - info += ['\tExternal calls sending eth:\n'] + info += ["\tExternal calls sending eth:\n"] for (call_info, calls_list) in send_eth: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] - info += ['\tState variables written after the call(s):\n'] + info += ["\t\t- ", call_list_info, "\n"] + info += ["\tState variables written after the call(s):\n"] for finding_value in varsWritten: - info += ['\t- ', finding_value.node, '\n'] + info += ["\t- ", finding_value.node, "\n"] for other_node in finding_value.nodes: if other_node != finding_value.node: - info += ['\t\t- ', other_node, '\n'] + info += ["\t\t- ", other_node, "\n"] # Create our JSON result res = self.generate_result(info) @@ -117,41 +125,41 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr # Add all underlying calls in the function which are potentially problematic. for (call_info, calls_list) in calls: - res.add(call_info, { - "underlying_type": "external_calls" - }) + res.add(call_info, {"underlying_type": "external_calls"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"}) # # If the calls are not the same ones that send eth, add the eth sending nodes. if calls != send_eth: for (call_info, calls_list) in calls: - res.add(call_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_info, {"underlying_type": "external_calls_sending_eth"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add( + call_list_info, {"underlying_type": "external_calls_sending_eth"} + ) # Add all variables written via nodes which write them. for finding_value in varsWritten: - res.add(finding_value.node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + finding_value.node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) for other_node in finding_value.nodes: if other_node != finding_value.node: - res.add(other_node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + other_node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) # Append our result results.append(res) diff --git a/slither/detectors/reentrancy/reentrancy_eth.py b/slither/detectors/reentrancy/reentrancy_eth.py index ea70987ce..729f55443 100644 --- a/slither/detectors/reentrancy/reentrancy_eth.py +++ b/slither/detectors/reentrancy/reentrancy_eth.py @@ -10,23 +10,25 @@ from typing import List from slither.detectors.abstract_detector import DetectorClassification from .reentrancy import Reentrancy, to_hashable -FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth']) -FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes']) +FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) +FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) class ReentrancyEth(Reentrancy): - ARGUMENT = 'reentrancy-eth' - HELP = 'Reentrancy vulnerabilities (theft of ethers)' + ARGUMENT = "reentrancy-eth" + HELP = "Reentrancy vulnerabilities (theft of ethers)" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities" + ) - WIKI_TITLE = 'Reentrancy vulnerabilities' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Reentrancy vulnerabilities" + WIKI_DESCRIPTION = """ Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)''' - WIKI_EXPLOIT_SCENARIO = ''' +Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity function withdrawBalance(){ // send userBalance[msg.sender] Ether to msg.sender @@ -38,9 +40,9 @@ Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)''' } ``` -Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw more than its initial deposit to the contract.''' +Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw more than its initial deposit to the contract.""" - WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions pattern`](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions pattern`](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)." STANDARD_JSON = False @@ -59,18 +61,23 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m for c in node.context[self.KEY].calls: if c == node: continue - read_then_written |= set([FindingValue(v, - node, - tuple(sorted(nodes, key=lambda x: x.node_id))) - for (v, nodes) - in node.context[self.KEY].written.items() - if v in node.context[self.KEY].reads_prior_calls[c]]) + read_then_written |= set( + [ + FindingValue( + v, node, tuple(sorted(nodes, key=lambda x: x.node_id)) + ) + for (v, nodes) in node.context[self.KEY].written.items() + if v in node.context[self.KEY].reads_prior_calls[c] + ] + ) if read_then_written: # calls are ordered - finding_key = FindingKey(function=node.function, - calls=to_hashable(node.context[self.KEY].calls), - send_eth=to_hashable(node.context[self.KEY].send_eth)) + finding_key = FindingKey( + function=node.function, + calls=to_hashable(node.context[self.KEY].calls), + send_eth=to_hashable(node.context[self.KEY].send_eth), + ) result[finding_key] |= set(read_then_written) return result @@ -91,26 +98,26 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id) varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id)) - info = ['Reentrancy in ', func, ':\n'] - info += ['\tExternal calls:\n'] + info = ["Reentrancy in ", func, ":\n"] + info += ["\tExternal calls:\n"] for (call_info, calls_list) in calls: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] + info += ["\t\t- ", call_list_info, "\n"] if calls != send_eth and send_eth: - info += ['\tExternal calls sending eth:\n'] + info += ["\tExternal calls sending eth:\n"] for (call_info, calls_list) in send_eth: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] - info += ['\tState variables written after the call(s):\n'] + info += ["\t\t- ", call_list_info, "\n"] + info += ["\tState variables written after the call(s):\n"] for finding_value in varsWritten: - info += ['\t- ', finding_value.node, '\n'] + info += ["\t- ", finding_value.node, "\n"] for other_node in finding_value.nodes: if other_node != finding_value.node: - info += ['\t\t- ', other_node, '\n'] + info += ["\t\t- ", other_node, "\n"] # Create our JSON result res = self.generate_result(info) @@ -120,39 +127,39 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m # Add all underlying calls in the function which are potentially problematic. for (call_info, calls_list) in calls: - res.add(call_info, { - "underlying_type": "external_calls" - }) + res.add(call_info, {"underlying_type": "external_calls"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"}) # If the calls are not the same ones that send eth, add the eth sending nodes. if calls != send_eth: for (call_info, calls_list) in send_eth: - res.add(call_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_info, {"underlying_type": "external_calls_sending_eth"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add( + call_list_info, {"underlying_type": "external_calls_sending_eth"} + ) # Add all variables written via nodes which write them. for finding_value in varsWritten: - res.add(finding_value.node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + finding_value.node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) for other_node in finding_value.nodes: if other_node != finding_value.node: - res.add(other_node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + other_node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) # Append our result results.append(res) diff --git a/slither/detectors/reentrancy/reentrancy_events.py b/slither/detectors/reentrancy/reentrancy_events.py index f0f9d0db3..c23f3110d 100644 --- a/slither/detectors/reentrancy/reentrancy_events.py +++ b/slither/detectors/reentrancy/reentrancy_events.py @@ -9,23 +9,25 @@ from collections import namedtuple, defaultdict from slither.detectors.abstract_detector import DetectorClassification from .reentrancy import Reentrancy, to_hashable -FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth']) -FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes']) +FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) +FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) class ReentrancyEvent(Reentrancy): - ARGUMENT = 'reentrancy-events' - HELP = 'Reentrancy vulnerabilities leading to out-of-order Events' + ARGUMENT = "reentrancy-events" + HELP = "Reentrancy vulnerabilities leading to out-of-order Events" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3" + ) - WIKI_TITLE = 'Reentrancy vulnerabilities' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Reentrancy vulnerabilities" + WIKI_DESCRIPTION = """ Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Only report reentrancies leading to out-of-order events.''' - WIKI_EXPLOIT_SCENARIO = ''' +Only report reentrancies leading to out-of-order events.""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity function bug(Called d){ counter += 1; @@ -34,9 +36,9 @@ Only report reentrancies leading to out-of-order events.''' } ``` -If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties.''' +If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties.""" - WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)." STANDARD_JSON = False @@ -53,14 +55,19 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w continue # calls are ordered - finding_key = FindingKey(function=node.function, - calls=to_hashable(node.context[self.KEY].calls), - send_eth=to_hashable(node.context[self.KEY].send_eth)) - finding_vars = set([FindingValue(e, - e.node, - tuple(sorted(nodes, key=lambda x: x.node_id))) - for (e, nodes) - in node.context[self.KEY].events.items()]) + finding_key = FindingKey( + function=node.function, + calls=to_hashable(node.context[self.KEY].calls), + send_eth=to_hashable(node.context[self.KEY].send_eth), + ) + finding_vars = set( + [ + FindingValue( + e, e.node, tuple(sorted(nodes, key=lambda x: x.node_id)) + ) + for (e, nodes) in node.context[self.KEY].events.items() + ] + ) if finding_vars: result[finding_key] |= finding_vars return result @@ -80,26 +87,26 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id) events = sorted(events, key=lambda x: (str(x.variable.name), x.node.node_id)) - info = ['Reentrancy in ', func, ':\n'] - info += ['\tExternal calls:\n'] + info = ["Reentrancy in ", func, ":\n"] + info += ["\tExternal calls:\n"] for (call_info, calls_list) in calls: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] + info += ["\t\t- ", call_list_info, "\n"] if calls != send_eth and send_eth: - info += ['\tExternal calls sending eth:\n'] + info += ["\tExternal calls sending eth:\n"] for (call_info, calls_list) in send_eth: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] - info += ['\tEvent emitted after the call(s):\n'] + info += ["\t\t- ", call_list_info, "\n"] + info += ["\tEvent emitted after the call(s):\n"] for finding_value in events: - info += ['\t- ', finding_value.node, '\n'] + info += ["\t- ", finding_value.node, "\n"] for other_node in finding_value.nodes: if other_node != finding_value.node: - info += ['\t\t- ', other_node, '\n'] + info += ["\t\t- ", other_node, "\n"] # Create our JSON result res = self.generate_result(info) @@ -109,38 +116,28 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w # Add all underlying calls in the function which are potentially problematic. for (call_info, calls_list) in calls: - res.add(call_info, { - "underlying_type": "external_calls" - }) + res.add(call_info, {"underlying_type": "external_calls"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"}) # # If the calls are not the same ones that send eth, add the eth sending nodes. if calls != send_eth: for (call_info, calls_list) in send_eth: - res.add(call_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_info, {"underlying_type": "external_calls_sending_eth"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add( + call_list_info, {"underlying_type": "external_calls_sending_eth"} + ) for finding_value in events: - res.add(finding_value.node, { - "underlying_type": "event" - }) + res.add(finding_value.node, {"underlying_type": "event"}) for other_node in finding_value.nodes: if other_node != finding_value.node: - res.add(other_node, { - "underlying_type": "event" - }) + res.add(other_node, {"underlying_type": "event"}) # Append our result results.append(res) diff --git a/slither/detectors/reentrancy/reentrancy_no_gas.py b/slither/detectors/reentrancy/reentrancy_no_gas.py index ab1a2e9c2..02dda3da2 100644 --- a/slither/detectors/reentrancy/reentrancy_no_gas.py +++ b/slither/detectors/reentrancy/reentrancy_no_gas.py @@ -11,25 +11,27 @@ from slither.detectors.abstract_detector import DetectorClassification from slither.slithir.operations import Send, Transfer, EventCall from .reentrancy import Reentrancy, to_hashable -FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth']) -FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes']) +FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) +FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) class ReentrancyNoGas(Reentrancy): - KEY = 'REENTRANCY_NO_GAS' + KEY = "REENTRANCY_NO_GAS" - ARGUMENT = 'reentrancy-unlimited-gas' - HELP = 'Reentrancy vulnerabilities through send and transfer' + ARGUMENT = "reentrancy-unlimited-gas" + HELP = "Reentrancy vulnerabilities through send and transfer" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4" + ) - WIKI_TITLE = 'Reentrancy vulnerabilities' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Reentrancy vulnerabilities" + WIKI_DESCRIPTION = """ Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Only report reentrancy that is based on `transfer` or `send`.''' - WIKI_EXPLOIT_SCENARIO = ''' +Only report reentrancy that is based on `transfer` or `send`.""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity function callme(){ msg.sender.transfer(balances[msg.sender]): @@ -37,9 +39,9 @@ Only report reentrancy that is based on `transfer` or `send`.''' } ``` -`send` and `transfer` do not protect from reentrancies in case of gas price changes.''' +`send` and `transfer` do not protect from reentrancies in case of gas price changes.""" - WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' + WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)." @staticmethod def can_callback(ir): @@ -64,19 +66,25 @@ Only report reentrancy that is based on `transfer` or `send`.''' continue # calls are ordered - finding_key = FindingKey(function=node.function, - calls=to_hashable(node.context[self.KEY].calls), - send_eth=to_hashable(node.context[self.KEY].send_eth)) - finding_vars = set([FindingValue(v, - node, - tuple(sorted(nodes, key=lambda x: x.node_id))) - for (v, nodes) - in node.context[self.KEY].written.items()]) - finding_vars |= set([FindingValue(e, - e.node, - tuple(sorted(nodes, key=lambda x: x.node_id))) - for (e, nodes) - in node.context[self.KEY].events.items()]) + finding_key = FindingKey( + function=node.function, + calls=to_hashable(node.context[self.KEY].calls), + send_eth=to_hashable(node.context[self.KEY].send_eth), + ) + finding_vars = set( + [ + FindingValue(v, node, tuple(sorted(nodes, key=lambda x: x.node_id))) + for (v, nodes) in node.context[self.KEY].written.items() + ] + ) + finding_vars |= set( + [ + FindingValue( + e, e.node, tuple(sorted(nodes, key=lambda x: x.node_id)) + ) + for (e, nodes) in node.context[self.KEY].events.items() + ] + ) if finding_vars: result[finding_key] |= finding_vars return result @@ -94,43 +102,49 @@ Only report reentrancy that is based on `transfer` or `send`.''' for (func, calls, send_eth), varsWrittenOrEvent in result_sorted: calls = sorted(list(set(calls)), key=lambda x: x[0].node_id) send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id) - info = ['Reentrancy in ', func, ':\n'] + info = ["Reentrancy in ", func, ":\n"] - info += ['\tExternal calls:\n'] + info += ["\tExternal calls:\n"] for (call_info, calls_list) in calls: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] + info += ["\t\t- ", call_list_info, "\n"] if calls != send_eth and send_eth: - info += ['\tExternal calls sending eth:\n'] + info += ["\tExternal calls sending eth:\n"] for (call_info, calls_list) in send_eth: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] + info += ["\t\t- ", call_list_info, "\n"] - varsWritten = [FindingValue(v, node, nodes) for (v, node, nodes) - in varsWrittenOrEvent if isinstance(v, Variable)] + varsWritten = [ + FindingValue(v, node, nodes) + for (v, node, nodes) in varsWrittenOrEvent + if isinstance(v, Variable) + ] varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id)) if varsWritten: - info += ['\tState variables written after the call(s):\n'] + info += ["\tState variables written after the call(s):\n"] for finding_value in varsWritten: - info += ['\t- ', finding_value.node, '\n'] + info += ["\t- ", finding_value.node, "\n"] for other_node in finding_value.nodes: if other_node != finding_value.node: - info += ['\t\t- ', other_node, '\n'] + info += ["\t\t- ", other_node, "\n"] - events = [FindingValue(v, node, nodes) for (v, node, nodes) - in varsWrittenOrEvent if isinstance(v, EventCall)] + events = [ + FindingValue(v, node, nodes) + for (v, node, nodes) in varsWrittenOrEvent + if isinstance(v, EventCall) + ] events = sorted(events, key=lambda x: (x.variable.name, x.node.node_id)) if events: - info += ['\tEvent emitted after the call(s):\n'] + info += ["\tEvent emitted after the call(s):\n"] for finding_value in events: - info += ['\t- ', finding_value.node, '\n'] + info += ["\t- ", finding_value.node, "\n"] for other_node in finding_value.nodes: if other_node != finding_value.node: - info += ['\t\t- ', other_node, '\n'] + info += ["\t\t- ", other_node, "\n"] # Create our JSON result res = self.generate_result(info) @@ -140,50 +154,46 @@ Only report reentrancy that is based on `transfer` or `send`.''' # Add all underlying calls in the function which are potentially problematic. for (call_info, calls_list) in calls: - res.add(call_info, { - "underlying_type": "external_calls" - }) + res.add(call_info, {"underlying_type": "external_calls"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"}) # # If the calls are not the same ones that send eth, add the eth sending nodes. if calls != send_eth: for (call_info, calls_list) in send_eth: - res.add(call_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_info, {"underlying_type": "external_calls_sending_eth"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add( + call_list_info, {"underlying_type": "external_calls_sending_eth"} + ) # Add all variables written via nodes which write them. for finding_value in varsWritten: - res.add(finding_value.node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + finding_value.node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) for other_node in finding_value.nodes: if other_node != finding_value.node: - res.add(other_node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + other_node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) for finding_value in events: - res.add(finding_value.node, { - "underlying_type": "event" - }) + res.add(finding_value.node, {"underlying_type": "event"}) for other_node in finding_value.nodes: if other_node != finding_value.node: - res.add(other_node, { - "underlying_type": "event" - }) + res.add(other_node, {"underlying_type": "event"}) # Append our result results.append(res) diff --git a/slither/detectors/reentrancy/reentrancy_read_before_write.py b/slither/detectors/reentrancy/reentrancy_read_before_write.py index 944ab2b69..e08dcd79e 100644 --- a/slither/detectors/reentrancy/reentrancy_read_before_write.py +++ b/slither/detectors/reentrancy/reentrancy_read_before_write.py @@ -9,25 +9,26 @@ from collections import namedtuple, defaultdict from slither.detectors.abstract_detector import DetectorClassification from .reentrancy import Reentrancy, to_hashable -FindingKey = namedtuple('FindingKey', ['function', 'calls']) -FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes']) - +FindingKey = namedtuple("FindingKey", ["function", "calls"]) +FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) class ReentrancyReadBeforeWritten(Reentrancy): - ARGUMENT = 'reentrancy-no-eth' - HELP = 'Reentrancy vulnerabilities (no theft of ethers)' + ARGUMENT = "reentrancy-no-eth" + HELP = "Reentrancy vulnerabilities (no theft of ethers)" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1" + ) - WIKI_TITLE = 'Reentrancy vulnerabilities' - WIKI_DESCRIPTION = ''' + WIKI_TITLE = "Reentrancy vulnerabilities" + WIKI_DESCRIPTION = """ Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy). -Do not report reentrancies that involve Ether (see `reentrancy-eth`).''' +Do not report reentrancies that involve Ether (see `reentrancy-eth`).""" - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_EXPLOIT_SCENARIO = """ ```solidity function bug(){ require(not_called); @@ -37,8 +38,8 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).''' not_called = False; } ``` -''' - WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).' +""" + WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)." STANDARD_JSON = False @@ -55,18 +56,23 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).''' for c in node.context[self.KEY].calls: if c == node: continue - read_then_written |= set([FindingValue(v, - node, - tuple(sorted(nodes, key=lambda x: x.node_id))) - for (v, nodes) - in node.context[self.KEY].written.items() - if v in node.context[self.KEY].reads_prior_calls[c]]) + read_then_written |= set( + [ + FindingValue( + v, node, tuple(sorted(nodes, key=lambda x: x.node_id)) + ) + for (v, nodes) in node.context[self.KEY].written.items() + if v in node.context[self.KEY].reads_prior_calls[c] + ] + ) # We found a potential re-entrancy bug if read_then_written: # calls are ordered - finding_key = FindingKey(function=node.function, - calls=to_hashable(node.context[self.KEY].calls)) + finding_key = FindingKey( + function=node.function, + calls=to_hashable(node.context[self.KEY].calls), + ) result[finding_key] |= read_then_written return result @@ -84,20 +90,20 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).''' calls = sorted(list(set(calls)), key=lambda x: x[0].node_id) varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id)) - info = ['Reentrancy in ', func, ':\n'] + info = ["Reentrancy in ", func, ":\n"] - info += ['\tExternal calls:\n'] + info += ["\tExternal calls:\n"] for (call_info, calls_list) in calls: - info += ['\t- ', call_info, '\n'] + info += ["\t- ", call_info, "\n"] for call_list_info in calls_list: if call_list_info != call_info: - info += ['\t\t- ', call_list_info, '\n'] - info += '\tState variables written after the call(s):\n' + info += ["\t\t- ", call_list_info, "\n"] + info += "\tState variables written after the call(s):\n" for finding_value in varsWritten: - info += ['\t- ', finding_value.node, '\n'] + info += ["\t- ", finding_value.node, "\n"] for other_node in finding_value.nodes: if other_node != finding_value.node: - info += ['\t\t- ', other_node, '\n'] + info += ["\t\t- ", other_node, "\n"] # Create our JSON result res = self.generate_result(info) @@ -107,27 +113,29 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).''' # Add all underlying calls in the function which are potentially problematic. for (call_info, calls_list) in calls: - res.add(call_info, { - "underlying_type": "external_calls" - }) + res.add(call_info, {"underlying_type": "external_calls"}) for call_list_info in calls_list: if call_list_info != call_info: - res.add(call_list_info, { - "underlying_type": "external_calls_sending_eth" - }) + res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"}) # Add all variables written via nodes which write them. for finding_value in varsWritten: - res.add(finding_value.node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + finding_value.node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) for other_node in finding_value.nodes: if other_node != finding_value.node: - res.add(other_node, { - "underlying_type": "variables_written", - "variable_name": finding_value.variable.name - }) + res.add( + other_node, + { + "underlying_type": "variables_written", + "variable_name": finding_value.variable.name, + }, + ) # Append our result results.append(res) diff --git a/slither/detectors/shadowing/abstract.py b/slither/detectors/shadowing/abstract.py index d882a09a3..e8fc20e52 100644 --- a/slither/detectors/shadowing/abstract.py +++ b/slither/detectors/shadowing/abstract.py @@ -11,17 +11,16 @@ class ShadowingAbstractDetection(AbstractDetector): Shadowing detection """ - ARGUMENT = 'shadowing-abstract' - HELP = 'State variables shadowing from abstract contracts' + ARGUMENT = "shadowing-abstract" + HELP = "State variables shadowing from abstract contracts" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts" - - WIKI_TITLE = 'State variable shadowing from abstract contracts' - WIKI_DESCRIPTION = 'Detection of state variables shadowed from abstract contracts.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "State variable shadowing from abstract contracts" + WIKI_DESCRIPTION = "Detection of state variables shadowed from abstract contracts." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract BaseContract{ address owner; @@ -31,10 +30,9 @@ contract DerivedContract is BaseContract{ address owner; } ``` -`owner` of `BaseContract` is shadowed in `DerivedContract`.''' - - WIKI_RECOMMENDATION = 'Remove the state variable shadowing.' +`owner` of `BaseContract` is shadowed in `DerivedContract`.""" + WIKI_RECOMMENDATION = "Remove the state variable shadowing." def detect_shadowing(self, contract): ret = [] @@ -49,7 +47,6 @@ contract DerivedContract is BaseContract{ ret.append([var] + shadow) return ret - def _detect(self): """ Detect shadowing @@ -65,7 +62,7 @@ contract DerivedContract is BaseContract{ for all_variables in shadowing: shadow = all_variables[0] variables = all_variables[1:] - info = [shadow, ' shadows:\n'] + info = [shadow, " shadows:\n"] for var in variables: info += ["\t- ", var, "\n"] diff --git a/slither/detectors/shadowing/builtin_symbols.py b/slither/detectors/shadowing/builtin_symbols.py index 18c010ba4..e6a06dc1e 100644 --- a/slither/detectors/shadowing/builtin_symbols.py +++ b/slither/detectors/shadowing/builtin_symbols.py @@ -10,17 +10,16 @@ class BuiltinSymbolShadowing(AbstractDetector): Built-in symbol shadowing """ - ARGUMENT = 'shadowing-builtin' - HELP = 'Built-in symbol shadowing' + ARGUMENT = "shadowing-builtin" + HELP = "Built-in symbol shadowing" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing" - - WIKI_TITLE = 'Builtin Symbol Shadowing' - WIKI_DESCRIPTION = 'Detection of shadowing built-in symbols using local variables, state variables, functions, modifiers, or events.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Builtin Symbol Shadowing" + WIKI_DESCRIPTION = "Detection of shadowing built-in symbols using local variables, state variables, functions, modifiers, or events." + WIKI_EXPLOIT_SCENARIO = """ ```solidity pragma solidity ^0.4.24; @@ -36,9 +35,9 @@ contract Bug { } } ``` -`now` is defined as a state variable, and shadows with the built-in symbol `now`. The function `assert` overshadows the built-in `assert` function. Any use of either of these built-in symbols may lead to unexpected results.''' +`now` is defined as a state variable, and shadows with the built-in symbol `now`. The function `assert` overshadows the built-in `assert` function. Any use of either of these built-in symbols may lead to unexpected results.""" - WIKI_RECOMMENDATION = 'Rename the local variables, state variables, functions, modifiers, and events that shadow a builtin symbol.' + WIKI_RECOMMENDATION = "Rename the local variables, state variables, functions, modifiers, and events that shadow a builtin symbol." SHADOWING_FUNCTION = "function" SHADOWING_MODIFIER = "modifier" @@ -47,17 +46,70 @@ contract Bug { SHADOWING_EVENT = "event" # Reserved keywords reference: https://solidity.readthedocs.io/en/v0.5.2/units-and-global-variables.html - BUILTIN_SYMBOLS = ["assert", "require", "revert", "block", "blockhash", - "gasleft", "msg", "now", "tx", "this", "addmod", "mulmod", - "keccak256", "sha256", "sha3", "ripemd160", "ecrecover", - "selfdestruct", "suicide", "abi", "fallback", "receive"] + BUILTIN_SYMBOLS = [ + "assert", + "require", + "revert", + "block", + "blockhash", + "gasleft", + "msg", + "now", + "tx", + "this", + "addmod", + "mulmod", + "keccak256", + "sha256", + "sha3", + "ripemd160", + "ecrecover", + "selfdestruct", + "suicide", + "abi", + "fallback", + "receive", + ] # https://solidity.readthedocs.io/en/v0.5.2/miscellaneous.html#reserved-keywords - RESERVED_KEYWORDS = ['abstract', 'after', 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', - 'default', 'define', 'final', 'immutable', 'implements', 'in', 'inline', - 'let', 'macro', 'match', 'mutable', 'null', 'of', 'override', 'partial', - 'promise', 'reference', 'relocatable', 'sealed', 'sizeof', 'static', - 'supports', 'switch', 'try', 'type', 'typedef', 'typeof', 'unchecked'] + RESERVED_KEYWORDS = [ + "abstract", + "after", + "alias", + "apply", + "auto", + "case", + "catch", + "copyof", + "default", + "define", + "final", + "immutable", + "implements", + "in", + "inline", + "let", + "macro", + "match", + "mutable", + "null", + "of", + "override", + "partial", + "promise", + "reference", + "relocatable", + "sealed", + "sizeof", + "static", + "supports", + "switch", + "try", + "type", + "typedef", + "typeof", + "unchecked", + ] def is_builtin_symbol(self, word): """ Detects if a given word is a built-in symbol. diff --git a/slither/detectors/shadowing/local.py b/slither/detectors/shadowing/local.py index e1ec20870..9323c4cf4 100644 --- a/slither/detectors/shadowing/local.py +++ b/slither/detectors/shadowing/local.py @@ -10,16 +10,16 @@ class LocalShadowing(AbstractDetector): Local variable shadowing """ - ARGUMENT = 'shadowing-local' - HELP = 'Local variables shadowing' + ARGUMENT = "shadowing-local" + HELP = "Local variables shadowing" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing" - WIKI_TITLE = 'Local variable shadowing' - WIKI_DESCRIPTION = 'Detection of shadowing using local variables.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Local variable shadowing" + WIKI_DESCRIPTION = "Detection of shadowing using local variables." + WIKI_EXPLOIT_SCENARIO = """ ```solidity pragma solidity ^0.4.24; @@ -38,9 +38,9 @@ contract Bug { } } ``` -`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` in `sensitive_function` might be incorrect.''' +`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` in `sensitive_function` might be incorrect.""" - WIKI_RECOMMENDATION = 'Rename the local variables that shadow another component.' + WIKI_RECOMMENDATION = "Rename the local variables that shadow another component." OVERSHADOWED_FUNCTION = "function" OVERSHADOWED_MODIFIER = "modifier" @@ -80,7 +80,9 @@ contract Bug { # Check state variables for scope_state_variable in scope_contract.state_variables_declared: if variable.name == scope_state_variable.name: - overshadowed.append((self.OVERSHADOWED_STATE_VARIABLE, scope_state_variable)) + overshadowed.append( + (self.OVERSHADOWED_STATE_VARIABLE, scope_state_variable) + ) # If we have found any overshadowed objects, we'll want to add it to our result list. if overshadowed: @@ -104,7 +106,7 @@ contract Bug { for shadow in shadows: local_variable = shadow[0] overshadowed = shadow[1] - info = [local_variable, ' shadows:\n'] + info = [local_variable, " shadows:\n"] for overshadowed_entry in overshadowed: info += ["\t- ", overshadowed_entry[1], f" ({overshadowed_entry[0]})\n"] diff --git a/slither/detectors/shadowing/state.py b/slither/detectors/shadowing/state.py index 81421cd2d..ea56169a1 100644 --- a/slither/detectors/shadowing/state.py +++ b/slither/detectors/shadowing/state.py @@ -10,16 +10,16 @@ class StateShadowing(AbstractDetector): Shadowing of state variable """ - ARGUMENT = 'shadowing-state' - HELP = 'State variables shadowing' + ARGUMENT = "shadowing-state" + HELP = "State variables shadowing" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing" - WIKI_TITLE = 'State variable shadowing' - WIKI_DESCRIPTION = 'Detection of state variables shadowed.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "State variable shadowing" + WIKI_DESCRIPTION = "Detection of state variables shadowed." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract BaseContract{ address owner; @@ -43,10 +43,9 @@ contract DerivedContract is BaseContract{ } } ``` -`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work.''' - - WIKI_RECOMMENDATION = 'Remove the state variable shadowing.' +`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work.""" + WIKI_RECOMMENDATION = "Remove the state variable shadowing." def detect_shadowing(self, contract): ret = [] @@ -76,12 +75,11 @@ contract DerivedContract is BaseContract{ for all_variables in shadowing: shadow = all_variables[0] variables = all_variables[1:] - info = [shadow, ' shadows:\n'] + info = [shadow, " shadows:\n"] for var in variables: info += ["\t- ", var, "\n"] res = self.generate_result(info) results.append(res) - return results diff --git a/slither/detectors/slither/name_reused.py b/slither/detectors/slither/name_reused.py index 705d22927..9f0b1c3c3 100644 --- a/slither/detectors/slither/name_reused.py +++ b/slither/detectors/slither/name_reused.py @@ -1,7 +1,6 @@ from collections import defaultdict -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification def _find_missing_inheritance(slither): @@ -26,22 +25,22 @@ def _find_missing_inheritance(slither): class NameReused(AbstractDetector): - ARGUMENT = 'name-reused' + ARGUMENT = "name-reused" HELP = "Contract's name reused" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused" - WIKI_TITLE = 'Name reused' - WIKI_DESCRIPTION = '''If a codebase has two contracts the similar names, the compilation artifacts -will not contain one of the contracts with the duplicate name.''' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Name reused" + WIKI_DESCRIPTION = """If a codebase has two contracts the similar names, the compilation artifacts +will not contain one of the contracts with the duplicate name.""" + WIKI_EXPLOIT_SCENARIO = """ Bob's `truffle` codebase has two contracts named `ERC20`. When `truffle compile` runs, only one of the two contracts will generate artifacts in `build/contracts`. As a result, the second contract cannot be analyzed. -''' - WIKI_RECOMMENDATION = 'Rename the contract.' +""" + WIKI_RECOMMENDATION = "Rename the contract." def _detect(self): results = [] @@ -49,8 +48,9 @@ As a result, the second contract cannot be analyzed. names_reused = self.slither.contract_name_collisions # First show the contracts that we know are missing - incorrectly_constructed = [contract for contract in self.contracts - if contract.is_incorrectly_constructed] + incorrectly_constructed = [ + contract for contract in self.contracts if contract.is_incorrectly_constructed + ] inheritance_corrupted = defaultdict(list) for contract in incorrectly_constructed: @@ -58,17 +58,17 @@ As a result, the second contract cannot be analyzed. inheritance_corrupted[father.name].append(contract) for contract_name, files in names_reused.items(): - info = [contract_name, ' is re-used:\n'] + info = [contract_name, " is re-used:\n"] for file in files: if file is None: - info += ['\t- In an file not found, most likely in\n'] + info += ["\t- In an file not found, most likely in\n"] else: - info += ['\t- ', file, '\n'] + info += ["\t- ", file, "\n"] if contract_name in inheritance_corrupted: - info += ['\tAs a result, the inherited contracts are not correctly analyzed:\n'] + info += ["\tAs a result, the inherited contracts are not correctly analyzed:\n"] for corrupted in inheritance_corrupted[contract_name]: - info += ["\t\t- ", corrupted, '\n'] + info += ["\t\t- ", corrupted, "\n"] res = self.generate_result(info) results.append(res) @@ -77,18 +77,18 @@ As a result, the second contract cannot be analyzed. most_base_with_missing_inheritance = _find_missing_inheritance(self.slither) for b in most_base_with_missing_inheritance: - info = [b, ' inherits from a contract for which the name is reused.\n'] + info = [b, " inherits from a contract for which the name is reused.\n"] if b.inheritance: - info += ['\t- Slither could not determine which contract has a duplicate name:\n'] + info += ["\t- Slither could not determine which contract has a duplicate name:\n"] for inheritance in b.inheritance: - info += ['\t\t-', inheritance, '\n'] - info += ['\t- Check if:\n'] - info += ['\t\t- A inherited contract is missing from this list,\n'] - info += ['\t\t- The contract are imported from the correct files.\n'] + info += ["\t\t-", inheritance, "\n"] + info += ["\t- Check if:\n"] + info += ["\t\t- A inherited contract is missing from this list,\n"] + info += ["\t\t- The contract are imported from the correct files.\n"] if b.derived_contracts: - info += [f'\t- This issue impacts the contracts inheriting from {b.name}:\n'] + info += [f"\t- This issue impacts the contracts inheriting from {b.name}:\n"] for derived in b.derived_contracts: - info += ['\t\t-', derived, '\n'] + info += ["\t\t-", derived, "\n"] res = self.generate_result(info) results.append(res) return results diff --git a/slither/detectors/source/rtlo.py b/slither/detectors/source/rtlo.py index 41ab7dc3e..56d66cfa4 100644 --- a/slither/detectors/source/rtlo.py +++ b/slither/detectors/source/rtlo.py @@ -7,15 +7,15 @@ class RightToLeftOverride(AbstractDetector): Detect the usage of a Right-To-Left-Override (U+202E) character """ - ARGUMENT = 'rtlo' - HELP = 'Right-To-Left-Override control character is used' + ARGUMENT = "rtlo" + HELP = "Right-To-Left-Override control character is used" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character' - WIKI_TITLE = 'Right-to-Left-Override character' - WIKI_DESCRIPTION = 'An attacker can manipulate the logic of the contract by using a right-to-left-override character (`U+202E)`.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character" + WIKI_TITLE = "Right-to-Left-Override character" + WIKI_DESCRIPTION = "An attacker can manipulate the logic of the contract by using a right-to-left-override character (`U+202E)`." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Token { @@ -42,19 +42,19 @@ contract Token `Token` uses the right-to-left-override character when calling `_withdraw`. As a result, the fee is incorrectly sent to `msg.sender`, and the token balance is sent to the owner. -''' - WIKI_RECOMMENDATION = 'Special control characters must not be allowed.' +""" + WIKI_RECOMMENDATION = "Special control characters must not be allowed." - RTLO_CHARACTER_ENCODED = "\u202e".encode('utf-8') + RTLO_CHARACTER_ENCODED = "\u202e".encode("utf-8") STANDARD_JSON = False def _detect(self): results = [] - pattern = re.compile(".*\u202e.*".encode('utf-8')) + pattern = re.compile(".*\u202e.*".encode("utf-8")) for filename, source in self.slither.source_code.items(): # Attempt to find all RTLO characters in this source file. - original_source_encoded = source.encode('utf-8') + original_source_encoded = source.encode("utf-8") start_index = 0 # Keep searching all file contents for the character. @@ -68,7 +68,7 @@ contract Token else: # We found another instance of the character, define our output idx = start_index + result_index - + relative = self.slither.crytic_compile.filename_lookup(filename).relative info = f"{relative} contains a unicode right-to-left-override character at byte offset {idx}:\n" @@ -76,9 +76,11 @@ contract Token info += f"\t- {pattern.findall(source_encoded)[0]}\n" res = self.generate_result(info) - res.add_other("rtlo-character", - (filename, idx, len(self.RTLO_CHARACTER_ENCODED)), - self.slither) + res.add_other( + "rtlo-character", + (filename, idx, len(self.RTLO_CHARACTER_ENCODED)), + self.slither, + ) results.append(res) # Advance the start index for the next iteration diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 032787d07..a16634d80 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -11,17 +11,16 @@ class Assembly(AbstractDetector): Detect usage of inline assembly """ - ARGUMENT = 'assembly' - HELP = 'Assembly usage' + ARGUMENT = "assembly" + HELP = "Assembly usage" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage" - - WIKI_TITLE = 'Assembly usage' - WIKI_DESCRIPTION = 'The use of assembly is error-prone and should be avoided.' - WIKI_RECOMMENDATION = 'Do not use `evm` assembly.' + WIKI_TITLE = "Assembly usage" + WIKI_DESCRIPTION = "The use of assembly is error-prone and should be avoided." + WIKI_RECOMMENDATION = "Do not use `evm` assembly." @staticmethod def _contains_inline_assembly_use(node): @@ -38,8 +37,7 @@ class Assembly(AbstractDetector): if f.contract_declarer != contract: continue nodes = f.nodes - assembly_nodes = [n for n in nodes if - self._contains_inline_assembly_use(n)] + assembly_nodes = [n for n in nodes if self._contains_inline_assembly_use(n)] if assembly_nodes: ret.append((f, assembly_nodes)) return ret diff --git a/slither/detectors/statements/boolean_constant_equality.py b/slither/detectors/statements/boolean_constant_equality.py index 8788325e8..d82573518 100644 --- a/slither/detectors/statements/boolean_constant_equality.py +++ b/slither/detectors/statements/boolean_constant_equality.py @@ -12,16 +12,16 @@ class BooleanEquality(AbstractDetector): Boolean constant misuse """ - ARGUMENT = 'boolean-equal' - HELP = 'Comparison to boolean constant' + ARGUMENT = "boolean-equal" + HELP = "Comparison to boolean constant" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality" - WIKI_TITLE = 'Boolean equality' - WIKI_DESCRIPTION = '''Detects the comparison to boolean constants.''' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Boolean equality" + WIKI_DESCRIPTION = """Detects the comparison to boolean constants.""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract A { function f(bool x) public { @@ -33,9 +33,9 @@ contract A { } } ``` -Boolean constants can be used directly and do not need to be compare to `true` or `false`.''' +Boolean constants can be used directly and do not need to be compare to `true` or `false`.""" - WIKI_RECOMMENDATION = '''Remove the equality to the boolean constant.''' + WIKI_RECOMMENDATION = """Remove the equality to the boolean constant.""" @staticmethod def _detect_boolean_equality(contract): diff --git a/slither/detectors/statements/boolean_constant_misuse.py b/slither/detectors/statements/boolean_constant_misuse.py index 56a9af1af..c4e574a00 100644 --- a/slither/detectors/statements/boolean_constant_misuse.py +++ b/slither/detectors/statements/boolean_constant_misuse.py @@ -4,7 +4,15 @@ Module detecting misuse of Boolean constants from slither.core.cfg.node import NodeType from slither.core.solidity_types import ElementaryType from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations import Assignment, Call, Return, InitArray, Binary, BinaryType, Condition +from slither.slithir.operations import ( + Assignment, + Call, + Return, + InitArray, + Binary, + BinaryType, + Condition, +) from slither.slithir.variables import Constant @@ -13,16 +21,18 @@ class BooleanConstantMisuse(AbstractDetector): Boolean constant misuse """ - ARGUMENT = 'boolean-cst' - HELP = 'Misuse of Boolean constant' + ARGUMENT = "boolean-cst" + HELP = "Misuse of Boolean constant" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant" + ) - WIKI_TITLE = 'Misuse of a Boolean constant' - WIKI_DESCRIPTION = '''Detects the misuse of a Boolean constant.''' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Misuse of a Boolean constant" + WIKI_DESCRIPTION = """Detects the misuse of a Boolean constant.""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract A { function f(uint x) public { @@ -41,9 +51,9 @@ contract A { } ``` Boolean constants in code have only a few legitimate uses. -Other uses (in complex expressions, as conditionals) indicate either an error or, most likely, the persistence of faulty code.''' +Other uses (in complex expressions, as conditionals) indicate either an error or, most likely, the persistence of faulty code.""" - WIKI_RECOMMENDATION = '''Verify and simplify the condition.''' + WIKI_RECOMMENDATION = """Verify and simplify the condition.""" @staticmethod def _detect_boolean_constant_misuses(contract): @@ -68,7 +78,9 @@ Other uses (in complex expressions, as conditionals) indicate either an error or if node.irs: if len(node.irs) == 1: ir = node.irs[0] - if isinstance(ir, Condition) and ir.value == Constant('True', ElementaryType('bool')): + if isinstance(ir, Condition) and ir.value == Constant( + "True", ElementaryType("bool") + ): continue for ir in node.irs: @@ -103,5 +115,5 @@ Other uses (in complex expressions, as conditionals) indicate either an error or res = self.generate_result(info) results.append(res) - + return results diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index 53df5b7e6..bcc8e15f5 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -1,27 +1,24 @@ """ """ from slither.core.cfg.node import NodeType -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (HighLevelCall, LibraryCall, - LowLevelCall, Send, Transfer) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import HighLevelCall, LibraryCall, LowLevelCall, Send, Transfer class MultipleCallsInLoop(AbstractDetector): """ """ - ARGUMENT = 'calls-loop' - HELP = 'Multiple calls in a loop' + ARGUMENT = "calls-loop" + HELP = "Multiple calls in a loop" IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop" - - WIKI_TITLE = 'Calls inside a loop' - WIKI_DESCRIPTION = 'Calls inside a loop might lead to a denial-of-service attack.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Calls inside a loop" + WIKI_DESCRIPTION = "Calls inside a loop might lead to a denial-of-service attack." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract CallsInLoop{ @@ -39,9 +36,9 @@ contract CallsInLoop{ } ``` -If one of the destinations has a fallback function that reverts, `bad` will always revert.''' +If one of the destinations has a fallback function that reverts, `bad` will always revert.""" - WIKI_RECOMMENDATION = 'Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls.' + WIKI_RECOMMENDATION = "Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls." @staticmethod def call_in_loop(node, in_loop, visited, ret): @@ -57,10 +54,7 @@ If one of the destinations has a fallback function that reverts, `bad` will alwa if in_loop: for ir in node.irs: - if isinstance(ir, (LowLevelCall, - HighLevelCall, - Send, - Transfer)): + if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)): if isinstance(ir, LibraryCall): continue ret.append(node) @@ -73,8 +67,7 @@ If one of the destinations has a fallback function that reverts, `bad` will alwa ret = [] for f in contract.functions + contract.modifiers: if f.contract_declarer == contract and f.is_implemented: - MultipleCallsInLoop.call_in_loop(f.entry_point, - False, [], ret) + MultipleCallsInLoop.call_in_loop(f.entry_point, False, [], ret) return ret diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py index 08751cc7e..784301c8c 100644 --- a/slither/detectors/statements/controlled_delegatecall.py +++ b/slither/detectors/statements/controlled_delegatecall.py @@ -7,17 +7,16 @@ class ControlledDelegateCall(AbstractDetector): """ """ - ARGUMENT = 'controlled-delegatecall' - HELP = 'Controlled delegatecall destination' + ARGUMENT = "controlled-delegatecall" + HELP = "Controlled delegatecall destination" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall" - - WIKI_TITLE = 'Controlled Delegatecall' - WIKI_DESCRIPTION = '`Delegatecall` or `callcode` to an address controlled by the user.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Controlled Delegatecall" + WIKI_DESCRIPTION = "`Delegatecall` or `callcode` to an address controlled by the user." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Delegatecall{ function delegate(address to, bytes data){ @@ -25,17 +24,19 @@ contract Delegatecall{ } } ``` -Bob calls `delegate` and delegates the execution to his malicious contract. As a result, Bob withdraws the funds of the contract and destructs it.''' +Bob calls `delegate` and delegates the execution to his malicious contract. As a result, Bob withdraws the funds of the contract and destructs it.""" - WIKI_RECOMMENDATION = 'Avoid using `delegatecall`. Use only trusted destinations.' + WIKI_RECOMMENDATION = "Avoid using `delegatecall`. Use only trusted destinations." def controlled_delegatecall(self, function): ret = [] for node in function.nodes: for ir in node.irs: - if isinstance(ir, LowLevelCall) and ir.function_name in ['delegatecall', 'callcode']: - if is_tainted(ir.destination, - function.contract): + if isinstance(ir, LowLevelCall) and ir.function_name in [ + "delegatecall", + "callcode", + ]: + if is_tainted(ir.destination, function.contract): ret.append(node) return ret @@ -50,10 +51,10 @@ Bob calls `delegate` and delegates the execution to his malicious contract. As a continue nodes = self.controlled_delegatecall(f) if nodes: - func_info = [f, ' uses delegatecall to a input-controlled function id\n'] + func_info = [f, " uses delegatecall to a input-controlled function id\n"] for node in nodes: - node_info = func_info + ['\t- ', node,'\n'] + node_info = func_info + ["\t- ", node, "\n"] res = self.generate_result(node_info) results.append(res) diff --git a/slither/detectors/statements/deprecated_calls.py b/slither/detectors/statements/deprecated_calls.py index ade436844..a12d3403d 100644 --- a/slither/detectors/statements/deprecated_calls.py +++ b/slither/detectors/statements/deprecated_calls.py @@ -15,16 +15,16 @@ class DeprecatedStandards(AbstractDetector): Use of Deprecated Standards """ - ARGUMENT = 'deprecated-standards' - HELP = 'Deprecated Solidity Standards' + ARGUMENT = "deprecated-standards" + HELP = "Deprecated Solidity Standards" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards" - WIKI_TITLE = 'Deprecated standards' - WIKI_DESCRIPTION = 'Detect the usage of deprecated standards.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Deprecated standards" + WIKI_DESCRIPTION = "Detect the usage of deprecated standards." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract ContractWithDeprecatedReferences { // Deprecated: Change block.blockhash() -> blockhash() @@ -51,15 +51,19 @@ contract ContractWithDeprecatedReferences { suicide(address(0)); } } -```''' +```""" - WIKI_RECOMMENDATION = 'Replace all uses of deprecated symbols.' + WIKI_RECOMMENDATION = "Replace all uses of deprecated symbols." # The format for the following deprecated lists is [(detecting_signature, original_text, recommended_text)] - DEPRECATED_SOLIDITY_VARIABLE = [("block.blockhash", "block.blockhash()", "blockhash()"), - ("msg.gas", "msg.gas", "gasleft()")] - DEPRECATED_SOLIDITY_FUNCTIONS = [("suicide(address)", "suicide()", "selfdestruct()"), - ("sha3()", "sha3()", "keccak256()")] + DEPRECATED_SOLIDITY_VARIABLE = [ + ("block.blockhash", "block.blockhash()", "blockhash()"), + ("msg.gas", "msg.gas", "gasleft()"), + ] + DEPRECATED_SOLIDITY_FUNCTIONS = [ + ("suicide(address)", "suicide()", "selfdestruct()"), + ("sha3()", "sha3()", "keccak256()"), + ] DEPRECATED_NODE_TYPES = [(NodeType.THROW, "throw", "revert()")] DEPRECATED_LOW_LEVEL_CALLS = [("callcode", "callcode", "delegatecall")] @@ -113,7 +117,9 @@ contract ContractWithDeprecatedReferences { for state_variable in contract.state_variables_declared: if state_variable.expression: - deprecated_results = self.detect_deprecation_in_expression(state_variable.expression) + deprecated_results = self.detect_deprecation_in_expression( + state_variable.expression + ) if deprecated_results: results.append((state_variable, deprecated_results)) @@ -152,10 +158,12 @@ contract ContractWithDeprecatedReferences { for deprecated_reference in deprecated_references: source_object = deprecated_reference[0] deprecated_entries = deprecated_reference[1] - info = ['Deprecated standard detected ', source_object, ':\n'] + info = ["Deprecated standard detected ", source_object, ":\n"] for (dep_id, original_desc, recommended_disc) in deprecated_entries: - info += [f"\t- Usage of \"{original_desc}\" should be replaced with \"{recommended_disc}\"\n"] + info += [ + f'\t- Usage of "{original_desc}" should be replaced with "{recommended_disc}"\n' + ] res = self.generate_result(info) results.append(res) diff --git a/slither/detectors/statements/divide_before_multiply.py b/slither/detectors/statements/divide_before_multiply.py index 6fff9ed4c..25ced7972 100644 --- a/slither/detectors/statements/divide_before_multiply.py +++ b/slither/detectors/statements/divide_before_multiply.py @@ -13,7 +13,10 @@ def is_division(ir): return True if isinstance(ir, LibraryCall): - if ir.function.name.lower() in ['div', 'safediv', ]: + if ir.function.name.lower() in [ + "div", + "safediv", + ]: if len(ir.arguments) == 2: if ir.lvalue: return True @@ -26,7 +29,10 @@ def is_multiplication(ir): return True if isinstance(ir, LibraryCall): - if ir.function.name.lower() in ['mul', 'safemul', ]: + if ir.function.name.lower() in [ + "mul", + "safemul", + ]: if len(ir.arguments) == 2: if ir.lvalue: return True @@ -49,17 +55,17 @@ class DivideBeforeMultiply(AbstractDetector): Divide before multiply """ - ARGUMENT = 'divide-before-multiply' - HELP = 'Imprecise arithmetic operations order' + ARGUMENT = "divide-before-multiply" + HELP = "Imprecise arithmetic operations order" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply" - WIKI_TITLE = 'Divide before multiply' - WIKI_DESCRIPTION = '''Solidity only supports integers, so division will often truncate; performing a multiply before a divison can sometimes avoid loss of precision.''' - WIKI_DESCRIPTION = '''Solidity integer division might truncate. As a result, performing multiplication before divison might reduce precision.''' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Divide before multiply" + WIKI_DESCRIPTION = """Solidity only supports integers, so division will often truncate; performing a multiply before a divison can sometimes avoid loss of precision.""" + WIKI_DESCRIPTION = """Solidity integer division might truncate. As a result, performing multiplication before divison might reduce precision.""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract A { function f(uint n) public { @@ -69,9 +75,9 @@ contract A { ``` If `n` is greater than `oldSupply`, `coins` will be zero. For example, with `oldSupply = 5; n = 10, interest = 2`, coins will be zero. If `(oldSupply * interest / n)` was used, `coins` would have been `1`. -In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous.''' +In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous.""" - WIKI_RECOMMENDATION = '''Consider ordering multiplication before division.''' + WIKI_RECOMMENDATION = """Consider ordering multiplication before division.""" def _explore(self, node, explored, f_results, divisions): if node in explored: diff --git a/slither/detectors/statements/incorrect_strict_equality.py b/slither/detectors/statements/incorrect_strict_equality.py index bd970ca8c..d4300534f 100644 --- a/slither/detectors/statements/incorrect_strict_equality.py +++ b/slither/detectors/statements/incorrect_strict_equality.py @@ -5,10 +5,8 @@ from slither.analyses.data_dependency.data_dependency import is_dependent_ssa from slither.core.declarations import Function -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (Assignment, Balance, Binary, BinaryType, - HighLevelCall) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import Assignment, Balance, Binary, BinaryType, HighLevelCall from slither.core.solidity_types import MappingType, ElementaryType @@ -17,16 +15,18 @@ from slither.core.declarations.solidity_variables import SolidityVariable, Solid class IncorrectStrictEquality(AbstractDetector): - ARGUMENT = 'incorrect-equality' - HELP = 'Dangerous strict equalities' + ARGUMENT = "incorrect-equality" + HELP = "Dangerous strict equalities" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities" + ) - WIKI_TITLE = 'Dangerous strict equalities' - WIKI_DESCRIPTION = 'Use of strict equalities that can be easily manipulated by an attacker.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Dangerous strict equalities" + WIKI_DESCRIPTION = "Use of strict equalities that can be easily manipulated by an attacker." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Crowdsale{ function fund_reached() public returns(bool){ @@ -34,13 +34,17 @@ contract Crowdsale{ } ``` `Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. -`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends.''' +`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends.""" - WIKI_RECOMMENDATION = '''Don't use strict equality to determine if an account has enough Ether or tokens.''' + WIKI_RECOMMENDATION = ( + """Don't use strict equality to determine if an account has enough Ether or tokens.""" + ) - sources_taint = [SolidityVariable('now'), - SolidityVariableComposed('block.number'), - SolidityVariableComposed('block.timestamp')] + sources_taint = [ + SolidityVariable("now"), + SolidityVariableComposed("block.number"), + SolidityVariableComposed("block.timestamp"), + ] @staticmethod def is_direct_comparison(ir): @@ -48,7 +52,13 @@ contract Crowdsale{ @staticmethod def is_any_tainted(variables, taints, function): - return any([is_dependent_ssa(var, taint, function.contract) for var in variables for taint in taints]) + return any( + [ + is_dependent_ssa(var, taint, function.contract) + for var in variables + for taint in taints + ] + ) def taint_balance_equalities(self, functions): taints = [] @@ -58,16 +68,20 @@ contract Crowdsale{ if isinstance(ir, Balance): taints.append(ir.lvalue) if isinstance(ir, HighLevelCall): - #print(ir.function.full_name) - if isinstance(ir.function, Function) and\ - ir.function.full_name == 'balanceOf(address)': - taints.append(ir.lvalue) - if isinstance(ir.function, StateVariable) and\ - isinstance(ir.function.type, MappingType) and\ - ir.function.name == 'balanceOf' and\ - ir.function.type.type_from == ElementaryType('address') and\ - ir.function.type.type_to == ElementaryType('uint256'): - taints.append(ir.lvalue) + # print(ir.function.full_name) + if ( + isinstance(ir.function, Function) + and ir.function.full_name == "balanceOf(address)" + ): + taints.append(ir.lvalue) + if ( + isinstance(ir.function, StateVariable) + and isinstance(ir.function.type, MappingType) + and ir.function.name == "balanceOf" + and ir.function.type.type_from == ElementaryType("address") + and ir.function.type.type_to == ElementaryType("uint256") + ): + taints.append(ir.lvalue) if isinstance(ir, Assignment): if ir.rvalue in self.sources_taint: taints.append(ir.lvalue) @@ -109,7 +123,7 @@ contract Crowdsale{ ret = self.detect_strict_equality(c) # sort ret to get deterministic results - ret = sorted(list(ret.items()), key=lambda x:x[0].name) + ret = sorted(list(ret.items()), key=lambda x: x[0].name) for f, nodes in ret: func_info = [f, " uses a dangerous strict equality:\n"] diff --git a/slither/detectors/statements/too_many_digits.py b/slither/detectors/statements/too_many_digits.py index 1201815e8..ec0d557dc 100644 --- a/slither/detectors/statements/too_many_digits.py +++ b/slither/detectors/statements/too_many_digits.py @@ -11,17 +11,17 @@ class TooManyDigits(AbstractDetector): Detect numbers with too many digits """ - ARGUMENT = 'too-many-digits' - HELP = 'Conformance to numeric notation best practices' + ARGUMENT = "too-many-digits" + HELP = "Conformance to numeric notation best practices" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits' - WIKI_TITLE = 'Too many digits' - WIKI_DESCRIPTION = ''' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits" + WIKI_TITLE = "Too many digits" + WIKI_DESCRIPTION = """ Literals with many digits are difficult to read and review. -''' - WIKI_EXPLOIT_SCENARIO = ''' +""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract MyContract{ uint 1_ether = 10000000000000000000; @@ -29,13 +29,13 @@ contract MyContract{ ``` While `1_ether` looks like `1 ether`, it is `10 ether`. As a result, it's likely to be used incorrectly. -''' - WIKI_RECOMMENDATION = ''' +""" + WIKI_RECOMMENDATION = """ Use: - [Ether suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#ether-units), - [Time suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#time-units), or - [The scientific notation](https://solidity.readthedocs.io/en/latest/types.html#rational-and-integer-literals) -''' +""" @staticmethod def _detect_too_many_digits(f): @@ -49,7 +49,7 @@ Use: if isinstance(read, Constant): # read.value can return an int or a str. Convert it to str value_as_str = read.original_value - if '00000' in value_as_str: + if "00000" in value_as_str: # Info to be printed ret.append(node) return ret @@ -59,14 +59,14 @@ Use: # iterate over all contracts for contract in self.slither.contracts_derived: - # iterate over all functions + # iterate over all functions for f in contract.functions: # iterate over all the nodes ret = self._detect_too_many_digits(f) if ret: - func_info = [f, ' uses literals with too many digits:'] + func_info = [f, " uses literals with too many digits:"] for node in ret: - node_info = func_info + ['\n\t- ', node,'\n'] + node_info = func_info + ["\n\t- ", node, "\n"] # Add the result in result res = self.generate_result(node_info) diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index 06eed1016..88adab962 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -10,16 +10,18 @@ class TxOrigin(AbstractDetector): Detect usage of tx.origin in a conditional node """ - ARGUMENT = 'tx-origin' - HELP = 'Dangerous usage of `tx.origin`' + ARGUMENT = "tx-origin" + HELP = "Dangerous usage of `tx.origin`" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin" + ) - WIKI_TITLE = 'Dangerous usage of `tx.origin`' - WIKI_DESCRIPTION = '`tx.origin`-based protection can be abused by a malicious contract if a legitimate user interacts with the malicious contract.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Dangerous usage of `tx.origin`" + WIKI_DESCRIPTION = "`tx.origin`-based protection can be abused by a malicious contract if a legitimate user interacts with the malicious contract." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract TxOrigin { address owner = msg.sender; @@ -28,9 +30,9 @@ contract TxOrigin { require(tx.origin == owner); } ``` -Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `TxOrigin` and bypasses the `tx.origin` protection.''' +Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `TxOrigin` and bypasses the `tx.origin` protection.""" - WIKI_RECOMMENDATION = 'Do not use `tx.origin` for authorization.' + WIKI_RECOMMENDATION = "Do not use `tx.origin` for authorization." @staticmethod def _contains_incorrect_tx_origin_use(node): @@ -42,8 +44,9 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls ` """ solidity_var_read = node.solidity_variables_read if solidity_var_read: - return any(v.name == 'tx.origin' for v in solidity_var_read) and\ - all(v.name != 'msg.sender' for v in solidity_var_read) + return any(v.name == "tx.origin" for v in solidity_var_read) and all( + v.name != "msg.sender" for v in solidity_var_read + ) return False def detect_tx_origin(self, contract): @@ -51,10 +54,12 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls ` for f in contract.functions: nodes = f.nodes - condtional_nodes = [n for n in nodes if n.contains_if() or - n.contains_require_or_assert()] - bad_tx_nodes = [n for n in condtional_nodes if - self._contains_incorrect_tx_origin_use(n)] + condtional_nodes = [ + n for n in nodes if n.contains_if() or n.contains_require_or_assert() + ] + bad_tx_nodes = [ + n for n in condtional_nodes if self._contains_incorrect_tx_origin_use(n) + ] if bad_tx_nodes: ret.append((f, bad_tx_nodes)) return ret diff --git a/slither/detectors/statements/type_based_tautology.py b/slither/detectors/statements/type_based_tautology.py index 4f334805e..f3bd276db 100644 --- a/slither/detectors/statements/type_based_tautology.py +++ b/slither/detectors/statements/type_based_tautology.py @@ -7,21 +7,24 @@ from slither.slithir.operations import Binary, BinaryType from slither.slithir.variables import Constant from slither.core.solidity_types.elementary_type import Int, Uint + class TypeBasedTautology(AbstractDetector): """ Type-based tautology or contradiction """ - ARGUMENT = 'tautology' - HELP = 'Tautology or contradiction' + ARGUMENT = "tautology" + HELP = "Tautology or contradiction" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction' + WIKI = ( + "https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction" + ) - WIKI_TITLE = 'Tautology or contradiction' - WIKI_DESCRIPTION = '''Detects expressions that are tautologies or contradictions.''' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Tautology or contradiction" + WIKI_DESCRIPTION = """Detects expressions that are tautologies or contradictions.""" + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract A { function f(uint x) public { @@ -41,19 +44,20 @@ contract A { ``` `x` is a `uint256`, so `x >= 0` will be always true. `y` is a `uint8`, so `y <512` will be always true. -''' +""" - WIKI_RECOMMENDATION = '''Fix the incorrect comparison by changing the value type or the comparison.''' + WIKI_RECOMMENDATION = ( + """Fix the incorrect comparison by changing the value type or the comparison.""" + ) def typeRange(self, t): bits = int(t.split("int")[1]) if t in Uint: - return (0, (2**bits)-1) + return (0, (2 ** bits) - 1) if t in Int: - v = (2**(bits-1))-1 + v = (2 ** (bits - 1)) - 1 return (-v, v) - flip_table = { BinaryType.GREATER: BinaryType.LESS, BinaryType.GREATER_EQUAL: BinaryType.LESS_EQUAL, @@ -62,14 +66,14 @@ contract A { } def _detect_tautology_or_contradiction(self, low, high, cval, op): - ''' + """ Return true if "[low high] op cval " is always true or always false :param low: :param high: :param cval: :param op: :return: - ''' + """ if op == BinaryType.LESS: # a < cval # its a tautology if @@ -124,7 +128,9 @@ contract A { rtype = str(ir.variable_right.type) if rtype in allInts: (low, high) = self.typeRange(rtype) - if self._detect_tautology_or_contradiction(low, high, cval, self.flip_table[ir.type]): + if self._detect_tautology_or_contradiction( + low, high, cval, self.flip_table[ir.type] + ): f_results.add(node) if isinstance(ir.variable_right, Constant): @@ -132,7 +138,9 @@ contract A { ltype = str(ir.variable_left.type) if ltype in allInts: (low, high) = self.typeRange(ltype) - if self._detect_tautology_or_contradiction(low, high, cval, ir.type): + if self._detect_tautology_or_contradiction( + low, high, cval, ir.type + ): f_results.add(node) results.append((function, f_results)) diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index ee87cbd38..39930ed28 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -18,32 +18,33 @@ class ConstCandidateStateVars(AbstractDetector): Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables """ - ARGUMENT = 'constable-states' - HELP = 'State variables that could be declared constant' + ARGUMENT = "constable-states" + HELP = "State variables that could be declared constant" IMPACT = DetectorClassification.OPTIMIZATION CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant" - - WIKI_TITLE = 'State variables that could be declared constant' - WIKI_DESCRIPTION = 'Constant state variables should be declared constant to save gas.' - WIKI_RECOMMENDATION = 'Add the `constant` attributes to state variables that never change.' + WIKI_TITLE = "State variables that could be declared constant" + WIKI_DESCRIPTION = "Constant state variables should be declared constant to save gas." + WIKI_RECOMMENDATION = "Add the `constant` attributes to state variables that never change." @staticmethod def _valid_candidate(v): return isinstance(v.type, ElementaryType) and not v.is_constant # https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables - valid_solidity_function = [SolidityFunction('keccak256()'), - SolidityFunction('keccak256(bytes)'), - SolidityFunction('sha256()'), - SolidityFunction('sha256(bytes)'), - SolidityFunction('ripemd160()'), - SolidityFunction('ripemd160(bytes)'), - SolidityFunction('ecrecover(bytes32,uint8,bytes32,bytes32)'), - SolidityFunction('addmod(uint256,uint256,uint256)'), - SolidityFunction('mulmod(uint256,uint256,uint256)')] + valid_solidity_function = [ + SolidityFunction("keccak256()"), + SolidityFunction("keccak256(bytes)"), + SolidityFunction("sha256()"), + SolidityFunction("sha256(bytes)"), + SolidityFunction("ripemd160()"), + SolidityFunction("ripemd160(bytes)"), + SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"), + SolidityFunction("addmod(uint256,uint256,uint256)"), + SolidityFunction("mulmod(uint256,uint256,uint256)"), + ] @staticmethod def _is_constant_var(v): @@ -59,12 +60,12 @@ class ConstCandidateStateVars(AbstractDetector): values = export.result() if not values: return True - if all((val in self.valid_solidity_function or self._is_constant_var(val) for val in values)): + if all( + (val in self.valid_solidity_function or self._is_constant_var(val) for val in values) + ): return True return False - - def _detect(self): """ Detect state variables that could be const """ @@ -72,17 +73,23 @@ class ConstCandidateStateVars(AbstractDetector): all_variables = [c.state_variables for c in self.slither.contracts] all_variables = set([item for sublist in all_variables for item in sublist]) - all_non_constant_elementary_variables = set([v for v in all_variables - if self._valid_candidate(v)]) + all_non_constant_elementary_variables = set( + [v for v in all_variables if self._valid_candidate(v)] + ) all_functions = [c.all_functions_called for c in self.slither.contracts] all_functions = list(set([item for sublist in all_functions for item in sublist])) - all_variables_written = [f.state_variables_written for f in all_functions if not f.is_constructor_variables] + all_variables_written = [ + f.state_variables_written for f in all_functions if not f.is_constructor_variables + ] all_variables_written = set([item for sublist in all_variables_written for item in sublist]) - constable_variables = [v for v in all_non_constant_elementary_variables - if (not v in all_variables_written) and self._constant_initial_expression(v)] + constable_variables = [ + v + for v in all_non_constant_elementary_variables + if (not v in all_variables_written) and self._constant_initial_expression(v) + ] # Order for deterministic results constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name) diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index 02051f913..bb663ba7b 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -12,17 +12,16 @@ class UninitializedLocalVars(AbstractDetector): """ """ - ARGUMENT = 'uninitialized-local' - HELP = 'Uninitialized local variables' + ARGUMENT = "uninitialized-local" + HELP = "Uninitialized local variables" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables" - - WIKI_TITLE = 'Uninitialized local variables' - WIKI_DESCRIPTION = 'Uninitialized local variables.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Uninitialized local variables" + WIKI_DESCRIPTION = "Uninitialized local variables." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Uninitialized is Owner{ function withdraw() payable public onlyOwner{ @@ -31,9 +30,9 @@ contract Uninitialized is Owner{ } } ``` -Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is lost.''' +Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is lost.""" - WIKI_RECOMMENDATION = 'Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero.' + WIKI_RECOMMENDATION = "Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero." key = "UNINITIALIZEDLOCAL" @@ -73,7 +72,6 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is for son in node.sons: self._detect_uninitialized(function, son, visited) - def _detect(self): """ Detect uninitialized local variables @@ -92,11 +90,13 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is if function.contains_assembly: continue # dont consider storage variable, as they are detected by another detector - uninitialized_local_variables = [v for v in function.local_variables if not v.is_storage and v.uninitialized] + uninitialized_local_variables = [ + v for v in function.local_variables if not v.is_storage and v.uninitialized + ] function.entry_point.context[self.key] = uninitialized_local_variables self._detect_uninitialized(function, function.entry_point, []) all_results = list(set(self.results)) - for(function, uninitialized_local_variable) in all_results: + for (function, uninitialized_local_variable) in all_results: info = [uninitialized_local_variable, " is a local variable never initialized\n"] json = self.generate_result(info) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index f4be60bc7..97b12e6be 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -19,16 +19,16 @@ class UninitializedStateVarsDetection(AbstractDetector): Constant function detector """ - ARGUMENT = 'uninitialized-state' - HELP = 'Uninitialized state variables' + ARGUMENT = "uninitialized-state" + HELP = "Uninitialized state variables" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables" - WIKI_TITLE = 'Uninitialized state variables' - WIKI_DESCRIPTION = 'Uninitialized state variables.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Uninitialized state variables" + WIKI_DESCRIPTION = "Uninitialized state variables." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Uninitialized{ address destination; @@ -39,10 +39,10 @@ contract Uninitialized{ } ``` Bob calls `transfer`. As a result, the Ether are sent to the address `0x0` and are lost. -''' - WIKI_RECOMMENDATION = ''' +""" + WIKI_RECOMMENDATION = """ Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero. -''' +""" @staticmethod def _written_variables(contract): @@ -51,12 +51,11 @@ Initialize all the variables. If a variable is meant to be initialized to zero, for n in f.nodes: ret += n.state_variables_written for ir in n.irs: - if isinstance(ir, LibraryCall) \ - or isinstance(ir, InternalCall): + if isinstance(ir, LibraryCall) or isinstance(ir, InternalCall): idx = 0 if ir.function: for param in ir.function.parameters: - if param.location == 'storage': + if param.location == "storage": # If its a storage variable, add either the variable # Or the variable it points to if its a reference if isinstance(ir.arguments[idx], ReferenceVariable): @@ -69,7 +68,7 @@ Initialize all the variables. If a variable is meant to be initialized to zero, def _variable_written_in_proxy(self): # Hack to memoize without having it define in the init - if hasattr(self, '__variables_written_in_proxy'): + if hasattr(self, "__variables_written_in_proxy"): return self.__variables_written_in_proxy variables_written_in_proxy = [] @@ -85,7 +84,10 @@ Initialize all the variables. If a variable is meant to be initialized to zero, if contract.is_upgradeable: variables_name_written_in_proxy = self._variable_written_in_proxy() if variables_name_written_in_proxy: - variables_in_contract = [contract.get_state_variable_from_name(v) for v in variables_name_written_in_proxy] + variables_in_contract = [ + contract.get_state_variable_from_name(v) + for v in variables_name_written_in_proxy + ] variables_in_contract = [v for v in variables_in_contract if v] variables += variables_in_contract return list(set(variables)) @@ -101,10 +103,13 @@ Initialize all the variables. If a variable is meant to be initialized to zero, written_variables = self._written_variables(contract) written_variables += self._written_variables_in_proxy(contract) read_variables = self._read_variables(contract) - return [(variable, contract.get_functions_reading_from_variable(variable)) - for variable in contract.state_variables if variable not in written_variables and \ - not variable.expression and \ - variable in read_variables] + return [ + (variable, contract.get_functions_reading_from_variable(variable)) + for variable in contract.state_variables + if variable not in written_variables + and not variable.expression + and variable in read_variables + ] def _detect(self): """ Detect uninitialized state variables diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 728597d2e..d4cd82081 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -12,16 +12,16 @@ class UninitializedStorageVars(AbstractDetector): """ """ - ARGUMENT = 'uninitialized-storage' - HELP = 'Uninitialized storage variables' + ARGUMENT = "uninitialized-storage" + HELP = "Uninitialized storage variables" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables' + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables" - WIKI_TITLE = 'Uninitialized storage variables' - WIKI_DESCRIPTION = 'An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable.' - WIKI_EXPLOIT_SCENARIO = ''' + WIKI_TITLE = "Uninitialized storage variables" + WIKI_DESCRIPTION = "An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable." + WIKI_EXPLOIT_SCENARIO = """ ```solidity contract Uninitialized{ address owner = msg.sender; @@ -37,9 +37,9 @@ contract Uninitialized{ } ``` Bob calls `func`. As a result, `owner` is overridden to `0`. -''' +""" - WIKI_RECOMMENDATION = 'Initialize all storage variables.' + WIKI_RECOMMENDATION = "Initialize all storage variables." # node.context[self.key] contains the uninitialized storage variables key = "UNINITIALIZEDSTORAGE" @@ -80,7 +80,6 @@ Bob calls `func`. As a result, `owner` is overridden to `0`. for son in node.sons: self._detect_uninitialized(function, son, visited) - def _detect(self): """ Detect uninitialized storage variables @@ -96,11 +95,13 @@ Bob calls `func`. As a result, `owner` is overridden to `0`. for contract in self.slither.contracts: for function in contract.functions: if function.is_implemented: - uninitialized_storage_variables = [v for v in function.local_variables if v.is_storage and v.uninitialized] + uninitialized_storage_variables = [ + v for v in function.local_variables if v.is_storage and v.uninitialized + ] function.entry_point.context[self.key] = uninitialized_storage_variables self._detect_uninitialized(function, function.entry_point, []) - for(function, uninitialized_storage_variable) in self.results: + for (function, uninitialized_storage_variable) in self.results: info = [uninitialized_storage_variable, " is a storage variable never initialized\n"] json = self.generate_result(info) results.append(json) diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 195c79aca..17c2e36dd 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -8,49 +8,54 @@ from slither.visitors.expression.export_values import ExportValues from slither.core.variables.state_variable import StateVariable from slither.formatters.variables.unused_state_variables import format + class UnusedStateVars(AbstractDetector): """ Unused state variables detector """ - ARGUMENT = 'unused-state' - HELP = 'Unused state variables' + ARGUMENT = "unused-state" + HELP = "Unused state variables" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables' - + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables" - WIKI_TITLE = 'Unused state variable' - WIKI_DESCRIPTION = 'Unused state variable.' - WIKI_EXPLOIT_SCENARIO = '' - WIKI_RECOMMENDATION = 'Remove unused state variables.' + WIKI_TITLE = "Unused state variable" + WIKI_DESCRIPTION = "Unused state variable." + WIKI_EXPLOIT_SCENARIO = "" + WIKI_RECOMMENDATION = "Remove unused state variables." def detect_unused(self, contract): if contract.is_signature_only(): return None # Get all the variables read in all the functions and modifiers - all_functions = (contract.all_functions_called + contract.modifiers) + all_functions = contract.all_functions_called + contract.modifiers variables_used = [x.state_variables_read for x in all_functions] - variables_used += [x.state_variables_written for x in all_functions if not x.is_constructor_variables] + variables_used += [ + x.state_variables_written for x in all_functions if not x.is_constructor_variables + ] array_candidates = [x.variables for x in all_functions] array_candidates = [i for sl in array_candidates for i in sl] + contract.state_variables - array_candidates = [x.type.length for x in array_candidates if isinstance(x.type, ArrayType) and x.type.length] + array_candidates = [ + x.type.length + for x in array_candidates + if isinstance(x.type, ArrayType) and x.type.length + ] array_candidates = [ExportValues(x).result() for x in array_candidates] array_candidates = [i for sl in array_candidates for i in sl] array_candidates = [v for v in array_candidates if isinstance(v, StateVariable)] - - # Flat list variables_used = [item for sublist in variables_used for item in sublist] variables_used = list(set(variables_used + array_candidates)) # Return the variables unused that are not public - return [x for x in contract.variables if - x not in variables_used and x.visibility != 'public'] + return [ + x for x in contract.variables if x not in variables_used and x.visibility != "public" + ] def _detect(self): """ Detect unused state variables diff --git a/slither/exceptions.py b/slither/exceptions.py index 85f7e737b..d9376421d 100644 --- a/slither/exceptions.py +++ b/slither/exceptions.py @@ -1,3 +1,6 @@ -class SlitherException(Exception): pass +class SlitherException(Exception): + pass -class SlitherError(SlitherException): pass + +class SlitherError(SlitherException): + pass diff --git a/slither/formatters/attributes/const_functions.py b/slither/formatters/attributes/const_functions.py index ed16c29a6..baa5d30d9 100644 --- a/slither/formatters/attributes/const_functions.py +++ b/slither/formatters/attributes/const_functions.py @@ -2,35 +2,48 @@ import re from slither.formatters.exceptions import FormatError from slither.formatters.utils.patches import create_patch + def format(slither, result): - elements = result['elements'] + elements = result["elements"] for element in elements: - if element['type'] != "function": + if element["type"] != "function": # Skip variable elements continue - target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name']) + target_contract = slither.get_contract_from_name( + element["type_specific_fields"]["parent"]["name"] + ) if target_contract: - function = target_contract.get_function_from_signature(element['type_specific_fields']['signature']) + function = target_contract.get_function_from_signature( + element["type_specific_fields"]["signature"] + ) if function: - _patch(slither, - result, - element['source_mapping']['filename_absolute'], - int(function.parameters_src.source_mapping['start'] + - function.parameters_src.source_mapping['length']), - int(function.returns_src.source_mapping['start'])) + _patch( + slither, + result, + element["source_mapping"]["filename_absolute"], + int( + function.parameters_src.source_mapping["start"] + + function.parameters_src.source_mapping["length"] + ), + int(function.returns_src.source_mapping["start"]), + ) def _patch(slither, result, in_file, modify_loc_start, modify_loc_end): - in_file_str = slither.source_code[in_file].encode('utf8') + in_file_str = slither.source_code[in_file].encode("utf8") old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Find the keywords view|pure|constant and remove them - m = re.search("(view|pure|constant)", old_str_of_interest.decode('utf-8')) + m = re.search("(view|pure|constant)", old_str_of_interest.decode("utf-8")) if m: - create_patch(result, - in_file, - modify_loc_start + m.span()[0], - modify_loc_start + m.span()[1], - m.groups(0)[0], # this is view|pure|constant - "") + create_patch( + result, + in_file, + modify_loc_start + m.span()[0], + modify_loc_start + m.span()[1], + m.groups(0)[0], # this is view|pure|constant + "", + ) else: - raise FormatError("No view/pure/constant specifier exists. Regex failed to remove specifier!") + raise FormatError( + "No view/pure/constant specifier exists. Regex failed to remove specifier!" + ) diff --git a/slither/formatters/attributes/constant_pragma.py b/slither/formatters/attributes/constant_pragma.py index b039f9895..94d0d2663 100644 --- a/slither/formatters/attributes/constant_pragma.py +++ b/slither/formatters/attributes/constant_pragma.py @@ -11,19 +11,24 @@ REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"] # 2: version number # 3: version number # 4: version number -PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') +PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)") def format(slither, result): - elements = result['elements'] + elements = result["elements"] versions_used = [] for element in elements: - versions_used.append(''.join(element['type_specific_fields']['directive'][1:])) + versions_used.append("".join(element["type_specific_fields"]["directive"][1:])) solc_version_replace = _analyse_versions(versions_used) for element in elements: - _patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace, - element['source_mapping']['start'], - element['source_mapping']['start'] + element['source_mapping']['length']) + _patch( + slither, + result, + element["source_mapping"]["filename_absolute"], + solc_version_replace, + element["source_mapping"]["start"], + element["source_mapping"]["start"] + element["source_mapping"]["length"], + ) def _analyse_versions(used_solc_versions): @@ -40,30 +45,27 @@ def _determine_solc_version_replacement(used_solc_version): versions = PATTERN.findall(used_solc_version) if len(versions) == 1: version = versions[0] - minor_version = '.'.join(version[2:])[2] - if minor_version == '4': - return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' - elif minor_version == '5': - return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + minor_version = ".".join(version[2:])[2] + if minor_version == "4": + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";" + elif minor_version == "5": + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";" else: raise FormatImpossible("Unknown version!") elif len(versions) == 2: version_right = versions[1] - minor_version_right = '.'.join(version_right[2:])[2] - if minor_version_right == '4': + minor_version_right = ".".join(version_right[2:])[2] + if minor_version_right == "4": # Replace with 0.4.25 - return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' - elif minor_version_right in ['5', '6']: + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";" + elif minor_version_right in ["5", "6"]: # Replace with 0.5.3 - return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";" def _patch(slither, result, in_file, pragma, modify_loc_start, modify_loc_end): - in_file_str = slither.source_code[in_file].encode('utf8') + in_file_str = slither.source_code[in_file].encode("utf8") old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] - create_patch(result, - in_file, - int(modify_loc_start), - int(modify_loc_end), - old_str_of_interest, - pragma) + create_patch( + result, in_file, int(modify_loc_start), int(modify_loc_end), old_str_of_interest, pragma + ) diff --git a/slither/formatters/attributes/incorrect_solc.py b/slither/formatters/attributes/incorrect_solc.py index 33013c954..ccec963ff 100644 --- a/slither/formatters/attributes/incorrect_solc.py +++ b/slither/formatters/attributes/incorrect_solc.py @@ -12,48 +12,58 @@ REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"] # 2: version number # 3: version number # 4: version number -PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)') +PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)") + def format(slither, result): - elements = result['elements'] + elements = result["elements"] for element in elements: solc_version_replace = _determine_solc_version_replacement( - ''.join(element['type_specific_fields']['directive'][1:])) + "".join(element["type_specific_fields"]["directive"][1:]) + ) + + _patch( + slither, + result, + element["source_mapping"]["filename_absolute"], + solc_version_replace, + element["source_mapping"]["start"], + element["source_mapping"]["start"] + element["source_mapping"]["length"], + ) - _patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace, - element['source_mapping']['start'], element['source_mapping']['start'] + - element['source_mapping']['length']) def _determine_solc_version_replacement(used_solc_version): versions = PATTERN.findall(used_solc_version) if len(versions) == 1: version = versions[0] - minor_version = '.'.join(version[2:])[2] - if minor_version == '4': + minor_version = ".".join(version[2:])[2] + if minor_version == "4": # Replace with 0.4.25 - return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' - elif minor_version == '5': + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";" + elif minor_version == "5": # Replace with 0.5.3 - return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";" else: raise FormatImpossible(f"Unknown version {versions}") elif len(versions) == 2: version_right = versions[1] - minor_version_right = '.'.join(version_right[2:])[2] - if minor_version_right == '4': + minor_version_right = ".".join(version_right[2:])[2] + if minor_version_right == "4": # Replace with 0.4.25 - return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';' - elif minor_version_right in ['5','6']: + return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";" + elif minor_version_right in ["5", "6"]: # Replace with 0.5.3 - return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';' + return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";" def _patch(slither, result, in_file, solc_version, modify_loc_start, modify_loc_end): - in_file_str = slither.source_code[in_file].encode('utf8') + in_file_str = slither.source_code[in_file].encode("utf8") old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] - create_patch(result, - in_file, - int(modify_loc_start), - int(modify_loc_end), - old_str_of_interest, - solc_version) + create_patch( + result, + in_file, + int(modify_loc_start), + int(modify_loc_end), + old_str_of_interest, + solc_version, + ) diff --git a/slither/formatters/exceptions.py b/slither/formatters/exceptions.py index eac7df205..28fe7f54a 100644 --- a/slither/formatters/exceptions.py +++ b/slither/formatters/exceptions.py @@ -1,5 +1,9 @@ from slither.exceptions import SlitherException -class FormatImpossible(SlitherException): pass -class FormatError(SlitherException): pass +class FormatImpossible(SlitherException): + pass + + +class FormatError(SlitherException): + pass diff --git a/slither/formatters/functions/external_function.py b/slither/formatters/functions/external_function.py index 1eb027902..17f6c1361 100644 --- a/slither/formatters/functions/external_function.py +++ b/slither/formatters/functions/external_function.py @@ -1,42 +1,53 @@ import re from slither.formatters.utils.patches import create_patch + def format(slither, result): - elements = result['elements'] + elements = result["elements"] for element in elements: - target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name']) + target_contract = slither.get_contract_from_name( + element["type_specific_fields"]["parent"]["name"] + ) if target_contract: - function = target_contract.get_function_from_signature(element['type_specific_fields']['signature']) + function = target_contract.get_function_from_signature( + element["type_specific_fields"]["signature"] + ) if function: - _patch(slither, - result, - element['source_mapping']['filename_absolute'], - int(function.parameters_src.source_mapping['start']), - int(function.returns_src.source_mapping['start'])) + _patch( + slither, + result, + element["source_mapping"]["filename_absolute"], + int(function.parameters_src.source_mapping["start"]), + int(function.returns_src.source_mapping["start"]), + ) def _patch(slither, result, in_file, modify_loc_start, modify_loc_end): - in_file_str = slither.source_code[in_file].encode('utf8') + in_file_str = slither.source_code[in_file].encode("utf8") old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Search for 'public' keyword which is in-between the function name and modifier name (if present) # regex: 'public' could have spaces around or be at the end of the line - m = re.search(r'((\spublic)\s+)|(\spublic)$|(\)public)$', old_str_of_interest.decode('utf-8')) + m = re.search(r"((\spublic)\s+)|(\spublic)$|(\)public)$", old_str_of_interest.decode("utf-8")) if m is None: # No visibility specifier exists; public by default. - create_patch(result, - in_file, - # start after the function definition's closing paranthesis - modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1, - # end is same as start because we insert the keyword `external` at that location - modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1, - "", - " external") # replace_text is `external` + create_patch( + result, + in_file, + # start after the function definition's closing paranthesis + modify_loc_start + len(old_str_of_interest.decode("utf-8").split(")")[0]) + 1, + # end is same as start because we insert the keyword `external` at that location + modify_loc_start + len(old_str_of_interest.decode("utf-8").split(")")[0]) + 1, + "", + " external", + ) # replace_text is `external` else: - create_patch(result, - in_file, - # start at the keyword `public` - modify_loc_start + m.span()[0] + 1, - # end after the keyword `public` = start + len('public'') - modify_loc_start + m.span()[0] + 1 + len('public'), - "public", - "external") + create_patch( + result, + in_file, + # start at the keyword `public` + modify_loc_start + m.span()[0] + 1, + # end after the keyword `public` = start + len('public'') + modify_loc_start + m.span()[0] + 1 + len("public"), + "public", + "external", + ) diff --git a/slither/formatters/naming_convention/naming_convention.py b/slither/formatters/naming_convention/naming_convention.py index 49edaf1b5..018920800 100644 --- a/slither/formatters/naming_convention/naming_convention.py +++ b/slither/formatters/naming_convention/naming_convention.py @@ -1,7 +1,14 @@ import re import logging -from slither.slithir.operations import Send, Transfer, OperationWithLValue, HighLevelCall, LowLevelCall, \ - InternalCall, InternalDynamicCall +from slither.slithir.operations import ( + Send, + Transfer, + OperationWithLValue, + HighLevelCall, + LowLevelCall, + InternalCall, + InternalDynamicCall, +) from slither.core.declarations import Modifier from slither.core.solidity_types import UserDefinedType, MappingType from slither.core.declarations import Enum, Contract, Structure, Function @@ -11,22 +18,26 @@ from slither.formatters.exceptions import FormatError, FormatImpossible from slither.formatters.utils.patches import create_patch logging.basicConfig(level=logging.INFO) -logger = logging.getLogger('Slither.Format') +logger = logging.getLogger("Slither.Format") + def format(slither, result): - elements = result['elements'] + elements = result["elements"] for element in elements: - target = element['additional_fields']['target'] + target = element["additional_fields"]["target"] - convention = element['additional_fields']['convention'] + convention = element["additional_fields"]["convention"] if convention == "l_O_I_should_not_be_used": # l_O_I_should_not_be_used cannot be automatically patched - logger.info(f'The following naming convention cannot be patched: \n{result["description"]}') + logger.info( + f'The following naming convention cannot be patched: \n{result["description"]}' + ) continue _patch(slither, result, element, target) + # endregion ################################################################################### ################################################################################### @@ -34,21 +45,82 @@ def format(slither, result): ################################################################################### ################################################################################### -KEY = 'ALL_NAMES_USED' +KEY = "ALL_NAMES_USED" # https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#reserved-keywords -SOLIDITY_KEYWORDS = ['abstract', 'after', 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', 'default', 'define', - 'final', 'immutable', 'implements', 'in', 'inline', 'let', 'macro', 'match', 'mutable', 'null', - 'of', 'override', 'partial', 'promise', 'reference', 'relocatable', 'sealed', 'sizeof', 'static', - 'supports', 'switch', 'try', 'typedef', 'typeof', 'unchecked'] +SOLIDITY_KEYWORDS = [ + "abstract", + "after", + "alias", + "apply", + "auto", + "case", + "catch", + "copyof", + "default", + "define", + "final", + "immutable", + "implements", + "in", + "inline", + "let", + "macro", + "match", + "mutable", + "null", + "of", + "override", + "partial", + "promise", + "reference", + "relocatable", + "sealed", + "sizeof", + "static", + "supports", + "switch", + "try", + "typedef", + "typeof", + "unchecked", +] # https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#language-grammar -SOLIDITY_KEYWORDS += ['pragma', 'import', 'contract', 'library', 'contract', 'function', 'using', 'struct', 'enum', - 'public', 'private', 'internal', 'external', 'calldata', 'memory', 'modifier', 'view', 'pure', - 'constant', 'storage', 'for', 'if', 'while', 'break', 'return', 'throw', 'else', 'type'] +SOLIDITY_KEYWORDS += [ + "pragma", + "import", + "contract", + "library", + "contract", + "function", + "using", + "struct", + "enum", + "public", + "private", + "internal", + "external", + "calldata", + "memory", + "modifier", + "view", + "pure", + "constant", + "storage", + "for", + "if", + "while", + "break", + "return", + "throw", + "else", + "type", +] SOLIDITY_KEYWORDS += ElementaryTypeName + def _name_already_use(slither, name): # Do not convert to a name used somewhere else if not KEY in slither.context: @@ -65,49 +137,53 @@ def _name_already_use(slither, name): slither.context[KEY] = all_names return name in slither.context[KEY] + def _convert_CapWords(original_name, slither): name = original_name.capitalize() - while '_' in name: - offset = name.find('_') + while "_" in name: + offset = name.find("_") if len(name) > offset: - name = name[0:offset] + name[offset+1].upper() + name[offset+1:] + name = name[0:offset] + name[offset + 1].upper() + name[offset + 1 :] if _name_already_use(slither, name): - raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)') + raise FormatImpossible(f"{original_name} cannot be converted to {name} (already used)") if name in SOLIDITY_KEYWORDS: - raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)') + raise FormatImpossible(f"{original_name} cannot be converted to {name} (Solidity keyword)") return name + def _convert_mixedCase(original_name, slither): name = original_name if isinstance(name, bytes): - name = name.decode('utf8') + name = name.decode("utf8") - while '_' in name: - offset = name.find('_') + while "_" in name: + offset = name.find("_") if len(name) > offset: - name = name[0:offset] + name[offset + 1].upper() + name[offset + 2:] + name = name[0:offset] + name[offset + 1].upper() + name[offset + 2 :] name = name[0].lower() + name[1:] if _name_already_use(slither, name): - raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)') + raise FormatImpossible(f"{original_name} cannot be converted to {name} (already used)") if name in SOLIDITY_KEYWORDS: - raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)') + raise FormatImpossible(f"{original_name} cannot be converted to {name} (Solidity keyword)") return name + def _convert_UPPER_CASE_WITH_UNDERSCORES(name, slither): if _name_already_use(slither, name.upper()): - raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (already used)') + raise FormatImpossible(f"{name} cannot be converted to {name.upper()} (already used)") if name.upper() in SOLIDITY_KEYWORDS: - raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (Solidity keyword)') + raise FormatImpossible(f"{name} cannot be converted to {name.upper()} (Solidity keyword)") return name.upper() -conventions ={ - "CapWords":_convert_CapWords, - "mixedCase":_convert_mixedCase, - "UPPER_CASE_WITH_UNDERSCORES":_convert_UPPER_CASE_WITH_UNDERSCORES + +conventions = { + "CapWords": _convert_CapWords, + "mixedCase": _convert_mixedCase, + "UPPER_CASE_WITH_UNDERSCORES": _convert_UPPER_CASE_WITH_UNDERSCORES, } @@ -118,11 +194,13 @@ conventions ={ ################################################################################### ################################################################################### + def _get_from_contract(slither, element, name, getter): - contract_name = element['type_specific_fields']['parent']['name'] + contract_name = element["type_specific_fields"]["parent"]["name"] contract = slither.get_contract_from_name(contract_name) return getattr(contract, getter)(name) + # endregion ################################################################################### ################################################################################### @@ -130,58 +208,70 @@ def _get_from_contract(slither, element, name, getter): ################################################################################### ################################################################################### + def _patch(slither, result, element, _target): if _target == "contract": - target = slither.get_contract_from_name(element['name']) + target = slither.get_contract_from_name(element["name"]) elif _target == "structure": - target = _get_from_contract(slither, element, element['name'], 'get_structure_from_name') + target = _get_from_contract(slither, element, element["name"], "get_structure_from_name") elif _target == "event": - target = _get_from_contract(slither, element, element['name'], 'get_event_from_name') + target = _get_from_contract(slither, element, element["name"], "get_event_from_name") elif _target == "function": # Avoid constructor (FP?) - if element['name'] != element['type_specific_fields']['parent']['name']: - function_sig = element['type_specific_fields']['signature'] - target = _get_from_contract(slither, element, function_sig, 'get_function_from_signature') + if element["name"] != element["type_specific_fields"]["parent"]["name"]: + function_sig = element["type_specific_fields"]["signature"] + target = _get_from_contract( + slither, element, function_sig, "get_function_from_signature" + ) elif _target == "modifier": - modifier_sig = element['type_specific_fields']['signature'] - target = _get_from_contract(slither, element, modifier_sig, 'get_modifier_from_signature') + modifier_sig = element["type_specific_fields"]["signature"] + target = _get_from_contract(slither, element, modifier_sig, "get_modifier_from_signature") elif _target == "parameter": - contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name'] - function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature'] - param_name = element['name'] + contract_name = element["type_specific_fields"]["parent"]["type_specific_fields"]["parent"][ + "name" + ] + function_sig = element["type_specific_fields"]["parent"]["type_specific_fields"][ + "signature" + ] + param_name = element["name"] contract = slither.get_contract_from_name(contract_name) function = contract.get_function_from_signature(function_sig) target = function.get_local_variable_from_name(param_name) elif _target in ["variable", "variable_constant"]: # Local variable - if element['type_specific_fields']['parent'] == 'function': - contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name'] - function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature'] - var_name = element['name'] + if element["type_specific_fields"]["parent"] == "function": + contract_name = element["type_specific_fields"]["parent"]["type_specific_fields"][ + "parent" + ]["name"] + function_sig = element["type_specific_fields"]["parent"]["type_specific_fields"][ + "signature" + ] + var_name = element["name"] contract = slither.get_contract_from_name(contract_name) function = contract.get_function_from_signature(function_sig) target = function.get_local_variable_from_name(var_name) # State variable else: - target = _get_from_contract(slither, element, element['name'], 'get_state_variable_from_name') + target = _get_from_contract( + slither, element, element["name"], "get_state_variable_from_name" + ) elif _target == "enum": - target = _get_from_contract(slither, element, element['name'], 'get_enum_from_canonical_name') + target = _get_from_contract( + slither, element, element["name"], "get_enum_from_canonical_name" + ) else: raise FormatError("Unknown naming convention! " + _target) - _explore(slither, - result, - target, - conventions[element['additional_fields']['convention']]) + _explore(slither, result, target, conventions[element["additional_fields"]["convention"]]) # endregion @@ -194,22 +284,24 @@ def _patch(slither, result, element, _target): # group 1: beginning of the from type # group 2: beginning of the to type # nested mapping are within the group 1 -#RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)' -RE_MAPPING_FROM = b'([a-zA-Z0-9\._\[\]]*)' -RE_MAPPING_TO = b'([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)' -RE_MAPPING = b'[ ]*mapping[ ]*\([ ]*' + RE_MAPPING_FROM + b'[ ]*' + b'=>' + b'[ ]*'+ RE_MAPPING_TO + b'\)' +# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)' +RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)" +RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)" +RE_MAPPING = ( + b"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + b"\)" +) def _is_var_declaration(slither, filename, start): - ''' + """ Detect usage of 'var ' for Solidity < 0.5 :param slither: :param filename: :param start: :return: - ''' - v = 'var ' - return slither.source_code[filename][start:start + len(v)] == v + """ + v = "var " + return slither.source_code[filename][start : start + len(v)] == v def _explore_type(slither, result, target, convert, type, filename_source_code, start, end): @@ -222,17 +314,11 @@ def _explore_type(slither, result, target, convert, type, filename_source_code, loc_start = start if _is_var_declaration(slither, filename_source_code, start): - loc_end = loc_start + len('var') + loc_end = loc_start + len("var") else: loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) - + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) else: # Patch type based on structure @@ -243,24 +329,17 @@ def _explore_type(slither, result, target, convert, type, filename_source_code, loc_start = start if _is_var_declaration(slither, filename_source_code, start): - loc_end = loc_start + len('var') + loc_end = loc_start + len("var") else: loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) # Structure contain a list of elements, that might need patching # .elems return a list of VariableStructure - _explore_variables_declaration(slither, - type.type.elems.values(), - result, - target, - convert) + _explore_variables_declaration( + slither, type.type.elems.values(), result, target, convert + ) if isinstance(type, MappingType): # Mapping has three steps: @@ -271,11 +350,16 @@ def _explore_type(slither, result, target, convert, type, filename_source_code, # Do the comparison twice, so we can factor together the re matching # mapping can only have elementary type in type_from - if isinstance(type.type_to, (UserDefinedType, MappingType)) or target in [type.type_from, type.type_to]: + if isinstance(type.type_to, (UserDefinedType, MappingType)) or target in [ + type.type_from, + type.type_to, + ]: full_txt_start = start full_txt_end = end - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] re_match = re.match(RE_MAPPING, full_txt) assert re_match @@ -286,12 +370,7 @@ def _explore_type(slither, result, target, convert, type, filename_source_code, loc_start = start + re_match.start(1) loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) if type.type_to == target: @@ -301,97 +380,98 @@ def _explore_type(slither, result, target, convert, type, filename_source_code, loc_start = start + re_match.start(2) loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) if isinstance(type.type_to, (UserDefinedType, MappingType)): loc_start = start + re_match.start(2) loc_end = start + re_match.end(2) - _explore_type(slither, - result, - target, - convert, - type.type_to, - filename_source_code, - loc_start, - loc_end) - - - -def _explore_variables_declaration(slither, variables, result, target, convert, patch_comment=False): + _explore_type( + slither, + result, + target, + convert, + type.type_to, + filename_source_code, + loc_start, + loc_end, + ) + + +def _explore_variables_declaration( + slither, variables, result, target, convert, patch_comment=False +): for variable in variables: # First explore the type of the variable - filename_source_code = variable.source_mapping['filename_absolute'] - full_txt_start = variable.source_mapping['start'] - full_txt_end = full_txt_start + variable.source_mapping['length'] - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] - - _explore_type(slither, - result, - target, - convert, - variable.type, - filename_source_code, - full_txt_start, - variable.source_mapping['start'] + variable.source_mapping['length']) + filename_source_code = variable.source_mapping["filename_absolute"] + full_txt_start = variable.source_mapping["start"] + full_txt_end = full_txt_start + variable.source_mapping["length"] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] + + _explore_type( + slither, + result, + target, + convert, + variable.type, + filename_source_code, + full_txt_start, + variable.source_mapping["start"] + variable.source_mapping["length"], + ) # If the variable is the target if variable == target: old_str = variable.name new_str = convert(old_str, slither) - loc_start = full_txt_start + full_txt.find(old_str.encode('utf8')) + loc_start = full_txt_start + full_txt.find(old_str.encode("utf8")) loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) # Patch comment only makes sense for local variable declaration in the parameter list if patch_comment and isinstance(variable, LocalVariable): - if 'lines' in variable.source_mapping and variable.source_mapping['lines']: + if "lines" in variable.source_mapping and variable.source_mapping["lines"]: func = variable.function - end_line = func.source_mapping['lines'][0] + end_line = func.source_mapping["lines"][0] if variable in func.parameters: idx = len(func.parameters) - func.parameters.index(variable) + 1 first_line = end_line - idx - 2 - potential_comments = slither.source_code[filename_source_code].encode('utf8') - potential_comments = potential_comments.splitlines(keepends=True)[first_line:end_line-1] + potential_comments = slither.source_code[filename_source_code].encode( + "utf8" + ) + potential_comments = potential_comments.splitlines(keepends=True)[ + first_line : end_line - 1 + ] - idx_beginning = func.source_mapping['start'] - idx_beginning += - func.source_mapping['starting_column'] + 1 - idx_beginning += - sum([len(c) for c in potential_comments]) + idx_beginning = func.source_mapping["start"] + idx_beginning += -func.source_mapping["starting_column"] + 1 + idx_beginning += -sum([len(c) for c in potential_comments]) - old_comment = f'@param {old_str}'.encode('utf8') + old_comment = f"@param {old_str}".encode("utf8") for line in potential_comments: idx = line.find(old_comment) - if idx >=0: + if idx >= 0: loc_start = idx + idx_beginning loc_end = loc_start + len(old_comment) - new_comment = f'@param {new_str}'.encode('utf8') + new_comment = f"@param {new_str}".encode("utf8") - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_comment, - new_comment) + create_patch( + result, + filename_source_code, + loc_start, + loc_end, + old_comment, + new_comment, + ) break idx_beginning += len(line) - - def _explore_structures_declaration(slither, structures, result, target, convert): for st in structures: # Explore the variable declared within the structure (VariableStructure) @@ -402,23 +482,20 @@ def _explore_structures_declaration(slither, structures, result, target, convert old_str = st.name new_str = convert(old_str, slither) - filename_source_code = st.source_mapping['filename_absolute'] - full_txt_start = st.source_mapping['start'] - full_txt_end = full_txt_start + st.source_mapping['length'] - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + filename_source_code = st.source_mapping["filename_absolute"] + full_txt_start = st.source_mapping["start"] + full_txt_end = full_txt_start + st.source_mapping["length"] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] # The name is after the space - matches = re.finditer(b'struct[ ]*', full_txt) + matches = re.finditer(b"struct[ ]*", full_txt) # Look for the end offset of the largest list of ' ' loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) def _explore_events_declaration(slither, events, result, target, convert): @@ -428,20 +505,16 @@ def _explore_events_declaration(slither, events, result, target, convert): # If the event is the target if event == target: - filename_source_code = event.source_mapping['filename_absolute'] + filename_source_code = event.source_mapping["filename_absolute"] old_str = event.name new_str = convert(old_str, slither) - loc_start = event.source_mapping['start'] + loc_start = event.source_mapping["start"] loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) + def get_ir_variables(ir): vars = ir.read @@ -460,24 +533,29 @@ def get_ir_variables(ir): return [v for v in vars if v] + def _explore_irs(slither, irs, result, target, convert): if irs is None: return for ir in irs: for v in get_ir_variables(ir): if target == v or ( - isinstance(target, Function) and isinstance(v, Function) and - v.canonical_name == target.canonical_name): + isinstance(target, Function) + and isinstance(v, Function) + and v.canonical_name == target.canonical_name + ): source_mapping = ir.expression.source_mapping - filename_source_code = source_mapping['filename_absolute'] - full_txt_start = source_mapping['start'] - full_txt_end = full_txt_start + source_mapping['length'] - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + filename_source_code = source_mapping["filename_absolute"] + full_txt_start = source_mapping["start"] + full_txt_end = full_txt_start + source_mapping["length"] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] - if not target.name.encode('utf8') in full_txt: - raise FormatError(f'{target} not found in {full_txt} ({source_mapping}') + if not target.name.encode("utf8") in full_txt: + raise FormatError(f"{target} not found in {full_txt} ({source_mapping}") - old_str = target.name.encode('utf8') + old_str = target.name.encode("utf8") new_str = convert(old_str, slither) counter = 0 @@ -487,18 +565,13 @@ def _explore_irs(slither, irs, result, target, convert): target_found_at = full_txt.find((old_str)) - full_txt = full_txt[target_found_at+1:] + full_txt = full_txt[target_found_at + 1 :] counter += target_found_at loc_start = full_txt_start + counter loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) def _explore_functions(slither, functions, result, target, convert): @@ -510,26 +583,24 @@ def _explore_functions(slither, functions, result, target, convert): old_str = function.name new_str = convert(old_str, slither) - filename_source_code = function.source_mapping['filename_absolute'] - full_txt_start = function.source_mapping['start'] - full_txt_end = full_txt_start + function.source_mapping['length'] - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + filename_source_code = function.source_mapping["filename_absolute"] + full_txt_start = function.source_mapping["start"] + full_txt_end = full_txt_start + function.source_mapping["length"] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] # The name is after the space if isinstance(target, Modifier): - matches = re.finditer(b'modifier([ ]*)', full_txt) + matches = re.finditer(b"modifier([ ]*)", full_txt) else: - matches = re.finditer(b'function([ ]*)', full_txt) + matches = re.finditer(b"function([ ]*)", full_txt) # Look for the end offset of the largest list of ' ' loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) + def _explore_enums(slither, enums, result, target, convert): for enum in enums: @@ -537,23 +608,20 @@ def _explore_enums(slither, enums, result, target, convert): old_str = enum.name new_str = convert(old_str, slither) - filename_source_code = enum.source_mapping['filename_absolute'] - full_txt_start = enum.source_mapping['start'] - full_txt_end = full_txt_start + enum.source_mapping['length'] - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + filename_source_code = enum.source_mapping["filename_absolute"] + full_txt_start = enum.source_mapping["start"] + full_txt_end = full_txt_start + enum.source_mapping["length"] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] # The name is after the space - matches = re.finditer(b'enum([ ]*)', full_txt) + matches = re.finditer(b"enum([ ]*)", full_txt) # Look for the end offset of the largest list of ' ' loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) def _explore_contract(slither, contract, result, target, convert): @@ -563,27 +631,24 @@ def _explore_contract(slither, contract, result, target, convert): _explore_enums(slither, contract.enums, result, target, convert) if contract == target: - filename_source_code = contract.source_mapping['filename_absolute'] - full_txt_start = contract.source_mapping['start'] - full_txt_end = full_txt_start + contract.source_mapping['length'] - full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end] + filename_source_code = contract.source_mapping["filename_absolute"] + full_txt_start = contract.source_mapping["start"] + full_txt_end = full_txt_start + contract.source_mapping["length"] + full_txt = slither.source_code[filename_source_code].encode("utf8")[ + full_txt_start:full_txt_end + ] old_str = contract.name new_str = convert(old_str, slither) # The name is after the space - matches = re.finditer(b'contract[ ]*', full_txt) + matches = re.finditer(b"contract[ ]*", full_txt) # Look for the end offset of the largest list of ' ' loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end() loc_end = loc_start + len(old_str) - create_patch(result, - filename_source_code, - loc_start, - loc_end, - old_str, - new_str) + create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) def _explore(slither, result, target, convert): @@ -591,8 +656,4 @@ def _explore(slither, result, target, convert): _explore_contract(slither, contract, result, target, convert) - - # endregion - - diff --git a/slither/formatters/utils/patches.py b/slither/formatters/utils/patches.py index d92c42c56..72b25a97e 100644 --- a/slither/formatters/utils/patches.py +++ b/slither/formatters/utils/patches.py @@ -2,42 +2,41 @@ import os import difflib from collections import defaultdict + def create_patch(result, file, start, end, old_str, new_str): if isinstance(old_str, bytes): - old_str = old_str.decode('utf8') + old_str = old_str.decode("utf8") if isinstance(new_str, bytes): - new_str = new_str.decode('utf8') - p = {"start": start, - "end": end, - "old_string": old_str, - "new_string": new_str - } - if 'patches' not in result: - result['patches'] = defaultdict(list) - if p not in result['patches'][file]: - result['patches'][file].append(p) + new_str = new_str.decode("utf8") + p = {"start": start, "end": end, "old_string": old_str, "new_string": new_str} + if "patches" not in result: + result["patches"] = defaultdict(list) + if p not in result["patches"][file]: + result["patches"][file].append(p) def apply_patch(original_txt, patch, offset): - patched_txt = original_txt[:int(patch['start'] + offset)] - patched_txt += patch['new_string'].encode('utf8') - patched_txt += original_txt[int(patch['end'] + offset):] + patched_txt = original_txt[: int(patch["start"] + offset)] + patched_txt += patch["new_string"].encode("utf8") + patched_txt += original_txt[int(patch["end"] + offset) :] # Keep the diff of text added or sub, in case of multiple patches - patch_length_diff = len(patch['new_string']) - (patch['end'] - patch['start']) + patch_length_diff = len(patch["new_string"]) - (patch["end"] - patch["start"]) return patched_txt, patch_length_diff + offset def create_diff(slither, original_txt, patched_txt, filename): if slither.crytic_compile: relative_path = slither.crytic_compile.filename_lookup(filename).relative - relative_path = os.path.join('.', relative_path) + relative_path = os.path.join(".", relative_path) else: relative_path = filename - diff = difflib.unified_diff(original_txt.decode('utf8').splitlines(False), - patched_txt.decode('utf8').splitlines(False), - fromfile=relative_path, - tofile=relative_path, - lineterm='') + diff = difflib.unified_diff( + original_txt.decode("utf8").splitlines(False), + patched_txt.decode("utf8").splitlines(False), + fromfile=relative_path, + tofile=relative_path, + lineterm="", + ) - return '\n'.join(list(diff)) + '\n' + return "\n".join(list(diff)) + "\n" diff --git a/slither/formatters/variables/possible_const_state_variables.py b/slither/formatters/variables/possible_const_state_variables.py index 2c4c378dd..da52aa89f 100644 --- a/slither/formatters/variables/possible_const_state_variables.py +++ b/slither/formatters/variables/possible_const_state_variables.py @@ -2,37 +2,45 @@ import re from slither.formatters.exceptions import FormatError, FormatImpossible from slither.formatters.utils.patches import create_patch + def format(slither, result): - elements = result['elements'] + elements = result["elements"] for element in elements: # TODO: decide if this should be changed in the constant detector - contract_name = element['type_specific_fields']['parent']['name'] + contract_name = element["type_specific_fields"]["parent"]["name"] contract = slither.get_contract_from_name(contract_name) - var = contract.get_state_variable_from_name(element['name']) + var = contract.get_state_variable_from_name(element["name"]) if not var.expression: - raise FormatImpossible(f'{var.name} is uninitialized and cannot become constant.') + raise FormatImpossible(f"{var.name} is uninitialized and cannot become constant.") - _patch(slither, result, element['source_mapping']['filename_absolute'], - element['name'], - "constant " + element['name'], - element['source_mapping']['start'], - element['source_mapping']['start'] + element['source_mapping']['length']) + _patch( + slither, + result, + element["source_mapping"]["filename_absolute"], + element["name"], + "constant " + element["name"], + element["source_mapping"]["start"], + element["source_mapping"]["start"] + element["source_mapping"]["length"], + ) def _patch(slither, result, in_file, match_text, replace_text, modify_loc_start, modify_loc_end): - in_file_str = slither.source_code[in_file].encode('utf8') + in_file_str = slither.source_code[in_file].encode("utf8") old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end] # Add keyword `constant` before the variable name - (new_str_of_interest, num_repl) = re.subn(match_text, replace_text, old_str_of_interest.decode('utf-8'), 1) + (new_str_of_interest, num_repl) = re.subn( + match_text, replace_text, old_str_of_interest.decode("utf-8"), 1 + ) if num_repl != 0: - create_patch(result, - in_file, - modify_loc_start, - modify_loc_end, - old_str_of_interest, - new_str_of_interest) + create_patch( + result, + in_file, + modify_loc_start, + modify_loc_end, + old_str_of_interest, + new_str_of_interest, + ) else: raise FormatError("State variable not found?!") - diff --git a/slither/formatters/variables/unused_state_variables.py b/slither/formatters/variables/unused_state_variables.py index c1a6e535c..8f8262383 100644 --- a/slither/formatters/variables/unused_state_variables.py +++ b/slither/formatters/variables/unused_state_variables.py @@ -2,27 +2,31 @@ from slither.formatters.utils.patches import create_patch def format(slither, result): - elements = result['elements'] + elements = result["elements"] for element in elements: - if element['type'] == "variable": - _patch(slither, - result, - element['source_mapping']['filename_absolute'], - element['source_mapping']['start']) + if element["type"] == "variable": + _patch( + slither, + result, + element["source_mapping"]["filename_absolute"], + element["source_mapping"]["start"], + ) def _patch(slither, result, in_file, modify_loc_start): - in_file_str = slither.source_code[in_file].encode('utf8') + in_file_str = slither.source_code[in_file].encode("utf8") old_str_of_interest = in_file_str[modify_loc_start:] - old_str = old_str_of_interest.decode('utf-8').partition(';')[0]\ - + old_str_of_interest.decode('utf-8').partition(';')[1] - - create_patch(result, - in_file, - int(modify_loc_start), - # Remove the entire declaration until the semicolon - int(modify_loc_start + len(old_str_of_interest.decode('utf-8').partition(';')[0]) + 1), - old_str, - "") - + old_str = ( + old_str_of_interest.decode("utf-8").partition(";")[0] + + old_str_of_interest.decode("utf-8").partition(";")[1] + ) + create_patch( + result, + in_file, + int(modify_loc_start), + # Remove the entire declaration until the semicolon + int(modify_loc_start + len(old_str_of_interest.decode("utf-8").partition(";")[0]) + 1), + old_str, + "", + ) diff --git a/slither/printers/abstract_printer.py b/slither/printers/abstract_printer.py index ceaa75d5d..7d6968757 100644 --- a/slither/printers/abstract_printer.py +++ b/slither/printers/abstract_printer.py @@ -8,10 +8,10 @@ class IncorrectPrinterInitialization(Exception): class AbstractPrinter(metaclass=abc.ABCMeta): - ARGUMENT = '' # run the printer with slither.py --ARGUMENT - HELP = '' # help information + ARGUMENT = "" # run the printer with slither.py --ARGUMENT + HELP = "" # help information - WIKI = '' + WIKI = "" def __init__(self, slither, logger): self.slither = slither @@ -20,24 +20,29 @@ class AbstractPrinter(metaclass=abc.ABCMeta): self.logger = logger if not self.HELP: - raise IncorrectPrinterInitialization('HELP is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectPrinterInitialization( + "HELP is not initialized {}".format(self.__class__.__name__) + ) if not self.ARGUMENT: - raise IncorrectPrinterInitialization('ARGUMENT is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectPrinterInitialization( + "ARGUMENT is not initialized {}".format(self.__class__.__name__) + ) if not self.WIKI: - raise IncorrectPrinterInitialization('WIKI is not initialized {}'.format(self.__class__.__name__)) + raise IncorrectPrinterInitialization( + "WIKI is not initialized {}".format(self.__class__.__name__) + ) def info(self, info): if self.logger: self.logger.info(info) - def generate_output(self, info, additional_fields=None): if additional_fields is None: additional_fields = {} d = output.Output(info, additional_fields) - d.data['printer'] = self.ARGUMENT + d.data["printer"] = self.ARGUMENT return d diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index 97f19b853..699c0ae94 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -15,4 +15,4 @@ from .summary.modifier_calls import Modifiers from .summary.require_calls import RequireOrAssert from .summary.constructor_calls import ConstructorPrinter from .guidance.echidna import Echidna -from .summary.evm import PrinterEVM \ No newline at end of file +from .summary.evm import PrinterEVM diff --git a/slither/printers/call/call_graph.py b/slither/printers/call/call_graph.py index deab6594b..3085e1513 100644 --- a/slither/printers/call/call_graph.py +++ b/slither/printers/call/call_graph.py @@ -12,61 +12,66 @@ from slither.core.declarations.function import Function from slither.core.variables.variable import Variable - def _contract_subgraph(contract): - return f'cluster_{contract.id}_{contract.name}' + return f"cluster_{contract.id}_{contract.name}" + # return unique id for contract function to use as node name def _function_node(contract, function): - return f'{contract.id}_{function.name}' + return f"{contract.id}_{function.name}" + # return unique id for solidity function to use as node name def _solidity_function_node(solidity_function): - return f'{solidity_function.name}' + return f"{solidity_function.name}" + # return dot language string to add graph edge def _edge(from_node, to_node): return f'"{from_node}" -> "{to_node}"' + # return dot language string to add graph node (with optional label) def _node(node, label=None): - return ' '.join(( - f'"{node}"', - f'[label="{label}"]' if label is not None else '', - )) + return " ".join((f'"{node}"', f'[label="{label}"]' if label is not None else "",)) + class PrinterCallGraph(AbstractPrinter): - ARGUMENT = 'call-graph' - HELP = 'Export the call-graph of the contracts to a dot file' + ARGUMENT = "call-graph" + HELP = "Export the call-graph of the contracts to a dot file" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph" def _process_functions(self, functions): - contract_functions = defaultdict(set) # contract -> contract functions nodes - contract_calls = defaultdict(set) # contract -> contract calls edges + contract_functions = defaultdict(set) # contract -> contract functions nodes + contract_calls = defaultdict(set) # contract -> contract calls edges - solidity_functions = set() # solidity function nodes - solidity_calls = set() # solidity calls edges - external_calls = set() # external calls edges + solidity_functions = set() # solidity function nodes + solidity_calls = set() # solidity calls edges + external_calls = set() # external calls edges all_contracts = set() for function in functions: all_contracts.add(function.contract_declarer) for function in functions: - self._process_function(function.contract_declarer, - function, - contract_functions, - contract_calls, - solidity_functions, - solidity_calls, - external_calls, - all_contracts) - - render_internal_calls = '' + self._process_function( + function.contract_declarer, + function, + contract_functions, + contract_calls, + solidity_functions, + solidity_calls, + external_calls, + all_contracts, + ) + + render_internal_calls = "" for contract in all_contracts: - render_internal_calls += self._render_internal_calls(contract, contract_functions, contract_calls) + render_internal_calls += self._render_internal_calls( + contract, contract_functions, contract_calls + ) render_solidity_calls = self._render_solidity_calls(solidity_functions, solidity_calls) @@ -74,32 +79,49 @@ class PrinterCallGraph(AbstractPrinter): return render_internal_calls + render_solidity_calls + render_external_calls - def _process_function(self, contract, function, contract_functions, contract_calls, solidity_functions, solidity_calls, external_calls, all_contracts): - contract_functions[contract].add( - _node(_function_node(contract, function), function.name), - ) + def _process_function( + self, + contract, + function, + contract_functions, + contract_calls, + solidity_functions, + solidity_calls, + external_calls, + all_contracts, + ): + contract_functions[contract].add(_node(_function_node(contract, function), function.name),) for internal_call in function.internal_calls: - self._process_internal_call(contract, function, internal_call, contract_calls, solidity_functions, solidity_calls) + self._process_internal_call( + contract, + function, + internal_call, + contract_calls, + solidity_functions, + solidity_calls, + ) for external_call in function.high_level_calls: - self._process_external_call(contract, function, external_call, contract_functions, external_calls, all_contracts) + self._process_external_call( + contract, function, external_call, contract_functions, external_calls, all_contracts + ) - def _process_internal_call(self, contract, function, internal_call, contract_calls, solidity_functions, solidity_calls): + def _process_internal_call( + self, contract, function, internal_call, contract_calls, solidity_functions, solidity_calls + ): if isinstance(internal_call, (Function)): - contract_calls[contract].add(_edge( - _function_node(contract, function), - _function_node(contract, internal_call), - )) + contract_calls[contract].add( + _edge(_function_node(contract, function), _function_node(contract, internal_call),) + ) elif isinstance(internal_call, (SolidityFunction)): - solidity_functions.add( - _node(_solidity_function_node(internal_call)), + solidity_functions.add(_node(_solidity_function_node(internal_call)),) + solidity_calls.add( + _edge(_function_node(contract, function), _solidity_function_node(internal_call),) ) - solidity_calls.add(_edge( - _function_node(contract, function), - _solidity_function_node(internal_call), - )) - def _process_external_call(self, contract, function, external_call, contract_functions, external_calls, all_contracts): + def _process_external_call( + self, contract, function, external_call, contract_functions, external_calls, all_contracts + ): external_contract, external_function = external_call if not external_contract in all_contracts: @@ -107,46 +129,45 @@ class PrinterCallGraph(AbstractPrinter): # add variable as node to respective contract if isinstance(external_function, (Variable)): - contract_functions[external_contract].add(_node( - _function_node(external_contract, external_function), - external_function.name - )) + contract_functions[external_contract].add( + _node(_function_node(external_contract, external_function), external_function.name) + ) - external_calls.add(_edge( - _function_node(contract, function), - _function_node(external_contract, external_function), - )) + external_calls.add( + _edge( + _function_node(contract, function), + _function_node(external_contract, external_function), + ) + ) def _render_internal_calls(self, contract, contract_functions, contract_calls): lines = [] - lines.append(f'subgraph {_contract_subgraph(contract)} {{') + lines.append(f"subgraph {_contract_subgraph(contract)} {{") lines.append(f'label = "{contract.name}"') lines.extend(contract_functions[contract]) lines.extend(contract_calls[contract]) - lines.append('}') + lines.append("}") - return '\n'.join(lines) + return "\n".join(lines) def _render_solidity_calls(self, solidity_functions, solidity_calls): lines = [] - lines.append('subgraph cluster_solidity {') + lines.append("subgraph cluster_solidity {") lines.append('label = "[Solidity]"') lines.extend(solidity_functions) lines.extend(solidity_calls) - lines.append('}') + lines.append("}") - return '\n'.join(lines) + return "\n".join(lines) def _render_external_calls(self, external_calls): - return '\n'.join(external_calls) - - + return "\n".join(external_calls) def output(self, filename): """ @@ -155,23 +176,29 @@ class PrinterCallGraph(AbstractPrinter): filename(string) """ - if not filename.endswith('.dot'): - filename += '.dot' + if not filename.endswith(".dot"): + filename += ".dot" if filename == ".dot": filename = "all_contracts.dot" - info = '' + info = "" results = [] - with open(filename, 'w', encoding='utf8') as f: - info += f'Call Graph: {filename}\n' - content = '\n'.join(['strict digraph {'] + [self._process_functions(self.slither.functions)] + ['}']) + with open(filename, "w", encoding="utf8") as f: + info += f"Call Graph: {filename}\n" + content = "\n".join( + ["strict digraph {"] + [self._process_functions(self.slither.functions)] + ["}"] + ) f.write(content) results.append((filename, content)) for derived_contract in self.slither.contracts_derived: - with open(f'{derived_contract.name}.dot', 'w', encoding='utf8') as f: - info += f'Call Graph: {derived_contract.name}.dot\n' - content = '\n'.join(['strict digraph {'] + [self._process_functions(derived_contract.functions)] + ['}']) + with open(f"{derived_contract.name}.dot", "w", encoding="utf8") as f: + info += f"Call Graph: {derived_contract.name}.dot\n" + content = "\n".join( + ["strict digraph {"] + + [self._process_functions(derived_contract.functions)] + + ["}"] + ) f.write(content) results.append((filename, content)) @@ -181,4 +208,3 @@ class PrinterCallGraph(AbstractPrinter): res.add_file(filename, content) return res - diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index 70c281a6c..af66b3b9d 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -9,10 +9,10 @@ from slither.utils.myprettytable import MyPrettyTable class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): - ARGUMENT = 'vars-and-auth' - HELP = 'Print the state variables written and the authorization of the functions' + ARGUMENT = "vars-and-auth" + HELP = "Print the state variables written and the authorization of the functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization" @staticmethod def get_msg_sender_checks(function): @@ -21,10 +21,14 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): all_nodes = [f.nodes for f in all_functions if isinstance(f, Function)] all_nodes = [item for sublist in all_nodes for item in sublist] - all_conditional_nodes = [n for n in all_nodes if\ - n.contains_if() or n.contains_require_or_assert()] - all_conditional_nodes_on_msg_sender = [str(n.expression) for n in all_conditional_nodes if\ - 'msg.sender' in [v.name for v in n.solidity_variables_read]] + all_conditional_nodes = [ + n for n in all_nodes if n.contains_if() or n.contains_require_or_assert() + ] + all_conditional_nodes_on_msg_sender = [ + str(n.expression) + for n in all_conditional_nodes + if "msg.sender" in [v.name for v in n.solidity_variables_read] + ] return all_conditional_nodes_on_msg_sender def output(self, _filename): @@ -34,24 +38,28 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): _filename(string) """ - txt = '' + txt = "" all_tables = [] for contract in self.contracts: if contract.is_top_level: continue - txt += "\nContract %s\n"%contract.name - table = MyPrettyTable(["Function", "State variables written", "Conditions on msg.sender"]) + txt += "\nContract %s\n" % contract.name + table = MyPrettyTable( + ["Function", "State variables written", "Conditions on msg.sender"] + ) for function in contract.functions: state_variables_written = [v.name for v in function.all_state_variables_written()] msg_sender_condition = self.get_msg_sender_checks(function) - table.add_row([function.name, str(state_variables_written), str(msg_sender_condition)]) + table.add_row( + [function.name, str(state_variables_written), str(msg_sender_condition)] + ) all_tables.append((contract.name, table)) - txt += str(table) + '\n' + txt += str(table) + "\n" self.info(txt) res = self.generate_output(txt) for name, table in all_tables: res.add_pretty_table(table, name) - return res \ No newline at end of file + return res diff --git a/slither/printers/functions/cfg.py b/slither/printers/functions/cfg.py index 74c5c0f1f..963c98bba 100644 --- a/slither/printers/functions/cfg.py +++ b/slither/printers/functions/cfg.py @@ -6,10 +6,10 @@ from slither.printers.abstract_printer import AbstractPrinter class CFG(AbstractPrinter): - ARGUMENT = 'cfg' - HELP = 'Export the CFG of each functions' + ARGUMENT = "cfg" + HELP = "Export the CFG of each functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg" def output(self, original_filename): """ @@ -18,19 +18,21 @@ class CFG(AbstractPrinter): _filename(string) """ - info = '' + info = "" all_files = [] for contract in self.contracts: if contract.is_top_level: continue for function in contract.functions + contract.modifiers: if original_filename: - filename = "{}-{}-{}.dot".format(original_filename, contract.name, function.full_name) + filename = "{}-{}-{}.dot".format( + original_filename, contract.name, function.full_name + ) else: filename = "{}-{}.dot".format(contract.name, function.full_name) - info += 'Export {}\n'.format(filename) + info += "Export {}\n".format(filename) content = function.slithir_cfg_to_dot_str() - with open(filename, 'w', encoding='utf8') as f: + with open(filename, "w", encoding="utf8") as f: f.write(content) all_files.append((filename, content)) @@ -39,4 +41,4 @@ class CFG(AbstractPrinter): res = self.generate_output(info) for filename, content in all_files: res.add_file(filename, content) - return res \ No newline at end of file + return res diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 9036c09ce..46031be45 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -8,21 +8,37 @@ from typing import Dict, List, Set, Tuple, Union, NamedTuple from slither.analyses.data_dependency.data_dependency import is_dependent from slither.core.cfg.node import Node from slither.core.declarations import Function -from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction, SolidityVariable +from slither.core.declarations.solidity_variables import ( + SolidityVariableComposed, + SolidityFunction, + SolidityVariable, +) from slither.core.expressions import NewContract from slither.core.slither_core import SlitherCore from slither.core.variables.state_variable import StateVariable from slither.core.variables.variable import Variable from slither.printers.abstract_printer import AbstractPrinter -from slither.slithir.operations import Member, Operation, SolidityCall, LowLevelCall, HighLevelCall, EventCall, Send, \ - Transfer, InternalDynamicCall, InternalCall, TypeConversion, Balance +from slither.slithir.operations import ( + Member, + Operation, + SolidityCall, + LowLevelCall, + HighLevelCall, + EventCall, + Send, + Transfer, + InternalDynamicCall, + InternalCall, + TypeConversion, + Balance, +) from slither.slithir.operations.binary import Binary, BinaryType from slither.slithir.variables import Constant def _get_name(f: Function) -> str: if f.is_fallback or f.is_receive: - return f'()' + return f"()" return f.solidity_signature @@ -35,7 +51,9 @@ def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]: return ret -def _extract_solidity_variable_usage(slither: SlitherCore, sol_var: SolidityVariable) -> Dict[str, List[str]]: +def _extract_solidity_variable_usage( + slither: SlitherCore, sol_var: SolidityVariable +) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in slither.contracts: functions_using_sol_var = [] @@ -61,7 +79,7 @@ def _is_constant(f: Function) -> bool: :return: """ if f.view or f.pure: - if not f.contract.slither.crytic_compile.compiler_version.version.startswith('0.4'): + if not f.contract.slither.crytic_compile.compiler_version.version.startswith("0.4"): return True if f.payable: return False @@ -76,13 +94,15 @@ def _is_constant(f: Function) -> bool: return False if isinstance(ir, (EventCall, NewContract, LowLevelCall, Send, Transfer)): return False - if isinstance(ir, SolidityCall) and ir.function in [SolidityFunction('selfdestruct(address)'), - SolidityFunction('suicide(address)')]: + if isinstance(ir, SolidityCall) and ir.function in [ + SolidityFunction("selfdestruct(address)"), + SolidityFunction("suicide(address)"), + ]: return False if isinstance(ir, HighLevelCall): if isinstance(ir.function, Variable) or ir.function.view or ir.function.pure: # External call to constant functions are ensured to be constant only for solidity >= 0.5 - if f.contract.slither.crytic_compile.compiler_version.version.startswith('0.4'): + if f.contract.slither.crytic_compile.compiler_version.version.startswith("0.4"): return False else: return False @@ -97,7 +117,9 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in slither.contracts: cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)] - cst_functions += [v.function_name for v in contract.state_variables if v.visibility in ['public']] + cst_functions += [ + v.function_name for v in contract.state_variables if v.visibility in ["public"] + ] if cst_functions: ret[contract.name] = cst_functions return ret @@ -109,7 +131,7 @@ def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]: functions_using_assert = [] for f in contract.functions_entry_points: for v in f.all_solidity_calls(): - if v == SolidityFunction('assert(bool)'): + if v == SolidityFunction("assert(bool)"): functions_using_assert.append(_get_name(f)) break if functions_using_assert: @@ -120,9 +142,7 @@ def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]: # Create a named tuple that is serialization in json def json_serializable(cls): def as_dict(self): - yield {name: value for name, value in zip( - self._fields, - iter(super(cls, self).__iter__()))} + yield {name: value for name, value in zip(self._fields, iter(super(cls, self).__iter__()))} cls.__iter__ = as_dict return cls @@ -137,15 +157,19 @@ class ConstantValue(NamedTuple): type: str -def _extract_constants_from_irs(irs: List[Operation], - all_cst_used: List[ConstantValue], - all_cst_used_in_binary: Dict[str, List[ConstantValue]], - context_explored: Set[Node]): +def _extract_constants_from_irs( + irs: List[Operation], + all_cst_used: List[ConstantValue], + all_cst_used_in_binary: Dict[str, List[ConstantValue]], + context_explored: Set[Node], +): for ir in irs: if isinstance(ir, Binary): for r in ir.read: if isinstance(r, Constant): - all_cst_used_in_binary[str(ir.type)].append(ConstantValue(str(r.value), str(r.type))) + all_cst_used_in_binary[str(ir.type)].append( + ConstantValue(str(r.value), str(r.type)) + ) if isinstance(ir, TypeConversion): if isinstance(ir.variable, Constant): all_cst_used.append(ConstantValue(str(ir.variable.value), str(ir.type))) @@ -163,13 +187,17 @@ def _extract_constants_from_irs(irs: List[Operation], continue else: context_explored.add(r.node_initialization) - _extract_constants_from_irs(r.node_initialization.irs, - all_cst_used, - all_cst_used_in_binary, - context_explored) + _extract_constants_from_irs( + r.node_initialization.irs, + all_cst_used, + all_cst_used_in_binary, + context_explored, + ) -def _extract_constants(slither: SlitherCore) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]: +def _extract_constants( + slither: SlitherCore, +) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]: # contract -> function -> [ {"value": value, "type": type} ] ret_cst_used: Dict[str, Dict[str, List[ConstantValue]]] = defaultdict(dict) # contract -> function -> binary_operand -> [ {"value": value, "type": type ] @@ -181,18 +209,21 @@ def _extract_constants(slither: SlitherCore) -> Tuple[Dict[str, Dict[str, List]] context_explored = set() context_explored.add(function) - _extract_constants_from_irs(function.all_slithir_operations(), - all_cst_used, - all_cst_used_in_binary, - context_explored) + _extract_constants_from_irs( + function.all_slithir_operations(), + all_cst_used, + all_cst_used_in_binary, + context_explored, + ) # Note: use list(set()) instead of set # As this is meant to be serialized in JSON, and JSON does not support set if all_cst_used: ret_cst_used[contract.name][_get_name(function)] = list(set(all_cst_used)) if all_cst_used_in_binary: - ret_cst_used_in_binary[contract.name][_get_name(function)] = {k: list(set(v)) for k, v in - all_cst_used_in_binary.items()} + ret_cst_used_in_binary[contract.name][_get_name(function)] = { + k: list(set(v)) for k, v in all_cst_used_in_binary.items() + } return ret_cst_used, ret_cst_used_in_binary @@ -201,13 +232,16 @@ def _extract_function_relations(slither: SlitherCore) -> Dict[str, Dict[str, Dic ret: Dict[str, Dict[str, Dict[str, List[str]]]] = defaultdict(dict) for contract in slither.contracts: ret[contract.name] = defaultdict(dict) - written = {_get_name(function): function.all_state_variables_written() - for function in contract.functions_entry_points} - read = {_get_name(function): function.all_state_variables_read() - for function in contract.functions_entry_points} + written = { + _get_name(function): function.all_state_variables_written() + for function in contract.functions_entry_points + } + read = { + _get_name(function): function.all_state_variables_read() + for function in contract.functions_entry_points + } for function in contract.functions_entry_points: - ret[contract.name][_get_name(function)] = {"impacts": [], - "is_impacted_by": []} + ret[contract.name][_get_name(function)] = {"impacts": [], "is_impacted_by": []} for candidate, varsWritten in written.items(): if any((r in varsWritten for r in function.all_state_variables_read())): ret[contract.name][_get_name(function)]["is_impacted_by"].append(candidate) @@ -264,27 +298,31 @@ def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]: if isinstance(ir, HighLevelCall): for idx, parameter in enumerate(function.parameters): if is_dependent(ir.destination, parameter, function): - ret[contract.name].append({ - "function": _get_name(function), - "parameter_idx": idx, - "signature": _get_name(ir.function) - }) + ret[contract.name].append( + { + "function": _get_name(function), + "parameter_idx": idx, + "signature": _get_name(ir.function), + } + ) if isinstance(ir, LowLevelCall): for idx, parameter in enumerate(function.parameters): if is_dependent(ir.destination, parameter, function): - ret[contract.name].append({ - "function": _get_name(function), - "parameter_idx": idx, - "signature": None - }) + ret[contract.name].append( + { + "function": _get_name(function), + "parameter_idx": idx, + "signature": None, + } + ) return ret class Echidna(AbstractPrinter): - ARGUMENT = 'echidna' - HELP = 'Export Echidna guiding information' + ARGUMENT = "echidna" + HELP = "Export Echidna guiding information" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna" def output(self, filename): """ @@ -296,22 +334,29 @@ class Echidna(AbstractPrinter): """ payable = _extract_payable(self.slither) - timestamp = _extract_solidity_variable_usage(self.slither, - SolidityVariableComposed('block.timestamp')) - block_number = _extract_solidity_variable_usage(self.slither, - SolidityVariableComposed('block.number')) - msg_sender = _extract_solidity_variable_usage(self.slither, - SolidityVariableComposed('msg.sender')) - msg_gas = _extract_solidity_variable_usage(self.slither, - SolidityVariableComposed('msg.gas')) + timestamp = _extract_solidity_variable_usage( + self.slither, SolidityVariableComposed("block.timestamp") + ) + block_number = _extract_solidity_variable_usage( + self.slither, SolidityVariableComposed("block.number") + ) + msg_sender = _extract_solidity_variable_usage( + self.slither, SolidityVariableComposed("msg.sender") + ) + msg_gas = _extract_solidity_variable_usage( + self.slither, SolidityVariableComposed("msg.gas") + ) assert_usage = _extract_assert(self.slither) cst_functions = _extract_constant_functions(self.slither) (cst_used, cst_used_in_binary) = _extract_constants(self.slither) functions_relations = _extract_function_relations(self.slither) - constructors = {contract.name: contract.constructor.full_name - for contract in self.slither.contracts if contract.constructor} + constructors = { + contract.name: contract.constructor.full_name + for contract in self.slither.contracts + if contract.constructor + } external_calls = _have_external_calls(self.slither) @@ -319,20 +364,22 @@ class Echidna(AbstractPrinter): use_balance = _use_balance(self.slither) - d = {'payable': payable, - 'timestamp': timestamp, - 'block_number': block_number, - 'msg_sender': msg_sender, - 'msg_gas': msg_gas, - 'assert': assert_usage, - 'constant_functions': cst_functions, - 'constants_used': cst_used, - 'constants_used_in_binary': cst_used_in_binary, - 'functions_relations': functions_relations, - 'constructors': constructors, - 'have_external_calls': external_calls, - 'call_a_parameter': call_parameters, - 'use_balance': use_balance} + d = { + "payable": payable, + "timestamp": timestamp, + "block_number": block_number, + "msg_sender": msg_sender, + "msg_gas": msg_gas, + "assert": assert_usage, + "constant_functions": cst_functions, + "constants_used": cst_used, + "constants_used_in_binary": cst_used_in_binary, + "functions_relations": functions_relations, + "constructors": constructors, + "have_external_calls": external_calls, + "call_a_parameter": call_parameters, + "use_balance": use_balance, + } self.info(json.dumps(d, indent=4)) diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index ffeb81007..79d009f83 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -9,10 +9,10 @@ from slither.utils.colors import blue, green class PrinterInheritance(AbstractPrinter): - ARGUMENT = 'inheritance' - HELP = 'Print the inheritance relations between contracts' + ARGUMENT = "inheritance" + HELP = "Print the inheritance relations between contracts" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance" def _get_child_contracts(self, base): # Generate function to get all child contracts of a base contract @@ -28,54 +28,54 @@ class PrinterInheritance(AbstractPrinter): Args: _filename(string) """ - info = 'Inheritance\n' + info = "Inheritance\n" if not self.contracts: return - info += blue('Child_Contract -> ') + green('Immediate_Base_Contracts') - info += green(' [Not_Immediate_Base_Contracts]') + info += blue("Child_Contract -> ") + green("Immediate_Base_Contracts") + info += green(" [Not_Immediate_Base_Contracts]") - result = {'child_to_base': {}} + result = {"child_to_base": {}} for child in self.contracts: if child.is_top_level: continue - info += blue(f'\n+ {child.name}\n') - result['child_to_base'][child.name] = {'immediate': [], - 'not_immediate': []} + info += blue(f"\n+ {child.name}\n") + result["child_to_base"][child.name] = {"immediate": [], "not_immediate": []} if child.inheritance: immediate = child.immediate_inheritance not_immediate = [i for i in child.inheritance if i not in immediate] - info += ' -> ' + green(", ".join(map(str, immediate))) + '\n' - result['child_to_base'][child.name]['immediate'] = list(map(str, immediate)) + info += " -> " + green(", ".join(map(str, immediate))) + "\n" + result["child_to_base"][child.name]["immediate"] = list(map(str, immediate)) if not_immediate: - info += ", ["+ green(", ".join(map(str, not_immediate))) + "]\n" - result['child_to_base'][child.name]['not_immediate'] = list(map(str, not_immediate)) + info += ", [" + green(", ".join(map(str, not_immediate))) + "]\n" + result["child_to_base"][child.name]["not_immediate"] = list( + map(str, not_immediate) + ) - info += green('\n\nBase_Contract -> ') + blue('Immediate_Child_Contracts') + '\n' - info += blue(' [Not_Immediate_Child_Contracts]') + '\n' + info += green("\n\nBase_Contract -> ") + blue("Immediate_Child_Contracts") + "\n" + info += blue(" [Not_Immediate_Child_Contracts]") + "\n" - result['base_to_child'] = {} + result["base_to_child"] = {} for base in self.contracts: if base.is_top_level: continue - info += green(f'\n+ {base.name}') + '\n' + info += green(f"\n+ {base.name}") + "\n" children = list(self._get_child_contracts(base)) - result['base_to_child'][base.name] = {'immediate': [], - 'not_immediate': []} + result["base_to_child"][base.name] = {"immediate": [], "not_immediate": []} if children: immediate = [child for child in children if base in child.immediate_inheritance] not_immediate = [child for child in children if not child in immediate] - info += ' -> ' + blue(", ".join(map(str, immediate))) + '\n' - result['base_to_child'][base.name]['immediate'] = list(map(str, immediate)) + info += " -> " + blue(", ".join(map(str, immediate))) + "\n" + result["base_to_child"][base.name]["immediate"] = list(map(str, immediate)) if not_immediate: - info += ', [' + blue(", ".join(map(str, not_immediate))) + ']' + '\n' - result['base_to_child'][base.name]['not_immediate'] = list(map(str, immediate)) + info += ", [" + blue(", ".join(map(str, not_immediate))) + "]" + "\n" + result["base_to_child"][base.name]["not_immediate"] = list(map(str, immediate)) self.info(info) res = self.generate_output(info, additional_fields=result) diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py index e1311d7a3..a560abe25 100644 --- a/slither/printers/inheritance/inheritance_graph.py +++ b/slither/printers/inheritance/inheritance_graph.py @@ -9,15 +9,17 @@ from slither.core.declarations.contract import Contract from slither.core.solidity_types.user_defined_type import UserDefinedType from slither.printers.abstract_printer import AbstractPrinter -from slither.utils.inheritance_analysis import (detect_c3_function_shadowing, - detect_state_variable_shadowing) +from slither.utils.inheritance_analysis import ( + detect_c3_function_shadowing, + detect_state_variable_shadowing, +) class PrinterInheritanceGraph(AbstractPrinter): - ARGUMENT = 'inheritance-graph' - HELP = 'Export the inheritance graph of each contract to a dot file' + ARGUMENT = "inheritance-graph" + HELP = "Export the inheritance graph of each contract to a dot file" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph" def __init__(self, slither, logger): super(PrinterInheritanceGraph, self).__init__(slither, logger) @@ -49,7 +51,9 @@ class PrinterInheritanceGraph(AbstractPrinter): # Html pattern, each line is a row in a table var_name = var.name pattern = ' %s' - pattern_contract = ' %s (%s)' + pattern_contract = ( + ' %s (%s)' + ) pattern_shadow = ' %s' pattern_contract_shadow = ' %s (%s)' @@ -76,10 +80,14 @@ class PrinterInheritanceGraph(AbstractPrinter): result = [] indirect_shadows = detect_c3_function_shadowing(contract) for winner, colliding_functions in indirect_shadows.items(): - collision_steps = ', '.join([f.contract_declarer.name - for f in colliding_functions] + [winner.contract_declarer.name]) - result.append(f"'{winner.full_name}' collides in inherited contracts {collision_steps} where {winner.contract_declarer.name} is chosen.") - return '\n'.join(result) + collision_steps = ", ".join( + [f.contract_declarer.name for f in colliding_functions] + + [winner.contract_declarer.name] + ) + result.append( + f"'{winner.full_name}' collides in inherited contracts {collision_steps} where {winner.contract_declarer.name} is chosen." + ) + return "\n".join(result) def _get_port_id(self, var, contract): return "%s%s" % (var.name, contract.name) @@ -88,38 +96,62 @@ class PrinterInheritanceGraph(AbstractPrinter): """ Build summary using HTML """ - ret = '' + ret = "" # Add arrows (number them if there is more than one path so we know order of declaration for inheritance). if len(contract.immediate_inheritance) == 1: - ret += '%s -> %s;\n' % (contract.name, contract.immediate_inheritance[0]) + ret += "%s -> %s;\n" % (contract.name, contract.immediate_inheritance[0]) else: for i in range(0, len(contract.immediate_inheritance)): - ret += '%s -> %s [ label="%s" ];\n' % (contract.name, contract.immediate_inheritance[i], i + 1) + ret += '%s -> %s [ label="%s" ];\n' % ( + contract.name, + contract.immediate_inheritance[i], + i + 1, + ) # Functions - visibilities = ['public', 'external'] - public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and not f.is_constructor_variables - and f.contract_declarer == contract and f.visibility in visibilities] - public_functions = ''.join(public_functions) - private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and not f.is_constructor_variables - and f.contract_declarer == contract and f.visibility not in visibilities] - private_functions = ''.join(private_functions) + visibilities = ["public", "external"] + public_functions = [ + self._get_pattern_func(f, contract) + for f in contract.functions + if not f.is_constructor + and not f.is_constructor_variables + and f.contract_declarer == contract + and f.visibility in visibilities + ] + public_functions = "".join(public_functions) + private_functions = [ + self._get_pattern_func(f, contract) + for f in contract.functions + if not f.is_constructor + and not f.is_constructor_variables + and f.contract_declarer == contract + and f.visibility not in visibilities + ] + private_functions = "".join(private_functions) # Modifiers - modifiers = [self._get_pattern_func(m, contract) for m in contract.modifiers if m.contract_declarer == contract] - modifiers = ''.join(modifiers) + modifiers = [ + self._get_pattern_func(m, contract) + for m in contract.modifiers + if m.contract_declarer == contract + ] + modifiers = "".join(modifiers) # Public variables - public_variables = [self._get_pattern_var(v, contract) for v in contract.state_variables_declared - if v.visibility in visibilities] - public_variables = ''.join(public_variables) - - private_variables = [self._get_pattern_var(v, contract) for v in contract.state_variables_declared - if v.visibility not in visibilities] - private_variables = ''.join(private_variables) + public_variables = [ + self._get_pattern_var(v, contract) + for v in contract.state_variables_declared + if v.visibility in visibilities + ] + public_variables = "".join(public_variables) + + private_variables = [ + self._get_pattern_var(v, contract) + for v in contract.state_variables_declared + if v.visibility not in visibilities + ] + private_variables = "".join(private_variables) # Obtain any indirect shadowing information for this node. indirect_shadowing_information = self._get_indirect_shadowing_information(contract) @@ -130,23 +162,26 @@ class PrinterInheritanceGraph(AbstractPrinter): ret += '%s' % contract.name if public_functions: ret += 'Public Functions:' - ret += '%s' % public_functions + ret += "%s" % public_functions if private_functions: ret += 'Private Functions:' - ret += '%s' % private_functions + ret += "%s" % private_functions if modifiers: ret += 'Modifiers:' - ret += '%s' % modifiers + ret += "%s" % modifiers if public_variables: ret += 'Public Variables:' - ret += '%s' % public_variables + ret += "%s" % public_variables if private_variables: ret += 'Private Variables:' - ret += '%s' % private_variables + ret += "%s" % private_variables if indirect_shadowing_information: - ret += '
%s' % indirect_shadowing_information.replace('\n', '
') - ret += ' >];\n' + ret += ( + '
%s' + % indirect_shadowing_information.replace("\n", "
") + ) + ret += " >];\n" return ret @@ -157,24 +192,24 @@ class PrinterInheritanceGraph(AbstractPrinter): filename(string) """ - if filename == '': - filename = 'contracts.dot' - if not filename.endswith('.dot'): + if filename == "": + filename = "contracts.dot" + if not filename.endswith(".dot"): filename += ".dot" - info = 'Inheritance Graph: ' + filename + '\n' + info = "Inheritance Graph: " + filename + "\n" self.info(info) content = 'digraph "" {\n' for c in self.contracts: if c.is_top_level: continue - content += self._summary(c) + '\n' - content += '}' + content += self._summary(c) + "\n" + content += "}" - with open(filename, 'w', encoding='utf8') as f: + with open(filename, "w", encoding="utf8") as f: f.write(content) res = self.generate_output(info) res.add_file(filename, content) - return res \ No newline at end of file + return res diff --git a/slither/printers/summary/constructor_calls.py b/slither/printers/summary/constructor_calls.py index a3c33285d..c6205b02c 100644 --- a/slither/printers/summary/constructor_calls.py +++ b/slither/printers/summary/constructor_calls.py @@ -6,20 +6,20 @@ from slither.utils import output class ConstructorPrinter(AbstractPrinter): - WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls' - ARGUMENT = 'constructor-calls' - HELP = 'Print the constructors executed' + WIKI = "https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls" + ARGUMENT = "constructor-calls" + HELP = "Print the constructors executed" def _get_soruce_code(self, cst): src_mapping = cst.source_mapping - content = self.slither.source_code[src_mapping['filename_absolute']] - start = src_mapping['start'] - end = src_mapping['start'] + src_mapping['length'] - initial_space = src_mapping['starting_column'] - return ' ' * initial_space + content[start:end] + content = self.slither.source_code[src_mapping["filename_absolute"]] + start = src_mapping["start"] + end = src_mapping["start"] + src_mapping["length"] + initial_space = src_mapping["starting_column"] + return " " * initial_space + content[start:end] def output(self, _filename): - info = '' + info = "" for contract in self.slither.contracts_derived: stack_name = [] stack_definition = [] @@ -35,18 +35,18 @@ class ConstructorPrinter(AbstractPrinter): if len(stack_name) > 0: - info += '\n########' + "#" * len(contract.name) + "########\n" + info += "\n########" + "#" * len(contract.name) + "########\n" info += "####### " + contract.name + " #######\n" - info += '########' + "#" * len(contract.name) + "########\n\n" - info += "## Constructor Call Sequence" + '\n' + info += "########" + "#" * len(contract.name) + "########\n\n" + info += "## Constructor Call Sequence" + "\n" for name in stack_name[::-1]: - info += "\t- " + name + '\n' - info += "\n## Constructor Definitions" + '\n' + info += "\t- " + name + "\n" + info += "\n## Constructor Definitions" + "\n" count = len(stack_definition) - 1 while count >= 0: - info += "\n### " + stack_name[count] + '\n' - info += "\n" + str(stack_definition[count]) + '\n' + info += "\n### " + stack_name[count] + "\n" + info += "\n" + str(stack_definition[count]) + "\n" count = count - 1 self.info(info) diff --git a/slither/printers/summary/contract.py b/slither/printers/summary/contract.py index b231c0b36..7196ad7ae 100644 --- a/slither/printers/summary/contract.py +++ b/slither/printers/summary/contract.py @@ -8,10 +8,10 @@ from slither.utils.colors import blue, green, magenta class ContractSummary(AbstractPrinter): - ARGUMENT = 'contract-summary' - HELP = 'Print a summary of the contracts' + ARGUMENT = "contract-summary" + HELP = "Print a summary of the contracts" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary" def output(self, _filename): """ @@ -30,28 +30,34 @@ class ContractSummary(AbstractPrinter): is_upgradeable_proxy = c.is_upgradeable_proxy is_upgradeable = c.is_upgradeable - additional_txt_info = '' + additional_txt_info = "" if is_upgradeable_proxy: - additional_txt_info += ' (Upgradeable Proxy)' + additional_txt_info += " (Upgradeable Proxy)" if is_upgradeable: - additional_txt_info += ' (Upgradeable)' + additional_txt_info += " (Upgradeable)" if c in self.slither.contracts_derived: - additional_txt_info += ' (Most derived contract)' + additional_txt_info += " (Most derived contract)" txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n") - additional_fields = output.Output('', additional_fields={ - 'is_upgradeable_proxy': is_upgradeable_proxy, - 'is_upgradeable': is_upgradeable, - 'is_most_derived': c in self.slither.contracts_derived - }) + additional_fields = output.Output( + "", + additional_fields={ + "is_upgradeable_proxy": is_upgradeable_proxy, + "is_upgradeable": is_upgradeable, + "is_most_derived": c in self.slither.contracts_derived, + }, + ) # Order the function with # contract_declarer -> list_functions - public = [(f.contract_declarer.name, f) for f in c.functions if (not f.is_shadowed and - not f.is_constructor_variables)] + public = [ + (f.contract_declarer.name, f) + for f in c.functions + if (not f.is_shadowed and not f.is_constructor_variables) + ] collect = collections.defaultdict(list) for a, b in public: collect[a].append(b) @@ -63,15 +69,20 @@ class ContractSummary(AbstractPrinter): functions = sorted(functions, key=lambda f: f.full_name) for function in functions: - if function.visibility in ['external', 'public']: - txt += green(" - {} ({})\n".format(function.full_name, function.visibility)) - if function.visibility in ['internal', 'private']: - txt += magenta(" - {} ({})\n".format(function.full_name, function.visibility)) - if function.visibility not in ['external', 'public', 'internal', 'private']: + if function.visibility in ["external", "public"]: + txt += green( + " - {} ({})\n".format(function.full_name, function.visibility) + ) + if function.visibility in ["internal", "private"]: + txt += magenta( + " - {} ({})\n".format(function.full_name, function.visibility) + ) + if function.visibility not in ["external", "public", "internal", "private"]: txt += " - {}  ({})\n".format(function.full_name, function.visibility) - additional_fields.add(function, additional_fields={"visibility": - function.visibility}) + additional_fields.add( + function, additional_fields={"visibility": function.visibility} + ) all_contracts.append((c, additional_fields.data)) diff --git a/slither/printers/summary/data_depenency.py b/slither/printers/summary/data_depenency.py index 3e33bae57..cef518119 100644 --- a/slither/printers/summary/data_depenency.py +++ b/slither/printers/summary/data_depenency.py @@ -9,15 +9,23 @@ from slither.utils.myprettytable import MyPrettyTable def _get(v, c): - return list(set([d.name for d in get_dependencies(v, c) if not isinstance(d, (TemporaryVariable, - ReferenceVariable))])) + return list( + set( + [ + d.name + for d in get_dependencies(v, c) + if not isinstance(d, (TemporaryVariable, ReferenceVariable)) + ] + ) + ) + class DataDependency(AbstractPrinter): - ARGUMENT = 'data-dependency' - HELP = 'Print the data dependencies of the variables' + ARGUMENT = "data-dependency" + HELP = "Print the data dependencies of the variables" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#data-dependencies' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#data-dependencies" def output(self, _filename): """ @@ -27,14 +35,14 @@ class DataDependency(AbstractPrinter): """ all_tables = [] - all_txt = '' + all_txt = "" - txt = '' + txt = "" for c in self.contracts: if c.is_top_level: continue - txt += "\nContract %s\n"%c.name - table = MyPrettyTable(['Variable', 'Dependencies']) + txt += "\nContract %s\n" % c.name + table = MyPrettyTable(["Variable", "Dependencies"]) for v in c.state_variables: table.add_row([v.name, _get(v, c)]) @@ -42,8 +50,8 @@ class DataDependency(AbstractPrinter): txt += "\n" for f in c.functions_and_modifiers_declared: - txt += "\nFunction %s\n"%f.full_name - table = MyPrettyTable(['Variable', 'Dependencies']) + txt += "\nFunction %s\n" % f.full_name + table = MyPrettyTable(["Variable", "Dependencies"]) for v in f.variables: table.add_row([v.name, _get(v, f)]) for v in c.state_variables: diff --git a/slither/printers/summary/evm.py b/slither/printers/summary/evm.py index 98de8391a..c7eb472f0 100644 --- a/slither/printers/summary/evm.py +++ b/slither/printers/summary/evm.py @@ -7,10 +7,10 @@ from slither.utils.colors import blue, green, magenta, red class PrinterEVM(AbstractPrinter): - ARGUMENT = 'evm' - HELP = 'Print the evm instructions of nodes in functions' + ARGUMENT = "evm" + HELP = "Print the evm instructions of nodes in functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#evm' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#evm" def output(self, _filename): """ @@ -22,53 +22,77 @@ class PrinterEVM(AbstractPrinter): txt = "" if not self.slither.crytic_compile: - txt = 'The EVM printer requires to compile with crytic-compile' + txt = "The EVM printer requires to compile with crytic-compile" self.info(red(txt)) res = self.generate_output(txt) return res evm_info = self._extract_evm_info(self.slither) for contract in self.slither.contracts_derived: - txt += blue('Contract {}\n'.format(contract.name)) + txt += blue("Contract {}\n".format(contract.name)) - contract_file = self.slither.source_code[contract.source_mapping['filename_absolute']].encode('utf-8') - contract_file_lines = open(contract.source_mapping['filename_absolute'], 'r').readlines() + contract_file = self.slither.source_code[ + contract.source_mapping["filename_absolute"] + ].encode("utf-8") + contract_file_lines = open( + contract.source_mapping["filename_absolute"], "r" + ).readlines() contract_pcs = {} contract_cfg = {} for function in contract.functions: - txt += blue(f'\tFunction {function.canonical_name}\n') + txt += blue(f"\tFunction {function.canonical_name}\n") # CFG and source mapping depend on function being constructor or not if function.is_constructor: - contract_cfg = evm_info['cfg_init', contract.name] - contract_pcs = evm_info['mapping_init', contract.name] + contract_cfg = evm_info["cfg_init", contract.name] + contract_pcs = evm_info["mapping_init", contract.name] else: - contract_cfg = evm_info['cfg', contract.name] - contract_pcs = evm_info['mapping', contract.name] + contract_cfg = evm_info["cfg", contract.name] + contract_pcs = evm_info["mapping", contract.name] for node in function.nodes: txt += green("\t\tNode: " + str(node) + "\n") - node_source_line = contract_file[0:node.source_mapping['start']].count("\n".encode("utf-8")) + 1 - txt += green('\t\tSource line {}: {}\n'.format(node_source_line, - contract_file_lines[node_source_line - 1].rstrip())) - txt += magenta('\t\tEVM Instructions:\n') + node_source_line = ( + contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8")) + + 1 + ) + txt += green( + "\t\tSource line {}: {}\n".format( + node_source_line, contract_file_lines[node_source_line - 1].rstrip() + ) + ) + txt += magenta("\t\tEVM Instructions:\n") node_pcs = contract_pcs.get(node_source_line, []) for pc in node_pcs: - txt += magenta('\t\t\t0x{:x}: {}\n'.format(int(pc), contract_cfg.get_instruction_at(pc))) + txt += magenta( + "\t\t\t0x{:x}: {}\n".format( + int(pc), contract_cfg.get_instruction_at(pc) + ) + ) for modifier in contract.modifiers: - txt += blue(f'\tModifier {modifier.canonical_name}\n') + txt += blue(f"\tModifier {modifier.canonical_name}\n") for node in modifier.nodes: txt += green("\t\tNode: " + str(node) + "\n") - node_source_line = contract_file[0:node.source_mapping['start']].count("\n".encode("utf-8")) + 1 - txt += green('\t\tSource line {}: {}\n'.format(node_source_line, - contract_file_lines[node_source_line - 1].rstrip())) - txt += magenta('\t\tEVM Instructions:\n') + node_source_line = ( + contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8")) + + 1 + ) + txt += green( + "\t\tSource line {}: {}\n".format( + node_source_line, contract_file_lines[node_source_line - 1].rstrip() + ) + ) + txt += magenta("\t\tEVM Instructions:\n") node_pcs = contract_pcs.get(node_source_line, []) for pc in node_pcs: - txt += magenta('\t\t\t0x{:x}: {}\n'.format(int(pc), contract_cfg.get_instruction_at(pc))) + txt += magenta( + "\t\t\t0x{:x}: {}\n".format( + int(pc), contract_cfg.get_instruction_at(pc) + ) + ) self.info(txt) res = self.generate_output(txt) @@ -89,21 +113,24 @@ class PrinterEVM(AbstractPrinter): contract_bytecode_runtime = slither.crytic_compile.bytecode_runtime(contract.name) contract_srcmap_runtime = slither.crytic_compile.srcmap_runtime(contract.name) cfg = CFG(contract_bytecode_runtime) - evm_info['cfg', contract.name] = cfg - evm_info['mapping', contract.name] = generate_source_to_evm_ins_mapping( + evm_info["cfg", contract.name] = cfg + evm_info["mapping", contract.name] = generate_source_to_evm_ins_mapping( cfg.instructions, contract_srcmap_runtime, slither, - contract.source_mapping['filename_absolute']) + contract.source_mapping["filename_absolute"], + ) contract_bytecode_init = slither.crytic_compile.bytecode_init(contract.name) contract_srcmap_init = slither.crytic_compile.srcmap_init(contract.name) cfg_init = CFG(contract_bytecode_init) - evm_info['cfg_init', contract.name] = cfg_init - evm_info['mapping_init', contract.name] = generate_source_to_evm_ins_mapping( + evm_info["cfg_init", contract.name] = cfg_init + evm_info["mapping_init", contract.name] = generate_source_to_evm_ins_mapping( cfg_init.instructions, - contract_srcmap_init, slither, - contract.source_mapping['filename_absolute']) + contract_srcmap_init, + slither, + contract.source_mapping["filename_absolute"], + ) return evm_info diff --git a/slither/printers/summary/function.py b/slither/printers/summary/function.py index b334e860a..31d6ce75f 100644 --- a/slither/printers/summary/function.py +++ b/slither/printers/summary/function.py @@ -8,16 +8,16 @@ from slither.utils.myprettytable import MyPrettyTable class FunctionSummary(AbstractPrinter): - ARGUMENT = 'function-summary' - HELP = 'Print a summary of the functions' + ARGUMENT = "function-summary" + HELP = "Print a summary of the functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary" @staticmethod def _convert(l): if l: n = 2 - l = [l[i:i + n] for i in range(0, len(l), n)] + l = [l[i : i + n] for i in range(0, len(l), n)] l = [str(x) for x in l] return "\n".join(l) return str(l) @@ -30,42 +30,63 @@ class FunctionSummary(AbstractPrinter): """ all_tables = [] - all_txt = '' + all_txt = "" for c in self.contracts: if c.is_top_level: continue (name, inheritance, var, func_summaries, modif_summaries) = c.get_summary() - txt = "\nContract %s"%name - txt += '\nContract vars: '+str(var) - txt += '\nInheritance:: '+str(inheritance) - table = MyPrettyTable(["Function", - "Visibility", - "Modifiers", - "Read", - "Write", - "Internal Calls", - "External Calls"]) - for (_c_name, f_name, visi, modifiers, read, write, internal_calls, external_calls) in func_summaries: + txt = "\nContract %s" % name + txt += "\nContract vars: " + str(var) + txt += "\nInheritance:: " + str(inheritance) + table = MyPrettyTable( + [ + "Function", + "Visibility", + "Modifiers", + "Read", + "Write", + "Internal Calls", + "External Calls", + ] + ) + for ( + _c_name, + f_name, + visi, + modifiers, + read, + write, + internal_calls, + external_calls, + ) in func_summaries: read = self._convert(read) write = self._convert(write) internal_calls = self._convert(internal_calls) external_calls = self._convert(external_calls) - table.add_row([f_name, visi, modifiers, read, write, internal_calls, external_calls]) - txt += "\n \n"+str(table) - table = MyPrettyTable(["Modifiers", - "Visibility", - "Read", - "Write", - "Internal Calls", - "External Calls"]) - for (_c_name, f_name, visi, _, read, write, internal_calls, external_calls) in modif_summaries: + table.add_row( + [f_name, visi, modifiers, read, write, internal_calls, external_calls] + ) + txt += "\n \n" + str(table) + table = MyPrettyTable( + ["Modifiers", "Visibility", "Read", "Write", "Internal Calls", "External Calls"] + ) + for ( + _c_name, + f_name, + visi, + _, + read, + write, + internal_calls, + external_calls, + ) in modif_summaries: read = self._convert(read) write = self._convert(write) internal_calls = self._convert(internal_calls) external_calls = self._convert(external_calls) table.add_row([f_name, visi, read, write, internal_calls, external_calls]) - txt += "\n\n"+str(table) + txt += "\n\n" + str(table) txt += "\n" self.info(txt) diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 2ebbc1bdf..50dbafd87 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -8,10 +8,10 @@ from slither.utils.myprettytable import MyPrettyTable class FunctionIds(AbstractPrinter): - ARGUMENT = 'function-id' - HELP = 'Print the keccack256 signature of the functions' + ARGUMENT = "function-id" + HELP = "Print the keccack256 signature of the functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id" def output(self, _filename): """ @@ -20,21 +20,21 @@ class FunctionIds(AbstractPrinter): _filename(string) """ - txt = '' + txt = "" all_tables = [] for contract in self.slither.contracts_derived: - txt += '\n{}:\n'.format(contract.name) - table = MyPrettyTable(['Name', 'ID']) + txt += "\n{}:\n".format(contract.name) + table = MyPrettyTable(["Name", "ID"]) for function in contract.functions: - if function.visibility in ['public', 'external']: + if function.visibility in ["public", "external"]: function_id = get_function_id(function.solidity_signature) table.add_row([function.solidity_signature, f"{function_id:#0{10}x}"]) for variable in contract.state_variables: - if variable.visibility in ['public']: + if variable.visibility in ["public"]: sig = variable.function_name function_id = get_function_id(sig) table.add_row([sig, f"{function_id:#0{10}x}"]) - txt += str(table) + '\n' + txt += str(table) + "\n" all_tables.append((contract.name, table)) self.info(txt) @@ -43,4 +43,4 @@ class FunctionIds(AbstractPrinter): for name, table in all_tables: res.add_pretty_table(table, name) - return res \ No newline at end of file + return res diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index aaad9e324..77fc25581 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -19,10 +19,10 @@ from slither.utils.tests_pattern import is_test_file class PrinterHumanSummary(AbstractPrinter): - ARGUMENT = 'human-summary' - HELP = 'Print a human-readable summary of the contracts' + ARGUMENT = "human-summary" + HELP = "Print a human-readable summary of the contracts" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary" @staticmethod def _get_summary_erc20(contract): @@ -30,23 +30,24 @@ class PrinterHumanSummary(AbstractPrinter): functions_name = [f.name for f in contract.functions] state_variables = [v.name for v in contract.state_variables] - pause = 'pause' in functions_name + pause = "pause" in functions_name - if 'mint' in functions_name: - if 'mintingFinished' in state_variables: + if "mint" in functions_name: + if "mintingFinished" in state_variables: mint_unlimited = False else: mint_unlimited = True else: mint_unlimited = None # no minting - race_condition_mitigated = 'increaseApproval' in functions_name or \ - 'safeIncreaseAllowance' in functions_name + race_condition_mitigated = ( + "increaseApproval" in functions_name or "safeIncreaseAllowance" in functions_name + ) return pause, mint_unlimited, race_condition_mitigated def get_summary_erc20(self, contract): - txt = '' + txt = "" pause, mint_unlimited, race_condition_mitigated = self._get_summary_erc20(contract) @@ -66,9 +67,9 @@ class PrinterHumanSummary(AbstractPrinter): return txt - def _get_detectors_result(self) -> Tuple[List[Dict],int, int, int, int, int]: + def _get_detectors_result(self) -> Tuple[List[Dict], int, int, int, int, int]: # disable detectors logger - logger = logging.getLogger('Detectors') + logger = logging.getLogger("Detectors") logger.setLevel(logging.ERROR) checks_optimization = self.slither.detectors_optimization @@ -97,16 +98,20 @@ class PrinterHumanSummary(AbstractPrinter): issues_high = [c for c in issues_high if c] issues_high = [item for sublist in issues_high for item in sublist] - all_results = issues_optimization + issues_informational + issues_low + issues_medium + issues_high + all_results = ( + issues_optimization + issues_informational + issues_low + issues_medium + issues_high + ) - return (all_results, - len(issues_optimization), - len(issues_informational), - len(issues_low), - len(issues_medium), - len(issues_high)) + return ( + all_results, + len(issues_optimization), + len(issues_informational), + len(issues_low), + len(issues_medium), + len(issues_high), + ) - def get_detectors_result(self) -> Tuple[str, List[Dict],int, int, int, int, int]: + def get_detectors_result(self) -> Tuple[str, List[Dict], int, int, int, int, int]: all_results, optimization, informational, low, medium, high = self._get_detectors_result() txt = "Number of optimization issues: {}\n".format(green(optimization)) txt += "Number of informational issues: {}\n".format(green(informational)) @@ -140,7 +145,7 @@ class PrinterHumanSummary(AbstractPrinter): is_complex = self._is_complex_code(contract) - result = red('Yes') if is_complex else green('No') + result = red("Yes") if is_complex else green("No") return result @staticmethod @@ -181,8 +186,8 @@ class PrinterHumanSummary(AbstractPrinter): def _compilation_type(self): if self.slither.crytic_compile is None: - return 'Compilation non standard\n' - return f'Compiled with {str(self.slither.crytic_compile.type)}\n' + return "Compilation non standard\n" + return f"Compiled with {str(self.slither.crytic_compile.type)}\n" def _number_contracts(self): if self.slither.crytic_compile is None: @@ -221,7 +226,10 @@ class PrinterHumanSummary(AbstractPrinter): use_abi_encoder = False for pragma in self.slither.pragma_directives: - if pragma.source_mapping["filename_absolute"] == contract.source_mapping["filename_absolute"]: + if ( + pragma.source_mapping["filename_absolute"] + == contract.source_mapping["filename_absolute"] + ): if pragma.is_abi_encoder_v2: use_abi_encoder = True @@ -235,16 +243,25 @@ class PrinterHumanSummary(AbstractPrinter): for ir in function.slithir_operations: if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)) and ir.call_value: can_send_eth = True - if isinstance(ir, SolidityCall) and ir.function in [SolidityFunction("suicide(address)"), - SolidityFunction("selfdestruct(address)")]: + if isinstance(ir, SolidityCall) and ir.function in [ + SolidityFunction("suicide(address)"), + SolidityFunction("selfdestruct(address)"), + ]: can_selfdestruct = True - if (isinstance(ir, SolidityCall) and - ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)")): + if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( + "ecrecover(bytes32,uint8,bytes32,bytes32)" + ): has_ecrecover = True - if isinstance(ir, LowLevelCall) and ir.function_name in ["delegatecall", "callcode"]: + if isinstance(ir, LowLevelCall) and ir.function_name in [ + "delegatecall", + "callcode", + ]: can_delegatecall = True if isinstance(ir, HighLevelCall): - if isinstance(ir.function, (Function, StateVariable)) and ir.function.contract.is_possible_token: + if ( + isinstance(ir.function, (Function, StateVariable)) + and ir.function.contract.is_possible_token + ): has_token_interaction = True return { @@ -271,54 +288,62 @@ class PrinterHumanSummary(AbstractPrinter): txt += self._compilation_type() results = { - 'contracts': { - "elements": [] - }, - 'number_lines': 0, - 'number_lines_in_dependencies': 0, - 'number_lines_assembly': 0, - 'standard_libraries': [], - 'ercs': [], - 'number_findings': dict(), - 'detectors': [] + "contracts": {"elements": []}, + "number_lines": 0, + "number_lines_in_dependencies": 0, + "number_lines_assembly": 0, + "standard_libraries": [], + "ercs": [], + "number_findings": dict(), + "detectors": [], } lines_number = self._lines_number() if lines_number: total_lines, total_dep_lines, total_tests_lines = lines_number - txt += f'Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n' - results['number_lines'] = total_lines - results['number_lines__dependencies'] = total_dep_lines + txt += f"Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n" + results["number_lines"] = total_lines + results["number_lines__dependencies"] = total_dep_lines total_asm_lines = self._get_number_of_assembly_lines() txt += f"Number of assembly lines: {total_asm_lines}\n" - results['number_lines_assembly'] = total_asm_lines + results["number_lines_assembly"] = total_asm_lines number_contracts, number_contracts_deps, number_contracts_tests = self._number_contracts() - txt += f'Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n' - - txt_detectors, detectors_results, optimization, info, low, medium, high = self.get_detectors_result() + txt += f"Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n" + + ( + txt_detectors, + detectors_results, + optimization, + info, + low, + medium, + high, + ) = self.get_detectors_result() txt += txt_detectors - results['number_findings'] = { - 'optimization_issues': optimization, - 'informational_issues': info, - 'low_issues': low, - 'medium_issues': medium, - 'high_issues': high + results["number_findings"] = { + "optimization_issues": optimization, + "informational_issues": info, + "low_issues": low, + "medium_issues": medium, + "high_issues": high, } - results['detectors'] = detectors_results + results["detectors"] = detectors_results libs = self._standard_libraries() if libs: txt += f'\nUse: {", ".join(libs)}\n' - results['standard_libraries'] = [str(l) for l in libs] + results["standard_libraries"] = [str(l) for l in libs] ercs = self._ercs() if ercs: txt += f'ERCs: {", ".join(ercs)}\n' - results['ercs'] = [str(e) for e in ercs] + results["ercs"] = [str(e) for e in ercs] - table = MyPrettyTable(["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"]) + table = MyPrettyTable( + ["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"] + ) for contract in self.slither.contracts_derived: if contract.is_from_dependency() or contract.is_test: @@ -326,40 +351,46 @@ class PrinterHumanSummary(AbstractPrinter): is_complex = self.is_complex_code(contract) number_functions = self._number_functions(contract) - ercs = ','.join(contract.ercs()) + ercs = ",".join(contract.ercs()) is_erc20 = contract.is_erc20() - erc20_info = '' + erc20_info = "" if is_erc20: erc20_info += self.get_summary_erc20(contract) - features = "\n".join([name for name, to_print in self._get_features(contract).items() if to_print]) + features = "\n".join( + [name for name, to_print in self._get_features(contract).items() if to_print] + ) table.add_row([contract.name, number_functions, ercs, erc20_info, is_complex, features]) - self.info(txt + '\n' + str(table)) + self.info(txt + "\n" + str(table)) - results_contract = output.Output('') + results_contract = output.Output("") for contract in self.slither.contracts_derived: if contract.is_test or contract.is_from_dependency(): continue - contract_d = {'contract_name': contract.name, - 'is_complex_code': self._is_complex_code(contract), - 'is_erc20': contract.is_erc20(), - 'number_functions': self._number_functions(contract), - 'features': [name for name, to_print in self._get_features(contract).items() if to_print]} - if contract_d['is_erc20']: + contract_d = { + "contract_name": contract.name, + "is_complex_code": self._is_complex_code(contract), + "is_erc20": contract.is_erc20(), + "number_functions": self._number_functions(contract), + "features": [ + name for name, to_print in self._get_features(contract).items() if to_print + ], + } + if contract_d["is_erc20"]: pause, mint_limited, race_condition_mitigated = self._get_summary_erc20(contract) - contract_d['erc20_pause'] = pause + contract_d["erc20_pause"] = pause if mint_limited is not None: - contract_d['erc20_can_mint'] = True - contract_d['erc20_mint_limited'] = mint_limited + contract_d["erc20_can_mint"] = True + contract_d["erc20_mint_limited"] = mint_limited else: - contract_d['erc20_can_mint'] = False - contract_d['erc20_race_condition_mitigated'] = race_condition_mitigated + contract_d["erc20_can_mint"] = False + contract_d["erc20_race_condition_mitigated"] = race_condition_mitigated results_contract.add_contract(contract, additional_fields=contract_d) - results['contracts']['elements'] = results_contract.elements + results["contracts"]["elements"] = results_contract.elements json = self.generate_output(txt, additional_fields=results) diff --git a/slither/printers/summary/modifier_calls.py b/slither/printers/summary/modifier_calls.py index 242b9dac9..faefa0e9d 100644 --- a/slither/printers/summary/modifier_calls.py +++ b/slither/printers/summary/modifier_calls.py @@ -9,10 +9,10 @@ from slither.utils.myprettytable import MyPrettyTable class Modifiers(AbstractPrinter): - ARGUMENT = 'modifiers' - HELP = 'Print the modifiers called by each function' + ARGUMENT = "modifiers" + HELP = "Print the modifiers called by each function" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#modifiers' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#modifiers" def output(self, _filename): """ @@ -21,13 +21,12 @@ class Modifiers(AbstractPrinter): _filename(string) """ - all_txt = '' + all_txt = "" all_tables = [] for contract in self.slither.contracts_derived: - txt = "\nContract %s"%contract.name - table = MyPrettyTable(["Function", - "Modifiers"]) + txt = "\nContract %s" % contract.name + table = MyPrettyTable(["Function", "Modifiers"]) for function in contract.functions: modifiers = function.modifiers for call in function.all_internal_calls(): @@ -37,11 +36,11 @@ class Modifiers(AbstractPrinter): if isinstance(call, Function): modifiers += call.modifiers table.add_row([function.name, [m.name for m in set(modifiers)]]) - txt += "\n"+str(table) + txt += "\n" + str(table) self.info(txt) res = self.generate_output(all_txt) for name, table in all_tables: res.add_pretty_table(table, name) - return res \ No newline at end of file + return res diff --git a/slither/printers/summary/require_calls.py b/slither/printers/summary/require_calls.py index e51670ca2..6b68f99bb 100644 --- a/slither/printers/summary/require_calls.py +++ b/slither/printers/summary/require_calls.py @@ -7,16 +7,19 @@ from slither.printers.abstract_printer import AbstractPrinter from slither.slithir.operations import SolidityCall from slither.utils.myprettytable import MyPrettyTable -require_or_assert = [SolidityFunction("assert(bool)"), - SolidityFunction("require(bool)"), - SolidityFunction("require(bool,string)")] +require_or_assert = [ + SolidityFunction("assert(bool)"), + SolidityFunction("require(bool)"), + SolidityFunction("require(bool,string)"), +] + class RequireOrAssert(AbstractPrinter): - ARGUMENT = 'require' - HELP = 'Print the require and assert calls of each function' + ARGUMENT = "require" + HELP = "Print the require and assert calls of each function" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#require' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#require" @staticmethod def _convert(l): @@ -30,17 +33,22 @@ class RequireOrAssert(AbstractPrinter): """ all_tables = [] - all_txt = '' + all_txt = "" for contract in self.slither.contracts_derived: - txt = "\nContract %s"%contract.name - table = MyPrettyTable(["Function", - "require or assert"]) + txt = "\nContract %s" % contract.name + table = MyPrettyTable(["Function", "require or assert"]) for function in contract.functions: require = function.all_slithir_operations() - require = [ir for ir in require if isinstance(ir, SolidityCall) and ir.function in require_or_assert] + require = [ + ir + for ir in require + if isinstance(ir, SolidityCall) and ir.function in require_or_assert + ] require = [ir.node for ir in require] - table.add_row([function.name, self._convert([str(m.expression) for m in set(require)])]) - txt += "\n"+str(table) + table.add_row( + [function.name, self._convert([str(m.expression) for m in set(require)])] + ) + txt += "\n" + str(table) self.info(txt) all_tables.append((contract.name, table)) all_txt += txt diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index 3abf89adf..e3f1d95e1 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -6,10 +6,10 @@ from slither.printers.abstract_printer import AbstractPrinter class PrinterSlithIR(AbstractPrinter): - ARGUMENT = 'slithir' - HELP = 'Print the slithIR representation of the functions' + ARGUMENT = "slithir" + HELP = "Print the slithIR representation of the functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir" def output(self, _filename): """ @@ -22,28 +22,28 @@ class PrinterSlithIR(AbstractPrinter): for contract in self.contracts: if contract.is_top_level: continue - txt += 'Contract {}\n'.format(contract.name) + txt += "Contract {}\n".format(contract.name) for function in contract.functions: txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}\n' for node in function.nodes: if node.expression: - txt += '\t\tExpression: {}\n'.format(node.expression) - txt += '\t\tIRs:\n' + txt += "\t\tExpression: {}\n".format(node.expression) + txt += "\t\tIRs:\n" for ir in node.irs: - txt += '\t\t\t{}\n'.format(ir) + txt += "\t\t\t{}\n".format(ir) elif node.irs: - txt += '\t\tIRs:\n' + txt += "\t\tIRs:\n" for ir in node.irs: - txt += '\t\t\t{}\n'.format(ir) + txt += "\t\t\t{}\n".format(ir) for modifier in contract.modifiers: - txt += '\tModifier {}\n'.format(modifier.canonical_name) + txt += "\tModifier {}\n".format(modifier.canonical_name) for node in modifier.nodes: txt += str(node) if node.expression: - txt += '\t\tExpression: {}\n'.format(node.expression) - txt += '\t\tIRs:\n' + txt += "\t\tExpression: {}\n".format(node.expression) + txt += "\t\tIRs:\n" for ir in node.irs: - txt += '\t\t\t{}\n'.format(ir) + txt += "\t\t\t{}\n".format(ir) self.info(txt) res = self.generate_output(txt) return res diff --git a/slither/printers/summary/slithir_ssa.py b/slither/printers/summary/slithir_ssa.py index b743dbdf2..8c01c78ec 100644 --- a/slither/printers/summary/slithir_ssa.py +++ b/slither/printers/summary/slithir_ssa.py @@ -7,10 +7,10 @@ from slither.printers.abstract_printer import AbstractPrinter class PrinterSlithIRSSA(AbstractPrinter): - ARGUMENT = 'slithir-ssa' - HELP = 'Print the slithIR representation of the functions' + ARGUMENT = "slithir-ssa" + HELP = "Print the slithIR representation of the functions" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir-ssa' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir-ssa" def output(self, _filename): """ @@ -23,26 +23,26 @@ class PrinterSlithIRSSA(AbstractPrinter): for contract in self.contracts: if contract.is_top_level: continue - txt += 'Contract {}'.format(contract.name) + '\n' + txt += "Contract {}".format(contract.name) + "\n" for function in contract.functions: - txt += '\tFunction {}'.format(function.canonical_name) + '\n' + txt += "\tFunction {}".format(function.canonical_name) + "\n" for node in function.nodes: if node.expression: - txt += '\t\tExpression: {}'.format(node.expression) + '\n' + txt += "\t\tExpression: {}".format(node.expression) + "\n" if node.irs_ssa: - txt += '\t\tIRs:' + '\n' + txt += "\t\tIRs:" + "\n" for ir in node.irs_ssa: - txt += '\t\t\t{}'.format(ir) + '\n' + txt += "\t\t\t{}".format(ir) + "\n" for modifier in contract.modifiers: - txt += '\tModifier {}'.format(modifier.canonical_name) + '\n' + txt += "\tModifier {}".format(modifier.canonical_name) + "\n" for node in modifier.nodes: - txt += str(node) + '\n' + txt += str(node) + "\n" if node.expression: - txt += '\t\tExpression: {}'.format(node.expression) + '\n' + txt += "\t\tExpression: {}".format(node.expression) + "\n" if node.irs_ssa: - txt += '\t\tIRs:' + '\n' + txt += "\t\tIRs:" + "\n" for ir in node.irs_ssa: - txt += '\t\t\t{}'.format(ir) + '\n' + txt += "\t\t\t{}".format(ir) + "\n" self.info(txt) res = self.generate_output(txt) return res diff --git a/slither/printers/summary/variable_order.py b/slither/printers/summary/variable_order.py index d00fd7f56..3cb26cfa5 100644 --- a/slither/printers/summary/variable_order.py +++ b/slither/printers/summary/variable_order.py @@ -8,10 +8,10 @@ from slither.utils.myprettytable import MyPrettyTable class VariableOrder(AbstractPrinter): - ARGUMENT = 'variable-order' - HELP = 'Print the storage order of the state variables' + ARGUMENT = "variable-order" + HELP = "Print the storage order of the state variables" - WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#variable-order' + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#variable-order" def output(self, _filename): """ @@ -20,24 +20,24 @@ class VariableOrder(AbstractPrinter): _filename(string) """ - txt = '' + txt = "" all_tables = [] for contract in self.slither.contracts_derived: - txt += '\n{}:\n'.format(contract.name) - table = MyPrettyTable(['Name', 'Type', 'Slot', 'Offset']) + txt += "\n{}:\n".format(contract.name) + table = MyPrettyTable(["Name", "Type", "Slot", "Offset"]) for variable in contract.state_variables_ordered: if not variable.is_constant: slot, offset = self.slither.storage_layout_of(contract, variable) table.add_row([variable.canonical_name, str(variable.type), slot, offset]) all_tables.append((contract.name, table)) - txt += str(table) + '\n' + txt += str(table) + "\n" self.info(txt) res = self.generate_output(txt) for name, table in all_tables: res.add_pretty_table(table, name) - return res \ No newline at end of file + return res diff --git a/slither/slither.py b/slither/slither.py index 6d8f35a9d..b44ad66d8 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -44,7 +44,6 @@ class Slither(SlitherCore): super().__init__() self._parser: SlitherSolc # This could be another parser, like SlitherVyper, interface needs to be determined - self._disallow_partial: bool = kwargs.get("disallow_partial", False) # list of files provided (see --splitted option) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index e4d7fba18..5149a6331 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,13 +1,25 @@ import logging from typing import List -from slither.core.declarations import (Contract, Enum, Event, Function, - SolidityFunction, SolidityVariable, - SolidityVariableComposed, Structure) +from slither.core.declarations import ( + Contract, + Enum, + Event, + Function, + SolidityFunction, + SolidityVariable, + SolidityVariableComposed, + Structure, +) from slither.core.expressions import Identifier, Literal -from slither.core.solidity_types import (ArrayType, ElementaryType, - FunctionType, MappingType, - UserDefinedType, TypeInformation) +from slither.core.solidity_types import ( + ArrayType, + ElementaryType, + FunctionType, + MappingType, + UserDefinedType, + TypeInformation, +) from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt from slither.core.solidity_types.type import Type from slither.core.variables.function_type_variable import FunctionTypeVariable @@ -15,32 +27,52 @@ from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable from slither.slithir.operations.codesize import CodeSize from slither.slithir.variables import TupleVariable -from slither.slithir.operations import (Assignment, Balance, Binary, - BinaryType, Call, Condition, Delete, - EventCall, HighLevelCall, Index, - InitArray, InternalCall, - InternalDynamicCall, Length, - LibraryCall, LowLevelCall, Member, - NewArray, NewContract, - NewElementaryType, NewStructure, - OperationWithLValue, Push, Return, - Send, SolidityCall, Transfer, - TypeConversion, Unary, Unpack, Nop) +from slither.slithir.operations import ( + Assignment, + Balance, + Binary, + BinaryType, + Call, + Condition, + Delete, + EventCall, + HighLevelCall, + Index, + InitArray, + InternalCall, + InternalDynamicCall, + Length, + LibraryCall, + LowLevelCall, + Member, + NewArray, + NewContract, + NewElementaryType, + NewStructure, + OperationWithLValue, + Push, + Return, + Send, + SolidityCall, + Transfer, + TypeConversion, + Unary, + Unpack, + Nop, +) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract -from slither.slithir.tmp_operations.tmp_new_elementary_type import \ - TmpNewElementaryType +from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure -from slither.slithir.variables import (Constant, ReferenceVariable, - TemporaryVariable) +from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVariable from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR from slither.utils.function import get_function_id from slither.utils.type import export_nested_types_from_variable from slither.slithir.exceptions import SlithIRError -logger = logging.getLogger('ConvertToIR') +logger = logging.getLogger("ConvertToIR") def convert_expression(expression, node): @@ -91,10 +123,11 @@ def convert_expression(expression, node): ################################################################################### ################################################################################### + def is_value(ins): if isinstance(ins, TmpCall): if isinstance(ins.ori, Member): - if ins.ori.variable_right == 'value': + if ins.ori.variable_right == "value": return True return False @@ -102,64 +135,64 @@ def is_value(ins): def is_gas(ins): if isinstance(ins, TmpCall): if isinstance(ins.ori, Member): - if ins.ori.variable_right == 'gas': + if ins.ori.variable_right == "gas": return True return False def get_sig(ir, name): - ''' + """ Return a list of potential signature It is a list, as Constant variables can be converted to int256 Args: ir (slithIR.operation) Returns: list(str) - ''' - sig = '{}({})' + """ + sig = "{}({})" # list of list of arguments argss = convert_arguments(ir.arguments) - return [sig.format(name, ','.join(args)) for args in argss] + return [sig.format(name, ",".join(args)) for args in argss] def get_canonical_names(ir, function_name, contract_name): - ''' + """ Return a list of potential signature It is a list, as Constant variables can be converted to int256 Args: ir (slithIR.operation) Returns: list(str) - ''' - sig = '{}({})' + """ + sig = "{}({})" # list of list of arguments argss = convert_arguments(ir.arguments) - return [sig.format(f'{contract_name}.{function_name}', ','.join(args)) for args in argss] + return [sig.format(f"{contract_name}.{function_name}", ",".join(args)) for args in argss] def convert_arguments(arguments): argss = [[]] for arg in arguments: if isinstance(arg, (list,)): - type_arg = '{}[{}]'.format(get_type(arg[0].type), len(arg)) + type_arg = "{}[{}]".format(get_type(arg[0].type), len(arg)) elif isinstance(arg, Function): type_arg = arg.signature_str else: type_arg = get_type(arg.type) - if isinstance(arg, Constant) and arg.type == ElementaryType('uint256'): + if isinstance(arg, Constant) and arg.type == ElementaryType("uint256"): # If it is a constant # We dupplicate the existing list # And we add uint256 and int256 cases # There is no potential collision, as the compiler - # Prevent it with a + # Prevent it with a # "not unique after argument-dependent loopkup" issue argss_new = [list(args) for args in argss] for args in argss: - args.append(str(ElementaryType('uint256'))) + args.append(str(ElementaryType("uint256"))) for args in argss_new: - args.append(str(ElementaryType('int256'))) + args.append(str(ElementaryType("int256"))) argss = argss + argss_new else: for args in argss: @@ -168,11 +201,9 @@ def convert_arguments(arguments): def is_temporary(ins): - return isinstance(ins, (Argument, - TmpNewElementaryType, - TmpNewContract, - TmpNewArray, - TmpNewStructure)) + return isinstance( + ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure) + ) # endregion @@ -182,10 +213,11 @@ def is_temporary(ins): ################################################################################### ################################################################################### + def integrate_value_gas(result): - ''' + """ Integrate value and gas temporary arguments to call instruction - ''' + """ was_changed = True calls = [] @@ -230,7 +262,7 @@ def integrate_value_gas(result): # Remove the call to value/gas instruction result = [i for i in result if not i in to_remove] - # update the real call + # update the real call for ins in result: if isinstance(ins, TmpCall): # use of while if there redirections @@ -261,10 +293,11 @@ def integrate_value_gas(result): ################################################################################### ################################################################################### + def propagate_type_and_convert_call(result, node): - ''' + """ Propagate the types variables and convert tmp call to real call operation - ''' + """ calls_value = {} calls_gas = {} @@ -354,57 +387,55 @@ def _convert_type_contract(ir, slither): assert isinstance(ir.variable_left.type, TypeInformation) contract = ir.variable_left.type.type - if ir.variable_right == 'creationCode': + if ir.variable_right == "creationCode": if slither.crytic_compile: bytecode = slither.crytic_compile.bytecode_init(contract.name) else: logger.info( - 'The codebase uses type(x).creationCode, but crytic-compile was not used. As a result, the bytecode cannot be found') + "The codebase uses type(x).creationCode, but crytic-compile was not used. As a result, the bytecode cannot be found" + ) bytecode = "MISSING_BYTECODE" - assignment = Assignment(ir.lvalue, - Constant(str(bytecode)), - ElementaryType('bytes')) + assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) - assignment.lvalue.set_type(ElementaryType('bytes')) + assignment.lvalue.set_type(ElementaryType("bytes")) return assignment - if ir.variable_right == 'runtimeCode': + if ir.variable_right == "runtimeCode": if slither.crytic_compile: bytecode = slither.crytic_compile.bytecode_runtime(contract.name) else: logger.info( - 'The codebase uses type(x).runtimeCode, but crytic-compile was not used. As a result, the bytecode cannot be found') + "The codebase uses type(x).runtimeCode, but crytic-compile was not used. As a result, the bytecode cannot be found" + ) bytecode = "MISSING_BYTECODE" - assignment = Assignment(ir.lvalue, - Constant(str(bytecode)), - ElementaryType('bytes')) + assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) - assignment.lvalue.set_type(ElementaryType('bytes')) + assignment.lvalue.set_type(ElementaryType("bytes")) return assignment - if ir.variable_right == 'interfaceId': + if ir.variable_right == "interfaceId": entry_points = contract.functions_entry_points interfaceId = 0 for entry_point in entry_points: interfaceId = interfaceId ^ get_function_id(entry_point.full_name) - assignment = Assignment(ir.lvalue, - Constant(str(interfaceId), type=ElementaryType('bytes4')), - ElementaryType('bytes4')) + assignment = Assignment( + ir.lvalue, + Constant(str(interfaceId), type=ElementaryType("bytes4")), + ElementaryType("bytes4"), + ) assignment.set_expression(ir.expression) assignment.set_node(ir.node) - assignment.lvalue.set_type(ElementaryType('bytes4')) + assignment.lvalue.set_type(ElementaryType("bytes4")) return assignment - if ir.variable_right == 'name': - assignment = Assignment(ir.lvalue, - Constant(contract.name), - ElementaryType('string')) + if ir.variable_right == "name": + assignment = Assignment(ir.lvalue, Constant(contract.name), ElementaryType("string")) assignment.set_expression(ir.expression) assignment.set_node(ir.node) - assignment.lvalue.set_type(ElementaryType('string')) + assignment.lvalue.set_type(ElementaryType("string")) return assignment - raise SlithIRError(f'type({contract.name}).{ir.variable_right} is unknown') + raise SlithIRError(f"type({contract.name}).{ir.variable_right} is unknown") def propagate_types(ir, node): @@ -417,7 +448,7 @@ def propagate_types(ir, node): ir.lvalue.set_type(ir.rvalue.type) elif isinstance(ir, Binary): if BinaryType.return_bool(ir.type): - ir.lvalue.set_type(ElementaryType('bool')) + ir.lvalue.set_type(ElementaryType("bool")) else: ir.lvalue.set_type(ir.variable_left.type) elif isinstance(ir, Delete): @@ -432,12 +463,12 @@ def propagate_types(ir, node): if t is None: return - if isinstance(t, ElementaryType) and t.name == 'address': + if isinstance(t, ElementaryType) and t.name == "address": if can_be_solidity_func(ir): return convert_to_solidity_func(ir) # convert library - if t in using_for or '*' in using_for: + if t in using_for or "*" in using_for: new_ir = convert_to_library(ir, node, using_for) if new_ir: return new_ir @@ -450,19 +481,23 @@ def propagate_types(ir, node): return convert_type_of_high_and_internal_level_call(ir, contract) # Convert HighLevelCall to LowLevelCall - if isinstance(t, ElementaryType) and t.name == 'address': - if ir.destination.name == 'this': - return convert_type_of_high_and_internal_level_call(ir, node.function.contract) + if isinstance(t, ElementaryType) and t.name == "address": + if ir.destination.name == "this": + return convert_type_of_high_and_internal_level_call( + ir, node.function.contract + ) if can_be_low_level(ir): return convert_to_low_level(ir) # Convert push operations # May need to insert a new operation # Which leads to return a list of operation - if isinstance(t, ArrayType) or (isinstance(t, ElementaryType) and t.type == 'bytes'): - if ir.function_name == 'push' and len(ir.arguments) == 1: + if isinstance(t, ArrayType) or ( + isinstance(t, ElementaryType) and t.type == "bytes" + ): + if ir.function_name == "push" and len(ir.arguments) == 1: return convert_to_push(ir, node) - if ir.function_name == 'pop' and len(ir.arguments) == 0: + if ir.function_name == "pop" and len(ir.arguments) == 0: return convert_to_pop(ir, node) elif isinstance(ir, Index): @@ -503,49 +538,67 @@ def propagate_types(ir, node): assert False elif isinstance(ir, Member): # TODO we should convert the reference to a temporary if the member is a length or a balance - if ir.variable_right == 'length' and not isinstance(ir.variable_left, Contract) and isinstance( - ir.variable_left.type, (ElementaryType, ArrayType)): + if ( + ir.variable_right == "length" + and not isinstance(ir.variable_left, Contract) + and isinstance(ir.variable_left.type, (ElementaryType, ArrayType)) + ): length = Length(ir.variable_left, ir.lvalue) length.set_expression(ir.expression) length.lvalue.points_to = ir.variable_left length.set_node(ir.node) return length - if ir.variable_right == 'balance' and not isinstance(ir.variable_left, Contract) and isinstance( - ir.variable_left.type, ElementaryType): + if ( + ir.variable_right == "balance" + and not isinstance(ir.variable_left, Contract) + and isinstance(ir.variable_left.type, ElementaryType) + ): b = Balance(ir.variable_left, ir.lvalue) b.set_expression(ir.expression) b.set_node(ir.node) return b - if ir.variable_right == 'codesize' and not isinstance(ir.variable_left, Contract) and isinstance( - ir.variable_left.type, ElementaryType): + if ( + ir.variable_right == "codesize" + and not isinstance(ir.variable_left, Contract) + and isinstance(ir.variable_left.type, ElementaryType) + ): b = CodeSize(ir.variable_left, ir.lvalue) b.set_expression(ir.expression) b.set_node(ir.node) return b - if ir.variable_right == 'selector' and isinstance(ir.variable_left.type, Function): - assignment = Assignment(ir.lvalue, - Constant(str(get_function_id(ir.variable_left.type.full_name))), - ElementaryType('bytes4')) + if ir.variable_right == "selector" and isinstance(ir.variable_left.type, Function): + assignment = Assignment( + ir.lvalue, + Constant(str(get_function_id(ir.variable_left.type.full_name))), + ElementaryType("bytes4"), + ) assignment.set_expression(ir.expression) assignment.set_node(ir.node) - assignment.lvalue.set_type(ElementaryType('bytes4')) + assignment.lvalue.set_type(ElementaryType("bytes4")) return assignment - if isinstance(ir.variable_left, TemporaryVariable) and isinstance(ir.variable_left.type, - TypeInformation): + if isinstance(ir.variable_left, TemporaryVariable) and isinstance( + ir.variable_left.type, TypeInformation + ): return _convert_type_contract(ir, node.function.slither) left = ir.variable_left t = None # Handling of this.function_name usage - if (left == SolidityVariable("this") and - isinstance(ir.variable_right, Constant) and - str(ir.variable_right) in [x.name for x in ir.function.contract.functions] + if ( + left == SolidityVariable("this") + and isinstance(ir.variable_right, Constant) + and str(ir.variable_right) in [x.name for x in ir.function.contract.functions] ): # Assumption that this.function_name can only compile if # And the contract does not have two functions starting with function_name # Otherwise solc raises: # Error: Member "f" not unique after argument-dependent lookup in contract - targeted_function = next((x for x in ir.function.contract.functions if - x.name == str(ir.variable_right))) + targeted_function = next( + ( + x + for x in ir.function.contract.functions + if x.name == str(ir.variable_right) + ) + ) parameters = [] returns = [] for parameter in targeted_function.parameters: @@ -581,13 +634,22 @@ def propagate_types(ir, node): # This allows to track the selector keyword # We dont need to check for function collision, as solc prevents the use of selector # if there are multiple functions with the same name - f = next((f for f in type_t.functions if f.name == ir.variable_right), None) + f = next( + (f for f in type_t.functions if f.name == ir.variable_right), None + ) if f: ir.lvalue.set_type(f) else: # Allow propgation for variable access through contract's nale # like Base_contract.my_variable - v = next((v for v in type_t.state_variables if v.name == ir.variable_right), None) + v = next( + ( + v + for v in type_t.state_variables + if v.name == ir.variable_right + ), + None, + ) if v: ir.lvalue.set_type(v.type) elif isinstance(ir, NewArray): @@ -603,9 +665,9 @@ def propagate_types(ir, node): # No change required pass elif isinstance(ir, Send): - ir.lvalue.set_type(ElementaryType('bool')) + ir.lvalue.set_type(ElementaryType("bool")) elif isinstance(ir, SolidityCall): - if ir.function.name in ['type(address)', 'type()']: + if ir.function.name in ["type(address)", "type()"]: ir.function.return_type = [TypeInformation(ir.arguments[0])] return_type = ir.function.return_type if len(return_type) == 1: @@ -621,12 +683,21 @@ def propagate_types(ir, node): idx = ir.index t = types[idx] ir.lvalue.set_type(t) - elif isinstance(ir, - (Argument, TmpCall, TmpNewArray, TmpNewContract, TmpNewStructure, TmpNewElementaryType)): + elif isinstance( + ir, + ( + Argument, + TmpCall, + TmpNewArray, + TmpNewContract, + TmpNewStructure, + TmpNewElementaryType, + ), + ): # temporary operation; they will be removed pass else: - raise SlithIRError('Not handling {} during type propgation'.format(type(ir))) + raise SlithIRError("Not handling {} during type propgation".format(type(ir))) def extract_tmp_call(ins, contract): @@ -641,8 +712,12 @@ def extract_tmp_call(ins, contract): # If there is a call on an inherited contract, it is an internal call or an event if ins.ori.variable_left in contract.inheritance + [contract]: if str(ins.ori.variable_right) in [f.name for f in contract.functions]: - internalcall = InternalCall((ins.ori.variable_right, ins.ori.variable_left.name), ins.nbr_arguments, - ins.lvalue, ins.type_call) + internalcall = InternalCall( + (ins.ori.variable_right, ins.ori.variable_left.name), + ins.nbr_arguments, + ins.lvalue, + ins.type_call, + ) internalcall.set_expression(ins.expression) internalcall.call_id = ins.call_id return internalcall @@ -658,13 +733,23 @@ def extract_tmp_call(ins, contract): op.set_expression(ins.expression) op.call_id = ins.call_id return op - libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, - ins.type_call) + libcall = LibraryCall( + ins.ori.variable_left, + ins.ori.variable_right, + ins.nbr_arguments, + ins.lvalue, + ins.type_call, + ) libcall.set_expression(ins.expression) libcall.call_id = ins.call_id return libcall - msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, - ins.type_call) + msgcall = HighLevelCall( + ins.ori.variable_left, + ins.ori.variable_right, + ins.nbr_arguments, + ins.lvalue, + ins.type_call, + ) msgcall.call_id = ins.call_id if ins.call_gas: @@ -680,10 +765,12 @@ def extract_tmp_call(ins, contract): r.set_node(ins.node) return r if isinstance(ins.called, SolidityVariableComposed): - if str(ins.called) == 'block.blockhash': - ins.called = SolidityFunction('blockhash(uint256)') - elif str(ins.called) == 'this.balance': - s = SolidityCall(SolidityFunction('this.balance()'), ins.nbr_arguments, ins.lvalue, ins.type_call) + if str(ins.called) == "block.blockhash": + ins.called = SolidityFunction("blockhash(uint256)") + elif str(ins.called) == "this.balance": + s = SolidityCall( + SolidityFunction("this.balance()"), ins.nbr_arguments, ins.lvalue, ins.type_call + ) s.set_expression(ins.expression) return s @@ -736,13 +823,14 @@ def extract_tmp_call(ins, contract): # Ideally we should compare here for the parameters types too if len(ins.called.constructor.parameters) != ins.nbr_arguments: return Nop() - internalcall = InternalCall(ins.called.constructor, ins.nbr_arguments, ins.lvalue, - ins.type_call) + internalcall = InternalCall( + ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call + ) internalcall.call_id = ins.call_id internalcall.set_expression(ins.expression) return internalcall - raise Exception('Not extracted {} {}'.format(type(ins.called), ins)) + raise Exception("Not extracted {} {}".format(type(ins.called), ins)) # endregion @@ -752,13 +840,16 @@ def extract_tmp_call(ins, contract): ################################################################################### ################################################################################### + def can_be_low_level(ir): - return ir.function_name in ['transfer', - 'send', - 'call', - 'delegatecall', - 'callcode', - 'staticcall'] + return ir.function_name in [ + "transfer", + "send", + "call", + "delegatecall", + "callcode", + "staticcall", + ] def convert_to_low_level(ir): @@ -769,49 +860,46 @@ def convert_to_low_level(ir): Must be called after can_be_low_level """ - if ir.function_name == 'transfer': + if ir.function_name == "transfer": assert len(ir.arguments) == 1 prev_ir = ir ir = Transfer(ir.destination, ir.arguments[0]) ir.set_expression(prev_ir.expression) ir.set_node(prev_ir.node) return ir - elif ir.function_name == 'send': + elif ir.function_name == "send": assert len(ir.arguments) == 1 prev_ir = ir ir = Send(ir.destination, ir.arguments[0], ir.lvalue) ir.set_expression(prev_ir.expression) ir.set_node(prev_ir.node) - ir.lvalue.set_type(ElementaryType('bool')) + ir.lvalue.set_type(ElementaryType("bool")) return ir - elif ir.function_name in ['call', - 'delegatecall', - 'callcode', - 'staticcall']: - new_ir = LowLevelCall(ir.destination, - ir.function_name, - ir.nbr_arguments, - ir.lvalue, - ir.type_call) + elif ir.function_name in ["call", "delegatecall", "callcode", "staticcall"]: + new_ir = LowLevelCall( + ir.destination, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call + ) new_ir.call_gas = ir.call_gas new_ir.call_value = ir.call_value new_ir.arguments = ir.arguments if ir.slither.solc_version >= "0.5": - new_ir.lvalue.set_type([ElementaryType('bool'), ElementaryType('bytes')]) + new_ir.lvalue.set_type([ElementaryType("bool"), ElementaryType("bytes")]) else: - new_ir.lvalue.set_type(ElementaryType('bool')) + new_ir.lvalue.set_type(ElementaryType("bool")) new_ir.set_expression(ir.expression) new_ir.set_node(ir.node) return new_ir - raise SlithIRError('Incorrect conversion to low level {}'.format(ir)) + raise SlithIRError("Incorrect conversion to low level {}".format(ir)) def can_be_solidity_func(ir): - return ir.destination.name == 'abi' and ir.function_name in ['encode', - 'encodePacked', - 'encodeWithSelector', - 'encodeWithSignature', - 'decode'] + return ir.destination.name == "abi" and ir.function_name in [ + "encode", + "encodePacked", + "encodeWithSelector", + "encodeWithSignature", + "decode", + ] def convert_to_solidity_func(ir): @@ -820,17 +908,19 @@ def convert_to_solidity_func(ir): :param ir: :return: """ - call = SolidityFunction('abi.{}()'.format(ir.function_name)) + call = SolidityFunction("abi.{}()".format(ir.function_name)) new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) new_ir.arguments = ir.arguments new_ir.set_expression(ir.expression) new_ir.set_node(ir.node) if isinstance(call.return_type, list) and len(call.return_type) == 1: new_ir.lvalue.set_type(call.return_type[0]) - elif (isinstance(new_ir.lvalue, TupleVariable) and - call == SolidityFunction("abi.decode()") and - len(new_ir.arguments) == 2 and - isinstance(new_ir.arguments[1], list)): + elif ( + isinstance(new_ir.lvalue, TupleVariable) + and call == SolidityFunction("abi.decode()") + and len(new_ir.arguments) == 2 + and isinstance(new_ir.arguments[1], list) + ): types = [x for x in new_ir.arguments[1]] new_ir.lvalue.set_type(types) # abi.decode where the type to decode is a singleton @@ -877,7 +967,7 @@ def convert_to_push(ir, node): ir.set_expression(prev_ir.expression) ir.set_node(prev_ir.node) - length = Literal(len(operation.init_values), 'uint256') + length = Literal(len(operation.init_values), "uint256") t = operation.init_values[0].type ir.lvalue.set_type(ArrayType(t, length)) @@ -921,7 +1011,7 @@ def convert_to_pop(ir, node): arr = ir.destination length = ReferenceVariable(node) - length.set_type(ElementaryType('uint256')) + length.set_type(ElementaryType("uint256")) ir_length = Length(arr, length) ir_length.set_expression(ir.expression) @@ -931,15 +1021,15 @@ def convert_to_pop(ir, node): val = TemporaryVariable(node) - ir_sub_1 = Binary(val, length, Constant("1", ElementaryType('uint256')), BinaryType.SUBTRACTION) + ir_sub_1 = Binary(val, length, Constant("1", ElementaryType("uint256")), BinaryType.SUBTRACTION) ir_sub_1.set_expression(ir.expression) ir_sub_1.set_node(ir.node) ret.append(ir_sub_1) element_to_delete = ReferenceVariable(node) - ir_assign_element_to_delete = Index(element_to_delete, arr, val, ElementaryType('uint256')) + ir_assign_element_to_delete = Index(element_to_delete, arr, val, ElementaryType("uint256")) ir_length.lvalue.points_to = arr - element_to_delete.set_type(ElementaryType('uint256')) + element_to_delete.set_type(ElementaryType("uint256")) ir_assign_element_to_delete.set_expression(ir.expression) ir_assign_element_to_delete.set_node(ir.node) ret.append(ir_assign_element_to_delete) @@ -950,14 +1040,14 @@ def convert_to_pop(ir, node): ret.append(ir_delete) length_to_assign = ReferenceVariable(node) - length_to_assign.set_type(ElementaryType('uint256')) + length_to_assign.set_type(ElementaryType("uint256")) ir_length = Length(arr, length_to_assign) ir_length.set_expression(ir.expression) ir_length.lvalue.points_to = arr ir_length.set_node(ir.node) ret.append(ir_length) - ir_assign_length = Assignment(length_to_assign, val, ElementaryType('uint256')) + ir_assign_length = Assignment(length_to_assign, val, ElementaryType("uint256")) ir_assign_length.set_expression(ir.expression) ir_assign_length.set_node(ir.node) ret.append(ir_assign_length) @@ -969,11 +1059,9 @@ def look_for_library(contract, ir, node, using_for, t): for destination in using_for[t]: lib_contract = contract.slither.get_contract_from_name(str(destination)) if lib_contract: - lib_call = LibraryCall(lib_contract, - ir.function_name, - ir.nbr_arguments, - ir.lvalue, - ir.type_call) + lib_call = LibraryCall( + lib_contract, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call + ) lib_call.set_expression(ir.expression) lib_call.set_node(ir.node) lib_call.call_gas = ir.call_gas @@ -996,8 +1084,8 @@ def convert_to_library(ir, node, using_for): if new_ir: return new_ir - if '*' in using_for: - new_ir = look_for_library(contract, ir, node, using_for, '*') + if "*" in using_for: + new_ir = look_for_library(contract, ir, node, using_for, "*") if new_ir: return new_ir @@ -1011,7 +1099,7 @@ def get_type(t): """ if isinstance(t, UserDefinedType): if isinstance(t.type, Contract): - return 'address' + return "address" return str(t) @@ -1129,7 +1217,7 @@ def convert_type_of_high_and_internal_level_call(ir, contract): if can_be_solidity_func(ir): return convert_to_solidity_func(ir) if not func: - logger.error('Function not found {}'.format(sig)) + logger.error("Function not found {}".format(sig)) ir.function = func if isinstance(func, Function): return_type = func.return_type @@ -1159,9 +1247,11 @@ def convert_type_of_high_and_internal_level_call(ir, contract): # If the return type is a structure, but the lvalue is a tuple # We convert the type of the structure to a list of element # TODO: explore to replace all tuple variables by structures - if (isinstance(ir.lvalue, TupleVariable) and - isinstance(return_type, UserDefinedType) and - isinstance(return_type.type, Structure)): + if ( + isinstance(ir.lvalue, TupleVariable) + and isinstance(return_type, UserDefinedType) + and isinstance(return_type.type, Structure) + ): return_type = _convert_to_structure_to_list(return_type) ir.lvalue.set_type(return_type) @@ -1178,6 +1268,7 @@ def convert_type_of_high_and_internal_level_call(ir, contract): ################################################################################### ################################################################################### + def find_references_origin(irs): """ Make lvalue of each Index, Member operation @@ -1195,12 +1286,15 @@ def find_references_origin(irs): ################################################################################### ################################################################################### + def remove_temporary(result): - result = [ins for ins in result if not isinstance(ins, (Argument, - TmpNewElementaryType, - TmpNewContract, - TmpNewArray, - TmpNewStructure))] + result = [ + ins + for ins in result + if not isinstance( + ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure) + ) + ] return result @@ -1245,6 +1339,7 @@ def remove_unused(result): ################################################################################### ################################################################################### + def convert_constant_types(irs): """ late conversion of uint -> type for constant (Literal) @@ -1265,15 +1360,15 @@ def convert_constant_types(irs): # TODO: fix missing Unpack conversion continue else: - if ir.rvalue.type.type != 'int256': - ir.rvalue.set_type(ElementaryType('int256')) + if ir.rvalue.type.type != "int256": + ir.rvalue.set_type(ElementaryType("int256")) was_changed = True if isinstance(ir, Binary): if isinstance(ir.lvalue.type, ElementaryType): if ir.lvalue.type.type in ElementaryTypeInt: for r in ir.read: - if r.type.type != 'int256': - r.set_type(ElementaryType('int256')) + if r.type.type != "int256": + r.set_type(ElementaryType("int256")) was_changed = True if isinstance(ir, (HighLevelCall, InternalCall)): func = ir.function @@ -1288,8 +1383,8 @@ def convert_constant_types(irs): t = types[idx] if isinstance(t, ElementaryType): if t.type in ElementaryTypeInt: - if arg.type.type != 'int256': - arg.set_type(ElementaryType('int256')) + if arg.type.type != "int256": + arg.set_type(ElementaryType("int256")) was_changed = True if isinstance(ir, NewStructure): st = ir.structure @@ -1297,16 +1392,16 @@ def convert_constant_types(irs): e = st.elems_ordered[idx] if isinstance(e.type, ElementaryType): if e.type.type in ElementaryTypeInt: - if arg.type.type != 'int256': - arg.set_type(ElementaryType('int256')) + if arg.type.type != "int256": + arg.set_type(ElementaryType("int256")) was_changed = True if isinstance(ir, InitArray): if isinstance(ir.lvalue.type, ArrayType): if isinstance(ir.lvalue.type.type, ElementaryType): if ir.lvalue.type.type.type in ElementaryTypeInt: for r in ir.read: - if r.type.type != 'int256': - r.set_type(ElementaryType('int256')) + if r.type.type != "int256": + r.set_type(ElementaryType("int256")) was_changed = True @@ -1317,6 +1412,7 @@ def convert_constant_types(irs): ################################################################################### ################################################################################### + def convert_delete(irs): """ Convert the lvalue of the Delete to point to the variable removed @@ -1337,6 +1433,7 @@ def convert_delete(irs): ################################################################################### ################################################################################### + def apply_ir_heuristics(irs, node): """ Apply a set of heuristic to improve slithIR diff --git a/slither/slithir/exceptions.py b/slither/slithir/exceptions.py index b0b82341c..2bfa5ab51 100644 --- a/slither/slithir/exceptions.py +++ b/slither/slithir/exceptions.py @@ -1,3 +1,5 @@ from slither.exceptions import SlitherException -class SlithIRError(SlitherException): pass + +class SlithIRError(SlitherException): + pass diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index f6277b413..66d6acd6b 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -7,11 +7,13 @@ from slither.slithir.variables import TupleVariable, ReferenceVariable logger = logging.getLogger("AssignmentOperationIR") -class Assignment(OperationWithLValue): +class Assignment(OperationWithLValue): def __init__(self, left_variable, right_variable, variable_return_type): assert is_valid_lvalue(left_variable) - assert is_valid_rvalue(right_variable) or isinstance(right_variable, (Function, TupleVariable)) + assert is_valid_rvalue(right_variable) or isinstance( + right_variable, (Function, TupleVariable) + ) super(Assignment, self).__init__() self._variables = [left_variable, right_variable] self._lvalue = left_variable @@ -39,5 +41,7 @@ class Assignment(OperationWithLValue): points = self.lvalue.points_to while isinstance(points, ReferenceVariable): points = points.points_to - return '{} (->{}) := {}({})'.format(self.lvalue, points, self.rvalue, self.rvalue.type) - return '{}({}) := {}({})'.format(self.lvalue, self.lvalue.type, self.rvalue, self.rvalue.type) + return "{} (->{}) := {}({})".format(self.lvalue, points, self.rvalue, self.rvalue.type) + return "{}({}) := {}({})".format( + self.lvalue, self.lvalue.type, self.rvalue, self.rvalue.type + ) diff --git a/slither/slithir/operations/balance.py b/slither/slithir/operations/balance.py index 648b83fbe..45a3b0313 100644 --- a/slither/slithir/operations/balance.py +++ b/slither/slithir/operations/balance.py @@ -4,13 +4,12 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class Balance(OperationWithLValue): - def __init__(self, value, lvalue): assert is_valid_rvalue(value) assert is_valid_lvalue(lvalue) self._value = value self._lvalue = lvalue - lvalue.set_type(ElementaryType('uint256')) + lvalue.set_type(ElementaryType("uint256")) @property def read(self): diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index cd886197f..553d0e8fe 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -33,57 +33,59 @@ class BinaryType(Enum): @staticmethod def return_bool(operation_type): - return operation_type in [BinaryType.OROR, - BinaryType.ANDAND, - BinaryType.LESS, - BinaryType.GREATER, - BinaryType.LESS_EQUAL, - BinaryType.GREATER_EQUAL, - BinaryType.EQUAL, - BinaryType.NOT_EQUAL] + return operation_type in [ + BinaryType.OROR, + BinaryType.ANDAND, + BinaryType.LESS, + BinaryType.GREATER, + BinaryType.LESS_EQUAL, + BinaryType.GREATER_EQUAL, + BinaryType.EQUAL, + BinaryType.NOT_EQUAL, + ] @staticmethod def get_type(operation_type): - if operation_type == '**': + if operation_type == "**": return BinaryType.POWER - if operation_type == '*': + if operation_type == "*": return BinaryType.MULTIPLICATION - if operation_type == '/': + if operation_type == "/": return BinaryType.DIVISION - if operation_type == '%': + if operation_type == "%": return BinaryType.MODULO - if operation_type == '+': + if operation_type == "+": return BinaryType.ADDITION - if operation_type == '-': + if operation_type == "-": return BinaryType.SUBTRACTION - if operation_type == '<<': + if operation_type == "<<": return BinaryType.LEFT_SHIFT - if operation_type == '>>': + if operation_type == ">>": return BinaryType.RIGHT_SHIFT - if operation_type == '&': + if operation_type == "&": return BinaryType.AND - if operation_type == '^': + if operation_type == "^": return BinaryType.CARET - if operation_type == '|': + if operation_type == "|": return BinaryType.OR - if operation_type == '<': + if operation_type == "<": return BinaryType.LESS - if operation_type == '>': + if operation_type == ">": return BinaryType.GREATER - if operation_type == '<=': + if operation_type == "<=": return BinaryType.LESS_EQUAL - if operation_type == '>=': + if operation_type == ">=": return BinaryType.GREATER_EQUAL - if operation_type == '==': + if operation_type == "==": return BinaryType.EQUAL - if operation_type == '!=': + if operation_type == "!=": return BinaryType.NOT_EQUAL - if operation_type == '&&': + if operation_type == "&&": return BinaryType.ANDAND - if operation_type == '||': + if operation_type == "||": return BinaryType.OROR - raise SlithIRError('get_type: Unknown operation type {})'.format(operation_type)) + raise SlithIRError("get_type: Unknown operation type {})".format(operation_type)) def __str__(self): if self == BinaryType.POWER: @@ -128,7 +130,6 @@ class BinaryType(Enum): class Binary(OperationWithLValue): - def __init__(self, result, left_variable, right_variable, operation_type): assert is_valid_rvalue(left_variable) assert is_valid_rvalue(right_variable) @@ -139,7 +140,7 @@ class Binary(OperationWithLValue): self._type = operation_type self._lvalue = result if BinaryType.return_bool(operation_type): - result.set_type(ElementaryType('bool')) + result.set_type(ElementaryType("bool")) else: result.set_type(left_variable.type) @@ -172,13 +173,13 @@ class Binary(OperationWithLValue): points = self.lvalue.points_to while isinstance(points, ReferenceVariable): points = points.points_to - return '{}(-> {}) = {} {} {}'.format(str(self.lvalue), - points, - self.variable_left, - self.type_str, - self.variable_right) - return '{}({}) = {} {} {}'.format(str(self.lvalue), - self.lvalue.type, - self.variable_left, - self.type_str, - self.variable_right) + return "{}(-> {}) = {} {} {}".format( + str(self.lvalue), points, self.variable_left, self.type_str, self.variable_right + ) + return "{}({}) = {} {} {}".format( + str(self.lvalue), + self.lvalue.type, + self.variable_left, + self.type_str, + self.variable_right, + ) diff --git a/slither/slithir/operations/call.py b/slither/slithir/operations/call.py index c52fbc7aa..bc57eab8f 100644 --- a/slither/slithir/operations/call.py +++ b/slither/slithir/operations/call.py @@ -2,7 +2,6 @@ from slither.slithir.operations.operation import Operation class Call(Operation): - def __init__(self): super(Call, self).__init__() self._arguments = [] @@ -16,15 +15,15 @@ class Call(Operation): self._arguments = v def can_reenter(self, callstack=None): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ return False def can_send_eth(self): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ return False diff --git a/slither/slithir/operations/codesize.py b/slither/slithir/operations/codesize.py index 85289dc38..10e221509 100644 --- a/slither/slithir/operations/codesize.py +++ b/slither/slithir/operations/codesize.py @@ -4,13 +4,12 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class CodeSize(OperationWithLValue): - def __init__(self, value, lvalue): assert is_valid_rvalue(value) assert is_valid_lvalue(lvalue) self._value = value self._lvalue = lvalue - lvalue.set_type(ElementaryType('uint256')) + lvalue.set_type(ElementaryType("uint256")) @property def read(self): diff --git a/slither/slithir/operations/event_call.py b/slither/slithir/operations/event_call.py index ead814675..1e369dc4e 100644 --- a/slither/slithir/operations/event_call.py +++ b/slither/slithir/operations/event_call.py @@ -1,4 +1,3 @@ - from slither.slithir.operations.call import Call @@ -18,4 +17,4 @@ class EventCall(Call): def __str__(self): args = [str(a) for a in self.arguments] - return 'Emit {}({})'.format(self.name, '.'.join(args)) + return "Emit {}({})".format(self.name, ".".join(args)) diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index f6ff22c52..de42140a8 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -97,15 +97,15 @@ class HighLevelCall(Call, OperationWithLValue): ################################################################################### def can_reenter(self, callstack=None): - ''' + """ Must be called after slithIR analysis pass For Solidity > 0.5, filter access to public variables and constant/pure/view For call to this. check if the destination can re-enter :param callstack: check for recursion :return: bool - ''' + """ # If solidity >0.5, STATICCALL is used - if self.slither.solc_version and self.slither.solc_version >= '0.5.0': + if self.slither.solc_version and self.slither.solc_version >= "0.5.0": if isinstance(self.function, Function) and (self.function.view or self.function.pure): return False if isinstance(self.function, Variable): @@ -113,7 +113,7 @@ class HighLevelCall(Call, OperationWithLValue): # If there is a call to itself # We can check that the function called is # reentrancy-safe - if self.destination == SolidityVariable('this'): + if self.destination == SolidityVariable("this"): if isinstance(self.function, Variable): return False # In case of recursion, return False @@ -126,10 +126,10 @@ class HighLevelCall(Call, OperationWithLValue): return True def can_send_eth(self): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ return self._call_value is not None # endregion @@ -140,27 +140,29 @@ class HighLevelCall(Call, OperationWithLValue): ################################################################################### def __str__(self): - value = '' - gas = '' + value = "" + gas = "" if self.call_value: - value = 'value:{}'.format(self.call_value) + value = "value:{}".format(self.call_value) if self.call_gas: - gas = 'gas:{}'.format(self.call_gas) + gas = "gas:{}".format(self.call_gas) arguments = [] if self.arguments: arguments = self.arguments - txt = '{}HIGH_LEVEL_CALL, dest:{}({}), function:{}, arguments:{} {} {}' + txt = "{}HIGH_LEVEL_CALL, dest:{}({}), function:{}, arguments:{} {} {}" if not self.lvalue: - lvalue = '' + lvalue = "" elif isinstance(self.lvalue.type, (list,)): - lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + lvalue = "{}({}) = ".format(self.lvalue, ",".join(str(x) for x in self.lvalue.type)) else: - lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) - return txt.format(lvalue, - self.destination, - self.destination.type, - self.function_name, - [str(x) for x in arguments], - value, - gas) + lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) + return txt.format( + lvalue, + self.destination, + self.destination.type, + self.function_name, + [str(x) for x in arguments], + value, + gas, + ) diff --git a/slither/slithir/operations/index.py b/slither/slithir/operations/index.py index 2a873c9fc..31093632f 100644 --- a/slither/slithir/operations/index.py +++ b/slither/slithir/operations/index.py @@ -5,10 +5,11 @@ from slither.slithir.variables.reference import ReferenceVariable class Index(OperationWithLValue): - def __init__(self, result, left_variable, right_variable, index_type): super(Index, self).__init__() - assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed('msg.data') + assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed( + "msg.data" + ) assert is_valid_rvalue(right_variable) assert isinstance(result, ReferenceVariable) self._variables = [left_variable, right_variable] @@ -36,4 +37,6 @@ class Index(OperationWithLValue): return self._type def __str__(self): - return "{}({}) -> {}[{}]".format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right) + return "{}({}) -> {}[{}]".format( + self.lvalue, self.lvalue.type, self.variable_left, self.variable_right + ) diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py index d3f71145b..f19d38227 100644 --- a/slither/slithir/operations/init_array.py +++ b/slither/slithir/operations/init_array.py @@ -3,7 +3,6 @@ from slither.slithir.utils.utils import is_valid_rvalue class InitArray(OperationWithLValue): - def __init__(self, init_values, lvalue): # init_values can be an array of n dimension # reduce was removed in py3 @@ -33,7 +32,6 @@ class InitArray(OperationWithLValue): return list(self._init_values) def __str__(self): - def convert(elem): if isinstance(elem, (list,)): return str([convert(x) for x in elem]) diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py index dbd99607b..e2bcfbb14 100644 --- a/slither/slithir/operations/internal_call.py +++ b/slither/slithir/operations/internal_call.py @@ -5,7 +5,6 @@ from slither.slithir.operations.lvalue import OperationWithLValue class InternalCall(Call, OperationWithLValue): - def __init__(self, function, nbr_arguments, result, type_call): super(InternalCall, self).__init__() if isinstance(function, Function): @@ -59,15 +58,13 @@ class InternalCall(Call, OperationWithLValue): def __str__(self): args = [str(a) for a in self.arguments] if not self.lvalue: - lvalue = '' + lvalue = "" elif isinstance(self.lvalue.type, (list,)): - lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + lvalue = "{}({}) = ".format(self.lvalue, ",".join(str(x) for x in self.lvalue.type)) else: - lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) + lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) if self.is_modifier_call: - txt = '{}MODIFIER_CALL, {}({})' + txt = "{}MODIFIER_CALL, {}({})" else: - txt = '{}INTERNAL_CALL, {}({})' - return txt.format(lvalue, - self.function.canonical_name, - ','.join(args)) + txt = "{}INTERNAL_CALL, {}({})" + return txt.format(lvalue, self.function.canonical_name, ",".join(args)) diff --git a/slither/slithir/operations/internal_dynamic_call.py b/slither/slithir/operations/internal_dynamic_call.py index ec7c940a2..509996ced 100644 --- a/slither/slithir/operations/internal_dynamic_call.py +++ b/slither/slithir/operations/internal_dynamic_call.py @@ -6,7 +6,6 @@ from slither.slithir.utils.utils import is_valid_lvalue class InternalDynamicCall(Call, OperationWithLValue): - def __init__(self, lvalue, function, function_type): assert isinstance(function_type, FunctionType) assert isinstance(function, Variable) @@ -16,7 +15,7 @@ class InternalDynamicCall(Call, OperationWithLValue): self._function_type = function_type self._lvalue = lvalue - self._callid = None # only used if gas/value != 0 + self._callid = None # only used if gas/value != 0 self._call_value = None self._call_gas = None @@ -40,7 +39,6 @@ class InternalDynamicCall(Call, OperationWithLValue): def call_value(self, v): self._call_value = v - @property def call_gas(self): return self._call_gas @@ -58,23 +56,18 @@ class InternalDynamicCall(Call, OperationWithLValue): self._callid = c def __str__(self): - value = '' - gas = '' + value = "" + gas = "" args = [str(a) for a in self.arguments] if self.call_value: - value = 'value:{}'.format(self.call_value) + value = "value:{}".format(self.call_value) if self.call_gas: - gas = 'gas:{}'.format(self.call_gas) + gas = "gas:{}".format(self.call_gas) if not self.lvalue: - lvalue = '' + lvalue = "" elif isinstance(self.lvalue.type, (list,)): - lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + lvalue = "{}({}) = ".format(self.lvalue, ",".join(str(x) for x in self.lvalue.type)) else: - lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) - txt = '{}INTERNAL_DYNAMIC_CALL {}({}) {} {}' - return txt.format(lvalue, - self.function.name, - ','.join(args), - value, - gas) - + lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) + txt = "{}INTERNAL_DYNAMIC_CALL {}({}) {} {}" + return txt.format(lvalue, self.function.name, ",".join(args), value, gas) diff --git a/slither/slithir/operations/length.py b/slither/slithir/operations/length.py index 1bdefb44b..7f134fb4d 100644 --- a/slither/slithir/operations/length.py +++ b/slither/slithir/operations/length.py @@ -4,13 +4,12 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class Length(OperationWithLValue): - def __init__(self, value, lvalue): assert is_valid_rvalue(value) assert is_valid_lvalue(lvalue) self._value = value self._lvalue = lvalue - lvalue.set_type(ElementaryType('uint256')) + lvalue.set_type(ElementaryType("uint256")) @property def read(self): diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index 71a11ef96..7947c8ae3 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -12,10 +12,10 @@ class LibraryCall(HighLevelCall): assert isinstance(destination, Contract) def can_reenter(self, callstack=None): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ # In case of recursion, return False callstack = [] if callstack is None else callstack if self.function in callstack: @@ -24,21 +24,19 @@ class LibraryCall(HighLevelCall): return self.function.can_reenter(callstack) def __str__(self): - gas = '' + gas = "" if self.call_gas: - gas = 'gas:{}'.format(self.call_gas) + gas = "gas:{}".format(self.call_gas) arguments = [] if self.arguments: arguments = self.arguments if not self.lvalue: - lvalue = '' + lvalue = "" elif isinstance(self.lvalue.type, (list,)): - lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + lvalue = "{}({}) = ".format(self.lvalue, ",".join(str(x) for x in self.lvalue.type)) else: - lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) - txt = '{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}' - return txt.format(lvalue, - self.destination, - self.function_name, - [str(x) for x in arguments], - gas) + lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) + txt = "{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}" + return txt.format( + lvalue, self.destination, self.function_name, [str(x) for x in arguments], gas + ) diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index 1db3ba850..f36601a8c 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -56,17 +56,17 @@ class LowLevelCall(Call, OperationWithLValue): return self._unroll([x for x in all_read if x]) def can_reenter(self, callstack=None): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ return True def can_send_eth(self): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ return self._call_value is not None @property @@ -86,25 +86,27 @@ class LowLevelCall(Call, OperationWithLValue): return self._type_call def __str__(self): - value = '' - gas = '' + value = "" + gas = "" if self.call_value: - value = 'value:{}'.format(self.call_value) + value = "value:{}".format(self.call_value) if self.call_gas: - gas = 'gas:{}'.format(self.call_gas) + gas = "gas:{}".format(self.call_gas) arguments = [] if self.arguments: arguments = self.arguments return_type = self.lvalue.type if return_type and isinstance(return_type, list): - return_type = ','.join(str(x) for x in return_type) - - txt = '{}({}) = LOW_LEVEL_CALL, dest:{}, function:{}, arguments:{} {} {}' - return txt.format(self.lvalue, - return_type, - self.destination, - self.function_name, - [str(x) for x in arguments], - value, - gas) + return_type = ",".join(str(x) for x in return_type) + + txt = "{}({}) = LOW_LEVEL_CALL, dest:{}, function:{}, arguments:{} {} {}" + return txt.format( + self.lvalue, + return_type, + self.destination, + self.function_name, + [str(x) for x in arguments], + value, + gas, + ) diff --git a/slither/slithir/operations/lvalue.py b/slither/slithir/operations/lvalue.py index a5f009005..2aab8226b 100644 --- a/slither/slithir/operations/lvalue.py +++ b/slither/slithir/operations/lvalue.py @@ -2,9 +2,9 @@ from slither.slithir.operations.operation import Operation class OperationWithLValue(Operation): - ''' + """ Operation with a lvalue - ''' + """ def __init__(self): super(OperationWithLValue, self).__init__() diff --git a/slither/slithir/operations/member.py b/slither/slithir/operations/member.py index a8d66fb03..656aaa442 100644 --- a/slither/slithir/operations/member.py +++ b/slither/slithir/operations/member.py @@ -7,7 +7,6 @@ from slither.slithir.variables.reference import ReferenceVariable class Member(OperationWithLValue): - def __init__(self, variable_left, variable_right, result): assert is_valid_rvalue(variable_left) or isinstance(variable_left, (Contract, Enum)) assert isinstance(variable_right, Constant) @@ -19,7 +18,6 @@ class Member(OperationWithLValue): self._gas = None self._value = None - @property def read(self): return [self.variable_left, self.variable_right] @@ -49,5 +47,6 @@ class Member(OperationWithLValue): self._gas = gas def __str__(self): - return '{}({}) -> {}.{}'.format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right) - + return "{}({}) -> {}.{}".format( + self.lvalue, self.lvalue.type, self.variable_left, self.variable_right + ) diff --git a/slither/slithir/operations/new_array.py b/slither/slithir/operations/new_array.py index 2191dc66e..259e2ec47 100644 --- a/slither/slithir/operations/new_array.py +++ b/slither/slithir/operations/new_array.py @@ -4,7 +4,6 @@ from slither.core.solidity_types.type import Type class NewArray(Call, OperationWithLValue): - def __init__(self, depth, array_type, lvalue): super(NewArray, self).__init__() assert isinstance(array_type, Type) @@ -27,4 +26,6 @@ class NewArray(Call, OperationWithLValue): def __str__(self): args = [str(a) for a in self.arguments] - return '{} = new {}{}({})'.format(self.lvalue, self.array_type, '[]' * self.depth, ','.join(args)) + return "{} = new {}{}({})".format( + self.lvalue, self.array_type, "[]" * self.depth, ",".join(args) + ) diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index b1dbd57ed..f535e6e02 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -4,7 +4,6 @@ from slither.slithir.variables.constant import Constant class NewContract(Call, OperationWithLValue): - def __init__(self, contract_name, lvalue): assert isinstance(contract_name, Constant) assert is_valid_lvalue(lvalue) @@ -61,13 +60,13 @@ class NewContract(Call, OperationWithLValue): ################################################################################### def can_reenter(self, callstack=None): - ''' + """ Must be called after slithIR analysis pass For Solidity > 0.5, filter access to public variables and constant/pure/view For call to this. check if the destination can re-enter :param callstack: check for recursion :return: bool - ''' + """ callstack = [] if callstack is None else callstack constructor = self.contract_created.constructor if constructor is None: @@ -78,19 +77,19 @@ class NewContract(Call, OperationWithLValue): return constructor.can_reenter(callstack) def can_send_eth(self): - ''' + """ Must be called after slithIR analysis pass :return: bool - ''' + """ return self._call_value is not None # endregion def __str__(self): - options = '' + options = "" if self.call_value: - options = 'value:{} '.format(self.call_value) + options = "value:{} ".format(self.call_value) if self.call_salt: - options += 'salt:{} '.format(self.call_salt) + options += "salt:{} ".format(self.call_salt) args = [str(a) for a in self.arguments] - return '{} = new {}({}) {}'.format(self.lvalue, self.contract_name, ','.join(args), options) + return "{} = new {}({}) {}".format(self.lvalue, self.contract_name, ",".join(args), options) diff --git a/slither/slithir/operations/new_elementary_type.py b/slither/slithir/operations/new_elementary_type.py index a2048a319..300aed31c 100644 --- a/slither/slithir/operations/new_elementary_type.py +++ b/slither/slithir/operations/new_elementary_type.py @@ -5,7 +5,6 @@ from slither.slithir.utils.utils import is_valid_lvalue class NewElementaryType(Call, OperationWithLValue): - def __init__(self, new_type, lvalue): assert isinstance(new_type, ElementaryType) assert is_valid_lvalue(lvalue) @@ -24,4 +23,4 @@ class NewElementaryType(Call, OperationWithLValue): def __str__(self): args = [str(a) for a in self.arguments] - return '{} = new {}({})'.format(self.lvalue, self._type, ','.join(args)) + return "{} = new {}({})".format(self.lvalue, self._type, ",".join(args)) diff --git a/slither/slithir/operations/new_structure.py b/slither/slithir/operations/new_structure.py index dbfc86b77..f17acb47a 100644 --- a/slither/slithir/operations/new_structure.py +++ b/slither/slithir/operations/new_structure.py @@ -7,7 +7,6 @@ from slither.core.declarations.structure import Structure class NewStructure(Call, OperationWithLValue): - def __init__(self, structure, lvalue): super(NewStructure, self).__init__() assert isinstance(structure, Structure) @@ -30,4 +29,4 @@ class NewStructure(Call, OperationWithLValue): def __str__(self): args = [str(a) for a in self.arguments] - return '{} = new {}({})'.format(self.lvalue, self.structure_name, ','.join(args)) + return "{} = new {}({})".format(self.lvalue, self.structure_name, ",".join(args)) diff --git a/slither/slithir/operations/nop.py b/slither/slithir/operations/nop.py index fd73c45f8..2d2d360dd 100644 --- a/slither/slithir/operations/nop.py +++ b/slither/slithir/operations/nop.py @@ -2,7 +2,6 @@ from .operation import Operation class Nop(Operation): - @property def read(self): return [] diff --git a/slither/slithir/operations/operation.py b/slither/slithir/operations/operation.py index c8b2228ca..e0df640c1 100644 --- a/slither/slithir/operations/operation.py +++ b/slither/slithir/operations/operation.py @@ -6,7 +6,6 @@ from slither.utils.utils import unroll class AbstractOperation(abc.ABC): - @property @abc.abstractmethod def read(self): @@ -25,7 +24,6 @@ class AbstractOperation(abc.ABC): class Operation(Context, ChildExpression, ChildNode, AbstractOperation): - @property def used(self): """ diff --git a/slither/slithir/operations/phi.py b/slither/slithir/operations/phi.py index 0660e6062..5438490d3 100644 --- a/slither/slithir/operations/phi.py +++ b/slither/slithir/operations/phi.py @@ -3,11 +3,10 @@ from slither.slithir.utils.utils import is_valid_lvalue class Phi(OperationWithLValue): - def __init__(self, left_variable, nodes): # When Phi operations are created the # correct indexes of the variables are not yet computed - # We store the nodes where the variables are written + # We store the nodes where the variables are written # so we can update the rvalues of the Phi operation # after its instantiation assert is_valid_lvalue(left_variable) @@ -34,4 +33,6 @@ class Phi(OperationWithLValue): return self._nodes def __str__(self): - return '{}({}) := \u03D5({})'.format(self.lvalue, self.lvalue.type, [str(v) for v in self._rvalues]) + return "{}({}) := \u03D5({})".format( + self.lvalue, self.lvalue.type, [str(v) for v in self._rvalues] + ) diff --git a/slither/slithir/operations/phi_callback.py b/slither/slithir/operations/phi_callback.py index 252ea739c..ce59730cf 100644 --- a/slither/slithir/operations/phi_callback.py +++ b/slither/slithir/operations/phi_callback.py @@ -3,7 +3,6 @@ from .phi import Phi class PhiCallback(Phi): - def __init__(self, left_variable, nodes, call_ir, rvalue): assert is_valid_lvalue(left_variable) assert isinstance(nodes, set) @@ -26,9 +25,9 @@ class PhiCallback(Phi): @property def rvalue_no_callback(self): - ''' + """ rvalue if callback are not considered - ''' + """ return self._rvalue_no_callback @rvalues.setter @@ -40,4 +39,6 @@ class PhiCallback(Phi): return self._nodes def __str__(self): - return '{}({}) := \u03D5({})'.format(self.lvalue, self.lvalue.type, [v.ssa_name for v in self._rvalues]) + return "{}({}) := \u03D5({})".format( + self.lvalue, self.lvalue.type, [v.ssa_name for v in self._rvalues] + ) diff --git a/slither/slithir/operations/push.py b/slither/slithir/operations/push.py index 3cf068363..dc836c5c0 100644 --- a/slither/slithir/operations/push.py +++ b/slither/slithir/operations/push.py @@ -3,7 +3,6 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class Push(OperationWithLValue): - def __init__(self, array, value): assert is_valid_rvalue(value) or isinstance(value, Function) assert is_valid_lvalue(array) diff --git a/slither/slithir/operations/return_operation.py b/slither/slithir/operations/return_operation.py index 2e5ae3b6a..9297820ec 100644 --- a/slither/slithir/operations/return_operation.py +++ b/slither/slithir/operations/return_operation.py @@ -12,11 +12,15 @@ class Return(Operation): """ def __init__(self, values): - # Note: Can return None + # Note: Can return None # ex: return call() # where call() dont return if not isinstance(values, list): - assert is_valid_rvalue(values) or isinstance(values, (TupleVariable, Function)) or values is None + assert ( + is_valid_rvalue(values) + or isinstance(values, (TupleVariable, Function)) + or values is None + ) if values is None: values = [] else: @@ -47,4 +51,4 @@ class Return(Operation): return self._unroll(self._values) def __str__(self): - return "RETURN {}".format(','.join(['{}'.format(x) for x in self.values])) + return "RETURN {}".format(",".join(["{}".format(x) for x in self.values])) diff --git a/slither/slithir/operations/send.py b/slither/slithir/operations/send.py index 14209705d..cbd5901f5 100644 --- a/slither/slithir/operations/send.py +++ b/slither/slithir/operations/send.py @@ -6,7 +6,6 @@ from slither.slithir.utils.utils import is_valid_lvalue class Send(Call, OperationWithLValue): - def __init__(self, destination, value, result): assert is_valid_lvalue(result) assert isinstance(destination, (Variable, SolidityVariable)) @@ -32,6 +31,8 @@ class Send(Call, OperationWithLValue): return self._destination def __str__(self): - value = 'value:{}'.format(self.call_value) - return str(self.lvalue) + ' = SEND dest:{} {}'.format(self.destination, value) + value = "value:{}".format(self.call_value) + return str(self.lvalue) + " = SEND dest:{} {}".format(self.destination, value) + + # diff --git a/slither/slithir/operations/solidity_call.py b/slither/slithir/operations/solidity_call.py index 4d4ea7592..3823bccf8 100644 --- a/slither/slithir/operations/solidity_call.py +++ b/slither/slithir/operations/solidity_call.py @@ -4,7 +4,6 @@ from slither.slithir.operations.lvalue import OperationWithLValue class SolidityCall(Call, OperationWithLValue): - def __init__(self, function, nbr_arguments, result, type_call): assert isinstance(function, SolidityFunction) super(SolidityCall, self).__init__() @@ -30,17 +29,21 @@ class SolidityCall(Call, OperationWithLValue): return self._type_call def __str__(self): - if (self.function == SolidityFunction("abi.decode()") and - len(self.arguments) == 2 and - isinstance(self.arguments[1], list)): - args = str(self.arguments[0]) + '(' + ','.join([str(a) for a in self.arguments[1]]) + ')' + if ( + self.function == SolidityFunction("abi.decode()") + and len(self.arguments) == 2 + and isinstance(self.arguments[1], list) + ): + args = ( + str(self.arguments[0]) + "(" + ",".join([str(a) for a in self.arguments[1]]) + ")" + ) else: - args = ','.join([str(a) for a in self.arguments]) + args = ",".join([str(a) for a in self.arguments]) - lvalue = '' + lvalue = "" if self.lvalue: if isinstance(self.lvalue.type, (list,)): - lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + lvalue = "{}({}) = ".format(self.lvalue, ",".join(str(x) for x in self.lvalue.type)) else: - lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) - return lvalue + 'SOLIDITY_CALL {}({})'.format(self.function.full_name, args) + lvalue = "{}({}) = ".format(self.lvalue, self.lvalue.type) + return lvalue + "SOLIDITY_CALL {}({})".format(self.function.full_name, args) diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py index 6fb5d50df..9b7977283 100644 --- a/slither/slithir/operations/transfer.py +++ b/slither/slithir/operations/transfer.py @@ -4,7 +4,6 @@ from slither.core.declarations.solidity_variables import SolidityVariable class Transfer(Call): - def __init__(self, destination, value): assert isinstance(destination, (Variable, SolidityVariable)) self._destination = destination @@ -28,5 +27,5 @@ class Transfer(Call): return self._destination def __str__(self): - value = 'value:{}'.format(self.call_value) - return 'Transfer dest:{} {}'.format(self.destination, value) + value = "value:{}".format(self.call_value) + return "Transfer dest:{} {}".format(self.destination, value) diff --git a/slither/slithir/operations/type_conversion.py b/slither/slithir/operations/type_conversion.py index d80802054..b326abf2d 100644 --- a/slither/slithir/operations/type_conversion.py +++ b/slither/slithir/operations/type_conversion.py @@ -5,7 +5,6 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class TypeConversion(OperationWithLValue): - def __init__(self, result, variable, variable_type): super().__init__() assert is_valid_rvalue(variable) or isinstance(variable, Contract) @@ -15,7 +14,6 @@ class TypeConversion(OperationWithLValue): self._variable = variable self._type = variable_type self._lvalue = result - @property def variable(self): @@ -30,4 +28,4 @@ class TypeConversion(OperationWithLValue): return [self.variable] def __str__(self): - return str(self.lvalue) +' = CONVERT {} to {}'.format(self.variable, self.type) + return str(self.lvalue) + " = CONVERT {} to {}".format(self.variable, self.type) diff --git a/slither/slithir/operations/unary.py b/slither/slithir/operations/unary.py index 99ca109e7..e09054918 100644 --- a/slither/slithir/operations/unary.py +++ b/slither/slithir/operations/unary.py @@ -15,11 +15,11 @@ class UnaryType(Enum): @staticmethod def get_type(operation_type, isprefix): if isprefix: - if operation_type == '!': + if operation_type == "!": return UnaryType.BANG - if operation_type == '~': + if operation_type == "~": return UnaryType.TILD - raise SlithIRError('get_type: Unknown operation type {}'.format(operation_type)) + raise SlithIRError("get_type: Unknown operation type {}".format(operation_type)) def __str__(self): if self == UnaryType.BANG: @@ -31,7 +31,6 @@ class UnaryType(Enum): class Unary(OperationWithLValue): - def __init__(self, result, variable, operation_type): assert is_valid_rvalue(variable) assert is_valid_lvalue(result) diff --git a/slither/slithir/operations/unpack.py b/slither/slithir/operations/unpack.py index cfc7ca42a..553f7ef1a 100644 --- a/slither/slithir/operations/unpack.py +++ b/slither/slithir/operations/unpack.py @@ -4,7 +4,6 @@ from slither.slithir.variables.tuple import TupleVariable class Unpack(OperationWithLValue): - def __init__(self, result, tuple_var, idx): assert is_valid_lvalue(result) assert isinstance(tuple_var, TupleVariable) @@ -27,7 +26,6 @@ class Unpack(OperationWithLValue): return self._idx def __str__(self): - return "{}({})= UNPACK {} index: {} ".format(self.lvalue, - self.lvalue.type, - self.tuple, - self.index) + return "{}({})= UNPACK {} index: {} ".format( + self.lvalue, self.lvalue.type, self.tuple, self.index + ) diff --git a/slither/slithir/tmp_operations/argument.py b/slither/slithir/tmp_operations/argument.py index 4c04924c2..15ecf358b 100644 --- a/slither/slithir/tmp_operations/argument.py +++ b/slither/slithir/tmp_operations/argument.py @@ -1,21 +1,21 @@ from enum import Enum from slither.slithir.operations.operation import Operation + class ArgumentType(Enum): CALL = 0 VALUE = 1 GAS = 2 DATA = 3 -class Argument(Operation): +class Argument(Operation): def __init__(self, argument): super(Argument, self).__init__() self._argument = argument self._type = ArgumentType.CALL self._callid = None - @property def argument(self): return self._argument @@ -31,17 +31,16 @@ class Argument(Operation): @property def read(self): return [self.argument] - + def set_type(self, t): assert isinstance(t, ArgumentType) self._type = t def get_type(self): return self._type - + def __str__(self): - call_id = 'none' + call_id = "none" if self.call_id: - call_id = '(id ({}))'.format(self.call_id) - return 'ARG_{} {} {}'.format(self._type.name, str(self._argument), call_id) - + call_id = "(id ({}))".format(self.call_id) + return "ARG_{} {} {}".format(self._type.name, str(self._argument), call_id) diff --git a/slither/slithir/tmp_operations/tmp_call.py b/slither/slithir/tmp_operations/tmp_call.py index 4d52a93c8..f56631a6b 100644 --- a/slither/slithir/tmp_operations/tmp_call.py +++ b/slither/slithir/tmp_operations/tmp_call.py @@ -1,23 +1,26 @@ -from slither.core.declarations import Event, Contract, SolidityVariableComposed, SolidityFunction, Structure +from slither.core.declarations import ( + Event, + Contract, + SolidityVariableComposed, + SolidityFunction, + Structure, +) from slither.core.variables.variable import Variable from slither.slithir.operations.lvalue import OperationWithLValue class TmpCall(OperationWithLValue): - def __init__(self, called, nbr_arguments, result, type_call): - assert isinstance(called, (Contract, - Variable, - SolidityVariableComposed, - SolidityFunction, - Structure, - Event)) + assert isinstance( + called, + (Contract, Variable, SolidityVariableComposed, SolidityFunction, Structure, Event), + ) super(TmpCall, self).__init__() self._called = called self._nbr_arguments = nbr_arguments self._type_call = type_call self._lvalue = result - self._ori = None # + self._ori = None # self._callid = None self._gas = None self._value = None @@ -83,5 +86,4 @@ class TmpCall(OperationWithLValue): self._ori = ori def __str__(self): - return str(self.lvalue) +' = TMPCALL{} '.format(self.nbr_arguments)+ str(self._called) - + return str(self.lvalue) + " = TMPCALL{} ".format(self.nbr_arguments) + str(self._called) diff --git a/slither/slithir/tmp_operations/tmp_new_array.py b/slither/slithir/tmp_operations/tmp_new_array.py index e4deb4fe7..0f7fa30aa 100644 --- a/slither/slithir/tmp_operations/tmp_new_array.py +++ b/slither/slithir/tmp_operations/tmp_new_array.py @@ -1,8 +1,8 @@ from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.solidity_types.type import Type -class TmpNewArray(OperationWithLValue): +class TmpNewArray(OperationWithLValue): def __init__(self, depth, array_type, lvalue): super(TmpNewArray, self).__init__() assert isinstance(array_type, Type) @@ -23,5 +23,4 @@ class TmpNewArray(OperationWithLValue): return self._depth def __str__(self): - return '{} = new {}{}'.format(self.lvalue, self.array_type, '[]'*self._depth) - + return "{} = new {}{}".format(self.lvalue, self.array_type, "[]" * self._depth) diff --git a/slither/slithir/tmp_operations/tmp_new_contract.py b/slither/slithir/tmp_operations/tmp_new_contract.py index f29d007d4..699562960 100644 --- a/slither/slithir/tmp_operations/tmp_new_contract.py +++ b/slither/slithir/tmp_operations/tmp_new_contract.py @@ -1,7 +1,7 @@ from slither.slithir.operations.lvalue import OperationWithLValue -class TmpNewContract(OperationWithLValue): +class TmpNewContract(OperationWithLValue): def __init__(self, contract_name, lvalue): super(TmpNewContract, self).__init__() self._contract_name = contract_name @@ -34,5 +34,4 @@ class TmpNewContract(OperationWithLValue): return [] def __str__(self): - return '{} = new {}'.format(self.lvalue, self.contract_name) - + return "{} = new {}".format(self.lvalue, self.contract_name) diff --git a/slither/slithir/tmp_operations/tmp_new_elementary_type.py b/slither/slithir/tmp_operations/tmp_new_elementary_type.py index 1fcb65334..1a70c5074 100644 --- a/slither/slithir/tmp_operations/tmp_new_elementary_type.py +++ b/slither/slithir/tmp_operations/tmp_new_elementary_type.py @@ -1,8 +1,8 @@ from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.solidity_types.elementary_type import ElementaryType -class TmpNewElementaryType(OperationWithLValue): +class TmpNewElementaryType(OperationWithLValue): def __init__(self, new_type, lvalue): assert isinstance(new_type, ElementaryType) super(TmpNewElementaryType, self).__init__() @@ -18,4 +18,4 @@ class TmpNewElementaryType(OperationWithLValue): return self._type def __str__(self): - return '{} = new {}'.format(self.lvalue, self._type) + return "{} = new {}".format(self.lvalue, self._type) diff --git a/slither/slithir/tmp_operations/tmp_new_structure.py b/slither/slithir/tmp_operations/tmp_new_structure.py index 90f11d115..6c5b5895b 100644 --- a/slither/slithir/tmp_operations/tmp_new_structure.py +++ b/slither/slithir/tmp_operations/tmp_new_structure.py @@ -1,7 +1,7 @@ from slither.slithir.operations.lvalue import OperationWithLValue -class TmpNewStructure(OperationWithLValue): +class TmpNewStructure(OperationWithLValue): def __init__(self, contract_name, lvalue): super(TmpNewStructure, self).__init__() self._contract_name = contract_name @@ -16,5 +16,4 @@ class TmpNewStructure(OperationWithLValue): return [] def __str__(self): - return '{} = tmpnew {}'.format(self.lvalue, self.contract_name) - + return "{} = tmpnew {}".format(self.lvalue, self.contract_name) diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index 277fb4ea7..063f7d96b 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -1,30 +1,65 @@ import logging from slither.core.cfg.node import NodeType -from slither.core.declarations import (Contract, Enum, Function, - SolidityFunction, SolidityVariable, Structure) +from slither.core.declarations import ( + Contract, + Enum, + Function, + SolidityFunction, + SolidityVariable, + Structure, +) from slither.core.solidity_types.type import Type from slither.core.variables.local_variable import LocalVariable from slither.core.variables.state_variable import StateVariable -from slither.slithir.operations import (Assignment, Balance, Binary, Condition, - Delete, EventCall, HighLevelCall, - Index, InitArray, InternalCall, - InternalDynamicCall, Length, - LibraryCall, LowLevelCall, Member, - NewArray, NewContract, - NewElementaryType, NewStructure, - OperationWithLValue, Phi, PhiCallback, - Push, Return, Send, SolidityCall, - Transfer, TypeConversion, Unary, - Unpack, Nop) +from slither.slithir.operations import ( + Assignment, + Balance, + Binary, + Condition, + Delete, + EventCall, + HighLevelCall, + Index, + InitArray, + InternalCall, + InternalDynamicCall, + Length, + LibraryCall, + LowLevelCall, + Member, + NewArray, + NewContract, + NewElementaryType, + NewStructure, + OperationWithLValue, + Phi, + PhiCallback, + Push, + Return, + Send, + SolidityCall, + Transfer, + TypeConversion, + Unary, + Unpack, + Nop, +) from slither.slithir.operations.codesize import CodeSize -from slither.slithir.variables import (Constant, LocalIRVariable, - ReferenceVariable, ReferenceVariableSSA, - StateIRVariable, TemporaryVariable, - TemporaryVariableSSA, TupleVariable, TupleVariableSSA) +from slither.slithir.variables import ( + Constant, + LocalIRVariable, + ReferenceVariable, + ReferenceVariableSSA, + StateIRVariable, + TemporaryVariable, + TemporaryVariableSSA, + TupleVariable, + TupleVariableSSA, +) from slither.slithir.exceptions import SlithIRError -logger = logging.getLogger('SSA_Conversion') +logger = logging.getLogger("SSA_Conversion") ################################################################################### ################################################################################### @@ -32,6 +67,7 @@ logger = logging.getLogger('SSA_Conversion') ################################################################################### ################################################################################### + def transform_slithir_vars_to_ssa(function): """ Transform slithIR vars to SSA (TemporaryVariable, ReferenceVariable, TupleVariable) @@ -52,19 +88,21 @@ def transform_slithir_vars_to_ssa(function): for idx in range(len(tuple_variables)): tuple_variables[idx].index = idx + ################################################################################### ################################################################################### # region SSA conversion ################################################################################### ################################################################################### + def add_ssa_ir(function, all_state_variables_instances): - ''' + """ Add SSA version of the IR Args: function all_state_variables_instances - ''' + """ if not function.is_implemented: return @@ -89,18 +127,17 @@ def add_ssa_ir(function, all_state_variables_instances): add_phi_origins(function.entry_point, init_definition, dict()) - for node in function.nodes: for (variable, nodes) in node.phi_origins_local_variables.values(): - if len(nodes)<2: + if len(nodes) < 2: continue if not is_used_later(node, variable): continue node.add_ssa_ir(Phi(LocalIRVariable(variable), nodes)) for (variable, nodes) in node.phi_origins_state_variables.values(): - if len(nodes)<2: + if len(nodes) < 2: continue - #if not is_used_later(node, variable.name, []): + # if not is_used_later(node, variable.name, []): # continue node.add_ssa_ir(Phi(StateIRVariable(variable), nodes)) @@ -111,8 +148,8 @@ def add_ssa_ir(function, all_state_variables_instances): function.add_parameter_ssa(new_var) if new_var.is_storage: fake_variable = LocalIRVariable(v) - fake_variable.name = 'STORAGE_'+fake_variable.name - fake_variable.set_location('reference_to_storage') + fake_variable.name = "STORAGE_" + fake_variable.name + fake_variable.set_location("reference_to_storage") new_var.refers_to = {fake_variable} init_local_variables_instances[fake_variable.name] = fake_variable init_local_variables_instances[v.name] = new_var @@ -123,8 +160,8 @@ def add_ssa_ir(function, all_state_variables_instances): function.add_return_ssa(new_var) if new_var.is_storage: fake_variable = LocalIRVariable(v) - fake_variable.name = 'STORAGE_'+fake_variable.name - fake_variable.set_location('reference_to_storage') + fake_variable.name = "STORAGE_" + fake_variable.name + fake_variable.set_location("reference_to_storage") new_var.refers_to = {fake_variable} init_local_variables_instances[fake_variable.name] = fake_variable init_local_variables_instances[v.name] = new_var @@ -133,29 +170,46 @@ def add_ssa_ir(function, all_state_variables_instances): init_state_variables_instances = dict(all_state_variables_instances) - initiate_all_local_variables_instances(function.nodes, init_local_variables_instances, all_init_local_variables_instances) - - generate_ssa_irs(function.entry_point, - dict(init_local_variables_instances), - all_init_local_variables_instances, - dict(init_state_variables_instances), - all_state_variables_instances, - init_local_variables_instances, - []) - - fix_phi_rvalues_and_storage_ref(function.entry_point, - dict(init_local_variables_instances), - all_init_local_variables_instances, - dict(init_state_variables_instances), - all_state_variables_instances, - init_local_variables_instances) - -def generate_ssa_irs(node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances, init_local_variables_instances, visited): + initiate_all_local_variables_instances( + function.nodes, init_local_variables_instances, all_init_local_variables_instances + ) + + generate_ssa_irs( + function.entry_point, + dict(init_local_variables_instances), + all_init_local_variables_instances, + dict(init_state_variables_instances), + all_state_variables_instances, + init_local_variables_instances, + [], + ) + + fix_phi_rvalues_and_storage_ref( + function.entry_point, + dict(init_local_variables_instances), + all_init_local_variables_instances, + dict(init_state_variables_instances), + all_state_variables_instances, + init_local_variables_instances, + ) + + +def generate_ssa_irs( + node, + local_variables_instances, + all_local_variables_instances, + state_variables_instances, + all_state_variables_instances, + init_local_variables_instances, + visited, +): if node in visited: return - if node.type in [NodeType.ENDIF, NodeType.ENDLOOP] and any(not father in visited for father in node.fathers): + if node.type in [NodeType.ENDIF, NodeType.ENDLOOP] and any( + not father in visited for father in node.fathers + ): return # visited is shared @@ -163,8 +217,14 @@ def generate_ssa_irs(node, local_variables_instances, all_local_variables_instan for ir in node.irs_ssa: assert isinstance(ir, Phi) - update_lvalue(ir, node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances) - + update_lvalue( + ir, + node, + local_variables_instances, + all_local_variables_instances, + state_variables_instances, + all_state_variables_instances, + ) # these variables are lived only during the liveness of the block # They dont need phi function @@ -173,23 +233,27 @@ def generate_ssa_irs(node, local_variables_instances, all_local_variables_instan tuple_variables_instances = dict() for ir in node.irs: - new_ir = copy_ir(ir, - local_variables_instances, - state_variables_instances, - temporary_variables_instances, - reference_variables_instances, - tuple_variables_instances, - all_local_variables_instances) + new_ir = copy_ir( + ir, + local_variables_instances, + state_variables_instances, + temporary_variables_instances, + reference_variables_instances, + tuple_variables_instances, + all_local_variables_instances, + ) new_ir.set_expression(ir.expression) new_ir.set_node(ir.node) - update_lvalue(new_ir, - node, - local_variables_instances, - all_local_variables_instances, - state_variables_instances, - all_state_variables_instances) + update_lvalue( + new_ir, + node, + local_variables_instances, + all_local_variables_instances, + state_variables_instances, + all_state_variables_instances, + ) if new_ir: @@ -218,24 +282,28 @@ def generate_ssa_irs(node, local_variables_instances, all_local_variables_instan else: new_ir.lvalue.add_refers_to(new_ir.rvalue) - for succ in node.dominator_successors: - generate_ssa_irs(succ, - dict(local_variables_instances), - all_local_variables_instances, - dict(state_variables_instances), - all_state_variables_instances, - init_local_variables_instances, - visited) + generate_ssa_irs( + succ, + dict(local_variables_instances), + all_local_variables_instances, + dict(state_variables_instances), + all_state_variables_instances, + init_local_variables_instances, + visited, + ) for dominated in node.dominance_frontier: - generate_ssa_irs(dominated, - dict(local_variables_instances), - all_local_variables_instances, - dict(state_variables_instances), - all_state_variables_instances, - init_local_variables_instances, - visited) + generate_ssa_irs( + dominated, + dict(local_variables_instances), + all_local_variables_instances, + dict(state_variables_instances), + all_state_variables_instances, + init_local_variables_instances, + visited, + ) + # endregion ################################################################################### @@ -244,6 +312,7 @@ def generate_ssa_irs(node, local_variables_instances, all_local_variables_instan ################################################################################### ################################################################################### + def last_name(n, var, init_vars): candidates = [] # Todo optimize by creating a variables_ssa_written attribute @@ -262,6 +331,7 @@ def last_name(n, var, init_vars): assert candidates return max(candidates, key=lambda v: v.index) + def is_used_later(initial_node, variable): # TODO: does not handle the case where its read and written in the declaration node # It can be problematic if this happens in a loop/if structure @@ -283,9 +353,15 @@ def is_used_later(initial_node, variable): if any(v.name == variable.name for v in node.local_variables_written): return False if isinstance(variable, StateVariable): - if any(v.name == variable.name and v.contract == variable.contract for v in node.state_variables_read): + if any( + v.name == variable.name and v.contract == variable.contract + for v in node.state_variables_read + ): return True - if any(v.name == variable.name and v.contract == variable.contract for v in node.state_variables_written): + if any( + v.name == variable.name and v.contract == variable.contract + for v in node.state_variables_written + ): return False for son in node.sons: if not son in explored: @@ -301,7 +377,15 @@ def is_used_later(initial_node, variable): ################################################################################### ################################################################################### -def update_lvalue(new_ir, node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances): + +def update_lvalue( + new_ir, + node, + local_variables_instances, + all_local_variables_instances, + state_variables_instances, + all_state_variables_instances, +): if isinstance(new_ir, OperationWithLValue): lvalue = new_ir.lvalue update_through_ref = False @@ -341,7 +425,10 @@ def update_lvalue(new_ir, node, local_variables_instances, all_local_variables_i ################################################################################### ################################################################################### -def initiate_all_local_variables_instances(nodes, local_variables_instances, all_local_variables_instances): + +def initiate_all_local_variables_instances( + nodes, local_variables_instances, all_local_variables_instances +): for node in nodes: if node.variable_declaration: new_var = LocalIRVariable(node.variable_declaration) @@ -351,7 +438,6 @@ def initiate_all_local_variables_instances(nodes, local_variables_instances, all all_local_variables_instances[node.variable_declaration.name] = new_var - # endregion ################################################################################### ################################################################################### @@ -360,10 +446,19 @@ def initiate_all_local_variables_instances(nodes, local_variables_instances, all ################################################################################### -def fix_phi_rvalues_and_storage_ref(node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances, init_local_variables_instances): +def fix_phi_rvalues_and_storage_ref( + node, + local_variables_instances, + all_local_variables_instances, + state_variables_instances, + all_state_variables_instances, + init_local_variables_instances, +): for ir in node.irs_ssa: if isinstance(ir, (Phi)) and not ir.rvalues: - variables = [last_name(dst, ir.lvalue, init_local_variables_instances) for dst in ir.nodes] + variables = [ + last_name(dst, ir.lvalue, init_local_variables_instances) for dst in ir.nodes + ] ir.rvalues = variables if isinstance(ir, (Phi, PhiCallback)): if isinstance(ir.lvalue, LocalIRVariable): @@ -382,28 +477,49 @@ def fix_phi_rvalues_and_storage_ref(node, local_variables_instances, all_local_v phi_ir = Phi(refers_to, {node}) phi_ir.rvalues = [origin] node.add_ssa_ir(phi_ir) - update_lvalue(phi_ir, node, local_variables_instances, all_local_variables_instances, state_variables_instances, all_state_variables_instances) + update_lvalue( + phi_ir, + node, + local_variables_instances, + all_local_variables_instances, + state_variables_instances, + all_state_variables_instances, + ) for succ in node.dominator_successors: - fix_phi_rvalues_and_storage_ref(succ, dict(local_variables_instances), all_local_variables_instances, dict(state_variables_instances), all_state_variables_instances, init_local_variables_instances) - + fix_phi_rvalues_and_storage_ref( + succ, + dict(local_variables_instances), + all_local_variables_instances, + dict(state_variables_instances), + all_state_variables_instances, + init_local_variables_instances, + ) def add_phi_origins(node, local_variables_definition, state_variables_definition): # Add new key to local_variables_definition - # The key is the variable_name + # The key is the variable_name # The value is (variable_instance, the node where its written) # We keep the instance as we want to avoid to add __hash__ on v.name in Variable # That might work for this used, but could create collision for other uses - local_variables_definition = dict(local_variables_definition, - **{v.name: (v, node) for v in node.local_variables_written}) - state_variables_definition = dict(state_variables_definition, - **{v.canonical_name: (v, node) for v in node.state_variables_written}) + local_variables_definition = dict( + local_variables_definition, **{v.name: (v, node) for v in node.local_variables_written} + ) + state_variables_definition = dict( + state_variables_definition, + **{v.canonical_name: (v, node) for v in node.state_variables_written}, + ) # For unini variable declaration - if node.variable_declaration and\ - not node.variable_declaration.name in local_variables_definition: - local_variables_definition[node.variable_declaration.name] = (node.variable_declaration, node) + if ( + node.variable_declaration + and not node.variable_declaration.name in local_variables_definition + ): + local_variables_definition[node.variable_declaration.name] = ( + node.variable_declaration, + node, + ) # filter length of successors because we have node with one successor # while most of the ssa textbook would represent following nodes as one @@ -427,7 +543,16 @@ def add_phi_origins(node, local_variables_definition, state_variables_definition ################################################################################### ################################################################################### -def get(variable, local_variables_instances, state_variables_instances, temporary_variables_instances, reference_variables_instances, tuple_variables_instances, all_local_variables_instances): + +def get( + variable, + local_variables_instances, + state_variables_instances, + temporary_variables_instances, + reference_variables_instances, + tuple_variables_instances, + all_local_variables_instances, +): # variable can be None # for example, on LowLevelCall, ir.lvalue can be none if variable is None: @@ -445,13 +570,15 @@ def get(variable, local_variables_instances, state_variables_instances, temporar if not variable.index in reference_variables_instances: new_variable = ReferenceVariableSSA(variable) if variable.points_to: - new_variable.points_to = get(variable.points_to, - local_variables_instances, - state_variables_instances, - temporary_variables_instances, - reference_variables_instances, - tuple_variables_instances, - all_local_variables_instances) + new_variable.points_to = get( + variable.points_to, + local_variables_instances, + state_variables_instances, + temporary_variables_instances, + reference_variables_instances, + tuple_variables_instances, + all_local_variables_instances, + ) new_variable.set_type(variable.type) reference_variables_instances[variable.index] = new_variable return reference_variables_instances[variable.index] @@ -467,21 +594,19 @@ def get(variable, local_variables_instances, state_variables_instances, temporar new_variable.set_type(variable.type) tuple_variables_instances[variable.index] = new_variable return tuple_variables_instances[variable.index] - assert isinstance(variable, (Constant, - SolidityVariable, - Contract, - Enum, - SolidityFunction, - Structure, - Function, - Type)) # type for abi.decode(.., t) + assert isinstance( + variable, + (Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type), + ) # type for abi.decode(.., t) return variable + def get_variable(ir, f, *instances): variable = f(ir) variable = get(variable, *instances) return variable + def _get_traversal(values, *instances): ret = [] for v in values: @@ -492,9 +617,11 @@ def _get_traversal(values, *instances): ret.append(v) return ret + def get_arguments(ir, *instances): return _get_traversal(ir.arguments, *instances) + def get_rec_values(ir, f, *instances): # Use by InitArray and NewArray # Potential recursive array(s) @@ -502,8 +629,9 @@ def get_rec_values(ir, f, *instances): return _get_traversal(ori_init_values, *instances) + def copy_ir(ir, *instances): - ''' + """ Args: ir (Operation) local_variables_instances(dict(str -> LocalVariable)) @@ -512,7 +640,7 @@ def copy_ir(ir, *instances): reference_variables_instances(dict(int -> Variable)) Note: temporary and reference can be indexed by int, as they dont need phi functions - ''' + """ if isinstance(ir, Assignment): lvalue = get_variable(ir, lambda x: x.lvalue, *instances) rvalue = get_variable(ir, lambda x: x.rvalue, *instances) @@ -542,7 +670,7 @@ def copy_ir(ir, *instances): elif isinstance(ir, EventCall): name = ir.name return EventCall(name) - elif isinstance(ir, HighLevelCall): # include LibraryCall + elif isinstance(ir, HighLevelCall): # include LibraryCall destination = get_variable(ir, lambda x: x.destination, *instances) function_name = ir.function_name nbr_arguments = ir.nbr_arguments @@ -673,7 +801,7 @@ def copy_ir(ir, *instances): value = get_variable(ir, lambda x: x.value, *instances) return Length(value, lvalue) + raise SlithIRError("Impossible ir copy on {} ({})".format(ir, type(ir))) - raise SlithIRError('Impossible ir copy on {} ({})'.format(ir, type(ir))) # endregion diff --git a/slither/slithir/utils/utils.py b/slither/slithir/utils/utils.py index 74ef1b2cf..31a637d19 100644 --- a/slither/slithir/utils/utils.py +++ b/slither/slithir/utils/utils.py @@ -8,9 +8,22 @@ from slither.slithir.variables.constant import Constant from slither.slithir.variables.reference import ReferenceVariable from slither.slithir.variables.tuple import TupleVariable + def is_valid_rvalue(v): - return isinstance(v, (StateVariable, LocalVariable, TemporaryVariable, Constant, SolidityVariable, ReferenceVariable)) + return isinstance( + v, + ( + StateVariable, + LocalVariable, + TemporaryVariable, + Constant, + SolidityVariable, + ReferenceVariable, + ), + ) -def is_valid_lvalue(v): - return isinstance(v, (StateVariable, LocalVariable, TemporaryVariable, ReferenceVariable, TupleVariable)) +def is_valid_lvalue(v): + return isinstance( + v, (StateVariable, LocalVariable, TemporaryVariable, ReferenceVariable, TupleVariable) + ) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index d1e1a5524..5e47b6677 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -9,7 +9,6 @@ from ..exceptions import SlithIRError @total_ordering class Constant(SlithIRVariable): - def __init__(self, val, type=None, subdenomination=None): super(Constant, self).__init__() assert isinstance(val, str) @@ -23,12 +22,12 @@ class Constant(SlithIRVariable): if type: assert isinstance(type, ElementaryType) self._type = type - if type.type in Int + Uint + ['address']: - if val.startswith('0x') or val.startswith('0X'): + if type.type in Int + Uint + ["address"]: + if val.startswith("0x") or val.startswith("0X"): self._val = int(val, 16) else: - if 'e' in val or 'E' in val: - base, expo = val.split('e') if 'e' in val else val.split('E') + if "e" in val or "E" in val: + base, expo = val.split("e") if "e" in val else val.split("E") base, expo = Decimal(base), int(expo) # The resulting number must be < 2**256-1, otherwise solc # Would not be able to compile it @@ -36,42 +35,44 @@ class Constant(SlithIRVariable): # See https://github.com/ethereum/solidity/blob/9e61f92bd4d19b430cb8cb26f1c7cf79f1dff380/libsolidity/ast/Types.cpp#L1281-L1290 if expo > 77: if base != Decimal(0): - raise SlithIRError(f"{base}e{expo} is too large to fit in any Solidity integer size") + raise SlithIRError( + f"{base}e{expo} is too large to fit in any Solidity integer size" + ) else: self._val = 0 else: self._val = int(Decimal(base) * Decimal(10 ** expo)) else: self._val = int(Decimal(val)) - elif type.type == 'bool': - self._val = (val == 'true') | (val == 'True') + elif type.type == "bool": + self._val = (val == "true") | (val == "True") else: self._val = val else: if val.isdigit(): - self._type = ElementaryType('uint256') + self._type = ElementaryType("uint256") self._val = int(Decimal(val)) else: - self._type = ElementaryType('string') + self._type = ElementaryType("string") self._val = val @property def value(self): - ''' + """ Return the value. If the expression was an hexadecimal delcared as hex'...' return a str Returns: (str | int | bool) - ''' + """ return self._val @property def original_value(self): - ''' + """ Return the string representation of the value :return: str - ''' + """ return self._original_value def __str__(self): diff --git a/slither/slithir/variables/local_variable.py b/slither/slithir/variables/local_variable.py index 43818b052..5ee9e6660 100644 --- a/slither/slithir/variables/local_variable.py +++ b/slither/slithir/variables/local_variable.py @@ -1,11 +1,10 @@ - from .variable import SlithIRVariable from .temporary import TemporaryVariable from slither.core.variables.local_variable import LocalVariable from slither.core.children.child_node import ChildNode -class LocalIRVariable(LocalVariable, SlithIRVariable): +class LocalIRVariable(LocalVariable, SlithIRVariable): def __init__(self, local_variable): assert isinstance(local_variable, LocalVariable) @@ -69,7 +68,5 @@ class LocalIRVariable(LocalVariable, SlithIRVariable): @property def ssa_name(self): if self.is_storage: - return '{}_{} (-> {})'.format(self._name, - self.index, - [v.name for v in self.refers_to]) - return '{}_{}'.format(self._name, self.index) + return "{}_{} (-> {})".format(self._name, self.index, [v.name for v in self.refers_to]) + return "{}_{}".format(self._name, self.index) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index f4f0ec286..457c9e660 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -1,4 +1,3 @@ - from .variable import SlithIRVariable from slither.core.children.child_node import ChildNode from slither.core.variables.variable import Variable @@ -47,14 +46,16 @@ class ReferenceVariable(ChildNode, Variable): # Can only be a rvalue of # Member or Index operator from slither.slithir.utils.utils import is_valid_lvalue - assert is_valid_lvalue(points_to) \ - or isinstance(points_to, (SolidityVariable, Contract, Enum)) + + assert is_valid_lvalue(points_to) or isinstance( + points_to, (SolidityVariable, Contract, Enum) + ) self._points_to = points_to @property def name(self): - return 'REF_{}'.format(self.index) + return "REF_{}".format(self.index) # overide of core.variables.variables # reference can have Function has a type diff --git a/slither/slithir/variables/reference_ssa.py b/slither/slithir/variables/reference_ssa.py index b8c10993c..4f67ad327 100644 --- a/slither/slithir/variables/reference_ssa.py +++ b/slither/slithir/variables/reference_ssa.py @@ -1,13 +1,13 @@ -''' +""" This class is used for the SSA version of slithIR It is similar to the non-SSA version of slithIR as the ReferenceVariable are in SSA form in both version -''' +""" from .reference import ReferenceVariable from .variable import SlithIRVariable -class ReferenceVariableSSA(ReferenceVariable): +class ReferenceVariableSSA(ReferenceVariable): def __init__(self, reference): super(ReferenceVariableSSA, self).__init__(reference.node, reference.index) diff --git a/slither/slithir/variables/state_variable.py b/slither/slithir/variables/state_variable.py index ecefe98c7..bbabce205 100644 --- a/slither/slithir/variables/state_variable.py +++ b/slither/slithir/variables/state_variable.py @@ -1,10 +1,9 @@ - from .variable import SlithIRVariable from slither.core.variables.state_variable import StateVariable from slither.core.children.child_node import ChildNode -class StateIRVariable(StateVariable, SlithIRVariable): +class StateIRVariable(StateVariable, SlithIRVariable): def __init__(self, state_variable): assert isinstance(state_variable, StateVariable) @@ -43,4 +42,4 @@ class StateIRVariable(StateVariable, SlithIRVariable): @property def ssa_name(self): - return '{}_{}'.format(self._name, self.index) + return "{}_{}".format(self._name, self.index) diff --git a/slither/slithir/variables/temporary.py b/slither/slithir/variables/temporary.py index e5b3ad297..6940a303e 100644 --- a/slither/slithir/variables/temporary.py +++ b/slither/slithir/variables/temporary.py @@ -1,8 +1,8 @@ - from .variable import SlithIRVariable from slither.core.variables.variable import Variable from slither.core.children.child_node import ChildNode + class TemporaryVariable(ChildNode, Variable): COUNTER = 0 @@ -16,7 +16,6 @@ class TemporaryVariable(ChildNode, Variable): self._index = index self._node = node - @property def index(self): return self._index @@ -27,8 +26,7 @@ class TemporaryVariable(ChildNode, Variable): @property def name(self): - return 'TMP_{}'.format(self.index) + return "TMP_{}".format(self.index) def __str__(self): return self.name - diff --git a/slither/slithir/variables/temporary_ssa.py b/slither/slithir/variables/temporary_ssa.py index 0366d6547..29853d080 100644 --- a/slither/slithir/variables/temporary_ssa.py +++ b/slither/slithir/variables/temporary_ssa.py @@ -1,13 +1,12 @@ -''' +""" This class is used for the SSA version of slithIR It is similar to the non-SSA version of slithIR as the TemporaryVariable are in SSA form in both version -''' +""" from .temporary import TemporaryVariable class TemporaryVariableSSA(TemporaryVariable): - def __init__(self, temporary): super(TemporaryVariableSSA, self).__init__(temporary.node, temporary.index) @@ -16,4 +15,3 @@ class TemporaryVariableSSA(TemporaryVariable): @property def non_ssa_version(self): return self._non_ssa_version - diff --git a/slither/slithir/variables/tuple.py b/slither/slithir/variables/tuple.py index bfff48586..57ae3b6ad 100644 --- a/slither/slithir/variables/tuple.py +++ b/slither/slithir/variables/tuple.py @@ -3,6 +3,8 @@ from slither.core.variables.variable import Variable from slither.core.children.child_node import ChildNode from slither.core.solidity_types.type import Type + + class TupleVariable(ChildNode, SlithIRVariable): COUNTER = 0 @@ -27,7 +29,7 @@ class TupleVariable(ChildNode, SlithIRVariable): @property def name(self): - return 'TUPLE_{}'.format(self.index) + return "TUPLE_{}".format(self.index) def __str__(self): return self.name diff --git a/slither/slithir/variables/tuple_ssa.py b/slither/slithir/variables/tuple_ssa.py index 04c050260..3db2ad2f4 100644 --- a/slither/slithir/variables/tuple_ssa.py +++ b/slither/slithir/variables/tuple_ssa.py @@ -1,20 +1,18 @@ -''' +""" This class is used for the SSA version of slithIR It is similar to the non-SSA version of slithIR as the TupleVariable are in SSA form in both version -''' +""" from .tuple import TupleVariable from .variable import SlithIRVariable -class TupleVariableSSA(TupleVariable): +class TupleVariableSSA(TupleVariable): def __init__(self, t): super(TupleVariableSSA, self).__init__(t.node, t.index) self._non_ssa_version = t - @property def non_ssa_version(self): return self._non_ssa_version - diff --git a/slither/slithir/variables/variable.py b/slither/slithir/variables/variable.py index dfdbb7b59..70be6d246 100644 --- a/slither/slithir/variables/variable.py +++ b/slither/slithir/variables/variable.py @@ -1,7 +1,7 @@ from slither.core.variables.variable import Variable -class SlithIRVariable(Variable): +class SlithIRVariable(Variable): def __init__(self): super(SlithIRVariable, self).__init__() self._index = 0 diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index c89f4ff6e..ad170f91f 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -278,7 +278,12 @@ class ContractSolc: for father in self._contract.inheritance_reverse: self._contract.variables_as_dict.update(father.variables_as_dict) self._contract.add_variables_ordered( - [var for var in father.state_variables_ordered if var not in self._contract.state_variables_ordered]) + [ + var + for var in father.state_variables_ordered + if var not in self._contract.state_variables_ordered + ] + ) for varNotParsed in self._variablesNotParsed: var = StateVariable() diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index d7845be0b..905095888 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -321,7 +321,12 @@ class FunctionSolc: def _new_yul_block(self, src: Union[str, Dict]) -> YulBlock: node = self._function.new_node(NodeType.ASSEMBLY, src) - yul_object = YulBlock(self._function.contract, node, [self._function.name, f"asm_{len(self._node_to_yulobject)}"], parent_func=self._function) + yul_object = YulBlock( + self._function.contract, + node, + [self._function.name, f"asm_{len(self._node_to_yulobject)}"], + parent_func=self._function, + ) self._node_to_yulobject[node] = yul_object return yul_object @@ -821,22 +826,22 @@ class FunctionSolc: node = self._parse_block(statement, node) elif name == "InlineAssembly": # Added with solc 0.6 - the yul code is an AST - if 'AST' in statement: + if "AST" in statement: self._function.contains_assembly = True - yul_object = self._new_yul_block(statement['src']) + yul_object = self._new_yul_block(statement["src"]) entrypoint = yul_object.entrypoint - exitpoint = yul_object.convert(statement['AST']) + exitpoint = yul_object.convert(statement["AST"]) # technically, entrypoint and exitpoint are YulNodes and we should be returning a NodeSolc here # but they both expose an underlying_node so oh well link_underlying_nodes(node, entrypoint) node = exitpoint else: - asm_node = self._new_node(NodeType.ASSEMBLY, statement['src']) + asm_node = self._new_node(NodeType.ASSEMBLY, statement["src"]) self._function._contains_assembly = True # Added with solc 0.4.12 - if 'operations' in statement: - asm_node.underlying_node.add_inline_asm(statement['operations']) + if "operations" in statement: + asm_node.underlying_node.add_inline_asm(statement["operations"]) link_underlying_nodes(node, asm_node) node = asm_node elif name == "DoWhileStatement": diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 22ba493f6..584e4dbd4 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -163,7 +163,8 @@ class SlitherSolc: assert self._is_compact_ast # Do not support top level definition for legacy AST fake_contract_data = { "name": f"SlitherInternalTopLevelContract{self._top_level_contracts_counter}", - "id": -1000 + self._top_level_contracts_counter, # TODO: determine if collission possible + "id": -1000 + + self._top_level_contracts_counter, # TODO: determine if collission possible "linearizedBaseContracts": [], "fullyImplemented": True, "contractKind": "SLitherInternal", @@ -243,8 +244,8 @@ class SlitherSolc: # the key is the contractid for contract in self._underlying_contract_to_parser.keys(): if ( - contract.name.startswith("SlitherInternalTopLevelContract") - and not contract.is_top_level + contract.name.startswith("SlitherInternalTopLevelContract") + and not contract.is_top_level ): raise SlitherException( """Your codebase has a contract named 'SlitherInternalTopLevelContract'. @@ -364,7 +365,7 @@ Please rename it, this name is reserved for Slither's internals""" return def _analyze_first_part( - self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] ): for lib in libraries: self._parse_struct_var_modifiers_functions(lib) @@ -390,7 +391,7 @@ Please rename it, this name is reserved for Slither's internals""" return def _analyze_second_part( - self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] ): for lib in libraries: self._analyze_struct_events(lib) @@ -416,7 +417,7 @@ Please rename it, this name is reserved for Slither's internals""" return def _analyze_third_part( - self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] ): for lib in libraries: self._analyze_variables_modifiers_functions(lib) diff --git a/slither/solc_parsing/yul/evm_functions.py b/slither/solc_parsing/yul/evm_functions.py index aa237428b..56f1e7f99 100644 --- a/slither/solc_parsing/yul/evm_functions.py +++ b/slither/solc_parsing/yul/evm_functions.py @@ -155,69 +155,73 @@ yul_funcs = [ "loadimmutable", ] -builtins = [x.lower() for x in evm_opcodes if not ( - x.startswith("PUSH") or - x.startswith("SWAP") or - x.startswith("DUP") or - x == "JUMP" or - x == "JUMPI" or - x == "JUMPDEST" -)] + yul_funcs +builtins = [ + x.lower() + for x in evm_opcodes + if not ( + x.startswith("PUSH") + or x.startswith("SWAP") + or x.startswith("DUP") + or x == "JUMP" + or x == "JUMPI" + or x == "JUMPDEST" + ) +] + yul_funcs function_args = { - 'byte': [2, 1], - 'addmod': [3, 1], - 'mulmod': [3, 1], - 'signextend': [2, 1], - 'keccak256': [2, 1], - 'pc': [0, 1], - 'pop': [1, 0], - 'mload': [1, 1], - 'mstore': [2, 0], - 'mstore8': [2, 0], - 'sload': [1, 1], - 'sstore': [2, 0], - 'msize': [1, 1], - 'gas': [0, 1], - 'address': [0, 1], - 'balance': [1, 1], - 'selfbalance': [0, 1], - 'caller': [0, 1], - 'callvalue': [0, 1], - 'calldataload': [1, 1], - 'calldatasize': [0, 1], - 'calldatacopy': [3, 0], - 'codesize': [0, 1], - 'codecopy': [3, 0], - 'extcodesize': [1, 1], - 'extcodecopy': [4, 0], - 'returndatasize': [0, 1], - 'returndatacopy': [3, 0], - 'extcodehash': [1, 1], - 'create': [3, 1], - 'create2': [4, 1], - 'call': [7, 1], - 'callcode': [7, 1], - 'delegatecall': [6, 1], - 'staticcall': [6, 1], - 'return': [2, 0], - 'revert': [2, 0], - 'selfdestruct': [1, 0], - 'invalid': [0, 0], - 'log0': [2, 0], - 'log1': [3, 0], - 'log2': [4, 0], - 'log3': [5, 0], - 'log4': [6, 0], - 'chainid': [0, 1], - 'origin': [0, 1], - 'gasprice': [0, 1], - 'blockhash': [1, 1], - 'coinbase': [0, 1], - 'timestamp': [0, 1], - 'number': [0, 1], - 'difficulty': [0, 1], - 'gaslimit': [0, 1], + "byte": [2, 1], + "addmod": [3, 1], + "mulmod": [3, 1], + "signextend": [2, 1], + "keccak256": [2, 1], + "pc": [0, 1], + "pop": [1, 0], + "mload": [1, 1], + "mstore": [2, 0], + "mstore8": [2, 0], + "sload": [1, 1], + "sstore": [2, 0], + "msize": [1, 1], + "gas": [0, 1], + "address": [0, 1], + "balance": [1, 1], + "selfbalance": [0, 1], + "caller": [0, 1], + "callvalue": [0, 1], + "calldataload": [1, 1], + "calldatasize": [0, 1], + "calldatacopy": [3, 0], + "codesize": [0, 1], + "codecopy": [3, 0], + "extcodesize": [1, 1], + "extcodecopy": [4, 0], + "returndatasize": [0, 1], + "returndatacopy": [3, 0], + "extcodehash": [1, 1], + "create": [3, 1], + "create2": [4, 1], + "call": [7, 1], + "callcode": [7, 1], + "delegatecall": [6, 1], + "staticcall": [6, 1], + "return": [2, 0], + "revert": [2, 0], + "selfdestruct": [1, 0], + "invalid": [0, 0], + "log0": [2, 0], + "log1": [3, 0], + "log2": [4, 0], + "log3": [5, 0], + "log4": [6, 0], + "chainid": [0, 1], + "origin": [0, 1], + "gasprice": [0, 1], + "blockhash": [1, 1], + "coinbase": [0, 1], + "timestamp": [0, 1], + "number": [0, 1], + "difficulty": [0, 1], + "gaslimit": [0, 1], } @@ -229,33 +233,33 @@ def format_function_descriptor(name): for k, v in function_args.items(): - SOLIDITY_FUNCTIONS[format_function_descriptor(k)] = ['uint256'] * v[1] + SOLIDITY_FUNCTIONS[format_function_descriptor(k)] = ["uint256"] * v[1] unary_ops = { - 'not': UnaryOperationType.TILD, - 'iszero': UnaryOperationType.BANG, + "not": UnaryOperationType.TILD, + "iszero": UnaryOperationType.BANG, } binary_ops = { - 'add': BinaryOperationType.ADDITION, - 'sub': BinaryOperationType.SUBTRACTION, - 'mul': BinaryOperationType.MULTIPLICATION, - 'div': BinaryOperationType.DIVISION, - 'sdiv': BinaryOperationType.DIVISION_SIGNED, - 'mod': BinaryOperationType.MODULO, - 'smod': BinaryOperationType.MODULO_SIGNED, - 'exp': BinaryOperationType.POWER, - 'lt': BinaryOperationType.LESS, - 'gt': BinaryOperationType.GREATER, - 'slt': BinaryOperationType.LESS_SIGNED, - 'sgt': BinaryOperationType.GREATER_SIGNED, - 'eq': BinaryOperationType.EQUAL, - 'and': BinaryOperationType.AND, - 'or': BinaryOperationType.OR, - 'xor': BinaryOperationType.CARET, - 'shl': BinaryOperationType.LEFT_SHIFT, - 'shr': BinaryOperationType.RIGHT_SHIFT, - 'sar': BinaryOperationType.RIGHT_SHIFT_ARITHMETIC, + "add": BinaryOperationType.ADDITION, + "sub": BinaryOperationType.SUBTRACTION, + "mul": BinaryOperationType.MULTIPLICATION, + "div": BinaryOperationType.DIVISION, + "sdiv": BinaryOperationType.DIVISION_SIGNED, + "mod": BinaryOperationType.MODULO, + "smod": BinaryOperationType.MODULO_SIGNED, + "exp": BinaryOperationType.POWER, + "lt": BinaryOperationType.LESS, + "gt": BinaryOperationType.GREATER, + "slt": BinaryOperationType.LESS_SIGNED, + "sgt": BinaryOperationType.GREATER_SIGNED, + "eq": BinaryOperationType.EQUAL, + "and": BinaryOperationType.AND, + "or": BinaryOperationType.OR, + "xor": BinaryOperationType.CARET, + "shl": BinaryOperationType.LEFT_SHIFT, + "shr": BinaryOperationType.RIGHT_SHIFT, + "sar": BinaryOperationType.RIGHT_SHIFT_ARITHMETIC, } diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 993d389cb..d663e48ed 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -8,7 +8,11 @@ from slither.core.expressions import ( Literal, AssignmentOperation, AssignmentOperationType, - Identifier, CallExpression, TupleExpression, BinaryOperation, UnaryOperation, + Identifier, + CallExpression, + TupleExpression, + BinaryOperation, + UnaryOperation, ) from slither.core.expressions.expression import Expression from slither.core.slither_core import SlitherCore @@ -22,7 +26,7 @@ from slither.visitors.expression.write_var import WriteVar class YulNode: - def __init__(self, node: Node, scope: 'YulScope'): + def __init__(self, node: Node, scope: "YulScope"): self._node = node self._scope = scope self._unparsed_expression: Optional[Dict] = None @@ -114,7 +118,9 @@ class YulScope(metaclass=abc.ABCMeta): self._yul_local_variables.append(var) def get_yul_local_variable_from_name(self, variable_name): - return next((v for v in self._yul_local_variables if v.underlying.name == variable_name), None) + return next( + (v for v in self._yul_local_variables if v.underlying.name == variable_name), None + ) def add_yul_local_function(self, func): self._yul_local_functions.append(func) @@ -127,7 +133,7 @@ class YulLocalVariable: __slots__ = ["_variable", "_root"] def __init__(self, var: LocalVariable, root: YulScope, ast: Dict): - assert (ast['nodeType'] == 'YulTypedName') + assert ast["nodeType"] == "YulTypedName" self._variable = var self._root = root @@ -136,9 +142,9 @@ class YulLocalVariable: var.set_function(root.function) var.set_offset(ast["src"], root.slither) - var.name = ast['name'] - var.set_type(ElementaryType('uint256')) - var.set_location('memory') + var.name = ast["name"] + var.set_type(ElementaryType("uint256")) + var.set_location("memory") @property def underlying(self) -> LocalVariable: @@ -149,9 +155,9 @@ class YulFunction(YulScope): __slots__ = ["_function", "_root", "_ast", "_nodes", "_entrypoint"] def __init__(self, func: Function, root: YulScope, ast: Dict): - super().__init__(root.contract, root.id + [ast['name']], parent_func=root.parent_func) + super().__init__(root.contract, root.id + [ast["name"]], parent_func=root.parent_func) - assert (ast['nodeType'] == 'YulFunctionDefinition') + assert ast["nodeType"] == "YulFunctionDefinition" self._function: Function = func self._root: YulScope = root @@ -159,8 +165,8 @@ class YulFunction(YulScope): # start initializing the underlying function - func.name = ast['name'] - func.set_visibility('private') + func.name = ast["name"] + func.set_visibility("private") func.set_offset(ast["src"], root.slither) func.set_contract(root.contract) func.set_contract_declarer(root.contract) @@ -168,7 +174,7 @@ class YulFunction(YulScope): func.is_implemented = True self._nodes: List[YulNode] = [] - self._entrypoint = self.new_node(NodeType.ASSEMBLY, ast['src']) + self._entrypoint = self.new_node(NodeType.ASSEMBLY, ast["src"]) func.entry_point = self._entrypoint.underlying_node self.add_yul_local_function(self) @@ -186,18 +192,20 @@ class YulFunction(YulScope): return self._function def convert_body(self): - node = self.new_node(NodeType.ENTRYPOINT, self._ast['src']) + node = self.new_node(NodeType.ENTRYPOINT, self._ast["src"]) link_underlying_nodes(self._entrypoint, node) - for param in self._ast.get('parameters', []): + for param in self._ast.get("parameters", []): node = convert_yul(self, node, param) - self._function.add_parameters(self.get_yul_local_variable_from_name(param['name']).underlying) + self._function.add_parameters( + self.get_yul_local_variable_from_name(param["name"]).underlying + ) - for ret in self._ast.get('returnVariables', []): + for ret in self._ast.get("returnVariables", []): node = convert_yul(self, node, ret) - self._function.add_return(self.get_yul_local_variable_from_name(ret['name']).underlying) + self._function.add_return(self.get_yul_local_variable_from_name(ret["name"]).underlying) - convert_yul(self, node, self._ast['body']) + convert_yul(self, node, self._ast["body"]) def parse_body(self): for node in self._nodes: @@ -220,6 +228,7 @@ class YulBlock(YulScope): For example an inline assembly block """ + __slots__ = ["_entrypoint", "_parent_func", "_nodes"] def __init__(self, contract: Contract, entrypoint: Node, id: List[str], **kwargs): @@ -299,7 +308,7 @@ def convert_yul_function_definition(root: YulScope, parent: YulNode, ast: Dict) def convert_yul_variable_declaration(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - for variable_ast in ast['variables']: + for variable_ast in ast["variables"]: parent = convert_yul(root, parent, variable_ast) node = root.new_node(NodeType.EXPRESSION, ast["src"]) @@ -317,8 +326,8 @@ def convert_yul_assignment(root: YulScope, parent: YulNode, ast: Dict) -> YulNod def convert_yul_expression_statement(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - src = ast['src'] - expression_ast = ast['expression'] + src = ast["src"] + expression_ast = ast["expression"] expression = root.new_node(NodeType.EXPRESSION, src) expression.add_unparsed_expression(expression_ast) @@ -330,10 +339,10 @@ def convert_yul_expression_statement(root: YulScope, parent: YulNode, ast: Dict) def convert_yul_if(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: # we're cheating and pretending that yul supports if/else so we can convert switch cleaner - src = ast['src'] - condition_ast = ast['condition'] - true_body_ast = ast['body'] - false_body_ast = ast['false_body'] if 'false_body' in ast else None + src = ast["src"] + condition_ast = ast["condition"] + true_body_ast = ast["body"] + false_body_ast = ast["false_body"] if "false_body" in ast else None condition = root.new_node(NodeType.IF, src) end = root.new_node(NodeType.ENDIF, src) @@ -359,28 +368,28 @@ def convert_yul_switch(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: This is unfortunate. We don't really want a switch in our IR so we're going to translate it into a series of if/else statements. """ - cases_ast = ast['cases'] - expression_ast = ast['expression'] + cases_ast = ast["cases"] + expression_ast = ast["expression"] # this variable stores the result of the expression so we don't accidentally compute it more than once - switch_expr_var = 'switch_expr_{}'.format(ast['src'].replace(':', '_')) + switch_expr_var = "switch_expr_{}".format(ast["src"].replace(":", "_")) rewritten_switch = { - 'nodeType': 'YulBlock', - 'src': ast['src'], - 'statements': [ + "nodeType": "YulBlock", + "src": ast["src"], + "statements": [ { - 'nodeType': 'YulVariableDeclaration', - 'src': expression_ast['src'], - 'variables': [ + "nodeType": "YulVariableDeclaration", + "src": expression_ast["src"], + "variables": [ { - 'nodeType': 'YulTypedName', - 'src': expression_ast['src'], - 'name': switch_expr_var, - 'type': '', + "nodeType": "YulTypedName", + "src": expression_ast["src"], + "name": switch_expr_var, + "type": "", }, ], - 'value': expression_ast, + "value": expression_ast, }, ], } @@ -390,68 +399,64 @@ def convert_yul_switch(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: default_ast = None for case_ast in cases_ast: - body_ast = case_ast['body'] - value_ast = case_ast['value'] + body_ast = case_ast["body"] + value_ast = case_ast["value"] - if value_ast == 'default': + if value_ast == "default": default_ast = case_ast continue current_if = { - 'nodeType': 'YulIf', - 'src': case_ast['src'], - 'condition': { - 'nodeType': 'YulFunctionCall', - 'src': case_ast['src'], - 'functionName': { - 'nodeType': 'YulIdentifier', - 'src': case_ast['src'], - 'name': 'eq', + "nodeType": "YulIf", + "src": case_ast["src"], + "condition": { + "nodeType": "YulFunctionCall", + "src": case_ast["src"], + "functionName": { + "nodeType": "YulIdentifier", + "src": case_ast["src"], + "name": "eq", }, - 'arguments': [ - { - 'nodeType': 'YulIdentifier', - 'src': case_ast['src'], - 'name': switch_expr_var, - }, + "arguments": [ + {"nodeType": "YulIdentifier", "src": case_ast["src"], "name": switch_expr_var,}, value_ast, - ] + ], }, - 'body': body_ast, + "body": body_ast, } if last_if: - last_if['false_body'] = current_if + last_if["false_body"] = current_if else: - rewritten_switch['statements'].append(current_if) + rewritten_switch["statements"].append(current_if) last_if = current_if if default_ast: - body_ast = default_ast['body'] + body_ast = default_ast["body"] if last_if: - last_if['false_body'] = body_ast + last_if["false_body"] = body_ast else: - rewritten_switch['statements'].append(body_ast) + rewritten_switch["statements"].append(body_ast) return convert_yul(root, parent, rewritten_switch) def convert_yul_for_loop(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - pre_ast = ast['pre'] - condition_ast = ast['condition'] - post_ast = ast['post'] - body_ast = ast['body'] + pre_ast = ast["pre"] + condition_ast = ast["condition"] + post_ast = ast["post"] + body_ast = ast["body"] - start_loop = root.new_node(NodeType.STARTLOOP, ast['src']) - end_loop = root.new_node(NodeType.ENDLOOP, ast['src']) + start_loop = root.new_node(NodeType.STARTLOOP, ast["src"]) + end_loop = root.new_node(NodeType.ENDLOOP, ast["src"]) link_underlying_nodes(parent, start_loop) pre = convert_yul(root, start_loop, pre_ast) - condition = root.new_node(NodeType.IFLOOP, condition_ast['src']) + condition = root.new_node(NodeType.IFLOOP, condition_ast["src"]) condition.add_unparsed_expression(condition_ast) link_underlying_nodes(pre, condition) @@ -467,19 +472,19 @@ def convert_yul_for_loop(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: def convert_yul_break(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - break_ = root.new_node(NodeType.BREAK, ast['src']) + break_ = root.new_node(NodeType.BREAK, ast["src"]) link_underlying_nodes(parent, break_) return break_ def convert_yul_continue(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - continue_ = root.new_node(NodeType.CONTINUE, ast['src']) + continue_ = root.new_node(NodeType.CONTINUE, ast["src"]) link_underlying_nodes(parent, continue_) return continue_ def convert_yul_leave(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - leave = root.new_node(NodeType.RETURN, ast['src']) + leave = root.new_node(NodeType.RETURN, ast["src"]) link_underlying_nodes(parent, leave) return leave @@ -491,7 +496,7 @@ def convert_yul_typed_name(root: YulScope, parent: YulNode, ast: Dict) -> YulNod root.add_yul_local_variable(var) - node = root.new_node(NodeType.VARIABLE, ast['src']) + node = root.new_node(NodeType.VARIABLE, ast["src"]) node.underlying_node.add_variable_declaration(local_var) link_underlying_nodes(parent, node) @@ -499,26 +504,28 @@ def convert_yul_typed_name(root: YulScope, parent: YulNode, ast: Dict) -> YulNod def convert_yul_unsupported(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - raise SlitherException(f"no converter available for {ast['nodeType']} {json.dumps(ast, indent=2)}") + raise SlitherException( + f"no converter available for {ast['nodeType']} {json.dumps(ast, indent=2)}" + ) def convert_yul(root: YulScope, parent: YulNode, ast: Dict) -> YulNode: - return converters.get(ast['nodeType'], convert_yul_unsupported)(root, parent, ast) + return converters.get(ast["nodeType"], convert_yul_unsupported)(root, parent, ast) converters = { - 'YulBlock': convert_yul_block, - 'YulFunctionDefinition': convert_yul_function_definition, - 'YulVariableDeclaration': convert_yul_variable_declaration, - 'YulAssignment': convert_yul_assignment, - 'YulExpressionStatement': convert_yul_expression_statement, - 'YulIf': convert_yul_if, - 'YulSwitch': convert_yul_switch, - 'YulForLoop': convert_yul_for_loop, - 'YulBreak': convert_yul_break, - 'YulContinue': convert_yul_continue, - 'YulLeave': convert_yul_leave, - 'YulTypedName': convert_yul_typed_name, + "YulBlock": convert_yul_block, + "YulFunctionDefinition": convert_yul_function_definition, + "YulVariableDeclaration": convert_yul_variable_declaration, + "YulAssignment": convert_yul_assignment, + "YulExpressionStatement": convert_yul_expression_statement, + "YulIf": convert_yul_if, + "YulSwitch": convert_yul_switch, + "YulForLoop": convert_yul_for_loop, + "YulBreak": convert_yul_break, + "YulContinue": convert_yul_continue, + "YulLeave": convert_yul_leave, + "YulTypedName": convert_yul_typed_name, } # endregion @@ -547,32 +554,38 @@ dispatches to a specialized function based on a lookup dictionary. """ -def _parse_yul_assignment_common(root: YulScope, node: YulNode, ast: Dict, key: str) -> Optional[Expression]: +def _parse_yul_assignment_common( + root: YulScope, node: YulNode, ast: Dict, key: str +) -> Optional[Expression]: lhs = [parse_yul(root, node, arg) for arg in ast[key]] - rhs = parse_yul(root, node, ast['value']) + rhs = parse_yul(root, node, ast["value"]) - return AssignmentOperation(vars_to_val(lhs), rhs, AssignmentOperationType.ASSIGN, vars_to_typestr(lhs)) + return AssignmentOperation( + vars_to_val(lhs), rhs, AssignmentOperationType.ASSIGN, vars_to_typestr(lhs) + ) -def parse_yul_variable_declaration(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: +def parse_yul_variable_declaration( + root: YulScope, node: YulNode, ast: Dict +) -> Optional[Expression]: """ We already created variables in the conversion phase, so just do the assignment """ - if not ast['value']: + if not ast["value"]: return None - return _parse_yul_assignment_common(root, node, ast, 'variables') + return _parse_yul_assignment_common(root, node, ast, "variables") def parse_yul_assignment(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - return _parse_yul_assignment_common(root, node, ast, 'variableNames') + return _parse_yul_assignment_common(root, node, ast, "variableNames") def parse_yul_function_call(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - args = [parse_yul(root, node, arg) for arg in ast['arguments']] - ident = parse_yul(root, node, ast['functionName']) + args = [parse_yul(root, node, arg) for arg in ast["arguments"]] + ident = parse_yul(root, node, ast["functionName"]) if not isinstance(ident, Identifier): raise SlitherException("expected identifier from parsing function name") @@ -580,7 +593,7 @@ def parse_yul_function_call(root: YulScope, node: YulNode, ast: Dict) -> Optiona if isinstance(ident.value, YulBuiltin): name = ident.value.name if name in binary_ops: - if name in ['shl', 'shr', 'sar']: + if name in ["shl", "shr", "sar"]: # lmao ok return BinaryOperation(args[1], args[0], binary_ops[name]) @@ -600,7 +613,7 @@ def parse_yul_function_call(root: YulScope, node: YulNode, ast: Dict) -> Optiona def parse_yul_identifier(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - name = ast['name'] + name = ast["name"] if name in builtins: return Identifier(YulBuiltin(name)) @@ -642,17 +655,17 @@ def parse_yul_identifier(root: YulScope, node: YulNode, ast: Dict) -> Optional[E def parse_yul_literal(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - type_ = ast['type'] - value = ast['value'] + type_ = ast["type"] + value = ast["value"] if not type_: - type_ = 'bool' if value in ['true', 'false'] else 'uint256' + type_ = "bool" if value in ["true", "false"] else "uint256" return Literal(value, ElementaryType(type_)) def parse_yul_typed_name(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - var = root.get_yul_local_variable_from_name(ast['name']) + var = root.get_yul_local_variable_from_name(ast["name"]) i = Identifier(var.underlying) i._type = var.underlying.type @@ -664,19 +677,19 @@ def parse_yul_unsupported(root: YulScope, node: YulNode, ast: Dict) -> Optional[ def parse_yul(root: YulScope, node: YulNode, ast: Dict) -> Optional[Expression]: - op = parsers.get(ast['nodeType'], parse_yul_unsupported)(root, node, ast) + op = parsers.get(ast["nodeType"], parse_yul_unsupported)(root, node, ast) if op: op.set_offset(ast["src"], root.slither) return op parsers = { - 'YulVariableDeclaration': parse_yul_variable_declaration, - 'YulAssignment': parse_yul_assignment, - 'YulFunctionCall': parse_yul_function_call, - 'YulIdentifier': parse_yul_identifier, - 'YulTypedName': parse_yul_typed_name, - 'YulLiteral': parse_yul_literal, + "YulVariableDeclaration": parse_yul_variable_declaration, + "YulAssignment": parse_yul_assignment, + "YulFunctionCall": parse_yul_function_call, + "YulIdentifier": parse_yul_identifier, + "YulTypedName": parse_yul_typed_name, + "YulLiteral": parse_yul_literal, } @@ -684,6 +697,7 @@ parsers = { ################################################################################### ################################################################################### + def vars_to_typestr(rets: List[Expression]) -> str: if len(rets) == 0: return "" diff --git a/slither/tools/flattening/__main__.py b/slither/tools/flattening/__main__.py index aef147d05..0639c481b 100644 --- a/slither/tools/flattening/__main__.py +++ b/slither/tools/flattening/__main__.py @@ -77,7 +77,10 @@ def parse_args(): ) group_patching.add_argument( - "--pragma-solidity", help="Set the solidity pragma with a given version.", action="store", default=None + "--pragma-solidity", + help="Set the solidity pragma with a given version.", + action="store", + default=None, ) # Add default arguments from crytic-compile @@ -100,7 +103,7 @@ def main(): remove_assert=args.remove_assert, private_to_internal=args.convert_private, export_path=args.dir, - pragma_solidity=args.pragma_solidity + pragma_solidity=args.pragma_solidity, ) try: diff --git a/slither/tools/flattening/flattening.py b/slither/tools/flattening/flattening.py index 40199d80e..1ddcf42d5 100644 --- a/slither/tools/flattening/flattening.py +++ b/slither/tools/flattening/flattening.py @@ -43,7 +43,7 @@ class Flattening: remove_assert=False, private_to_internal=False, export_path: Optional[str] = None, - pragma_solidity: Optional[str] = None + pragma_solidity: Optional[str] = None, ): self._source_codes: Dict[Contract, str] = {} self._slither = slither diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index eb24bba4e..066a9fab0 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -1,30 +1,44 @@ import logging from slither.core.declarations import Function, SolidityVariable, SolidityVariableComposed -from slither.core.expressions import (AssignmentOperationType, - UnaryOperationType, BinaryOperationType) +from slither.core.expressions import ( + AssignmentOperationType, + UnaryOperationType, + BinaryOperationType, +) from slither.core.solidity_types import ArrayType, ElementaryType from slither.core.solidity_types.type import Type from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple -from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete, - Index, InitArray, InternalCall, Member, - NewArray, NewContract, - TypeConversion, Unary, Unpack, Return) +from slither.slithir.operations import ( + Assignment, + Binary, + BinaryType, + Delete, + Index, + InitArray, + InternalCall, + Member, + NewArray, + NewContract, + TypeConversion, + Unary, + Unpack, + Return, +) from slither.slithir.tmp_operations.argument import Argument from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract -from slither.slithir.tmp_operations.tmp_new_elementary_type import \ - TmpNewElementaryType -from slither.slithir.variables import (Constant, ReferenceVariable, - TemporaryVariable, TupleVariable) +from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType +from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVariable, TupleVariable from slither.visitors.expression.expression import ExpressionVisitor from slither.slithir.exceptions import SlithIRError logger = logging.getLogger("VISTIOR:ExpressionToSlithIR") -key = 'expressionToSlithIR' +key = "expressionToSlithIR" + def get(expression): val = expression.context[key] @@ -32,6 +46,7 @@ def get(expression): del expression.context[key] return val + def set_val(expression, val): expression.context[key] = val @@ -91,12 +106,13 @@ def convert_assignment(left, right, t, return_type): elif t == AssignmentOperationType.ASSIGN_MODULO: return Binary(left, left, right, BinaryType.MODULO) - raise SlithIRError('Missing type during assignment conversion') + raise SlithIRError("Missing type during assignment conversion") -class ExpressionToSlithIR(ExpressionVisitor): +class ExpressionToSlithIR(ExpressionVisitor): def __init__(self, expression, node): from slither.core.cfg.node import NodeType + self._expression = expression self._node = node self._result = [] @@ -114,12 +130,17 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_assignement_operation(self, expression): left = get(expression.expression_left) right = get(expression.expression_right) - if isinstance(left, list): # tuple expression: - if isinstance(right, list): # unbox assigment + if isinstance(left, list): # tuple expression: + if isinstance(right, list): # unbox assigment assert len(left) == len(right) for idx in range(len(left)): if not left[idx] is None: - operation = convert_assignment(left[idx], right[idx], expression.type, expression.expression_return_type) + operation = convert_assignment( + left[idx], + right[idx], + expression.type, + expression.expression_return_type, + ) operation.set_expression(expression) self._result.append(operation) set_val(expression, None) @@ -129,7 +150,10 @@ class ExpressionToSlithIR(ExpressionVisitor): if not left[idx] is None: index = idx # The following test is probably always true? - if isinstance(left[idx], LocalVariableInitFromTuple) and left[idx].tuple_index is not None: + if ( + isinstance(left[idx], LocalVariableInitFromTuple) + and left[idx].tuple_index is not None + ): index = left[idx].tuple_index operation = Unpack(left[idx], right, index) operation.set_expression(expression) @@ -138,9 +162,11 @@ class ExpressionToSlithIR(ExpressionVisitor): # Tuple with only one element. We need to convert the assignment to a Unpack # Ex: # (uint a,,) = g() - elif (isinstance(left, LocalVariableInitFromTuple) and - left.tuple_index is not None and - isinstance(right, TupleVariable)): + elif ( + isinstance(left, LocalVariableInitFromTuple) + and left.tuple_index is not None + and isinstance(right, TupleVariable) + ): operation = Unpack(left, right, left.tuple_index) operation.set_expression(expression) self._result.append(operation) @@ -154,11 +180,13 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, left) else: - operation = convert_assignment(left, right, expression.type, expression.expression_return_type) + operation = convert_assignment( + left, right, expression.type, expression.expression_return_type + ) operation.set_expression(expression) self._result.append(operation) # Return left to handle - # a = b = 1; + # a = b = 1; set_val(expression, left) def _post_binary_operation(self, expression): @@ -168,13 +196,13 @@ class ExpressionToSlithIR(ExpressionVisitor): if expression.type in _signed_to_unsigned: new_left = TemporaryVariable(self._node) - conv_left = TypeConversion(new_left, left, ElementaryType('int256')) + conv_left = TypeConversion(new_left, left, ElementaryType("int256")) conv_left.set_expression(expression) self._result.append(conv_left) if expression.type != BinaryOperationType.RIGHT_SHIFT_ARITHMETIC: new_right = TemporaryVariable(self._node) - conv_right = TypeConversion(new_right, right, ElementaryType('int256')) + conv_right = TypeConversion(new_right, right, ElementaryType("int256")) conv_right.set_expression(expression) self._result.append(conv_right) else: @@ -185,7 +213,7 @@ class ExpressionToSlithIR(ExpressionVisitor): operation.set_expression(expression) self._result.append(operation) - conv_final = TypeConversion(val, new_final, ElementaryType('uint256')) + conv_final = TypeConversion(val, new_final, ElementaryType("uint256")) conv_final.set_expression(expression) self._result.append(conv_final) else: @@ -206,7 +234,7 @@ class ExpressionToSlithIR(ExpressionVisitor): # internal call # If tuple - if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': + if expression.type_call.startswith("tuple(") and expression.type_call != "tuple()": val = TupleVariable(self._node) else: val = TemporaryVariable(self._node) @@ -216,43 +244,43 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, val) else: # yul things - if called.name == 'caller()': + if called.name == "caller()": val = TemporaryVariable(self._node) - var = Assignment(val, SolidityVariableComposed('msg.sender'), 'uint256') + var = Assignment(val, SolidityVariableComposed("msg.sender"), "uint256") self._result.append(var) set_val(expression, val) - elif called.name == 'origin()': + elif called.name == "origin()": val = TemporaryVariable(self._node) - var = Assignment(val, SolidityVariableComposed('tx.origin'), 'uint256') + var = Assignment(val, SolidityVariableComposed("tx.origin"), "uint256") self._result.append(var) set_val(expression, val) - elif called.name == 'extcodesize(uint256)': + elif called.name == "extcodesize(uint256)": val = ReferenceVariable(self._node) - var = Member(args[0], Constant('codesize'), val) + var = Member(args[0], Constant("codesize"), val) self._result.append(var) set_val(expression, val) - elif called.name == 'selfbalance()': + elif called.name == "selfbalance()": val = TemporaryVariable(self._node) - var = TypeConversion(val, SolidityVariable('this'), ElementaryType('address')) + var = TypeConversion(val, SolidityVariable("this"), ElementaryType("address")) self._result.append(var) val1 = ReferenceVariable(self._node) - var1 = Member(val, Constant('balance'), val1) + var1 = Member(val, Constant("balance"), val1) self._result.append(var1) set_val(expression, val1) - elif called.name == 'address()': + elif called.name == "address()": val = TemporaryVariable(self._node) - var = TypeConversion(val, SolidityVariable('this'), ElementaryType('address')) + var = TypeConversion(val, SolidityVariable("this"), ElementaryType("address")) self._result.append(var) set_val(expression, val) - elif called.name == 'callvalue()': + elif called.name == "callvalue()": val = TemporaryVariable(self._node) - var = Assignment(val, SolidityVariableComposed('msg.value'), 'uint256') + var = Assignment(val, SolidityVariableComposed("msg.value"), "uint256") self._result.append(var) set_val(expression, val) else: # If tuple - if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': + if expression.type_call.startswith("tuple(") and expression.type_call != "tuple()": val = TupleVariable(self._node) else: val = TemporaryVariable(self._node) @@ -274,7 +302,7 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, val) def _post_conditional_expression(self, expression): - raise Exception('Ternary operator are not convertible to SlithIR {}'.format(expression)) + raise Exception("Ternary operator are not convertible to SlithIR {}".format(expression)) def _post_elementary_type_name_expression(self, expression): set_val(expression, expression.type) @@ -415,5 +443,4 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, lvalue) else: - raise SlithIRError('Unary operation to IR not supported {}'.format(expression)) - + raise SlithIRError("Unary operation to IR not supported {}".format(expression)) From 950d7b42db294ab10c81843524360d37ca671481 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 11:34:24 +0200 Subject: [PATCH 70/73] Run black --- slither/__main__.py | 1 - slither/core/cfg/node.py | 6 +++--- slither/solc_parsing/slitherSolc.py | 10 +++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 12d8d7fe0..69cc0cf95 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -733,7 +733,6 @@ def main_impl(all_detector_classes, all_printer_classes): logging.error("Error in %s" % args.filename) logging.error(output_error) - # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. if outputting_json: if "console" in args.json_types: diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 8c2d1e274..1dc3d759b 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -984,11 +984,11 @@ class Node(SourceMapping, ChildFunction): ################################################################################### def __str__(self): - additional_info = '' + additional_info = "" if self.expression: - additional_info += ' ' + str(self.expression) + additional_info += " " + str(self.expression) elif self.variable_declaration: - additional_info += ' ' + str(self.variable_declaration) + additional_info += " " + str(self.variable_declaration) txt = str(self._node_type) + additional_info return txt diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index d5de1b56a..5a3634507 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -242,8 +242,8 @@ class SlitherSolc: # the key is the contractid for contract in self._underlying_contract_to_parser.keys(): if ( - contract.name.startswith("SlitherInternalTopLevelContract") - and not contract.is_top_level + contract.name.startswith("SlitherInternalTopLevelContract") + and not contract.is_top_level ): raise SlitherException( """Your codebase has a contract named 'SlitherInternalTopLevelContract'. @@ -362,7 +362,7 @@ Please rename it, this name is reserved for Slither's internals""" return def _analyze_first_part( - self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] ): for lib in libraries: self._parse_struct_var_modifiers_functions(lib) @@ -388,7 +388,7 @@ Please rename it, this name is reserved for Slither's internals""" return def _analyze_second_part( - self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] ): for lib in libraries: self._analyze_struct_events(lib) @@ -414,7 +414,7 @@ Please rename it, this name is reserved for Slither's internals""" return def _analyze_third_part( - self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] + self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] ): for lib in libraries: self._analyze_variables_modifiers_functions(lib) From f8a812141ea2dd55590abcc2d6826ee9bcce7adf Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 12:18:45 +0200 Subject: [PATCH 71/73] Run black --- scripts/json_diff.py | 6 +++-- slither/slithir/convert.py | 24 +++++++++++-------- slither/slithir/operations/__init__.py | 1 + slither/slithir/operations/assignment.py | 1 - slither/slithir/tmp_operations/argument.py | 1 - .../slithir/tmp_operations/tmp_new_array.py | 1 + slither/slithir/variables/constant.py | 2 +- slither/slithir/variables/local_variable.py | 1 - slither/slithir/variables/state_variable.py | 3 +-- slither/slithir/variables/temporary.py | 1 - slither/slithir/variables/tuple_ssa.py | 1 - .../visitors/slithir/expression_to_slithir.py | 1 - 12 files changed, 22 insertions(+), 21 deletions(-) diff --git a/scripts/json_diff.py b/scripts/json_diff.py index 4c274a2a6..220956fff 100644 --- a/scripts/json_diff.py +++ b/scripts/json_diff.py @@ -22,6 +22,7 @@ for elem in d2: if "description" in elem: del elem["description"] + def removes_lines(d): if isinstance(d, list): for sub in d: @@ -29,11 +30,12 @@ def removes_lines(d): return if not isinstance(d, dict): return - if 'lines' in d: - del d['lines'] + if "lines" in d: + del d["lines"] for key in d.keys(): removes_lines(d[key]) + results = DeepDiff(d1, d2, ignore_order=True, verbose_level=2) removes_lines(results) pprint(results) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index e0b509e98..4f4b9e47d 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -223,7 +223,9 @@ def convert_arguments(arguments): def is_temporary(ins: Operation) -> bool: - return isinstance(ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure)) + return isinstance( + ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure) + ) # endregion @@ -629,9 +631,9 @@ def propagate_types(ir: Operation, node: "Node") -> Optional[Operation]: t = None # Handling of this.function_name usage if ( - left == SolidityVariable("this") - and isinstance(ir.variable_right, Constant) - and str(ir.variable_right) in [x.name for x in ir.function.contract.functions] + left == SolidityVariable("this") + and isinstance(ir.variable_right, Constant) + and str(ir.variable_right) in [x.name for x in ir.function.contract.functions] ): # Assumption that this.function_name can only compile if # And the contract does not have two functions starting with function_name @@ -897,7 +899,6 @@ def can_be_low_level(ir): ] - def convert_to_low_level(ir: HighLevelCall) -> Union[LowLevelCall, Transfer, Send]: """ Convert to a transfer/send/or low level call @@ -970,10 +971,10 @@ def convert_to_solidity_func(ir: HighLevelCall) -> SolidityCall: if isinstance(call.return_type, list) and len(call.return_type) == 1: new_ir.lvalue.set_type(call.return_type[0]) elif ( - isinstance(new_ir.lvalue, TupleVariable) - and call == SolidityFunction("abi.decode()") - and len(new_ir.arguments) == 2 - and isinstance(new_ir.arguments[1], list) + isinstance(new_ir.lvalue, TupleVariable) + and call == SolidityFunction("abi.decode()") + and len(new_ir.arguments) == 2 + and isinstance(new_ir.arguments[1], list) ): types = [x for x in new_ir.arguments[1]] new_ir.lvalue.set_type(types) @@ -1241,6 +1242,7 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]: return [] return [return_type.type] + def convert_type_of_high_and_internal_level_call( ir: Union[InternalCall, HighLevelCall], contract: Contract ): @@ -1357,7 +1359,9 @@ def remove_temporary(result: List[Operation]) -> List[Operation]: result = [ ins for ins in result - if not isinstance(ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure)) + if not isinstance( + ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure) + ) ] return result diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index 81e669f5a..6e8e3a235 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -37,3 +37,4 @@ from .update_member import UpdateMember from .update_member_dependency import UpdateMemberDependency from .phi_member_may import PhiMemberMay from .update_index import UpdateIndex +from .codesize import CodeSize diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index af1b56f04..ac4741b38 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -15,7 +15,6 @@ logger = logging.getLogger("AssignmentOperationIR") ASSIGNEMENT_TYPE = Union["VALID_RVALUE", "VALID_LVALUE", Function, TupleVariable] - class Assignment(OperationWithLValue): def __init__( self, diff --git a/slither/slithir/tmp_operations/argument.py b/slither/slithir/tmp_operations/argument.py index f82a5594e..17d335896 100644 --- a/slither/slithir/tmp_operations/argument.py +++ b/slither/slithir/tmp_operations/argument.py @@ -11,7 +11,6 @@ if TYPE_CHECKING: from slither.core.variables.variable import Variable - class ArgumentType(Enum): CALL = 0 VALUE = 1 diff --git a/slither/slithir/tmp_operations/tmp_new_array.py b/slither/slithir/tmp_operations/tmp_new_array.py index bea71d03c..c411b68af 100644 --- a/slither/slithir/tmp_operations/tmp_new_array.py +++ b/slither/slithir/tmp_operations/tmp_new_array.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Optional from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.solidity_types.type import Type + class TmpNewArray(OperationWithLValue): def __init__(self, depth: int, array_type: Type, lvalue: Optional["VALID_LVALUE"]): super(TmpNewArray, self).__init__() diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 9d9583ff2..6902aab55 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -8,10 +8,10 @@ from slither.utils.arithmetic import convert_subdenomination from ..exceptions import SlithIRError - if TYPE_CHECKING: from slither.core.solidity_types.type import Type + @total_ordering class Constant(SlithIRVariable): def __init__( diff --git a/slither/slithir/variables/local_variable.py b/slither/slithir/variables/local_variable.py index 18674653a..c2cbb9c52 100644 --- a/slither/slithir/variables/local_variable.py +++ b/slither/slithir/variables/local_variable.py @@ -5,7 +5,6 @@ from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.variable import SlithIRVariable - class LocalIRVariable(LocalVariable, SlithIRVariable): def __init__(self, local_variable: LocalVariable): assert isinstance(local_variable, LocalVariable) diff --git a/slither/slithir/variables/state_variable.py b/slither/slithir/variables/state_variable.py index 52212157d..bd2ba87a7 100644 --- a/slither/slithir/variables/state_variable.py +++ b/slither/slithir/variables/state_variable.py @@ -9,10 +9,8 @@ if TYPE_CHECKING: from slither.core.expressions.expression import Expression - class StateIRVariable(StateVariable, SlithIRVariable): def __init__(self, state_variable: StateVariable): - def __init__(self, state_variable): assert isinstance(state_variable, StateVariable) super(StateVariable, self).__init__() @@ -51,5 +49,6 @@ class StateIRVariable(StateVariable, SlithIRVariable): @property def ssa_name(self) -> str: return "{}_{}".format(self._name, self.index) + def ssa_name(self): return "{}_{}".format(self._name, self.index) diff --git a/slither/slithir/variables/temporary.py b/slither/slithir/variables/temporary.py index 8613a5180..7e475cc9f 100644 --- a/slither/slithir/variables/temporary.py +++ b/slither/slithir/variables/temporary.py @@ -7,7 +7,6 @@ if TYPE_CHECKING: from slither.core.cfg.node import Node - class TemporaryVariable(ChildNode, Variable): COUNTER = 0 diff --git a/slither/slithir/variables/tuple_ssa.py b/slither/slithir/variables/tuple_ssa.py index c51cb5092..f2611b787 100644 --- a/slither/slithir/variables/tuple_ssa.py +++ b/slither/slithir/variables/tuple_ssa.py @@ -6,7 +6,6 @@ from slither.slithir.variables.tuple import TupleVariable - class TupleVariableSSA(TupleVariable): def __init__(self, t): super(TupleVariableSSA, self).__init__(t.node, t.index) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index e5dc5ffdc..cbc0cdd33 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -111,7 +111,6 @@ _signed_to_unsigned = { } - def convert_assignement_member(left, right, t): operations = [] From 9444b2fe26dab3fcc84e2e5b571a32e11fc3dd89 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 13:22:36 +0200 Subject: [PATCH 72/73] Add pyproject.toml --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..7d4861850 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[tool.black] +target-version = ["py36"] +line-length = 100 +[tool.pylint.messages_control] +disable = """ +C0116,C0114 +""" From 517fe3d281008e958295206d1eb5de7ebf8b0178 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 31 Aug 2020 13:22:53 +0200 Subject: [PATCH 73/73] Fix missing import --- slither/slithir/variables/reference.py | 2 +- slither/visitors/slithir/expression_to_slithir.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index dff893cc7..427238db4 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -1,7 +1,7 @@ from typing import Union, Optional, TYPE_CHECKING from slither.core.children.child_node import ChildNode -from slither.core.declarations import Contract, Enum, SolidityVariable +from slither.core.declarations import Contract, Enum, SolidityVariable, Function from slither.slithir.variables.variable import SlithIRVariable diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index cbc0cdd33..9206afb93 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -8,6 +8,7 @@ from slither.core.expressions import ( BinaryOperationType, ) from slither.core.expressions.expression import Expression +from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple from slither.slithir.operations import ( Assignment, Binary,