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 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', 'codecall']: if is_tainted(ir.destination, function.contract): ret.append(node) return ret
def _node_taint(self, node): if node.high_level_calls or node.low_level_calls: for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag is True: return True return False
def _node_taint(self, node): if node.high_level_calls or node.low_level_calls: for highLevelCall in node.high_level_calls: contract, functionOrVariable = highLevelCall if isinstance(functionOrVariable, Function): for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag is True: # print(str(node.expression) + '\t' + str(taintflag)) return True if node.low_level_calls: for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag is True: # print(str(node.expression) + '\t' + str(taintflag)) return True return False
def isTaint(self, contract): for function in contract.functions_and_modifiers_declared: # print('{} -> {}'.format(type(function.visibility), function.visibility)) #if function.visibility in ['public', 'external']: for node in function.nodes: taintRes = False if node.high_level_calls or node.low_level_calls: for ir in node.irs: if hasattr(ir, 'destination'): taintRes = is_tainted(ir.destination, function.contract) print('Function: {} dest: {} isTaint {}'.format( function.full_name, ir.destination, taintRes))
def call_in_loop(f, node, in_loop, visited, ret, loopsum): if node in visited: return # shared visited visited.append(node) if node.type == NodeType.STARTLOOP: loopsum.append('hasloop') # print('>>>>>>>>>>>>>begin loop') in_loop = True # is_tainted_by_arg, is_tainted_by_statevar, is_tained_by_local, has_transaction, has_lib_call, has_nested_loop = MultipleCallsInLoop.collect_summary(node, f) # print('Loop Summary----', 'has_nested_loop:', has_nested_loop, 'has_lib_call:', has_lib_call, 'has_transaction:', has_transaction, # 'is_tained_by_local:', is_tained_by_local, 'is_tainted_by_arg:', is_tainted_by_arg, 'is_tainted_by_statevar:', is_tainted_by_statevar ) elif node.type == NodeType.ENDLOOP: # print('<<<<<<<<<<<<<exit loop') in_loop = False if in_loop: has_nested_loop = any( (son.type == NodeType.STARTLOOP) for son in node.sons) if has_nested_loop: loopsum.append('nested_loop') for ir in node.irs: if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)): if isinstance(ir, LibraryCall): has_lib_call = True loopsum.append('lib_call') else: has_transaction = True loopsum.append('transaction') if isinstance(ir, Condition): is_tainted_by_arg = is_tainted(ir.value, f) is_tainted_by_statevar = any( is_dependent(ir.value, t, f) for t in f.contract.state_variables) is_tained_by_local = not (is_tainted_by_arg or is_tainted_by_statevar) # print(is_tainted_by_arg, is_tainted_by_statevar, is_tained_by_local, node, type(node)) if is_tainted_by_arg: loopsum.append('tainted_by_arg') if is_tainted_by_statevar: loopsum.append('tainted_by_statevar') if is_tained_by_local: loopsum.append('tained_by_local') for son in node.sons: MultipleCallsInLoop.call_in_loop(f, son, in_loop, visited, ret, loopsum)
def node_taint(node): if node.internal_calls: for internalCall in node.internal_calls: if isinstance(internalCall, Function): node.callee.append(internalCall) if node.high_level_calls or node.low_level_calls: for highLevelCall in node.high_level_calls: contract, functionOrVariable = highLevelCall if isinstance(functionOrVariable, Function): node.callee.append(functionOrVariable) for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag is True: return True if node.low_level_calls: for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag is True: return True return False
def _detect_missing_zero_address_validation(self, contract): """ Detects if addresses are zero address validated before use. :param contract: The contract to check :return: Functions with nodes where addresses used are not zero address validated earlier """ results = [] for function in contract.functions_entry_points: var_nodes = defaultdict(list) for node in function.nodes: sv_addrs_written = [ sv for sv in node.state_variables_written if sv.type == ElementaryType("address") ] addr_calls = False for ir in node.irs: if isinstance(ir, (Send, Transfer, LowLevelCall)): addr_calls = True # Continue if no address-typed state variables are written and if no send/transfer/call if not sv_addrs_written and not addr_calls: continue # Check local variables used in such nodes for var in node.local_variables_read: # Check for address types that are tainted but not by msg.sender if var.type == ElementaryType("address") and is_tainted( var, function, ignore_generic_taint=True ): # Check for zero address validation of variable # in the context of modifiers used or prior function context if not ( self._zero_address_validation_in_modifier( var, function.modifiers_statements ) or self._zero_address_validation(var, node, []) ): # Report a variable only once per function var_nodes[var].append(node) if var_nodes: results.append((function, var_nodes)) return results
def detect_array_length_assignment(contract): """ Detects and returns all nodes which assign array length. :param contract: Contract to detect assignment within. :return: A list of tuples with (Variable, node) where Variable references an array whose length was set by node. """ # 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: # Define a set of reference variables which refer to array length. array_length_refs = set() # Loop for every node in this function, looking for expressions where array length references are made, # and subsequent expressions where array length references are assigned to. for node in function.nodes: if node.type == NodeType.EXPRESSION: for ir in node.irs: # First we look for the member access for 'length', for which a reference is created. # We add the reference to our list of array length references. if isinstance(ir, Length): # a # if ir.variable_right == "length": array_length_refs.add(ir.lvalue) # If we have an assignment/binary operation, verify the left side refers to a reference variable # which is in our list or array length references. (Array length is being assigned to). elif isinstance(ir, (Assignment, Binary)): if isinstance(ir.lvalue, ReferenceVariable): if ir.lvalue in array_length_refs and any( is_tainted(v, contract) for v in ir.read): # the taint is not precise enough yet # as a result, REF_0 = REF_0 + 1 # where REF_0 points to a LENGTH operation # is considered as tainted if ir.lvalue in ir.read: continue results.add(node) break # Return the resulting set of nodes which set array length. return results
def _externalCallLinkparse(self, node): externalCallLink_parse_Result = 0 # 证明这个调用链的dest不脏 """ :param node: 含有外部调用的node(跨合约) :return: 如果确实是脏数据则返回1 遇到外部调用需要进行的分析 1:先进行外部调用的脏数据分析: 若确定为脏数据,打印‘reentrance’并返回1 否则, 进入这个干净外部函数内得到他的所有node,再次分析调用链self._analyzerCallLink(calledExternalFunctionNode) """ print("进入外部调用链分析") print('开始分析的节点是', node.type) taintflag = False if node.high_level_calls or node.low_level_calls: # if node.low_level_calls: # 这块仅仅是模拟没有任何参考价值 # taintflag = True print('进行脏数据分析...') for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag: # 如果数据脏则打印Reentrance print('脏') externalCallLink_parse_Result = 1 return externalCallLink_parse_Result else: # 如果数据干净,跳入external call的Function. print('不脏') high_level_calls = [] for high_level_calls_tuple in node.high_level_calls: high_level_calls.append(high_level_calls_tuple[1]) # for external_calls in high_level_calls: # node.low_level_calls暂不考虑,因为根据node.low_level_call目前还跳不进去这个called外部调用函数 for external_call in high_level_calls: calledExternalFunction = external_call # 得到called外部调用函数对象实例 print('准备跳转到外部调用函数中:', calledExternalFunction.full_name) calledExternalFunctionNodeList = self._getAllNodes( calledExternalFunction) for calledExternalFunctionNode in calledExternalFunctionNodeList: externalCallLink_parse_Result = self._analyzerCallLink( calledExternalFunctionNode) if externalCallLink_parse_Result == 1: return externalCallLink_parse_Result return externalCallLink_parse_Result
def _detect_missing_events(self, contract): """ Detects if critical contract parameters set by owners and used in arithmetic are missing events :param contract: The contract to check :return: Functions with nodes of critical operations but no events """ results = [] for function in contract.functions_entry_points: nodes = [] # Check for any events in the function and skip if found # Note: not checking if event corresponds to critical parameter if any(ir for node in function.nodes for ir in node.irs if isinstance(ir, EventCall)): continue # Ignore constructors and private/internal functions # Heuristic-1: functions writing to critical parameters are typically "protected". # Skip unprotected functions. if function.is_constructor or not function.is_protected(): continue # Heuristic-2: Critical operations are where state variables are written and tainted # Heuristic-3: Variables of interest are int/uint types that are used (mostly in arithmetic) # in other unprotected functions # Heuristic-4: Critical operations present but no events in the function is not a good practice for node in function.nodes: for sv in node.state_variables_written: if ( is_tainted(sv, function) and isinstance(sv.type, ElementaryType) and sv.type.type in Int + Uint ): used_nodes = self._detect_unprotected_use(contract, sv) if used_nodes: nodes.append((node, used_nodes)) if nodes: results.append((function, nodes)) 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
def _detect_missing_events(contract): """ Detects if critical contract parameters set by owners and used in access control are missing events :param contract: The contract to check :return: Functions with nodes of critical operations but no events """ results = [] # pylint: disable=too-many-nested-blocks for function in contract.functions_entry_points: nodes = [] # Check for any events in the function and skip if found # Note: not checking if event corresponds to critical parameter if any(ir for node in function.nodes for ir in node.irs if isinstance(ir, EventCall)): continue # Ignore constructors and private/internal functions # Heuristic-1: functions with critical operations are typically "protected". Skip unprotected functions. if function.is_constructor or not function.is_protected(): continue # Heuristic-2: Critical operations are where state variables are written and tainted # Heuristic-3: Variables of interest are address type that are used in modifiers i.e. access control # Heuristic-4: Critical operations present but no events in the function is not a good practice for node in function.nodes: for sv in node.state_variables_written: if is_tainted( sv, function) and sv.type == ElementaryType("address"): for mod in function.contract.modifiers: if sv in mod.state_variables_read: nodes.append((node, sv, mod)) if nodes: results.append((function, nodes)) return results
from slither.core.declarations.solidity_variables import SolidityVariableComposed slither = Slither('data_dependency.sol') contract = slither.get_contract_from_name('Simple') destination = contract.get_state_variable_from_name('destination') source = contract.get_state_variable_from_name('source') print('{} is dependent of {}: {}'.format( source, destination, is_dependent(source, destination, contract))) assert not is_dependent(source, destination, contract) print('{} is dependent of {}: {}'.format( destination, source, is_dependent(destination, source, contract))) assert is_dependent(destination, source, contract) print('{} is tainted {}'.format(source, is_tainted(source, contract, slither))) assert not is_tainted(source, contract, slither) print('{} is tainted {}'.format(destination, is_tainted(destination, contract, slither))) assert is_tainted(destination, contract, slither) contract = slither.get_contract_from_name('Reference') destination = contract.get_state_variable_from_name('destination') source = contract.get_state_variable_from_name('source') print('Reference contract') print('{} is dependent of {}: {}'.format( source, destination, is_dependent(source, destination, contract))) assert not is_dependent(source, destination, contract) print('{} is dependent of {}: {}'.format(
def detect_reentrancy(self, contract, callGraph, dangerFunctionList): ''' 得到taintFunctionNode 对应 的function得到function所有的taintNode,append到taintFunction.taintNodes属性中 ''' for taintFunctionNode in callGraph.taintFunctionNodes: taintFunction = taintFunctionNode.function for node in taintFunction.nodes: if node.high_level_calls or node.low_level_calls: for ir in node.irs: if hasattr(ir, 'destination'): taintflag = is_tainted(ir.destination, node.function.contract) if taintflag: taintFunction.taintNodes.append(node) taintFunction.directTaintNodes.append(node) for function in contract.functions: # callGraph.test(function) if function.is_implemented: dm = DM(function) # 准备DM callPath = [] fatherFunctionLayerCount = 0 # 回溯调用记录的层数 eth_nodes = [] # 存储本函数含有转帐功能的cfg节点 advanceUpdateFlag = False taint_nodes = [] isReentrancy = False # False代表没有检测到reentrance after_ethNodeList = [] # 用于存储转账节点的所有后续节点 afer_taintNodeList = [] # 往 eth_nodes列表中append值 nodes = function.nodes # 得到函数中所有的节点 for node in nodes: if self._can_send_eth(node.irs): # 如果这个节点可以发送eth function.set_canEth(True) eth_nodes.append(node) function.ethNodes = eth_nodes # for ir in node.irs: # if isinstance(ir, Transfer): # transferORsendNodes.append(node) # result = self._can_send_eth(node.irs) # if result: # self._retrospect(node) # afterNodeList = [] # 用于存储每一个函数中的transfer或send所有后续节点 # for transferORsendNode in transferORsendNodes: # self._getAllbehindNode(transferORsendNode, afterNodeList) #此时afterNodeList中存入了一个函数中的transfer或send所有后续节点 # # for afterNode in afterNodeList: # 找出后续是internal call的节点 # self._analyzerCallLink(afterNode) # # 得到每一个eth_node的后续节点并进行存储到after_ethNodeList列表中 if function.canEth == False: print('合约{}.函数{} 不含有直接转张功能,跳过此函数的分析'.format( function.contract.name, function.full_name)) continue for eth_cfg_node in eth_nodes: for taint_cfg_Node in function.directTaintNodes: # function.directTaintNodes !!! ethCFGnode_To_taintCFGnode = [] ethCFGnode_To_taintCFGnode.append(eth_cfg_node) ethCFGnode_To_taintCFGnode.extend( list( set(function.nodes) - set([eth_cfg_node, taint_cfg_Node]))) ethCFGnode_To_taintCFGnode.append(taint_cfg_Node) allPaths = getCfgAllPath(ethCFGnode_To_taintCFGnode) allPathsNode = allPaths_intToNode( allPaths, ethCFGnode_To_taintCFGnode) if allPathsNode: # 如果当前eth_node 和 当前taintNode之间至少有一条路径 advanceUpdateFlag = dm.advancedUpdateEth(function) human_allPathsNode = [] for path in allPathsNode: temp = [] for node in reversed(path): temp.append(str(node.expression)) human_allPathsNode.append(temp) privateVisibility = dm.privateVisibility( function) # 检查function是否为private if privateVisibility is True: # 如果function 可见性为private havePublicCaller = callerVisibilityHavePublic( function, callGraph) # 看是否有public caller if havePublicCaller is True: isReentrancy = True print( '合约{}.函数{}: 本身就有Reentrancy路径结构 | 可见性是private但有publicCaller | 钱更新/锁: {} | paths: {}' .format(function.contract.name, function.full_name, privateVisibility, advanceUpdateFlag, havePublicCaller, human_allPathsNode)) else: isReentrancy = True print( '合约{}.函数{}: 本身就有Reentrancy路径结构 | 且可见性是public | 钱更新/锁: {} | paths: {}' .format(function.contract.name, function.full_name, advanceUpdateFlag, human_allPathsNode)) if isReentrancy is True: print('合约{}.函数{}: 是个危险函数!'.format(function.contract.name, function.full_name)) continue ''' for eth_node in eth_nodes: after_ethNodeList.append( eth_node) # !!!在得到传送eth节点的所有后续节点列表之前,先把负责传送eth节点的本身添加进来,因为.call.value()类型的node本身也是外部调用!!! self._getAllbehindNode(eth_node, after_ethNodeList) # 从after_ethNodeList列表中取出所有的节点(记为afterEthNode), 然后进行调用链的分析 if after_ethNodeList: print('开始分析 合约{}.函数{}'.format(function.contract.name, function.full_name)) directTaintReentrancy = False for node_afterEthNode in after_ethNodeList: # taintflag = False if node_afterEthNode.high_level_calls or node_afterEthNode.low_level_calls: print('进行脏数据分析...') for ir in node_afterEthNode.irs: if hasattr(ir, 'destination'): result = is_tainted(ir.destination, node_afterEthNode.function.contract) if result == True: directTaintReentrancy = True break if directTaintReentrancy == True: break if directTaintReentrancy == True: #待检测函数内部直接taint出Reentrancy advanceUpdateFlag = dm.advancedUpdateEth(function) privateVisibility = dm.privateVisibility(function) # 检查function是否为private if privateVisibility is True: # 如果function 可见性为private havePublicCaller = callerVisibilityHavePublic(function, callGraph) # 看是否有public caller if havePublicCaller is True: # 如果有publice caller # 钱是否提前更新/锁(本质是条件控制的全局变量是否提前更新) print('{}.{} 从本身分析就得到了reentrance结果, 就不用forward call graph了, 钱提前更新/锁:{}, 可见性为private但存在public_Caller'.format( function.contract.name, function.full_name, advanceUpdateFlag)) continue else: # 如果function 可见性为public print( '{}.{} 从本身分析就得到了reentrance结果, 就不用forward call graph了, 钱提前更新/锁:{}, 且可见性不是private'.format( function.contract.name, function.full_name, advanceUpdateFlag)) continue # taintflag = True # if taintflag == True: # isReentrancy = True # break # 如果只从本身分析就得到了reentrance结果, 就不用forward call graph了。 # else: # continue # advanceUpdateFlag = dm.advancedUpdateEth(function) # if isReentrancy == True: # 到这里这个函数的所有afterEthNode就遍历分析完了,如果为True。输出reentrance,然后分析下一个函数 # privateVisibility = dm.privateVisibility(function) # if privateVisibility is True: # havePublicCaller = callerVisibilityHavePublic(function, callGraph) # if havePublicCaller == False: # continue # print('{}.{} 从本身分析就得到了reentrance结果, 就不用forward call graph了, 钱提前更新:{}, 可见性为private但存在public_Caller: {}'.format(function.contract.name, function.full_name, advanceUpdateFlag, privateVisibility)) # continue # 直接开始分析下一个函数 ''' if not isReentrancy and function.canEth is True: # 进行forward call graph print(function.full_name, '可以传送eth,但自身函数体内没直接的reetrance的结构需要进行前向的call graph') function_canEth_Node = callGraph.function_Map_node.get( function) # 得到这个函数的节点 # print(function_canEth_Node.function.full_name) forwardDangerPath = self.forwardPath( function_canEth_Node, after_ethNodeList, callGraph) if forwardDangerPath: # [[Vt,...Ve], [dangerPath2], [], ...] # 前向路径存在,准备DM advanceUpdateFlag = dm.advancedUpdateEth(function) huamnlook_forwardDangerPath = [] for path in forwardDangerPath: temp = [] for item in reversed(path): temp.append(item.function.full_name) huamnlook_forwardDangerPath.append(temp) privateVisibility = dm.privateVisibility(function) if privateVisibility is True: havePublicCaller = callerVisibilityHavePublic( function, callGraph) if havePublicCaller == True: isReentrancy = True print( '合约{}.函数{} forward Reentrancy | 可见性是private但有publicCaller | 钱更新/锁: {} | paths: {}' .format(function.contract.name, function.full_name, advanceUpdateFlag, huamnlook_forwardDangerPath)) continue #这个地方是否continue还得考虑 else: isReentrancy = True print( '合约{}.函数{} forwardReentrancy | 且可见性是public | 钱更新/锁: {} | paths: {}e' .format(function.contract.name, function.full_name, advanceUpdateFlag, huamnlook_forwardDangerPath)) continue if isReentrancy is True: print('合约{}.函数{}: 是个危险函数!'.format( function.contract.name, function.full_name)) continue backwardDangerPath = self.backwardPath( function_canEth_Node, callGraph) if backwardDangerPath: # [[Ve,...Vt], [dangerPath2], [], ...] # 后向路径存在,准备DM afterDM_backwardDangerPaths = [] advanceUpdateFlag = dm.advancedUpdateEth(function) huamnlook_backwardDangerPaths = [] for path in backwardDangerPath: # 取出后向路径的每一条路径 finallCaller = path[-1] finallCallerPrivateVisibility = dm.privateVisibility( finallCaller) if finallCallerPrivateVisibility is True: # 如果最后的caller 是private havePublicCaller = callerVisibilityHavePublic( function, finallCaller ) # 判断finall caller是否有public caller if havePublicCaller is True: afterDM_backwardDangerPaths.append(path) else: # 如果最后的caller 是public afterDM_backwardDangerPaths.append(path) # temp = [] # for item in path: # temp.append(item.function.full_name) # huamnlook_backwardDangerPath.append(temp) if afterDM_backwardDangerPaths: isReentrancy = True for afterDM_backwardDangerPath in afterDM_backwardDangerPaths: temp_path = [] for item in reversed( afterDM_backwardDangerPath): temp_path.append(item.full_name) huamnlook_backwardDangerPaths.append(temp_path) print( '合约{}.函数{} backwardReentrancy | 钱更新/锁: {} | Path:{}' .format(function.contract.name, function.full_name, advanceUpdateFlag, huamnlook_backwardDangerPaths)) if isReentrancy is True: benci_dangerFunctionList = set() for path in huamnlook_backwardDangerPaths: if path[-1] not in dangerFunctionList: benci_dangerFunctionList.add(path[-1]) for dangerFunction in benci_dangerFunctionList: print('合约{}.函数{} 是一个危险函数'.format( dangerFunction.contract.name, dangerFunction.full_name)) continue
sys.exit(-1) slither = Slither(sys.argv[1]) contract = slither.get_contract_from_name("Simple") assert contract destination = contract.get_state_variable_from_name("destination") source = contract.get_state_variable_from_name("source") print("{} is dependent of {}: {}".format( source, destination, is_dependent(source, destination, contract))) assert not is_dependent(source, destination, contract) print("{} is dependent of {}: {}".format( destination, source, is_dependent(destination, source, contract))) assert is_dependent(destination, source, contract) print("{} is tainted {}".format(source, is_tainted(source, contract))) assert not is_tainted(source, contract) print("{} is tainted {}".format(destination, is_tainted(destination, contract))) assert is_tainted(destination, contract) contract = slither.get_contract_from_name("Reference") assert contract destination = contract.get_state_variable_from_name("destination") assert destination source = contract.get_state_variable_from_name("source") assert source print("Reference contract") print("{} is dependent of {}: {}".format( source, destination, is_dependent(source, destination, contract)))
sys.exit(-1) slither = Slither(sys.argv[1]) contracts = slither.get_contract_from_name("Simple") assert len(contracts) == 1 contract = contracts[0] destination = contract.get_state_variable_from_name("destination") source = contract.get_state_variable_from_name("source") print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}") assert not is_dependent(source, destination, contract) print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}") assert is_dependent(destination, source, contract) print(f"{source} is tainted {is_tainted(source, contract)}") assert not is_tainted(source, contract) print(f"{destination} is tainted {is_tainted(destination, contract)}") assert is_tainted(destination, contract) contracts = slither.get_contract_from_name("Reference") assert len(contracts) == 1 contract = contracts[0] destination = contract.get_state_variable_from_name("destination") assert destination source = contract.get_state_variable_from_name("source") assert source print("Reference contract") print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}") assert not is_dependent(source, destination, contract) print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")