def will_return(self) -> bool: if not self.sons and self.type != NodeType.THROW: if SolidityFunction("revert()") not in self.solidity_calls: if SolidityFunction( "revert(string)") not in self.solidity_calls: return True return False
def _is_constant(f: Function) -> bool: # pylint: disable=too-many-branches """ Heuristic: - If view/pure with Solidity >= 0.4 -> Return true - If it contains assembly -> Return false (SlitherCore doesn't analyze asm) - Otherwise check for the rules from https://solidity.readthedocs.io/en/v0.5.0/contracts.html?highlight=pure#view-functions with an exception: internal dynamic call are not correctly handled, so we consider them as non-constant :param f: :return: """ if f.view or f.pure: if f.contract.slither.crytic_compile and f.contract.slither.crytic_compile.compiler_version: if not f.contract.slither.crytic_compile.compiler_version.version.startswith( "0.4"): return True if f.payable: return False if not f.is_implemented: return False if f.contains_assembly: return False if f.all_state_variables_written(): return False for ir in f.all_slithir_operations(): if isinstance(ir, InternalDynamicCall): 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)"), ]: 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 and f.contract.slither.crytic_compile.compiler_version): if f.contract.slither.crytic_compile.compiler_version.version.startswith( "0.4"): return False else: return False if isinstance(ir, InternalCall): # Storage write are not properly handled by all_state_variables_written if any(parameter.is_storage for parameter in ir.function.parameters): return False return True
def arbitrary_send(self, func): """ """ if func.is_protected(): return [] ret = [] for node in func.nodes: for ir in node.irs: if isinstance(ir, SolidityCall): if ir.function == SolidityFunction('ecrecover(bytes32,uint8,bytes32,bytes32)'): return False if isinstance(ir, Index): if ir.variable_right == SolidityVariableComposed('msg.sender'): return False 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)': return False if ir.call_value is None: continue if ir.call_value == SolidityVariableComposed('msg.value'): continue if is_dependent(ir.call_value, SolidityVariableComposed('msg.value'), func.contract): continue if is_tainted(ir.destination, func.contract, self.slither): ret.append(node) return ret
def arbitrary_send(func): """ """ if func.is_protected(): return [] ret = [] for node in func.nodes: for ir in node.irs: if isinstance(ir, SolidityCall): if ir.function == SolidityFunction( 'ecrecover(bytes32,uint8,bytes32,bytes32)'): return False if isinstance(ir, Index): if ir.variable_right == SolidityVariableComposed( 'msg.sender'): return False if is_tainted(ir.variable_right, SolidityVariableComposed('msg.sender')): return False if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): if ir.call_value is None: continue if ir.call_value == SolidityVariableComposed('msg.value'): continue if is_tainted(ir.call_value, SolidityVariableComposed('msg.value')): continue if KEY in ir.context: if ir.context[KEY]: ret.append(node) return ret
def taint_balance_equalities(self, functions): taints = [] for func in functions: for node in func.nodes: for ir in node.irs_ssa: if isinstance( ir, SolidityCall ) and ir.function == SolidityFunction("balance(address)"): 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) if isinstance(ir, Assignment): if ir.rvalue in self.sources_taint: taints.append(ir.lvalue) return taints
def _extract_assert(slither): ret = {} for contract in slither.contracts: functions_using_assert = [] for f in contract.functions_entry_points: for v in f.all_solidity_calls(): if v == SolidityFunction('assert(bool)'): functions_using_assert.append(f.full_name) break if functions_using_assert: ret[contract.name] = functions_using_assert return ret
def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]: ret: Dict[str, List[str]] = {} for contract in slither.contracts: functions_using_assert = [] for f in contract.functions_entry_points: for v in f.all_solidity_calls(): if v == SolidityFunction("assert(bool)"): functions_using_assert.append(_get_name(f)) break if functions_using_assert: ret[contract.name] = functions_using_assert return ret
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]]) + ')' 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)
def collect_return_values_of_bad_PRNG_functions(f: Function) -> List: """ Return the return-values of calls to blockhash() Args: f (Function) Returns: list(values) """ values_returned = [] for n in f.nodes: for ir in n.irs_ssa: if (isinstance(ir, SolidityCall) and ir.function == SolidityFunction("blockhash(uint256)") and ir.lvalue): values_returned.append(ir.lvalue) return values_returned
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]]) + ")") else: args = ",".join([str(a) for a in self.arguments]) lvalue = "" if self.lvalue: if isinstance(self.lvalue.type, (list, )): lvalue = f"{self.lvalue}({','.join(str(x) for x in self.lvalue.type)}) = " else: lvalue = f"{self.lvalue}({self.lvalue.type}) = " return lvalue + f"SOLIDITY_CALL {self.function.full_name}({args})"
def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]: """ Detect the functions with external calls :param slither: :return: """ ret: Dict[str, List[str]] = defaultdict(list) for contract in slither.contracts: for function in contract.functions_entry_points: for ir in function.all_slithir_operations(): if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( "balance(address)" ): ret[contract.name].append(_get_name(function)) if contract.name in ret: ret[contract.name] = list(set(ret[contract.name])) return ret
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]]) + ")" ) 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)
def detect_deprecation_in_expression(self, expression): """ Detects if an expression makes use of any deprecated standards. Returns: list of tuple: (detecting_signature, original_text, recommended_text)""" # Perform analysis on this expression export = ExportValues(expression) export_values = export.result() # Define our results list results = [] # Check if there is usage of any deprecated solidity variables or functions for dep_var in self.DEPRECATED_SOLIDITY_VARIABLE: if SolidityVariableComposed(dep_var[0]) in export_values: results.append(dep_var) for dep_func in self.DEPRECATED_SOLIDITY_FUNCTIONS: if SolidityFunction(dep_func[0]) in export_values: results.append(dep_func) return results
def _detect_storage_abiencoderv2_arrays(contract): """ Detects and returns all nodes with storage-allocated abiencoderv2 arrays of arrays/structs in abi.encode, events or external calls :param contract: Contract to detect within :return: A list of tuples with (function, node) where function node has storage-allocated abiencoderv2 arrays of arrays/structs """ # Create our result set. results = set() # Loop for each function and modifier. # pylint: disable=too-many-nested-blocks for function in contract.functions_and_modifiers_declared: # Loop every node, looking for storage-allocated array of arrays/structs # in arguments to abi.encode, events or external calls for node in function.nodes: for ir in node.irs: # Call to abi.encode() if (isinstance(ir, SolidityCall) and ir.function == SolidityFunction("abi.encode()") or # Call to emit event # Call to external function isinstance(ir, (EventCall, HighLevelCall))): for arg in unroll(ir.arguments): # Check if arguments are storage allocated arrays of arrays/structs if (isinstance(arg.type, ArrayType) # Storage allocated and (isinstance(arg, StateVariable) or (isinstance(arg, LocalVariable) and arg.is_storage)) # Array of arrays or structs and isinstance(arg.type.type, (ArrayType, UserDefinedType))): results.add((function, node)) break # Return the resulting set of tuples return results
def arbitrary_send(func: Function): if func.is_protected(): return [] ret: List[Node] = [] for node in func.nodes: for ir in node.irs: if isinstance(ir, SolidityCall): if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"): return False if isinstance(ir, Index): if ir.variable_right == SolidityVariableComposed("msg.sender"): return False 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)": return False if ir.call_value is None: continue if ir.call_value == SolidityVariableComposed("msg.value"): continue if is_dependent( ir.call_value, SolidityVariableComposed("msg.value"), func.contract, ): continue if is_tainted(ir.destination, func.contract): ret.append(node) return ret
class ConstCandidateStateVars(AbstractDetector): """ State variables that could be declared as constant detector. Not all types for constants are implemented in Solidity as of 0.4.25. The only supported types are value types and strings (ElementaryType). Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables """ 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_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.' @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)') ] @staticmethod def _is_constant_var(v): if isinstance(v, StateVariable): return v.is_constant return False def _constant_initial_expression(self, v): if not v.expression: return True export = ExportValues(v.expression) 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)): return True return False def _detect(self): """ Detect state variables that could be const """ results = [] 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_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 ] 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) ] # Order for deterministic results constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name) # Create a result for each finding for v in constable_variables: info = "{} should be constant ({})\n".format( v.canonical_name, v.source_mapping_str) json = self.generate_json_result(info) self.add_variable_to_json(v, json) results.append(json) return results
def find_variable(var_name, caller_context): if isinstance(caller_context, Contract): function = None contract = caller_context elif isinstance(caller_context, Function): function = caller_context contract = function.contract else: logger.error('Incorrect caller context') exit(-1) if function: func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] contract_variables = contract.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = {get_pointer_name(f): f for f in contract.variables} if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] functions = contract.functions_as_dict() if var_name in functions: return functions[var_name] modifiers = contract.modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] structures = contract.structures_as_dict() if var_name in structures: return structures[var_name] events = contract.events_as_dict() if var_name in events: return events[var_name] enums = contract.enums_as_dict() if var_name in enums: return enums[var_name] # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict() for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] raise VariableNotFound('Variable not found: {}'.format(var_name))
class ConstCandidateStateVars(AbstractDetector): """ State variables that could be declared as constant detector. Not all types for constants are implemented in Solidity as of 0.4.25. The only supported types are value types and strings (ElementaryType). Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables """ 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_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 or v.is_immutable) # 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)"), ] @staticmethod def _is_constant_var(v): if isinstance(v, StateVariable): return v.is_constant return False def _constant_initial_expression(self, v): if not v.expression: return True export = ExportValues(v.expression) 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) ): return True return False def _detect(self): """Detect state variables that could be const""" results = [] all_variables = [c.state_variables for c in self.compilation_unit.contracts] all_variables = {item for sublist in all_variables for item in sublist} all_non_constant_elementary_variables = { v for v in all_variables if self._valid_candidate(v) } all_functions = [c.all_functions_called for c in self.compilation_unit.contracts] all_functions = list({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 = {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) ] # Order for deterministic results constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name) # Create a result for each finding for v in constable_variables: info = [v, " should be constant\n"] json = self.generate_result(info) results.append(json) return results @staticmethod def _format(compilation_unit: SlitherCompilationUnit, result): custom_format(compilation_unit, result)
def find_variable( var_name: str, caller_context: CallerContext, referenced_declaration: Optional[int] = None, is_super=False, ) -> Union[Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure]: from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.function import FunctionSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed if isinstance(caller_context, ContractSolc): function: Optional[FunctionSolc] = None contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): function = caller_context contract = function.underlying_function.contract contract_declarer = function.underlying_function.contract_declarer else: raise ParsingError("Incorrect caller context") if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration].underlying_variable # If not found, check for name func_variables = function.underlying_function.variables_as_dict if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.underlying_function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] # variable are looked from the contract declarer contract_variables = contract_declarer.variables_as_dict if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = { get_pointer_name(f): f for f in contract_declarer.variables } if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: getter_available = lambda f: f.functions_declared d = {f.canonical_name: f for f in contract.functions} functions = { f.full_name: f for f in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: functions = contract.available_functions_as_dict() if var_name in functions: return functions[var_name] if is_super: getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} modifiers = { m.full_name: m for m in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] # structures are looked on the contract declarer structures = contract.structures_as_dict 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] enums = contract.enums_as_dict 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: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict if var_name in contracts: return contracts[var_name] if referenced_declaration: # id of the contracts is the referenced declaration # This is not true for the functions, as we dont always have the referenced_declaration # But maybe we could? (TODO) for contract_candidate in contract.slither.contracts: if contract_candidate.id == referenced_declaration: return contract_candidate for function_candidate in caller_context.slither_parser.all_functions_parser: if function_candidate.referenced_declaration == referenced_declaration: return function_candidate.underlying_function raise VariableNotFound("Variable not found: {} (context {})".format( var_name, caller_context))
def find_variable( var_name: str, caller_context: CallerContext, referenced_declaration: Optional[int] = None, is_super=False, ) -> Union[ Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure, ]: from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.contract import ContractSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context) all_contracts = sl.contracts all_functions_parser = sl_parser.all_functions_and_modifiers_parser # Look for all references delcaration # First look only in the context of function/contract # Then look everywhere # Because functions are copied between contracts, two functions can have the same ref # So we need to first look with respect to the direct context ret = _find_variable_from_ref_declaration( referenced_declaration, direct_contracts, direct_functions_parser ) if ret: return ret ret = _find_variable_from_ref_declaration( referenced_declaration, all_contracts, all_functions_parser ) if ret: return ret function_parser: Optional[FunctionSolc] = ( caller_context if isinstance(caller_context, FunctionSolc) else None ) ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration) if ret: return ret contract: Optional[Contract] = None contract_declarer: Optional[Contract] = None if isinstance(caller_context, ContractSolc): contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): underlying_func = caller_context.underlying_function # If contract_parser is set to None, then underlying_function is a functionContract assert isinstance(underlying_func, FunctionContract) contract = underlying_func.contract contract_declarer = underlying_func.contract_declarer ret = _find_in_contract(var_name, contract, contract_declarer, is_super) if ret: return ret # Could refer to any enum all_enumss = [c.enums_as_dict for c in sl.contracts] all_enums = {k: v for d in all_enumss for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] contracts = sl.contracts_as_dict if var_name in contracts: return contracts[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) # Top level must be at the end, if nothing else was found ret = _find_top_level(var_name, sl) if ret: return ret raise VariableNotFound("Variable not found: {} (context {})".format(var_name, caller_context))
def find_variable(var_name, caller_context, referenced_declaration=None, is_super=False): # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed if isinstance(caller_context, Contract): function = None contract = caller_context contract_declarer = caller_context elif isinstance(caller_context, Function): function = caller_context contract = function.contract contract_declarer = function.contract_declarer else: raise ParsingError('Incorrect caller context') if function: # We look for variable declared with the referencedDeclaration attr func_variables = function.variables_renamed if referenced_declaration and referenced_declaration in func_variables: return func_variables[referenced_declaration] # If not found, check for name func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] # A local variable can be a pointer # for example # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = { get_pointer_name(f): f for f in function.variables } if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] # variable are looked from the contract declarer contract_variables = contract_declarer.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] # A state variable can be a pointer conc_variables_ptr = { get_pointer_name(f): f for f in contract_declarer.variables } if var_name and var_name in conc_variables_ptr: return conc_variables_ptr[var_name] if is_super: getter_available = lambda f: f.functions_declared d = {f.canonical_name: f for f in contract.functions} functions = { f.full_name: f for f in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: functions = contract.available_functions_as_dict() if var_name in functions: return functions[var_name] if is_super: getter_available = lambda m: m.modifiers_declared d = {m.canonical_name: m for m in contract.modifiers} modifiers = { m.full_name: m for m in contract_declarer.available_elements_from_inheritances( d, getter_available).values() } else: modifiers = contract.available_modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] # structures are looked on the contract declarer structures = contract.structures_as_dict() if var_name in structures: return structures[var_name] events = contract.events_as_dict() if var_name in events: return events[var_name] enums = contract.enums_as_dict() if var_name in enums: return enums[var_name] # If the enum is refered as its name rather than its canonicalName enums = {e.name: e for e in contract.enums} if var_name in enums: return enums[var_name] # Could refer to any enum all_enums = [c.enums_as_dict() for c in contract.slither.contracts] all_enums = {k: v for d in all_enums for k, v in d.items()} if var_name in all_enums: return all_enums[var_name] if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name) if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] if referenced_declaration: for contract in contract.slither.contracts: if contract.id == referenced_declaration: return contract for function in contract.slither.functions: if function.referenced_declaration == referenced_declaration: return function raise VariableNotFound('Variable not found: {} (context {})'.format( var_name, caller_context))
def find_variable( var_name: str, caller_context: CallerContextExpression, referenced_declaration: Optional[int] = None, is_super: bool = False, ) -> Tuple[ Union[ Variable, Function, Contract, SolidityVariable, SolidityFunction, Event, Enum, Structure, CustomError, TypeAlias, ], bool, ]: """ Return the variable found and a boolean indicating if the variable was created If the variable was created, it has no source mapping, and it the caller must add it :param var_name: :type var_name: :param caller_context: :type caller_context: :param referenced_declaration: :type referenced_declaration: :param is_super: :type is_super: :return: :rtype: """ from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.contract import ContractSolc # variable are looked from the contract declarer # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer # the difference between function and variable come from the fact that an internal call, or an variable access # in a function does not behave similariy, for example in: # contract C{ # function f(){ # state_var = 1 # f2() # } # state_var will refer to C.state_var, no mater if C is inherited # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or # the contract inheriting from C) # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact # structure/enums cannot be shadowed direct_contracts, direct_functions, current_scope = _find_variable_init(caller_context) # Only look for reference declaration in the direct contract, see comment at the end # Reference looked are split between direct and all # Because functions are copied between contracts, two functions can have the same ref # So we need to first look with respect to the direct context if var_name in current_scope.renaming: var_name = current_scope.renaming[var_name] if var_name in current_scope.user_defined_types: return current_scope.user_defined_types[var_name], False # Use ret0/ret1 to help mypy ret0 = _find_variable_from_ref_declaration( referenced_declaration, direct_contracts, direct_functions ) if ret0: return ret0, False function_parser: Optional[FunctionSolc] = ( caller_context if isinstance(caller_context, FunctionSolc) else None ) ret1 = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration) if ret1: return ret1, False contract: Optional[Contract] = None contract_declarer: Optional[Contract] = None if isinstance(caller_context, ContractSolc): contract = caller_context.underlying_contract contract_declarer = caller_context.underlying_contract elif isinstance(caller_context, FunctionSolc): underlying_func = caller_context.underlying_function if isinstance(underlying_func, FunctionContract): contract = underlying_func.contract contract_declarer = underlying_func.contract_declarer else: assert isinstance(underlying_func, FunctionTopLevel) ret = _find_in_contract(var_name, contract, contract_declarer, is_super) if ret: return ret, False # Could refer to any enum all_enumss = [c.enums_as_dict for c in current_scope.contracts.values()] all_enums = {k: v for d in all_enumss for k, v in d.items()} if var_name in all_enums: return all_enums[var_name], False contracts = current_scope.contracts if var_name in contracts: return contracts[var_name], False if var_name in SOLIDITY_VARIABLES: return SolidityVariable(var_name), False if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name), False # Top level must be at the end, if nothing else was found ret, var_was_created = _find_top_level(var_name, current_scope) if ret: return ret, var_was_created # Look from reference declaration in all the contracts at the end # Because they are many instances where this can't be trusted # For example in # contract A{ # function _f() internal view returns(uint){ # return 1; # } # # function get() public view returns(uint){ # return _f(); # } # } # # contract B is A{ # function _f() internal view returns(uint){ # return 2; # } # # } # get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not ret = _find_variable_from_ref_declaration( referenced_declaration, list(current_scope.contracts.values()), list(current_scope.functions), ) if ret: return ret, False raise VariableNotFound(f"Variable not found: {var_name} (context {contract})")