Repository: crytic/slither

Issue:
controlled-delegatecall: do not report if the contract is a delegatecall proxy
For the `controlled-delegatecall` detector, we should:
- Filter the results for delegatecall proxys, as it is the intended behavior.
- If the contract is a proxy, only report the case where the taint was made from an unprotected function


Discussion:

Resolving patch:
diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py
--- a/slither/core/declarations/contract.py
+++ b/slither/core/declarations/contract.py
@@ -863,7 +863,7 @@ def is_upgradeable_proxy(self):
             self._is_upgradeable_proxy = False
             for f in self.functions:
                 if f.is_fallback:
-                    for node in f.nodes:
+                    for node in f.all_nodes():
                         for ir in node.irs:
                             if isinstance(ir, LowLevelCall) and ir.function_name == 'delegatecall':
                                 self._is_upgradeable_proxy = True
diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py
--- a/slither/core/declarations/function.py
+++ b/slither/core/declarations/function.py
@@ -121,6 +121,7 @@ def __init__(self):
         self._all_solidity_variables_read = None
         self._all_state_variables_written = None
         self._all_slithir_variables = None
+        self._all_nodes = None
         self._all_conditional_state_variables_read = None
         self._all_conditional_state_variables_read_with_loop = None
         self._all_conditional_solidity_variables_read = None
@@ -833,6 +834,13 @@ def all_slithir_variables(self):
                 lambda x: x.slithir_variable)
         return self._all_slithir_variables
 
+    def all_nodes(self):
+        """ recursive version of nodes
+        """
+        if self._all_nodes is None:
+            self._all_nodes = self._explore_functions(lambda x: x.nodes)
+        return self._all_nodes
+
     def all_expressions(self):
         """ recursive version of variables_read
         """
diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py
--- a/slither/detectors/statements/controlled_delegatecall.py
+++ b/slither/detectors/statements/controlled_delegatecall.py
@@ -34,16 +34,19 @@ def controlled_delegatecall(self, function):
         for node in function.nodes:
             for ir in node.irs:
                 if isinstance(ir, LowLevelCall) and ir.function_name in ['delegatecall', 'callcode']:
-                    if is_tainted(ir.destination, function.contract):
+                    if is_tainted(ir.destination,
+                                  function.contract):
                         ret.append(node)
         return ret
 
     def _detect(self):
         results = []
 
-        for contract in self.slither.contracts:
+        for contract in self.slither.contracts_derived:
             for f in contract.functions:
-                if f.contract_declarer != contract:
+                # If its an upgradeable proxy, do not report protected function
+                # As functions to upgrades the destination lead to too many FPs
+                if contract.is_upgradeable_proxy and f.is_protected():
                     continue
                 nodes = self.controlled_delegatecall(f)
                 if nodes:


Test patch: