From 78c3ea2c34b24dd2a13f078d234f8ce0798aaae6 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 3 Nov 2023 15:57:54 +0100 Subject: [PATCH 01/87] Oracle module --- slither/detectors/all_detectors.py | 4 ++ slither/detectors/oracles/__init__.py | 0 slither/detectors/oracles/oracle.py | 73 +++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 slither/detectors/oracles/__init__.py create mode 100644 slither/detectors/oracles/oracle.py diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index fab9562d20..e8681696b6 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -97,3 +97,7 @@ from .operations.incorrect_exp import IncorrectOperatorExponentiation from .statements.tautological_compare import TautologicalCompare from .statements.return_bomb import ReturnBomb + +# my detector + +from .oracles.oracle import MyDetector \ No newline at end of file diff --git a/slither/detectors/oracles/__init__.py b/slither/detectors/oracles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py new file mode 100644 index 0000000000..ed298177a8 --- /dev/null +++ b/slither/detectors/oracles/oracle.py @@ -0,0 +1,73 @@ +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract + +class Oracle: + def __init__(self, _contract, _function, _var): + self.contract = _contract + self.function = _function + self.var = _var + +class MyDetector(AbstractDetector): + """ + Documentation + """ + + ARGUMENT = 'mydetector' # slither will launch the detector with slither.py --detect mydetector + HELP = 'Help printed by slither' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = 'RUN' + + WIKI_TITLE = 'asda' + WIKI_DESCRIPTION = 'asdsad' + WIKI_EXPLOIT_SCENARIO = 'asdsad' + WIKI_RECOMMENDATION = 'asdsad' + # https://github.com/crytic/slither/wiki/Python-API + # def detect_stale_price(Function): + def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: + """ + Detects off-chain oracle contract and VAR + """ + oracles = [] + for contract in contracts: + if "Oracle" in contract.name: + for function in contract.functions: + if function.is_constructor: + continue + for var in function.state_variables_read: + # print(var.name) + # print(var.type) + # print(type(var.type)) + # print("------------------") + if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): + oracles.append(Oracle(contract, function, var)) + # print(f.nodes) + return oracles + + def check_latestRoundData(self, function: FunctionContract) -> bool: + for functionCalled in function.high_level_calls: # Returns tuple (first contract, second function) + if str(functionCalled[1].name) == "latestRoundData": + return True + + def checks_for_timestamp(self, contract : Contract, function: FunctionContract) -> bool: + """ + Detects timestamp usage + """ + for var in function.variables_written: + if ("timestamp" in str(var.name)): + if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): + return True + return False + + def _detect(self): + info = [] + oracles = self.chainlink_oracles(self.contracts) + for oracle in oracles: + if(not self.checks_for_timestamp(oracle.contract, oracle.function)): + rep = "Oracle {} in contract {} does not check timestamp\n".format(oracle.function.name, oracle.contract.name) + info.append(rep) + res = self.generate_result(info) + + return [res] \ No newline at end of file From 6c3da7cafb7b6a9a19f9bb4be4f948740dca5391 Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 8 Nov 2023 10:18:19 +0100 Subject: [PATCH 02/87] Before mapping --- slither/detectors/oracles/oracle.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index ed298177a8..78778e6bfe 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -32,17 +32,16 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: """ oracles = [] for contract in contracts: - if "Oracle" in contract.name: - for function in contract.functions: - if function.is_constructor: - continue - for var in function.state_variables_read: - # print(var.name) - # print(var.type) - # print(type(var.type)) - # print("------------------") - if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): - oracles.append(Oracle(contract, function, var)) + for function in contract.functions: + if function.is_constructor: + continue + for var in function.state_variables_read: + # print(var.name) + # print(var.type) + # print(type(var.type)) + # print("------------------") + if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): + oracles.append(Oracle(contract, function, var)) # print(f.nodes) return oracles @@ -50,13 +49,15 @@ def check_latestRoundData(self, function: FunctionContract) -> bool: for functionCalled in function.high_level_calls: # Returns tuple (first contract, second function) if str(functionCalled[1].name) == "latestRoundData": return True + return False def checks_for_timestamp(self, contract : Contract, function: FunctionContract) -> bool: """ Detects timestamp usage """ for var in function.variables_written: - if ("timestamp" in str(var.name)): + + if ("timestamp" in str(var.name)): #TODO add lowercasing if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): return True return False From 86d7dd31fea6c509674bc69ed67b0984e9ded725 Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 8 Nov 2023 11:07:48 +0100 Subject: [PATCH 03/87] Change the approach --- slither/detectors/oracles/oracle.py | 45 ++++++++++++++++++----------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 78778e6bfe..fe908be4a4 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -3,10 +3,11 @@ from slither.core.declarations.function_contract import FunctionContract class Oracle: - def __init__(self, _contract, _function, _var): + def __init__(self, _contract, _function, _interface_var, _line_of_call): self.contract = _contract self.function = _function - self.var = _var + self.interface_var = _interface_var + self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] class MyDetector(AbstractDetector): """ @@ -35,39 +36,49 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: for function in contract.functions: if function.is_constructor: continue + found_latest_round,name_interface, line = self.latestRoundData(function) for var in function.state_variables_read: # print(var.name) # print(var.type) # print(type(var.type)) # print("------------------") - if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): - oracles.append(Oracle(contract, function, var)) + if (str(var.name) == str(name_interface)) and found_latest_round: + oracles.append(Oracle(contract, function, var, line)) + # if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): + # oracles.append(Oracle(contract, function, var)) # print(f.nodes) return oracles - def check_latestRoundData(self, function: FunctionContract) -> bool: - for functionCalled in function.high_level_calls: # Returns tuple (first contract, second function) - if str(functionCalled[1].name) == "latestRoundData": - return True - return False + def latestRoundData(self, function: FunctionContract) -> (bool, str, int): + for functionCalled in function.external_calls_as_expressions: # Returns tuple (first contract, second function) + if ("latestRoundData" in str(functionCalled)): + return (True, str(functionCalled).split(".")[0], functionCalled.source_mapping.lines[0]) # The external call is in format contract.function, so we split it and get the contract name + return (False, "", 0) + + def get_returned_variables_from_oracle(self, function: FunctionContract, oracle_call_line) -> list: + returned_vars = [] + for var in function.variables_written: + if var.source_mapping.lines[0] == oracle_call_line: + returned_vars.append(var) + return returned_vars - def checks_for_timestamp(self, contract : Contract, function: FunctionContract) -> bool: + def checks_if_vars_in_condition(self, contract : Contract, function: FunctionContract, oracle_vars) -> bool: """ Detects timestamp usage """ - for var in function.variables_written: - - if ("timestamp" in str(var.name)): #TODO add lowercasing - if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): + for var in oracle_vars: + if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): return True - return False + else: + return False def _detect(self): info = [] oracles = self.chainlink_oracles(self.contracts) for oracle in oracles: - if(not self.checks_for_timestamp(oracle.contract, oracle.function)): - rep = "Oracle {} in contract {} does not check timestamp\n".format(oracle.function.name, oracle.contract.name) + oracle_vars = self.get_returned_variables_from_oracle(oracle.function, oracle.line_of_call) + if(not self.checks_if_vars_in_condition(oracle.contract, oracle.function, oracle_vars)): + rep = "Oracle {} in contract {} does not check the value of var\n".format(oracle.function.name, oracle.contract.name) info.append(rep) res = self.generate_result(info) From 9f8f9ab9321498b0d22693588c2363b34ea4465d Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 8 Nov 2023 11:10:24 +0100 Subject: [PATCH 04/87] Change the approach --- slither/detectors/oracles/oracle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index fe908be4a4..4c368403b6 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -64,7 +64,7 @@ def get_returned_variables_from_oracle(self, function: FunctionContract, oracle_ def checks_if_vars_in_condition(self, contract : Contract, function: FunctionContract, oracle_vars) -> bool: """ - Detects timestamp usage + Detects if vars from oracles are in some condition """ for var in oracle_vars: if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): From e8a8758767f053e386fcc770170e3d6460bfa4c2 Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 8 Nov 2023 11:49:59 +0100 Subject: [PATCH 05/87] Return just not checked vars --- slither/detectors/oracles/oracle.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 4c368403b6..99b4a90977 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -8,6 +8,8 @@ def __init__(self, _contract, _function, _interface_var, _line_of_call): self.function = _function self.interface_var = _interface_var self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] + self.vars_in_condition = [] + self.vars_not_in_condition = [] class MyDetector(AbstractDetector): """ @@ -62,23 +64,27 @@ def get_returned_variables_from_oracle(self, function: FunctionContract, oracle_ returned_vars.append(var) return returned_vars - def checks_if_vars_in_condition(self, contract : Contract, function: FunctionContract, oracle_vars) -> bool: + def checks_if_vars_in_condition(self, oracle: Oracle, contract : Contract, function: FunctionContract, oracle_vars) -> bool: """ Detects if vars from oracles are in some condition """ + oracle.vars_in_condition = [] + oracle.vars_not_in_condition = [] for var in oracle_vars: if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): - return True + oracle.vars_in_condition.append(var) else: - return False + oracle.vars_not_in_condition.append(var) + + def _detect(self): info = [] oracles = self.chainlink_oracles(self.contracts) for oracle in oracles: oracle_vars = self.get_returned_variables_from_oracle(oracle.function, oracle.line_of_call) - if(not self.checks_if_vars_in_condition(oracle.contract, oracle.function, oracle_vars)): - rep = "Oracle {} in contract {} does not check the value of var\n".format(oracle.function.name, oracle.contract.name) + if(not self.checks_if_vars_in_condition(oracle, oracle.contract, oracle.function, oracle_vars)): + rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format(oracle.contract.name, oracle.function.name, oracle.interface_var, [var.name for var in oracle.vars_not_in_condition] ) info.append(rep) res = self.generate_result(info) From aadbd581e592878a3ae63e4b8c840832a566ba68 Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 8 Nov 2023 15:12:56 +0100 Subject: [PATCH 06/87] Creating check for updated at - not finished --- slither/detectors/oracles/oracle.py | 50 +++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 99b4a90977..b1846d6ed9 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -1,6 +1,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.core.declarations.contract import Contract from slither.core.declarations.function_contract import FunctionContract +from slither.core.expressions import expression class Oracle: def __init__(self, _contract, _function, _interface_var, _line_of_call): @@ -10,6 +11,7 @@ def __init__(self, _contract, _function, _interface_var, _line_of_call): self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] self.vars_in_condition = [] self.vars_not_in_condition = [] + self.possible_variables_names = ["price", "timestamp", "updatedAt", "answer", "roundID", "startedAt"] class MyDetector(AbstractDetector): """ @@ -29,6 +31,7 @@ class MyDetector(AbstractDetector): WIKI_RECOMMENDATION = 'asdsad' # https://github.com/crytic/slither/wiki/Python-API # def detect_stale_price(Function): + ORACLE_CALLS = ["latestRoundData", "getRoundData"] def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: """ Detects off-chain oracle contract and VAR @@ -38,7 +41,7 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: for function in contract.functions: if function.is_constructor: continue - found_latest_round,name_interface, line = self.latestRoundData(function) + found_latest_round,name_interface, line = self.check_chainlink_call(function) for var in function.state_variables_read: # print(var.name) # print(var.type) @@ -51,9 +54,15 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: # print(f.nodes) return oracles - def latestRoundData(self, function: FunctionContract) -> (bool, str, int): + def compare_chainlink_call(self, function: expression) -> bool: + for call in self.ORACLE_CALLS: + if call in str(function): + return True + return False + + def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): for functionCalled in function.external_calls_as_expressions: # Returns tuple (first contract, second function) - if ("latestRoundData" in str(functionCalled)): + if self.compare_chainlink_call(functionCalled): return (True, str(functionCalled).split(".")[0], functionCalled.source_mapping.lines[0]) # The external call is in format contract.function, so we split it and get the contract name return (False, "", 0) @@ -75,7 +84,39 @@ def checks_if_vars_in_condition(self, oracle: Oracle, contract : Contract, func oracle.vars_in_condition.append(var) else: oracle.vars_not_in_condition.append(var) + if len(oracle.vars_not_in_condition) > 0: + return False + return True + + def check_var_condition_match(self, var, node) -> bool: + for var2 in node.variables_read: + if var.name == var2.name: + return True + return False + + def check_updatedAt(self, node: Node) -> bool: + statement = node.split(" ") + if "block.timestamp" not in statement: + return False + + return True + def direct_variable_check(node: Node, var) -> bool: + var_name = str(var.name) + if "updatedAt" in var_name: + return self.check_updatedAt(node) + + + return True + def check_conditions_enough(self, oracle: Oracle) -> bool: + checks_not_enough = [] + for var in oracle.vars_in_condition: + for node in oracle.function.nodes: + if node.is_conditional() and self.check_var_condition_match(var, node): + if not self.direct_variable_check(node, var): + checks_not_enough.append(var) + return checks_not_enough + def _detect(self): @@ -86,6 +127,9 @@ def _detect(self): if(not self.checks_if_vars_in_condition(oracle, oracle.contract, oracle.function, oracle_vars)): rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format(oracle.contract.name, oracle.function.name, oracle.interface_var, [var.name for var in oracle.vars_not_in_condition] ) info.append(rep) + if (len(oracle.vars_in_condition) > 0): + for var in self.check_conditions_enough(oracle): + info.append("Problem with {}",var.name) res = self.generate_result(info) return [res] \ No newline at end of file From ed029ed153cd202e78d37184698b4eea8c7d7efe Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 8 Nov 2023 23:11:42 +0100 Subject: [PATCH 07/87] Add some documentation --- slither/detectors/oracles/oracle.py | 52 +++++++++++++++++------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index b1846d6ed9..b2b8c9ddf2 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -2,6 +2,7 @@ from slither.core.declarations.contract import Contract from slither.core.declarations.function_contract import FunctionContract from slither.core.expressions import expression +from slither.slithir.operations import Condition, Binary, BinaryType class Oracle: def __init__(self, _contract, _function, _interface_var, _line_of_call): @@ -31,7 +32,9 @@ class MyDetector(AbstractDetector): WIKI_RECOMMENDATION = 'asdsad' # https://github.com/crytic/slither/wiki/Python-API # def detect_stale_price(Function): - ORACLE_CALLS = ["latestRoundData", "getRoundData"] + ORACLE_CALLS = ["latestRoundData", "getRoundData"] # Calls i found which are generally used to get data from oracles, based on docs. Mostly it is lastestRoundData + + def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: """ Detects off-chain oracle contract and VAR @@ -61,15 +64,15 @@ def compare_chainlink_call(self, function: expression) -> bool: return False def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): - for functionCalled in function.external_calls_as_expressions: # Returns tuple (first contract, second function) + for functionCalled in function.external_calls_as_expressions: if self.compare_chainlink_call(functionCalled): return (True, str(functionCalled).split(".")[0], functionCalled.source_mapping.lines[0]) # The external call is in format contract.function, so we split it and get the contract name return (False, "", 0) def get_returned_variables_from_oracle(self, function: FunctionContract, oracle_call_line) -> list: returned_vars = [] - for var in function.variables_written: - if var.source_mapping.lines[0] == oracle_call_line: + for var in function.variables_written: # This iterates through list of variables which are written in function + if var.source_mapping.lines[0] == oracle_call_line: # We need to match line of var with line of oracle call returned_vars.append(var) return returned_vars @@ -80,41 +83,48 @@ def checks_if_vars_in_condition(self, oracle: Oracle, contract : Contract, func oracle.vars_in_condition = [] oracle.vars_not_in_condition = [] for var in oracle_vars: - if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): + if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): # These two functions check if within the function some var is in require/assert of in if statement oracle.vars_in_condition.append(var) else: oracle.vars_not_in_condition.append(var) - if len(oracle.vars_not_in_condition) > 0: + if len(oracle.vars_not_in_condition) > 0: # If there are some vars which are not in condition, we return false, to indicate that there is a problem return False return True def check_var_condition_match(self, var, node) -> bool: - for var2 in node.variables_read: + for var2 in node.variables_read: #This iterates through all variables which are read in node, what means that they are used in condition if var.name == var2.name: return True return False - def check_updatedAt(self, node: Node) -> bool: - statement = node.split(" ") - if "block.timestamp" not in statement: - return False - - return True - def direct_variable_check(node: Node, var) -> bool: - var_name = str(var.name) - if "updatedAt" in var_name: - return self.check_updatedAt(node) - + def check_condition(self,node) -> bool: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type == BinaryType.LESS or ir.type == BinaryType.LESS_EQUAL: # require(block.timestamp - updatedAt < b) + if node.contains_require_or_assert(): + return + elif node.contains_conditional(): # (if block.timestamp - updatedAt > b) then fail + return + elif ir.type == BinaryType.GREATER or ir.type == BinaryType.GREATER_EQUAL: + pass + + return False - return True def check_conditions_enough(self, oracle: Oracle) -> bool: checks_not_enough = [] for var in oracle.vars_in_condition: for node in oracle.function.nodes: if node.is_conditional() and self.check_var_condition_match(var, node): - if not self.direct_variable_check(node, var): - checks_not_enough.append(var) + # print(node.slithir_generation) + self.check_condition(node) + # for ir in node.irs: + # if isinstance(ir, Binary): + # print(ir.type) + # print(ir.variable_left) + # print(ir.variable_right) + # print("-----------") + return checks_not_enough From 33b1c1f6ab2b30847346d963c0e772c863378645 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 10 Nov 2023 09:56:24 +0100 Subject: [PATCH 08/87] add checking of internal calls --- slither/detectors/all_detectors.py | 2 +- slither/detectors/oracles/oracle.py | 155 +++++++++++++++++++--------- 2 files changed, 110 insertions(+), 47 deletions(-) diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index e8681696b6..413a66b8cc 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -100,4 +100,4 @@ # my detector -from .oracles.oracle import MyDetector \ No newline at end of file +from .oracles.oracle import MyDetector diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index b2b8c9ddf2..4a4549a1cb 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -2,38 +2,49 @@ from slither.core.declarations.contract import Contract from slither.core.declarations.function_contract import FunctionContract from slither.core.expressions import expression -from slither.slithir.operations import Condition, Binary, BinaryType +from slither.slithir.operations import Binary, BinaryType + class Oracle: def __init__(self, _contract, _function, _interface_var, _line_of_call): self.contract = _contract self.function = _function self.interface_var = _interface_var - self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] + self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] self.vars_in_condition = [] self.vars_not_in_condition = [] - self.possible_variables_names = ["price", "timestamp", "updatedAt", "answer", "roundID", "startedAt"] + self.possible_variables_names = [ + "price", + "timestamp", + "updatedAt", + "answer", + "roundID", + "startedAt", + ] + class MyDetector(AbstractDetector): """ Documentation """ - ARGUMENT = 'mydetector' # slither will launch the detector with slither.py --detect mydetector - HELP = 'Help printed by slither' + ARGUMENT = "mydetector" # slither will launch the detector with slither.py --detect mydetector + HELP = "Help printed by slither" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = 'RUN' + WIKI = "RUN" - WIKI_TITLE = 'asda' - WIKI_DESCRIPTION = 'asdsad' - WIKI_EXPLOIT_SCENARIO = 'asdsad' - WIKI_RECOMMENDATION = 'asdsad' + WIKI_TITLE = "asda" + WIKI_DESCRIPTION = "asdsad" + WIKI_EXPLOIT_SCENARIO = "asdsad" + WIKI_RECOMMENDATION = "asdsad" # https://github.com/crytic/slither/wiki/Python-API # def detect_stale_price(Function): - ORACLE_CALLS = ["latestRoundData", "getRoundData"] # Calls i found which are generally used to get data from oracles, based on docs. Mostly it is lastestRoundData - + ORACLE_CALLS = [ + "latestRoundData", + "getRoundData", + ] # Calls i found which are generally used to get data from oracles, based on docs. Mostly it is lastestRoundData def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: """ @@ -44,7 +55,7 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: for function in contract.functions: if function.is_constructor: continue - found_latest_round,name_interface, line = self.check_chainlink_call(function) + found_latest_round, name_interface, line = self.check_chainlink_call(function) for var in function.state_variables_read: # print(var.name) # print(var.type) @@ -53,7 +64,7 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: if (str(var.name) == str(name_interface)) and found_latest_round: oracles.append(Oracle(contract, function, var, line)) # if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): - # oracles.append(Oracle(contract, function, var)) + # oracles.append(Oracle(contract, function, var)) # print(f.nodes) return oracles @@ -62,51 +73,98 @@ def compare_chainlink_call(self, function: expression) -> bool: if call in str(function): return True return False - - def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): + + def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): for functionCalled in function.external_calls_as_expressions: if self.compare_chainlink_call(functionCalled): - return (True, str(functionCalled).split(".")[0], functionCalled.source_mapping.lines[0]) # The external call is in format contract.function, so we split it and get the contract name + return ( + True, + str(functionCalled).split(".", maxsplit=1)[0], + functionCalled.source_mapping.lines[0], + ) # The external call is in format contract.function, so we split it and get the contract name return (False, "", 0) - - def get_returned_variables_from_oracle(self, function: FunctionContract, oracle_call_line) -> list: + + def get_returned_variables_from_oracle( + self, function: FunctionContract, oracle_call_line + ) -> list: returned_vars = [] - for var in function.variables_written: # This iterates through list of variables which are written in function - if var.source_mapping.lines[0] == oracle_call_line: # We need to match line of var with line of oracle call + for ( + var + ) in ( + function.variables_written + ): # This iterates through list of variables which are written in function + if ( + var.source_mapping.lines[0] == oracle_call_line + ): # We need to match line of var with line of oracle call returned_vars.append(var) return returned_vars - def checks_if_vars_in_condition(self, oracle: Oracle, contract : Contract, function: FunctionContract, oracle_vars) -> bool: + def investigate_internal_call(self, function: FunctionContract, var) -> bool: + if function is None: + return False + + for functionCalled in function.internal_calls: + if isinstance(functionCalled, FunctionContract): + for local_var in functionCalled.variables_read: + if local_var.name == var.name: + if functionCalled.is_reading_in_conditional_node( + local_var + ) or functionCalled.is_reading_in_require_or_assert( + local_var + ): # These two functions check if within the function some var is in require/assert of in if statement + return True + if self.investigate_internal_call(functionCalled, var): + return True + return False + + def check_vars(self, oracle: Oracle, oracle_vars) -> bool: """ Detects if vars from oracles are in some condition """ - oracle.vars_in_condition = [] - oracle.vars_not_in_condition = [] + vars_in_condition = [] + vars_not_in_condition = [] + for var in oracle_vars: - if function.is_reading_in_conditional_node(var) or function.is_reading_in_require_or_assert(var): # These two functions check if within the function some var is in require/assert of in if statement - oracle.vars_in_condition.append(var) - else: - oracle.vars_not_in_condition.append(var) - if len(oracle.vars_not_in_condition) > 0: # If there are some vars which are not in condition, we return false, to indicate that there is a problem + if oracle.function.is_reading_in_conditional_node( + var + ) or oracle.function.is_reading_in_require_or_assert( + var + ): # These two functions check if within the function some var is in require/assert of in if statement + vars_in_condition.append(var) + else: + if self.investigate_internal_call(oracle.function, var): + vars_in_condition.append(var) + else: + vars_not_in_condition.append(var) + oracle.vars_in_condition = vars_in_condition + oracle.vars_not_in_condition = vars_not_in_condition + if ( + len(oracle.vars_not_in_condition) > 0 + ): # If there are some vars which are not in condition, we return false, to indicate that there is a problem return False return True - + def check_var_condition_match(self, var, node) -> bool: - for var2 in node.variables_read: #This iterates through all variables which are read in node, what means that they are used in condition + for ( + var2 + ) in ( + node.variables_read + ): # This iterates through all variables which are read in node, what means that they are used in condition if var.name == var2.name: return True return False - - def check_condition(self,node) -> bool: + def check_condition(self, node) -> bool: for ir in node.irs: if isinstance(ir, Binary): - if ir.type == BinaryType.LESS or ir.type == BinaryType.LESS_EQUAL: # require(block.timestamp - updatedAt < b) + if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): # require(block.timestamp - updatedAt < b) if node.contains_require_or_assert(): - return - elif node.contains_conditional(): # (if block.timestamp - updatedAt > b) then fail return - elif ir.type == BinaryType.GREATER or ir.type == BinaryType.GREATER_EQUAL: + elif ( + node.contains_conditional() + ): # (if block.timestamp - updatedAt > b) then fail + return + elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): pass return False @@ -126,20 +184,25 @@ def check_conditions_enough(self, oracle: Oracle) -> bool: # print("-----------") return checks_not_enough - - - + def _detect(self): info = [] oracles = self.chainlink_oracles(self.contracts) for oracle in oracles: - oracle_vars = self.get_returned_variables_from_oracle(oracle.function, oracle.line_of_call) - if(not self.checks_if_vars_in_condition(oracle, oracle.contract, oracle.function, oracle_vars)): - rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format(oracle.contract.name, oracle.function.name, oracle.interface_var, [var.name for var in oracle.vars_not_in_condition] ) + oracle_vars = self.get_returned_variables_from_oracle( + oracle.function, oracle.line_of_call + ) + if not self.check_vars(oracle, oracle_vars): + rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format( + oracle.contract.name, + oracle.function.name, + oracle.interface_var, + [var.name for var in oracle.vars_not_in_condition], + ) info.append(rep) - if (len(oracle.vars_in_condition) > 0): + if len(oracle.vars_in_condition) > 0: for var in self.check_conditions_enough(oracle): - info.append("Problem with {}",var.name) + info.append("Problem with {}", var.name) res = self.generate_result(info) - return [res] \ No newline at end of file + return [res] From 398f067c6a4883666166d1e8ca96b7923491b3d2 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 19 Nov 2023 10:10:40 +0100 Subject: [PATCH 09/87] Added basics of navive approach and now try to divide the files --- slither/detectors/oracles/oracle.py | 77 ++++++++++++++----- .../detectors/oracles/oracle_validate_data.py | 0 2 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 slither/detectors/oracles/oracle_validate_data.py diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 4a4549a1cb..7a92d94b55 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -3,7 +3,14 @@ from slither.core.declarations.function_contract import FunctionContract from slither.core.expressions import expression from slither.slithir.operations import Binary, BinaryType +from enum import Enum +class OracleVarType(Enum): + ROUNDID = 0 + ANSWER = 1 + STARTEDAT = 2 + UPDATEDAT = 3 + ANSWEREDINROUND = 4 class Oracle: def __init__(self, _contract, _function, _interface_var, _line_of_call): @@ -13,14 +20,14 @@ def __init__(self, _contract, _function, _interface_var, _line_of_call): self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] self.vars_in_condition = [] self.vars_not_in_condition = [] - self.possible_variables_names = [ - "price", - "timestamp", - "updatedAt", - "answer", - "roundID", - "startedAt", - ] + # self.possible_variables_names = [ + # "price", + # "timestamp", + # "updatedAt", + # "answer", + # "roundID", + # "startedAt", + # ] class MyDetector(AbstractDetector): @@ -67,6 +74,47 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: # oracles.append(Oracle(contract, function, var)) # print(f.nodes) return oracles + + def check_condition(self, node) -> bool: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): # require(block.timestamp - updatedAt < b) + if node.contains_require_or_assert(): + return + elif ( + node.contains_conditional() + ): # (if block.timestamp - updatedAt > b) then fail + return + elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): + pass + + return False + + def check_staleness(self, var, function: FunctionContract): + pass + def check_price(self, var, function: FunctionContract): + pass + + def naive_check(self, ordered_returned_vars): + checks = {} + for i in range(0,len(ordered_returned_vars)): + if i == OracleVarType.ROUNDID.value: + pass + elif i == OracleVarType.ANSWER.value: + pass + elif i == OracleVarType.STARTEDAT.value: + pass + elif i == OracleVarType.UPDATEDAT.value: + checks[3] = self.check_staleness(ordered_returned_vars[i]) + else: + pass + + # require( + # answeredInRound >= roundID, + # "Chainlink Price Stale" + # ); + # require(price > 0, "Chainlink Malfunction"); + # require(updateTime != 0, "Incomplete round"); def compare_chainlink_call(self, function: expression) -> bool: for call in self.ORACLE_CALLS: @@ -154,20 +202,7 @@ def check_var_condition_match(self, var, node) -> bool: return True return False - def check_condition(self, node) -> bool: - for ir in node.irs: - if isinstance(ir, Binary): - if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): # require(block.timestamp - updatedAt < b) - if node.contains_require_or_assert(): - return - elif ( - node.contains_conditional() - ): # (if block.timestamp - updatedAt > b) then fail - return - elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): - pass - return False def check_conditions_enough(self, oracle: Oracle) -> bool: checks_not_enough = [] diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py new file mode 100644 index 0000000000..e69de29bb2 From bbe73f9ad1e7053fc63fa0331d0cb187ee0b47c0 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 19 Nov 2023 12:49:18 +0100 Subject: [PATCH 10/87] Divided to two files and started with naive_check --- slither/detectors/all_detectors.py | 2 +- slither/detectors/oracles/oracle.py | 175 +++++++----------- .../detectors/oracles/oracle_validate_data.py | 112 +++++++++++ 3 files changed, 180 insertions(+), 109 deletions(-) diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 413a66b8cc..8552b74c71 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -100,4 +100,4 @@ # my detector -from .oracles.oracle import MyDetector +from .oracles.oracle_validate_data import OracleDataCheck diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 7a92d94b55..3a332d1505 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -18,6 +18,7 @@ def __init__(self, _contract, _function, _interface_var, _line_of_call): self.function = _function self.interface_var = _interface_var self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] + self.oracle_vars = [] self.vars_in_condition = [] self.vars_not_in_condition = [] # self.possible_variables_names = [ @@ -29,8 +30,12 @@ def __init__(self, _contract, _function, _interface_var, _line_of_call): # "startedAt", # ] +class VarInCondition(): + def __init__(self, _var, _nodes): + self.var = _var + self.node = _nodes -class MyDetector(AbstractDetector): +class OracleDetector(AbstractDetector): """ Documentation """ @@ -75,47 +80,6 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: # print(f.nodes) return oracles - def check_condition(self, node) -> bool: - for ir in node.irs: - if isinstance(ir, Binary): - if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): # require(block.timestamp - updatedAt < b) - if node.contains_require_or_assert(): - return - elif ( - node.contains_conditional() - ): # (if block.timestamp - updatedAt > b) then fail - return - elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): - pass - - return False - - def check_staleness(self, var, function: FunctionContract): - pass - def check_price(self, var, function: FunctionContract): - pass - - def naive_check(self, ordered_returned_vars): - checks = {} - for i in range(0,len(ordered_returned_vars)): - if i == OracleVarType.ROUNDID.value: - pass - elif i == OracleVarType.ANSWER.value: - pass - elif i == OracleVarType.STARTEDAT.value: - pass - elif i == OracleVarType.UPDATEDAT.value: - checks[3] = self.check_staleness(ordered_returned_vars[i]) - else: - pass - - # require( - # answeredInRound >= roundID, - # "Chainlink Price Stale" - # ); - # require(price > 0, "Chainlink Malfunction"); - # require(updateTime != 0, "Incomplete round"); - def compare_chainlink_call(self, function: expression) -> bool: for call in self.ORACLE_CALLS: if call in str(function): @@ -146,26 +110,26 @@ def get_returned_variables_from_oracle( ): # We need to match line of var with line of oracle call returned_vars.append(var) return returned_vars - - def investigate_internal_call(self, function: FunctionContract, var) -> bool: - if function is None: - return False - - for functionCalled in function.internal_calls: - if isinstance(functionCalled, FunctionContract): - for local_var in functionCalled.variables_read: - if local_var.name == var.name: - if functionCalled.is_reading_in_conditional_node( - local_var - ) or functionCalled.is_reading_in_require_or_assert( - local_var - ): # These two functions check if within the function some var is in require/assert of in if statement - return True - if self.investigate_internal_call(functionCalled, var): - return True + + def check_var_condition_match(self, var, node) -> bool: + for ( + var2 + ) in ( + node.variables_read + ): # This iterates through all variables which are read in node, what means that they are used in condition + if var.name == var2.name: + return True return False - def check_vars(self, oracle: Oracle, oracle_vars) -> bool: + + def map_condition_to_var(self, var, function: FunctionContract): + nodes = [] + for node in function.nodes: + if node.is_conditional() and self.check_var_condition_match(var, node): + nodes.append(node) + return nodes + + def vars_in_conditions(self, oracle: Oracle, oracle_vars) -> bool: """ Detects if vars from oracles are in some condition """ @@ -178,66 +142,61 @@ def check_vars(self, oracle: Oracle, oracle_vars) -> bool: ) or oracle.function.is_reading_in_require_or_assert( var ): # These two functions check if within the function some var is in require/assert of in if statement - vars_in_condition.append(var) + nodes = self.map_condition_to_var(var, oracle.function) + # if len(nodes) > 0: + # vars_in_condition.append(VarInCondition(var, nodes)) else: - if self.investigate_internal_call(oracle.function, var): + if self.investigate_internal_call(oracle.function, var): #TODO i need to chnge this to check for taint analysis somehow vars_in_condition.append(var) else: vars_not_in_condition.append(var) oracle.vars_in_condition = vars_in_condition oracle.vars_not_in_condition = vars_not_in_condition - if ( - len(oracle.vars_not_in_condition) > 0 - ): # If there are some vars which are not in condition, we return false, to indicate that there is a problem - return False - return True - def check_var_condition_match(self, var, node) -> bool: - for ( - var2 - ) in ( - node.variables_read - ): # This iterates through all variables which are read in node, what means that they are used in condition - if var.name == var2.name: - return True - return False + def investigate_internal_call(self, function: FunctionContract, var) -> bool: + if function is None: + return False - def check_conditions_enough(self, oracle: Oracle) -> bool: - checks_not_enough = [] - for var in oracle.vars_in_condition: - for node in oracle.function.nodes: - if node.is_conditional() and self.check_var_condition_match(var, node): - # print(node.slithir_generation) - self.check_condition(node) - # for ir in node.irs: - # if isinstance(ir, Binary): - # print(ir.type) - # print(ir.variable_left) - # print(ir.variable_right) - # print("-----------") - - return checks_not_enough + for functionCalled in function.internal_calls: + if isinstance(functionCalled, FunctionContract): + for local_var in functionCalled.variables_read: + if local_var.name == var.name: + if functionCalled.is_reading_in_conditional_node( + local_var + ) or functionCalled.is_reading_in_require_or_assert( + local_var + ): # These two functions check if within the function some var is in require/assert of in if statement + return True + if self.investigate_internal_call(functionCalled, var): + return True + return False def _detect(self): info = [] - oracles = self.chainlink_oracles(self.contracts) - for oracle in oracles: - oracle_vars = self.get_returned_variables_from_oracle( + self.oracles = self.chainlink_oracles(self.contracts) + for oracle in self.oracles: + oracle.oracle_vars = self.get_returned_variables_from_oracle( oracle.function, oracle.line_of_call ) - if not self.check_vars(oracle, oracle_vars): - rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format( - oracle.contract.name, - oracle.function.name, - oracle.interface_var, - [var.name for var in oracle.vars_not_in_condition], - ) - info.append(rep) - if len(oracle.vars_in_condition) > 0: - for var in self.check_conditions_enough(oracle): - info.append("Problem with {}", var.name) - res = self.generate_result(info) - - return [res] + self.vars_in_conditions(oracle, oracle.oracle_vars) + + # for oracle in oracles: + # oracle_vars = self.get_returned_variables_from_oracle( + # oracle.function, oracle.line_of_call + # ) + # if not self.check_vars(oracle, oracle_vars): + # rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format( + # oracle.contract.name, + # oracle.function.name, + # oracle.interface_var, + # [var.name for var in oracle.vars_not_in_condition], + # ) + # info.append(rep) + # if len(oracle.vars_in_condition) > 0: + # for var in self.check_conditions_enough(oracle): + # info.append("Problem with {}", var.name) + # res = self.generate_result(info) + + return [] diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index e69de29bb2..3fc4cbbd34 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -0,0 +1,112 @@ +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.core.expressions import expression +from slither.slithir.operations import Binary, BinaryType +from enum import Enum +from slither.detectors.oracles.oracle import OracleDetector, OracleVarType, Oracle +from slither.detectors.operations.unused_return_values import UnusedReturnValues + + +class OracleDataCheck(OracleDetector): + """ + Documentation + """ + + ARGUMENT = "oracle" # slither will launch the detector with slither.py --detect mydetector + HELP = "Help printed by slither" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "RUN" + + WIKI_TITLE = "asda" + WIKI_DESCRIPTION = "asdsad" + WIKI_EXPLOIT_SCENARIO = "asdsad" + WIKI_RECOMMENDATION = "asdsad" + + + + + def check_conditions_enough(self, oracle: Oracle) -> bool: + checks_not_enough = [] + for var in oracle.vars_in_condition: + for node in oracle.function.nodes: + if node.is_conditional() and self.check_var_condition_match(var, node): + # print(node.slithir_generation) + self.check_condition(node) + # for ir in node.irs: + # if isinstance(ir, Binary): + # print(ir.type) + # print(ir.variable_left) + # print(ir.variable_right) + # print("-----------") + + return checks_not_enough + + def check_condition(self, node) -> bool: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): # require(block.timestamp - updatedAt < b) + if node.contains_require_or_assert(): + return + elif ( + node.contains_conditional() + ): # (if block.timestamp - updatedAt > b) then fail + return + elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): + pass + + return False + def check_staleness(self, var, function: FunctionContract): + pass + def check_price(self, var, function: FunctionContract): + pass + + def naive_check(self, ordered_returned_vars): + checks = {} + for i in range(0,len(ordered_returned_vars)): + if i == OracleVarType.ROUNDID.value: + pass + elif i == OracleVarType.ANSWER.value: + pass + elif i == OracleVarType.STARTEDAT.value: + pass + elif i == OracleVarType.UPDATEDAT.value: + checks[3] = self.check_staleness(ordered_returned_vars[i]) + else: + pass + + # require( + # answeredInRound >= roundID, + # "Chainlink Price Stale" + # ); + # require(price > 0, "Chainlink Malfunction"); + # require(updateTime != 0, "Incomplete round"); + + def process_checked_vars(self): + result = [] + for oracle in self.oracles: + return_vars_num = len(oracle.oracle_vars) + if return_vars_num >=4: + self.naive_check(oracle.oracle_vars) + def process_not_checked_vars(self): + result = [] + for oracle in self.oracles: + if len(oracle.vars_not_in_condition) > 0: + result.append("In contract `{}` a function `{}` uses oracle `{}` where the values of vars {} are not checked. This can potentially lead to a problem! \n".format( + oracle.contract.name, + oracle.function.name, + oracle.interface_var, + [var.name for var in oracle.vars_not_in_condition], + )) + return result + + + def _detect(self): + results = [] + super()._detect() + not_checked_vars = self.process_not_checked_vars() + res = self.generate_result(not_checked_vars) + results.append(res) + return results \ No newline at end of file From 6fded76d742cbaf329461229b2dfbb8eee539298 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 19 Nov 2023 14:18:07 +0100 Subject: [PATCH 11/87] Reoder values based on the nodes mapping and check_price --- slither/detectors/oracles/oracle.py | 53 ++++++++--------- .../detectors/oracles/oracle_validate_data.py | 58 +++++++++++++------ 2 files changed, 63 insertions(+), 48 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 3a332d1505..1fbd297a52 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -1,16 +1,11 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.core.declarations.contract import Contract +from slither.core.cfg.node import NodeType from slither.core.declarations.function_contract import FunctionContract from slither.core.expressions import expression from slither.slithir.operations import Binary, BinaryType from enum import Enum -class OracleVarType(Enum): - ROUNDID = 0 - ANSWER = 1 - STARTEDAT = 2 - UPDATEDAT = 3 - ANSWEREDINROUND = 4 class Oracle: def __init__(self, _contract, _function, _interface_var, _line_of_call): @@ -33,24 +28,10 @@ def __init__(self, _contract, _function, _interface_var, _line_of_call): class VarInCondition(): def __init__(self, _var, _nodes): self.var = _var - self.node = _nodes + self.nodes = _nodes class OracleDetector(AbstractDetector): - """ - Documentation - """ - - ARGUMENT = "mydetector" # slither will launch the detector with slither.py --detect mydetector - HELP = "Help printed by slither" - IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.HIGH - - WIKI = "RUN" - - WIKI_TITLE = "asda" - WIKI_DESCRIPTION = "asdsad" - WIKI_EXPLOIT_SCENARIO = "asdsad" - WIKI_RECOMMENDATION = "asdsad" + # https://github.com/crytic/slither/wiki/Python-API # def detect_stale_price(Function): ORACLE_CALLS = [ @@ -99,7 +80,8 @@ def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): def get_returned_variables_from_oracle( self, function: FunctionContract, oracle_call_line ) -> list: - returned_vars = [] + written_vars = [] + ordered_vars = [] for ( var ) in ( @@ -108,8 +90,15 @@ def get_returned_variables_from_oracle( if ( var.source_mapping.lines[0] == oracle_call_line ): # We need to match line of var with line of oracle call - returned_vars.append(var) - return returned_vars + written_vars.append(var) + + for node in function.nodes: + for var in written_vars: + if node.type is NodeType.VARIABLE and node.variable_declaration == var: + if ordered_vars.count(var) == 0: + ordered_vars.append(var) + break + return ordered_vars def check_var_condition_match(self, var, node) -> bool: for ( @@ -129,29 +118,33 @@ def map_condition_to_var(self, var, function: FunctionContract): nodes.append(node) return nodes - def vars_in_conditions(self, oracle: Oracle, oracle_vars) -> bool: + def vars_in_conditions(self, oracle: Oracle) -> bool: """ Detects if vars from oracles are in some condition """ vars_in_condition = [] vars_not_in_condition = [] + oracle_vars = [] - for var in oracle_vars: + for var in oracle.oracle_vars: if oracle.function.is_reading_in_conditional_node( var ) or oracle.function.is_reading_in_require_or_assert( var ): # These two functions check if within the function some var is in require/assert of in if statement nodes = self.map_condition_to_var(var, oracle.function) - # if len(nodes) > 0: - # vars_in_condition.append(VarInCondition(var, nodes)) + if len(nodes) > 0: + vars_in_condition.append(VarInCondition(var, nodes)) + oracle_vars.append(VarInCondition(var, nodes)) else: + oracle_vars.append(var) if self.investigate_internal_call(oracle.function, var): #TODO i need to chnge this to check for taint analysis somehow vars_in_condition.append(var) else: vars_not_in_condition.append(var) oracle.vars_in_condition = vars_in_condition oracle.vars_not_in_condition = vars_not_in_condition + oracle.oracle_vars = oracle_vars @@ -180,7 +173,7 @@ def _detect(self): oracle.oracle_vars = self.get_returned_variables_from_oracle( oracle.function, oracle.line_of_call ) - self.vars_in_conditions(oracle, oracle.oracle_vars) + self.vars_in_conditions(oracle) # for oracle in oracles: # oracle_vars = self.get_returned_variables_from_oracle( diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 3fc4cbbd34..eba2eb687f 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -4,10 +4,17 @@ from slither.core.expressions import expression from slither.slithir.operations import Binary, BinaryType from enum import Enum -from slither.detectors.oracles.oracle import OracleDetector, OracleVarType, Oracle +from slither.detectors.oracles.oracle import OracleDetector, Oracle, VarInCondition from slither.detectors.operations.unused_return_values import UnusedReturnValues +class OracleVarType(Enum): + ROUNDID = 0 + ANSWER = 1 + STARTEDAT = 2 + UPDATEDAT = 3 + ANSWEREDINROUND = 4 + class OracleDataCheck(OracleDetector): """ Documentation @@ -44,24 +51,37 @@ def check_conditions_enough(self, oracle: Oracle) -> bool: return checks_not_enough - def check_condition(self, node) -> bool: - for ir in node.irs: - if isinstance(ir, Binary): - if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): # require(block.timestamp - updatedAt < b) - if node.contains_require_or_assert(): - return - elif ( - node.contains_conditional() - ): # (if block.timestamp - updatedAt > b) then fail - return - elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): - pass return False - def check_staleness(self, var, function: FunctionContract): - pass - def check_price(self, var, function: FunctionContract): - pass + def check_staleness(self, var: VarInCondition): + for node in var.nodes: + str_node = str(node) + if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b + return True + + + # for ir in node.irs: + # if isinstance(ir, Binary): + # if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): + # if node.contains_require_or_assert(): + # pass + # elif node.contains_conditional(): + # pass + # elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): + # pass + return False + + def check_price(self, var: VarInCondition): + for node in var.nodes: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type in (BinaryType.GREATER, BinaryType.NOT_EQUAL): + print(ir.variable_right) + if (ir.variable_right.value == 0): + return True + + + return False def naive_check(self, ordered_returned_vars): checks = {} @@ -69,7 +89,8 @@ def naive_check(self, ordered_returned_vars): if i == OracleVarType.ROUNDID.value: pass elif i == OracleVarType.ANSWER.value: - pass + checks[1] = self.check_price(ordered_returned_vars[i]) + print(checks[1]) elif i == OracleVarType.STARTEDAT.value: pass elif i == OracleVarType.UPDATEDAT.value: @@ -107,6 +128,7 @@ def _detect(self): results = [] super()._detect() not_checked_vars = self.process_not_checked_vars() + self.process_checked_vars() res = self.generate_result(not_checked_vars) results.append(res) return results \ No newline at end of file From 72eda7f7e0272700ac8ea8efba5ec16fb4226ece Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 19 Nov 2023 14:38:00 +0100 Subject: [PATCH 12/87] Add text output for price and timestamp --- .../detectors/oracles/oracle_validate_data.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index eba2eb687f..140c0c91f4 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -76,7 +76,6 @@ def check_price(self, var: VarInCondition): for ir in node.irs: if isinstance(ir, Binary): if ir.type in (BinaryType.GREATER, BinaryType.NOT_EQUAL): - print(ir.variable_right) if (ir.variable_right.value == 0): return True @@ -85,18 +84,28 @@ def check_price(self, var: VarInCondition): def naive_check(self, ordered_returned_vars): checks = {} + for i in range(0,5): + checks[i] = False for i in range(0,len(ordered_returned_vars)): if i == OracleVarType.ROUNDID.value: pass elif i == OracleVarType.ANSWER.value: checks[1] = self.check_price(ordered_returned_vars[i]) - print(checks[1]) elif i == OracleVarType.STARTEDAT.value: pass elif i == OracleVarType.UPDATEDAT.value: checks[3] = self.check_staleness(ordered_returned_vars[i]) else: pass + return checks + + def process_checks(self,checks): + result = [] + if checks[1] == False: + result.append("Price is not checked well!\n") + if checks[3] == False: + result.append("The price could be probably stale\n") + return result # require( # answeredInRound >= roundID, @@ -106,11 +115,11 @@ def naive_check(self, ordered_returned_vars): # require(updateTime != 0, "Incomplete round"); def process_checked_vars(self): - result = [] for oracle in self.oracles: return_vars_num = len(oracle.oracle_vars) if return_vars_num >=4: - self.naive_check(oracle.oracle_vars) + checks = self.naive_check(oracle.oracle_vars) + return self.process_checks(checks) def process_not_checked_vars(self): result = [] for oracle in self.oracles: @@ -131,4 +140,6 @@ def _detect(self): self.process_checked_vars() res = self.generate_result(not_checked_vars) results.append(res) + res = self.generate_result(self.process_checked_vars()) + results.append(res) return results \ No newline at end of file From e964ba359e1996a0f773dc1ca067e9be42b253de Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 19 Nov 2023 15:24:34 +0100 Subject: [PATCH 13/87] Improve checks part for price --- .../detectors/oracles/oracle_validate_data.py | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 140c0c91f4..ae07ad22f1 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -34,25 +34,6 @@ class OracleDataCheck(OracleDetector): - - def check_conditions_enough(self, oracle: Oracle) -> bool: - checks_not_enough = [] - for var in oracle.vars_in_condition: - for node in oracle.function.nodes: - if node.is_conditional() and self.check_var_condition_match(var, node): - # print(node.slithir_generation) - self.check_condition(node) - # for ir in node.irs: - # if isinstance(ir, Binary): - # print(ir.type) - # print(ir.variable_left) - # print(ir.variable_right) - # print("-----------") - - return checks_not_enough - - - return False def check_staleness(self, var: VarInCondition): for node in var.nodes: str_node = str(node) @@ -75,9 +56,15 @@ def check_price(self, var: VarInCondition): for node in var.nodes: for ir in node.irs: if isinstance(ir, Binary): - if ir.type in (BinaryType.GREATER, BinaryType.NOT_EQUAL): + if ir.type in (BinaryType.GREATER): if (ir.variable_right.value == 0): return True + elif ir.type in (BinaryType.LESS): + if (ir.variable_left.value == 0): + return True + elif ir.type in (BinaryType.NOT_EQUAL): + if (ir.variable_left.value == 0 or ir.variable_right.value == 0): + return True return False From e98f03efa10208ca74853f43d28901c40a5fda3c Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 19 Nov 2023 17:04:12 +0100 Subject: [PATCH 14/87] Naive check work, however i need to find a way how to represent not existing values --- slither/detectors/oracles/oracle.py | 1 - .../detectors/oracles/oracle_validate_data.py | 47 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 1fbd297a52..fce84f3539 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -91,7 +91,6 @@ def get_returned_variables_from_oracle( var.source_mapping.lines[0] == oracle_call_line ): # We need to match line of var with line of oracle call written_vars.append(var) - for node in function.nodes: for var in written_vars: if node.type is NodeType.VARIABLE and node.variable_declaration == var: diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index ae07ad22f1..4c1a892188 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -37,6 +37,7 @@ class OracleDataCheck(OracleDetector): def check_staleness(self, var: VarInCondition): for node in var.nodes: str_node = str(node) + print(str_node) if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b return True @@ -52,17 +53,31 @@ def check_staleness(self, var: VarInCondition): # pass return False - def check_price(self, var: VarInCondition): + + def check_RoundId(self, var: VarInCondition, var2: VarInCondition): # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf + for node in var.nodes: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): + if ir.variable_right == var.var and ir.variable_left == var2.var: + return True + elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): + if (ir.variable_right == var2.var and ir.variable_left == var.var): + return True + + return False + + def check_price(self, var: VarInCondition): #TODO I need to divie require or IF for node in var.nodes: for ir in node.irs: if isinstance(ir, Binary): - if ir.type in (BinaryType.GREATER): + if ir.type is (BinaryType.GREATER): if (ir.variable_right.value == 0): return True - elif ir.type in (BinaryType.LESS): + elif ir.type is (BinaryType.LESS): if (ir.variable_left.value == 0): return True - elif ir.type in (BinaryType.NOT_EQUAL): + elif ir.type is (BinaryType.NOT_EQUAL): if (ir.variable_left.value == 0 or ir.variable_right.value == 0): return True @@ -70,28 +85,30 @@ def check_price(self, var: VarInCondition): return False def naive_check(self, ordered_returned_vars): + print([var.var.name for var in ordered_returned_vars]) checks = {} for i in range(0,5): checks[i] = False for i in range(0,len(ordered_returned_vars)): if i == OracleVarType.ROUNDID.value: - pass + checks[0] = self.check_RoundId(ordered_returned_vars[i], ordered_returned_vars[-1]) elif i == OracleVarType.ANSWER.value: checks[1] = self.check_price(ordered_returned_vars[i]) - elif i == OracleVarType.STARTEDAT.value: - pass elif i == OracleVarType.UPDATEDAT.value: checks[3] = self.check_staleness(ordered_returned_vars[i]) - else: - pass + print(checks[3]) + return checks def process_checks(self,checks): result = [] - if checks[1] == False: - result.append("Price is not checked well!\n") - if checks[3] == False: - result.append("The price could be probably stale\n") + for check in checks: + if check[1] == False: + result.append("Price is not checked well!\n") + if check[3] == False: + result.append("The price could be probably stale!\n") + if check[0] == False: + result.append("RoundID is not checked well!\n") return result # require( @@ -102,11 +119,13 @@ def process_checks(self,checks): # require(updateTime != 0, "Incomplete round"); def process_checked_vars(self): + checks = [] for oracle in self.oracles: return_vars_num = len(oracle.oracle_vars) if return_vars_num >=4: - checks = self.naive_check(oracle.oracle_vars) + checks.append(self.naive_check(oracle.oracle_vars)) return self.process_checks(checks) + def process_not_checked_vars(self): result = [] for oracle in self.oracles: From c1532d0d9cb60d3e890af2d02b2d5b6bc4b23c1a Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 26 Nov 2023 09:31:29 +0100 Subject: [PATCH 15/87] Slot0 simple check --- slither/detectors/oracles/oracle_slot0.py | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 slither/detectors/oracles/oracle_slot0.py diff --git a/slither/detectors/oracles/oracle_slot0.py b/slither/detectors/oracles/oracle_slot0.py new file mode 100644 index 0000000000..a9a233ad74 --- /dev/null +++ b/slither/detectors/oracles/oracle_slot0.py @@ -0,0 +1,52 @@ +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.cfg.node import NodeType +from slither.core.declarations.function_contract import FunctionContract +from slither.detectors.oracles.oracle import Oracle + + +class OracleSlot0(AbstractDetector): + """ + Documentation + """ + + ARGUMENT = "slot0" # slither will launch the detector with slither.py --detect mydetector + HELP = "Help printed by slither" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "RUN" + + WIKI_TITLE = "asda" + WIKI_DESCRIPTION = "Slot0 is vulnerable to price manipulation as it gets price at the current moment. TWAP should be used instead." + WIKI_EXPLOIT_SCENARIO = "asdsad" + WIKI_RECOMMENDATION = "asdsad" + + oracles = [] + + def detect_slot0(self, contracts: Contract): + """ + Detects off-chain oracle contract and VAR + """ + self.oracles = [] + for contract in contracts: + for function in contract.functions: + if function.is_constructor: + continue + for functionCalled in function.external_calls_as_expressions: + if "slot0" in str(functionCalled): + self.oracles.append(Oracle(contract, function, str(functionCalled).split(".", maxsplit=1)[0], functionCalled.source_mapping.lines[0])) + + + def _detect(self): + results = [] + self.detect_slot0(self.contracts) + # for oracle in self.oracles: + # print(oracle.contract.name, oracle.function.name) + for oracle in self.oracles: + results.append("Slot0 usage found in contract " + oracle.contract.name + " in function " + oracle.function.name) + results.append("\n") + res = self.generate_result(results) + output = [] + output.append(res) + return output \ No newline at end of file From 509ebaf1e6c00d096f9790de82cda3bb27e92ae7 Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 29 Nov 2023 10:30:35 +0100 Subject: [PATCH 16/87] Before experimenting with unused impl detector --- slither/detectors/oracles/oracle.py | 70 ++++++++---- .../detectors/oracles/oracle_validate_data.py | 102 +++++++++++++----- 2 files changed, 123 insertions(+), 49 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index fce84f3539..12fd231e2f 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -4,14 +4,23 @@ from slither.core.declarations.function_contract import FunctionContract from slither.core.expressions import expression from slither.slithir.operations import Binary, BinaryType +from slither.slithir.operations import HighLevelCall from enum import Enum +# For debugging +# import debugpy +# # 5678 is the default attach port in the VS Code debug configurations. Unless a host and port are specified, host defaults to 127.0.0.1 +# debugpy.listen(5678) +# print("Waiting for debugger attach") +# debugpy.wait_for_client() +# debugpy.breakpoint() +# print('break on this line') class Oracle: - def __init__(self, _contract, _function, _interface_var, _line_of_call): + def __init__(self, _contract, _function, _ir_rep, _line_of_call): self.contract = _contract self.function = _function - self.interface_var = _interface_var + self.ir = _ir_rep self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] self.oracle_vars = [] self.vars_in_condition = [] @@ -48,33 +57,52 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: for function in contract.functions: if function.is_constructor: continue - found_latest_round, name_interface, line = self.check_chainlink_call(function) - for var in function.state_variables_read: - # print(var.name) - # print(var.type) - # print(type(var.type)) - # print("------------------") - if (str(var.name) == str(name_interface)) and found_latest_round: - oracles.append(Oracle(contract, function, var, line)) - # if (str(var.type) == "AggregatorV3Interface") and self.check_latestRoundData(function): - # oracles.append(Oracle(contract, function, var)) - # print(f.nodes) + found_latest_round, ir_rep, line = self.check_chainlink_call(function) + if found_latest_round: + oracles.append(Oracle(contract, function, ir_rep, line)) return oracles - def compare_chainlink_call(self, function: expression) -> bool: + def compare_chainlink_call(self, function) -> bool: for call in self.ORACLE_CALLS: if call in str(function): return True return False def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): - for functionCalled in function.external_calls_as_expressions: - if self.compare_chainlink_call(functionCalled): - return ( - True, - str(functionCalled).split(".", maxsplit=1)[0], - functionCalled.source_mapping.lines[0], - ) # The external call is in format contract.function, so we split it and get the contract name + values_returned = [] + nodes_origin = {} + for node in function.nodes: + for ir in node.irs: + if isinstance(ir,HighLevelCall): + if(self.compare_chainlink_call(ir.function_name)): + if ir.lvalue and not isinstance(ir.lvalue, StateVariable): + values_returned.append((ir.lvalue, None)) + nodes_origin[ir.lvalue] = ir + if isinstance(ir.lvalue, TupleVariable): + # we iterate the number of elements the tuple has + # and add a (variable, index) in values_returned for each of them + for index in range(len(ir.lvalue.type)): + values_returned.append((ir.lvalue, index)) + for read in ir.read: + remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) + if remove in values_returned: + # this is needed to remove the tuple variable when the first time one of its element is used + if remove[1] is not None and (remove[0], None) in values_returned: + values_returned.remove((remove[0], None)) + values_returned.remove(remove) + return (True, ir, node.source_mapping.lines[0]) + + # for functionCalled in function.external_calls_as_expressions: + # if self.compare_chainlink_call(functionCalled): + # # debugpy.breakpoint() + # # print(functionCalled.source_mapping.filename.absolute) # Absolute path to file + # # print("Mapping high level call", functionCalled) + + # return ( + # True, + # str(functionCalled).split(".", maxsplit=1)[0], + # functionCalled.source_mapping.lines[0], + # ) # The external call is in format contract.function, so we split it and get the contract name return (False, "", 0) def get_returned_variables_from_oracle( diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 4c1a892188..03633de957 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -6,6 +6,8 @@ from enum import Enum from slither.detectors.oracles.oracle import OracleDetector, Oracle, VarInCondition from slither.detectors.operations.unused_return_values import UnusedReturnValues +from slither.core.cfg.node import NodeType +from slither.core.variables.state_variable import StateVariable class OracleVarType(Enum): @@ -37,7 +39,7 @@ class OracleDataCheck(OracleDetector): def check_staleness(self, var: VarInCondition): for node in var.nodes: str_node = str(node) - print(str_node) + # print(str_node) if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b return True @@ -68,7 +70,13 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition): # https://so return False def check_price(self, var: VarInCondition): #TODO I need to divie require or IF - for node in var.nodes: + look_for_revert = False + for node in var.nodes: #TODO testing + if look_for_revert: + if node.type == NodeType.THROW: + return True + else: + look_for_revert = False for ir in node.irs: if isinstance(ir, Binary): if ir.type is (BinaryType.GREATER): @@ -78,27 +86,68 @@ def check_price(self, var: VarInCondition): #TODO I need to divie require or IF if (ir.variable_left.value == 0): return True elif ir.type is (BinaryType.NOT_EQUAL): - if (ir.variable_left.value == 0 or ir.variable_right.value == 0): + if (ir.variable_right.value == 0): return True + else: + look_for_revert = True return False + + def generate_naive_order(self): + vars_order = {} + vars_order[OracleVarType.ROUNDID] = None + vars_order[OracleVarType.ANSWER] = None + vars_order[OracleVarType.STARTEDAT] = None + vars_order[OracleVarType.UPDATEDAT] = None + vars_order[OracleVarType.ANSWEREDINROUND] = None + return vars_order + + def find_which_vars_are_used(self, oracle: Oracle): + vars_order = self.generate_naive_order() + returned_types_of_high_call = oracle.ir.function.return_type + for i in oracle.ir.used: + if isinstance(i, StateVariable): + print(i.name) + if isinstance(i, Tup): + print(i.name) + # print() + types_of_used_vars = [] + for var in oracle.oracle_vars: + if type(var) == VarInCondition: + types_of_used_vars.append(var.var.type) + else: + types_of_used_vars.append(var.type) + for i in range(0,len(types_of_used_vars)): + if types_of_used_vars[i].name == "uint80" and i == 0: + vars_order[OracleVarType.ROUNDID] = oracle.oracle_vars[i] + elif types_of_used_vars[i].name == "int256": + vars_order[OracleVarType.ANSWER] = oracle.oracle_vars[i] + elif types_of_used_vars[i].name == "uint80" and i == len(types_of_used_vars) - 1: + vars_order[OracleVarType.ANSWEREDINROUND] = oracle.oracle_vars[i] + + + - def naive_check(self, ordered_returned_vars): - print([var.var.name for var in ordered_returned_vars]) - checks = {} - for i in range(0,5): - checks[i] = False - for i in range(0,len(ordered_returned_vars)): - if i == OracleVarType.ROUNDID.value: - checks[0] = self.check_RoundId(ordered_returned_vars[i], ordered_returned_vars[-1]) - elif i == OracleVarType.ANSWER.value: - checks[1] = self.check_price(ordered_returned_vars[i]) - elif i == OracleVarType.UPDATEDAT.value: - checks[3] = self.check_staleness(ordered_returned_vars[i]) - print(checks[3]) - - return checks + + def naive_check(self, oracle: Oracle): + self.find_which_vars_are_used(oracle) + # print([var.var.name for var in ordered_returned_vars]) + # checks = {} + # for i in range(0,5): + # checks[i] = False + # for var in oracle.vars_not_in_condition: + + # for i in range(0,len(ordered_returned_vars)): + # if i == OracleVarType.ROUNDID.value: + # checks[0] = self.check_RoundId(ordered_returned_vars[i], ordered_returned_vars[-1]) + # elif i == OracleVarType.ANSWER.value: + # checks[1] = self.check_price(ordered_returned_vars[i]) + # elif i == OracleVarType.UPDATEDAT.value: + # checks[3] = self.check_staleness(ordered_returned_vars[i]) + # print(checks[3]) + + # return checks def process_checks(self,checks): result = [] @@ -121,19 +170,16 @@ def process_checks(self,checks): def process_checked_vars(self): checks = [] for oracle in self.oracles: - return_vars_num = len(oracle.oracle_vars) - if return_vars_num >=4: - checks.append(self.naive_check(oracle.oracle_vars)) - return self.process_checks(checks) + checks.append(self.naive_check(oracle)) + # return self.process_checks(checks) def process_not_checked_vars(self): result = [] for oracle in self.oracles: if len(oracle.vars_not_in_condition) > 0: - result.append("In contract `{}` a function `{}` uses oracle `{}` where the values of vars {} are not checked. This can potentially lead to a problem! \n".format( + result.append("In contract `{}` a function `{}` uses oracle where the values of vars {} are not checked. This can potentially lead to a problem! \n".format( oracle.contract.name, oracle.function.name, - oracle.interface_var, [var.name for var in oracle.vars_not_in_condition], )) return result @@ -143,9 +189,9 @@ def _detect(self): results = [] super()._detect() not_checked_vars = self.process_not_checked_vars() - self.process_checked_vars() res = self.generate_result(not_checked_vars) - results.append(res) - res = self.generate_result(self.process_checked_vars()) - results.append(res) + self.process_checked_vars() + # results.append(res) + # res = self.generate_result(self.process_checked_vars()) + # results.append(res) return results \ No newline at end of file From 3f77626dfde85c4ca04e48c27881c7d5c0da918b Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 29 Nov 2023 13:51:09 +0100 Subject: [PATCH 17/87] Found used order --- slither/detectors/oracles/oracle.py | 19 +-- .../detectors/oracles/oracle_validate_data.py | 109 ++++++++++++++++-- 2 files changed, 102 insertions(+), 26 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 12fd231e2f..12583e9d8f 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -69,28 +69,11 @@ def compare_chainlink_call(self, function) -> bool: return False def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): - values_returned = [] - nodes_origin = {} for node in function.nodes: for ir in node.irs: if isinstance(ir,HighLevelCall): if(self.compare_chainlink_call(ir.function_name)): - if ir.lvalue and not isinstance(ir.lvalue, StateVariable): - values_returned.append((ir.lvalue, None)) - nodes_origin[ir.lvalue] = ir - if isinstance(ir.lvalue, TupleVariable): - # we iterate the number of elements the tuple has - # and add a (variable, index) in values_returned for each of them - for index in range(len(ir.lvalue.type)): - values_returned.append((ir.lvalue, index)) - for read in ir.read: - remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) - if remove in values_returned: - # this is needed to remove the tuple variable when the first time one of its element is used - if remove[1] is not None and (remove[0], None) in values_returned: - values_returned.remove((remove[0], None)) - values_returned.remove(remove) - return (True, ir, node.source_mapping.lines[0]) + return (True, ir, node.source_mapping.lines[0]) # for functionCalled in function.external_calls_as_expressions: # if self.compare_chainlink_call(functionCalled): diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 03633de957..4558c00644 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -9,6 +9,21 @@ from slither.core.cfg.node import NodeType from slither.core.variables.state_variable import StateVariable +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations import Function +from slither.core.declarations.function_contract import FunctionContract +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation +from slither.slithir.variables import TupleVariable +from slither.detectors.operations.unused_return_values import UnusedReturnValues +from typing import List + + class OracleVarType(Enum): ROUNDID = 0 @@ -103,15 +118,87 @@ def generate_naive_order(self): vars_order[OracleVarType.ANSWEREDINROUND] = None return vars_order + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use + return ( + isinstance(ir, HighLevelCall) + and ( + ( + isinstance(ir.function, Function) + and "latestRoundData" in ir.function.name + ) + or not isinstance(ir.function, Function) + ) + or ir.node.type == NodeType.TRY + and isinstance(ir, (Assignment, Unpack)) + ) + + + def detect_unused_return_values( + self, f: FunctionContract + ) -> List[Node]: # pylint: disable=no-self-use + """ + Return the nodes where the return value of a call is unused + Args: + f (Function) + Returns: + list(Node) + """ + used_returned_vars = [] + values_returned = [] + nodes_origin = {} + # pylint: disable=too-many-nested-blocks + for n in f.nodes: + for ir in n.irs: + if self._is_instance(ir): + # if a return value is stored in a state variable, it's ok + if ir.lvalue and not isinstance(ir.lvalue, StateVariable): + values_returned.append((ir.lvalue, None)) + nodes_origin[ir.lvalue] = ir + if isinstance(ir.lvalue, TupleVariable): + # we iterate the number of elements the tuple has + # and add a (variable, index) in values_returned for each of them + for index in range(len(ir.lvalue.type)): + values_returned.append((ir.lvalue, index)) + for read in ir.read: + remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) + if remove in values_returned: + used_returned_vars.append(remove) # This is saying which element is used based on the index + # this is needed to remove the tuple variable when the first time one of its element is used + if remove[1] is not None and (remove[0], None) in values_returned: + values_returned.remove((remove[0], None)) + values_returned.remove(remove) + output = [] + for (value, index) in used_returned_vars: + output.append((nodes_origin[value].node, index)) + return output + def find_which_vars_are_used(self, oracle: Oracle): vars_order = self.generate_naive_order() - returned_types_of_high_call = oracle.ir.function.return_type - for i in oracle.ir.used: - if isinstance(i, StateVariable): - print(i.name) - if isinstance(i, Tup): - print(i.name) - # print() + # ir = oracle.ir + + # values_returned = [] + # nodes_origin = {} + # # print(ir.lvalue) + # if ir.lvalue and not isinstance(ir.lvalue, StateVariable): + # values_returned.append((ir.lvalue, None)) + # nodes_origin[ir.lvalue] = ir + # if isinstance(ir.lvalue, TupleVariable): + # for index in range(len(ir.lvalue.type)): + # values_returned.append((ir.lvalue, index)) + # else: + # print(ir.lvalue) + + # for read in ir.read: + # remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) + # if remove in values_returned: + # # this is needed to remove the tuple variable when the first time one of its element is used + # if remove[1] is not None and (remove[0], None) in values_returned: + # values_returned.remove((remove[0], None)) + # values_returned.remove(remove) + # for node in [nodes_origin[value].node for (value, _) in values_returned]: + # print(node) + + types_of_used_vars = [] for var in oracle.oracle_vars: if type(var) == VarInCondition: @@ -190,7 +277,13 @@ def _detect(self): super()._detect() not_checked_vars = self.process_not_checked_vars() res = self.generate_result(not_checked_vars) - self.process_checked_vars() + for c in self.compilation_unit.contracts_derived: + for f in c.functions_and_modifiers: + unused_return = self.detect_unused_return_values(f) + if unused_return: + for i in unused_return: + print(i) + # self.process_checked_vars() # results.append(res) # res = self.generate_result(self.process_checked_vars()) # results.append(res) From 876860d9592815a28dfb6e54c8124884d5718941 Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 29 Nov 2023 14:18:38 +0100 Subject: [PATCH 18/87] Finding indexes of returned vars in oracle --- slither/detectors/oracles/oracle.py | 92 ++++++++++++++----- .../detectors/oracles/oracle_validate_data.py | 69 ++------------ 2 files changed, 78 insertions(+), 83 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 12583e9d8f..ac4ee0ee83 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -6,6 +6,19 @@ from slither.slithir.operations import Binary, BinaryType from slither.slithir.operations import HighLevelCall from enum import Enum +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations import Function +from slither.core.declarations.function_contract import FunctionContract +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation +from slither.slithir.variables import TupleVariable +from typing import List + # For debugging # import debugpy @@ -17,7 +30,7 @@ # print('break on this line') class Oracle: - def __init__(self, _contract, _function, _ir_rep, _line_of_call): + def __init__(self, _contract, _function, _ir_rep, _line_of_call, _returned_used_vars): self.contract = _contract self.function = _function self.ir = _ir_rep @@ -25,6 +38,7 @@ def __init__(self, _contract, _function, _ir_rep, _line_of_call): self.oracle_vars = [] self.vars_in_condition = [] self.vars_not_in_condition = [] + self.returned_vars_indexes = _returned_used_vars # self.possible_variables_names = [ # "price", # "timestamp", @@ -57,9 +71,17 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: for function in contract.functions: if function.is_constructor: continue - found_latest_round, ir_rep, line = self.check_chainlink_call(function) - if found_latest_round: - oracles.append(Oracle(contract, function, ir_rep, line)) + oracle_calls_in_function, oracle_returned_var_indexes, = self.check_chainlink_call(function) + if oracle_calls_in_function: + print("ORacle calls", oracle_calls_in_function) + print("Oracle returned var indexes", oracle_returned_var_indexes) + for node in oracle_calls_in_function: + idxs = [] + for idx in oracle_returned_var_indexes: + if idx[0] == node: + idxs.append(idx[1]) + oracle = Oracle(contract, function, node, node.source_mapping.lines[0], idxs) + oracles.append(oracle) return oracles def compare_chainlink_call(self, function) -> bool: @@ -67,26 +89,53 @@ def compare_chainlink_call(self, function) -> bool: if call in str(function): return True return False + + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use + return ( + isinstance(ir, HighLevelCall) + and ( + ( + isinstance(ir.function, Function) + and self.compare_chainlink_call(ir.function.name) + ) + # or not isinstance(ir.function, Function) + ) + # or ir.node.type == NodeType.TRY + # and isinstance(ir, (Assignment, Unpack)) + ) + - def check_chainlink_call(self, function: FunctionContract) -> (bool, str, int): + def check_chainlink_call(self, function: FunctionContract): + used_returned_vars = [] + values_returned = [] + nodes_origin = {} + oracle_calls = [] for node in function.nodes: for ir in node.irs: - if isinstance(ir,HighLevelCall): - if(self.compare_chainlink_call(ir.function_name)): - return (True, ir, node.source_mapping.lines[0]) - - # for functionCalled in function.external_calls_as_expressions: - # if self.compare_chainlink_call(functionCalled): - # # debugpy.breakpoint() - # # print(functionCalled.source_mapping.filename.absolute) # Absolute path to file - # # print("Mapping high level call", functionCalled) - - # return ( - # True, - # str(functionCalled).split(".", maxsplit=1)[0], - # functionCalled.source_mapping.lines[0], - # ) # The external call is in format contract.function, so we split it and get the contract name - return (False, "", 0) + if self._is_instance(ir): + oracle_calls.append(node) + if ir.lvalue and not isinstance(ir.lvalue, StateVariable): + values_returned.append((ir.lvalue, None)) + nodes_origin[ir.lvalue] = ir + if isinstance(ir.lvalue, TupleVariable): + # we iterate the number of elements the tuple has + # and add a (variable, index) in values_returned for each of them + for index in range(len(ir.lvalue.type)): + values_returned.append((ir.lvalue, index)) + for read in ir.read: + remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) + if remove in values_returned: + used_returned_vars.append(remove) # This is saying which element is used based on the index + # this is needed to remove the tuple variable when the first time one of its element is used + if remove[1] is not None and (remove[0], None) in values_returned: + values_returned.remove((remove[0], None)) + values_returned.remove(remove) + # if(self.compare_chainlink_call(ir.function_name)): + # return (True, ir, node.source_mapping.lines[0]) + returned_vars_used_indexes = [] + for (value, index) in used_returned_vars: + returned_vars_used_indexes.append((nodes_origin[value].node,index)) + return oracle_calls, returned_vars_used_indexes def get_returned_variables_from_oracle( self, function: FunctionContract, oracle_call_line @@ -184,7 +233,6 @@ def _detect(self): oracle.function, oracle.line_of_call ) self.vars_in_conditions(oracle) - # for oracle in oracles: # oracle_vars = self.get_returned_variables_from_oracle( # oracle.function, oracle.line_of_call diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 4558c00644..62b1903a47 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -20,7 +20,6 @@ ) from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation from slither.slithir.variables import TupleVariable -from slither.detectors.operations.unused_return_values import UnusedReturnValues from typing import List @@ -118,59 +117,7 @@ def generate_naive_order(self): vars_order[OracleVarType.ANSWEREDINROUND] = None return vars_order - def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use - return ( - isinstance(ir, HighLevelCall) - and ( - ( - isinstance(ir.function, Function) - and "latestRoundData" in ir.function.name - ) - or not isinstance(ir.function, Function) - ) - or ir.node.type == NodeType.TRY - and isinstance(ir, (Assignment, Unpack)) - ) - - def detect_unused_return_values( - self, f: FunctionContract - ) -> List[Node]: # pylint: disable=no-self-use - """ - Return the nodes where the return value of a call is unused - Args: - f (Function) - Returns: - list(Node) - """ - used_returned_vars = [] - values_returned = [] - nodes_origin = {} - # pylint: disable=too-many-nested-blocks - for n in f.nodes: - for ir in n.irs: - if self._is_instance(ir): - # if a return value is stored in a state variable, it's ok - if ir.lvalue and not isinstance(ir.lvalue, StateVariable): - values_returned.append((ir.lvalue, None)) - nodes_origin[ir.lvalue] = ir - if isinstance(ir.lvalue, TupleVariable): - # we iterate the number of elements the tuple has - # and add a (variable, index) in values_returned for each of them - for index in range(len(ir.lvalue.type)): - values_returned.append((ir.lvalue, index)) - for read in ir.read: - remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) - if remove in values_returned: - used_returned_vars.append(remove) # This is saying which element is used based on the index - # this is needed to remove the tuple variable when the first time one of its element is used - if remove[1] is not None and (remove[0], None) in values_returned: - values_returned.remove((remove[0], None)) - values_returned.remove(remove) - output = [] - for (value, index) in used_returned_vars: - output.append((nodes_origin[value].node, index)) - return output def find_which_vars_are_used(self, oracle: Oracle): vars_order = self.generate_naive_order() @@ -275,14 +222,14 @@ def process_not_checked_vars(self): def _detect(self): results = [] super()._detect() - not_checked_vars = self.process_not_checked_vars() - res = self.generate_result(not_checked_vars) - for c in self.compilation_unit.contracts_derived: - for f in c.functions_and_modifiers: - unused_return = self.detect_unused_return_values(f) - if unused_return: - for i in unused_return: - print(i) + # not_checked_vars = self.process_not_checked_vars() + # res = self.generate_result(not_checked_vars) + # for c in self.compilation_unit.contracts_derived: + # for f in c.functions_and_modifiers: + # unused_return = self.detect_unused_return_values(f) + # if unused_return: + # for i in unused_return: + # print(i) # self.process_checked_vars() # results.append(res) # res = self.generate_result(self.process_checked_vars()) From 5260262f44bcb03d00aff39fe036f0cfdeacd8ef Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 29 Nov 2023 14:46:32 +0100 Subject: [PATCH 19/87] Naive check should always work --- slither/detectors/oracles/oracle.py | 8 +- .../detectors/oracles/oracle_validate_data.py | 116 +++++++----------- 2 files changed, 46 insertions(+), 78 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index ac4ee0ee83..0aa915fa80 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -30,10 +30,10 @@ # print('break on this line') class Oracle: - def __init__(self, _contract, _function, _ir_rep, _line_of_call, _returned_used_vars): + def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_vars): self.contract = _contract self.function = _function - self.ir = _ir_rep + self.node = _node self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] self.oracle_vars = [] self.vars_in_condition = [] @@ -73,8 +73,6 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: continue oracle_calls_in_function, oracle_returned_var_indexes, = self.check_chainlink_call(function) if oracle_calls_in_function: - print("ORacle calls", oracle_calls_in_function) - print("Oracle returned var indexes", oracle_returned_var_indexes) for node in oracle_calls_in_function: idxs = [] for idx in oracle_returned_var_indexes: @@ -130,8 +128,6 @@ def check_chainlink_call(self, function: FunctionContract): if remove[1] is not None and (remove[0], None) in values_returned: values_returned.remove((remove[0], None)) values_returned.remove(remove) - # if(self.compare_chainlink_call(ir.function_name)): - # return (True, ir, node.source_mapping.lines[0]) returned_vars_used_indexes = [] for (value, index) in used_returned_vars: returned_vars_used_indexes.append((nodes_origin[value].node,index)) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 62b1903a47..b1c36d0233 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -51,6 +51,8 @@ class OracleDataCheck(OracleDetector): def check_staleness(self, var: VarInCondition): + if var is None: + return False for node in var.nodes: str_node = str(node) # print(str_node) @@ -71,6 +73,8 @@ def check_staleness(self, var: VarInCondition): def check_RoundId(self, var: VarInCondition, var2: VarInCondition): # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf + if var is None or var2 is None: + return False for node in var.nodes: for ir in node.irs: if isinstance(ir, Binary): @@ -84,6 +88,8 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition): # https://so return False def check_price(self, var: VarInCondition): #TODO I need to divie require or IF + if var is None: + return False look_for_revert = False for node in var.nodes: #TODO testing if look_for_revert: @@ -110,63 +116,46 @@ def check_price(self, var: VarInCondition): #TODO I need to divie require or IF def generate_naive_order(self): vars_order = {} - vars_order[OracleVarType.ROUNDID] = None - vars_order[OracleVarType.ANSWER] = None - vars_order[OracleVarType.STARTEDAT] = None - vars_order[OracleVarType.UPDATEDAT] = None - vars_order[OracleVarType.ANSWEREDINROUND] = None + vars_order[OracleVarType.ROUNDID.value] = None + vars_order[OracleVarType.ANSWER.value] = None + vars_order[OracleVarType.STARTEDAT.value] = None + vars_order[OracleVarType.UPDATEDAT.value] = None + vars_order[OracleVarType.ANSWEREDINROUND.value] = None return vars_order def find_which_vars_are_used(self, oracle: Oracle): vars_order = self.generate_naive_order() - # ir = oracle.ir - - # values_returned = [] - # nodes_origin = {} - # # print(ir.lvalue) - # if ir.lvalue and not isinstance(ir.lvalue, StateVariable): - # values_returned.append((ir.lvalue, None)) - # nodes_origin[ir.lvalue] = ir - # if isinstance(ir.lvalue, TupleVariable): - # for index in range(len(ir.lvalue.type)): - # values_returned.append((ir.lvalue, index)) - # else: - # print(ir.lvalue) - - # for read in ir.read: - # remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) - # if remove in values_returned: - # # this is needed to remove the tuple variable when the first time one of its element is used - # if remove[1] is not None and (remove[0], None) in values_returned: - # values_returned.remove((remove[0], None)) - # values_returned.remove(remove) - # for node in [nodes_origin[value].node for (value, _) in values_returned]: - # print(node) - - - types_of_used_vars = [] - for var in oracle.oracle_vars: - if type(var) == VarInCondition: - types_of_used_vars.append(var.var.type) - else: - types_of_used_vars.append(var.type) - for i in range(0,len(types_of_used_vars)): - if types_of_used_vars[i].name == "uint80" and i == 0: - vars_order[OracleVarType.ROUNDID] = oracle.oracle_vars[i] - elif types_of_used_vars[i].name == "int256": - vars_order[OracleVarType.ANSWER] = oracle.oracle_vars[i] - elif types_of_used_vars[i].name == "uint80" and i == len(types_of_used_vars) - 1: - vars_order[OracleVarType.ANSWEREDINROUND] = oracle.oracle_vars[i] - + for i in range(len(oracle.oracle_vars)): + vars_order[oracle.returned_vars_indexes[i]] = oracle.oracle_vars[i] + return vars_order + - + def is_needed_to_check_conditions(self, oracle, var): + if isinstance(var, VarInCondition): + var = var.var + if var in oracle.vars_not_in_condition: + return False + return True def naive_check(self, oracle: Oracle): - self.find_which_vars_are_used(oracle) - # print([var.var.name for var in ordered_returned_vars]) + vars_order = self.find_which_vars_are_used(oracle) + problems = [] + for (index, var) in vars_order.items(): + if not self.is_needed_to_check_conditions(oracle, var): + continue + if index == OracleVarType.ROUNDID.value: + if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): + problems.append("The RoundID is not checked\n") #TODO add more info + elif index == OracleVarType.ANSWER.value: + if not self.check_price(var): + problems.append("The price is not checked\n") #TODO add more info + elif index == OracleVarType.UPDATEDAT.value: + if not self.check_staleness(var): + problems.append("The staleness is not checked\n") #TODO add more info + return problems # checks = {} # for i in range(0,5): # checks[i] = False @@ -182,17 +171,6 @@ def naive_check(self, oracle: Oracle): # print(checks[3]) # return checks - - def process_checks(self,checks): - result = [] - for check in checks: - if check[1] == False: - result.append("Price is not checked well!\n") - if check[3] == False: - result.append("The price could be probably stale!\n") - if check[0] == False: - result.append("RoundID is not checked well!\n") - return result # require( # answeredInRound >= roundID, @@ -201,10 +179,6 @@ def process_checks(self,checks): # require(price > 0, "Chainlink Malfunction"); # require(updateTime != 0, "Incomplete round"); - def process_checked_vars(self): - checks = [] - for oracle in self.oracles: - checks.append(self.naive_check(oracle)) # return self.process_checks(checks) def process_not_checked_vars(self): @@ -222,16 +196,14 @@ def process_not_checked_vars(self): def _detect(self): results = [] super()._detect() - # not_checked_vars = self.process_not_checked_vars() - # res = self.generate_result(not_checked_vars) - # for c in self.compilation_unit.contracts_derived: - # for f in c.functions_and_modifiers: - # unused_return = self.detect_unused_return_values(f) - # if unused_return: - # for i in unused_return: - # print(i) - # self.process_checked_vars() - # results.append(res) + not_checked_vars = self.process_not_checked_vars() + res = self.generate_result(not_checked_vars) + results.append(res) + for oracle in self.oracles: + checked_vars = self.naive_check(oracle) + if len(checked_vars) > 0: + res = self.generate_result(checked_vars) + results.append(res) # res = self.generate_result(self.process_checked_vars()) # results.append(res) return results \ No newline at end of file From ec5c69fac618a5a3eb1cdd576a9e04ee7b7a880c Mon Sep 17 00:00:00 2001 From: Talfao Date: Wed, 29 Nov 2023 15:09:34 +0100 Subject: [PATCH 20/87] Change output from detector of data validiy --- slither/detectors/oracles/oracle.py | 9 +++++++-- slither/detectors/oracles/oracle_validate_data.py | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 0aa915fa80..44c7380584 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -30,7 +30,7 @@ # print('break on this line') class Oracle: - def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_vars): + def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_vars, _interface): self.contract = _contract self.function = _function self.node = _node @@ -39,6 +39,7 @@ def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_va self.vars_in_condition = [] self.vars_not_in_condition = [] self.returned_vars_indexes = _returned_used_vars + self.interface = _interface # self.possible_variables_names = [ # "price", # "timestamp", @@ -74,11 +75,15 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: oracle_calls_in_function, oracle_returned_var_indexes, = self.check_chainlink_call(function) if oracle_calls_in_function: for node in oracle_calls_in_function: + interface = None + for ir in node.irs: + if isinstance(ir, HighLevelCall): + interface = ir.destination idxs = [] for idx in oracle_returned_var_indexes: if idx[0] == node: idxs.append(idx[1]) - oracle = Oracle(contract, function, node, node.source_mapping.lines[0], idxs) + oracle = Oracle(contract, function, node, node.source_mapping.lines[0], idxs, interface) oracles.append(oracle) return oracles diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index b1c36d0233..a6c1589134 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -148,13 +148,13 @@ def naive_check(self, oracle: Oracle): continue if index == OracleVarType.ROUNDID.value: if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): - problems.append("The RoundID is not checked\n") #TODO add more info + problems.append("RoundID value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.ANSWER.value: if not self.check_price(var): - problems.append("The price is not checked\n") #TODO add more info + problems.append("Price value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): - problems.append("The staleness is not checked\n") #TODO add more info + problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) return problems # checks = {} # for i in range(0,5): From c250443443c5203a790ba74048d8ebb7d823d547 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 30 Nov 2023 18:26:45 +0100 Subject: [PATCH 21/87] Add revert check --- .../detectors/oracles/oracle_validate_data.py | 39 ++++++------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index a6c1589134..3a6f6f73d2 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -50,7 +50,7 @@ class OracleDataCheck(OracleDetector): - def check_staleness(self, var: VarInCondition): + def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False for node in var.nodes: @@ -71,11 +71,18 @@ def check_staleness(self, var: VarInCondition): # pass return False + - def check_RoundId(self, var: VarInCondition, var2: VarInCondition): # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf + def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf if var is None or var2 is None: return False + look_for_revert = False for node in var.nodes: + if look_for_revert: + if node.type == NodeType.THROW: + return True + else: + look_for_revert = False for ir in node.irs: if isinstance(ir, Binary): if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): @@ -84,10 +91,12 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition): # https://so elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): if (ir.variable_right == var2.var and ir.variable_left == var.var): return True + else: + look_for_revert = True return False - def check_price(self, var: VarInCondition): #TODO I need to divie require or IF + def check_price(self, var: VarInCondition) -> bool: #TODO I need to divie require or IF if var is None: return False look_for_revert = False @@ -156,30 +165,6 @@ def naive_check(self, oracle: Oracle): if not self.check_staleness(var): problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) return problems - # checks = {} - # for i in range(0,5): - # checks[i] = False - # for var in oracle.vars_not_in_condition: - - # for i in range(0,len(ordered_returned_vars)): - # if i == OracleVarType.ROUNDID.value: - # checks[0] = self.check_RoundId(ordered_returned_vars[i], ordered_returned_vars[-1]) - # elif i == OracleVarType.ANSWER.value: - # checks[1] = self.check_price(ordered_returned_vars[i]) - # elif i == OracleVarType.UPDATEDAT.value: - # checks[3] = self.check_staleness(ordered_returned_vars[i]) - # print(checks[3]) - - # return checks - - # require( - # answeredInRound >= roundID, - # "Chainlink Price Stale" - # ); - # require(price > 0, "Chainlink Malfunction"); - # require(updateTime != 0, "Incomplete round"); - - # return self.process_checks(checks) def process_not_checked_vars(self): result = [] From 227b0f7efdb8a8082a98add187525da835780747 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 30 Nov 2023 18:55:58 +0100 Subject: [PATCH 22/87] Fix an issue if variable is not constant in price --- .../detectors/oracles/oracle_validate_data.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 3a6f6f73d2..9f2aa274f9 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -21,6 +21,7 @@ from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation from slither.slithir.variables import TupleVariable from typing import List +from slither.slithir.variables.constant import Constant @@ -108,17 +109,18 @@ def check_price(self, var: VarInCondition) -> bool: #TODO I need to divie requir look_for_revert = False for ir in node.irs: if isinstance(ir, Binary): - if ir.type is (BinaryType.GREATER): - if (ir.variable_right.value == 0): - return True - elif ir.type is (BinaryType.LESS): - if (ir.variable_left.value == 0): - return True - elif ir.type is (BinaryType.NOT_EQUAL): - if (ir.variable_right.value == 0): - return True - else: - look_for_revert = True + if isinstance(ir.variable_right, Constant): + if ir.type is (BinaryType.GREATER): + if (ir.variable_right.value == 0): + return True + elif ir.type is (BinaryType.NOT_EQUAL): + if (ir.variable_right.value == 0): + return True + if isinstance(ir.variable_left, Constant): + if ir.type is (BinaryType.LESS): + if (ir.variable_left.value == 0): + return True + look_for_revert = True return False @@ -155,7 +157,7 @@ def naive_check(self, oracle: Oracle): for (index, var) in vars_order.items(): if not self.is_needed_to_check_conditions(oracle, var): continue - if index == OracleVarType.ROUNDID.value: + if index == OracleVarType.ROUNDID.value: #TODO this is maybe not so mandatory if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): problems.append("RoundID value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.ANSWER.value: From b4c9b260695316ed9c072cbbfcba265aef85cdfa Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 30 Nov 2023 19:30:42 +0100 Subject: [PATCH 23/87] Correct revert check --- .../detectors/oracles/oracle_validate_data.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 9f2aa274f9..09d33e5ec4 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -5,7 +5,7 @@ from slither.slithir.operations import Binary, BinaryType from enum import Enum from slither.detectors.oracles.oracle import OracleDetector, Oracle, VarInCondition -from slither.detectors.operations.unused_return_values import UnusedReturnValues +from slither.slithir.operations.solidity_call import SolidityCall from slither.core.cfg.node import NodeType from slither.core.variables.state_variable import StateVariable @@ -20,6 +20,7 @@ ) from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation from slither.slithir.variables import TupleVariable +from slither.core.expressions.expression import Expression from typing import List from slither.slithir.variables.constant import Constant @@ -77,13 +78,7 @@ def check_staleness(self, var: VarInCondition) -> bool: def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf if var is None or var2 is None: return False - look_for_revert = False for node in var.nodes: - if look_for_revert: - if node.type == NodeType.THROW: - return True - else: - look_for_revert = False for ir in node.irs: if isinstance(ir, Binary): if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): @@ -92,21 +87,24 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # ht elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): if (ir.variable_right == var2.var and ir.variable_left == var.var): return True - else: - look_for_revert = True + if self.check_revert(node): + return True return False - def check_price(self, var: VarInCondition) -> bool: #TODO I need to divie require or IF + def check_revert(self, node: Node) -> bool: + for n in node.sons: + if n.type == NodeType.EXPRESSION: + for ir in n.irs: + if isinstance(ir, SolidityCall): + if "revert" in ir.function.name: + return True + return False + + def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: #TODO I need to divie require or IF if var is None: return False - look_for_revert = False for node in var.nodes: #TODO testing - if look_for_revert: - if node.type == NodeType.THROW: - return True - else: - look_for_revert = False for ir in node.irs: if isinstance(ir, Binary): if isinstance(ir.variable_right, Constant): @@ -120,7 +118,8 @@ def check_price(self, var: VarInCondition) -> bool: #TODO I need to divie requir if ir.type is (BinaryType.LESS): if (ir.variable_left.value == 0): return True - look_for_revert = True + if self.check_revert(node): + return True return False @@ -161,7 +160,7 @@ def naive_check(self, oracle: Oracle): if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): problems.append("RoundID value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.ANSWER.value: - if not self.check_price(var): + if not self.check_price(var, oracle): problems.append("Price value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): From f584e6dbf1897c046a3962efcfb77bb6d97b5973 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 30 Nov 2023 21:11:34 +0100 Subject: [PATCH 24/87] Rechange the output for mitigating duplicates of arrays --- slither/detectors/oracles/oracle.py | 8 +++++++- slither/detectors/oracles/oracle_validate_data.py | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 44c7380584..bca9a3da58 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -17,6 +17,7 @@ ) from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation from slither.slithir.variables import TupleVariable +from slither.slithir.variables.reference import ReferenceVariable from typing import List # For debugging @@ -80,9 +81,14 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: if isinstance(ir, HighLevelCall): interface = ir.destination idxs = [] + # if referecne_variable_assigned and isinstance(interface, ReferenceVariable): + # break + # if isinstance(interface, ReferenceVariable): + # referecne_variable_assigned = True for idx in oracle_returned_var_indexes: if idx[0] == node: idxs.append(idx[1]) + # print(node, interface, contract) oracle = Oracle(contract, function, node, node.source_mapping.lines[0], idxs, interface) oracles.append(oracle) return oracles @@ -135,7 +141,7 @@ def check_chainlink_call(self, function: FunctionContract): values_returned.remove(remove) returned_vars_used_indexes = [] for (value, index) in used_returned_vars: - returned_vars_used_indexes.append((nodes_origin[value].node,index)) + returned_vars_used_indexes.append((nodes_origin[value].node,index)) return oracle_calls, returned_vars_used_indexes def get_returned_variables_from_oracle( diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 09d33e5ec4..8aae811a8d 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -40,8 +40,8 @@ class OracleDataCheck(OracleDetector): ARGUMENT = "oracle" # slither will launch the detector with slither.py --detect mydetector HELP = "Help printed by slither" - IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.HIGH + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.MEDIUM WIKI = "RUN" @@ -158,13 +158,13 @@ def naive_check(self, oracle: Oracle): continue if index == OracleVarType.ROUNDID.value: #TODO this is maybe not so mandatory if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): - problems.append("RoundID value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) + problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.ANSWER.value: if not self.check_price(var, oracle): - problems.append("Price value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) + problems.append("Price value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): - problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call {}, in the function {} of contract {}.\n".format(oracle.interface, oracle.function, oracle.node.source_mapping)) + problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) return problems def process_not_checked_vars(self): From 5d79f48579dd5f02a02a3b2331217095ca68727a Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 30 Nov 2023 21:29:51 +0100 Subject: [PATCH 25/87] Simple detection of sequencer --- slither/detectors/oracles/oracle.py | 26 ++++++++++++------- .../detectors/oracles/oracle_validate_data.py | 24 +++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index bca9a3da58..3bd2cb1816 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -203,11 +203,13 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: vars_in_condition.append(VarInCondition(var, nodes)) oracle_vars.append(VarInCondition(var, nodes)) else: - oracle_vars.append(var) + self.nodes = [] if self.investigate_internal_call(oracle.function, var): #TODO i need to chnge this to check for taint analysis somehow - vars_in_condition.append(var) + vars_in_condition.append(VarInCondition(var, self.nodes)) + oracle_vars.append(VarInCondition(var, self.nodes)) else: vars_not_in_condition.append(var) + oracle_vars.append(var) oracle.vars_in_condition = vars_in_condition oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars @@ -220,14 +222,18 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: for functionCalled in function.internal_calls: if isinstance(functionCalled, FunctionContract): - for local_var in functionCalled.variables_read: - if local_var.name == var.name: - if functionCalled.is_reading_in_conditional_node( - local_var - ) or functionCalled.is_reading_in_require_or_assert( - local_var - ): # These two functions check if within the function some var is in require/assert of in if statement - return True + self.nodes = self.map_condition_to_var(var, functionCalled) + if len(self.nodes) > 0: + return True + # for local_var in functionCalled.variables_read: + # if local_var.name == var.name: + + # if functionCalled.is_reading_in_conditional_node( + # local_var + # ) or functionCalled.is_reading_in_require_or_assert( + # local_var + # ): # These two functions check if within the function some var is in require/assert of in if statement + # return True if self.investigate_internal_call(functionCalled, var): return True return False diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 8aae811a8d..27d10b04e8 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -150,6 +150,26 @@ def is_needed_to_check_conditions(self, oracle, var): return True + def is_sequencer(self, answer, startedAt): + if answer is None or startedAt is None: + return False + answer_checked = False + startedAt_checked = False + + for node in answer.nodes: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type is (BinaryType.EQUAL): + if isinstance(ir.variable_right, Constant): + if ir.variable_right.value == 1: + answer_checked = True + startedAt_checked = self.check_staleness(startedAt) + print(answer_checked, startedAt_checked) + + return answer_checked and startedAt_checked + + + def naive_check(self, oracle: Oracle): vars_order = self.find_which_vars_are_used(oracle) problems = [] @@ -165,6 +185,10 @@ def naive_check(self, oracle: Oracle): elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) + elif index == OracleVarType.STARTEDAT.value: + if self.is_sequencer(vars_order[OracleVarType.ANSWER.value], var): + problems = [] #TODO send some hook to another detector + break return problems def process_not_checked_vars(self): From b9ffe013d9ded230d52ba2b8c6a4ed46be554105 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 1 Dec 2023 00:21:21 +0100 Subject: [PATCH 26/87] Solved ordering of writing to vars --- slither/detectors/oracles/oracle.py | 25 +++++++------------ .../detectors/oracles/oracle_validate_data.py | 7 +++--- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 3bd2cb1816..23d270be54 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -19,6 +19,7 @@ from slither.slithir.variables import TupleVariable from slither.slithir.variables.reference import ReferenceVariable from typing import List +from slither.analyses.data_dependency.data_dependency import is_tainted # For debugging # import debugpy @@ -145,25 +146,17 @@ def check_chainlink_call(self, function: FunctionContract): return oracle_calls, returned_vars_used_indexes def get_returned_variables_from_oracle( - self, function: FunctionContract, oracle_call_line + self, function: FunctionContract, oracle_call_line, node ) -> list: written_vars = [] ordered_vars = [] - for ( - var - ) in ( - function.variables_written - ): # This iterates through list of variables which are written in function - if ( - var.source_mapping.lines[0] == oracle_call_line - ): # We need to match line of var with line of oracle call - written_vars.append(var) - for node in function.nodes: - for var in written_vars: - if node.type is NodeType.VARIABLE and node.variable_declaration == var: - if ordered_vars.count(var) == 0: + for var in node.variables_written: + written_vars.append(var) + for exp in node.variables_written_as_expression: + for v in exp.expressions: + for var in written_vars: + if (str(v) == str(var.name)): ordered_vars.append(var) - break return ordered_vars def check_var_condition_match(self, var, node) -> bool: @@ -243,7 +236,7 @@ def _detect(self): self.oracles = self.chainlink_oracles(self.contracts) for oracle in self.oracles: oracle.oracle_vars = self.get_returned_variables_from_oracle( - oracle.function, oracle.line_of_call + oracle.function, oracle.line_of_call, oracle.node ) self.vars_in_conditions(oracle) # for oracle in oracles: diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 27d10b04e8..1fbba2c8d1 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -149,8 +149,7 @@ def is_needed_to_check_conditions(self, oracle, var): return False return True - - def is_sequencer(self, answer, startedAt): + def is_sequencer_check(self, answer, startedAt): if answer is None or startedAt is None: return False answer_checked = False @@ -185,8 +184,8 @@ def naive_check(self, oracle: Oracle): elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) - elif index == OracleVarType.STARTEDAT.value: - if self.is_sequencer(vars_order[OracleVarType.ANSWER.value], var): + elif index == OracleVarType.STARTEDAT.value and vars_order[OracleVarType.STARTEDAT.value] is not None: + if self.is_sequencer_check(vars_order[OracleVarType.ANSWER.value], var): problems = [] #TODO send some hook to another detector break return problems From 57df560c66e0ae67bfb900f52a852cafdc66591e Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 3 Dec 2023 10:57:06 +0100 Subject: [PATCH 27/87] Add handler if the node is not var --- slither/detectors/oracles/oracle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 23d270be54..3d760c0101 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -165,6 +165,8 @@ def check_var_condition_match(self, var, node) -> bool: ) in ( node.variables_read ): # This iterates through all variables which are read in node, what means that they are used in condition + if var is None or var2 is None: + continue if var.name == var2.name: return True return False From d45d1449f42135dc307f7bfb30e2327ef3cf42b8 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 10 Dec 2023 09:41:24 +0100 Subject: [PATCH 28/87] First testing --- ...Check_0_8_20_oracle_data_check1_sol__0.txt | 5 + .../oracle/0.8.20/oracle_data_check1.sol | 50 + .../0.8.20/oracle_data_check1.sol-0.8.20.zip | Bin 0 -> 4593 bytes tests/e2e/detectors/test_detectors.py | 3246 +++++++++-------- tests/e2e/vyper_parsing/test_ast_parsing.py | 36 +- 5 files changed, 1697 insertions(+), 1640 deletions(-) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol-0.8.20.zip diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt new file mode 100644 index 0000000000..a0beedad98 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt @@ -0,0 +1,5 @@ +In contract `OracleWithoutChecks` a function `getPriceUSD` uses oracle where the values of vars ['price'] are not checked. This can potentially lead to a problem! + +RoundID value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check1.sol#46. +UpdatedAt value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check1.sol#46. + diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol new file mode 100644 index 0000000000..7c9b99b6ee --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol @@ -0,0 +1,50 @@ +pragma solidity 0.8.20; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract OracleWithoutChecks { + AggregatorV3Interface priceFeed; + + constructor() { + priceFeed = AggregatorV3Interface( + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 + ); + } + + function getPriceUSD() external view returns (uint256) { + //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeed.latestRoundData(); + (, int256 price, , , ) = priceFeed.latestRoundData(); + // chainlink price data is 8 decimals for WETH/USD + return uint256(price) * 1e10; + } +} diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..35d6cd6408cdbe1033c5199b815f74d09324fc7e GIT binary patch literal 4593 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 Date: Sun, 10 Dec 2023 09:44:59 +0100 Subject: [PATCH 29/87] Some changes to test --- ...detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt index a0beedad98..2c8d827159 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt @@ -1,5 +1,4 @@ In contract `OracleWithoutChecks` a function `getPriceUSD` uses oracle where the values of vars ['price'] are not checked. This can potentially lead to a problem! -RoundID value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check1.sol#46. UpdatedAt value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check1.sol#46. From 9eecd914859a612045eeb6edb8f33da10f82004a Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 10 Dec 2023 09:45:34 +0100 Subject: [PATCH 30/87] Comment roundID for now --- slither/detectors/oracles/oracle.py | 8 ++------ .../detectors/oracles/oracle_validate_data.py | 20 +++++++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 3d760c0101..0dde33534b 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -70,7 +70,7 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: Detects off-chain oracle contract and VAR """ oracles = [] - for contract in contracts: + for contract in contracts: for function in contract.functions: if function.is_constructor: continue @@ -103,15 +103,11 @@ def compare_chainlink_call(self, function) -> bool: def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use return ( isinstance(ir, HighLevelCall) - and ( + and ( isinstance(ir.function, Function) and self.compare_chainlink_call(ir.function.name) ) - # or not isinstance(ir.function, Function) - ) - # or ir.node.type == NodeType.TRY - # and isinstance(ir, (Assignment, Unpack)) ) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 1fbba2c8d1..60eea3772d 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -10,6 +10,7 @@ from slither.core.variables.state_variable import StateVariable from slither.core.cfg.node import Node, NodeType +from slither.slithir.operations.return_operation import Return from slither.core.declarations import Function from slither.core.declarations.function_contract import FunctionContract from slither.core.variables.state_variable import StateVariable @@ -58,7 +59,7 @@ def check_staleness(self, var: VarInCondition) -> bool: for node in var.nodes: str_node = str(node) # print(str_node) - if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b + if "block.timesslitamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b return True @@ -101,6 +102,15 @@ def check_revert(self, node: Node) -> bool: return True return False + def return_boolean(self, node: Node) -> bool: + for n in node.sons: + if n.type == NodeType.RETURN: + for ir in n.irs: + if isinstance(ir, Return): + for value in ir.values: + print(value) + + def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: #TODO I need to divie require or IF if var is None: return False @@ -120,6 +130,8 @@ def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: #TODO I need return True if self.check_revert(node): return True + elif self.return_boolean(node): + return True return False @@ -175,9 +187,9 @@ def naive_check(self, oracle: Oracle): for (index, var) in vars_order.items(): if not self.is_needed_to_check_conditions(oracle, var): continue - if index == OracleVarType.ROUNDID.value: #TODO this is maybe not so mandatory - if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): - problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) + # if index == OracleVarType.ROUNDID.value: #TODO this is maybe not so mandatory + # if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): + # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.ANSWER.value: if not self.check_price(var, oracle): problems.append("Price value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) From fa79feb78d9c10c37b4901eb6e4ef6fff4389ddd Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 10 Dec 2023 10:12:39 +0100 Subject: [PATCH 31/87] Add other test --- ...Check_0_8_20_oracle_data_check2_sol__0.txt | 3 + .../oracle/0.8.20/oracle_data_check2.sol | 71 ++++++++++++++++++ .../0.8.20/oracle_data_check2.sol-0.8.20.zip | Bin 0 -> 6190 bytes ...oracle_data_check_price_in_internal_fc.sol | 71 ++++++++++++++++++ tests/e2e/detectors/test_detectors.py | 1 + 5 files changed, 146 insertions(+) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol-0.8.20.zip create mode 100644 tests/e2e/detectors/test_data/oracle/oracle_data_check_price_in_internal_fc.sol diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt new file mode 100644 index 0000000000..384a5ffcfe --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt @@ -0,0 +1,3 @@ + +Price value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check2.sol#55-61. + diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol new file mode 100644 index 0000000000..da3a6e4816 --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol @@ -0,0 +1,71 @@ +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract StableOracleDAI { + AggregatorV3Interface priceFeedDAIETH; + + constructor() { + priceFeedDAIETH = AggregatorV3Interface( + 0x773616E4d11A78F511299002da57A0a94577F1f4 + ); + } + + function price_check(int price) internal view returns (bool) { + if (price > 0) { + return true; + } + return false; + } + + function getPriceUSD() external view returns (uint256) { + uint256 wethPriceUSD = 1; + uint256 DAIWethPrice = 1; + + // chainlink price data is 8 decimals for WETH/USD, so multiply by 10 decimals to get 18 decimal fractional + //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeedDAIETH.latestRoundData(); + ( + uint80 roundID, + int256 price, + , + uint256 updatedAt, + uint80 answeredInRound + ) = priceFeedDAIETH.latestRoundData(); + // bool val = price_check(price); + require(price == 0); + require(updatedAt - block.timestamp < 500); + require(answeredInRound > roundID); + + return + (wethPriceUSD * 1e18) / + ((DAIWethPrice + uint256(price) * 1e10) / 2); + } +} diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..2e62ecbc20884e3e4e95621d62d0e985f4031e6d GIT binary patch literal 6190 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_data/oracle/oracle_data_check_price_in_internal_fc.sol b/tests/e2e/detectors/test_data/oracle/oracle_data_check_price_in_internal_fc.sol new file mode 100644 index 0000000000..c0c2f55c43 --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle/oracle_data_check_price_in_internal_fc.sol @@ -0,0 +1,71 @@ +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract StableOracleDAI { + AggregatorV3Interface priceFeedDAIETH; + + constructor() { + priceFeedDAIETH = AggregatorV3Interface( + 0x773616E4d11A78F511299002da57A0a94577F1f4 + ); + } + + function price_check(int price) internal pure returns (bool) { + if (price > 0) { + return true; + } + return false; + } + + function getPriceUSD() external view returns (uint256) { + uint256 wethPriceUSD = 1; + uint256 DAIWethPrice = 1; + + // chainlink price data is 8 decimals for WETH/USD, so multiply by 10 decimals to get 18 decimal fractional + //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeedDAIETH.latestRoundData(); + ( + uint80 roundID, + int256 price, + , + uint256 updatedAt, + uint80 answeredInRound + ) = priceFeedDAIETH.latestRoundData(); + // bool val = price_check(price); + require(price_check(price)); + require(updatedAt - block.timestamp < 500); + require(answeredInRound > roundID); + + return + (wethPriceUSD * 1e18) / + ((DAIWethPrice + uint256(price) * 1e10) / 2); + } +} diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index f8da576f81..9620a6de3b 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1681,6 +1681,7 @@ def id_test(test_item: Test): # ), ##TODO My tests for oracle Test(all_detectors.OracleDataCheck, "oracle_data_check1.sol", "0.8.20"), + Test(all_detectors.OracleDataCheck, "oracle_data_check2.sol", "0.8.20"), ] GENERIC_PATH = "/GENERIC_PATH" From f7fa45cdcef2cfaafdb7fc83e2bc70f232f182d2 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 10 Dec 2023 10:13:35 +0100 Subject: [PATCH 32/87] Fix some issue --- slither/detectors/oracles/oracle_validate_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 60eea3772d..d44e16ed25 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -59,7 +59,7 @@ def check_staleness(self, var: VarInCondition) -> bool: for node in var.nodes: str_node = str(node) # print(str_node) - if "block.timesslitamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b + if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b return True @@ -190,7 +190,7 @@ def naive_check(self, oracle: Oracle): # if index == OracleVarType.ROUNDID.value: #TODO this is maybe not so mandatory # if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) - elif index == OracleVarType.ANSWER.value: + if index == OracleVarType.ANSWER.value: if not self.check_price(var, oracle): problems.append("Price value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) elif index == OracleVarType.UPDATEDAT.value: From 583b0e3ed4db05fa1b60aead62f0a47014f537a4 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 08:17:10 +0100 Subject: [PATCH 33/87] Before changing vars_in_conditions --- slither/detectors/oracles/oracle.py | 63 +++++++++++-------- .../detectors/oracles/oracle_validate_data.py | 6 +- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 0dde33534b..845e6d4945 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -15,7 +15,7 @@ DetectorClassification, DETECTOR_INFO, ) -from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation +from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation, InternalCall from slither.slithir.variables import TupleVariable from slither.slithir.variables.reference import ReferenceVariable from typing import List @@ -54,7 +54,7 @@ def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_va class VarInCondition(): def __init__(self, _var, _nodes): self.var = _var - self.nodes = _nodes + self.nodes_with_var = _nodes class OracleDetector(AbstractDetector): @@ -141,9 +141,7 @@ def check_chainlink_call(self, function: FunctionContract): returned_vars_used_indexes.append((nodes_origin[value].node,index)) return oracle_calls, returned_vars_used_indexes - def get_returned_variables_from_oracle( - self, function: FunctionContract, oracle_call_line, node - ) -> list: + def get_returned_variables_from_oracle(self, node) -> list: written_vars = [] ordered_vars = [] for var in node.variables_written: @@ -162,7 +160,7 @@ def check_var_condition_match(self, var, node) -> bool: node.variables_read ): # This iterates through all variables which are read in node, what means that they are used in condition if var is None or var2 is None: - continue + return False if var.name == var2.name: return True return False @@ -194,10 +192,10 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: vars_in_condition.append(VarInCondition(var, nodes)) oracle_vars.append(VarInCondition(var, nodes)) else: - self.nodes = [] + self.nodes_with_var = [] if self.investigate_internal_call(oracle.function, var): #TODO i need to chnge this to check for taint analysis somehow - vars_in_condition.append(VarInCondition(var, self.nodes)) - oracle_vars.append(VarInCondition(var, self.nodes)) + vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) + oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: vars_not_in_condition.append(var) oracle_vars.append(var) @@ -211,22 +209,37 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: return False - for functionCalled in function.internal_calls: - if isinstance(functionCalled, FunctionContract): - self.nodes = self.map_condition_to_var(var, functionCalled) - if len(self.nodes) > 0: - return True - # for local_var in functionCalled.variables_read: - # if local_var.name == var.name: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall): + for node2 in ir.function.nodes: + if node2.is_conditional(): + print(node) + # return True + # return True + # if self.check_var_condition_match(var, node2): + # print(node2) + # self.nodes_with_var.append(node2) + # return True + # if self.investigate_internal_call(node.function, var): + # return True + # for functionCalled in function.internal_calls: + # if isinstance(functionCalled, FunctionContract): + # print("functionCalled", functionCalled) + # self.nodes_with_var = self.map_condition_to_var(var, functionCalled) + # if len(self.nodes_with_var) > 0: + # return True + # # for local_var in functionCalled.variables_read: + # # if local_var.name == var.name: - # if functionCalled.is_reading_in_conditional_node( - # local_var - # ) or functionCalled.is_reading_in_require_or_assert( - # local_var - # ): # These two functions check if within the function some var is in require/assert of in if statement - # return True - if self.investigate_internal_call(functionCalled, var): - return True + # # if functionCalled.is_reading_in_conditional_node( + # # local_var + # # ) or functionCalled.is_reading_in_require_or_assert( + # # local_var + # # ): # These two functions check if within the function some var is in require/assert of in if statement + # # return True + # if self.investigate_internal_call(functionCalled, var): + # return True return False def _detect(self): @@ -234,7 +247,7 @@ def _detect(self): self.oracles = self.chainlink_oracles(self.contracts) for oracle in self.oracles: oracle.oracle_vars = self.get_returned_variables_from_oracle( - oracle.function, oracle.line_of_call, oracle.node + oracle.node ) self.vars_in_conditions(oracle) # for oracle in oracles: diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index d44e16ed25..00426c4e47 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -56,7 +56,7 @@ class OracleDataCheck(OracleDetector): def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False - for node in var.nodes: + for node in var.nodes_with_var: str_node = str(node) # print(str_node) if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b @@ -79,7 +79,7 @@ def check_staleness(self, var: VarInCondition) -> bool: def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf if var is None or var2 is None: return False - for node in var.nodes: + for node in var.nodes_with_var: for ir in node.irs: if isinstance(ir, Binary): if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): @@ -114,7 +114,7 @@ def return_boolean(self, node: Node) -> bool: def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: #TODO I need to divie require or IF if var is None: return False - for node in var.nodes: #TODO testing + for node in var.nodes_with_var: #TODO testing for ir in node.irs: if isinstance(ir, Binary): if isinstance(ir.variable_right, Constant): From e1cb0d35a3de754cf8b1d033b923cc210e525786 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 09:09:09 +0100 Subject: [PATCH 34/87] Solved internal calls --- slither/detectors/oracles/oracle.py | 62 +++++++++++++++++++---------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 845e6d4945..5ac618b8da 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -19,7 +19,7 @@ from slither.slithir.variables import TupleVariable from slither.slithir.variables.reference import ReferenceVariable from typing import List -from slither.analyses.data_dependency.data_dependency import is_tainted +from slither.analyses.data_dependency.data_dependency import is_tainted, get_dependencies # For debugging # import debugpy @@ -172,6 +172,10 @@ def map_condition_to_var(self, var, function: FunctionContract): if node.is_conditional() and self.check_var_condition_match(var, node): nodes.append(node) return nodes + + def var_in_function(self, var, function: FunctionContract): + + return False def vars_in_conditions(self, oracle: Oracle) -> bool: """ @@ -182,47 +186,65 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: oracle_vars = [] for var in oracle.oracle_vars: + self.nodes_with_var = [] if oracle.function.is_reading_in_conditional_node( var ) or oracle.function.is_reading_in_require_or_assert( var ): # These two functions check if within the function some var is in require/assert of in if statement - nodes = self.map_condition_to_var(var, oracle.function) - if len(nodes) > 0: - vars_in_condition.append(VarInCondition(var, nodes)) - oracle_vars.append(VarInCondition(var, nodes)) + + self.nodes_with_var = self.map_condition_to_var(var, oracle.function) + for node in self.nodes_with_var: + for ir in node.irs: + if isinstance(ir, InternalCall): + self.investigate_internal_call(ir.function, var) + + if len(self.nodes_with_var) > 0: + vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) + oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: - self.nodes_with_var = [] if self.investigate_internal_call(oracle.function, var): #TODO i need to chnge this to check for taint analysis somehow vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: vars_not_in_condition.append(var) oracle_vars.append(var) + oracle.vars_in_condition = vars_in_condition oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars + def map_param_to_var(self, var, function: FunctionContract): + for param in function.parameters: + origin_vars = get_dependencies(param, function) + for var2 in origin_vars: + if var2 == var: + return param def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: return False - for node in function.nodes: - for ir in node.irs: - if isinstance(ir, InternalCall): - for node2 in ir.function.nodes: - if node2.is_conditional(): - print(node) - # return True - # return True - # if self.check_var_condition_match(var, node2): - # print(node2) - # self.nodes_with_var.append(node2) - # return True - # if self.investigate_internal_call(node.function, var): - # return True + original_var_as_param = self.map_param_to_var(var, function) + if original_var_as_param is None: + return False + + + if function.is_reading_in_conditional_node(original_var_as_param) or function.is_reading_in_require_or_assert(original_var_as_param): + for node in function.nodes: + if node.is_conditional() and self.check_var_condition_match(original_var_as_param, node): + self.nodes_with_var.append(node) + if len(self.nodes_with_var) > 0: + return True + else: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall): + if self.investigate_internal_call(ir.function, var): + return True + + # for functionCalled in function.internal_calls: # if isinstance(functionCalled, FunctionContract): # print("functionCalled", functionCalled) From a93e35b661a7c74f01e6b4d2dddc9230b1114cc7 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 09:29:07 +0100 Subject: [PATCH 35/87] double internal problem solved --- slither/detectors/oracles/oracle.py | 105 ++++++++++-------- slither/detectors/oracles/oracle_slot0.py | 21 +++- .../detectors/oracles/oracle_validate_data.py | 76 +++++++------ 3 files changed, 117 insertions(+), 85 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 5ac618b8da..76ad3f4c87 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -31,6 +31,7 @@ # debugpy.breakpoint() # print('break on this line') + class Oracle: def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_vars, _interface): self.contract = _contract @@ -51,13 +52,15 @@ def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_va # "startedAt", # ] -class VarInCondition(): + +class VarInCondition: def __init__(self, _var, _nodes): self.var = _var self.nodes_with_var = _nodes + class OracleDetector(AbstractDetector): - + # https://github.com/crytic/slither/wiki/Python-API # def detect_stale_price(Function): ORACLE_CALLS = [ @@ -70,11 +73,14 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: Detects off-chain oracle contract and VAR """ oracles = [] - for contract in contracts: + for contract in contracts: for function in contract.functions: if function.is_constructor: continue - oracle_calls_in_function, oracle_returned_var_indexes, = self.check_chainlink_call(function) + ( + oracle_calls_in_function, + oracle_returned_var_indexes, + ) = self.check_chainlink_call(function) if oracle_calls_in_function: for node in oracle_calls_in_function: interface = None @@ -84,33 +90,29 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: idxs = [] # if referecne_variable_assigned and isinstance(interface, ReferenceVariable): # break - # if isinstance(interface, ReferenceVariable): + # if isinstance(interface, ReferenceVariable): # referecne_variable_assigned = True for idx in oracle_returned_var_indexes: if idx[0] == node: idxs.append(idx[1]) # print(node, interface, contract) - oracle = Oracle(contract, function, node, node.source_mapping.lines[0], idxs, interface) + oracle = Oracle( + contract, function, node, node.source_mapping.lines[0], idxs, interface + ) oracles.append(oracle) return oracles - + def compare_chainlink_call(self, function) -> bool: for call in self.ORACLE_CALLS: if call in str(function): return True return False - + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use - return ( - isinstance(ir, HighLevelCall) - and - ( - isinstance(ir.function, Function) - and self.compare_chainlink_call(ir.function.name) - ) + return isinstance(ir, HighLevelCall) and ( + isinstance(ir.function, Function) and self.compare_chainlink_call(ir.function.name) ) - def check_chainlink_call(self, function: FunctionContract): used_returned_vars = [] values_returned = [] @@ -131,14 +133,16 @@ def check_chainlink_call(self, function: FunctionContract): for read in ir.read: remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None) if remove in values_returned: - used_returned_vars.append(remove) # This is saying which element is used based on the index + used_returned_vars.append( + remove + ) # This is saying which element is used based on the index # this is needed to remove the tuple variable when the first time one of its element is used if remove[1] is not None and (remove[0], None) in values_returned: values_returned.remove((remove[0], None)) values_returned.remove(remove) returned_vars_used_indexes = [] for (value, index) in used_returned_vars: - returned_vars_used_indexes.append((nodes_origin[value].node,index)) + returned_vars_used_indexes.append((nodes_origin[value].node, index)) return oracle_calls, returned_vars_used_indexes def get_returned_variables_from_oracle(self, node) -> list: @@ -149,10 +153,10 @@ def get_returned_variables_from_oracle(self, node) -> list: for exp in node.variables_written_as_expression: for v in exp.expressions: for var in written_vars: - if (str(v) == str(var.name)): + if str(v) == str(var.name): ordered_vars.append(var) return ordered_vars - + def check_var_condition_match(self, var, node) -> bool: for ( var2 @@ -165,16 +169,15 @@ def check_var_condition_match(self, var, node) -> bool: return True return False - def map_condition_to_var(self, var, function: FunctionContract): nodes = [] for node in function.nodes: if node.is_conditional() and self.check_var_condition_match(var, node): nodes.append(node) return nodes - + def var_in_function(self, var, function: FunctionContract): - + return False def vars_in_conditions(self, oracle: Oracle) -> bool: @@ -192,18 +195,20 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: ) or oracle.function.is_reading_in_require_or_assert( var ): # These two functions check if within the function some var is in require/assert of in if statement - + self.nodes_with_var = self.map_condition_to_var(var, oracle.function) for node in self.nodes_with_var: for ir in node.irs: if isinstance(ir, InternalCall): self.investigate_internal_call(ir.function, var) - + if len(self.nodes_with_var) > 0: vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: - if self.investigate_internal_call(oracle.function, var): #TODO i need to chnge this to check for taint analysis somehow + if self.investigate_internal_call( + oracle.function, var + ): # TODO i need to chnge this to check for taint analysis somehow vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: @@ -214,7 +219,6 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars - def map_param_to_var(self, var, function: FunctionContract): for param in function.parameters: origin_vars = get_dependencies(param, function) @@ -222,6 +226,12 @@ def map_param_to_var(self, var, function: FunctionContract): if var2 == var: return param + def is_internal_call(self, node): + for ir in node.irs: + if isinstance(ir, InternalCall): + return True + return False + def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: return False @@ -229,22 +239,30 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: original_var_as_param = self.map_param_to_var(var, function) if original_var_as_param is None: return False - - - if function.is_reading_in_conditional_node(original_var_as_param) or function.is_reading_in_require_or_assert(original_var_as_param): + + if function.is_reading_in_conditional_node( + original_var_as_param + ) or function.is_reading_in_require_or_assert(original_var_as_param): + conditions = [] for node in function.nodes: - if node.is_conditional() and self.check_var_condition_match(original_var_as_param, node): - self.nodes_with_var.append(node) - if len(self.nodes_with_var) > 0: + if ( + node.is_conditional() + and self.check_var_condition_match(original_var_as_param, node) + and not self.is_internal_call(node) + ): + conditions.append(node) + if len(conditions) > 0: + for cond in conditions: + self.nodes_with_var.append(cond) return True - else: - for node in function.nodes: - for ir in node.irs: - if isinstance(ir, InternalCall): - if self.investigate_internal_call(ir.function, var): - return True - - + + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall): + if self.investigate_internal_call(ir.function, original_var_as_param): + return True + return False + # for functionCalled in function.internal_calls: # if isinstance(functionCalled, FunctionContract): # print("functionCalled", functionCalled) @@ -262,15 +280,12 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: # # return True # if self.investigate_internal_call(functionCalled, var): # return True - return False def _detect(self): info = [] self.oracles = self.chainlink_oracles(self.contracts) for oracle in self.oracles: - oracle.oracle_vars = self.get_returned_variables_from_oracle( - oracle.node - ) + oracle.oracle_vars = self.get_returned_variables_from_oracle(oracle.node) self.vars_in_conditions(oracle) # for oracle in oracles: # oracle_vars = self.get_returned_variables_from_oracle( diff --git a/slither/detectors/oracles/oracle_slot0.py b/slither/detectors/oracles/oracle_slot0.py index a9a233ad74..f680bbc20c 100644 --- a/slither/detectors/oracles/oracle_slot0.py +++ b/slither/detectors/oracles/oracle_slot0.py @@ -21,7 +21,7 @@ class OracleSlot0(AbstractDetector): WIKI_DESCRIPTION = "Slot0 is vulnerable to price manipulation as it gets price at the current moment. TWAP should be used instead." WIKI_EXPLOIT_SCENARIO = "asdsad" WIKI_RECOMMENDATION = "asdsad" - + oracles = [] def detect_slot0(self, contracts: Contract): @@ -35,8 +35,14 @@ def detect_slot0(self, contracts: Contract): continue for functionCalled in function.external_calls_as_expressions: if "slot0" in str(functionCalled): - self.oracles.append(Oracle(contract, function, str(functionCalled).split(".", maxsplit=1)[0], functionCalled.source_mapping.lines[0])) - + self.oracles.append( + Oracle( + contract, + function, + str(functionCalled).split(".", maxsplit=1)[0], + functionCalled.source_mapping.lines[0], + ) + ) def _detect(self): results = [] @@ -44,9 +50,14 @@ def _detect(self): # for oracle in self.oracles: # print(oracle.contract.name, oracle.function.name) for oracle in self.oracles: - results.append("Slot0 usage found in contract " + oracle.contract.name + " in function " + oracle.function.name) + results.append( + "Slot0 usage found in contract " + + oracle.contract.name + + " in function " + + oracle.function.name + ) results.append("\n") res = self.generate_result(results) output = [] output.append(res) - return output \ No newline at end of file + return output diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 00426c4e47..54d52f42e5 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -26,7 +26,6 @@ from slither.slithir.variables.constant import Constant - class OracleVarType(Enum): ROUNDID = 0 ANSWER = 1 @@ -34,6 +33,7 @@ class OracleVarType(Enum): UPDATEDAT = 3 ANSWEREDINROUND = 4 + class OracleDataCheck(OracleDetector): """ Documentation @@ -51,17 +51,16 @@ class OracleDataCheck(OracleDetector): WIKI_EXPLOIT_SCENARIO = "asdsad" WIKI_RECOMMENDATION = "asdsad" - - def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False for node in var.nodes_with_var: str_node = str(node) # print(str_node) - if "block.timestamp" in str_node: #TODO maybe try something like block.timestamp - updatedAt < b + if ( + "block.timestamp" in str_node + ): # TODO maybe try something like block.timestamp - updatedAt < b return True - # for ir in node.irs: # if isinstance(ir, Binary): @@ -73,10 +72,10 @@ def check_staleness(self, var: VarInCondition) -> bool: # elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): # pass return False - - - def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf + def check_RoundId( + self, var: VarInCondition, var2: VarInCondition + ) -> bool: # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf if var is None or var2 is None: return False for node in var.nodes_with_var: @@ -86,13 +85,13 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # ht if ir.variable_right == var.var and ir.variable_left == var2.var: return True elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): - if (ir.variable_right == var2.var and ir.variable_left == var.var): + if ir.variable_right == var2.var and ir.variable_left == var.var: return True if self.check_revert(node): return True - + return False - + def check_revert(self, node: Node) -> bool: for n in node.sons: if n.type == NodeType.EXPRESSION: @@ -110,32 +109,32 @@ def return_boolean(self, node: Node) -> bool: for value in ir.values: print(value) - - def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: #TODO I need to divie require or IF + def check_price( + self, var: VarInCondition, oracle: Oracle + ) -> bool: # TODO I need to divie require or IF if var is None: return False - for node in var.nodes_with_var: #TODO testing + for node in var.nodes_with_var: # TODO testing for ir in node.irs: if isinstance(ir, Binary): if isinstance(ir.variable_right, Constant): if ir.type is (BinaryType.GREATER): - if (ir.variable_right.value == 0): + if ir.variable_right.value == 0: return True elif ir.type is (BinaryType.NOT_EQUAL): - if (ir.variable_right.value == 0): + if ir.variable_right.value == 0: return True - if isinstance(ir.variable_left, Constant): + if isinstance(ir.variable_left, Constant): if ir.type is (BinaryType.LESS): - if (ir.variable_left.value == 0): + if ir.variable_left.value == 0: return True if self.check_revert(node): return True elif self.return_boolean(node): return True - return False - + def generate_naive_order(self): vars_order = {} vars_order[OracleVarType.ROUNDID.value] = None @@ -145,15 +144,12 @@ def generate_naive_order(self): vars_order[OracleVarType.ANSWEREDINROUND.value] = None return vars_order - - def find_which_vars_are_used(self, oracle: Oracle): vars_order = self.generate_naive_order() for i in range(len(oracle.oracle_vars)): vars_order[oracle.returned_vars_indexes[i]] = oracle.oracle_vars[i] return vars_order - def is_needed_to_check_conditions(self, oracle, var): if isinstance(var, VarInCondition): var = var.var @@ -176,10 +172,8 @@ def is_sequencer_check(self, answer, startedAt): answer_checked = True startedAt_checked = self.check_staleness(startedAt) print(answer_checked, startedAt_checked) - - return answer_checked and startedAt_checked - + return answer_checked and startedAt_checked def naive_check(self, oracle: Oracle): vars_order = self.find_which_vars_are_used(oracle) @@ -192,28 +186,40 @@ def naive_check(self, oracle: Oracle): # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) if index == OracleVarType.ANSWER.value: if not self.check_price(var, oracle): - problems.append("Price value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) + problems.append( + "Price value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( + oracle.function, oracle.node.source_mapping + ) + ) elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): - problems.append("UpdatedAt value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) - elif index == OracleVarType.STARTEDAT.value and vars_order[OracleVarType.STARTEDAT.value] is not None: + problems.append( + "UpdatedAt value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( + oracle.function, oracle.node.source_mapping + ) + ) + elif ( + index == OracleVarType.STARTEDAT.value + and vars_order[OracleVarType.STARTEDAT.value] is not None + ): if self.is_sequencer_check(vars_order[OracleVarType.ANSWER.value], var): - problems = [] #TODO send some hook to another detector + problems = [] # TODO send some hook to another detector break return problems - + def process_not_checked_vars(self): result = [] for oracle in self.oracles: if len(oracle.vars_not_in_condition) > 0: - result.append("In contract `{}` a function `{}` uses oracle where the values of vars {} are not checked. This can potentially lead to a problem! \n".format( + result.append( + "In contract `{}` a function `{}` uses oracle where the values of vars {} are not checked. This can potentially lead to a problem! \n".format( oracle.contract.name, oracle.function.name, [var.name for var in oracle.vars_not_in_condition], - )) + ) + ) return result - def _detect(self): results = [] super()._detect() @@ -227,4 +233,4 @@ def _detect(self): results.append(res) # res = self.generate_result(self.process_checked_vars()) # results.append(res) - return results \ No newline at end of file + return results From 2316cfbfed859794eccd44c21e374f86f7429b32 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 09:44:49 +0100 Subject: [PATCH 36/87] Add when not param --- slither/detectors/oracles/oracle.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 76ad3f4c87..8e31547734 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -195,7 +195,6 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: ) or oracle.function.is_reading_in_require_or_assert( var ): # These two functions check if within the function some var is in require/assert of in if statement - self.nodes_with_var = self.map_condition_to_var(var, oracle.function) for node in self.nodes_with_var: for ir in node.irs: @@ -208,7 +207,7 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: else: if self.investigate_internal_call( oracle.function, var - ): # TODO i need to chnge this to check for taint analysis somehow + ): vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: @@ -235,10 +234,10 @@ def is_internal_call(self, node): def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: return False - + original_var_as_param = self.map_param_to_var(var, function) if original_var_as_param is None: - return False + original_var_as_param = var if function.is_reading_in_conditional_node( original_var_as_param From 387d1717c8c7a40fa31d6e3a7188686eb3d2d981 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 10:17:19 +0100 Subject: [PATCH 37/87] Add some tests --- .../detectors/oracles/oracle_validate_data.py | 12 +-- ...eck_price_in_double_internal_fc_sol__0.txt | 1 + ...data_check_price_in_internal_fc_sol__0.txt | 1 + ...aCheck_0_8_20_oracle_non_revert_sol__0.txt | 1 + ...data_check_price_in_double_internal_fc.sol | 78 +++++++++++++++ ...price_in_double_internal_fc.sol-0.8.20.zip | Bin 0 -> 6599 bytes ...oracle_data_check_price_in_internal_fc.sol | 4 +- ..._check_price_in_internal_fc.sol-0.8.20.zip | Bin 0 -> 6325 bytes .../oracle/0.8.20/oracle_non_revert.sol | 93 ++++++++++++++++++ .../0.8.20/oracle_non_revert.sol-0.8.20.zip | Bin 0 -> 6729 bytes tests/e2e/detectors/test_detectors.py | 3 + 11 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip rename tests/e2e/detectors/test_data/oracle/{ => 0.8.20}/oracle_data_check_price_in_internal_fc.sol (95%) create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol-0.8.20.zip diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 54d52f42e5..3e2f1bb8e9 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -106,8 +106,7 @@ def return_boolean(self, node: Node) -> bool: if n.type == NodeType.RETURN: for ir in n.irs: if isinstance(ir, Return): - for value in ir.values: - print(value) + return True def check_price( self, var: VarInCondition, oracle: Oracle @@ -128,11 +127,12 @@ def check_price( if ir.type is (BinaryType.LESS): if ir.variable_left.value == 0: return True - if self.check_revert(node): - return True - elif self.return_boolean(node): - return True + if self.check_revert(node): + return True + elif self.return_boolean(node): + return True + return False def generate_naive_order(self): diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt @@ -0,0 +1 @@ + diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt @@ -0,0 +1 @@ + diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt @@ -0,0 +1 @@ + diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol new file mode 100644 index 0000000000..6eaa84684e --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol @@ -0,0 +1,78 @@ +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract StableOracleDAI { + AggregatorV3Interface priceFeedDAIETH; + + constructor() { + priceFeedDAIETH = AggregatorV3Interface( + 0x773616E4d11A78F511299002da57A0a94577F1f4 + ); + } + + function price_check(int price1) internal pure returns (bool) { + if (price_check2(price1)) { + return true; + } + return false; + } + + function price_check2(int price2) internal pure returns (bool) { + if (price2 > 0) { + return true; + } + return false; + } + + function getPriceUSD() external view returns (uint256) { + uint256 wethPriceUSD = 1; + uint256 DAIWethPrice = 1; + + // chainlink price data is 8 decimals for WETH/USD, so multiply by 10 decimals to get 18 decimal fractional + //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeedDAIETH.latestRoundData(); + ( + uint80 roundID, + int256 price, + , + uint256 updatedAt, + uint80 answeredInRound + ) = priceFeedDAIETH.latestRoundData(); + // bool val = price_check(price); + require(price_check(price)); + require(updatedAt - block.timestamp < 500); + require(answeredInRound > roundID); + + return + (wethPriceUSD * 1e18) / + ((DAIWethPrice + uint256(price) * 1e10) / 2); + } +} diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..27314b89973b6ce8b62ae106ef25007f1f2c9fab GIT binary patch literal 6599 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 0) { + function price_check(int price1) internal pure returns (bool) { + if (price1 > 0) { return true; } return false; diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..91cd680282cc1227dfd47305cc64dea75a1e8405 GIT binary patch literal 6325 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol new file mode 100644 index 0000000000..b70c534661 --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract StableOracleDAI { + AggregatorV3Interface priceFeedDAIETH; + + constructor() { + priceFeedDAIETH = AggregatorV3Interface( + 0x773616E4d11A78F511299002da57A0a94577F1f4 + ); + } + + function price_check(int price) internal pure returns (bool) { + if (price > 0) { + return true; + } + return false; + } + + function check_timestamp(uint256 updatedAt) internal view returns (bool) { + if (updatedAt - block.timestamp < 500) { + return true; + } + return false; + } + + function check_roundID( + uint80 roundID, + uint80 answeredInRound + ) internal pure returns (bool) { + if (answeredInRound > roundID) { + return true; + } + return false; + } + + function oracle_call() internal view returns (bool, uint256) { + ( + uint80 roundID, + int256 price, + , + uint256 updatedAt, + uint80 answeredInRound + ) = priceFeedDAIETH.latestRoundData(); + bool errorPrice = price_check(price); + if ( + errorPrice == false || + check_timestamp(updatedAt) == false || + check_roundID(roundID, answeredInRound) == false + ) { + return (false, 0); + } + + return (true, uint256(price)); + } + + function getPriceUSD() external view returns (uint256) { + (bool problem, uint price) = oracle_call(); + require(problem); + return price; + } +} diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..debab63c1d8a1af40c6239376fcde3328f3ffd74 GIT binary patch literal 6729 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 H0RaC4;eZYQ literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 9620a6de3b..a68f7b498c 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1682,6 +1682,9 @@ def id_test(test_item: Test): ##TODO My tests for oracle Test(all_detectors.OracleDataCheck, "oracle_data_check1.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_data_check2.sol", "0.8.20"), + Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_double_internal_fc.sol", "0.8.20"), + Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_internal_fc.sol", "0.8.20"), + Test(all_detectors.OracleDataCheck, "oracle_non_revert.sol", "0.8.20"), ] GENERIC_PATH = "/GENERIC_PATH" From 7452b4d71a056c9b9d2898010c22deaf99bac51c Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 10:39:53 +0100 Subject: [PATCH 38/87] Sequencer --- slither/detectors/all_detectors.py | 1 + slither/detectors/oracles/oracle_sequencer.py | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 slither/detectors/oracles/oracle_sequencer.py diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 8552b74c71..1f5c34dbfd 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -101,3 +101,4 @@ # my detector from .oracles.oracle_validate_data import OracleDataCheck +from .oracles.oracle_sequencer import SequencerCheck diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py new file mode 100644 index 0000000000..65a5ac84b4 --- /dev/null +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -0,0 +1,55 @@ +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.core.expressions import expression +from slither.slithir.operations import Binary, BinaryType +from enum import Enum +from slither.detectors.oracles.oracle import OracleDetector, Oracle, VarInCondition +from slither.slithir.operations.solidity_call import SolidityCall +from slither.core.cfg.node import NodeType +from slither.core.variables.state_variable import StateVariable + +from slither.core.cfg.node import Node, NodeType +from slither.slithir.operations.return_operation import Return +from slither.core.declarations import Function +from slither.core.declarations.function_contract import FunctionContract +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation +from slither.slithir.variables import TupleVariable +from slither.core.expressions.expression import Expression +from typing import List +from slither.slithir.variables.constant import Constant + + +class SequencerCheck(OracleDetector): + + """ + Documentation + """ + + ARGUMENT = "sequencer" # slither will launch the detector with slither.py --detect mydetector + HELP = "Help printed by slither" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.INFORMATIONAL + + WIKI = "RUN" + + WIKI_TITLE = "asda" + WIKI_DESCRIPTION = "asdsad" + WIKI_EXPLOIT_SCENARIO = "asdsad" + WIKI_RECOMMENDATION = "asdsad" + + def _detect(self): + results = [] + output = [] + super()._detect() + if len(self.oracles) > 0: + results.append("The application uses an oracle, if you deploy on second layer as an Arbitrum, you should check if the sequencer is active.") + res = self.generate_result(results) + output.append(res) + return output \ No newline at end of file From fe8e74414a519776906cb321fe60353395a675d1 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 10:49:37 +0100 Subject: [PATCH 39/87] Change text --- slither/detectors/oracles/oracle_sequencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py index 65a5ac84b4..ad3bed00ce 100644 --- a/slither/detectors/oracles/oracle_sequencer.py +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -49,7 +49,7 @@ def _detect(self): output = [] super()._detect() if len(self.oracles) > 0: - results.append("The application uses an oracle, if you deploy on second layer as an Arbitrum, you should check if the sequencer is active.") + results.append("The application uses an oracle, if you deploy on second layer as Arbitrum, you should check if the sequencer is active.") res = self.generate_result(results) output.append(res) return output \ No newline at end of file From de39bed8eeeb9c598ef2c9e703559fcd9fe4f18e Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 5 Jan 2024 11:50:25 +0100 Subject: [PATCH 40/87] Add deprecated call check --- slither/detectors/all_detectors.py | 1 + .../oracles/deprecated_chainlink_calls.py | 45 ++++++++++++++++++ .../oracle/0.8.20/oracle_deprecated_call.sol | 46 +++++++++++++++++++ .../oracle/0.8.20/oracle_non_revert.sol | 2 + 4 files changed, 94 insertions(+) create mode 100644 slither/detectors/oracles/deprecated_chainlink_calls.py create mode 100644 tests/e2e/detectors/test_data/oracle/0.8.20/oracle_deprecated_call.sol diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 1f5c34dbfd..90b44fcb68 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -102,3 +102,4 @@ from .oracles.oracle_validate_data import OracleDataCheck from .oracles.oracle_sequencer import SequencerCheck +from .oracles.deprecated_chainlink_calls import DeprecatedChainlinkCalls diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py new file mode 100644 index 0000000000..2c32b061a7 --- /dev/null +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -0,0 +1,45 @@ +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.core.declarations.contract import Contract +from slither.slithir.operations import HighLevelCall + +class DeprecatedChainlinkCalls(AbstractDetector): + """ + Documentation + """ + + ARGUMENT = "deprecated_chainlink_call" # slither will launch the detector with slither.py --detect mydetector + HELP = "Help printed by slither" + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "RUN" + + WIKI_TITLE = "asda" + WIKI_DESCRIPTION = "Slot0 is vulnerable to price manipulation as it gets price at the current moment. TWAP should be used instead." + WIKI_EXPLOIT_SCENARIO = "asdsad" + WIKI_RECOMMENDATION = "asdsad" + + DEPRECATED_CHAINLINK_CALLS = ["getAnswer", "getTimestamp", "latestAnswer", "latestRound", "latestTimestamp"] + + def find_usage_of_deprecated_chainlink_calls(self, contracts : Contract): + results = [] + for contract in contracts: + for function in contract.functions: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, HighLevelCall): + if ir.function.name in self.DEPRECATED_CHAINLINK_CALLS: + results.append(f"Deprecated Chainlink call {ir.function.name} found in {node.source_mapping}") + return results + + def _detect(self): + results = self.find_usage_of_deprecated_chainlink_calls(self.contracts) + if len(results) > 0: + res = self.generate_result(results) + return [res] + return [] + diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_deprecated_call.sol b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_deprecated_call.sol new file mode 100644 index 0000000000..a64f2f36d8 --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_deprecated_call.sol @@ -0,0 +1,46 @@ +pragma solidity 0.8.20; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestAnswer() external view returns (int256); +} + +contract Oracle { + function getCurrentPrice(address _asset) external view returns (uint256) { + AggregatorV3Interface aggregator = AggregatorV3Interface(_asset); + int256 answer = aggregator.latestAnswer(); + require( + answer > 0, + "ChainlinkOracleManager: No pricing data available" + ); + } +} diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol index b70c534661..b441e71e1d 100644 --- a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol +++ b/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol @@ -1,3 +1,5 @@ +pragma solidity 0.8.20; + // SPDX-License-Identifier: MIT interface AggregatorV3Interface { From e6d6b1b74ad0e39af8759ed375f94e09fee740e9 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 08:19:34 +0100 Subject: [PATCH 41/87] Add interface comparism to aggregatorV3Interface --- slither/detectors/oracles/deprecated_chainlink_calls.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index 2c32b061a7..b108955f8c 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -31,8 +31,8 @@ def find_usage_of_deprecated_chainlink_calls(self, contracts : Contract): for function in contract.functions: for node in function.nodes: for ir in node.irs: - if isinstance(ir, HighLevelCall): - if ir.function.name in self.DEPRECATED_CHAINLINK_CALLS: + if isinstance(ir, HighLevelCall): # TODO ADd interface check + if ir.function.name in self.DEPRECATED_CHAINLINK_CALLS and str(ir.destination.type) == "AggregatorV3Interface": results.append(f"Deprecated Chainlink call {ir.function.name} found in {node.source_mapping}") return results @@ -41,5 +41,4 @@ def _detect(self): if len(results) > 0: res = self.generate_result(results) return [res] - return [] - + return [] \ No newline at end of file From 8378204f6502cfa618a2e53945a492f6df03f075 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 08:45:39 +0100 Subject: [PATCH 42/87] Add some documentation --- .../oracles/deprecated_chainlink_calls.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index b108955f8c..b303ba947b 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -11,29 +11,31 @@ class DeprecatedChainlinkCalls(AbstractDetector): Documentation """ - ARGUMENT = "deprecated_chainlink_call" # slither will launch the detector with slither.py --detect mydetector - HELP = "Help printed by slither" + ARGUMENT = "deprecated_chainlink_call" + HELP = "Oracle vulnerabilities" IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - WIKI = "RUN" - - WIKI_TITLE = "asda" - WIKI_DESCRIPTION = "Slot0 is vulnerable to price manipulation as it gets price at the current moment. TWAP should be used instead." - WIKI_EXPLOIT_SCENARIO = "asdsad" - WIKI_RECOMMENDATION = "asdsad" + WIKI = "TODO: Will be added later." + WIKI_TITLE = "Oracle vulnerabilities" + WIKI_DESCRIPTION = "Detection of deprecated Chainlink calls." + WIKI_RECOMMENDATION = "Do not use deprecated Chainlink calls. Visit https://docs.chain.link/data-feeds/api-reference/ for more information." + WIKI_EXPLOIT_SCENARIO = "" DEPRECATED_CHAINLINK_CALLS = ["getAnswer", "getTimestamp", "latestAnswer", "latestRound", "latestTimestamp"] def find_usage_of_deprecated_chainlink_calls(self, contracts : Contract): + """ + Find usage of deprecated Chainlink calls in the contracts. + """ results = [] for contract in contracts: for function in contract.functions: for node in function.nodes: for ir in node.irs: - if isinstance(ir, HighLevelCall): # TODO ADd interface check + if isinstance(ir, HighLevelCall): if ir.function.name in self.DEPRECATED_CHAINLINK_CALLS and str(ir.destination.type) == "AggregatorV3Interface": - results.append(f"Deprecated Chainlink call {ir.function.name} found in {node.source_mapping}") + results.append(f"Deprecated Chainlink call {ir.destination}.{ir.function.name} used ({node.source_mapping}).\n") return results def _detect(self): From 4a8c4a5905ba9a30182150b74f38b6ad67d03e45 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 09:02:42 +0100 Subject: [PATCH 43/87] Changes in texting sorting and formatting --- slither/detectors/all_detectors.py | 2 +- .../oracles/deprecated_chainlink_calls.py | 47 +++++++++++------- ...l_0_8_20_oracle_deprecated_call_sol__0.txt | 2 + .../0.8.20/oracle_deprecated_call.sol | 0 .../oracle_deprecated_call.sol-0.8.20.zip | Bin 0 -> 4507 bytes tests/e2e/detectors/test_detectors.py | 1 + 6 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_DeprecatedChainlinkCall_0_8_20_oracle_deprecated_call_sol__0.txt rename tests/e2e/detectors/test_data/{oracle => deprecated-chainlink-call}/0.8.20/oracle_deprecated_call.sol (100%) create mode 100644 tests/e2e/detectors/test_data/deprecated-chainlink-call/0.8.20/oracle_deprecated_call.sol-0.8.20.zip diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 90b44fcb68..a618acc890 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -102,4 +102,4 @@ from .oracles.oracle_validate_data import OracleDataCheck from .oracles.oracle_sequencer import SequencerCheck -from .oracles.deprecated_chainlink_calls import DeprecatedChainlinkCalls +from .oracles.deprecated_chainlink_calls import DeprecatedChainlinkCall diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index b303ba947b..4a3a115d86 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -1,30 +1,34 @@ -from slither.detectors.abstract_detector import ( - AbstractDetector, - DetectorClassification, - DETECTOR_INFO, -) from slither.core.declarations.contract import Contract +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) from slither.slithir.operations import HighLevelCall -class DeprecatedChainlinkCalls(AbstractDetector): + +class DeprecatedChainlinkCall(AbstractDetector): """ Documentation """ - ARGUMENT = "deprecated_chainlink_call" + ARGUMENT = "deprecated-chainlink-call" HELP = "Oracle vulnerabilities" - IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.HIGH + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.MEDIUM - WIKI = "TODO: Will be added later." - WIKI_TITLE = "Oracle vulnerabilities" - WIKI_DESCRIPTION = "Detection of deprecated Chainlink calls." - WIKI_RECOMMENDATION = "Do not use deprecated Chainlink calls. Visit https://docs.chain.link/data-feeds/api-reference/ for more information." - WIKI_EXPLOIT_SCENARIO = "" + WIKI = "Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-chainlink-call" + WIKI_TITLE = "Deprecated Chainlink call" + WIKI_DESCRIPTION = "Detect deprecated Chainlink call." + WIKI_RECOMMENDATION = "Do not use deprecated Chainlink calls. Visit https://docs.chain.link/data-feeds/api-reference/ for active API calls." + WIKI_EXPLOIT_SCENARIO = "---" - DEPRECATED_CHAINLINK_CALLS = ["getAnswer", "getTimestamp", "latestAnswer", "latestRound", "latestTimestamp"] + DEPRECATED_CHAINLINK_CALLS = [ + "getAnswer", + "getTimestamp", + "latestAnswer", + "latestRound", + "latestTimestamp", + ] - def find_usage_of_deprecated_chainlink_calls(self, contracts : Contract): + def find_usage_of_deprecated_chainlink_calls(self, contracts: Contract): """ Find usage of deprecated Chainlink calls in the contracts. """ @@ -34,8 +38,13 @@ def find_usage_of_deprecated_chainlink_calls(self, contracts : Contract): for node in function.nodes: for ir in node.irs: if isinstance(ir, HighLevelCall): - if ir.function.name in self.DEPRECATED_CHAINLINK_CALLS and str(ir.destination.type) == "AggregatorV3Interface": - results.append(f"Deprecated Chainlink call {ir.destination}.{ir.function.name} used ({node.source_mapping}).\n") + if ( + ir.function.name in self.DEPRECATED_CHAINLINK_CALLS + and str(ir.destination.type) == "AggregatorV3Interface" + ): + results.append( + f"Deprecated Chainlink call {ir.destination}.{ir.function.name} used ({node.source_mapping}).\n" + ) return results def _detect(self): @@ -43,4 +52,4 @@ def _detect(self): if len(results) > 0: res = self.generate_result(results) return [res] - return [] \ No newline at end of file + return [] diff --git a/tests/e2e/detectors/snapshots/detectors__detector_DeprecatedChainlinkCall_0_8_20_oracle_deprecated_call_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_DeprecatedChainlinkCall_0_8_20_oracle_deprecated_call_sol__0.txt new file mode 100644 index 0000000000..1bf73b4aca --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_DeprecatedChainlinkCall_0_8_20_oracle_deprecated_call_sol__0.txt @@ -0,0 +1,2 @@ +Deprecated Chainlink call aggregator.latestAnswer used (tests/e2e/detectors/test_data/deprecated-chainlink-call/0.8.20/oracle_deprecated_call.sol#40). + diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_deprecated_call.sol b/tests/e2e/detectors/test_data/deprecated-chainlink-call/0.8.20/oracle_deprecated_call.sol similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_deprecated_call.sol rename to tests/e2e/detectors/test_data/deprecated-chainlink-call/0.8.20/oracle_deprecated_call.sol diff --git a/tests/e2e/detectors/test_data/deprecated-chainlink-call/0.8.20/oracle_deprecated_call.sol-0.8.20.zip b/tests/e2e/detectors/test_data/deprecated-chainlink-call/0.8.20/oracle_deprecated_call.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..ddf687a7eb2340337b8f52d885902f1c66b656d3 GIT binary patch literal 4507 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 Y`2UF$BRwMG|6X|i_W8ejO8wvZA1tV*y8r+H literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index a68f7b498c..30384371f3 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1685,6 +1685,7 @@ def id_test(test_item: Test): Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_double_internal_fc.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_internal_fc.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_non_revert.sol", "0.8.20"), + Test(all_detectors.DeprecatedChainlinkCall, "oracle_deprecated_call.sol", "0.8.20"), ] GENERIC_PATH = "/GENERIC_PATH" From 594e25e68fee0882497b287bf631f83b1b9fa547 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 09:03:31 +0100 Subject: [PATCH 44/87] Changes in doc --- slither/detectors/oracles/deprecated_chainlink_calls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index 4a3a115d86..17160cd0c6 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -14,9 +14,9 @@ class DeprecatedChainlinkCall(AbstractDetector): IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = "Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-chainlink-call" + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-chainlink-call" WIKI_TITLE = "Deprecated Chainlink call" - WIKI_DESCRIPTION = "Detect deprecated Chainlink call." + WIKI_DESCRIPTION = "Detection of deprecated Chainlink call." WIKI_RECOMMENDATION = "Do not use deprecated Chainlink calls. Visit https://docs.chain.link/data-feeds/api-reference/ for active API calls." WIKI_EXPLOIT_SCENARIO = "---" From 2e1da07592f66613d97b9e4201f2a8a991714b65 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 09:23:55 +0100 Subject: [PATCH 45/87] Sequencer documentation --- slither/detectors/oracles/oracle_sequencer.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py index ad3bed00ce..35652898c4 100644 --- a/slither/detectors/oracles/oracle_sequencer.py +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -32,24 +32,25 @@ class SequencerCheck(OracleDetector): Documentation """ - ARGUMENT = "sequencer" # slither will launch the detector with slither.py --detect mydetector - HELP = "Help printed by slither" + ARGUMENT = "oracle-sequencer" # slither will launch the detector with slither.py --detect mydetector + HELP = "Oracle vulnerabilities" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.INFORMATIONAL - WIKI = "RUN" + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#oracle-sequencer" - WIKI_TITLE = "asda" - WIKI_DESCRIPTION = "asdsad" - WIKI_EXPLOIT_SCENARIO = "asdsad" - WIKI_RECOMMENDATION = "asdsad" + WIKI_TITLE = "Oracle Sequencer" + WIKI_DESCRIPTION = "Detection of oracle sequencer." + WIKI_EXPLOIT_SCENARIO = "---" + WIKI_RECOMMENDATION = "If you deploy contracts on the second layer as Arbitrum, you should perform an additional check if the sequencer is active. For more information visit https://docs.chain.link/data-feeds/l2-sequencer-feeds#available-networks" def _detect(self): results = [] output = [] super()._detect() if len(self.oracles) > 0: - results.append("The application uses an oracle, if you deploy on second layer as Arbitrum, you should check if the sequencer is active.") + for oracle in self.oracles: + results.append(f"Oracle call to {oracle.interface} ({oracle.node.source_mapping}) is used. Additional checks for sequencer lifeness should be implemented if deployed on the second layer.\n") res = self.generate_result(results) output.append(res) return output \ No newline at end of file From 028e28c79ba1d340b513e4942fefa7d080cbc7da Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 09:27:04 +0100 Subject: [PATCH 46/87] Formatting/Refactoring of sequencer file --- slither/detectors/oracles/oracle_sequencer.py | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py index 35652898c4..36b9999185 100644 --- a/slither/detectors/oracles/oracle_sequencer.py +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -1,29 +1,5 @@ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.declarations.contract import Contract -from slither.core.declarations.function_contract import FunctionContract -from slither.core.expressions import expression -from slither.slithir.operations import Binary, BinaryType -from enum import Enum -from slither.detectors.oracles.oracle import OracleDetector, Oracle, VarInCondition -from slither.slithir.operations.solidity_call import SolidityCall -from slither.core.cfg.node import NodeType -from slither.core.variables.state_variable import StateVariable - -from slither.core.cfg.node import Node, NodeType -from slither.slithir.operations.return_operation import Return -from slither.core.declarations import Function -from slither.core.declarations.function_contract import FunctionContract -from slither.core.variables.state_variable import StateVariable -from slither.detectors.abstract_detector import ( - AbstractDetector, - DetectorClassification, - DETECTOR_INFO, -) -from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation -from slither.slithir.variables import TupleVariable -from slither.core.expressions.expression import Expression -from typing import List -from slither.slithir.variables.constant import Constant +from slither.detectors.abstract_detector import DetectorClassification +from slither.detectors.oracles.oracle import OracleDetector class SequencerCheck(OracleDetector): @@ -32,7 +8,9 @@ class SequencerCheck(OracleDetector): Documentation """ - ARGUMENT = "oracle-sequencer" # slither will launch the detector with slither.py --detect mydetector + ARGUMENT = ( + "oracle-sequencer" # slither will launch the detector with slither.py --detect mydetector + ) HELP = "Oracle vulnerabilities" IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.INFORMATIONAL @@ -50,7 +28,9 @@ def _detect(self): super()._detect() if len(self.oracles) > 0: for oracle in self.oracles: - results.append(f"Oracle call to {oracle.interface} ({oracle.node.source_mapping}) is used. Additional checks for sequencer lifeness should be implemented if deployed on the second layer.\n") + results.append( + f"Oracle call to {oracle.interface} ({oracle.node.source_mapping}) is used. Additional checks for sequencer lifeness should be implemented if deployed on the second layer.\n" + ) res = self.generate_result(results) output.append(res) - return output \ No newline at end of file + return output From 2926d28ca51ee97f7f892f0de3ab747b155709d6 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 09:28:02 +0100 Subject: [PATCH 47/87] Remove WIKI_EXPLOIT_SCENARIO from sequencer --- slither/detectors/oracles/oracle_sequencer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py index 36b9999185..9fe3cd7b7c 100644 --- a/slither/detectors/oracles/oracle_sequencer.py +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -19,7 +19,6 @@ class SequencerCheck(OracleDetector): WIKI_TITLE = "Oracle Sequencer" WIKI_DESCRIPTION = "Detection of oracle sequencer." - WIKI_EXPLOIT_SCENARIO = "---" WIKI_RECOMMENDATION = "If you deploy contracts on the second layer as Arbitrum, you should perform an additional check if the sequencer is active. For more information visit https://docs.chain.link/data-feeds/l2-sequencer-feeds#available-networks" def _detect(self): From e18751af2c8b15a85b4fe9f4e3d1c668bc4b9450 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 09:37:24 +0100 Subject: [PATCH 48/87] Remove unused code, add some comments, formatting --- slither/detectors/oracles/oracle.py | 111 +++++----------------------- 1 file changed, 17 insertions(+), 94 deletions(-) diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index 8e31547734..aa6a226e3a 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -1,35 +1,11 @@ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.declarations.contract import Contract -from slither.core.cfg.node import NodeType -from slither.core.declarations.function_contract import FunctionContract -from slither.core.expressions import expression -from slither.slithir.operations import Binary, BinaryType -from slither.slithir.operations import HighLevelCall -from enum import Enum -from slither.core.cfg.node import Node, NodeType +from slither.analyses.data_dependency.data_dependency import get_dependencies from slither.core.declarations import Function +from slither.core.declarations.contract import Contract from slither.core.declarations.function_contract import FunctionContract from slither.core.variables.state_variable import StateVariable -from slither.detectors.abstract_detector import ( - AbstractDetector, - DetectorClassification, - DETECTOR_INFO, -) -from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation, InternalCall +from slither.detectors.abstract_detector import AbstractDetector +from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack from slither.slithir.variables import TupleVariable -from slither.slithir.variables.reference import ReferenceVariable -from typing import List -from slither.analyses.data_dependency.data_dependency import is_tainted, get_dependencies - -# For debugging -# import debugpy - -# # 5678 is the default attach port in the VS Code debug configurations. Unless a host and port are specified, host defaults to 127.0.0.1 -# debugpy.listen(5678) -# print("Waiting for debugger attach") -# debugpy.wait_for_client() -# debugpy.breakpoint() -# print('break on this line') class Oracle: @@ -43,17 +19,9 @@ def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_va self.vars_not_in_condition = [] self.returned_vars_indexes = _returned_used_vars self.interface = _interface - # self.possible_variables_names = [ - # "price", - # "timestamp", - # "updatedAt", - # "answer", - # "roundID", - # "startedAt", - # ] -class VarInCondition: +class VarInCondition: # This class was created to store variable and all conditional nodes where it is used def __init__(self, _var, _nodes): self.var = _var self.nodes_with_var = _nodes @@ -61,12 +29,10 @@ def __init__(self, _var, _nodes): class OracleDetector(AbstractDetector): - # https://github.com/crytic/slither/wiki/Python-API - # def detect_stale_price(Function): ORACLE_CALLS = [ "latestRoundData", "getRoundData", - ] # Calls i found which are generally used to get data from oracles, based on docs. Mostly it is lastestRoundData + ] # Chainlink oracle calls -> The most used def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: """ @@ -88,14 +54,9 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: if isinstance(ir, HighLevelCall): interface = ir.destination idxs = [] - # if referecne_variable_assigned and isinstance(interface, ReferenceVariable): - # break - # if isinstance(interface, ReferenceVariable): - # referecne_variable_assigned = True for idx in oracle_returned_var_indexes: if idx[0] == node: idxs.append(idx[1]) - # print(node, interface, contract) oracle = Oracle( contract, function, node, node.source_mapping.lines[0], idxs, interface ) @@ -108,11 +69,15 @@ def compare_chainlink_call(self, function) -> bool: return True return False - def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use + def _is_instance(self, ir: Operation) -> bool: return isinstance(ir, HighLevelCall) and ( - isinstance(ir.function, Function) and self.compare_chainlink_call(ir.function.name) + isinstance(ir.function, Function) + and self.compare_chainlink_call( + ir.function.name + ) # Check if the function is a chainlink call ) + # This function was inspired by detector unused return values def check_chainlink_call(self, function: FunctionContract): used_returned_vars = [] values_returned = [] @@ -180,10 +145,8 @@ def var_in_function(self, var, function: FunctionContract): return False + # Check if the vars occurs in require/assert statement or in conditional node def vars_in_conditions(self, oracle: Oracle) -> bool: - """ - Detects if vars from oracles are in some condition - """ vars_in_condition = [] vars_not_in_condition = [] oracle_vars = [] @@ -192,9 +155,7 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: self.nodes_with_var = [] if oracle.function.is_reading_in_conditional_node( var - ) or oracle.function.is_reading_in_require_or_assert( - var - ): # These two functions check if within the function some var is in require/assert of in if statement + ) or oracle.function.is_reading_in_require_or_assert(var): self.nodes_with_var = self.map_condition_to_var(var, oracle.function) for node in self.nodes_with_var: for ir in node.irs: @@ -205,9 +166,7 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: - if self.investigate_internal_call( - oracle.function, var - ): + if self.investigate_internal_call(oracle.function, var): vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: @@ -231,10 +190,11 @@ def is_internal_call(self, node): return True return False + # This function interates through all internal calls in function and checks if the var is used in condition any of them def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: return False - + original_var_as_param = self.map_param_to_var(var, function) if original_var_as_param is None: original_var_as_param = var @@ -262,45 +222,8 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: return True return False - # for functionCalled in function.internal_calls: - # if isinstance(functionCalled, FunctionContract): - # print("functionCalled", functionCalled) - # self.nodes_with_var = self.map_condition_to_var(var, functionCalled) - # if len(self.nodes_with_var) > 0: - # return True - # # for local_var in functionCalled.variables_read: - # # if local_var.name == var.name: - - # # if functionCalled.is_reading_in_conditional_node( - # # local_var - # # ) or functionCalled.is_reading_in_require_or_assert( - # # local_var - # # ): # These two functions check if within the function some var is in require/assert of in if statement - # # return True - # if self.investigate_internal_call(functionCalled, var): - # return True - def _detect(self): - info = [] self.oracles = self.chainlink_oracles(self.contracts) for oracle in self.oracles: oracle.oracle_vars = self.get_returned_variables_from_oracle(oracle.node) self.vars_in_conditions(oracle) - # for oracle in oracles: - # oracle_vars = self.get_returned_variables_from_oracle( - # oracle.function, oracle.line_of_call - # ) - # if not self.check_vars(oracle, oracle_vars): - # rep = "In contract {} a function {} uses oracle {} where the values of vars {} are not checked \n".format( - # oracle.contract.name, - # oracle.function.name, - # oracle.interface_var, - # [var.name for var in oracle.vars_not_in_condition], - # ) - # info.append(rep) - # if len(oracle.vars_in_condition) > 0: - # for var in self.check_conditions_enough(oracle): - # info.append("Problem with {}", var.name) - # res = self.generate_result(info) - - return [] From a3b71dc34fa6f5759a9c5a586e655a2c35003cac Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 10:12:38 +0100 Subject: [PATCH 49/87] Formatting, comments, documentation --- .../oracles/deprecated_chainlink_calls.py | 3 +- slither/detectors/oracles/oracle.py | 22 +++- slither/detectors/oracles/oracle_sequencer.py | 2 +- slither/detectors/oracles/oracle_slot0.py | 5 +- .../detectors/oracles/oracle_validate_data.py | 103 ++++++------------ 5 files changed, 60 insertions(+), 75 deletions(-) diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index 17160cd0c6..21e3f927fb 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -1,6 +1,5 @@ from slither.core.declarations.contract import Contract -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import HighLevelCall diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle.py index aa6a226e3a..36bc88062a 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle.py @@ -9,7 +9,16 @@ class Oracle: - def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_vars, _interface): + def __init__( + self, + _contract, + _function, + _node, + _line_of_call, + _returned_used_vars, + _interface, + _oracle_api, + ): self.contract = _contract self.function = _function self.node = _node @@ -19,6 +28,7 @@ def __init__(self, _contract, _function, _node, _line_of_call, _returned_used_va self.vars_not_in_condition = [] self.returned_vars_indexes = _returned_used_vars self.interface = _interface + self.oracle_api = _oracle_api class VarInCondition: # This class was created to store variable and all conditional nodes where it is used @@ -50,15 +60,23 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: if oracle_calls_in_function: for node in oracle_calls_in_function: interface = None + oracle_api = None for ir in node.irs: if isinstance(ir, HighLevelCall): interface = ir.destination + oracle_api = ir.function.name idxs = [] for idx in oracle_returned_var_indexes: if idx[0] == node: idxs.append(idx[1]) oracle = Oracle( - contract, function, node, node.source_mapping.lines[0], idxs, interface + contract, + function, + node, + node.source_mapping.lines[0], + idxs, + interface, + oracle_api, ) oracles.append(oracle) return oracles diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py index 9fe3cd7b7c..ae4eb31622 100644 --- a/slither/detectors/oracles/oracle_sequencer.py +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -28,7 +28,7 @@ def _detect(self): if len(self.oracles) > 0: for oracle in self.oracles: results.append( - f"Oracle call to {oracle.interface} ({oracle.node.source_mapping}) is used. Additional checks for sequencer lifeness should be implemented if deployed on the second layer.\n" + f"Oracle call to {oracle.contract}.{oracle.interface} ({oracle.node.source_mapping}) is used. Additional checks for sequencer lifeness should be implemented if deployed on the second layer.\n" ) res = self.generate_result(results) output.append(res) diff --git a/slither/detectors/oracles/oracle_slot0.py b/slither/detectors/oracles/oracle_slot0.py index f680bbc20c..4731151eb8 100644 --- a/slither/detectors/oracles/oracle_slot0.py +++ b/slither/detectors/oracles/oracle_slot0.py @@ -1,10 +1,9 @@ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.core.declarations.contract import Contract -from slither.core.cfg.node import NodeType -from slither.core.declarations.function_contract import FunctionContract +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.oracles.oracle import Oracle +# TODO: Not ready for publishing class OracleSlot0(AbstractDetector): """ Documentation diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_validate_data.py index 3e2f1bb8e9..57e9c9a308 100644 --- a/slither/detectors/oracles/oracle_validate_data.py +++ b/slither/detectors/oracles/oracle_validate_data.py @@ -1,28 +1,14 @@ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.declarations.contract import Contract -from slither.core.declarations.function_contract import FunctionContract -from slither.core.expressions import expression -from slither.slithir.operations import Binary, BinaryType from enum import Enum -from slither.detectors.oracles.oracle import OracleDetector, Oracle, VarInCondition -from slither.slithir.operations.solidity_call import SolidityCall -from slither.core.cfg.node import NodeType -from slither.core.variables.state_variable import StateVariable from slither.core.cfg.node import Node, NodeType -from slither.slithir.operations.return_operation import Return -from slither.core.declarations import Function -from slither.core.declarations.function_contract import FunctionContract -from slither.core.variables.state_variable import StateVariable -from slither.detectors.abstract_detector import ( - AbstractDetector, - DetectorClassification, - DETECTOR_INFO, +from slither.detectors.abstract_detector import DetectorClassification +from slither.detectors.oracles.oracle import Oracle, OracleDetector, VarInCondition +from slither.slithir.operations import ( + Binary, + BinaryType, ) -from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation -from slither.slithir.variables import TupleVariable -from slither.core.expressions.expression import Expression -from typing import List +from slither.slithir.operations.return_operation import Return +from slither.slithir.operations.solidity_call import SolidityCall from slither.slithir.variables.constant import Constant @@ -39,43 +25,32 @@ class OracleDataCheck(OracleDetector): Documentation """ - ARGUMENT = "oracle" # slither will launch the detector with slither.py --detect mydetector - HELP = "Help printed by slither" + ARGUMENT = "oracle-data-validation" # slither will launch the detector with slither.py --detect mydetector + HELP = "Oracle vulnerabilities" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM - WIKI = "RUN" + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#oracle-data-validation" - WIKI_TITLE = "asda" - WIKI_DESCRIPTION = "asdsad" - WIKI_EXPLOIT_SCENARIO = "asdsad" - WIKI_RECOMMENDATION = "asdsad" + WIKI_TITLE = "Oracle data validation" + WIKI_DESCRIPTION = "The detection of not correct validation of oracle data." + WIKI_EXPLOIT_SCENARIO = "---" + WIKI_RECOMMENDATION = "Validate the data returned by the oracle. For more information visit https://docs.chain.link/data-feeds/api-reference" + # This function checks if the updatedAt value is validated. def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False for node in var.nodes_with_var: str_node = str(node) - # print(str_node) - if ( - "block.timestamp" in str_node - ): # TODO maybe try something like block.timestamp - updatedAt < b + # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly + if "block.timestamp" in str_node: return True - - # for ir in node.irs: - # if isinstance(ir, Binary): - # if ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): - # if node.contains_require_or_assert(): - # pass - # elif node.contains_conditional(): - # pass - # elif ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): - # pass return False - def check_RoundId( - self, var: VarInCondition, var2: VarInCondition - ) -> bool: # https://solodit.xyz/issues/chainlink-oracle-return-values-are-not-handled-property-halborn-savvy-defi-pdf + # This function checks if the RoundId value is validated in connection with answeredInRound value + # But this last variable was deprecated. We left this function for possible future use. + def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: if var is None or var2 is None: return False for node in var.nodes_with_var: @@ -89,6 +64,8 @@ def check_RoundId( return True if self.check_revert(node): return True + elif self.return_boolean(node): + return True return False @@ -108,12 +85,11 @@ def return_boolean(self, node: Node) -> bool: if isinstance(ir, Return): return True - def check_price( - self, var: VarInCondition, oracle: Oracle - ) -> bool: # TODO I need to divie require or IF + # This functions validates checks of price value + def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: if var is None: return False - for node in var.nodes_with_var: # TODO testing + for node in var.nodes_with_var: for ir in node.irs: if isinstance(ir, Binary): if isinstance(ir.variable_right, Constant): @@ -127,12 +103,12 @@ def check_price( if ir.type is (BinaryType.LESS): if ir.variable_left.value == 0: return True + # If the conditions does not match we are looking for revert or return node if self.check_revert(node): return True elif self.return_boolean(node): return True - return False def generate_naive_order(self): @@ -181,42 +157,36 @@ def naive_check(self, oracle: Oracle): for (index, var) in vars_order.items(): if not self.is_needed_to_check_conditions(oracle, var): continue - # if index == OracleVarType.ROUNDID.value: #TODO this is maybe not so mandatory + # if index == OracleVarType.ROUNDID.value: # Commented due to deprecation of AnsweredInRound # if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) if index == OracleVarType.ANSWER.value: if not self.check_price(var, oracle): problems.append( - "Price value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( - oracle.function, oracle.node.source_mapping - ) + f"The price value is validated incorrectly. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" ) elif index == OracleVarType.UPDATEDAT.value: if not self.check_staleness(var): problems.append( - "UpdatedAt value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( - oracle.function, oracle.node.source_mapping - ) + f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" ) elif ( index == OracleVarType.STARTEDAT.value and vars_order[OracleVarType.STARTEDAT.value] is not None ): + # If the startedAt is not None. We are checking if the oracle is a sequencer to ignore incorrect results. if self.is_sequencer_check(vars_order[OracleVarType.ANSWER.value], var): - problems = [] # TODO send some hook to another detector + problems = [] break return problems + # This function is necessary even though there is a detector for unused return values because the variable can be used but will not be validated in conditional statements def process_not_checked_vars(self): result = [] for oracle in self.oracles: if len(oracle.vars_not_in_condition) > 0: result.append( - "In contract `{}` a function `{}` uses oracle where the values of vars {} are not checked. This can potentially lead to a problem! \n".format( - oracle.contract.name, - oracle.function.name, - [var.name for var in oracle.vars_not_in_condition], - ) + f"The oracle {oracle.contract}.{oracle.interface} ({oracle.node.source_mapping}) returns the variables {[var.name for var in oracle.vars_not_in_condition]} which are not validated. It can potentially lead to unexpected behaviour.\n" ) return result @@ -224,13 +194,12 @@ def _detect(self): results = [] super()._detect() not_checked_vars = self.process_not_checked_vars() - res = self.generate_result(not_checked_vars) - results.append(res) + if len(not_checked_vars) > 0: + res = self.generate_result(not_checked_vars) + results.append(res) for oracle in self.oracles: checked_vars = self.naive_check(oracle) if len(checked_vars) > 0: res = self.generate_result(checked_vars) results.append(res) - # res = self.generate_result(self.process_checked_vars()) - # results.append(res) return results From 58b195fa9594bfff9636789c3f89a4fc5a8e1a42 Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 10:15:20 +0100 Subject: [PATCH 50/87] Change naming in testing and snapshots based on changed comments --- ...leDataCheck_0_8_20_oracle_data_check1_sol__0.txt | 4 ++-- ...leDataCheck_0_8_20_oracle_data_check2_sol__0.txt | 3 +-- ...ata_check_price_in_double_internal_fc_sol__0.txt | 1 - ...racle_data_check_price_in_internal_fc_sol__0.txt | 1 - ...cleDataCheck_0_8_20_oracle_non_revert_sol__0.txt | 1 - .../0.8.20/oracle_data_check1.sol | 0 .../0.8.20/oracle_data_check1.sol-0.8.20.zip | Bin .../0.8.20/oracle_data_check2.sol | 0 .../0.8.20/oracle_data_check2.sol-0.8.20.zip | Bin ...racle_data_check_price_in_double_internal_fc.sol | 0 ...check_price_in_double_internal_fc.sol-0.8.20.zip | Bin .../oracle_data_check_price_in_internal_fc.sol | 0 ...e_data_check_price_in_internal_fc.sol-0.8.20.zip | Bin .../0.8.20/oracle_non_revert.sol | 0 .../0.8.20/oracle_non_revert.sol-0.8.20.zip | Bin 15 files changed, 3 insertions(+), 7 deletions(-) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check1.sol (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check1.sol-0.8.20.zip (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check2.sol (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check2.sol-0.8.20.zip (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check_price_in_double_internal_fc.sol (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check_price_in_internal_fc.sol (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_non_revert.sol (100%) rename tests/e2e/detectors/test_data/{oracle => oracle-data-validation}/0.8.20/oracle_non_revert.sol-0.8.20.zip (100%) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt index 2c8d827159..f95daeacf3 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check1_sol__0.txt @@ -1,4 +1,4 @@ -In contract `OracleWithoutChecks` a function `getPriceUSD` uses oracle where the values of vars ['price'] are not checked. This can potentially lead to a problem! +The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call OracleWithoutChecks.priceFeed.latestRoundData (test_data/oracle/0.8.20/oracle_data_check1.sol#46). -UpdatedAt value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check1.sol#46. +The oracle OracleWithoutChecks.priceFeed (test_data/oracle/0.8.20/oracle_data_check1.sol#46) returns the variables ['price'] which are not validated. It can potentially lead to unexpected behaviour. diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt index 384a5ffcfe..db5c3b493f 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check2_sol__0.txt @@ -1,3 +1,2 @@ - -Price value is not checked correctly. It was returned by the oracle call in the function getPriceUSD of contract test_data/oracle/0.8.20/oracle_data_check2.sol#55-61. +The price value is validated incorrectly. This value is returned by Chainlink oracle call StableOracleDAI.priceFeedDAIETH.latestRoundData (test_data/oracle/0.8.20/oracle_data_check2.sol#55-61). diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt index 8b13789179..e69de29bb2 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_double_internal_fc_sol__0.txt @@ -1 +0,0 @@ - diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt index 8b13789179..e69de29bb2 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_data_check_price_in_internal_fc_sol__0.txt @@ -1 +0,0 @@ - diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt index 8b13789179..e69de29bb2 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_non_revert_sol__0.txt @@ -1 +0,0 @@ - diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check1.sol similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check1.sol diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check1.sol-0.8.20.zip similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check1.sol-0.8.20.zip rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check1.sol-0.8.20.zip diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check2.sol similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check2.sol diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check2.sol-0.8.20.zip similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check2.sol-0.8.20.zip rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check2.sol-0.8.20.zip diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol-0.8.20.zip diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol-0.8.20.zip diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_non_revert.sol similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_non_revert.sol diff --git a/tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_non_revert.sol-0.8.20.zip similarity index 100% rename from tests/e2e/detectors/test_data/oracle/0.8.20/oracle_non_revert.sol-0.8.20.zip rename to tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_non_revert.sol-0.8.20.zip From 716212a41ad9d083a19760b2860d6fe6e11ea35a Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 10:23:11 +0100 Subject: [PATCH 51/87] Renaming --- slither/detectors/all_detectors.py | 2 +- .../{oracle_validate_data.py => oracle_data_validation.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename slither/detectors/oracles/{oracle_validate_data.py => oracle_data_validation.py} (100%) diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index a618acc890..0c9cfb2837 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -100,6 +100,6 @@ # my detector -from .oracles.oracle_validate_data import OracleDataCheck +from .oracles.oracle_data_validation import OracleDataCheck from .oracles.oracle_sequencer import SequencerCheck from .oracles.deprecated_chainlink_calls import DeprecatedChainlinkCall diff --git a/slither/detectors/oracles/oracle_validate_data.py b/slither/detectors/oracles/oracle_data_validation.py similarity index 100% rename from slither/detectors/oracles/oracle_validate_data.py rename to slither/detectors/oracles/oracle_data_validation.py From b3e798eaf60925b3a656312e3bab65f798b2932e Mon Sep 17 00:00:00 2001 From: Talfao Date: Thu, 25 Jan 2024 10:24:12 +0100 Subject: [PATCH 52/87] Remove unnecessary comment --- slither/detectors/all_detectors.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 0c9cfb2837..56f8d7fc72 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -97,9 +97,6 @@ from .operations.incorrect_exp import IncorrectOperatorExponentiation from .statements.tautological_compare import TautologicalCompare from .statements.return_bomb import ReturnBomb - -# my detector - from .oracles.oracle_data_validation import OracleDataCheck from .oracles.oracle_sequencer import SequencerCheck from .oracles.deprecated_chainlink_calls import DeprecatedChainlinkCall From 5713e8b1abf4b1e67ed34160de46d63e50961a7a Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 26 Jan 2024 09:47:18 +0100 Subject: [PATCH 53/87] First restruct problems --- .../oracles/oracle_data_validation.py | 2 +- .../oracles/{oracle.py => oracle_detector.py} | 90 ++++++------------- slither/detectors/oracles/oracle_sequencer.py | 2 +- slither/detectors/oracles/oracle_slot0.py | 2 +- .../oracles/supported_oracles/__init__.py | 0 .../supported_oracles/chainlink_oracle.py | 17 ++++ .../oracles/supported_oracles/oracle.py | 46 ++++++++++ .../supported_oracles/tellor_oracle.py | 0 8 files changed, 92 insertions(+), 67 deletions(-) rename slither/detectors/oracles/{oracle.py => oracle_detector.py} (77%) create mode 100644 slither/detectors/oracles/supported_oracles/__init__.py create mode 100644 slither/detectors/oracles/supported_oracles/chainlink_oracle.py create mode 100644 slither/detectors/oracles/supported_oracles/oracle.py create mode 100644 slither/detectors/oracles/supported_oracles/tellor_oracle.py diff --git a/slither/detectors/oracles/oracle_data_validation.py b/slither/detectors/oracles/oracle_data_validation.py index 57e9c9a308..7cc0441c48 100644 --- a/slither/detectors/oracles/oracle_data_validation.py +++ b/slither/detectors/oracles/oracle_data_validation.py @@ -2,7 +2,7 @@ from slither.core.cfg.node import Node, NodeType from slither.detectors.abstract_detector import DetectorClassification -from slither.detectors.oracles.oracle import Oracle, OracleDetector, VarInCondition +from slither.detectors.oracles.oracle_detector import Oracle, OracleDetector, VarInCondition from slither.slithir.operations import ( Binary, BinaryType, diff --git a/slither/detectors/oracles/oracle.py b/slither/detectors/oracles/oracle_detector.py similarity index 77% rename from slither/detectors/oracles/oracle.py rename to slither/detectors/oracles/oracle_detector.py index 36bc88062a..50ac56be50 100644 --- a/slither/detectors/oracles/oracle.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -6,29 +6,12 @@ from slither.detectors.abstract_detector import AbstractDetector from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack from slither.slithir.variables import TupleVariable +from slither.detectors.oracles.supported_oracles.chainlink_oracle import ChainlinkOracle +from enum import Enum - -class Oracle: - def __init__( - self, - _contract, - _function, - _node, - _line_of_call, - _returned_used_vars, - _interface, - _oracle_api, - ): - self.contract = _contract - self.function = _function - self.node = _node - self.line_of_call = _line_of_call # can be get by node.source_mapping.lines[0] - self.oracle_vars = [] - self.vars_in_condition = [] - self.vars_not_in_condition = [] - self.returned_vars_indexes = _returned_used_vars - self.interface = _interface - self.oracle_api = _oracle_api +class SupportedOracles(Enum): + CHAINLINK = 0 + TELLOR = 1 class VarInCondition: # This class was created to store variable and all conditional nodes where it is used @@ -39,12 +22,7 @@ def __init__(self, _var, _nodes): class OracleDetector(AbstractDetector): - ORACLE_CALLS = [ - "latestRoundData", - "getRoundData", - ] # Chainlink oracle calls -> The most used - - def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: + def find_oracles(self, contracts: Contract) -> list[Oracle]: """ Detects off-chain oracle contract and VAR """ @@ -54,57 +32,44 @@ def chainlink_oracles(self, contracts: Contract) -> list[Oracle]: if function.is_constructor: continue ( - oracle_calls_in_function, + returned_oracles, oracle_returned_var_indexes, - ) = self.check_chainlink_call(function) - if oracle_calls_in_function: - for node in oracle_calls_in_function: + ) = self.oracle_call(function) + if returned_oracles: + for oracle in returned_oracles: interface = None oracle_api = None - for ir in node.irs: + for ir in oracle.node.irs: if isinstance(ir, HighLevelCall): interface = ir.destination oracle_api = ir.function.name idxs = [] for idx in oracle_returned_var_indexes: - if idx[0] == node: + if idx[0] == oracle.node: idxs.append(idx[1]) - oracle = Oracle( - contract, - function, - node, - node.source_mapping.lines[0], - idxs, - interface, - oracle_api, - ) + oracle.set_data(contract, function, idxs, interface, oracle_api) oracles.append(oracle) return oracles - def compare_chainlink_call(self, function) -> bool: - for call in self.ORACLE_CALLS: - if call in str(function): - return True - return False - def _is_instance(self, ir: Operation) -> bool: - return isinstance(ir, HighLevelCall) and ( - isinstance(ir.function, Function) - and self.compare_chainlink_call( - ir.function.name - ) # Check if the function is a chainlink call - ) + def generate_oracle(self, ir: Operation) -> Oracle: + if ChainlinkOracle().is_instance_of(ir): + return ChainlinkOracle() + return None + # This function was inspired by detector unused return values - def check_chainlink_call(self, function: FunctionContract): + def oracle_call(self, function: FunctionContract): used_returned_vars = [] values_returned = [] nodes_origin = {} - oracle_calls = [] + oracles = [] for node in function.nodes: for ir in node.irs: - if self._is_instance(ir): - oracle_calls.append(node) + oracle = self.generate_oracle(ir) + if oracle: + oracle.set_node(node) + oracles.append(oracle) if ir.lvalue and not isinstance(ir.lvalue, StateVariable): values_returned.append((ir.lvalue, None)) nodes_origin[ir.lvalue] = ir @@ -126,7 +91,7 @@ def check_chainlink_call(self, function: FunctionContract): returned_vars_used_indexes = [] for (value, index) in used_returned_vars: returned_vars_used_indexes.append((nodes_origin[value].node, index)) - return oracle_calls, returned_vars_used_indexes + return oracles, returned_vars_used_indexes def get_returned_variables_from_oracle(self, node) -> list: written_vars = [] @@ -159,9 +124,6 @@ def map_condition_to_var(self, var, function: FunctionContract): nodes.append(node) return nodes - def var_in_function(self, var, function: FunctionContract): - - return False # Check if the vars occurs in require/assert statement or in conditional node def vars_in_conditions(self, oracle: Oracle) -> bool: @@ -241,7 +203,7 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: return False def _detect(self): - self.oracles = self.chainlink_oracles(self.contracts) + self.oracles = self.find_oracles(self.contracts) for oracle in self.oracles: oracle.oracle_vars = self.get_returned_variables_from_oracle(oracle.node) self.vars_in_conditions(oracle) diff --git a/slither/detectors/oracles/oracle_sequencer.py b/slither/detectors/oracles/oracle_sequencer.py index ae4eb31622..d7067d137d 100644 --- a/slither/detectors/oracles/oracle_sequencer.py +++ b/slither/detectors/oracles/oracle_sequencer.py @@ -1,5 +1,5 @@ from slither.detectors.abstract_detector import DetectorClassification -from slither.detectors.oracles.oracle import OracleDetector +from slither.detectors.oracles.oracle_detector import OracleDetector class SequencerCheck(OracleDetector): diff --git a/slither/detectors/oracles/oracle_slot0.py b/slither/detectors/oracles/oracle_slot0.py index 4731151eb8..962bfce11f 100644 --- a/slither/detectors/oracles/oracle_slot0.py +++ b/slither/detectors/oracles/oracle_slot0.py @@ -1,6 +1,6 @@ from slither.core.declarations.contract import Contract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.detectors.oracles.oracle import Oracle +from slither.detectors.oracles.oracle_detector import Oracle # TODO: Not ready for publishing diff --git a/slither/detectors/oracles/supported_oracles/__init__.py b/slither/detectors/oracles/supported_oracles/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py new file mode 100644 index 0000000000..48320604da --- /dev/null +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -0,0 +1,17 @@ +from enum import Enum +from slither.detectors.oracles.supported_oracles.oracle import Oracle + +CHAINLINK_ORACLE_CALLS = ["latestRoundData","getRoundData",] + +class ChainlinkVars(Enum): + ROUNDID = 0 + ANSWER = 1 + STARTEDAT = 2 + UPDATEDAT = 3 + ANSWEREDINROUND = 4 + + +class ChainlinkOracle(Oracle): + def __init__(self): + super().__init__(CHAINLINK_ORACLE_CALLS) + self.oracle_type = "Chainlink" diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py new file mode 100644 index 0000000000..f14d34fbff --- /dev/null +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -0,0 +1,46 @@ +from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack + +class Oracle: + def __init__( + self, + _calls + ): + self.calls = _calls + self.contract = None + self.function = None + self.node = None + self.oracle_vars = [] + self.vars_in_condition = [] + self.vars_not_in_condition = [] + self.returned_vars_indexes = None + self.interface = None + self.oracle_api = None + self.oracle_type = None + + def get_calls(self): + return self.calls + + def set_node(self, _node): + self.node = _node + + def compare_call(self, function) -> bool: + for call in self.calls: + if call in str(function): + return True + return False + + def is_instance_of(self, ir: Operation) -> bool: + return isinstance(ir, HighLevelCall) and ( + isinstance(ir.function, Function) + and self.compare_call( + ir.function.name + ) + ) + + def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _oracle_api): + self.contract = _contract + self.function = _function + self.returned_vars_indexes = _returned_vars_indexes + self.interface = _interface + self.oracle_api = _oracle_api + diff --git a/slither/detectors/oracles/supported_oracles/tellor_oracle.py b/slither/detectors/oracles/supported_oracles/tellor_oracle.py new file mode 100644 index 0000000000..e69de29bb2 From 59236b6412527fd11a077c70b5c4e0c3735eadd4 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 26 Jan 2024 09:49:06 +0100 Subject: [PATCH 54/87] Successfull restruct --- slither/detectors/oracles/oracle_detector.py | 1 + slither/detectors/oracles/supported_oracles/oracle.py | 1 + 2 files changed, 2 insertions(+) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 50ac56be50..c52d9a3e34 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import AbstractDetector from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack from slither.slithir.variables import TupleVariable +from slither.detectors.oracles.supported_oracles.oracle import Oracle from slither.detectors.oracles.supported_oracles.chainlink_oracle import ChainlinkOracle from enum import Enum diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index f14d34fbff..4327bea027 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -1,4 +1,5 @@ from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack +from slither.core.declarations import Function class Oracle: def __init__( From 3d546f69cb1157d2c18f0068de94901485f415fd Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 26 Jan 2024 11:15:05 +0100 Subject: [PATCH 55/87] Data valid works for chainlink after restruct --- .../oracles/oracle_data_validation.py | 160 +----------------- slither/detectors/oracles/oracle_detector.py | 7 +- .../supported_oracles/chainlink_oracle.py | 140 ++++++++++++++- .../oracles/supported_oracles/oracle.py | 32 +++- 4 files changed, 174 insertions(+), 165 deletions(-) diff --git a/slither/detectors/oracles/oracle_data_validation.py b/slither/detectors/oracles/oracle_data_validation.py index 7cc0441c48..912ec89abe 100644 --- a/slither/detectors/oracles/oracle_data_validation.py +++ b/slither/detectors/oracles/oracle_data_validation.py @@ -1,23 +1,12 @@ from enum import Enum -from slither.core.cfg.node import Node, NodeType + from slither.detectors.abstract_detector import DetectorClassification from slither.detectors.oracles.oracle_detector import Oracle, OracleDetector, VarInCondition -from slither.slithir.operations import ( - Binary, - BinaryType, -) -from slither.slithir.operations.return_operation import Return -from slither.slithir.operations.solidity_call import SolidityCall -from slither.slithir.variables.constant import Constant -class OracleVarType(Enum): - ROUNDID = 0 - ANSWER = 1 - STARTEDAT = 2 - UPDATEDAT = 3 - ANSWEREDINROUND = 4 + + class OracleDataCheck(OracleDetector): @@ -37,148 +26,7 @@ class OracleDataCheck(OracleDetector): WIKI_EXPLOIT_SCENARIO = "---" WIKI_RECOMMENDATION = "Validate the data returned by the oracle. For more information visit https://docs.chain.link/data-feeds/api-reference" - # This function checks if the updatedAt value is validated. - def check_staleness(self, var: VarInCondition) -> bool: - if var is None: - return False - for node in var.nodes_with_var: - str_node = str(node) - # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly - if "block.timestamp" in str_node: - return True - return False - - # This function checks if the RoundId value is validated in connection with answeredInRound value - # But this last variable was deprecated. We left this function for possible future use. - def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: - if var is None or var2 is None: - return False - for node in var.nodes_with_var: - for ir in node.irs: - if isinstance(ir, Binary): - if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): - if ir.variable_right == var.var and ir.variable_left == var2.var: - return True - elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): - if ir.variable_right == var2.var and ir.variable_left == var.var: - return True - if self.check_revert(node): - return True - elif self.return_boolean(node): - return True - - return False - - def check_revert(self, node: Node) -> bool: - for n in node.sons: - if n.type == NodeType.EXPRESSION: - for ir in n.irs: - if isinstance(ir, SolidityCall): - if "revert" in ir.function.name: - return True - return False - - def return_boolean(self, node: Node) -> bool: - for n in node.sons: - if n.type == NodeType.RETURN: - for ir in n.irs: - if isinstance(ir, Return): - return True - - # This functions validates checks of price value - def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: - if var is None: - return False - for node in var.nodes_with_var: - for ir in node.irs: - if isinstance(ir, Binary): - if isinstance(ir.variable_right, Constant): - if ir.type is (BinaryType.GREATER): - if ir.variable_right.value == 0: - return True - elif ir.type is (BinaryType.NOT_EQUAL): - if ir.variable_right.value == 0: - return True - if isinstance(ir.variable_left, Constant): - if ir.type is (BinaryType.LESS): - if ir.variable_left.value == 0: - return True - # If the conditions does not match we are looking for revert or return node - if self.check_revert(node): - return True - elif self.return_boolean(node): - return True - - return False - - def generate_naive_order(self): - vars_order = {} - vars_order[OracleVarType.ROUNDID.value] = None - vars_order[OracleVarType.ANSWER.value] = None - vars_order[OracleVarType.STARTEDAT.value] = None - vars_order[OracleVarType.UPDATEDAT.value] = None - vars_order[OracleVarType.ANSWEREDINROUND.value] = None - return vars_order - - def find_which_vars_are_used(self, oracle: Oracle): - vars_order = self.generate_naive_order() - for i in range(len(oracle.oracle_vars)): - vars_order[oracle.returned_vars_indexes[i]] = oracle.oracle_vars[i] - return vars_order - - def is_needed_to_check_conditions(self, oracle, var): - if isinstance(var, VarInCondition): - var = var.var - if var in oracle.vars_not_in_condition: - return False - return True - - def is_sequencer_check(self, answer, startedAt): - if answer is None or startedAt is None: - return False - answer_checked = False - startedAt_checked = False - - for node in answer.nodes: - for ir in node.irs: - if isinstance(ir, Binary): - if ir.type is (BinaryType.EQUAL): - if isinstance(ir.variable_right, Constant): - if ir.variable_right.value == 1: - answer_checked = True - startedAt_checked = self.check_staleness(startedAt) - print(answer_checked, startedAt_checked) - - return answer_checked and startedAt_checked - def naive_check(self, oracle: Oracle): - vars_order = self.find_which_vars_are_used(oracle) - problems = [] - for (index, var) in vars_order.items(): - if not self.is_needed_to_check_conditions(oracle, var): - continue - # if index == OracleVarType.ROUNDID.value: # Commented due to deprecation of AnsweredInRound - # if not self.check_RoundId(var, vars_order[OracleVarType.ANSWEREDINROUND.value]): - # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) - if index == OracleVarType.ANSWER.value: - if not self.check_price(var, oracle): - problems.append( - f"The price value is validated incorrectly. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" - ) - elif index == OracleVarType.UPDATEDAT.value: - if not self.check_staleness(var): - problems.append( - f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" - ) - elif ( - index == OracleVarType.STARTEDAT.value - and vars_order[OracleVarType.STARTEDAT.value] is not None - ): - # If the startedAt is not None. We are checking if the oracle is a sequencer to ignore incorrect results. - if self.is_sequencer_check(vars_order[OracleVarType.ANSWER.value], var): - problems = [] - break - return problems # This function is necessary even though there is a detector for unused return values because the variable can be used but will not be validated in conditional statements def process_not_checked_vars(self): @@ -198,7 +46,7 @@ def _detect(self): res = self.generate_result(not_checked_vars) results.append(res) for oracle in self.oracles: - checked_vars = self.naive_check(oracle) + checked_vars = oracle.naive_data_validation() if len(checked_vars) > 0: res = self.generate_result(checked_vars) results.append(res) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index c52d9a3e34..6cc8aaee8b 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -6,7 +6,7 @@ from slither.detectors.abstract_detector import AbstractDetector from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack from slither.slithir.variables import TupleVariable -from slither.detectors.oracles.supported_oracles.oracle import Oracle +from slither.detectors.oracles.supported_oracles.oracle import Oracle, VarInCondition from slither.detectors.oracles.supported_oracles.chainlink_oracle import ChainlinkOracle from enum import Enum @@ -15,10 +15,7 @@ class SupportedOracles(Enum): TELLOR = 1 -class VarInCondition: # This class was created to store variable and all conditional nodes where it is used - def __init__(self, _var, _nodes): - self.var = _var - self.nodes_with_var = _nodes + class OracleDetector(AbstractDetector): diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 48320604da..e869b0fa30 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -1,8 +1,14 @@ from enum import Enum -from slither.detectors.oracles.supported_oracles.oracle import Oracle +from slither.detectors.oracles.supported_oracles.oracle import Oracle, VarInCondition +from slither.slithir.operations import ( + Binary, + BinaryType, +) + +from slither.slithir.variables.constant import Constant -CHAINLINK_ORACLE_CALLS = ["latestRoundData","getRoundData",] +CHAINLINK_ORACLE_CALLS = ["latestRoundData","getRoundData",] class ChainlinkVars(Enum): ROUNDID = 0 ANSWER = 1 @@ -15,3 +21,133 @@ class ChainlinkOracle(Oracle): def __init__(self): super().__init__(CHAINLINK_ORACLE_CALLS) self.oracle_type = "Chainlink" + + + # This function checks if the updatedAt value is validated. + def check_staleness(self, var: VarInCondition) -> bool: + if var is None: + return False + for node in var.nodes_with_var: + str_node = str(node) + # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly + if "block.timestamp" in str_node: + return True + return False + + # This function checks if the RoundId value is validated in connection with answeredInRound value + # But this last variable was deprecated. We left this function for possible future use. + def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: + if var is None or var2 is None: + return False + for node in var.nodes_with_var: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): + if ir.variable_right == var.var and ir.variable_left == var2.var: + return True + elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): + if ir.variable_right == var2.var and ir.variable_left == var.var: + return True + if self.check_revert(node): + return True + elif self.return_boolean(node): + return True + + return False + + + # This functions validates checks of price value + def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: + if var is None: + return False + for node in var.nodes_with_var: + for ir in node.irs: + if isinstance(ir, Binary): + if isinstance(ir.variable_right, Constant): + if ir.type is (BinaryType.GREATER): + if ir.variable_right.value == 0: + return True + elif ir.type is (BinaryType.NOT_EQUAL): + if ir.variable_right.value == 0: + return True + if isinstance(ir.variable_left, Constant): + if ir.type is (BinaryType.LESS): + if ir.variable_left.value == 0: + return True + # If the conditions does not match we are looking for revert or return node + if self.check_revert(node): + return True + elif self.return_boolean(node): + return True + + return False + + def generate_naive_order(self): + vars_order = {} + vars_order[ChainlinkVars.ROUNDID.value] = None + vars_order[ChainlinkVars.ANSWER.value] = None + vars_order[ChainlinkVars.STARTEDAT.value] = None + vars_order[ChainlinkVars.UPDATEDAT.value] = None + vars_order[ChainlinkVars.ANSWEREDINROUND.value] = None + return vars_order + + def find_which_vars_are_used(self, oracle: Oracle): + vars_order = self.generate_naive_order() + for i in range(len(oracle.oracle_vars)): + vars_order[oracle.returned_vars_indexes[i]] = oracle.oracle_vars[i] + return vars_order + + def is_needed_to_check_conditions(self, oracle, var): + if isinstance(var, VarInCondition): + var = var.var + if var in oracle.vars_not_in_condition: + return False + return True + + def is_sequencer_check(self, answer, startedAt): + if answer is None or startedAt is None: + return False + answer_checked = False + startedAt_checked = False + + for node in answer.nodes: + for ir in node.irs: + if isinstance(ir, Binary): + if ir.type is (BinaryType.EQUAL): + if isinstance(ir.variable_right, Constant): + if ir.variable_right.value == 1: + answer_checked = True + startedAt_checked = self.check_staleness(startedAt) + print(answer_checked, startedAt_checked) + + return answer_checked and startedAt_checked + + def naive_data_validation(self): + oracle = self + vars_order = self.find_which_vars_are_used(oracle) + problems = [] + for (index, var) in vars_order.items(): + if not self.is_needed_to_check_conditions(oracle, var): + continue + # if index == ChainlinkVars.ROUNDID.value: # Commented due to deprecation of AnsweredInRound + # if not self.check_RoundId(var, vars_order[ChainlinkVars.ANSWEREDINROUND.value]): + # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) + if index == ChainlinkVars.ANSWER.value: + if not self.check_price(var, oracle): + problems.append( + f"The price value is validated incorrectly. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" + ) + elif index == ChainlinkVars.UPDATEDAT.value: + if not self.check_staleness(var): + problems.append( + f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" + ) + elif ( + index == ChainlinkVars.STARTEDAT.value + and vars_order[ChainlinkVars.STARTEDAT.value] is not None + ): + # If the startedAt is not None. We are checking if the oracle is a sequencer to ignore incorrect results. + if self.is_sequencer_check(vars_order[ChainlinkVars.ANSWER.value], var): + problems = [] + break + return problems \ No newline at end of file diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 4327bea027..2470b48326 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -1,5 +1,13 @@ -from slither.slithir.operations import HighLevelCall, InternalCall, Operation, Unpack +from slither.slithir.operations import HighLevelCall, Operation from slither.core.declarations import Function +from slither.core.cfg.node import Node, NodeType +from slither.slithir.operations.return_operation import Return +from slither.slithir.operations.solidity_call import SolidityCall + +class VarInCondition: # This class was created to store variable and all conditional nodes where it is used + def __init__(self, _var, _nodes): + self.var = _var + self.nodes_with_var = _nodes class Oracle: def __init__( @@ -44,4 +52,24 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or self.returned_vars_indexes = _returned_vars_indexes self.interface = _interface self.oracle_api = _oracle_api - + + # Data validation helpful functions + def naive_data_validation(self): + return [] + + def check_revert(self, node: Node) -> bool: + for n in node.sons: + if n.type == NodeType.EXPRESSION: + for ir in n.irs: + if isinstance(ir, SolidityCall): + if "revert" in ir.function.name: + return True + return False + + def return_boolean(self, node: Node) -> bool: + for n in node.sons: + if n.type == NodeType.RETURN: + for ir in n.irs: + if isinstance(ir, Return): + return True + \ No newline at end of file From c20d24433b33d151bd90def6c5eaafedeaf3c0a0 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 2 Feb 2024 11:59:46 +0100 Subject: [PATCH 56/87] Renamed for generalisation --- .../supported_oracles/chainlink_oracle.py | 23 +++++++++---------- .../oracles/supported_oracles/oracle.py | 7 ++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index e869b0fa30..f328a16352 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -57,7 +57,7 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: # This functions validates checks of price value - def check_price(self, var: VarInCondition, oracle: Oracle) -> bool: + def check_price(self, var: VarInCondition) -> bool: if var is None: return False for node in var.nodes_with_var: @@ -91,16 +91,16 @@ def generate_naive_order(self): vars_order[ChainlinkVars.ANSWEREDINROUND.value] = None return vars_order - def find_which_vars_are_used(self, oracle: Oracle): + def find_which_vars_are_used(self): vars_order = self.generate_naive_order() - for i in range(len(oracle.oracle_vars)): - vars_order[oracle.returned_vars_indexes[i]] = oracle.oracle_vars[i] + for i in range(len(self.oracle_vars)): + vars_order[self.returned_vars_indexes[i]] = self.oracle_vars[i] return vars_order - def is_needed_to_check_conditions(self, oracle, var): + def is_needed_to_check_conditions(self, var): if isinstance(var, VarInCondition): var = var.var - if var in oracle.vars_not_in_condition: + if var in self.vars_not_in_condition: return False return True @@ -123,24 +123,23 @@ def is_sequencer_check(self, answer, startedAt): return answer_checked and startedAt_checked def naive_data_validation(self): - oracle = self - vars_order = self.find_which_vars_are_used(oracle) + vars_order = self.find_which_vars_are_used() problems = [] for (index, var) in vars_order.items(): - if not self.is_needed_to_check_conditions(oracle, var): + if not self.is_needed_to_check_conditions(var): continue # if index == ChainlinkVars.ROUNDID.value: # Commented due to deprecation of AnsweredInRound # if not self.check_RoundId(var, vars_order[ChainlinkVars.ANSWEREDINROUND.value]): # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) if index == ChainlinkVars.ANSWER.value: - if not self.check_price(var, oracle): + if not self.check_price(var): problems.append( - f"The price value is validated incorrectly. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" + f"The price value is validated incorrectly. This value is returned by Chainlink oracle call {self.contract}.{self.interface}.{self.oracle_api} ({self.node.source_mapping}).\n" ) elif index == ChainlinkVars.UPDATEDAT.value: if not self.check_staleness(var): problems.append( - f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {oracle.contract}.{oracle.interface}.{oracle.oracle_api} ({oracle.node.source_mapping}).\n" + f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {self.contract}.{self.interface}.{self.oracle_api} ({self.node.source_mapping}).\n" ) elif ( index == ChainlinkVars.STARTEDAT.value diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 2470b48326..1cea6dc594 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -57,6 +57,13 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or def naive_data_validation(self): return [] + def check_price(self, var: VarInCondition) -> bool: + return True + + def check_staleness(self, var: VarInCondition) -> bool: + return True + + def check_revert(self, node: Node) -> bool: for n in node.sons: if n.type == NodeType.EXPRESSION: From 70e36a219b76b39a819fb7b4383cf86c9020f54c Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 4 Feb 2024 13:14:29 +0100 Subject: [PATCH 57/87] Check return node --- slither/detectors/oracles/oracle_detector.py | 10 +++ .../0.8.20/oracle_check_out_of_function.sol | 65 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 6cc8aaee8b..db79ed70b9 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -147,6 +147,9 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: if self.investigate_internal_call(oracle.function, var): vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) + elif self.investigate_on_return(oracle.function, var): + vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) + oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: vars_not_in_condition.append(var) oracle_vars.append(var) @@ -167,6 +170,13 @@ def is_internal_call(self, node): if isinstance(ir, InternalCall): return True return False + + # Function which will check if the variable were returned and are checked in different part of the code + def investigate_on_return(self, function: FunctionContract, var) -> bool: + for value in function.return_values: + if var == value: + return print("asd") + return False # This function interates through all internal calls in function and checks if the var is used in condition any of them def investigate_internal_call(self, function: FunctionContract, var) -> bool: diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol new file mode 100644 index 0000000000..1f7cb37ec9 --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract ChainlinkETHUSDPriceConsumer { + AggregatorV3Interface internal priceFeed; + + constructor() public { + priceFeed = AggregatorV3Interface( + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 + ); + } + + function obtainValidatedPrice() { + int price = getLatestPrice(); + require(price > 0, "Price is not valid"); + return price; + } + + /** + * Returns the latest price + */ + function getLatestPrice() public view returns (int) { + (, int price, , , ) = priceFeed.latestRoundData(); + return price; + } + + function getDecimals() public view returns (uint8) { + return priceFeed.decimals(); + } +} From b434a75e8e48f1370c9f309f5e7135780a93c4cf Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 4 Feb 2024 13:59:26 +0100 Subject: [PATCH 58/87] Clear before PR --- .../oracles/oracle_data_validation.py | 11 +- slither/detectors/oracles/oracle_detector.py | 23 +- slither/detectors/oracles/oracle_slot0.py | 62 - .../supported_oracles/chainlink_oracle.py | 22 +- .../supported_oracles/help_functions.py | 30 + .../oracles/supported_oracles/oracle.py | 57 +- tests/e2e/detectors/test_detectors.py | 3250 +++++++++-------- 7 files changed, 1688 insertions(+), 1767 deletions(-) delete mode 100644 slither/detectors/oracles/oracle_slot0.py create mode 100644 slither/detectors/oracles/supported_oracles/help_functions.py diff --git a/slither/detectors/oracles/oracle_data_validation.py b/slither/detectors/oracles/oracle_data_validation.py index 912ec89abe..b9b94c21b5 100644 --- a/slither/detectors/oracles/oracle_data_validation.py +++ b/slither/detectors/oracles/oracle_data_validation.py @@ -1,12 +1,5 @@ -from enum import Enum - - from slither.detectors.abstract_detector import DetectorClassification -from slither.detectors.oracles.oracle_detector import Oracle, OracleDetector, VarInCondition - - - - +from slither.detectors.oracles.oracle_detector import OracleDetector class OracleDataCheck(OracleDetector): @@ -26,8 +19,6 @@ class OracleDataCheck(OracleDetector): WIKI_EXPLOIT_SCENARIO = "---" WIKI_RECOMMENDATION = "Validate the data returned by the oracle. For more information visit https://docs.chain.link/data-feeds/api-reference" - - # This function is necessary even though there is a detector for unused return values because the variable can be used but will not be validated in conditional statements def process_not_checked_vars(self): result = [] diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 6cc8aaee8b..6e212e31ed 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -1,5 +1,4 @@ from slither.analyses.data_dependency.data_dependency import get_dependencies -from slither.core.declarations import Function from slither.core.declarations.contract import Contract from slither.core.declarations.function_contract import FunctionContract from slither.core.variables.state_variable import StateVariable @@ -8,18 +7,10 @@ from slither.slithir.variables import TupleVariable from slither.detectors.oracles.supported_oracles.oracle import Oracle, VarInCondition from slither.detectors.oracles.supported_oracles.chainlink_oracle import ChainlinkOracle -from enum import Enum - -class SupportedOracles(Enum): - CHAINLINK = 0 - TELLOR = 1 - - - +from slither.detectors.oracles.supported_oracles.help_functions import is_internal_call class OracleDetector(AbstractDetector): - def find_oracles(self, contracts: Contract) -> list[Oracle]: """ Detects off-chain oracle contract and VAR @@ -49,12 +40,10 @@ def find_oracles(self, contracts: Contract) -> list[Oracle]: oracles.append(oracle) return oracles - def generate_oracle(self, ir: Operation) -> Oracle: if ChainlinkOracle().is_instance_of(ir): return ChainlinkOracle() return None - # This function was inspired by detector unused return values def oracle_call(self, function: FunctionContract): @@ -122,7 +111,6 @@ def map_condition_to_var(self, var, function: FunctionContract): nodes.append(node) return nodes - # Check if the vars occurs in require/assert statement or in conditional node def vars_in_conditions(self, oracle: Oracle) -> bool: vars_in_condition = [] @@ -161,12 +149,7 @@ def map_param_to_var(self, var, function: FunctionContract): for var2 in origin_vars: if var2 == var: return param - - def is_internal_call(self, node): - for ir in node.irs: - if isinstance(ir, InternalCall): - return True - return False + return None # This function interates through all internal calls in function and checks if the var is used in condition any of them def investigate_internal_call(self, function: FunctionContract, var) -> bool: @@ -185,7 +168,7 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: if ( node.is_conditional() and self.check_var_condition_match(original_var_as_param, node) - and not self.is_internal_call(node) + and not is_internal_call(node) ): conditions.append(node) if len(conditions) > 0: diff --git a/slither/detectors/oracles/oracle_slot0.py b/slither/detectors/oracles/oracle_slot0.py deleted file mode 100644 index 962bfce11f..0000000000 --- a/slither/detectors/oracles/oracle_slot0.py +++ /dev/null @@ -1,62 +0,0 @@ -from slither.core.declarations.contract import Contract -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.detectors.oracles.oracle_detector import Oracle - - -# TODO: Not ready for publishing -class OracleSlot0(AbstractDetector): - """ - Documentation - """ - - ARGUMENT = "slot0" # slither will launch the detector with slither.py --detect mydetector - HELP = "Help printed by slither" - IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.HIGH - - WIKI = "RUN" - - WIKI_TITLE = "asda" - WIKI_DESCRIPTION = "Slot0 is vulnerable to price manipulation as it gets price at the current moment. TWAP should be used instead." - WIKI_EXPLOIT_SCENARIO = "asdsad" - WIKI_RECOMMENDATION = "asdsad" - - oracles = [] - - def detect_slot0(self, contracts: Contract): - """ - Detects off-chain oracle contract and VAR - """ - self.oracles = [] - for contract in contracts: - for function in contract.functions: - if function.is_constructor: - continue - for functionCalled in function.external_calls_as_expressions: - if "slot0" in str(functionCalled): - self.oracles.append( - Oracle( - contract, - function, - str(functionCalled).split(".", maxsplit=1)[0], - functionCalled.source_mapping.lines[0], - ) - ) - - def _detect(self): - results = [] - self.detect_slot0(self.contracts) - # for oracle in self.oracles: - # print(oracle.contract.name, oracle.function.name) - for oracle in self.oracles: - results.append( - "Slot0 usage found in contract " - + oracle.contract.name - + " in function " - + oracle.function.name - ) - results.append("\n") - res = self.generate_result(results) - output = [] - output.append(res) - return output diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index f328a16352..b1bf76160a 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -6,9 +6,15 @@ ) from slither.slithir.variables.constant import Constant +from slither.detectors.oracles.supported_oracles.help_functions import check_revert, return_boolean + + +CHAINLINK_ORACLE_CALLS = [ + "latestRoundData", + "getRoundData", +] -CHAINLINK_ORACLE_CALLS = ["latestRoundData","getRoundData",] class ChainlinkVars(Enum): ROUNDID = 0 ANSWER = 1 @@ -22,7 +28,6 @@ def __init__(self): super().__init__(CHAINLINK_ORACLE_CALLS) self.oracle_type = "Chainlink" - # This function checks if the updatedAt value is validated. def check_staleness(self, var: VarInCondition) -> bool: if var is None: @@ -48,14 +53,10 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): if ir.variable_right == var2.var and ir.variable_left == var.var: return True - if self.check_revert(node): - return True - elif self.return_boolean(node): - return True + return check_revert(node) or return_boolean(node) return False - # This functions validates checks of price value def check_price(self, var: VarInCondition) -> bool: if var is None: @@ -75,10 +76,7 @@ def check_price(self, var: VarInCondition) -> bool: if ir.variable_left.value == 0: return True # If the conditions does not match we are looking for revert or return node - if self.check_revert(node): - return True - elif self.return_boolean(node): - return True + return check_revert(node) or return_boolean(node) return False @@ -149,4 +147,4 @@ def naive_data_validation(self): if self.is_sequencer_check(vars_order[ChainlinkVars.ANSWER.value], var): problems = [] break - return problems \ No newline at end of file + return problems diff --git a/slither/detectors/oracles/supported_oracles/help_functions.py b/slither/detectors/oracles/supported_oracles/help_functions.py new file mode 100644 index 0000000000..78ba8723ca --- /dev/null +++ b/slither/detectors/oracles/supported_oracles/help_functions.py @@ -0,0 +1,30 @@ +from slither.core.cfg.node import Node, NodeType +from slither.slithir.operations.return_operation import Return +from slither.slithir.operations import InternalCall +from slither.slithir.operations.solidity_call import SolidityCall + +# Helpfull functions +def check_revert(node: Node) -> bool: + for n in node.sons: + if n.type == NodeType.EXPRESSION: + for ir in n.irs: + if isinstance(ir, SolidityCall): + if "revert" in ir.function.name: + return True + return False + + +def return_boolean(node: Node) -> bool: + for n in node.sons: + if n.type == NodeType.RETURN: + for ir in n.irs: + if isinstance(ir, Return): + return True + return False + + +def is_internal_call(node): + for ir in node.irs: + if isinstance(ir, InternalCall): + return True + return False diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 1cea6dc594..6d9d990b4c 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -1,19 +1,15 @@ from slither.slithir.operations import HighLevelCall, Operation from slither.core.declarations import Function -from slither.core.cfg.node import Node, NodeType -from slither.slithir.operations.return_operation import Return -from slither.slithir.operations.solidity_call import SolidityCall -class VarInCondition: # This class was created to store variable and all conditional nodes where it is used +# This class was created to store variable and all conditional nodes where it is used +class VarInCondition: def __init__(self, _var, _nodes): self.var = _var self.nodes_with_var = _nodes -class Oracle: - def __init__( - self, - _calls - ): + +class Oracle: # pylint: disable=too-few-public-methods + def __init__(self, _calls): self.calls = _calls self.contract = None self.function = None @@ -28,23 +24,20 @@ def __init__( def get_calls(self): return self.calls - + + def is_instance_of(self, ir: Operation) -> bool: + return isinstance(ir, HighLevelCall) and ( + isinstance(ir.function, Function) and self.compare_call(ir.function.name) + ) + def set_node(self, _node): self.node = _node - + def compare_call(self, function) -> bool: for call in self.calls: if call in str(function): return True return False - - def is_instance_of(self, ir: Operation) -> bool: - return isinstance(ir, HighLevelCall) and ( - isinstance(ir.function, Function) - and self.compare_call( - ir.function.name - ) - ) def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _oracle_api): self.contract = _contract @@ -56,27 +49,13 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or # Data validation helpful functions def naive_data_validation(self): return [] - + def check_price(self, var: VarInCondition) -> bool: + if var is None: + return False return True - + def check_staleness(self, var: VarInCondition) -> bool: + if var is None: + return False return True - - - def check_revert(self, node: Node) -> bool: - for n in node.sons: - if n.type == NodeType.EXPRESSION: - for ir in n.irs: - if isinstance(ir, SolidityCall): - if "revert" in ir.function.name: - return True - return False - - def return_boolean(self, node: Node) -> bool: - for n in node.sons: - if n.type == NodeType.RETURN: - for ir in n.irs: - if isinstance(ir, Return): - return True - \ No newline at end of file diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 30384371f3..43983ad488 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -57,1632 +57,1634 @@ def id_test(test_item: Test): ALL_TESTS = [ - # Test( - # all_detectors.UninitializedFunctionPtrsConstructor, - # "uninitialized_function_ptr_constructor.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UninitializedFunctionPtrsConstructor, - # "uninitialized_function_ptr_constructor.sol", - # "0.5.8", - # ), - # Test( - # all_detectors.UninitializedFunctionPtrsConstructor, - # "uninitialized_function_ptr_constructor.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyBenign, - # "reentrancy-benign.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ReentrancyBenign, - # "reentrancy-benign.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyBenign, - # "reentrancy-benign.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ReentrancyBenign, - # "reentrancy-benign.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "reentrancy-write.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "reentrancy-write.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "reentrancy-write.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "reentrancy-write.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "DAO.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "comment.sol", - # "0.8.2", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "no-reentrancy-staticcall.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "no-reentrancy-staticcall.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ReentrancyReadBeforeWritten, - # "no-reentrancy-staticcall.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.BooleanEquality, - # "boolean-constant-equality.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.BooleanEquality, - # "boolean-constant-equality.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.BooleanEquality, - # "boolean-constant-equality.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.BooleanEquality, - # "boolean-constant-equality.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.BooleanConstantMisuse, - # "boolean-constant-misuse.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.BooleanConstantMisuse, - # "boolean-constant-misuse.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.BooleanConstantMisuse, - # "boolean-constant-misuse.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.BooleanConstantMisuse, - # "boolean-constant-misuse.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UncheckedLowLevel, - # "unchecked_lowlevel.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UncheckedLowLevel, - # "unchecked_lowlevel.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UncheckedLowLevel, - # "unchecked_lowlevel.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UncheckedLowLevel, - # "unchecked_lowlevel.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UnindexedERC20EventParameters, - # "erc20_indexed.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnindexedERC20EventParameters, - # "erc20_indexed.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnindexedERC20EventParameters, - # "erc20_indexed.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnindexedERC20EventParameters, - # "erc20_indexed.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.IncorrectERC20InterfaceDetection, - # "incorrect_erc20_interface.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.IncorrectERC20InterfaceDetection, - # "incorrect_erc20_interface.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.IncorrectERC20InterfaceDetection, - # "incorrect_erc20_interface.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.IncorrectERC20InterfaceDetection, - # "incorrect_erc20_interface.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.IncorrectERC721InterfaceDetection, - # "incorrect_erc721_interface.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.IncorrectERC721InterfaceDetection, - # "incorrect_erc721_interface.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.IncorrectERC721InterfaceDetection, - # "incorrect_erc721_interface.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.IncorrectERC721InterfaceDetection, - # "incorrect_erc721_interface.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UninitializedStateVarsDetection, - # "uninitialized.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UninitializedStateVarsDetection, - # "uninitialized.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UninitializedStateVarsDetection, - # "uninitialized.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UninitializedStateVarsDetection, - # "uninitialized.sol", - # "0.7.6", - # ), - # Test(all_detectors.Backdoor, "backdoor.sol", "0.4.25"), - # Test(all_detectors.Backdoor, "backdoor.sol", "0.5.16"), - # Test(all_detectors.Backdoor, "backdoor.sol", "0.6.11"), - # Test(all_detectors.Backdoor, "backdoor.sol", "0.7.6"), - # Test(all_detectors.Suicidal, "suicidal.sol", "0.4.25"), - # Test(all_detectors.Suicidal, "suicidal.sol", "0.5.16"), - # Test(all_detectors.Suicidal, "suicidal.sol", "0.6.11"), - # Test(all_detectors.Suicidal, "suicidal.sol", "0.7.6"), - # Test( - # all_detectors.ConstantPragma, - # "pragma.0.4.25.sol", - # "0.4.25", - # ["pragma.0.4.24.sol"], - # ), - # Test( - # all_detectors.ConstantPragma, - # "pragma.0.5.16.sol", - # "0.5.16", - # ["pragma.0.5.15.sol"], - # ), - # Test( - # all_detectors.ConstantPragma, - # "pragma.0.6.11.sol", - # "0.6.11", - # ["pragma.0.6.10.sol"], - # ), - # Test( - # all_detectors.ConstantPragma, - # "pragma.0.7.6.sol", - # "0.7.6", - # ["pragma.0.7.5.sol"], - # ), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.4.25"), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.5.14"), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.5.16"), - # Test(all_detectors.IncorrectSolc, "dynamic_1.sol", "0.5.16"), - # Test(all_detectors.IncorrectSolc, "dynamic_2.sol", "0.5.16"), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.6.10"), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.6.11"), - # Test(all_detectors.IncorrectSolc, "dynamic_1.sol", "0.6.11"), - # Test(all_detectors.IncorrectSolc, "dynamic_2.sol", "0.6.11"), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.7.4"), - # Test(all_detectors.IncorrectSolc, "static.sol", "0.7.6"), - # Test(all_detectors.IncorrectSolc, "dynamic_1.sol", "0.7.6"), - # Test(all_detectors.IncorrectSolc, "dynamic_2.sol", "0.7.6"), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy_indirect.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy_indirect.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy_indirect.sol", - # "0.6.11", - # ), - # Test(all_detectors.ReentrancyEth, "reentrancy.sol", "0.7.6"), - # Test( - # all_detectors.ReentrancyEth, - # "reentrancy_indirect.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ReentrancyEth, - # "DAO.sol", - # "0.4.25", - # ), - # # Test the nonReentrant filtering - # Test(all_detectors.ReentrancyEth, "reentrancy_with_non_reentrant.sol", "0.8.10"), - # # Test parse_ignore_comments - # Test(all_detectors.ReentrancyEth, "reentrancy_filtered_comments.sol", "0.8.10"), - # Test( - # all_detectors.UninitializedStorageVars, - # "uninitialized_storage_pointer.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UninitializedStorageVars, - # "uninitialized_storage_pointer.sol", - # "0.8.19", - # ), - # Test(all_detectors.TxOrigin, "tx_origin.sol", "0.4.25"), - # Test(all_detectors.TxOrigin, "tx_origin.sol", "0.5.16"), - # Test(all_detectors.TxOrigin, "tx_origin.sol", "0.6.11"), - # Test(all_detectors.TxOrigin, "tx_origin.sol", "0.7.6"), - # Test( - # all_detectors.UnusedStateVars, - # "unused_state.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnusedStateVars, - # "unused_state.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnusedStateVars, - # "unused_state.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnusedStateVars, - # "unused_state.sol", - # "0.7.6", - # ), - # Test(all_detectors.LockedEther, "locked_ether.sol", "0.4.25"), - # Test(all_detectors.LockedEther, "locked_ether.sol", "0.5.16"), - # Test(all_detectors.LockedEther, "locked_ether.sol", "0.6.11"), - # Test(all_detectors.LockedEther, "locked_ether.sol", "0.7.6"), - # Test( - # all_detectors.ArbitrarySendEth, - # "arbitrary_send_eth.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ArbitrarySendEth, - # "arbitrary_send_eth.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ArbitrarySendEth, - # "arbitrary_send_eth.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ArbitrarySendEth, - # "arbitrary_send_eth.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_contract.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_library.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_contract.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_library.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_contract.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_library.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_contract.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.Assembly, - # "inline_assembly_library.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.LowLevelCalls, - # "low_level_calls.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.LowLevelCalls, - # "low_level_calls.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.LowLevelCalls, - # "low_level_calls.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.LowLevelCalls, - # "low_level_calls.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.CouldBeConstant, - # "const_state_variables.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.CouldBeConstant, - # "const_state_variables.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.CouldBeConstant, - # "const_state_variables.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.CouldBeConstant, - # "const_state_variables.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.CouldBeConstant, - # "const_state_variables.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.CouldBeImmutable, - # "immut_state_variables.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.CouldBeImmutable, - # "immut_state_variables.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.CouldBeImmutable, - # "immut_state_variables.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.CouldBeImmutable, - # "immut_state_variables.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.CouldBeImmutable, - # "immut_state_variables.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_2.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_3.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_2.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_3.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_2.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_3.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_2.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ExternalFunction, - # "external_function_3.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.NamingConvention, - # "naming_convention.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.NamingConvention, - # "naming_convention.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.NamingConvention, - # "naming_convention.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.NamingConvention, - # "naming_convention.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.NamingConvention, - # "no_warning_for_public_constants.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.NamingConvention, - # "no_warning_for_public_constants.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.NamingConvention, - # "no_warning_for_public_constants.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.NamingConvention, - # "no_warning_for_public_constants.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ControlledDelegateCall, - # "controlled_delegatecall.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ControlledDelegateCall, - # "controlled_delegatecall.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ControlledDelegateCall, - # "controlled_delegatecall.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ControlledDelegateCall, - # "controlled_delegatecall.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UninitializedLocalVars, - # "uninitialized_local_variable.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UninitializedLocalVars, - # "uninitialized_local_variable.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UninitializedLocalVars, - # "uninitialized_local_variable.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UninitializedLocalVars, - # "uninitialized_local_variable.sol", - # "0.7.6", - # ), - # Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.4.25"), - # Test( - # all_detectors.ConstantFunctionsState, - # "constant.sol", - # "0.4.25", - # ), - # Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.5.16"), - # Test( - # all_detectors.ConstantFunctionsState, - # "constant.sol", - # "0.5.16", - # ), - # Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.6.11"), - # Test( - # all_detectors.ConstantFunctionsState, - # "constant.sol", - # "0.6.11", - # ), - # Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.7.6"), - # Test(all_detectors.ConstantFunctionsState, "constant.sol", "0.7.6"), - # Test( - # all_detectors.UnusedReturnValues, - # "unused_return.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnusedReturnValues, - # "unused_return.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnusedReturnValues, - # "unused_return.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnusedReturnValues, - # "unused_return.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UncheckedTransfer, - # "unused_return_transfers.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ShadowingAbstractDetection, - # "shadowing_abstract.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ShadowingAbstractDetection, - # "shadowing_abstract.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ShadowingAbstractDetection, - # "shadowing_state_variable.sol", - # "0.7.5", - # ), - # Test( - # all_detectors.ShadowingAbstractDetection, - # "public_gap_variable.sol", - # "0.7.5", - # ), - # Test( - # all_detectors.StateShadowing, - # "shadowing_state_variable.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.StateShadowing, - # "shadowing_state_variable.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.StateShadowing, - # "shadowing_state_variable.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.StateShadowing, - # "shadowing_state_variable.sol", - # "0.7.5", - # ), - # Test( - # all_detectors.StateShadowing, - # "public_gap_variable.sol", - # "0.7.5", - # ), - # Test( - # all_detectors.StateShadowing, - # "shadowing_state_variable.sol", - # "0.7.6", - # ), - # Test(all_detectors.Timestamp, "timestamp.sol", "0.4.25"), - # Test(all_detectors.Timestamp, "timestamp.sol", "0.5.16"), - # Test(all_detectors.Timestamp, "timestamp.sol", "0.6.11"), - # Test(all_detectors.Timestamp, "timestamp.sol", "0.7.6"), - # Test( - # all_detectors.MultipleCallsInLoop, - # "multiple_calls_in_loop.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MultipleCallsInLoop, - # "multiple_calls_in_loop.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MultipleCallsInLoop, - # "multiple_calls_in_loop.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MultipleCallsInLoop, - # "multiple_calls_in_loop.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.BuiltinSymbolShadowing, - # "shadowing_builtin_symbols.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.BuiltinSymbolShadowing, - # "shadowing_builtin_symbols.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.LocalShadowing, - # "shadowing_local_variable.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.LocalShadowing, - # "shadowing_local_variable.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.LocalShadowing, - # "shadowing_local_variable.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.LocalShadowing, - # "shadowing_local_variable.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.RightToLeftOverride, - # "right_to_left_override.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.RightToLeftOverride, - # "right_to_left_override.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.RightToLeftOverride, - # "right_to_left_override.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.RightToLeftOverride, - # "unicode_direction_override.sol", - # "0.8.0", - # ), - # Test(all_detectors.VoidConstructor, "void-cst.sol", "0.4.25"), - # Test(all_detectors.VoidConstructor, "void-cst.sol", "0.5.16"), - # Test(all_detectors.VoidConstructor, "void-cst.sol", "0.6.11"), - # Test(all_detectors.VoidConstructor, "void-cst.sol", "0.7.6"), - # Test( - # all_detectors.UncheckedSend, - # "unchecked_send.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UncheckedSend, - # "unchecked_send.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UncheckedSend, - # "unchecked_send.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UncheckedSend, - # "unchecked_send.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ReentrancyEvent, - # "reentrancy-events.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ReentrancyEvent, - # "reentrancy-events.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ReentrancyEvent, - # "reentrancy-events.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.IncorrectStrictEquality, - # "incorrect_equality.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.IncorrectStrictEquality, - # "incorrect_equality.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.IncorrectStrictEquality, - # "incorrect_equality.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.IncorrectStrictEquality, - # "incorrect_equality.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.TooManyDigits, - # "too_many_digits.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.TooManyDigits, - # "too_many_digits.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.TooManyDigits, - # "too_many_digits.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.TooManyDigits, - # "too_many_digits.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Buggy.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Fixed.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "whitelisted.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Buggy.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Fixed.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "whitelisted.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Buggy.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Fixed.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "whitelisted.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Buggy.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Fixed.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "whitelisted.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Buggy.sol", - # "0.8.15", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "Fixed.sol", - # "0.8.15", - # ), - # Test( - # all_detectors.UnprotectedUpgradeable, - # "whitelisted.sol", - # "0.8.15", - # ), - # Test( - # all_detectors.ABIEncoderV2Array, - # "storage_ABIEncoderV2_array.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ABIEncoderV2Array, - # "storage_ABIEncoderV2_array.sol", - # "0.5.10", - # ), - # Test( - # all_detectors.ABIEncoderV2Array, - # "storage_ABIEncoderV2_array.sol", - # "0.5.9", - # ), - # Test( - # all_detectors.ArrayByReference, - # "array_by_reference.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ArrayByReference, - # "array_by_reference.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ArrayByReference, - # "array_by_reference.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ArrayByReference, - # "array_by_reference.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.AssertStateChange, - # "assert_state_change.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.AssertStateChange, - # "assert_state_change.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.AssertStateChange, - # "assert_state_change.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.AssertStateChange, - # "assert_state_change.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ArrayLengthAssignment, - # "array_length_assignment.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ArrayLengthAssignment, - # "array_length_assignment.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.CostlyOperationsInLoop, - # "multiple_costly_operations_in_loop.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.CostlyOperationsInLoop, - # "multiple_costly_operations_in_loop.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.CostlyOperationsInLoop, - # "multiple_costly_operations_in_loop.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.CostlyOperationsInLoop, - # "multiple_costly_operations_in_loop.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.FunctionInitializedState, - # "function_init_state_variables.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.FunctionInitializedState, - # "function_init_state_variables.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.FunctionInitializedState, - # "function_init_state_variables.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.FunctionInitializedState, - # "function_init_state_variables.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.MappingDeletionDetection, - # "MappingDeletion.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MappingDeletionDetection, - # "MappingDeletion.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MappingDeletionDetection, - # "MappingDeletion.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MappingDeletionDetection, - # "MappingDeletion.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.PublicMappingNested, - # "public_mappings_nested.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.RedundantStatements, - # "redundant_statements.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.RedundantStatements, - # "redundant_statements.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.RedundantStatements, - # "redundant_statements.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.RedundantStatements, - # "redundant_statements.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ReusedBaseConstructor, - # "reused_base_constructor.sol", - # "0.4.21", - # ), - # Test( - # all_detectors.ReusedBaseConstructor, - # "reused_base_constructor.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.StorageSignedIntegerArray, - # "storage_signed_integer_array.sol", - # "0.5.10", - # ), - # Test( - # all_detectors.StorageSignedIntegerArray, - # "storage_signed_integer_array.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented_interfaces.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented_interfaces.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.UnimplementedFunctionDetection, - # "unimplemented_interfaces.sol", - # "0.7.6", - # ), - # Test(all_detectors.BadPRNG, "bad_prng.sol", "0.4.25"), - # Test(all_detectors.BadPRNG, "bad_prng.sol", "0.5.16"), - # Test(all_detectors.BadPRNG, "bad_prng.sol", "0.6.11"), - # Test(all_detectors.BadPRNG, "bad_prng.sol", "0.7.6"), - # Test( - # all_detectors.MissingEventsAccessControl, - # "missing_events_access_control.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MissingEventsAccessControl, - # "missing_events_access_control.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MissingEventsAccessControl, - # "missing_events_access_control.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MissingEventsAccessControl, - # "missing_events_access_control.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.MissingEventsArithmetic, - # "missing_events_arithmetic.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MissingEventsArithmetic, - # "missing_events_arithmetic.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MissingEventsArithmetic, - # "missing_events_arithmetic.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MissingEventsArithmetic, - # "missing_events_arithmetic.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ModifierDefaultDetection, - # "modifier_default.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ModifierDefaultDetection, - # "modifier_default.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ModifierDefaultDetection, - # "modifier_default.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ModifierDefaultDetection, - # "modifier_default.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.IncorrectUnaryExpressionDetection, - # "invalid_unary_expression.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MissingZeroAddressValidation, - # "missing_zero_address_validation.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MissingZeroAddressValidation, - # "missing_zero_address_validation.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MissingZeroAddressValidation, - # "missing_zero_address_validation.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MissingZeroAddressValidation, - # "missing_zero_address_validation.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.PredeclarationUsageLocal, - # "predeclaration_usage_local.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DeadCode, - # "dead-code.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.WriteAfterWrite, - # "write-after-write.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.ShiftParameterMixup, - # "shift_parameter_mixup.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ShiftParameterMixup, - # "shift_parameter_mixup.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ShiftParameterMixup, - # "shift_parameter_mixup.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ShiftParameterMixup, - # "shift_parameter_mixup.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.MissingInheritance, - # "unimplemented_interface.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MissingInheritance, - # "unimplemented_interface.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MissingInheritance, - # "unimplemented_interface.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MissingInheritance, - # "unimplemented_interface.sol", - # "0.7.6", - # ), - # # Does not work on the CI. Most likely because of solc 0.4.2? - # # Test( - # # all_detectors.EnumConversion, - # # "enum_conversion.sol", - # # "0.4.2", - # # ), - # Test( - # all_detectors.MultipleConstructorSchemes, - # "multiple_constructor_schemes.sol", - # "0.4.22", - # ), - # Test( - # all_detectors.DeprecatedStandards, - # "deprecated_calls.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DivideBeforeMultiply, - # "divide_before_multiply.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DivideBeforeMultiply, - # "divide_before_multiply.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.DivideBeforeMultiply, - # "divide_before_multiply.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.DivideBeforeMultiply, - # "divide_before_multiply.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.TypeBasedTautology, - # "type_based_tautology.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.TypeBasedTautology, - # "type_based_tautology.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.TypeBasedTautology, - # "type_based_tautology.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.TypeBasedTautology, - # "type_based_tautology.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.SimilarVarsDetection, - # "similar_variables.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.SimilarVarsDetection, - # "similar_variables.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.SimilarVarsDetection, - # "similar_variables.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.SimilarVarsDetection, - # "similar_variables.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.MsgValueInLoop, - # "msg_value_loop.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.MsgValueInLoop, - # "msg_value_loop.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.MsgValueInLoop, - # "msg_value_loop.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.MsgValueInLoop, - # "msg_value_loop.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.MsgValueInLoop, - # "msg_value_loop.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.DelegatecallInLoop, - # "delegatecall_loop.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DelegatecallInLoop, - # "delegatecall_loop.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.DelegatecallInLoop, - # "delegatecall_loop.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.DelegatecallInLoop, - # "delegatecall_loop.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.DelegatecallInLoop, - # "delegatecall_loop.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.ProtectedVariables, - # "comment.sol", - # "0.8.2", - # ), - # Test( - # all_detectors.ArbitrarySendErc20NoPermit, - # "arbitrary_send_erc20.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ArbitrarySendErc20NoPermit, - # "arbitrary_send_erc20.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ArbitrarySendErc20NoPermit, - # "arbitrary_send_erc20.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ArbitrarySendErc20NoPermit, - # "arbitrary_send_erc20.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ArbitrarySendErc20NoPermit, - # "arbitrary_send_erc20.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.ArbitrarySendErc20NoPermit, - # "arbitrary_send_erc20_inheritance.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.ArbitrarySendErc20Permit, - # "arbitrary_send_erc20_permit.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.ArbitrarySendErc20Permit, - # "arbitrary_send_erc20_permit.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.ArbitrarySendErc20Permit, - # "arbitrary_send_erc20_permit.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.ArbitrarySendErc20Permit, - # "arbitrary_send_erc20_permit.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.ArbitrarySendErc20Permit, - # "arbitrary_send_erc20_permit.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_collision.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_collision.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_collision.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_collision.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_collision.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_wrong_return_type.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_wrong_return_type.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_wrong_return_type.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_wrong_return_type.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_wrong_return_type.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_state_var_collision.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_state_var_collision.sol", - # "0.5.16", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_state_var_collision.sol", - # "0.6.11", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_state_var_collision.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.DomainSeparatorCollision, - # "permit_domain_state_var_collision.sol", - # "0.8.0", - # ), - # Test( - # all_detectors.VarReadUsingThis, - # "var_read_using_this.sol", - # "0.4.25", - # ), - # Test( - # all_detectors.VarReadUsingThis, - # "var_read_using_this.sol", - # "0.5.16", - # ), - # Test(all_detectors.VarReadUsingThis, "var_read_using_this.sol", "0.6.11"), - # Test( - # all_detectors.VarReadUsingThis, - # "var_read_using_this.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.VarReadUsingThis, - # "var_read_using_this.sol", - # "0.8.15", - # ), - # Test( - # all_detectors.CyclomaticComplexity, - # "HighCyclomaticComplexity.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.CyclomaticComplexity, - # "LowCyclomaticComplexity.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.CacheArrayLength, - # "CacheArrayLength.sol", - # "0.8.17", - # ), - # Test( - # all_detectors.IncorrectUsingFor, - # "IncorrectUsingForTopLevel.sol", - # "0.8.17", - # ), - # Test( - # all_detectors.EncodePackedCollision, - # "encode_packed_collision.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.IncorrectReturn, - # "incorrect_return.sol", - # "0.8.10", - # ), - # Test( - # all_detectors.ReturnInsteadOfLeave, - # "incorrect_return.sol", - # "0.8.10", - # ), - # Test( - # all_detectors.IncorrectOperatorExponentiation, - # "incorrect_exp.sol", - # "0.7.6", - # ), - # Test( - # all_detectors.TautologicalCompare, - # "compare.sol", - # "0.8.20", - # ), - # Test( - # all_detectors.ReturnBomb, - # "return_bomb.sol", - # "0.8.20", - # ), - ##TODO My tests for oracle + Test( + all_detectors.UninitializedFunctionPtrsConstructor, + "uninitialized_function_ptr_constructor.sol", + "0.4.25", + ), + Test( + all_detectors.UninitializedFunctionPtrsConstructor, + "uninitialized_function_ptr_constructor.sol", + "0.5.8", + ), + Test( + all_detectors.UninitializedFunctionPtrsConstructor, + "uninitialized_function_ptr_constructor.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyBenign, + "reentrancy-benign.sol", + "0.4.25", + ), + Test( + all_detectors.ReentrancyBenign, + "reentrancy-benign.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyBenign, + "reentrancy-benign.sol", + "0.6.11", + ), + Test( + all_detectors.ReentrancyBenign, + "reentrancy-benign.sol", + "0.7.6", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "reentrancy-write.sol", + "0.4.25", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "reentrancy-write.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "reentrancy-write.sol", + "0.6.11", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "reentrancy-write.sol", + "0.7.6", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "DAO.sol", + "0.4.25", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "comment.sol", + "0.8.2", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "no-reentrancy-staticcall.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "no-reentrancy-staticcall.sol", + "0.6.11", + ), + Test( + all_detectors.ReentrancyReadBeforeWritten, + "no-reentrancy-staticcall.sol", + "0.7.6", + ), + Test( + all_detectors.BooleanEquality, + "boolean-constant-equality.sol", + "0.4.25", + ), + Test( + all_detectors.BooleanEquality, + "boolean-constant-equality.sol", + "0.5.16", + ), + Test( + all_detectors.BooleanEquality, + "boolean-constant-equality.sol", + "0.6.11", + ), + Test( + all_detectors.BooleanEquality, + "boolean-constant-equality.sol", + "0.7.6", + ), + Test( + all_detectors.BooleanConstantMisuse, + "boolean-constant-misuse.sol", + "0.4.25", + ), + Test( + all_detectors.BooleanConstantMisuse, + "boolean-constant-misuse.sol", + "0.5.16", + ), + Test( + all_detectors.BooleanConstantMisuse, + "boolean-constant-misuse.sol", + "0.6.11", + ), + Test( + all_detectors.BooleanConstantMisuse, + "boolean-constant-misuse.sol", + "0.7.6", + ), + Test( + all_detectors.UncheckedLowLevel, + "unchecked_lowlevel.sol", + "0.4.25", + ), + Test( + all_detectors.UncheckedLowLevel, + "unchecked_lowlevel.sol", + "0.5.16", + ), + Test( + all_detectors.UncheckedLowLevel, + "unchecked_lowlevel.sol", + "0.6.11", + ), + Test( + all_detectors.UncheckedLowLevel, + "unchecked_lowlevel.sol", + "0.7.6", + ), + Test( + all_detectors.UnindexedERC20EventParameters, + "erc20_indexed.sol", + "0.4.25", + ), + Test( + all_detectors.UnindexedERC20EventParameters, + "erc20_indexed.sol", + "0.5.16", + ), + Test( + all_detectors.UnindexedERC20EventParameters, + "erc20_indexed.sol", + "0.6.11", + ), + Test( + all_detectors.UnindexedERC20EventParameters, + "erc20_indexed.sol", + "0.7.6", + ), + Test( + all_detectors.IncorrectERC20InterfaceDetection, + "incorrect_erc20_interface.sol", + "0.4.25", + ), + Test( + all_detectors.IncorrectERC20InterfaceDetection, + "incorrect_erc20_interface.sol", + "0.5.16", + ), + Test( + all_detectors.IncorrectERC20InterfaceDetection, + "incorrect_erc20_interface.sol", + "0.6.11", + ), + Test( + all_detectors.IncorrectERC20InterfaceDetection, + "incorrect_erc20_interface.sol", + "0.7.6", + ), + Test( + all_detectors.IncorrectERC721InterfaceDetection, + "incorrect_erc721_interface.sol", + "0.4.25", + ), + Test( + all_detectors.IncorrectERC721InterfaceDetection, + "incorrect_erc721_interface.sol", + "0.5.16", + ), + Test( + all_detectors.IncorrectERC721InterfaceDetection, + "incorrect_erc721_interface.sol", + "0.6.11", + ), + Test( + all_detectors.IncorrectERC721InterfaceDetection, + "incorrect_erc721_interface.sol", + "0.7.6", + ), + Test( + all_detectors.UninitializedStateVarsDetection, + "uninitialized.sol", + "0.4.25", + ), + Test( + all_detectors.UninitializedStateVarsDetection, + "uninitialized.sol", + "0.5.16", + ), + Test( + all_detectors.UninitializedStateVarsDetection, + "uninitialized.sol", + "0.6.11", + ), + Test( + all_detectors.UninitializedStateVarsDetection, + "uninitialized.sol", + "0.7.6", + ), + Test(all_detectors.Backdoor, "backdoor.sol", "0.4.25"), + Test(all_detectors.Backdoor, "backdoor.sol", "0.5.16"), + Test(all_detectors.Backdoor, "backdoor.sol", "0.6.11"), + Test(all_detectors.Backdoor, "backdoor.sol", "0.7.6"), + Test(all_detectors.Suicidal, "suicidal.sol", "0.4.25"), + Test(all_detectors.Suicidal, "suicidal.sol", "0.5.16"), + Test(all_detectors.Suicidal, "suicidal.sol", "0.6.11"), + Test(all_detectors.Suicidal, "suicidal.sol", "0.7.6"), + Test( + all_detectors.ConstantPragma, + "pragma.0.4.25.sol", + "0.4.25", + ["pragma.0.4.24.sol"], + ), + Test( + all_detectors.ConstantPragma, + "pragma.0.5.16.sol", + "0.5.16", + ["pragma.0.5.15.sol"], + ), + Test( + all_detectors.ConstantPragma, + "pragma.0.6.11.sol", + "0.6.11", + ["pragma.0.6.10.sol"], + ), + Test( + all_detectors.ConstantPragma, + "pragma.0.7.6.sol", + "0.7.6", + ["pragma.0.7.5.sol"], + ), + Test(all_detectors.IncorrectSolc, "static.sol", "0.4.25"), + Test(all_detectors.IncorrectSolc, "static.sol", "0.5.14"), + Test(all_detectors.IncorrectSolc, "static.sol", "0.5.16"), + Test(all_detectors.IncorrectSolc, "dynamic_1.sol", "0.5.16"), + Test(all_detectors.IncorrectSolc, "dynamic_2.sol", "0.5.16"), + Test(all_detectors.IncorrectSolc, "static.sol", "0.6.10"), + Test(all_detectors.IncorrectSolc, "static.sol", "0.6.11"), + Test(all_detectors.IncorrectSolc, "dynamic_1.sol", "0.6.11"), + Test(all_detectors.IncorrectSolc, "dynamic_2.sol", "0.6.11"), + Test(all_detectors.IncorrectSolc, "static.sol", "0.7.4"), + Test(all_detectors.IncorrectSolc, "static.sol", "0.7.6"), + Test(all_detectors.IncorrectSolc, "dynamic_1.sol", "0.7.6"), + Test(all_detectors.IncorrectSolc, "dynamic_2.sol", "0.7.6"), + Test( + all_detectors.ReentrancyEth, + "reentrancy.sol", + "0.4.25", + ), + Test( + all_detectors.ReentrancyEth, + "reentrancy_indirect.sol", + "0.4.25", + ), + Test( + all_detectors.ReentrancyEth, + "reentrancy.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyEth, + "reentrancy_indirect.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyEth, + "reentrancy.sol", + "0.6.11", + ), + Test( + all_detectors.ReentrancyEth, + "reentrancy_indirect.sol", + "0.6.11", + ), + Test(all_detectors.ReentrancyEth, "reentrancy.sol", "0.7.6"), + Test( + all_detectors.ReentrancyEth, + "reentrancy_indirect.sol", + "0.7.6", + ), + Test( + all_detectors.ReentrancyEth, + "DAO.sol", + "0.4.25", + ), + # Test the nonReentrant filtering + Test(all_detectors.ReentrancyEth, "reentrancy_with_non_reentrant.sol", "0.8.10"), + # Test parse_ignore_comments + Test(all_detectors.ReentrancyEth, "reentrancy_filtered_comments.sol", "0.8.10"), + Test( + all_detectors.UninitializedStorageVars, + "uninitialized_storage_pointer.sol", + "0.4.25", + ), + Test( + all_detectors.UninitializedStorageVars, + "uninitialized_storage_pointer.sol", + "0.8.19", + ), + Test(all_detectors.TxOrigin, "tx_origin.sol", "0.4.25"), + Test(all_detectors.TxOrigin, "tx_origin.sol", "0.5.16"), + Test(all_detectors.TxOrigin, "tx_origin.sol", "0.6.11"), + Test(all_detectors.TxOrigin, "tx_origin.sol", "0.7.6"), + Test( + all_detectors.UnusedStateVars, + "unused_state.sol", + "0.4.25", + ), + Test( + all_detectors.UnusedStateVars, + "unused_state.sol", + "0.5.16", + ), + Test( + all_detectors.UnusedStateVars, + "unused_state.sol", + "0.6.11", + ), + Test( + all_detectors.UnusedStateVars, + "unused_state.sol", + "0.7.6", + ), + Test(all_detectors.LockedEther, "locked_ether.sol", "0.4.25"), + Test(all_detectors.LockedEther, "locked_ether.sol", "0.5.16"), + Test(all_detectors.LockedEther, "locked_ether.sol", "0.6.11"), + Test(all_detectors.LockedEther, "locked_ether.sol", "0.7.6"), + Test( + all_detectors.ArbitrarySendEth, + "arbitrary_send_eth.sol", + "0.4.25", + ), + Test( + all_detectors.ArbitrarySendEth, + "arbitrary_send_eth.sol", + "0.5.16", + ), + Test( + all_detectors.ArbitrarySendEth, + "arbitrary_send_eth.sol", + "0.6.11", + ), + Test( + all_detectors.ArbitrarySendEth, + "arbitrary_send_eth.sol", + "0.7.6", + ), + Test( + all_detectors.Assembly, + "inline_assembly_contract.sol", + "0.4.25", + ), + Test( + all_detectors.Assembly, + "inline_assembly_library.sol", + "0.4.25", + ), + Test( + all_detectors.Assembly, + "inline_assembly_contract.sol", + "0.5.16", + ), + Test( + all_detectors.Assembly, + "inline_assembly_library.sol", + "0.5.16", + ), + Test( + all_detectors.Assembly, + "inline_assembly_contract.sol", + "0.6.11", + ), + Test( + all_detectors.Assembly, + "inline_assembly_library.sol", + "0.6.11", + ), + Test( + all_detectors.Assembly, + "inline_assembly_contract.sol", + "0.7.6", + ), + Test( + all_detectors.Assembly, + "inline_assembly_library.sol", + "0.7.6", + ), + Test( + all_detectors.LowLevelCalls, + "low_level_calls.sol", + "0.4.25", + ), + Test( + all_detectors.LowLevelCalls, + "low_level_calls.sol", + "0.5.16", + ), + Test( + all_detectors.LowLevelCalls, + "low_level_calls.sol", + "0.6.11", + ), + Test( + all_detectors.LowLevelCalls, + "low_level_calls.sol", + "0.7.6", + ), + Test( + all_detectors.CouldBeConstant, + "const_state_variables.sol", + "0.4.25", + ), + Test( + all_detectors.CouldBeConstant, + "const_state_variables.sol", + "0.5.16", + ), + Test( + all_detectors.CouldBeConstant, + "const_state_variables.sol", + "0.6.11", + ), + Test( + all_detectors.CouldBeConstant, + "const_state_variables.sol", + "0.7.6", + ), + Test( + all_detectors.CouldBeConstant, + "const_state_variables.sol", + "0.8.0", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.4.25", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.5.16", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.6.11", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.7.6", + ), + Test( + all_detectors.CouldBeImmutable, + "immut_state_variables.sol", + "0.8.0", + ), + Test( + all_detectors.ExternalFunction, + "external_function.sol", + "0.4.25", + ), + Test( + all_detectors.ExternalFunction, + "external_function_2.sol", + "0.4.25", + ), + Test( + all_detectors.ExternalFunction, + "external_function_3.sol", + "0.4.25", + ), + Test( + all_detectors.ExternalFunction, + "external_function.sol", + "0.5.16", + ), + Test( + all_detectors.ExternalFunction, + "external_function_2.sol", + "0.5.16", + ), + Test( + all_detectors.ExternalFunction, + "external_function_3.sol", + "0.5.16", + ), + Test( + all_detectors.ExternalFunction, + "external_function.sol", + "0.6.11", + ), + Test( + all_detectors.ExternalFunction, + "external_function_2.sol", + "0.6.11", + ), + Test( + all_detectors.ExternalFunction, + "external_function_3.sol", + "0.6.11", + ), + Test( + all_detectors.ExternalFunction, + "external_function.sol", + "0.7.6", + ), + Test( + all_detectors.ExternalFunction, + "external_function_2.sol", + "0.7.6", + ), + Test( + all_detectors.ExternalFunction, + "external_function_3.sol", + "0.7.6", + ), + Test( + all_detectors.NamingConvention, + "naming_convention.sol", + "0.4.25", + ), + Test( + all_detectors.NamingConvention, + "naming_convention.sol", + "0.5.16", + ), + Test( + all_detectors.NamingConvention, + "naming_convention.sol", + "0.6.11", + ), + Test( + all_detectors.NamingConvention, + "naming_convention.sol", + "0.7.6", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.4.25", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.5.16", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.6.11", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.7.6", + ), + Test( + all_detectors.ControlledDelegateCall, + "controlled_delegatecall.sol", + "0.4.25", + ), + Test( + all_detectors.ControlledDelegateCall, + "controlled_delegatecall.sol", + "0.5.16", + ), + Test( + all_detectors.ControlledDelegateCall, + "controlled_delegatecall.sol", + "0.6.11", + ), + Test( + all_detectors.ControlledDelegateCall, + "controlled_delegatecall.sol", + "0.7.6", + ), + Test( + all_detectors.UninitializedLocalVars, + "uninitialized_local_variable.sol", + "0.4.25", + ), + Test( + all_detectors.UninitializedLocalVars, + "uninitialized_local_variable.sol", + "0.5.16", + ), + Test( + all_detectors.UninitializedLocalVars, + "uninitialized_local_variable.sol", + "0.6.11", + ), + Test( + all_detectors.UninitializedLocalVars, + "uninitialized_local_variable.sol", + "0.7.6", + ), + Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.4.25"), + Test( + all_detectors.ConstantFunctionsState, + "constant.sol", + "0.4.25", + ), + Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.5.16"), + Test( + all_detectors.ConstantFunctionsState, + "constant.sol", + "0.5.16", + ), + Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.6.11"), + Test( + all_detectors.ConstantFunctionsState, + "constant.sol", + "0.6.11", + ), + Test(all_detectors.ConstantFunctionsAsm, "constant.sol", "0.7.6"), + Test(all_detectors.ConstantFunctionsState, "constant.sol", "0.7.6"), + Test( + all_detectors.UnusedReturnValues, + "unused_return.sol", + "0.4.25", + ), + Test( + all_detectors.UnusedReturnValues, + "unused_return.sol", + "0.5.16", + ), + Test( + all_detectors.UnusedReturnValues, + "unused_return.sol", + "0.6.11", + ), + Test( + all_detectors.UnusedReturnValues, + "unused_return.sol", + "0.7.6", + ), + Test( + all_detectors.UncheckedTransfer, + "unused_return_transfers.sol", + "0.7.6", + ), + Test( + all_detectors.ShadowingAbstractDetection, + "shadowing_abstract.sol", + "0.4.25", + ), + Test( + all_detectors.ShadowingAbstractDetection, + "shadowing_abstract.sol", + "0.5.16", + ), + Test( + all_detectors.ShadowingAbstractDetection, + "shadowing_state_variable.sol", + "0.7.5", + ), + Test( + all_detectors.ShadowingAbstractDetection, + "public_gap_variable.sol", + "0.7.5", + ), + Test( + all_detectors.StateShadowing, + "shadowing_state_variable.sol", + "0.4.25", + ), + Test( + all_detectors.StateShadowing, + "shadowing_state_variable.sol", + "0.5.16", + ), + Test( + all_detectors.StateShadowing, + "shadowing_state_variable.sol", + "0.6.11", + ), + Test( + all_detectors.StateShadowing, + "shadowing_state_variable.sol", + "0.7.5", + ), + Test( + all_detectors.StateShadowing, + "public_gap_variable.sol", + "0.7.5", + ), + Test( + all_detectors.StateShadowing, + "shadowing_state_variable.sol", + "0.7.6", + ), + Test(all_detectors.Timestamp, "timestamp.sol", "0.4.25"), + Test(all_detectors.Timestamp, "timestamp.sol", "0.5.16"), + Test(all_detectors.Timestamp, "timestamp.sol", "0.6.11"), + Test(all_detectors.Timestamp, "timestamp.sol", "0.7.6"), + Test( + all_detectors.MultipleCallsInLoop, + "multiple_calls_in_loop.sol", + "0.4.25", + ), + Test( + all_detectors.MultipleCallsInLoop, + "multiple_calls_in_loop.sol", + "0.5.16", + ), + Test( + all_detectors.MultipleCallsInLoop, + "multiple_calls_in_loop.sol", + "0.6.11", + ), + Test( + all_detectors.MultipleCallsInLoop, + "multiple_calls_in_loop.sol", + "0.7.6", + ), + Test( + all_detectors.BuiltinSymbolShadowing, + "shadowing_builtin_symbols.sol", + "0.4.25", + ), + Test( + all_detectors.BuiltinSymbolShadowing, + "shadowing_builtin_symbols.sol", + "0.5.16", + ), + Test( + all_detectors.LocalShadowing, + "shadowing_local_variable.sol", + "0.4.25", + ), + Test( + all_detectors.LocalShadowing, + "shadowing_local_variable.sol", + "0.5.16", + ), + Test( + all_detectors.LocalShadowing, + "shadowing_local_variable.sol", + "0.6.11", + ), + Test( + all_detectors.LocalShadowing, + "shadowing_local_variable.sol", + "0.7.6", + ), + Test( + all_detectors.RightToLeftOverride, + "right_to_left_override.sol", + "0.4.25", + ), + Test( + all_detectors.RightToLeftOverride, + "right_to_left_override.sol", + "0.5.16", + ), + Test( + all_detectors.RightToLeftOverride, + "right_to_left_override.sol", + "0.6.11", + ), + Test( + all_detectors.RightToLeftOverride, + "unicode_direction_override.sol", + "0.8.0", + ), + Test(all_detectors.VoidConstructor, "void-cst.sol", "0.4.25"), + Test(all_detectors.VoidConstructor, "void-cst.sol", "0.5.16"), + Test(all_detectors.VoidConstructor, "void-cst.sol", "0.6.11"), + Test(all_detectors.VoidConstructor, "void-cst.sol", "0.7.6"), + Test( + all_detectors.UncheckedSend, + "unchecked_send.sol", + "0.4.25", + ), + Test( + all_detectors.UncheckedSend, + "unchecked_send.sol", + "0.5.16", + ), + Test( + all_detectors.UncheckedSend, + "unchecked_send.sol", + "0.6.11", + ), + Test( + all_detectors.UncheckedSend, + "unchecked_send.sol", + "0.7.6", + ), + Test( + all_detectors.ReentrancyEvent, + "reentrancy-events.sol", + "0.5.16", + ), + Test( + all_detectors.ReentrancyEvent, + "reentrancy-events.sol", + "0.6.11", + ), + Test( + all_detectors.ReentrancyEvent, + "reentrancy-events.sol", + "0.7.6", + ), + Test( + all_detectors.IncorrectStrictEquality, + "incorrect_equality.sol", + "0.4.25", + ), + Test( + all_detectors.IncorrectStrictEquality, + "incorrect_equality.sol", + "0.5.16", + ), + Test( + all_detectors.IncorrectStrictEquality, + "incorrect_equality.sol", + "0.6.11", + ), + Test( + all_detectors.IncorrectStrictEquality, + "incorrect_equality.sol", + "0.7.6", + ), + Test( + all_detectors.TooManyDigits, + "too_many_digits.sol", + "0.4.25", + ), + Test( + all_detectors.TooManyDigits, + "too_many_digits.sol", + "0.5.16", + ), + Test( + all_detectors.TooManyDigits, + "too_many_digits.sol", + "0.6.11", + ), + Test( + all_detectors.TooManyDigits, + "too_many_digits.sol", + "0.7.6", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Buggy.sol", + "0.4.25", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Fixed.sol", + "0.4.25", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "whitelisted.sol", + "0.4.25", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Buggy.sol", + "0.5.16", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Fixed.sol", + "0.5.16", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "whitelisted.sol", + "0.5.16", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Buggy.sol", + "0.6.11", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Fixed.sol", + "0.6.11", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "whitelisted.sol", + "0.6.11", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Buggy.sol", + "0.7.6", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Fixed.sol", + "0.7.6", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "whitelisted.sol", + "0.7.6", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Buggy.sol", + "0.8.15", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "Fixed.sol", + "0.8.15", + ), + Test( + all_detectors.UnprotectedUpgradeable, + "whitelisted.sol", + "0.8.15", + ), + Test( + all_detectors.ABIEncoderV2Array, + "storage_ABIEncoderV2_array.sol", + "0.4.25", + ), + Test( + all_detectors.ABIEncoderV2Array, + "storage_ABIEncoderV2_array.sol", + "0.5.10", + ), + Test( + all_detectors.ABIEncoderV2Array, + "storage_ABIEncoderV2_array.sol", + "0.5.9", + ), + Test( + all_detectors.ArrayByReference, + "array_by_reference.sol", + "0.4.25", + ), + Test( + all_detectors.ArrayByReference, + "array_by_reference.sol", + "0.5.16", + ), + Test( + all_detectors.ArrayByReference, + "array_by_reference.sol", + "0.6.11", + ), + Test( + all_detectors.ArrayByReference, + "array_by_reference.sol", + "0.7.6", + ), + Test( + all_detectors.AssertStateChange, + "assert_state_change.sol", + "0.4.25", + ), + Test( + all_detectors.AssertStateChange, + "assert_state_change.sol", + "0.5.16", + ), + Test( + all_detectors.AssertStateChange, + "assert_state_change.sol", + "0.6.11", + ), + Test( + all_detectors.AssertStateChange, + "assert_state_change.sol", + "0.7.6", + ), + Test( + all_detectors.ArrayLengthAssignment, + "array_length_assignment.sol", + "0.4.25", + ), + Test( + all_detectors.ArrayLengthAssignment, + "array_length_assignment.sol", + "0.5.16", + ), + Test( + all_detectors.CostlyOperationsInLoop, + "multiple_costly_operations_in_loop.sol", + "0.4.25", + ), + Test( + all_detectors.CostlyOperationsInLoop, + "multiple_costly_operations_in_loop.sol", + "0.5.16", + ), + Test( + all_detectors.CostlyOperationsInLoop, + "multiple_costly_operations_in_loop.sol", + "0.6.11", + ), + Test( + all_detectors.CostlyOperationsInLoop, + "multiple_costly_operations_in_loop.sol", + "0.7.6", + ), + Test( + all_detectors.FunctionInitializedState, + "function_init_state_variables.sol", + "0.4.25", + ), + Test( + all_detectors.FunctionInitializedState, + "function_init_state_variables.sol", + "0.5.16", + ), + Test( + all_detectors.FunctionInitializedState, + "function_init_state_variables.sol", + "0.6.11", + ), + Test( + all_detectors.FunctionInitializedState, + "function_init_state_variables.sol", + "0.7.6", + ), + Test( + all_detectors.MappingDeletionDetection, + "MappingDeletion.sol", + "0.4.25", + ), + Test( + all_detectors.MappingDeletionDetection, + "MappingDeletion.sol", + "0.5.16", + ), + Test( + all_detectors.MappingDeletionDetection, + "MappingDeletion.sol", + "0.6.11", + ), + Test( + all_detectors.MappingDeletionDetection, + "MappingDeletion.sol", + "0.7.6", + ), + Test( + all_detectors.PublicMappingNested, + "public_mappings_nested.sol", + "0.4.25", + ), + Test( + all_detectors.RedundantStatements, + "redundant_statements.sol", + "0.4.25", + ), + Test( + all_detectors.RedundantStatements, + "redundant_statements.sol", + "0.5.16", + ), + Test( + all_detectors.RedundantStatements, + "redundant_statements.sol", + "0.6.11", + ), + Test( + all_detectors.RedundantStatements, + "redundant_statements.sol", + "0.7.6", + ), + Test( + all_detectors.ReusedBaseConstructor, + "reused_base_constructor.sol", + "0.4.21", + ), + Test( + all_detectors.ReusedBaseConstructor, + "reused_base_constructor.sol", + "0.4.25", + ), + Test( + all_detectors.StorageSignedIntegerArray, + "storage_signed_integer_array.sol", + "0.5.10", + ), + Test( + all_detectors.StorageSignedIntegerArray, + "storage_signed_integer_array.sol", + "0.5.16", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented.sol", + "0.4.25", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented.sol", + "0.5.16", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented.sol", + "0.6.11", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented.sol", + "0.7.6", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented_interfaces.sol", + "0.5.16", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented_interfaces.sol", + "0.6.11", + ), + Test( + all_detectors.UnimplementedFunctionDetection, + "unimplemented_interfaces.sol", + "0.7.6", + ), + Test(all_detectors.BadPRNG, "bad_prng.sol", "0.4.25"), + Test(all_detectors.BadPRNG, "bad_prng.sol", "0.5.16"), + Test(all_detectors.BadPRNG, "bad_prng.sol", "0.6.11"), + Test(all_detectors.BadPRNG, "bad_prng.sol", "0.7.6"), + Test( + all_detectors.MissingEventsAccessControl, + "missing_events_access_control.sol", + "0.4.25", + ), + Test( + all_detectors.MissingEventsAccessControl, + "missing_events_access_control.sol", + "0.5.16", + ), + Test( + all_detectors.MissingEventsAccessControl, + "missing_events_access_control.sol", + "0.6.11", + ), + Test( + all_detectors.MissingEventsAccessControl, + "missing_events_access_control.sol", + "0.7.6", + ), + Test( + all_detectors.MissingEventsArithmetic, + "missing_events_arithmetic.sol", + "0.4.25", + ), + Test( + all_detectors.MissingEventsArithmetic, + "missing_events_arithmetic.sol", + "0.5.16", + ), + Test( + all_detectors.MissingEventsArithmetic, + "missing_events_arithmetic.sol", + "0.6.11", + ), + Test( + all_detectors.MissingEventsArithmetic, + "missing_events_arithmetic.sol", + "0.7.6", + ), + Test( + all_detectors.ModifierDefaultDetection, + "modifier_default.sol", + "0.4.25", + ), + Test( + all_detectors.ModifierDefaultDetection, + "modifier_default.sol", + "0.5.16", + ), + Test( + all_detectors.ModifierDefaultDetection, + "modifier_default.sol", + "0.6.11", + ), + Test( + all_detectors.ModifierDefaultDetection, + "modifier_default.sol", + "0.7.6", + ), + Test( + all_detectors.IncorrectUnaryExpressionDetection, + "invalid_unary_expression.sol", + "0.4.25", + ), + Test( + all_detectors.MissingZeroAddressValidation, + "missing_zero_address_validation.sol", + "0.4.25", + ), + Test( + all_detectors.MissingZeroAddressValidation, + "missing_zero_address_validation.sol", + "0.5.16", + ), + Test( + all_detectors.MissingZeroAddressValidation, + "missing_zero_address_validation.sol", + "0.6.11", + ), + Test( + all_detectors.MissingZeroAddressValidation, + "missing_zero_address_validation.sol", + "0.7.6", + ), + Test( + all_detectors.PredeclarationUsageLocal, + "predeclaration_usage_local.sol", + "0.4.25", + ), + Test( + all_detectors.DeadCode, + "dead-code.sol", + "0.8.0", + ), + Test( + all_detectors.WriteAfterWrite, + "write-after-write.sol", + "0.8.0", + ), + Test( + all_detectors.ShiftParameterMixup, + "shift_parameter_mixup.sol", + "0.4.25", + ), + Test( + all_detectors.ShiftParameterMixup, + "shift_parameter_mixup.sol", + "0.5.16", + ), + Test( + all_detectors.ShiftParameterMixup, + "shift_parameter_mixup.sol", + "0.6.11", + ), + Test( + all_detectors.ShiftParameterMixup, + "shift_parameter_mixup.sol", + "0.7.6", + ), + Test( + all_detectors.MissingInheritance, + "unimplemented_interface.sol", + "0.4.25", + ), + Test( + all_detectors.MissingInheritance, + "unimplemented_interface.sol", + "0.5.16", + ), + Test( + all_detectors.MissingInheritance, + "unimplemented_interface.sol", + "0.6.11", + ), + Test( + all_detectors.MissingInheritance, + "unimplemented_interface.sol", + "0.7.6", + ), + # Does not work on the CI. Most likely because of solc 0.4.2? + # Test( + # all_detectors.EnumConversion, + # "enum_conversion.sol", + # "0.4.2", + # ), + Test( + all_detectors.MultipleConstructorSchemes, + "multiple_constructor_schemes.sol", + "0.4.22", + ), + Test( + all_detectors.DeprecatedStandards, + "deprecated_calls.sol", + "0.4.25", + ), + Test( + all_detectors.DivideBeforeMultiply, + "divide_before_multiply.sol", + "0.4.25", + ), + Test( + all_detectors.DivideBeforeMultiply, + "divide_before_multiply.sol", + "0.5.16", + ), + Test( + all_detectors.DivideBeforeMultiply, + "divide_before_multiply.sol", + "0.6.11", + ), + Test( + all_detectors.DivideBeforeMultiply, + "divide_before_multiply.sol", + "0.7.6", + ), + Test( + all_detectors.TypeBasedTautology, + "type_based_tautology.sol", + "0.4.25", + ), + Test( + all_detectors.TypeBasedTautology, + "type_based_tautology.sol", + "0.5.16", + ), + Test( + all_detectors.TypeBasedTautology, + "type_based_tautology.sol", + "0.6.11", + ), + Test( + all_detectors.TypeBasedTautology, + "type_based_tautology.sol", + "0.7.6", + ), + Test( + all_detectors.SimilarVarsDetection, + "similar_variables.sol", + "0.4.25", + ), + Test( + all_detectors.SimilarVarsDetection, + "similar_variables.sol", + "0.5.16", + ), + Test( + all_detectors.SimilarVarsDetection, + "similar_variables.sol", + "0.6.11", + ), + Test( + all_detectors.SimilarVarsDetection, + "similar_variables.sol", + "0.7.6", + ), + Test( + all_detectors.MsgValueInLoop, + "msg_value_loop.sol", + "0.4.25", + ), + Test( + all_detectors.MsgValueInLoop, + "msg_value_loop.sol", + "0.5.16", + ), + Test( + all_detectors.MsgValueInLoop, + "msg_value_loop.sol", + "0.6.11", + ), + Test( + all_detectors.MsgValueInLoop, + "msg_value_loop.sol", + "0.7.6", + ), + Test( + all_detectors.MsgValueInLoop, + "msg_value_loop.sol", + "0.8.0", + ), + Test( + all_detectors.DelegatecallInLoop, + "delegatecall_loop.sol", + "0.4.25", + ), + Test( + all_detectors.DelegatecallInLoop, + "delegatecall_loop.sol", + "0.5.16", + ), + Test( + all_detectors.DelegatecallInLoop, + "delegatecall_loop.sol", + "0.6.11", + ), + Test( + all_detectors.DelegatecallInLoop, + "delegatecall_loop.sol", + "0.7.6", + ), + Test( + all_detectors.DelegatecallInLoop, + "delegatecall_loop.sol", + "0.8.0", + ), + Test( + all_detectors.ProtectedVariables, + "comment.sol", + "0.8.2", + ), + Test( + all_detectors.ArbitrarySendErc20NoPermit, + "arbitrary_send_erc20.sol", + "0.4.25", + ), + Test( + all_detectors.ArbitrarySendErc20NoPermit, + "arbitrary_send_erc20.sol", + "0.5.16", + ), + Test( + all_detectors.ArbitrarySendErc20NoPermit, + "arbitrary_send_erc20.sol", + "0.6.11", + ), + Test( + all_detectors.ArbitrarySendErc20NoPermit, + "arbitrary_send_erc20.sol", + "0.7.6", + ), + Test( + all_detectors.ArbitrarySendErc20NoPermit, + "arbitrary_send_erc20.sol", + "0.8.0", + ), + Test( + all_detectors.ArbitrarySendErc20NoPermit, + "arbitrary_send_erc20_inheritance.sol", + "0.8.0", + ), + Test( + all_detectors.ArbitrarySendErc20Permit, + "arbitrary_send_erc20_permit.sol", + "0.4.25", + ), + Test( + all_detectors.ArbitrarySendErc20Permit, + "arbitrary_send_erc20_permit.sol", + "0.5.16", + ), + Test( + all_detectors.ArbitrarySendErc20Permit, + "arbitrary_send_erc20_permit.sol", + "0.6.11", + ), + Test( + all_detectors.ArbitrarySendErc20Permit, + "arbitrary_send_erc20_permit.sol", + "0.7.6", + ), + Test( + all_detectors.ArbitrarySendErc20Permit, + "arbitrary_send_erc20_permit.sol", + "0.8.0", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_collision.sol", + "0.4.25", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_collision.sol", + "0.5.16", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_collision.sol", + "0.6.11", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_collision.sol", + "0.7.6", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_collision.sol", + "0.8.0", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_wrong_return_type.sol", + "0.4.25", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_wrong_return_type.sol", + "0.5.16", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_wrong_return_type.sol", + "0.6.11", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_wrong_return_type.sol", + "0.7.6", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_wrong_return_type.sol", + "0.8.0", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_state_var_collision.sol", + "0.4.25", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_state_var_collision.sol", + "0.5.16", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_state_var_collision.sol", + "0.6.11", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_state_var_collision.sol", + "0.7.6", + ), + Test( + all_detectors.DomainSeparatorCollision, + "permit_domain_state_var_collision.sol", + "0.8.0", + ), + Test( + all_detectors.VarReadUsingThis, + "var_read_using_this.sol", + "0.4.25", + ), + Test( + all_detectors.VarReadUsingThis, + "var_read_using_this.sol", + "0.5.16", + ), + Test(all_detectors.VarReadUsingThis, "var_read_using_this.sol", "0.6.11"), + Test( + all_detectors.VarReadUsingThis, + "var_read_using_this.sol", + "0.7.6", + ), + Test( + all_detectors.VarReadUsingThis, + "var_read_using_this.sol", + "0.8.15", + ), + Test( + all_detectors.CyclomaticComplexity, + "HighCyclomaticComplexity.sol", + "0.8.16", + ), + Test( + all_detectors.CyclomaticComplexity, + "LowCyclomaticComplexity.sol", + "0.8.16", + ), + Test( + all_detectors.CacheArrayLength, + "CacheArrayLength.sol", + "0.8.17", + ), + Test( + all_detectors.IncorrectUsingFor, + "IncorrectUsingForTopLevel.sol", + "0.8.17", + ), + Test( + all_detectors.EncodePackedCollision, + "encode_packed_collision.sol", + "0.7.6", + ), + Test( + all_detectors.IncorrectReturn, + "incorrect_return.sol", + "0.8.10", + ), + Test( + all_detectors.ReturnInsteadOfLeave, + "incorrect_return.sol", + "0.8.10", + ), + Test( + all_detectors.IncorrectOperatorExponentiation, + "incorrect_exp.sol", + "0.7.6", + ), + Test( + all_detectors.TautologicalCompare, + "compare.sol", + "0.8.20", + ), + Test( + all_detectors.ReturnBomb, + "return_bomb.sol", + "0.8.20", + ), + ## My tests for oracle Test(all_detectors.OracleDataCheck, "oracle_data_check1.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_data_check2.sol", "0.8.20"), - Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_double_internal_fc.sol", "0.8.20"), + Test( + all_detectors.OracleDataCheck, "oracle_data_check_price_in_double_internal_fc.sol", "0.8.20" + ), Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_internal_fc.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_non_revert.sol", "0.8.20"), Test(all_detectors.DeprecatedChainlinkCall, "oracle_deprecated_call.sol", "0.8.20"), From 1780794a6ad0e82e8c8e2be5aacbc6b1e294f6e4 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 4 Feb 2024 15:32:27 +0100 Subject: [PATCH 59/87] Solve subscriptable type --- slither/detectors/oracles/oracle_detector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 6e212e31ed..8731257b3d 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -1,3 +1,4 @@ +from typing import List from slither.analyses.data_dependency.data_dependency import get_dependencies from slither.core.declarations.contract import Contract from slither.core.declarations.function_contract import FunctionContract @@ -11,7 +12,7 @@ class OracleDetector(AbstractDetector): - def find_oracles(self, contracts: Contract) -> list[Oracle]: + def find_oracles(self, contracts: Contract) -> List[Oracle]: """ Detects off-chain oracle contract and VAR """ From 2399a7a9eab29e973c9168611705a51f7c34d102 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 4 Feb 2024 16:28:37 +0100 Subject: [PATCH 60/87] Uncomment test_ast_parsing.vy + remove unnecessary comment --- tests/e2e/detectors/test_detectors.py | 1 - tests/e2e/vyper_parsing/test_ast_parsing.py | 36 ++++++++++----------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 43983ad488..dbfa79f944 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1679,7 +1679,6 @@ def id_test(test_item: Test): "return_bomb.sol", "0.8.20", ), - ## My tests for oracle Test(all_detectors.OracleDataCheck, "oracle_data_check1.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_data_check2.sol", "0.8.20"), Test( diff --git a/tests/e2e/vyper_parsing/test_ast_parsing.py b/tests/e2e/vyper_parsing/test_ast_parsing.py index 99e9d53de0..7ca8184364 100644 --- a/tests/e2e/vyper_parsing/test_ast_parsing.py +++ b/tests/e2e/vyper_parsing/test_ast_parsing.py @@ -1,25 +1,25 @@ -# from pathlib import Path -# from slither import Slither +from pathlib import Path +from slither import Slither -# TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" -# ALL_TESTS = list(Path(TEST_DATA_DIR).glob("*.vy")) +ALL_TESTS = list(Path(TEST_DATA_DIR).glob("*.vy")) -# def pytest_generate_tests(metafunc): -# test_cases = [] -# for test_file in ALL_TESTS: -# sl = Slither(test_file.as_posix()) -# for contract in sl.contracts: -# if contract.is_interface: -# continue -# for func_or_modifier in contract.functions: -# test_cases.append( -# (func_or_modifier.canonical_name, func_or_modifier.slithir_cfg_to_dot_str()) -# ) +def pytest_generate_tests(metafunc): + test_cases = [] + for test_file in ALL_TESTS: + sl = Slither(test_file.as_posix()) + for contract in sl.contracts: + if contract.is_interface: + continue + for func_or_modifier in contract.functions: + test_cases.append( + (func_or_modifier.canonical_name, func_or_modifier.slithir_cfg_to_dot_str()) + ) -# metafunc.parametrize("test_case", test_cases, ids=lambda x: x[0]) + metafunc.parametrize("test_case", test_cases, ids=lambda x: x[0]) -# def test_vyper_cfgir(test_case, snapshot): -# assert snapshot() == test_case[1] +def test_vyper_cfgir(test_case, snapshot): + assert snapshot() == test_case[1] From 3e21e7f84ed8fdfa41d5df521b63831242102426 Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 9 Feb 2024 09:37:38 +0100 Subject: [PATCH 61/87] fix: resolve pylint errors fix: resolve W0201 fix: pylint R1702 fix: pylint R1702 + Refactor fix: pylint errors --- .../oracles/deprecated_chainlink_calls.py | 24 ++-- slither/detectors/oracles/oracle_detector.py | 113 ++++++++++-------- .../supported_oracles/chainlink_oracle.py | 56 +++------ .../supported_oracles/help_functions.py | 6 +- .../oracles/supported_oracles/oracle.py | 55 +++++++-- 5 files changed, 148 insertions(+), 106 deletions(-) diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index 21e3f927fb..8045e3b59d 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -27,6 +27,18 @@ class DeprecatedChainlinkCall(AbstractDetector): "latestTimestamp", ] + def is_old_chainlink_call(self, ir) -> bool: + """ + Check if the given operation is an old Chainlink call. + """ + if isinstance(ir, HighLevelCall): + if ( + ir.function.name in self.DEPRECATED_CHAINLINK_CALLS + and str(ir.destination.type) == "AggregatorV3Interface" + ): + return True + return False + def find_usage_of_deprecated_chainlink_calls(self, contracts: Contract): """ Find usage of deprecated Chainlink calls in the contracts. @@ -36,14 +48,10 @@ def find_usage_of_deprecated_chainlink_calls(self, contracts: Contract): for function in contract.functions: for node in function.nodes: for ir in node.irs: - if isinstance(ir, HighLevelCall): - if ( - ir.function.name in self.DEPRECATED_CHAINLINK_CALLS - and str(ir.destination.type) == "AggregatorV3Interface" - ): - results.append( - f"Deprecated Chainlink call {ir.destination}.{ir.function.name} used ({node.source_mapping}).\n" - ) + if self.is_old_chainlink_call(ir): + results.append( + f"Deprecated Chainlink call {ir.destination}.{ir.function.name} used ({node.source_mapping}).\n" + ) return results def _detect(self): diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 8731257b3d..3f92c6596b 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -12,6 +12,68 @@ class OracleDetector(AbstractDetector): + def __init__(self, compilation_unit, slither, logger): + super().__init__(compilation_unit, slither, logger) + self.oracles = [] + self.nodes_with_var = [] + + # If the node is high level call, return the interface and the function name + @staticmethod + def obtain_interface_and_api(node) -> (str, str): + for ir in node.irs: + if isinstance(ir, HighLevelCall): + return ir.destination, ir.function.name + return None, None + + @staticmethod + def generate_oracle(ir: Operation) -> Oracle: + if ChainlinkOracle().is_instance_of(ir): + return ChainlinkOracle() + return None + + @staticmethod + def get_returned_variables_from_oracle(node) -> list: + written_vars = [] + ordered_vars = [] + for var in node.variables_written: + written_vars.append(var) + for exp in node.variables_written_as_expression: + for v in exp.expressions: + for var in written_vars: + if str(v) == str(var.name): + ordered_vars.append(var) + return ordered_vars + + @staticmethod + def check_var_condition_match(var, node) -> bool: + for ( + var2 + ) in ( + node.variables_read + ): # This iterates through all variables which are read in node, what means that they are used in condition + if var is None or var2 is None: + return False + if var.name == var2.name: + return True + return False + + @staticmethod + def map_param_to_var(var, function: FunctionContract): + for param in function.parameters: + origin_vars = get_dependencies(param, function) + for var2 in origin_vars: + if var2 == var: + return param + return None + + @staticmethod + def match_index_to_node(indexes, node): + idxs = [] + for idx in indexes: + if idx[0] == node: + idxs.append(idx[1]) + return idxs + def find_oracles(self, contracts: Contract) -> List[Oracle]: """ Detects off-chain oracle contract and VAR @@ -27,32 +89,19 @@ def find_oracles(self, contracts: Contract) -> List[Oracle]: ) = self.oracle_call(function) if returned_oracles: for oracle in returned_oracles: - interface = None - oracle_api = None - for ir in oracle.node.irs: - if isinstance(ir, HighLevelCall): - interface = ir.destination - oracle_api = ir.function.name - idxs = [] - for idx in oracle_returned_var_indexes: - if idx[0] == oracle.node: - idxs.append(idx[1]) + (interface, oracle_api) = self.obtain_interface_and_api(oracle.node) + idxs = self.match_index_to_node(oracle_returned_var_indexes, oracle.node) oracle.set_data(contract, function, idxs, interface, oracle_api) oracles.append(oracle) return oracles - def generate_oracle(self, ir: Operation) -> Oracle: - if ChainlinkOracle().is_instance_of(ir): - return ChainlinkOracle() - return None - # This function was inspired by detector unused return values def oracle_call(self, function: FunctionContract): used_returned_vars = [] values_returned = [] nodes_origin = {} oracles = [] - for node in function.nodes: + for node in function.nodes: # pylint: disable=too-many-nested-blocks for ir in node.irs: oracle = self.generate_oracle(ir) if oracle: @@ -81,30 +130,6 @@ def oracle_call(self, function: FunctionContract): returned_vars_used_indexes.append((nodes_origin[value].node, index)) return oracles, returned_vars_used_indexes - def get_returned_variables_from_oracle(self, node) -> list: - written_vars = [] - ordered_vars = [] - for var in node.variables_written: - written_vars.append(var) - for exp in node.variables_written_as_expression: - for v in exp.expressions: - for var in written_vars: - if str(v) == str(var.name): - ordered_vars.append(var) - return ordered_vars - - def check_var_condition_match(self, var, node) -> bool: - for ( - var2 - ) in ( - node.variables_read - ): # This iterates through all variables which are read in node, what means that they are used in condition - if var is None or var2 is None: - return False - if var.name == var2.name: - return True - return False - def map_condition_to_var(self, var, function: FunctionContract): nodes = [] for node in function.nodes: @@ -144,14 +169,6 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars - def map_param_to_var(self, var, function: FunctionContract): - for param in function.parameters: - origin_vars = get_dependencies(param, function) - for var2 in origin_vars: - if var2 == var: - return param - return None - # This function interates through all internal calls in function and checks if the var is used in condition any of them def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index b1bf76160a..1852497d9f 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -28,20 +28,10 @@ def __init__(self): super().__init__(CHAINLINK_ORACLE_CALLS) self.oracle_type = "Chainlink" - # This function checks if the updatedAt value is validated. - def check_staleness(self, var: VarInCondition) -> bool: - if var is None: - return False - for node in var.nodes_with_var: - str_node = str(node) - # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly - if "block.timestamp" in str_node: - return True - return False - # This function checks if the RoundId value is validated in connection with answeredInRound value # But this last variable was deprecated. We left this function for possible future use. - def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: + @staticmethod + def check_RoundId(var: VarInCondition, var2: VarInCondition) -> bool: if var is None or var2 is None: return False for node in var.nodes_with_var: @@ -57,30 +47,8 @@ def check_RoundId(self, var: VarInCondition, var2: VarInCondition) -> bool: return False - # This functions validates checks of price value - def check_price(self, var: VarInCondition) -> bool: - if var is None: - return False - for node in var.nodes_with_var: - for ir in node.irs: - if isinstance(ir, Binary): - if isinstance(ir.variable_right, Constant): - if ir.type is (BinaryType.GREATER): - if ir.variable_right.value == 0: - return True - elif ir.type is (BinaryType.NOT_EQUAL): - if ir.variable_right.value == 0: - return True - if isinstance(ir.variable_left, Constant): - if ir.type is (BinaryType.LESS): - if ir.variable_left.value == 0: - return True - # If the conditions does not match we are looking for revert or return node - return check_revert(node) or return_boolean(node) - - return False - - def generate_naive_order(self): + @staticmethod + def generate_naive_order(): vars_order = {} vars_order[ChainlinkVars.ROUNDID.value] = None vars_order[ChainlinkVars.ANSWER.value] = None @@ -91,7 +59,7 @@ def generate_naive_order(self): def find_which_vars_are_used(self): vars_order = self.generate_naive_order() - for i in range(len(self.oracle_vars)): + for i in range(len(self.oracle_vars)): # pylint: disable=consider-using-enumerate vars_order[self.returned_vars_indexes[i]] = self.oracle_vars[i] return vars_order @@ -102,6 +70,14 @@ def is_needed_to_check_conditions(self, var): return False return True + @staticmethod + def price_check_for_liveness(ir: Binary) -> bool: + if ir.type is (BinaryType.EQUAL): + if isinstance(ir.variable_right, Constant): + if ir.variable_right.value == 1: + return True + return False + def is_sequencer_check(self, answer, startedAt): if answer is None or startedAt is None: return False @@ -111,10 +87,8 @@ def is_sequencer_check(self, answer, startedAt): for node in answer.nodes: for ir in node.irs: if isinstance(ir, Binary): - if ir.type is (BinaryType.EQUAL): - if isinstance(ir.variable_right, Constant): - if ir.variable_right.value == 1: - answer_checked = True + if self.price_check_for_liveness(ir): + answer_checked = True startedAt_checked = self.check_staleness(startedAt) print(answer_checked, startedAt_checked) diff --git a/slither/detectors/oracles/supported_oracles/help_functions.py b/slither/detectors/oracles/supported_oracles/help_functions.py index 78ba8723ca..ece271836a 100644 --- a/slither/detectors/oracles/supported_oracles/help_functions.py +++ b/slither/detectors/oracles/supported_oracles/help_functions.py @@ -4,6 +4,8 @@ from slither.slithir.operations.solidity_call import SolidityCall # Helpfull functions + +# Check if the node's sons contain a revert statement def check_revert(node: Node) -> bool: for n in node.sons: if n.type == NodeType.EXPRESSION: @@ -14,6 +16,7 @@ def check_revert(node: Node) -> bool: return False +# Check if the node's sons contain a return statement def return_boolean(node: Node) -> bool: for n in node.sons: if n.type == NodeType.RETURN: @@ -23,7 +26,8 @@ def return_boolean(node: Node) -> bool: return False -def is_internal_call(node): +# Check if the node is an internal call +def is_internal_call(node) -> bool: for ir in node.irs: if isinstance(ir, InternalCall): return True diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 6d9d990b4c..590690fc29 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -1,14 +1,20 @@ from slither.slithir.operations import HighLevelCall, Operation from slither.core.declarations import Function +from slither.slithir.operations import ( + Binary, + BinaryType, +) +from slither.detectors.oracles.supported_oracles.help_functions import check_revert, return_boolean +from slither.slithir.variables.constant import Constant # This class was created to store variable and all conditional nodes where it is used -class VarInCondition: +class VarInCondition: # pylint: disable=too-few-public-methods def __init__(self, _var, _nodes): self.var = _var self.nodes_with_var = _nodes -class Oracle: # pylint: disable=too-few-public-methods +class Oracle: # pylint: disable=too-few-public-methods, too-many-instance-attributes def __init__(self, _calls): self.calls = _calls self.contract = None @@ -46,16 +52,49 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or self.interface = _interface self.oracle_api = _oracle_api - # Data validation helpful functions + # Data validation functions def naive_data_validation(self): - return [] + return self - def check_price(self, var: VarInCondition) -> bool: + @staticmethod + def check_greater_zero(ir: Operation) -> bool: + if isinstance(ir.variable_right, Constant): + if ir.type is (BinaryType.GREATER) or ir.type is (BinaryType.NOT_EQUAL): + if ir.variable_right.value == 0: + return True + elif isinstance(ir.variable_left, Constant): + if ir.type is (BinaryType.LESS) or ir.type is (BinaryType.NOT_EQUAL): + if ir.variable_left.value == 0: + return True + return False + + @staticmethod + def timestamp_in_node(node) -> bool: + if "block.timestamp" in str(node): + return True + return False + + # This function checks if the timestamp value is validated. + def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False - return True + for node in var.nodes_with_var: + # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly + if self.timestamp_in_node(node): + return True - def check_staleness(self, var: VarInCondition) -> bool: + return False + + # This functions validates checks of price value + def check_price(self, var: VarInCondition) -> bool: if var is None: return False - return True + for node in var.nodes_with_var: + for ir in node.irs: + if isinstance(ir, Binary): + if self.check_greater_zero(ir): + return True + # If the conditions does not match we are looking for revert or return node + return check_revert(node) or return_boolean(node) + + return False From 75712c5e0be76dbd673dfb93dea9ef7d6ceddda5 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 10 Feb 2024 12:25:01 +0100 Subject: [PATCH 62/87] feat: Support out of function check --- slither/detectors/oracles/oracle_detector.py | 58 +++++++++++++++---- .../oracles/supported_oracles/oracle.py | 4 ++ .../0.8.20/oracle_check_out_of_function.sol | 2 +- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 9e4e205bc6..653c92de2f 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -9,6 +9,8 @@ from slither.detectors.oracles.supported_oracles.oracle import Oracle, VarInCondition from slither.detectors.oracles.supported_oracles.chainlink_oracle import ChainlinkOracle from slither.detectors.oracles.supported_oracles.help_functions import is_internal_call +from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent +from slither.core.expressions.tuple_expression import TupleExpression class OracleDetector(AbstractDetector): @@ -38,10 +40,13 @@ def get_returned_variables_from_oracle(node) -> list: for var in node.variables_written: written_vars.append(var) for exp in node.variables_written_as_expression: - for v in exp.expressions: - for var in written_vars: - if str(v) == str(var.name): - ordered_vars.append(var) + if isinstance(exp, TupleExpression): + for v in exp.expressions: + for var in written_vars: + if str(v) == str(var.name): + ordered_vars.append(var) + if len(ordered_vars) == 0: + ordered_vars = written_vars return ordered_vars @staticmethod @@ -142,8 +147,10 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: vars_in_condition = [] vars_not_in_condition = [] oracle_vars = [] - + print("Vars in condition") for var in oracle.oracle_vars: + print(var) + print(oracle.function) self.nodes_with_var = [] if oracle.function.is_reading_in_conditional_node( var @@ -161,9 +168,8 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: if self.investigate_internal_call(oracle.function, var): vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) - elif self.investigate_on_return(oracle.function, var): - vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) - oracle_vars.append(VarInCondition(var, self.nodes_with_var)) + elif self.investigate_on_return(oracle, var): + return True else: vars_not_in_condition.append(var) oracle_vars.append(var) @@ -171,11 +177,39 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: oracle.vars_in_condition = vars_in_condition oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars + return True - def investigate_on_return(self, function: FunctionContract, var) -> bool: - for value in function.return_values: - if var == value: - return print("asd") + + def checks_performed_out_of_original_function(self, oracle): + node_of_call = None + function_of_call = None + for function in oracle.contract.functions: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall): + if (ir.function == oracle.function): + node_of_call = node + print(node) + function_of_call = function + print(function) + break + if node_of_call is None: + return False + + oracle.set_function(function_of_call) + oracle.set_node(node_of_call) + oracle.oracle_vars = self.get_returned_variables_from_oracle(node_of_call) + self.vars_in_conditions(oracle) + return True + + + + + def investigate_on_return(self, oracle, var) -> bool: + print("I am here") + for value in oracle.function.return_values: + if is_dependent(value, var, oracle.node): + return self.checks_performed_out_of_original_function(oracle) return False # This function interates through all internal calls in function and checks if the var is used in condition any of them diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 590690fc29..6d0eb3dccf 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -39,6 +39,9 @@ def is_instance_of(self, ir: Operation) -> bool: def set_node(self, _node): self.node = _node + def set_function(self, _function): + self.function = _function + def compare_call(self, function) -> bool: for call in self.calls: if call in str(function): @@ -51,6 +54,7 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or self.returned_vars_indexes = _returned_vars_indexes self.interface = _interface self.oracle_api = _oracle_api + # Data validation functions def naive_data_validation(self): diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol index 1f7cb37ec9..053806f3b6 100644 --- a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol +++ b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol @@ -45,7 +45,7 @@ contract ChainlinkETHUSDPriceConsumer { ); } - function obtainValidatedPrice() { + function obtainValidatedPrice() public view returns (int) { int price = getLatestPrice(); require(price > 0, "Price is not valid"); return price; From c71da945517438f7a422960306d84befb1bcc8af Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 10 Feb 2024 12:31:48 +0100 Subject: [PATCH 63/87] update test data --- ..._8_20_oracle_check_out_of_function_sol__0.txt | 2 ++ .../oracle_check_out_of_function.sol-0.8.20.zip | Bin 0 -> 5801 bytes tests/e2e/detectors/test_detectors.py | 1 + 3 files changed, 3 insertions(+) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol-0.8.20.zip diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt new file mode 100644 index 0000000000..75b51e2ce7 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt @@ -0,0 +1,2 @@ +The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call ChainlinkETHUSDPriceConsumer.priceFeed.latestRoundData (tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol#49). + diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..d89ebf2e96bf79b7978309e03afc2d29df3a57f2 GIT binary patch literal 5801 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index dbfa79f944..011ad4fc15 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1686,6 +1686,7 @@ def id_test(test_item: Test): ), Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_internal_fc.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_non_revert.sol", "0.8.20"), + Test(all_detectors.OracleDataCheck, "oracle_check_out_of_function.sol", "0.8.20"), Test(all_detectors.DeprecatedChainlinkCall, "oracle_deprecated_call.sol", "0.8.20"), ] From 72448353b02cbec88255304db07d2517b743b4c8 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 09:48:46 +0100 Subject: [PATCH 64/87] First changes for cross contract interaction --- slither/detectors/oracles/oracle_detector.py | 63 ++++++++++++------- .../oracles/supported_oracles/oracle.py | 2 +- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 653c92de2f..e71fad806d 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -11,6 +11,7 @@ from slither.detectors.oracles.supported_oracles.help_functions import is_internal_call from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent from slither.core.expressions.tuple_expression import TupleExpression +from copy import deepcopy class OracleDetector(AbstractDetector): @@ -147,10 +148,7 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: vars_in_condition = [] vars_not_in_condition = [] oracle_vars = [] - print("Vars in condition") for var in oracle.oracle_vars: - print(var) - print(oracle.function) self.nodes_with_var = [] if oracle.function.is_reading_in_conditional_node( var @@ -181,32 +179,53 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: def checks_performed_out_of_original_function(self, oracle): - node_of_call = None - function_of_call = None - for function in oracle.contract.functions: - for node in function.nodes: - for ir in node.irs: - if isinstance(ir, InternalCall): - if (ir.function == oracle.function): - node_of_call = node - print(node) - function_of_call = function - print(function) - break - if node_of_call is None: + nodes_of_call = [] + functions_of_call = [] + original_function = oracle.function + original_node = oracle.node + original_vars = oracle.oracle_vars + for contract in self.contracts: + for function in contract.functions: + if function == oracle.function: + continue + nodes, functions = self.find_if_original_function_called(oracle,function) + if nodes and functions: + nodes_of_call.extend(nodes) + functions_of_call.extend(functions) + if not nodes_of_call or not functions_of_call: return False - oracle.set_function(function_of_call) - oracle.set_node(node_of_call) - oracle.oracle_vars = self.get_returned_variables_from_oracle(node_of_call) - self.vars_in_conditions(oracle) + i = 0 + for node in nodes_of_call: + oracle.set_function(functions_of_call[i]) + oracle.set_node(node) + oracle.oracle_vars = self.get_returned_variables_from_oracle(node) + self.vars_in_conditions(oracle) + i+=1 + + + # Return back original node and function after recursion to let developer know on which line the oracle is used + oracle.set_function(original_function) + oracle.set_node(original_node) + oracle.oracle_vars = original_vars return True + + @staticmethod + def find_if_original_function_called(oracle, function): + nodes_of_call = [] + functions_of_call = [] + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall) or isinstance(ir, HighLevelCall): + print(ir.function, oracle.function) + if (ir.function == oracle.function): + nodes_of_call.append(node) + functions_of_call.append(function) + return nodes_of_call, functions_of_call - def investigate_on_return(self, oracle, var) -> bool: - print("I am here") for value in oracle.function.return_values: if is_dependent(value, var, oracle.node): return self.checks_performed_out_of_original_function(oracle) diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 6d0eb3dccf..3fafe45b0e 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -27,7 +27,7 @@ def __init__(self, _calls): self.interface = None self.oracle_api = None self.oracle_type = None - + def get_calls(self): return self.calls From c206ab29f92ab6deff0d6d7a228655b0be26d023 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 10:33:28 +0100 Subject: [PATCH 65/87] feat: cross contract capturing of nodes --- slither/detectors/oracles/oracle_detector.py | 41 +++++++++++-------- .../oracles/supported_oracles/oracle.py | 2 +- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index e71fad806d..4d7e546e6d 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -11,7 +11,6 @@ from slither.detectors.oracles.supported_oracles.help_functions import is_internal_call from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent from slither.core.expressions.tuple_expression import TupleExpression -from copy import deepcopy class OracleDetector(AbstractDetector): @@ -29,10 +28,11 @@ def obtain_interface_and_api(node) -> (str, str): return None, None @staticmethod - def generate_oracle(ir: Operation) -> Oracle: - if ChainlinkOracle().is_instance_of(ir): + def generate_oracle(ir: Operation, oracle_type=None) -> Oracle: + if ChainlinkOracle().is_instance_of(ir) or oracle_type == "Chainlink": return ChainlinkOracle() return None + @staticmethod def get_returned_variables_from_oracle(node) -> list: @@ -144,10 +144,11 @@ def map_condition_to_var(self, var, function: FunctionContract): return nodes # Check if the vars occurs in require/assert statement or in conditional node - def vars_in_conditions(self, oracle: Oracle) -> bool: - vars_in_condition = [] + def vars_in_conditions(self, oracle: Oracle): + # vars_in_condition = [] vars_not_in_condition = [] oracle_vars = [] + nodes = [] for var in oracle.oracle_vars: self.nodes_with_var = [] if oracle.function.is_reading_in_conditional_node( @@ -160,25 +161,25 @@ def vars_in_conditions(self, oracle: Oracle) -> bool: self.investigate_internal_call(ir.function, var) if len(self.nodes_with_var) > 0: - vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) + # vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: if self.investigate_internal_call(oracle.function, var): - vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) + # vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) - elif self.investigate_on_return(oracle, var): - return True + elif nodes := self.investigate_on_return(oracle, var): + oracle_vars.append(VarInCondition(var, nodes)) else: vars_not_in_condition.append(var) oracle_vars.append(var) - oracle.vars_in_condition = vars_in_condition - oracle.vars_not_in_condition = vars_not_in_condition + # oracle.vars_in_condition = vars_in_condition + oracle.vars_not_in_condition.extend(vars_not_in_condition) oracle.oracle_vars = oracle_vars return True - def checks_performed_out_of_original_function(self, oracle): + def checks_performed_out_of_original_function(self, oracle, returned_var): nodes_of_call = [] functions_of_call = [] original_function = oracle.function @@ -193,22 +194,27 @@ def checks_performed_out_of_original_function(self, oracle): nodes_of_call.extend(nodes) functions_of_call.extend(functions) if not nodes_of_call or not functions_of_call: - return False + return [] i = 0 + nodes = [] for node in nodes_of_call: oracle.set_function(functions_of_call[i]) oracle.set_node(node) - oracle.oracle_vars = self.get_returned_variables_from_oracle(node) + new_vars = self.get_returned_variables_from_oracle(node) + for var in new_vars: + if is_dependent(var, returned_var, node): + oracle.oracle_vars = [var] + break self.vars_in_conditions(oracle) + nodes.extend(oracle.oracle_vars[0].nodes_with_var) i+=1 # Return back original node and function after recursion to let developer know on which line the oracle is used oracle.set_function(original_function) oracle.set_node(original_node) - oracle.oracle_vars = original_vars - return True + return nodes @staticmethod @@ -218,7 +224,6 @@ def find_if_original_function_called(oracle, function): for node in function.nodes: for ir in node.irs: if isinstance(ir, InternalCall) or isinstance(ir, HighLevelCall): - print(ir.function, oracle.function) if (ir.function == oracle.function): nodes_of_call.append(node) functions_of_call.append(function) @@ -228,7 +233,7 @@ def find_if_original_function_called(oracle, function): def investigate_on_return(self, oracle, var) -> bool: for value in oracle.function.return_values: if is_dependent(value, var, oracle.node): - return self.checks_performed_out_of_original_function(oracle) + return self.checks_performed_out_of_original_function(oracle, value) return False # This function interates through all internal calls in function and checks if the var is used in condition any of them diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 3fafe45b0e..95c0534c83 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -21,7 +21,7 @@ def __init__(self, _calls): self.function = None self.node = None self.oracle_vars = [] - self.vars_in_condition = [] + # self.vars_in_condition = [] self.vars_not_in_condition = [] self.returned_vars_indexes = None self.interface = None From 7f49ad5176c713417db16c22fe9dfdb6e3022ba7 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 11:29:32 +0100 Subject: [PATCH 66/87] fix: Fix issues with messages --- slither/detectors/oracles/oracle_detector.py | 5 +++-- .../oracles/supported_oracles/chainlink_oracle.py | 5 +++-- slither/detectors/oracles/supported_oracles/oracle.py | 10 ++++++---- ...heck_0_8_20_oracle_check_out_of_function_sol__0.txt | 3 ++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 4d7e546e6d..45bb53ff51 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -28,8 +28,8 @@ def obtain_interface_and_api(node) -> (str, str): return None, None @staticmethod - def generate_oracle(ir: Operation, oracle_type=None) -> Oracle: - if ChainlinkOracle().is_instance_of(ir) or oracle_type == "Chainlink": + def generate_oracle(ir: Operation) -> Oracle: + if ChainlinkOracle().is_instance_of(ir): return ChainlinkOracle() return None @@ -169,6 +169,7 @@ def vars_in_conditions(self, oracle: Oracle): oracle_vars.append(VarInCondition(var, self.nodes_with_var)) elif nodes := self.investigate_on_return(oracle, var): oracle_vars.append(VarInCondition(var, nodes)) + oracle.out_of_function_checks = True else: vars_not_in_condition.append(var) oracle_vars.append(var) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 1852497d9f..1a7a54a8e1 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -26,7 +26,6 @@ class ChainlinkVars(Enum): class ChainlinkOracle(Oracle): def __init__(self): super().__init__(CHAINLINK_ORACLE_CALLS) - self.oracle_type = "Chainlink" # This function checks if the RoundId value is validated in connection with answeredInRound value # But this last variable was deprecated. We left this function for possible future use. @@ -90,7 +89,6 @@ def is_sequencer_check(self, answer, startedAt): if self.price_check_for_liveness(ir): answer_checked = True startedAt_checked = self.check_staleness(startedAt) - print(answer_checked, startedAt_checked) return answer_checked and startedAt_checked @@ -113,6 +111,7 @@ def naive_data_validation(self): problems.append( f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {self.contract}.{self.interface}.{self.oracle_api} ({self.node.source_mapping}).\n" ) + elif ( index == ChainlinkVars.STARTEDAT.value and vars_order[ChainlinkVars.STARTEDAT.value] is not None @@ -121,4 +120,6 @@ def naive_data_validation(self): if self.is_sequencer_check(vars_order[ChainlinkVars.ANSWER.value], var): problems = [] break + if self.out_of_function_checks: + problems.append("One or all of the variables are not checked within the function where the call to the oracle was performed.\n") return problems diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 95c0534c83..9e2642f0a8 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -20,13 +20,12 @@ def __init__(self, _calls): self.contract = None self.function = None self.node = None + self.out_of_function_checks = False self.oracle_vars = [] - # self.vars_in_condition = [] self.vars_not_in_condition = [] self.returned_vars_indexes = None self.interface = None self.oracle_api = None - self.oracle_type = None def get_calls(self): return self.calls @@ -59,6 +58,7 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or # Data validation functions def naive_data_validation(self): return self + @staticmethod def check_greater_zero(ir: Operation) -> bool: @@ -93,12 +93,14 @@ def check_staleness(self, var: VarInCondition) -> bool: def check_price(self, var: VarInCondition) -> bool: if var is None: return False + different_behavior = False for node in var.nodes_with_var: for ir in node.irs: if isinstance(ir, Binary): if self.check_greater_zero(ir): return True # If the conditions does not match we are looking for revert or return node - return check_revert(node) or return_boolean(node) + if not different_behavior: + different_behavior = check_revert(node) or return_boolean(node) - return False + return different_behavior diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt index 75b51e2ce7..227a78104e 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt @@ -1,2 +1,3 @@ -The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call ChainlinkETHUSDPriceConsumer.priceFeed.latestRoundData (tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol#49). +The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call ChainlinkETHUSDPriceConsumer.priceFeed.latestRoundData (tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol#58). +One or all of the variables are not checked within the function where the call to the oracle was performed. From b52d755d742832b32ddf6c181e041603e3641c5d Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 11:34:49 +0100 Subject: [PATCH 67/87] removing unused code --- slither/detectors/oracles/oracle_detector.py | 22 +++++++------------ .../supported_oracles/chainlink_oracle.py | 6 +++-- .../oracles/supported_oracles/oracle.py | 4 +--- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 45bb53ff51..87a9c3552a 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -9,7 +9,7 @@ from slither.detectors.oracles.supported_oracles.oracle import Oracle, VarInCondition from slither.detectors.oracles.supported_oracles.chainlink_oracle import ChainlinkOracle from slither.detectors.oracles.supported_oracles.help_functions import is_internal_call -from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent +from slither.analyses.data_dependency.data_dependency import is_dependent from slither.core.expressions.tuple_expression import TupleExpression @@ -32,7 +32,6 @@ def generate_oracle(ir: Operation) -> Oracle: if ChainlinkOracle().is_instance_of(ir): return ChainlinkOracle() return None - @staticmethod def get_returned_variables_from_oracle(node) -> list: @@ -179,24 +178,22 @@ def vars_in_conditions(self, oracle: Oracle): oracle.oracle_vars = oracle_vars return True - def checks_performed_out_of_original_function(self, oracle, returned_var): nodes_of_call = [] functions_of_call = [] original_function = oracle.function original_node = oracle.node - original_vars = oracle.oracle_vars for contract in self.contracts: for function in contract.functions: if function == oracle.function: continue - nodes, functions = self.find_if_original_function_called(oracle,function) + nodes, functions = self.find_if_original_function_called(oracle, function) if nodes and functions: nodes_of_call.extend(nodes) functions_of_call.extend(functions) if not nodes_of_call or not functions_of_call: return [] - + i = 0 nodes = [] for node in nodes_of_call: @@ -209,32 +206,29 @@ def checks_performed_out_of_original_function(self, oracle, returned_var): break self.vars_in_conditions(oracle) nodes.extend(oracle.oracle_vars[0].nodes_with_var) - i+=1 - + i += 1 # Return back original node and function after recursion to let developer know on which line the oracle is used oracle.set_function(original_function) oracle.set_node(original_node) return nodes - - + @staticmethod def find_if_original_function_called(oracle, function): nodes_of_call = [] functions_of_call = [] for node in function.nodes: for ir in node.irs: - if isinstance(ir, InternalCall) or isinstance(ir, HighLevelCall): - if (ir.function == oracle.function): + if isinstance(ir, (InternalCall, HighLevelCall)): + if ir.function == oracle.function: nodes_of_call.append(node) functions_of_call.append(function) return nodes_of_call, functions_of_call - def investigate_on_return(self, oracle, var) -> bool: for value in oracle.function.return_values: if is_dependent(value, var, oracle.node): - return self.checks_performed_out_of_original_function(oracle, value) + return self.checks_performed_out_of_original_function(oracle, value) return False # This function interates through all internal calls in function and checks if the var is used in condition any of them diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 1a7a54a8e1..07444c6cff 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -111,7 +111,7 @@ def naive_data_validation(self): problems.append( f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {self.contract}.{self.interface}.{self.oracle_api} ({self.node.source_mapping}).\n" ) - + elif ( index == ChainlinkVars.STARTEDAT.value and vars_order[ChainlinkVars.STARTEDAT.value] is not None @@ -121,5 +121,7 @@ def naive_data_validation(self): problems = [] break if self.out_of_function_checks: - problems.append("One or all of the variables are not checked within the function where the call to the oracle was performed.\n") + problems.append( + "One or all of the variables are not checked within the function where the call to the oracle was performed.\n" + ) return problems diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 9e2642f0a8..cf8a73bde2 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -26,7 +26,7 @@ def __init__(self, _calls): self.returned_vars_indexes = None self.interface = None self.oracle_api = None - + def get_calls(self): return self.calls @@ -53,12 +53,10 @@ def set_data(self, _contract, _function, _returned_vars_indexes, _interface, _or self.returned_vars_indexes = _returned_vars_indexes self.interface = _interface self.oracle_api = _oracle_api - # Data validation functions def naive_data_validation(self): return self - @staticmethod def check_greater_zero(ir: Operation) -> bool: From 28d456fd7efde50cfe80f44dedfdea95518fba0f Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 11:37:17 +0100 Subject: [PATCH 68/87] fix: changes to checks based on new feature --- slither/detectors/oracles/supported_oracles/oracle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index cf8a73bde2..94583c219e 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -80,12 +80,15 @@ def timestamp_in_node(node) -> bool: def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False + different_behavior = False for node in var.nodes_with_var: # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly if self.timestamp_in_node(node): return True + if not different_behavior: + different_behavior = check_revert(node) or return_boolean(node) - return False + return different_behavior # This functions validates checks of price value def check_price(self, var: VarInCondition) -> bool: From c65e887584855aa48e6e35db53bd112994053cdd Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 12:01:36 +0100 Subject: [PATCH 69/87] fix: undefined varible --- slither/detectors/oracles/supported_oracles/oracle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 94583c219e..880abe5fc2 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -85,8 +85,8 @@ def check_staleness(self, var: VarInCondition) -> bool: # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly if self.timestamp_in_node(node): return True - if not different_behavior: - different_behavior = check_revert(node) or return_boolean(node) + if not different_behavior: + different_behavior = check_revert(node) or return_boolean(node) return different_behavior From 165dccd3f02548b2a7e71648148599c9f959e1e6 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 14:38:08 +0100 Subject: [PATCH 70/87] fix: array issue --- slither/detectors/oracles/oracle_detector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 87a9c3552a..9e23ff87dd 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -205,7 +205,8 @@ def checks_performed_out_of_original_function(self, oracle, returned_var): oracle.oracle_vars = [var] break self.vars_in_conditions(oracle) - nodes.extend(oracle.oracle_vars[0].nodes_with_var) + if type(oracle.oracle_vars[0]) == VarInCondition: + nodes.extend(oracle.oracle_vars[0].nodes_with_var) i += 1 # Return back original node and function after recursion to let developer know on which line the oracle is used From fb5e6504288b415dc48368433ab9a248051d24f3 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 16:04:20 +0100 Subject: [PATCH 71/87] feat: check for timestamp in var --- slither/detectors/oracles/oracle_detector.py | 2 +- .../supported_oracles/help_functions.py | 10 ++- .../oracles/supported_oracles/oracle.py | 9 ++- ..._0_8_20_oracle_timestamp_in_var_sol__0.txt | 2 + .../0.8.20/oracle_timestamp_in_var.sol | 60 ++++++++++++++++++ .../oracle_timestamp_in_var.sol-0.8.20.zip | Bin 0 -> 5964 bytes tests/e2e/detectors/test_detectors.py | 1 + 7 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_timestamp_in_var_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol create mode 100644 tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol-0.8.20.zip diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 9e23ff87dd..54406b0a6e 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -174,7 +174,7 @@ def vars_in_conditions(self, oracle: Oracle): oracle_vars.append(var) # oracle.vars_in_condition = vars_in_condition - oracle.vars_not_in_condition.extend(vars_not_in_condition) + oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars return True diff --git a/slither/detectors/oracles/supported_oracles/help_functions.py b/slither/detectors/oracles/supported_oracles/help_functions.py index ece271836a..029caf8d0e 100644 --- a/slither/detectors/oracles/supported_oracles/help_functions.py +++ b/slither/detectors/oracles/supported_oracles/help_functions.py @@ -2,6 +2,7 @@ from slither.slithir.operations.return_operation import Return from slither.slithir.operations import InternalCall from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.variables.constant import Constant # Helpfull functions @@ -16,13 +17,20 @@ def check_revert(node: Node) -> bool: return False +def is_boolean(ir) -> bool: + for val in ir.values: + if isinstance(val, Constant): + if isinstance(val.value, bool): + return True + return False + # Check if the node's sons contain a return statement def return_boolean(node: Node) -> bool: for n in node.sons: if n.type == NodeType.RETURN: for ir in n.irs: if isinstance(ir, Return): - return True + return is_boolean(ir) return False diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 880abe5fc2..d77e558b31 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -6,6 +6,7 @@ ) from slither.detectors.oracles.supported_oracles.help_functions import check_revert, return_boolean from slither.slithir.variables.constant import Constant +from slither.core.cfg.node import recheable # This class was created to store variable and all conditional nodes where it is used class VarInCondition: # pylint: disable=too-few-public-methods @@ -72,8 +73,12 @@ def check_greater_zero(ir: Operation) -> bool: @staticmethod def timestamp_in_node(node) -> bool: - if "block.timestamp" in str(node): - return True + all_nodes = [node] + if (node.fathers): + all_nodes.extend(node.fathers) + for var in all_nodes: + if "block.timestamp" in str(var): + return True return False # This function checks if the timestamp value is validated. diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_timestamp_in_var_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_timestamp_in_var_sol__0.txt new file mode 100644 index 0000000000..dd42ee0851 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_timestamp_in_var_sol__0.txt @@ -0,0 +1,2 @@ +The oracle ChainlinkETHUSDPriceConsumer.priceFeed (tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol#51) returns the variables ['price'] which are not validated. It can potentially lead to unexpected behaviour. + diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol new file mode 100644 index 0000000000..13f5e76916 --- /dev/null +++ b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +interface AggregatorV3Interface { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + // getRoundData and latestRoundData should both raise "No data present" + // if they do not have data to report, instead of returning unset values + // which could be misinterpreted as actual reported values. + function getRoundData( + uint80 _roundId + ) + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); + + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract ChainlinkETHUSDPriceConsumer { + AggregatorV3Interface internal priceFeed; + + constructor() public { + priceFeed = AggregatorV3Interface( + 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 + ); + } + /** + * Returns the latest price + */ + function getLatestPrice() public view returns (int) { + (, int price, ,uint256 updateAt , ) = priceFeed.latestRoundData(); + uint current_timestamp = block.timestamp; + require(current_timestamp - updateAt < 1 minutes, "Price is outdated"); + return price; + } + + function getDecimals() public view returns (uint8) { + return priceFeed.decimals(); + } +} diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol-0.8.20.zip b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol-0.8.20.zip new file mode 100644 index 0000000000000000000000000000000000000000..854f0e5d4e5f75f68f11e5b6bb48c182492506b1 GIT binary patch literal 5964 zcmb8zMME2af&}2;Ry4S~yE_zud!V=^xVuAfcXuc*1&X^BD^euIiaUkkt_AkJz3*XX z4s)76@M)?bAW8w?0ayUiV150XB|+H)LI7Yl69C`>001uTPzy&ZGfz7wD-TboldGAX zvza&4ozug`(ag%%)y3V@4C-!SYv*mnY473UjE0B=FaZEU004=Y7#qHSd~PLkGwouS z1*6V(3so);RljlK|O+9 zZ@;f@P)&R~GSQ6hg!jejQvC9ua>)9DM<|2n5N(^(uP44_=NkmaC0YB%C zGOE4}PdDzck?VJHxCH!~d2}1BM;)qdM45qSeINyTQ`*3{XZooYBQfR(GJa(F@?k#R zuAVgRCx$h}jz#!kMY`|;AsG+&0G3xF zte-!XYTi<$*5JR?33%8X`d-v}<{=R!khC{R6gHLi{FlmK5w3M~^^=F5| z^iWVf$G`iG&Q}Bo=JDKjYfiy6Xcn#W_P!AiPD~jQ(B$D0ZJO-Lm-8J-3lJU~k#Kq- z4(DP{*g5pdT!kKg)!CIa^6U)K?VgRgho=4$88im?Ed>LD;RS_?vJ4=>pp#X=O@FM< zBeZ0z)OR`kO~5=<_2Q_JNJimH%bxsvF&?BaFuSWh1E=-zXUgG8_66?yG>fGC$KcRx zSM>O!eYLxnOKF)&4MB-hVUNE>uC3fqwHN(-58n9Es(EM5hi}L?lZ?Wp_N0V8z(fUcTq}FcAwj_}^n+L^{_52V&Rd zROm~eaV7}1SVh!}w3EwDCX6WiH-m?sd*Mtf?seX$=^-X|$>7>%dr-@?X#*q#^Yt;G zw7wnDp1pU{e|^k!lo9;EAGX(6A-@bT5DUogC zPg`JIOQYpsf5@dc))Xs^f%I2(QxK35UKd)>hI@7OE_pe$X6U_-3NMmA#iJrXJ2VFPLuboJF7M%)-H@%8t4L5-D zD~=|HI*xGneOlqGwvtJkEkRwur1n~FgkUpl0i15JI3I5l?XwIB?=OdNKFlG(Kg@b5 z(_=5x>M?UY*^4DnKpe(D3;ig^3(YNS4AkB&V-2?nOY9Io==pO@NP&t4XYyp}zn%Uf z;WxtwDrSZAZupS_NpH`$g*Qg7%X1n$-T2vhS8P1Z=wF=7Pqs^|y!u8LA!Pk4+6&<- z$e3gFThS8q#M=HzT|4WC`C)~W4IGS2QJ;l6tQt4Cchf9bP`XDb7o9G4qEvlqb^itL zM;;fosKMak2HI}ZS}1%WQ=b&QrPmO3okVNd1K#&cR*#-5G}2bB{V-7@u1&WJ?)l5> z^=!VYC_&q*ibnAw3xB zruLE(+@;dq1%A^c-~>gt+beXUNq57pFQ}>~T{JqA!Y(f|zk2kZO3o#cD)jkR>O2Sg zqpX1UrP%@E{)^^SLrub8W$!Bpp#nP}7Yl!rn78#CejXC-Lj5Wn@?5#ln}3 z<`48I8glpInL3M4n0)c%-}Po=80&BL`b`mA~fP{z66bK%`mGqS39MN?uwWm=vfM?czxVpW^qCwjaX z8gYvZgx9KDn#hKZ9}ihXczwnF6RGq&rW)VIs=NSr5Tn zbfCs-8khq&E$XcpTbNq>v+H2Sj%4GLj?^~>D)T&Jl2?12(u3);M`#$kG}Y=cDV+>6 z6Rm?rZT2fanqSK%A^Vn-kYU_G)tG1nX3c_jR7;akWRl)APrbE$B+18f0E9rhwAROV6Xc)w!cOI~3RZsC~L}A?^+=C2- z#gTx7Z5`o4PEi{v-TUqf1>-Gk1RY^pW2%F4(Ju%__ zWnj`qwD};_3b1UzEV;2t`3&2&catU1@7-}q%UFiu6);${+DrHlYIno{Db#d%7Sqr4 zK>R>W8Fp^bg$!Tz;+YVXC%o$G9f+~#JOhZ)rShBfFiSbPi1OG$qr1Y?81xz7ch){{ ztv!Z@J;8_-)wOtJI@n1`A|P868o`vGM)(-Uq*B8J#gwLrpKRbgSa}q7r2oEoGC8dO zu9J+cRr-@;oF)c^KP#|!+ir1T7_Yn~<@LmkII~e)ZnbG+s-ZWo*wH_l!LdHm=Qg$GULj#>E67tdZ*{H7s$#I|IH`>I!`5zwCwkib_Z*O?WKh4sq^iLP{VG3=VN|-0XuXAp za(a+_5E5!yJlKjOv5OIy8*1fGWCnbpjkIn!2%&ujdhRVM1;zy9a`O{yGpmKD(;fwLx>0u{k`eC^2VYuq*#i_ZsZ)F2AhcWVFKcv%-tuXdl$P(Y_LtUIj&a97l#kkx^tMmk@sg6kPv?6ECv zVar4aQw=Rv1X6+bDR)s&z_dN+r}2Tcf^rj~)X?vBto}9+_?BwRKeo$8ANyNXk}Sc% zd&c7=G|sRO#o4le2SZqgW+3Am@bb*6PByy)!yWwG@lj!A|K^w~Y<-}uqMzs!-RzL1 zF%B}&?*oDcerVbLmX@O*BI=#_<4|v4msr@oJk&^P|;o_OL%bXm2v&`qA|raSch<{F9UAh zW1yzib8A46>XHM0^T$G^MN0{d48$B2=BX}c-DBWRPP%a_d@HwUnJc6~bQ}AoSlNcC z_kiHqvg})eq-CVB=1=ws!lqM^bC~iek!t@Mza$k!b2BSc7Wr?QJVIW0TgoWwOLCQaMsZ_%HF=US2~yl2+cDXH zm~O5{#;m(aEnzKc`K#+x5LnOQuGZ<^I9*n#xMs@C2dp&+Z(sP5?A_$tBZ?TxSi6@s zu5*vW$`sPqS+<$UI2WWbrm!zz??_f3AzhJ|AILTwbi@NYcP5@}TcQ@lTDe`_?&u}n z&2uI0=X75EB(i(jc(12_XhQFVB?XYPk8XF!9CBpU68O$&DF}uQ&~~Y4NhVlX!sbYn zKn3U>F%u04TO5{S4acA+*pJDpr<6}+rR7;XU8NM=>jV1Ol+tpDyK4D86YSYP2DE7o zWT{@}@UQBWL)GHYkV;H@o34t5Ec=~n>`uKYY4+*y&jylX0(iJOnq)r;;Hrke^H%-V zsXLhHP`@!=_#6$up^Xr?mQFMt%e=EVOfY9t4QUSry{3x-Tj4#7+ydEQ2`;G%zNT|- zOrc6<1K2|N4V2ld=&5qKezSJtiTaT%#)SXAyL7I`n`0Y@!#ZVwAz$%~JY6YJk2iuG zmuu`N=knA7u73_$1&3V>VcEidp-iEv_IFIdNF#3&=6MP2%B%h5QHM;ZQzB5?H-4&? z&w9!IY1LA&5~cVF_tep-b{r|W3BIc~xK!K%9N z?(O}_zF&CSfmlh1eij*y(>EySHcoH(>-8|AmNh#LN%lAVo#~}dj^?&nzZvU{uMwpr zIz(KWRM5_k_}lHH_q-@2Q8gnm*VjH(;Lpi`Bi;tLR~TsTVsu!Uj*?Ab{caujfnlct zPx?Xi4-ZGdDoC-(oNa>m2IALCDn)XMA_g5F$-&7*Tbg)_Ms0uKs-PCvTC6B&w_|A^ z(2#3(hI47iT{}z8^W`7x-mQ#aY%K+ckg$VKS)(xfnI7fy5$~d`@pWfXJ&wH6X6;`Z zM#`NQj?U<%c;`GvNNXbo#@v;5r0RA%jpxO_Qr=w47;Vq4S%f`uIoUp@<;nH#f|B;m zIDX9{RBqOZ#l86Dv1&-+R(M)F@2n@k*unlEO6l}g3%!^dZP#djj~C-tX4uUKUx^*; zBMn%I1w`x>WFs_2B+sOUkQ{P34{iLYw)IzCdFE%sAzBapmai5S+-6ShtdPCN@=A`O zeva?;Qe@Y~6|Q40GVg){6m1h{iT}0rW=tJqdlTx{d*08W4ssR`R8_jBJ-n0B_o^-# z)A9Ti3Ld_GE z6q;k@keSEj+l3zlp`vId9JTi`*)QNv+_?6M> zO~czUl1>>!FN2?@HJ+BgUl$Dh<1^|ouUe|lD_)$l-cx}C1gzGmnjwcABSvTPqoK+e zDh>CFSn7T;16I^G$sW=!WktER=o z;r+6PamYQ3yHxyWqA{N;N>^g=QYid#C&1&=#Fxm7K1?a1%^WENl7m-e7ZzH~7~dHs zfQVVBjzn~Q>hhcpl*%Y=u$SneW_J?KYw;l#cXIHD+d#}KmYV|QNBosxLVkxWgbg9MaqrzVDad4(EuK#|f`i~`F z@o2vBHb{VA?e(f)l1t0tvW9VkQMvxud#B&_<>U@tRD|_adC?*IDg6AAn zhuS-i8hUjxp6DmhG!*BVaVknR_;bGJ#|0?fVapH#DKZ2pm_lmBQ5WJ4>JW!1J1$4c z4*6F@Yukh0+~X)L7x=Lvs}DQLfJL|P2#zIWRc+|mFE+4@ar2X1YbMI8tpgC$zzP>{ z#zw)=?2dgz+l^rSu&O(4C1-Eb5G!|QHkt#urDo11F(IiPK{5F`$S4w9d;>d7^%*@i ztbUB4urbZ@&|1Ruon=PF{`U#tXsQj#6ao$mo$FA!gQm;|KpKR3!oc@=r8yi47m%`CcS78G-&sVMk7 zqO~g6QN&i*dT;yT*8kc|Yu=3FfGFvrD1_Qjt+C23dw<}d2GdfysKEIK8xd*R85BU`dnI&o!PpBjD~)~M3=laB5ic{ z6!+6}Q~wjmDBrqT9miMrVFm~TD;TOi*ySu`TJ~A7r$`Cmjs<$IX<=bt7uUK)TlTq} zm#R=^K5cdFAwmWUljz02JSSrAxc6%>{1IyhV|ZyD#Q!YzPE02pBE60`2Io)Tjs=Ue zOw>IRYRI^P@Gl#s5td*Pr|5WKpeprp*x{ScBkt+>ue#7v#N_vgUFJ;TL%m;MP}_7L zQ=v>zNeJ4oKV`wG5{SFO@M;OCI^_F)Zj_Ye8TJ`H%v<6&dKqlLkymI9p zcXHpTIvRN*O;2*cC9tMKEVyzEKH z@x|~k(feb`5~D6CO1naM+}J9TSL!1aH?Xf_5=*GYC8(|V@XJvy%bdNw_F{As5*QL0 zpNCfBrFgg61rQ?4_iSJK93+#M={xZ5zKILaRDpw+LioRF`#-w Date: Sat, 24 Feb 2024 16:23:40 +0100 Subject: [PATCH 72/87] feat: more information if the variabe is checked out of original function --- slither/detectors/oracles/oracle_detector.py | 2 +- .../detectors/oracles/supported_oracles/chainlink_oracle.py | 4 ++-- slither/detectors/oracles/supported_oracles/oracle.py | 2 +- ...leDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 54406b0a6e..ba0df7a4d5 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -168,7 +168,7 @@ def vars_in_conditions(self, oracle: Oracle): oracle_vars.append(VarInCondition(var, self.nodes_with_var)) elif nodes := self.investigate_on_return(oracle, var): oracle_vars.append(VarInCondition(var, nodes)) - oracle.out_of_function_checks = True + oracle.out_of_function_checks.append((var, nodes)) else: vars_not_in_condition.append(var) oracle_vars.append(var) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 07444c6cff..012a91e78f 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -120,8 +120,8 @@ def naive_data_validation(self): if self.is_sequencer_check(vars_order[ChainlinkVars.ANSWER.value], var): problems = [] break - if self.out_of_function_checks: + for tup in self.out_of_function_checks: problems.append( - "One or all of the variables are not checked within the function where the call to the oracle was performed.\n" + f"The variation of {tup[0]} is checked on the lines {[str(node.source_mapping) for node in tup[1]]}. Not in the original function where the Oracle call is performed.\n" ) return problems diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index d77e558b31..519859048f 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -21,7 +21,7 @@ def __init__(self, _calls): self.contract = None self.function = None self.node = None - self.out_of_function_checks = False + self.out_of_function_checks = [] self.oracle_vars = [] self.vars_not_in_condition = [] self.returned_vars_indexes = None diff --git a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt index 227a78104e..fe94decd89 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_OracleDataCheck_0_8_20_oracle_check_out_of_function_sol__0.txt @@ -1,3 +1,3 @@ The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call ChainlinkETHUSDPriceConsumer.priceFeed.latestRoundData (tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol#58). -One or all of the variables are not checked within the function where the call to the oracle was performed. +The variation of price is checked on the lines ['tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_check_out_of_function.sol#50']. Not in the original function where the Oracle call is performed. From 0efcfce17d98f3223c6b84a90a41d1e7a7c8041a Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 16:34:57 +0100 Subject: [PATCH 73/87] fix: handle scenario when too many variations --- slither/detectors/oracles/supported_oracles/chainlink_oracle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 012a91e78f..1b90d167c0 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -122,6 +122,6 @@ def naive_data_validation(self): break for tup in self.out_of_function_checks: problems.append( - f"The variation of {tup[0]} is checked on the lines {[str(node.source_mapping) for node in tup[1]]}. Not in the original function where the Oracle call is performed.\n" + f"The variation of {tup[0]} is checked on the lines {[str(node.source_mapping) for node in tup[1][::5]]}. Not in the original function where the Oracle call is performed.\n" ) return problems From 060616982a20a09b20e1bb9c99614d0afa5be9da Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 24 Feb 2024 16:43:09 +0100 Subject: [PATCH 74/87] fix: use recommended methods --- slither/detectors/oracles/oracle_detector.py | 2 +- slither/detectors/oracles/supported_oracles/help_functions.py | 1 + slither/detectors/oracles/supported_oracles/oracle.py | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index ba0df7a4d5..d51306e062 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -205,7 +205,7 @@ def checks_performed_out_of_original_function(self, oracle, returned_var): oracle.oracle_vars = [var] break self.vars_in_conditions(oracle) - if type(oracle.oracle_vars[0]) == VarInCondition: + if isinstance(oracle.oracle_vars[0], VarInCondition): nodes.extend(oracle.oracle_vars[0].nodes_with_var) i += 1 diff --git a/slither/detectors/oracles/supported_oracles/help_functions.py b/slither/detectors/oracles/supported_oracles/help_functions.py index 029caf8d0e..a4c265b247 100644 --- a/slither/detectors/oracles/supported_oracles/help_functions.py +++ b/slither/detectors/oracles/supported_oracles/help_functions.py @@ -24,6 +24,7 @@ def is_boolean(ir) -> bool: return True return False + # Check if the node's sons contain a return statement def return_boolean(node: Node) -> bool: for n in node.sons: diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 519859048f..01fe126405 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -6,7 +6,6 @@ ) from slither.detectors.oracles.supported_oracles.help_functions import check_revert, return_boolean from slither.slithir.variables.constant import Constant -from slither.core.cfg.node import recheable # This class was created to store variable and all conditional nodes where it is used class VarInCondition: # pylint: disable=too-few-public-methods @@ -74,7 +73,7 @@ def check_greater_zero(ir: Operation) -> bool: @staticmethod def timestamp_in_node(node) -> bool: all_nodes = [node] - if (node.fathers): + if node.fathers: all_nodes.extend(node.fathers) for var in all_nodes: if "block.timestamp" in str(var): From 7a3ced0cff3d7632efbb011d474ce42874997e81 Mon Sep 17 00:00:00 2001 From: Talfao Date: Mon, 11 Mar 2024 15:52:30 +0100 Subject: [PATCH 75/87] fix: recursion error --- slither/detectors/oracles/oracle_detector.py | 63 +++++++++++-------- .../oracles/supported_oracles/oracle.py | 4 +- ...oracle_data_check_price_in_internal_fc.sol | 3 - 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index d51306e062..28b3418467 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -169,6 +169,9 @@ def vars_in_conditions(self, oracle: Oracle): elif nodes := self.investigate_on_return(oracle, var): oracle_vars.append(VarInCondition(var, nodes)) oracle.out_of_function_checks.append((var, nodes)) + # except RecursionError: + # vars_not_in_condition.append(var) + # oracle_vars.append(var) else: vars_not_in_condition.append(var) oracle_vars.append(var) @@ -227,9 +230,12 @@ def find_if_original_function_called(oracle, function): return nodes_of_call, functions_of_call def investigate_on_return(self, oracle, var) -> bool: - for value in oracle.function.return_values: - if is_dependent(value, var, oracle.node): - return self.checks_performed_out_of_original_function(oracle, value) + try: + for value in oracle.function.return_values: + if is_dependent(value, var, oracle.node): + return self.checks_performed_out_of_original_function(oracle, value) + except RecursionError: + return False return False # This function interates through all internal calls in function and checks if the var is used in condition any of them @@ -237,32 +243,35 @@ def investigate_internal_call(self, function: FunctionContract, var) -> bool: if function is None: return False - original_var_as_param = self.map_param_to_var(var, function) - if original_var_as_param is None: - original_var_as_param = var + try: + original_var_as_param = self.map_param_to_var(var, function) + if original_var_as_param is None: + original_var_as_param = var - if function.is_reading_in_conditional_node( - original_var_as_param - ) or function.is_reading_in_require_or_assert(original_var_as_param): - conditions = [] - for node in function.nodes: - if ( - node.is_conditional() - and self.check_var_condition_match(original_var_as_param, node) - and not is_internal_call(node) - ): - conditions.append(node) - if len(conditions) > 0: - for cond in conditions: - self.nodes_with_var.append(cond) - return True + if function.is_reading_in_conditional_node( + original_var_as_param + ) or function.is_reading_in_require_or_assert(original_var_as_param): + conditions = [] + for node in function.nodes: + if ( + node.is_conditional() + and self.check_var_condition_match(original_var_as_param, node) + and not is_internal_call(node) + ): + conditions.append(node) + if len(conditions) > 0: + for cond in conditions: + self.nodes_with_var.append(cond) + return True - for node in function.nodes: - for ir in node.irs: - if isinstance(ir, InternalCall): - if self.investigate_internal_call(ir.function, original_var_as_param): - return True - return False + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall): + if self.investigate_internal_call(ir.function, original_var_as_param): + return True + return False + except RecursionError: + return False def _detect(self): self.oracles = self.find_oracles(self.contracts) diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 01fe126405..6c13204711 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -32,7 +32,9 @@ def get_calls(self): def is_instance_of(self, ir: Operation) -> bool: return isinstance(ir, HighLevelCall) and ( - isinstance(ir.function, Function) and self.compare_call(ir.function.name) + isinstance(ir.function, Function) + and self.compare_call(ir.function.name) + # add interface ) def set_node(self, _node): diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol index d7899146cd..f4697213d8 100644 --- a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol +++ b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_internal_fc.sol @@ -49,9 +49,6 @@ contract StableOracleDAI { function getPriceUSD() external view returns (uint256) { uint256 wethPriceUSD = 1; uint256 DAIWethPrice = 1; - - // chainlink price data is 8 decimals for WETH/USD, so multiply by 10 decimals to get 18 decimal fractional - //(uint80 roundID, int256 price, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound) = priceFeedDAIETH.latestRoundData(); ( uint80 roundID, int256 price, From 6454d6ae484615578da0e86fade1555a55c2fd1f Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 17 Mar 2024 09:20:32 +0100 Subject: [PATCH 76/87] fix: sequencer check --- slither/detectors/oracles/supported_oracles/chainlink_oracle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 1b90d167c0..dad9f626ae 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -83,7 +83,7 @@ def is_sequencer_check(self, answer, startedAt): answer_checked = False startedAt_checked = False - for node in answer.nodes: + for node in answer.nodes_with_var: for ir in node.irs: if isinstance(ir, Binary): if self.price_check_for_liveness(ir): From dff5e50e896b8da2ef6c2432a6d563af1818628b Mon Sep 17 00:00:00 2001 From: Talfao Date: Mon, 18 Mar 2024 08:35:04 +0100 Subject: [PATCH 77/87] fix: better solution for recursion error --- slither/detectors/oracles/oracle_detector.py | 71 ++++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 28b3418467..9e1d584c27 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -157,13 +157,13 @@ def vars_in_conditions(self, oracle: Oracle): for node in self.nodes_with_var: for ir in node.irs: if isinstance(ir, InternalCall): - self.investigate_internal_call(ir.function, var) + self.investigate_internal_call(ir.function, var, None) if len(self.nodes_with_var) > 0: # vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: - if self.investigate_internal_call(oracle.function, var): + if self.investigate_internal_call(oracle.function, var, None): # vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) elif nodes := self.investigate_on_return(oracle, var): @@ -227,52 +227,51 @@ def find_if_original_function_called(oracle, function): if ir.function == oracle.function: nodes_of_call.append(node) functions_of_call.append(function) + if ir.function == function: + return None, None return nodes_of_call, functions_of_call def investigate_on_return(self, oracle, var) -> bool: - try: - for value in oracle.function.return_values: - if is_dependent(value, var, oracle.node): - return self.checks_performed_out_of_original_function(oracle, value) - except RecursionError: - return False + for value in oracle.function.return_values: + if is_dependent(value, var, oracle.node): + return self.checks_performed_out_of_original_function(oracle, value) + return False # This function interates through all internal calls in function and checks if the var is used in condition any of them - def investigate_internal_call(self, function: FunctionContract, var) -> bool: + def investigate_internal_call(self, function: FunctionContract, var, original_function) -> bool: if function is None: return False + if function == original_function: + return False - try: - original_var_as_param = self.map_param_to_var(var, function) - if original_var_as_param is None: - original_var_as_param = var - - if function.is_reading_in_conditional_node( - original_var_as_param - ) or function.is_reading_in_require_or_assert(original_var_as_param): - conditions = [] - for node in function.nodes: - if ( - node.is_conditional() - and self.check_var_condition_match(original_var_as_param, node) - and not is_internal_call(node) - ): - conditions.append(node) - if len(conditions) > 0: - for cond in conditions: - self.nodes_with_var.append(cond) - return True + original_var_as_param = self.map_param_to_var(var, function) + if original_var_as_param is None: + original_var_as_param = var + if function.is_reading_in_conditional_node( + original_var_as_param + ) or function.is_reading_in_require_or_assert(original_var_as_param): + conditions = [] for node in function.nodes: - for ir in node.irs: - if isinstance(ir, InternalCall): - if self.investigate_internal_call(ir.function, original_var_as_param): - return True - return False - except RecursionError: - return False + if ( + node.is_conditional() + and self.check_var_condition_match(original_var_as_param, node) + and not is_internal_call(node) + ): + conditions.append(node) + if len(conditions) > 0: + for cond in conditions: + self.nodes_with_var.append(cond) + return True + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, InternalCall): + if self.investigate_internal_call(ir.function, original_var_as_param, function): + return True + return False + def _detect(self): self.oracles = self.find_oracles(self.contracts) for oracle in self.oracles: From d6ac5cbc4d47dc9f5f5fe0a0e5a5085267b650d8 Mon Sep 17 00:00:00 2001 From: Talfao Date: Mon, 18 Mar 2024 08:36:35 +0100 Subject: [PATCH 78/87] feat: all done for merge --- slither/detectors/oracles/oracle_detector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 9e1d584c27..951f2c045e 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -235,7 +235,7 @@ def investigate_on_return(self, oracle, var) -> bool: for value in oracle.function.return_values: if is_dependent(value, var, oracle.node): return self.checks_performed_out_of_original_function(oracle, value) - + return False # This function interates through all internal calls in function and checks if the var is used in condition any of them @@ -271,7 +271,7 @@ def investigate_internal_call(self, function: FunctionContract, var, original_fu if self.investigate_internal_call(ir.function, original_var_as_param, function): return True return False - + def _detect(self): self.oracles = self.find_oracles(self.contracts) for oracle in self.oracles: From 19472cf4b52931cef94b64d7669559fd1ac52098 Mon Sep 17 00:00:00 2001 From: Talfao Date: Mon, 18 Mar 2024 08:59:48 +0100 Subject: [PATCH 79/87] fix: black issue --- tests/e2e/detectors/test_detectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index acf6ef2a02..fa5b6a1c1c 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1687,7 +1687,7 @@ def id_test(test_item: Test): Test(all_detectors.OracleDataCheck, "oracle_data_check_price_in_internal_fc.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_non_revert.sol", "0.8.20"), Test(all_detectors.OracleDataCheck, "oracle_check_out_of_function.sol", "0.8.20"), - Test(all_detectors.OracleDataCheck,"oracle_timestamp_in_var.sol", "0.8.20"), + Test(all_detectors.OracleDataCheck, "oracle_timestamp_in_var.sol", "0.8.20"), Test(all_detectors.DeprecatedChainlinkCall, "oracle_deprecated_call.sol", "0.8.20"), ] From 81365edb95c05cd60a80bff53db66d90d91c792f Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 6 Apr 2024 13:29:03 +0200 Subject: [PATCH 80/87] feat: interfaces detection --- .../oracles/supported_oracles/chainlink_oracle.py | 3 ++- slither/detectors/oracles/supported_oracles/oracle.py | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index dad9f626ae..aa564d77b6 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -13,6 +13,7 @@ "latestRoundData", "getRoundData", ] +INTERFACES = ["AggregatorV3Interface", "FeedRegistryInterface"] class ChainlinkVars(Enum): @@ -25,7 +26,7 @@ class ChainlinkVars(Enum): class ChainlinkOracle(Oracle): def __init__(self): - super().__init__(CHAINLINK_ORACLE_CALLS) + super().__init__(CHAINLINK_ORACLE_CALLS, INTERFACES) # This function checks if the RoundId value is validated in connection with answeredInRound value # But this last variable was deprecated. We left this function for possible future use. diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 6c13204711..7b37daf8a9 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -15,8 +15,9 @@ def __init__(self, _var, _nodes): class Oracle: # pylint: disable=too-few-public-methods, too-many-instance-attributes - def __init__(self, _calls): + def __init__(self, _calls, _interfaces): self.calls = _calls + self.interfaces = _interfaces self.contract = None self.function = None self.node = None @@ -33,8 +34,7 @@ def get_calls(self): def is_instance_of(self, ir: Operation) -> bool: return isinstance(ir, HighLevelCall) and ( isinstance(ir.function, Function) - and self.compare_call(ir.function.name) - # add interface + and self.compare_call(ir.function.name) and self.compare_interface(str(ir.destination.type)) ) def set_node(self, _node): @@ -43,6 +43,11 @@ def set_node(self, _node): def set_function(self, _function): self.function = _function + def compare_interface(self, interface) -> bool: + if interface in self.interfaces: + return True + return False + def compare_call(self, function) -> bool: for call in self.calls: if call in str(function): From b4fb7c25bba5f4031cebf86fea106e53cf0705b4 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 14 Apr 2024 16:04:50 +0200 Subject: [PATCH 81/87] fix: nodes_with_var attribute --- slither/detectors/oracles/oracle_detector.py | 7 ----- .../supported_oracles/chainlink_oracle.py | 13 ++++---- .../oracles/supported_oracles/oracle.py | 30 ++++++++++--------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 951f2c045e..f428e5a370 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -144,7 +144,6 @@ def map_condition_to_var(self, var, function: FunctionContract): # Check if the vars occurs in require/assert statement or in conditional node def vars_in_conditions(self, oracle: Oracle): - # vars_in_condition = [] vars_not_in_condition = [] oracle_vars = [] nodes = [] @@ -160,23 +159,17 @@ def vars_in_conditions(self, oracle: Oracle): self.investigate_internal_call(ir.function, var, None) if len(self.nodes_with_var) > 0: - # vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: if self.investigate_internal_call(oracle.function, var, None): - # vars_in_condition.append(VarInCondition(var, self.nodes_with_var)) oracle_vars.append(VarInCondition(var, self.nodes_with_var)) elif nodes := self.investigate_on_return(oracle, var): oracle_vars.append(VarInCondition(var, nodes)) oracle.out_of_function_checks.append((var, nodes)) - # except RecursionError: - # vars_not_in_condition.append(var) - # oracle_vars.append(var) else: vars_not_in_condition.append(var) oracle_vars.append(var) - # oracle.vars_in_condition = vars_in_condition oracle.vars_not_in_condition = vars_not_in_condition oracle.oracle_vars = oracle_vars return True diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index aa564d77b6..46054a1fff 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -83,12 +83,13 @@ def is_sequencer_check(self, answer, startedAt): return False answer_checked = False startedAt_checked = False - - for node in answer.nodes_with_var: - for ir in node.irs: - if isinstance(ir, Binary): - if self.price_check_for_liveness(ir): - answer_checked = True + + if hasattr(answer, "nodes_with_var"): + for node in answer.nodes_with_var: + for ir in node.irs: + if isinstance(ir, Binary): + if self.price_check_for_liveness(ir): + answer_checked = True startedAt_checked = self.check_staleness(startedAt) return answer_checked and startedAt_checked diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 7b37daf8a9..15b6d786a2 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -92,12 +92,13 @@ def check_staleness(self, var: VarInCondition) -> bool: if var is None: return False different_behavior = False - for node in var.nodes_with_var: - # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly - if self.timestamp_in_node(node): - return True - if not different_behavior: - different_behavior = check_revert(node) or return_boolean(node) + if hasattr(var, "nodes_with_var"): + for node in var.nodes_with_var: + # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly + if self.timestamp_in_node(node): + return True + if not different_behavior: + different_behavior = check_revert(node) or return_boolean(node) return different_behavior @@ -106,13 +107,14 @@ def check_price(self, var: VarInCondition) -> bool: if var is None: return False different_behavior = False - for node in var.nodes_with_var: - for ir in node.irs: - if isinstance(ir, Binary): - if self.check_greater_zero(ir): - return True - # If the conditions does not match we are looking for revert or return node - if not different_behavior: - different_behavior = check_revert(node) or return_boolean(node) + if hasattr(var, "nodes_with_var"): + for node in var.nodes_with_var: + for ir in node.irs: + if isinstance(ir, Binary): + if self.check_greater_zero(ir): + return True + # If the conditions does not match we are looking for revert or return node + if not different_behavior: + different_behavior = check_revert(node) or return_boolean(node) return different_behavior From 02e04dd02623d32007cfada12a06744bc2b6e894 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 14 Apr 2024 16:19:25 +0200 Subject: [PATCH 82/87] fix: DATA_DEPENDENCY error --- slither/detectors/oracles/oracle_detector.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index f428e5a370..747a365428 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -65,7 +65,11 @@ def check_var_condition_match(var, node) -> bool: @staticmethod def map_param_to_var(var, function: FunctionContract): for param in function.parameters: - origin_vars = get_dependencies(param, function) + try: + origin_vars = get_dependencies(param, function) + # DATA_DEPENDENCY error can be throw out + except KeyError: + continue for var2 in origin_vars: if var2 == var: return param From 1ebbe1bd926855e682a0517ce837a74f0afe5232 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 14 Apr 2024 17:18:16 +0200 Subject: [PATCH 83/87] fix: destination error --- slither/detectors/oracles/supported_oracles/oracle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 15b6d786a2..65415afef8 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -1,5 +1,6 @@ from slither.slithir.operations import HighLevelCall, Operation from slither.core.declarations import Function +from slither.core.declarations.contract import Contract from slither.slithir.operations import ( Binary, BinaryType, @@ -34,7 +35,7 @@ def get_calls(self): def is_instance_of(self, ir: Operation) -> bool: return isinstance(ir, HighLevelCall) and ( isinstance(ir.function, Function) - and self.compare_call(ir.function.name) and self.compare_interface(str(ir.destination.type)) + and self.compare_call(ir.function.name) and hasattr(ir.destination, "type") and self.compare_interface(str(ir.destination.type)) ) def set_node(self, _node): From f2b334640dfea774da0d72c59a51d54552bea781 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 14 Apr 2024 17:53:32 +0200 Subject: [PATCH 84/87] fix: another recursion error --- slither/detectors/oracles/oracle_detector.py | 29 +++++++++++++++---- .../supported_oracles/chainlink_oracle.py | 2 +- .../oracles/supported_oracles/oracle.py | 15 ++++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 747a365428..51d6228aae 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -14,6 +14,11 @@ class OracleDetector(AbstractDetector): + + # Vars for start point and max number of recursive calls + INVESTIGATION_START = 1 + MAX_INVESTIGATION_DEEP = 10 + def __init__(self, compilation_unit, slither, logger): super().__init__(compilation_unit, slither, logger) self.oracles = [] @@ -160,12 +165,16 @@ def vars_in_conditions(self, oracle: Oracle): for node in self.nodes_with_var: for ir in node.irs: if isinstance(ir, InternalCall): - self.investigate_internal_call(ir.function, var, None) + self.investigate_internal_call( + ir.function, var, None, self.INVESTIGATION_START + ) if len(self.nodes_with_var) > 0: oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: - if self.investigate_internal_call(oracle.function, var, None): + if self.investigate_internal_call( + oracle.function, var, None, self.INVESTIGATION_START + ): oracle_vars.append(VarInCondition(var, self.nodes_with_var)) elif nodes := self.investigate_on_return(oracle, var): oracle_vars.append(VarInCondition(var, nodes)) @@ -198,6 +207,9 @@ def checks_performed_out_of_original_function(self, oracle, returned_var): nodes = [] for node in nodes_of_call: oracle.set_function(functions_of_call[i]) + result = oracle.add_occurance_in_function(functions_of_call[i]) + if not result: + break oracle.set_node(node) new_vars = self.get_returned_variables_from_oracle(node) for var in new_vars: @@ -236,10 +248,13 @@ def investigate_on_return(self, oracle, var) -> bool: return False # This function interates through all internal calls in function and checks if the var is used in condition any of them - def investigate_internal_call(self, function: FunctionContract, var, original_function) -> bool: + def investigate_internal_call( + self, function: FunctionContract, var, original_function, depth + ) -> bool: if function is None: return False - if function == original_function: + if function == original_function or depth >= self.MAX_INVESTIGATION_DEEP: + print("Recursion limit reached") return False original_var_as_param = self.map_param_to_var(var, function) @@ -265,8 +280,12 @@ def investigate_internal_call(self, function: FunctionContract, var, original_fu for node in function.nodes: for ir in node.irs: if isinstance(ir, InternalCall): - if self.investigate_internal_call(ir.function, original_var_as_param, function): + if self.investigate_internal_call( + ir.function, original_var_as_param, function, depth + 1 + ): return True + elif depth >= self.INVESTIGATION_START: + return False return False def _detect(self): diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 46054a1fff..ab6835b68a 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -83,7 +83,7 @@ def is_sequencer_check(self, answer, startedAt): return False answer_checked = False startedAt_checked = False - + if hasattr(answer, "nodes_with_var"): for node in answer.nodes_with_var: for ir in node.irs: diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 65415afef8..cf74fd08f7 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -1,6 +1,5 @@ from slither.slithir.operations import HighLevelCall, Operation from slither.core.declarations import Function -from slither.core.declarations.contract import Contract from slither.slithir.operations import ( Binary, BinaryType, @@ -28,6 +27,16 @@ def __init__(self, _calls, _interfaces): self.returned_vars_indexes = None self.interface = None self.oracle_api = None + ## Works as protection against recursion loop + self.occured_in_functions = [] + + # This function adds function to the list of functions where the oracle data were used + # Used to prevent recursion of visiting same functions + def add_occurance_in_function(self, _function): + if _function in self.occured_in_functions: + return False + self.occured_in_functions.append(_function) + return True def get_calls(self): return self.calls @@ -35,7 +44,9 @@ def get_calls(self): def is_instance_of(self, ir: Operation) -> bool: return isinstance(ir, HighLevelCall) and ( isinstance(ir.function, Function) - and self.compare_call(ir.function.name) and hasattr(ir.destination, "type") and self.compare_interface(str(ir.destination.type)) + and self.compare_call(ir.function.name) + and hasattr(ir.destination, "type") + and self.compare_interface(str(ir.destination.type)) ) def set_node(self, _node): From 8b8a7bbe55bed4616a2087c8ecafe8ee50557ee3 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sun, 14 Apr 2024 18:15:56 +0200 Subject: [PATCH 85/87] fix: recursion error, better design --- slither/detectors/oracles/oracle_detector.py | 35 ++++++++----------- .../oracles/supported_oracles/oracle.py | 3 ++ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/slither/detectors/oracles/oracle_detector.py b/slither/detectors/oracles/oracle_detector.py index 51d6228aae..8073ee58c8 100644 --- a/slither/detectors/oracles/oracle_detector.py +++ b/slither/detectors/oracles/oracle_detector.py @@ -14,11 +14,6 @@ class OracleDetector(AbstractDetector): - - # Vars for start point and max number of recursive calls - INVESTIGATION_START = 1 - MAX_INVESTIGATION_DEEP = 10 - def __init__(self, compilation_unit, slither, logger): super().__init__(compilation_unit, slither, logger) self.oracles = [] @@ -166,19 +161,22 @@ def vars_in_conditions(self, oracle: Oracle): for ir in node.irs: if isinstance(ir, InternalCall): self.investigate_internal_call( - ir.function, var, None, self.INVESTIGATION_START + oracle, + ir.function, + var, ) + oracle.remove_occurances_in_function() if len(self.nodes_with_var) > 0: oracle_vars.append(VarInCondition(var, self.nodes_with_var)) else: - if self.investigate_internal_call( - oracle.function, var, None, self.INVESTIGATION_START - ): + if self.investigate_internal_call(oracle, oracle.function, var): oracle_vars.append(VarInCondition(var, self.nodes_with_var)) + oracle.remove_occurances_in_function() elif nodes := self.investigate_on_return(oracle, var): oracle_vars.append(VarInCondition(var, nodes)) oracle.out_of_function_checks.append((var, nodes)) + oracle.remove_occurances_in_function() else: vars_not_in_condition.append(var) oracle_vars.append(var) @@ -207,8 +205,7 @@ def checks_performed_out_of_original_function(self, oracle, returned_var): nodes = [] for node in nodes_of_call: oracle.set_function(functions_of_call[i]) - result = oracle.add_occurance_in_function(functions_of_call[i]) - if not result: + if not oracle.add_occurance_in_function(functions_of_call[i]): break oracle.set_node(node) new_vars = self.get_returned_variables_from_oracle(node) @@ -248,13 +245,12 @@ def investigate_on_return(self, oracle, var) -> bool: return False # This function interates through all internal calls in function and checks if the var is used in condition any of them - def investigate_internal_call( - self, function: FunctionContract, var, original_function, depth - ) -> bool: + def investigate_internal_call(self, oracle, function: FunctionContract, var) -> bool: if function is None: return False - if function == original_function or depth >= self.MAX_INVESTIGATION_DEEP: - print("Recursion limit reached") + + result = oracle.add_occurance_in_function(function) + if not result: return False original_var_as_param = self.map_param_to_var(var, function) @@ -280,12 +276,9 @@ def investigate_internal_call( for node in function.nodes: for ir in node.irs: if isinstance(ir, InternalCall): - if self.investigate_internal_call( - ir.function, original_var_as_param, function, depth + 1 - ): + if self.investigate_internal_call(oracle, ir.function, original_var_as_param): return True - elif depth >= self.INVESTIGATION_START: - return False + return False def _detect(self): diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index cf74fd08f7..0961c71003 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -38,6 +38,9 @@ def add_occurance_in_function(self, _function): self.occured_in_functions.append(_function) return True + def remove_occurances_in_function(self): + self.occured_in_functions = [] + def get_calls(self): return self.calls From 46b900cb06d375920a6812acc444be5a64e8208a Mon Sep 17 00:00:00 2001 From: Talfao Date: Fri, 26 Apr 2024 20:56:19 +0200 Subject: [PATCH 86/87] fix: dead code, improvements from coderabit --- .../oracles/deprecated_chainlink_calls.py | 2 +- .../supported_oracles/chainlink_oracle.py | 28 ++++--------------- .../oracles/supported_oracles/oracle.py | 2 +- ...data_check_price_in_double_internal_fc.sol | 1 + .../0.8.20/oracle_timestamp_in_var.sol | 4 +-- 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/slither/detectors/oracles/deprecated_chainlink_calls.py b/slither/detectors/oracles/deprecated_chainlink_calls.py index 8045e3b59d..4a37970a0e 100644 --- a/slither/detectors/oracles/deprecated_chainlink_calls.py +++ b/slither/detectors/oracles/deprecated_chainlink_calls.py @@ -5,7 +5,7 @@ class DeprecatedChainlinkCall(AbstractDetector): """ - Documentation + Documentation: This detector scans for deprecated Chainlink API calls in Solidity contracts. For example, it flags the use of `getAnswer` which is no longer recommended. """ ARGUMENT = "deprecated-chainlink-call" diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index ab6835b68a..2a25722f84 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -28,25 +28,6 @@ class ChainlinkOracle(Oracle): def __init__(self): super().__init__(CHAINLINK_ORACLE_CALLS, INTERFACES) - # This function checks if the RoundId value is validated in connection with answeredInRound value - # But this last variable was deprecated. We left this function for possible future use. - @staticmethod - def check_RoundId(var: VarInCondition, var2: VarInCondition) -> bool: - if var is None or var2 is None: - return False - for node in var.nodes_with_var: - for ir in node.irs: - if isinstance(ir, Binary): - if ir.type in (BinaryType.GREATER, BinaryType.GREATER_EQUAL): - if ir.variable_right == var.var and ir.variable_left == var2.var: - return True - elif ir.type in (BinaryType.LESS, BinaryType.LESS_EQUAL): - if ir.variable_right == var2.var and ir.variable_left == var.var: - return True - return check_revert(node) or return_boolean(node) - - return False - @staticmethod def generate_naive_order(): vars_order = {} @@ -97,23 +78,25 @@ def is_sequencer_check(self, answer, startedAt): def naive_data_validation(self): vars_order = self.find_which_vars_are_used() problems = [] + # Iterating through all oracle variables which were returned by the oracle call for (index, var) in vars_order.items(): if not self.is_needed_to_check_conditions(var): continue - # if index == ChainlinkVars.ROUNDID.value: # Commented due to deprecation of AnsweredInRound - # if not self.check_RoundId(var, vars_order[ChainlinkVars.ANSWEREDINROUND.value]): - # problems.append("RoundID value is not checked correctly. It was returned by the oracle call in the function {} of contract {}.\n".format( oracle.function, oracle.node.source_mapping)) + # Second variable is the price value if index == ChainlinkVars.ANSWER.value: if not self.check_price(var): problems.append( f"The price value is validated incorrectly. This value is returned by Chainlink oracle call {self.contract}.{self.interface}.{self.oracle_api} ({self.node.source_mapping}).\n" ) + # Third variable is the updatedAt value, indicating when the price was updated elif index == ChainlinkVars.UPDATEDAT.value: if not self.check_staleness(var): problems.append( f"The price can be stale due to incorrect validation of updatedAt value. This value is returned by Chainlink oracle call {self.contract}.{self.interface}.{self.oracle_api} ({self.node.source_mapping}).\n" ) + # Fourth variable is the startedAt value, indicating when the round was started. + # Used in connection with sequencer elif ( index == ChainlinkVars.STARTEDAT.value and vars_order[ChainlinkVars.STARTEDAT.value] is not None @@ -122,6 +105,7 @@ def naive_data_validation(self): if self.is_sequencer_check(vars_order[ChainlinkVars.ANSWER.value], var): problems = [] break + # Iterate through checks performed out of the function where the oracle call is performed for tup in self.out_of_function_checks: problems.append( f"The variation of {tup[0]} is checked on the lines {[str(node.source_mapping) for node in tup[1][::5]]}. Not in the original function where the Oracle call is performed.\n" diff --git a/slither/detectors/oracles/supported_oracles/oracle.py b/slither/detectors/oracles/supported_oracles/oracle.py index 0961c71003..7d874d5da3 100644 --- a/slither/detectors/oracles/supported_oracles/oracle.py +++ b/slither/detectors/oracles/supported_oracles/oracle.py @@ -109,7 +109,7 @@ def check_staleness(self, var: VarInCondition) -> bool: different_behavior = False if hasattr(var, "nodes_with_var"): for node in var.nodes_with_var: - # This is temporarily check which will be improved in the future. Mostly we are looking for block.timestamp and trust the developer that he is using it correctly + # This check look for conditions where the timestamp is used if self.timestamp_in_node(node): return True if not different_behavior: diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol index 6eaa84684e..37f9a8bff2 100644 --- a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol +++ b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_data_check_price_in_double_internal_fc.sol @@ -53,6 +53,7 @@ contract StableOracleDAI { return false; } + // Returns the latest price after validating it to be greater than zero and checking for data staleness and round completion. function getPriceUSD() external view returns (uint256) { uint256 wethPriceUSD = 1; uint256 DAIWethPrice = 1; diff --git a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol index 13f5e76916..38b86827ac 100644 --- a/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol +++ b/tests/e2e/detectors/test_data/oracle-data-validation/0.8.20/oracle_timestamp_in_var.sol @@ -45,10 +45,10 @@ contract ChainlinkETHUSDPriceConsumer { ); } /** - * Returns the latest price + * Returns the latest price. Ensures the price is not outdated by checking the `updatedAt` timestamp. */ function getLatestPrice() public view returns (int) { - (, int price, ,uint256 updateAt , ) = priceFeed.latestRoundData(); + (, int price, , uint256 updateAt, ) = priceFeed.latestRoundData(); uint current_timestamp = block.timestamp; require(current_timestamp - updateAt < 1 minutes, "Price is outdated"); return price; From 73f0b31936d3e42e479877e1d19742a766014ef4 Mon Sep 17 00:00:00 2001 From: Talfao Date: Sat, 27 Apr 2024 10:04:27 +0200 Subject: [PATCH 87/87] fix: remove unused code --- slither/detectors/oracles/supported_oracles/chainlink_oracle.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py index 2a25722f84..277f32d8a3 100644 --- a/slither/detectors/oracles/supported_oracles/chainlink_oracle.py +++ b/slither/detectors/oracles/supported_oracles/chainlink_oracle.py @@ -4,9 +4,7 @@ Binary, BinaryType, ) - from slither.slithir.variables.constant import Constant -from slither.detectors.oracles.supported_oracles.help_functions import check_revert, return_boolean CHAINLINK_ORACLE_CALLS = [