diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py
new file mode 100644
index 0000000000..4ab0f2f03f
--- /dev/null
+++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py
@@ -0,0 +1,154 @@
+from typing import Optional, Set, Union
+
+from slither.analyses.data_flow.analyses.reentrancy.analysis.domain import (
+ DomainVariant,
+ ReentrancyDomain,
+)
+from slither.analyses.data_flow.analyses.reentrancy.core.state import State
+from slither.analyses.data_flow.engine.analysis import Analysis
+from slither.analyses.data_flow.engine.direction import Direction, Forward
+from slither.analyses.data_flow.engine.domain import Domain
+from slither.core.cfg.node import Node
+from slither.core.declarations.function import Function
+from slither.core.variables.state_variable import StateVariable
+from slither.slithir.operations.event_call import EventCall
+from slither.slithir.operations.high_level_call import HighLevelCall
+from slither.slithir.operations.internal_call import InternalCall
+from slither.slithir.operations.low_level_call import LowLevelCall
+from slither.slithir.operations.operation import Operation
+from slither.slithir.operations.send import Send
+from slither.slithir.operations.transfer import Transfer
+
+
+class ReentrancyAnalysis(Analysis):
+ def __init__(self):
+ self._direction = Forward()
+
+ def domain(self) -> Domain:
+ return ReentrancyDomain.bottom()
+
+ def direction(self) -> Direction:
+ return self._direction
+
+ def bottom_value(self) -> Domain:
+ return ReentrancyDomain.bottom()
+
+ def transfer_function(self, node: Node, domain: ReentrancyDomain, operation: Operation):
+ self.transfer_function_helper(node, domain, operation, private_functions_seen=set())
+
+ def transfer_function_helper(
+ self,
+ node: Node,
+ domain: ReentrancyDomain,
+ operation: Operation,
+ private_functions_seen: Optional[Set[Function]] = None,
+ ):
+ if private_functions_seen is None:
+ private_functions_seen = set()
+
+ if domain.variant == DomainVariant.BOTTOM:
+ domain.variant = DomainVariant.STATE
+ domain.state = State()
+
+ self._analyze_operation_by_type(operation, domain, node, private_functions_seen)
+
+ def _analyze_operation_by_type(
+ self,
+ operation: Operation,
+ domain: ReentrancyDomain,
+ node: Node,
+ private_functions_seen: Set[Function],
+ ):
+ if isinstance(operation, EventCall):
+ self._handle_event_call_operation(operation, domain)
+ elif isinstance(operation, InternalCall):
+ self._handle_internal_call_operation(operation, domain, private_functions_seen)
+ elif isinstance(operation, (HighLevelCall, LowLevelCall, Transfer, Send)):
+ self._handle_abi_call_contract_operation(operation, domain, node)
+
+ self._handle_storage(domain, node)
+ self._update_writes_after_calls(domain, node)
+
+ def _handle_storage(self, domain: ReentrancyDomain, node: Node):
+ # Track state reads
+ for var in node.state_variables_read:
+ if isinstance(var, StateVariable) and var.is_stored:
+ domain.state.add_read(var, node)
+ # Track state writes
+ for var in node.state_variables_written:
+ if isinstance(var, StateVariable) and var.is_stored:
+ domain.state.add_written(var, node)
+
+ def _update_writes_after_calls(self, domain: ReentrancyDomain, node: Node):
+ # Writes after any external call
+ if node in domain.state.calls:
+ for var_name, write_nodes in domain.state.written.items():
+ for wn in write_nodes:
+ domain.state.add_write_after_call(var_name, wn)
+ # Writes after ETH-sending calls
+ if node in domain.state.send_eth:
+ for var_name, write_nodes in domain.state.written.items():
+ for wn in write_nodes:
+ domain.state.add_write_after_call(var_name, wn)
+
+ def _handle_internal_call_operation(
+ self,
+ operation: InternalCall,
+ domain: ReentrancyDomain,
+ private_functions_seen: Set[Function],
+ ):
+ function = operation.function
+ if not isinstance(function, Function) or function in private_functions_seen:
+ return
+
+ private_functions_seen.add(function)
+ for node in function.nodes:
+ for internal_operation in node.irs:
+ if isinstance(internal_operation, (HighLevelCall, LowLevelCall, Transfer, Send)):
+ continue
+ self.transfer_function_helper(
+ node,
+ domain,
+ internal_operation,
+ private_functions_seen,
+ )
+ # Mark cross-function reentrancy for written variables
+ for var_name in domain.state.written.keys():
+ domain.state.add_cross_function(var_name, function)
+
+ def _handle_abi_call_contract_operation(
+ self,
+ operation: Union[LowLevelCall, HighLevelCall, Send, Transfer],
+ domain: ReentrancyDomain,
+ node: Node,
+ ):
+ # Track all external calls - avoid duplicates
+ if operation.node not in domain.state.calls.get(node, set()):
+ domain.state.add_call(node, operation.node)
+
+ # Track variables read prior to this call
+ for var_name in domain.state.reads.keys():
+ domain.state.add_reads_prior_calls(node, var_name)
+
+ # Track external calls that send ETH - avoid duplicates
+ if operation.can_send_eth:
+ if operation.node not in domain.state.send_eth.get(node, set()):
+ domain.state.add_send_eth(node, operation.node)
+
+ def _handle_event_call_operation(self, operation: EventCall, domain: ReentrancyDomain):
+ # Track events and propagate previous external calls
+ # Only propagate calls that haven't already been propagated to this event node
+ existing_calls = domain.state.calls.get(operation.node, set())
+
+ # Collect all calls to add before modifying the dictionary
+ calls_to_add = []
+ for calls_set in domain.state.calls.values():
+ for call_node in calls_set:
+ if call_node not in existing_calls:
+ calls_to_add.append(call_node)
+
+ # Add all collected calls
+ for call_node in calls_to_add:
+ domain.state.add_call(operation.node, call_node)
+
+ domain.state.add_event(operation, operation.node)
diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py
new file mode 100644
index 0000000000..0b095b3d00
--- /dev/null
+++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py
@@ -0,0 +1,60 @@
+from enum import Enum, auto
+from typing import Optional
+
+from slither.analyses.data_flow.analyses.reentrancy.core.state import State
+from slither.analyses.data_flow.engine.domain import Domain
+
+
+class DomainVariant(Enum):
+ BOTTOM = auto()
+ TOP = auto()
+ STATE = auto()
+
+
+class ReentrancyDomain(Domain):
+ def __init__(self, variant: DomainVariant, state: Optional[State] = None):
+ self.variant = variant
+ self.state = state or State()
+
+ @classmethod
+ def bottom(cls) -> "ReentrancyDomain":
+ return cls(DomainVariant.BOTTOM)
+
+ @classmethod
+ def top(cls) -> "ReentrancyDomain":
+ return cls(DomainVariant.TOP)
+
+ @classmethod
+ def with_state(cls, info: State) -> "ReentrancyDomain":
+ return cls(DomainVariant.STATE, info)
+
+ def join(self, other: "ReentrancyDomain") -> bool:
+ if self.variant == DomainVariant.TOP or other.variant == DomainVariant.BOTTOM:
+ return False
+
+ if self.variant == DomainVariant.BOTTOM and other.variant == DomainVariant.STATE:
+ self.variant = DomainVariant.STATE
+ self.state = other.state.deep_copy()
+ self.state.written.clear()
+ self.state.events.clear()
+ self.state.writes_after_calls.clear()
+ self.state.cross_function.clear()
+ return True
+
+ if self.variant == DomainVariant.STATE and other.variant == DomainVariant.STATE:
+ if self.state == other.state:
+ return False
+
+ self.state.send_eth.update(other.state.send_eth)
+ self.state.calls.update(other.state.calls)
+ self.state.reads.update(other.state.reads)
+ self.state.reads_prior_calls.update(other.state.reads_prior_calls)
+ self.state.safe_send_eth.update(other.state.safe_send_eth)
+ self.state.writes_after_calls.update(other.state.writes_after_calls)
+ self.state.cross_function.update(other.state.cross_function)
+ return True
+
+ else:
+ self.variant = DomainVariant.TOP
+
+ return True
diff --git a/slither/analyses/data_flow/analyses/reentrancy/core/state.py b/slither/analyses/data_flow/analyses/reentrancy/core/state.py
new file mode 100644
index 0000000000..87e3c18540
--- /dev/null
+++ b/slither/analyses/data_flow/analyses/reentrancy/core/state.py
@@ -0,0 +1,162 @@
+import copy
+from collections import defaultdict
+from typing import Dict, Set
+
+from slither.core.cfg.node import Node
+from slither.core.declarations.function import Function
+from slither.core.variables.state_variable import StateVariable
+from slither.slithir.operations.event_call import EventCall
+
+
+class State:
+ def __init__(self):
+ self._send_eth: Dict[Node, Set[Node]] = defaultdict(set)
+ self._safe_send_eth: Dict[Node, Set[Node]] = defaultdict(set)
+ self._calls: Dict[Node, Set[Node]] = defaultdict(set)
+ self._reads: Dict[str, Set[Node]] = defaultdict(set)
+ self._reads_prior_calls: Dict[Node, Set[str]] = defaultdict(set)
+ self._events: Dict[EventCall, Set[Node]] = defaultdict(set)
+ self._written: Dict[str, Set[Node]] = defaultdict(set)
+ self.writes_after_calls: Dict[str, Set[Node]] = defaultdict(set)
+ self.cross_function: Dict[StateVariable, Set[Function]] = defaultdict(set)
+
+ # -------------------- Add methods --------------------
+ def add_call(self, node: Node, call_node: Node):
+ self._calls[node].add(call_node)
+
+ def add_send_eth(self, node: Node, call_node: Node):
+ self._send_eth[node].add(call_node)
+
+ def add_safe_send_eth(self, node: Node, call_node: Node):
+ self._safe_send_eth[node].add(call_node)
+
+ def add_written(self, var: StateVariable, node: Node):
+ # Ensure the canonical name exists and is not None
+ if var.canonical_name is not None:
+ # Ensure the key exists in the defaultdict
+ if var.canonical_name not in self._written:
+ self._written[var.canonical_name] = set()
+ self._written[var.canonical_name].add(node)
+
+ def add_read(self, var: StateVariable, node: Node):
+ # Ensure the canonical name exists and is not None
+ if var.canonical_name is not None:
+ # Ensure the key exists in the defaultdict
+ if var.canonical_name not in self._reads:
+ self._reads[var.canonical_name] = set()
+ self._reads[var.canonical_name].add(node)
+
+ def add_reads_prior_calls(self, node: Node, var_name: str):
+ self._reads_prior_calls[node].add(var_name)
+
+ def add_write_after_call(self, var_name: str, node: Node):
+ self.writes_after_calls[var_name].add(node)
+
+ def add_cross_function(self, var: StateVariable, function: Function):
+ self.cross_function[var].add(function)
+
+ def add_event(self, event: EventCall, node: Node):
+ self._events[event].add(node)
+
+ # -------------------- Properties --------------------
+ @property
+ def send_eth(self) -> Dict[Node, Set[Node]]:
+ return self._send_eth
+
+ @property
+ def safe_send_eth(self) -> Dict[Node, Set[Node]]:
+ return self._safe_send_eth
+
+ @property
+ def all_eth_calls(self) -> Dict[Node, Set[Node]]:
+ result = defaultdict(set)
+ for node, calls in self._send_eth.items():
+ result[node].update(calls)
+ for node, calls in self._safe_send_eth.items():
+ result[node].update(calls)
+ return result
+
+ @property
+ def calls(self) -> Dict[Node, Set[Node]]:
+ return self._calls
+
+ @property
+ def reads(self) -> Dict[str, Set[Node]]:
+ return self._reads
+
+ @property
+ def written(self) -> Dict[str, Set[Node]]:
+ return self._written
+
+ @property
+ def reads_prior_calls(self) -> Dict[Node, Set[str]]:
+ return self._reads_prior_calls
+
+ @property
+ def events(self) -> Dict[EventCall, Set[Node]]:
+ return self._events
+
+ # -------------------- Utilities --------------------
+ def __eq__(self, other):
+ if not isinstance(other, State):
+ return False
+ return (
+ self._send_eth == other._send_eth
+ and self._safe_send_eth == other._safe_send_eth
+ and self._calls == other._calls
+ and self._reads == other._reads
+ and self._reads_prior_calls == other._reads_prior_calls
+ and self._events == other._events
+ and self._written == other._written
+ and self.writes_after_calls == other.writes_after_calls
+ and self.cross_function == other.cross_function
+ )
+
+ def __hash__(self):
+ return hash(
+ (
+ frozenset(self._send_eth.items()),
+ frozenset(self._safe_send_eth.items()),
+ frozenset(self._calls.items()),
+ frozenset(self._reads.items()),
+ frozenset(self._reads_prior_calls.items()),
+ frozenset(self._events.items()),
+ frozenset(self._written.items()),
+ frozenset((k, frozenset(v)) for k, v in self.writes_after_calls.items()),
+ frozenset((k, frozenset(v)) for k, v in self.cross_function.items()),
+ )
+ )
+
+ def __str__(self):
+ return (
+ f"State(\n"
+ f" send_eth: {len(self._send_eth)} items,\n"
+ f" safe_send_eth: {len(self._safe_send_eth)} items,\n"
+ f" calls: {len(self._calls)} items,\n"
+ f" reads: {len(self._reads)} items,\n"
+ f" reads_prior_calls: {len(self._reads_prior_calls)} items,\n"
+ f" events: {len(self._events)} items,\n"
+ f" written: {len(self._written)} items,\n"
+ f" writes_after_calls: {len(self.writes_after_calls)} items,\n"
+ f" cross_function: {len(self.cross_function)} items,\n"
+ f")"
+ )
+
+ def deep_copy(self) -> "State":
+ new_state = State()
+ # Use shallow copy for Node objects to avoid circular reference issues
+
+ new_state._send_eth.update({k: v.copy() for k, v in self._send_eth.items()})
+ new_state._safe_send_eth.update({k: v.copy() for k, v in self._safe_send_eth.items()})
+ new_state._calls.update({k: v.copy() for k, v in self._calls.items()})
+ new_state._reads.update({k: v.copy() for k, v in self._reads.items()})
+ new_state._reads_prior_calls.update(
+ {k: v.copy() for k, v in self._reads_prior_calls.items()}
+ )
+ new_state._events.update({k: v.copy() for k, v in self._events.items()})
+ new_state._written.update({k: v.copy() for k, v in self._written.items()})
+ new_state.writes_after_calls.update(
+ {k: v.copy() for k, v in self.writes_after_calls.items()}
+ )
+ new_state.cross_function.update({k: v.copy() for k, v in self.cross_function.items()})
+ return new_state
diff --git a/slither/analyses/data_flow/engine/analysis.py b/slither/analyses/data_flow/engine/analysis.py
new file mode 100644
index 0000000000..5793333fec
--- /dev/null
+++ b/slither/analyses/data_flow/engine/analysis.py
@@ -0,0 +1,42 @@
+from abc import ABC, abstractmethod
+from typing import Generic, TypeVar
+
+from slither.analyses.data_flow.engine.direction import Direction
+from slither.analyses.data_flow.engine.domain import Domain
+from slither.core.cfg.node import Node
+from slither.slithir.operations.operation import Operation
+
+
+class Analysis(ABC):
+ @abstractmethod
+ def domain(self) -> Domain:
+ pass
+
+ @abstractmethod
+ def direction(self) -> Direction:
+ pass
+
+ @abstractmethod
+ def transfer_function(self, node: Node, domain: Domain, operation: Operation):
+ pass
+
+ @abstractmethod
+ def bottom_value(self) -> Domain:
+ pass
+
+ def apply_condition(self, domain: Domain, condition: Operation, branch_taken: bool) -> Domain:
+ """Override this to handle branch filtering. Default: no-op"""
+ return domain # Analyses that don't implement this get no filtering
+
+ def apply_widening(self, current_state: Domain, previous_state: Domain, set_b: set) -> Domain:
+ """Override this to implement widening operations. Default: no-op"""
+ return current_state # Analyses that don't implement this get no widening
+
+
+A = TypeVar("A", bound=Analysis)
+
+
+class AnalysisState(Generic[A]):
+ def __init__(self, pre: Domain, post: Domain) -> None:
+ self.pre: Domain = pre
+ self.post: Domain = post
diff --git a/slither/analyses/data_flow/engine/direction.py b/slither/analyses/data_flow/engine/direction.py
new file mode 100644
index 0000000000..6b63dbdeac
--- /dev/null
+++ b/slither/analyses/data_flow/engine/direction.py
@@ -0,0 +1,72 @@
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING, Deque, Dict
+
+if TYPE_CHECKING:
+ from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState
+
+from slither.core.cfg.node import Node
+
+
+class Direction(ABC):
+ @property
+ @abstractmethod
+ def IS_FORWARD(self) -> bool:
+ pass
+
+ @abstractmethod
+ def apply_transfer_function(
+ self,
+ analysis: "Analysis",
+ current_state: "AnalysisState",
+ node: Node,
+ worklist: Deque[Node],
+ global_state: Dict[int, "AnalysisState[A]"],
+ ):
+ pass
+
+
+class Forward(Direction):
+ def __init__(self):
+ pass
+
+ @property
+ def IS_FORWARD(self) -> bool:
+ return True
+
+ def apply_transfer_function(
+ self,
+ analysis: "Analysis",
+ current_state: "AnalysisState",
+ node: Node,
+ worklist: Deque[Node],
+ global_state: Dict[int, "AnalysisState[A]"],
+ ):
+ # Apply transfer function to current node
+ for operation in node.irs or [None]:
+ analysis.transfer_function(node=node, domain=current_state.pre, operation=operation)
+
+ # Set post state
+ global_state[node.node_id].post = current_state.pre
+
+ # Propagate to all successors
+ for successor in node.sons:
+ if successor and successor.node_id in global_state:
+ son_state = global_state[successor.node_id]
+ if son_state.pre.join(current_state.pre) and successor not in worklist:
+ worklist.append(successor)
+
+
+class Backward(Direction):
+ @property
+ def IS_FORWARD(self) -> bool:
+ return False
+
+ def apply_transfer_function(
+ self,
+ analysis: "Analysis",
+ current_state: "AnalysisState",
+ node: Node,
+ worklist: Deque[Node],
+ global_state: Dict[int, "AnalysisState[A]"],
+ ):
+ raise NotImplementedError("Backward transfer function hasn't been developed yet")
diff --git a/slither/analyses/data_flow/engine/domain.py b/slither/analyses/data_flow/engine/domain.py
new file mode 100644
index 0000000000..896d8b88c2
--- /dev/null
+++ b/slither/analyses/data_flow/engine/domain.py
@@ -0,0 +1,21 @@
+from abc import ABC, abstractmethod
+
+try:
+ from typing import Self
+except ImportError:
+ from typing_extensions import Self
+
+
+class Domain(ABC):
+ @abstractmethod
+ def top(cls) -> Self:
+ """The top element of the domain"""
+
+ @abstractmethod
+ def bottom(cls) -> Self:
+ """The bottom element of the domain"""
+
+ @abstractmethod
+ def join(self, other: Self) -> bool:
+ """Computes the least upper bound of two elements and store the result in self.
+ Return True if self changed."""
diff --git a/slither/analyses/data_flow/engine/engine.py b/slither/analyses/data_flow/engine/engine.py
new file mode 100644
index 0000000000..5db09391a9
--- /dev/null
+++ b/slither/analyses/data_flow/engine/engine.py
@@ -0,0 +1,60 @@
+from collections import deque
+from typing import Deque, Dict, Generic, List
+
+from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState
+from slither.core.cfg.node import Node
+from slither.core.declarations.function import Function
+
+
+class Engine(Generic[A]):
+ def __init__(self):
+ self.state: Dict[int, AnalysisState[A]] = {}
+ self.nodes: List[Node] = []
+ self.analysis: Analysis
+ self.function: Function # Single function being analyzed
+
+ @classmethod
+ def new(cls, analysis: Analysis, function: Function):
+ engine = cls()
+ engine.analysis = analysis
+ engine.function = function # Store single function
+
+ # Create state mapping for nodes in this single function only
+ # Data flow analysis operates on one function's CFG at a time
+ for node in function.nodes:
+ engine.nodes.append(node)
+ engine.state[node.node_id] = AnalysisState(
+ pre=analysis.bottom_value(), post=analysis.bottom_value()
+ )
+
+ return engine
+
+ def run_analysis(self):
+ worklist: Deque[Node] = deque()
+
+ if self.analysis.direction().IS_FORWARD:
+ if self.function.entry_point is not None:
+ worklist.append(self.function.entry_point)
+ else:
+ raise NotImplementedError("Backward analysis is not implemented")
+
+ while worklist:
+ node = worklist.popleft()
+
+ current_state = AnalysisState(
+ pre=self.state[node.node_id].pre, post=self.state[node.node_id].post
+ )
+
+ self.analysis.direction().apply_transfer_function(
+ analysis=self.analysis,
+ current_state=current_state,
+ node=node,
+ worklist=worklist,
+ global_state=self.state,
+ )
+
+ def result(self) -> Dict[Node, AnalysisState[A]]:
+ result = {}
+ for node in self.nodes:
+ result[node] = self.state[node.node_id]
+ return result
diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py
index a30d2b3c00..9bc8944035 100644
--- a/slither/detectors/all_detectors.py
+++ b/slither/detectors/all_detectors.py
@@ -18,6 +18,8 @@
from .reentrancy.reentrancy_eth import ReentrancyEth
from .reentrancy.reentrancy_no_gas import ReentrancyNoGas
from .reentrancy.reentrancy_events import ReentrancyEvent
+from .reentrancy_df.reentrancy_eth_df import ReentrancyEthDF
+
from .variables.unused_state_variables import UnusedStateVars
from .variables.could_be_constant import CouldBeConstant
from .variables.could_be_immutable import CouldBeImmutable
diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py
index 8f39c519d2..d1afc13ed5 100644
--- a/slither/detectors/reentrancy/reentrancy.py
+++ b/slither/detectors/reentrancy/reentrancy.py
@@ -1,9 +1,10 @@
-""""
- Re-entrancy detection
+""" "
+Re-entrancy detection
- Based on heuristics, it may lead to FP and FN
- Iterate over all the nodes of the graph until reaching a fixpoint
+Based on heuristics, it may lead to FP and FN
+Iterate over all the nodes of the graph until reaching a fixpoint
"""
+
from collections import defaultdict
from typing import Set, Dict, List, Tuple, Optional
diff --git a/slither/detectors/reentrancy_df/reentrancy_eth_df.py b/slither/detectors/reentrancy_df/reentrancy_eth_df.py
new file mode 100644
index 0000000000..5aec2a4efc
--- /dev/null
+++ b/slither/detectors/reentrancy_df/reentrancy_eth_df.py
@@ -0,0 +1,258 @@
+"""
+Re-entrancy detection
+"""
+
+from collections import defaultdict, namedtuple
+from typing import Dict, List, Set
+from loguru import logger
+
+from slither.analyses.data_flow.analyses.reentrancy.analysis.analysis import ReentrancyAnalysis
+from slither.analyses.data_flow.analyses.reentrancy.analysis.domain import (
+ DomainVariant,
+ ReentrancyDomain,
+)
+from slither.analyses.data_flow.engine.engine import Engine
+from slither.core.variables.state_variable import StateVariable
+from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
+from slither.detectors.reentrancy.reentrancy import to_hashable
+from slither.utils.output import Output
+
+FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"])
+FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes", "cross_functions"])
+
+
+class ReentrancyEthDF(AbstractDetector):
+ ARGUMENT = "reentrancy-eth-df"
+ HELP = "Reentrancy vulnerabilities (theft of ethers)"
+ IMPACT = DetectorClassification.HIGH
+ CONFIDENCE = DetectorClassification.MEDIUM
+ WIKI = (
+ "https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities"
+ )
+ WIKI_TITLE = "Reentrancy vulnerabilities"
+
+ # region wiki_description
+ WIKI_DESCRIPTION = """
+ Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
+ Do not report reentrancies that don't involve Ether (see reentrancy-no-eth)
+ """
+ # endregion wiki_description
+
+ # region wiki_exploit_scenario
+ WIKI_EXPLOIT_SCENARIO = """
+ solidity
+ function withdrawBalance(){
+ // send userBalance[msg.sender] Ether to msg.sender
+ // if msg.sender is a contract, it will call its fallback function
+ if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){
+ throw;
+ }
+ userBalance[msg.sender] = 0;
+ }
+ Bob uses the re-entrancy bug to call withdrawBalance two times, and withdraw more than its initial deposit to the contract.
+ """
+ # endregion wiki_exploit_scenario
+
+ WIKI_RECOMMENDATION = "Apply the [check-effects-interactions pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
+ STANDARD_JSON = False
+
+ def find_reentrancies(self) -> Dict[FindingKey, Set[FindingValue]]:
+ """Detect per-function reentrancy vulnerabilities using data flow analysis."""
+ result: Dict[FindingKey, Set[FindingValue]] = defaultdict(set)
+
+ for contract in self.contracts:
+ variables_used_in_reentrancy = contract.state_variables_used_in_reentrant_targets
+ functions = [
+ f
+ for f in contract.functions_and_modifiers_declared
+ if f.is_implemented and not f.is_constructor
+ ]
+
+ for f in functions:
+ engine = Engine.new(analysis=ReentrancyAnalysis(), function=f)
+ engine.run_analysis()
+ engine_result = engine.result()
+ vulnerable_findings: Set[FindingValue] = set()
+ function_calls = {}
+ function_send_eth = {}
+
+ for node in f.nodes:
+ if node not in engine_result:
+ continue
+
+ analysis = engine_result[node]
+ if not hasattr(analysis, "post") or not isinstance(
+ analysis.post, ReentrancyDomain
+ ):
+ continue
+ if analysis.post.variant != DomainVariant.STATE:
+ continue
+
+ state = analysis.post.state
+
+ # Track calls and sends
+ for call_node, call_destinations in state.calls.items():
+ function_calls.setdefault(call_node, set()).update(call_destinations)
+ for send_node, send_destinations in state.send_eth.items():
+ function_send_eth.setdefault(send_node, set()).update(send_destinations)
+
+ # Skip if not all reentrancy conditions are met: ETH calls, state writes, and reads before calls
+ if not (
+ (state.send_eth or state.safe_send_eth)
+ and state.written
+ and state.reads_prior_calls
+ ):
+ continue
+
+ # Iterate through all calls that have variables read before them
+ for call_node, vars_read_before_call in state.reads_prior_calls.items():
+ # Check each variable that was read before a call
+ for var_canonical_name in vars_read_before_call:
+ # Find the actual StateVariable object by canonical name
+ var = next(
+ (
+ sv
+ for sv in contract.state_variables
+ if sv.canonical_name == var_canonical_name
+ ),
+ None,
+ )
+ # Skip if variable not found or not a StateVariable
+ if not var or not isinstance(var, StateVariable):
+ continue
+
+ # Only proceed if this variable is written somewhere
+ if var_canonical_name not in state.written:
+ continue
+
+ writing_nodes = state.written[var_canonical_name]
+
+ # Filter out entry point nodes to avoid false positives
+ non_entry_writing_nodes = {
+ n for n in writing_nodes if n != f.entry_point
+ }
+
+ # Skip if no non-entry writing nodes found
+ if not non_entry_writing_nodes:
+ continue
+
+ # Only report if function is reentrant or variable used in cross-function reentrancy
+ if not (f.is_reentrant or var in variables_used_in_reentrancy):
+ continue
+
+ cross_functions = variables_used_in_reentrancy.get(var, [])
+
+ # Convert set to list if needed for consistent handling
+ if isinstance(cross_functions, set):
+ cross_functions = list(cross_functions)
+
+ # Use the first writing node as the main node for reporting
+ main_node = min(non_entry_writing_nodes, key=lambda x: x.node_id)
+
+ # Create finding value with all relevant information
+ finding_value = FindingValue(
+ var,
+ main_node,
+ tuple(sorted(non_entry_writing_nodes, key=lambda x: x.node_id)),
+ tuple(sorted(cross_functions, key=lambda x: str(x))),
+ )
+
+ # Add to vulnerable findings set
+ vulnerable_findings.add(finding_value)
+
+ if vulnerable_findings:
+ finding_key = FindingKey(
+ function=f,
+ calls=to_hashable(function_calls),
+ send_eth=to_hashable(function_send_eth),
+ )
+ result[finding_key] |= vulnerable_findings
+
+ return result
+
+ def _detect(self) -> List[Output]:
+ super()._detect()
+ reentrancies = self.find_reentrancies()
+ results = []
+
+ result_sorted = sorted(list(reentrancies.items()), key=lambda x: x[0].function.name)
+
+ for (func, calls, send_eth), varsWrittenSet in result_sorted:
+ calls_dict = {
+ call_node.node_id: (call_node, call_list) for call_node, call_list in calls
+ }
+ calls_sorted = sorted(calls_dict.values(), key=lambda x: x[0].node_id)
+ send_eth_dict = {
+ send_node.node_id: (send_node, send_list) for send_node, send_list in send_eth
+ }
+ send_eth_sorted = sorted(send_eth_dict.values(), key=lambda x: x[0].node_id)
+ varsWritten = sorted(varsWrittenSet, key=lambda x: (x.variable.name, x.node.node_id))
+
+ info = ["Reentrancy in ", func, ":\n"]
+ info += ["\tExternal calls:\n"]
+ for call_node, calls_list in calls_sorted:
+ info += ["\t- ", call_node, "\n"]
+ for c in calls_list:
+ if c != call_node:
+ info += ["\t\t- ", c, "\n"]
+
+ calls_node_ids = {call_node.node_id for call_node, _ in calls_sorted}
+ send_eth_node_ids = {send_node.node_id for send_node, _ in send_eth_sorted}
+ if calls_node_ids != send_eth_node_ids and send_eth_sorted:
+ info += ["\tExternal calls sending eth:\n"]
+ for send_node, send_list in send_eth_sorted:
+ info += ["\t- ", send_node, "\n"]
+ for s in send_list:
+ if s != send_node:
+ info += ["\t\t- ", s, "\n"]
+
+ info += ["\tState variables written after the call(s):\n"]
+ for finding_value in varsWritten:
+ info += ["\t- ", finding_value.node, "\n"]
+ for other_node in finding_value.nodes:
+ if other_node != finding_value.node:
+ info += ["\t\t- ", other_node, "\n"]
+ if finding_value.cross_functions:
+ info += [
+ "\t",
+ finding_value.variable,
+ " can be used in cross function reentrancies:\n",
+ ]
+ for cross in finding_value.cross_functions:
+ info += ["\t- ", cross, "\n"]
+
+ res = self.generate_result(info)
+ res.add(func)
+ for call_node, calls_list in calls_sorted:
+ res.add(call_node, {"underlying_type": "external_calls"})
+ for c in calls_list:
+ if c != call_node:
+ res.add(c, {"underlying_type": "external_calls_sending_eth"})
+
+ if calls_node_ids != send_eth_node_ids:
+ for send_node, send_list in send_eth_sorted:
+ res.add(send_node, {"underlying_type": "external_calls_sending_eth"})
+ for s in send_list:
+ if s != send_node:
+ res.add(s, {"underlying_type": "external_calls_sending_eth"})
+
+ for finding_value in varsWritten:
+ res.add(
+ finding_value.node,
+ {
+ "underlying_type": "variables_written",
+ "variable_name": finding_value.variable.name,
+ },
+ )
+ for other_node in finding_value.nodes:
+ if other_node != finding_value.node:
+ res.add(
+ other_node,
+ {
+ "underlying_type": "variables_written",
+ "variable_name": finding_value.variable.name,
+ },
+ )
+ results.append(res)
+
+ return results
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_DAO_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_DAO_sol__0.txt
new file mode 100644
index 0000000000..2d6c2b8204
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_DAO_sol__0.txt
@@ -0,0 +1,72 @@
+Reentrancy in TokenCreation.refund() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#318-332):
+ External calls:
+ - extraBalance.balance >= extraBalance.accumulatedInput() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#321)
+ - extraBalance.payOut(address(this),extraBalance.accumulatedInput()) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#322)
+ - msg.sender.call.value(weiGiven[msg.sender])() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#325)
+ External calls sending eth:
+ - msg.sender.call.value(weiGiven[msg.sender])() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#325)
+ State variables written after the call(s):
+ - weiGiven[msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#329)
+ TokenCreationInterface.weiGiven (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#251) can be used in cross function reentrancies:
+ - TokenCreation.createTokenProxy(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#299-316)
+ - TokenCreation.refund() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#318-332)
+
+Reentrancy in DAO.executeProposal(uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#853-937):
+ External calls:
+ - ! isRecipientAllowed(p.recipient) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#881)
+ - allowedRecipients[_recipient] || (_recipient == address(extraBalance) && totalRewardToken > extraBalance.accumulatedInput()) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1159-1163)
+ - ! p.recipient.call.value(p.amount)(_transactionData) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#915)
+ External calls sending eth:
+ - ! p.creator.send(p.proposalDeposit) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#904)
+ - ! p.recipient.call.value(p.amount)(_transactionData) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#915)
+ State variables written after the call(s):
+ - p.proposalPassed = true (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#918)
+ DAOInterface.proposals (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#394) can be used in cross function reentrancies:
+ - DAO.DAO(address,DAO_Creator,uint256,uint256,uint256,address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#702-726)
+ - DAO.checkProposalCode(uint256,address,uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#809-817)
+ - DAO.closeProposal(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#940-945)
+ - DAO.executeProposal(uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#853-937)
+ - DAO.getNewDAOAddress(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1204-1206)
+ - DAO.isBlocked(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1208-1218)
+ - DAO.newProposal(address,uint256,string,bytes,uint256,bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#741-806)
+ - DAO.numberOfProposals() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1199-1202)
+ - DAOInterface.proposals (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#394)
+ - DAO.splitDAO(uint256,address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#947-1020)
+ - DAO.vote(uint256,bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#820-850)
+ - closeProposal(_proposalID) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#933)
+ - p.open = false (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#944)
+ DAOInterface.proposals (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#394) can be used in cross function reentrancies:
+ - DAO.DAO(address,DAO_Creator,uint256,uint256,uint256,address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#702-726)
+ - DAO.checkProposalCode(uint256,address,uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#809-817)
+ - DAO.closeProposal(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#940-945)
+ - DAO.executeProposal(uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#853-937)
+ - DAO.getNewDAOAddress(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1204-1206)
+ - DAO.isBlocked(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1208-1218)
+ - DAO.newProposal(address,uint256,string,bytes,uint256,bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#741-806)
+ - DAO.numberOfProposals() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1199-1202)
+ - DAOInterface.proposals (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#394)
+ - DAO.splitDAO(uint256,address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#947-1020)
+ - DAO.vote(uint256,bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#820-850)
+ - rewardToken[address(this)] += p.amount (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#928)
+ DAOInterface.rewardToken (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#410) can be used in cross function reentrancies:
+ - DAO.changeProposalDeposit(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1139-1146)
+ - DAO.executeProposal(uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#853-937)
+ - DAO.minQuorum(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1174-1178)
+ - DAO.newContract(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1022-1034)
+ - DAO.retrieveDAOReward(bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1037-1057)
+ - DAOInterface.rewardToken (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#410)
+ - DAO.splitDAO(uint256,address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#947-1020)
+ - closeProposal(_proposalID) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#933)
+ - sumOfProposalDeposits -= p.proposalDeposit (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#943)
+ DAOInterface.sumOfProposalDeposits (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#436) can be used in cross function reentrancies:
+ - DAO.actualBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1169-1171)
+ - DAO.closeProposal(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#940-945)
+ - DAO.newProposal(address,uint256,string,bytes,uint256,bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#741-806)
+ - DAO.splitDAO(uint256,address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#947-1020)
+ - totalRewardToken += p.amount (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#929)
+ DAOInterface.totalRewardToken (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#412) can be used in cross function reentrancies:
+ - DAO.executeProposal(uint256,bytes) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#853-937)
+ - DAO.isRecipientAllowed(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1158-1167)
+ - DAO.retrieveDAOReward(bool) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#1037-1057)
+ - DAOInterface.totalRewardToken (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/DAO.sol#412)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_indirect_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_indirect_sol__0.txt
new file mode 100644
index 0000000000..4562e1bd7d
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_indirect_sol__0.txt
@@ -0,0 +1,15 @@
+Reentrancy in Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#22-29):
+ External calls:
+ - require(bool)(Token(token).transfer(msg.sender,token_deposed[token][msg.sender])) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#24)
+ External calls sending eth:
+ - msg.sender.transfer(eth_deposed[token][msg.sender]) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#23)
+ State variables written after the call(s):
+ - eth_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#26)
+ Reentrancy.eth_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#10) can be used in cross function reentrancies:
+ - Reentrancy.deposit_eth(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#13-15)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#22-29)
+ - token_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#27)
+ Reentrancy.token_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#11) can be used in cross function reentrancies:
+ - Reentrancy.deposit_token(address,uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#17-20)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy_indirect.sol#22-29)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_sol__0.txt
new file mode 100644
index 0000000000..81143018c6
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_sol__0.txt
@@ -0,0 +1,32 @@
+Reentrancy in Reentrancy.withdrawBalance_nested() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#74-80):
+ External calls:
+ - msg.sender.call.value(amount / 2)() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#77)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#78)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#15-22)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#24-31)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#33-41)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#43-50)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#52-60)
+ - Reentrancy.withdrawBalance_fixed_4() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#61-72)
+ - Reentrancy.withdrawBalance_nested() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#74-80)
+
+Reentrancy in Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#24-31):
+ External calls:
+ - ! (msg.sender.call.value(userBalance[msg.sender])()) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#27)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#30)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#15-22)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#24-31)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#33-41)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#43-50)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#52-60)
+ - Reentrancy.withdrawBalance_fixed_4() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#61-72)
+ - Reentrancy.withdrawBalance_nested() (tests/e2e/detectors/test_data/reentrancy-eth/0.4.25/reentrancy.sol#74-80)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_indirect_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_indirect_sol__0.txt
new file mode 100644
index 0000000000..eea2c47116
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_indirect_sol__0.txt
@@ -0,0 +1,15 @@
+Reentrancy in Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#22-29):
+ External calls:
+ - require(bool)(Token(token).transfer(msg.sender,token_deposed[token][msg.sender])) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#24)
+ External calls sending eth:
+ - msg.sender.transfer(eth_deposed[token][msg.sender]) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#23)
+ State variables written after the call(s):
+ - eth_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#26)
+ Reentrancy.eth_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#10) can be used in cross function reentrancies:
+ - Reentrancy.deposit_eth(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#13-15)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#22-29)
+ - token_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#27)
+ Reentrancy.token_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#11) can be used in cross function reentrancies:
+ - Reentrancy.deposit_token(address,uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#17-20)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy_indirect.sol#22-29)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_sol__0.txt
new file mode 100644
index 0000000000..d77726404c
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_sol__0.txt
@@ -0,0 +1,28 @@
+Reentrancy in Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#25-33):
+ External calls:
+ - (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#28)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#32)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#15-23)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#25-33)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#35-44)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#46-53)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#55-64)
+
+Reentrancy in Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#55-64):
+ External calls:
+ - (ret,mem) = msg.sender.call.value(amount)() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#60)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = amount (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#62)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#15-23)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#25-33)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#35-44)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#46-53)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.5.16/reentrancy.sol#55-64)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_indirect_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_indirect_sol__0.txt
new file mode 100644
index 0000000000..c4cda5f345
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_indirect_sol__0.txt
@@ -0,0 +1,15 @@
+Reentrancy in Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#22-29):
+ External calls:
+ - require(bool)(Token(token).transfer(msg.sender,token_deposed[token][msg.sender])) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#24)
+ External calls sending eth:
+ - msg.sender.transfer(eth_deposed[token][msg.sender]) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#23)
+ State variables written after the call(s):
+ - eth_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#26)
+ Reentrancy.eth_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#10) can be used in cross function reentrancies:
+ - Reentrancy.deposit_eth(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#13-15)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#22-29)
+ - token_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#27)
+ Reentrancy.token_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#11) can be used in cross function reentrancies:
+ - Reentrancy.deposit_token(address,uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#17-20)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy_indirect.sol#22-29)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_sol__0.txt
new file mode 100644
index 0000000000..a3e4ec83d4
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_sol__0.txt
@@ -0,0 +1,28 @@
+Reentrancy in Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#55-64):
+ External calls:
+ - (ret,mem) = msg.sender.call.value(amount)() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#60)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = amount (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#62)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#15-23)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#25-33)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#35-44)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#46-53)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#55-64)
+
+Reentrancy in Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#25-33):
+ External calls:
+ - (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#28)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#32)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#15-23)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#25-33)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#35-44)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#46-53)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.6.11/reentrancy.sol#55-64)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_indirect_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_indirect_sol__0.txt
new file mode 100644
index 0000000000..2be2ab3bb5
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_indirect_sol__0.txt
@@ -0,0 +1,15 @@
+Reentrancy in Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#22-29):
+ External calls:
+ - require(bool)(Token(token).transfer(msg.sender,token_deposed[token][msg.sender])) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#24)
+ External calls sending eth:
+ - msg.sender.transfer(eth_deposed[token][msg.sender]) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#23)
+ State variables written after the call(s):
+ - eth_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#26)
+ Reentrancy.eth_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#10) can be used in cross function reentrancies:
+ - Reentrancy.deposit_eth(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#13-15)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#22-29)
+ - token_deposed[token][msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#27)
+ Reentrancy.token_deposed (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#11) can be used in cross function reentrancies:
+ - Reentrancy.deposit_token(address,uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#17-20)
+ - Reentrancy.withdraw(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy_indirect.sol#22-29)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_sol__0.txt
new file mode 100644
index 0000000000..04098bd3fb
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_sol__0.txt
@@ -0,0 +1,28 @@
+Reentrancy in Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#55-64):
+ External calls:
+ - (ret,mem) = msg.sender.call{value: amount}() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#60)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = amount (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#62)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#15-23)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#25-33)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#35-44)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#46-53)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#55-64)
+
+Reentrancy in Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#25-33):
+ External calls:
+ - (ret,mem) = msg.sender.call{value: userBalance[msg.sender]}() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#28)
+ State variables written after the call(s):
+ - userBalance[msg.sender] = 0 (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#32)
+ Reentrancy.userBalance (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#4) can be used in cross function reentrancies:
+ - Reentrancy.addToBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#10-12)
+ - Reentrancy.constructor() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#15-23)
+ - Reentrancy.getBalance(address) (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#6-8)
+ - Reentrancy.withdrawBalance() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#25-33)
+ - Reentrancy.withdrawBalance_fixed() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#35-44)
+ - Reentrancy.withdrawBalance_fixed_2() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#46-53)
+ - Reentrancy.withdrawBalance_fixed_3() (tests/e2e/detectors/test_data/reentrancy-eth/0.7.6/reentrancy.sol#55-64)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_filtered_comments_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_filtered_comments_sol__0.txt
new file mode 100644
index 0000000000..4485754d3d
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_filtered_comments_sol__0.txt
@@ -0,0 +1,9 @@
+Reentrancy in TestWithBug.withdraw(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol#8-12):
+ External calls:
+ - Receiver(msg.sender).send_funds{value: amount}() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol#10)
+ State variables written after the call(s):
+ - balances[msg.sender] -= amount (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol#11)
+ TestWithBug.balances (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol#6) can be used in cross function reentrancies:
+ - TestWithBug.withdraw(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol#8-12)
+ - TestWithBug.withdrawFiltered(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol#15-19)
+
diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_with_non_reentrant_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_with_non_reentrant_sol__0.txt
new file mode 100644
index 0000000000..ab510d0e54
--- /dev/null
+++ b/tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_with_non_reentrant_sol__0.txt
@@ -0,0 +1,32 @@
+Reentrancy in TestWithBugInternal.withdraw_internal(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#62-66):
+ External calls:
+ - Receiver(msg.sender).send_funds{value: amount}() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#64)
+ State variables written after the call(s):
+ - balances[msg.sender] -= amount (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#65)
+ TestWithBugInternal.balances (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#52) can be used in cross function reentrancies:
+ - TestWithBugInternal.withdraw_all_internal() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#72-76)
+
+Reentrancy in TestWithBug.withdraw(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#13-17):
+ External calls:
+ - Receiver(msg.sender).send_funds{value: amount}() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#15)
+ State variables written after the call(s):
+ - balances[msg.sender] -= amount (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#16)
+ TestWithBug.balances (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#7) can be used in cross function reentrancies:
+ - TestWithBug.withdraw_all() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#19-23)
+
+Reentrancy in TestBugWithPublicVariable.withdraw_internal(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#122-126):
+ External calls:
+ - Receiver(msg.sender).send_funds{value: amount}() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#124)
+ State variables written after the call(s):
+ - balances[msg.sender] -= amount (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#125)
+ TestBugWithPublicVariable.balances (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#112) can be used in cross function reentrancies:
+ - TestBugWithPublicVariable.balances (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#112)
+
+Reentrancy in TestWithBugNonReentrantRead.withdraw(uint256) (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#138-142):
+ External calls:
+ - Receiver(msg.sender).send_funds{value: amount}() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#140)
+ State variables written after the call(s):
+ - balances[msg.sender] -= amount (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#141)
+ TestWithBugNonReentrantRead.balances (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#132) can be used in cross function reentrancies:
+ - TestWithBugNonReentrantRead.read() (tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_with_non_reentrant.sol#146-149)
+
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol
new file mode 100644
index 0000000000..6f03a624ca
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol
@@ -0,0 +1,1242 @@
+
+//
+// Modified version to be compiler with sol 0.4
+//
+/*
+
+- Bytecode Verification performed was compared on second iteration -
+
+This file is part of the DAO.
+
+The DAO is free software: you can redistribute it and/or modify
+it under the terms of the GNU lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The DAO is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU lesser General Public License for more details.
+
+You should have received a copy of the GNU lesser General Public License
+along with the DAO. If not, see .
+*/
+
+
+/*
+Basic, standardized Token contract with no "premine". Defines the functions to
+check token balances, send tokens, send tokens on behalf of a 3rd party and the
+corresponding approval process. Tokens need to be created by a derived
+contract (e.g. TokenCreation.sol).
+
+Thank you ConsenSys, this contract originated from:
+https://github.com/ConsenSys/Tokens/blob/master/Token_Contracts/contracts/Standard_Token.sol
+Which is itself based on the Ethereum standardized contract APIs:
+https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs
+*/
+
+/// @title Standard Token Contract.
+
+contract TokenInterface {
+ mapping (address => uint256) balances;
+ mapping (address => mapping (address => uint256)) allowed;
+
+ /// Total amount of tokens
+ uint256 public totalSupply;
+
+ /// @param _owner The address from which the balance will be retrieved
+ /// @return The balance
+ function balanceOf(address _owner) constant returns (uint256 balance);
+
+ /// @notice Send `_amount` tokens to `_to` from `msg.sender`
+ /// @param _to The address of the recipient
+ /// @param _amount The amount of tokens to be transferred
+ /// @return Whether the transfer was successful or not
+ function transfer(address _to, uint256 _amount) returns (bool success);
+
+ /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it
+ /// is approved by `_from`
+ /// @param _from The address of the origin of the transfer
+ /// @param _to The address of the recipient
+ /// @param _amount The amount of tokens to be transferred
+ /// @return Whether the transfer was successful or not
+ function transferFrom(address _from, address _to, uint256 _amount) returns (bool success);
+
+ /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
+ /// its behalf
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @param _amount The amount of tokens to be approved for transfer
+ /// @return Whether the approval was successful or not
+ function approve(address _spender, uint256 _amount) returns (bool success);
+
+ /// @param _owner The address of the account owning tokens
+ /// @param _spender The address of the account able to transfer the tokens
+ /// @return Amount of remaining tokens of _owner that _spender is allowed
+ /// to spend
+ function allowance(
+ address _owner,
+ address _spender
+ ) constant returns (uint256 remaining);
+
+ event Transfer(address indexed _from, address indexed _to, uint256 _amount);
+ event Approval(
+ address indexed _owner,
+ address indexed _spender,
+ uint256 _amount
+ );
+}
+
+
+contract Token is TokenInterface {
+ // Protects users by preventing the execution of method calls that
+ // inadvertently also transferred ether
+ modifier noEther() {if (msg.value > 0) throw; _;}
+
+ function balanceOf(address _owner) constant returns (uint256 balance) {
+ return balances[_owner];
+ }
+
+ function transfer(address _to, uint256 _amount) noEther returns (bool success) {
+ if (balances[msg.sender] >= _amount && _amount > 0) {
+ balances[msg.sender] -= _amount;
+ balances[_to] += _amount;
+ Transfer(msg.sender, _to, _amount);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function transferFrom(
+ address _from,
+ address _to,
+ uint256 _amount
+ ) noEther returns (bool success) {
+
+ if (balances[_from] >= _amount
+ && allowed[_from][msg.sender] >= _amount
+ && _amount > 0) {
+
+ balances[_to] += _amount;
+ balances[_from] -= _amount;
+ allowed[_from][msg.sender] -= _amount;
+ Transfer(_from, _to, _amount);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function approve(address _spender, uint256 _amount) returns (bool success) {
+ allowed[msg.sender][_spender] = _amount;
+ Approval(msg.sender, _spender, _amount);
+ return true;
+ }
+
+ function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
+ return allowed[_owner][_spender];
+ }
+}
+
+
+/*
+This file is part of the DAO.
+
+The DAO is free software: you can redistribute it and/or modify
+it under the terms of the GNU lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The DAO is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU lesser General Public License for more details.
+
+You should have received a copy of the GNU lesser General Public License
+along with the DAO. If not, see .
+*/
+
+
+/*
+Basic account, used by the DAO contract to separately manage both the rewards
+and the extraBalance accounts.
+*/
+
+contract ManagedAccountInterface {
+ // The only address with permission to withdraw from this account
+ address public owner;
+ // If true, only the owner of the account can receive ether from it
+ bool public payOwnerOnly;
+ // The sum of ether (in wei) which has been sent to this contract
+ uint public accumulatedInput;
+
+ /// @notice Sends `_amount` of wei to _recipient
+ /// @param _amount The amount of wei to send to `_recipient`
+ /// @param _recipient The address to receive `_amount` of wei
+ /// @return True if the send completed
+ function payOut(address _recipient, uint _amount) returns (bool);
+
+ event PayOut(address indexed _recipient, uint _amount);
+}
+
+
+contract ManagedAccount is ManagedAccountInterface{
+
+ // The constructor sets the owner of the account
+ function ManagedAccount(address _owner, bool _payOwnerOnly) {
+ owner = _owner;
+ payOwnerOnly = _payOwnerOnly;
+ }
+
+ // When the contract receives a transaction without data this is called.
+ // It counts the amount of ether it receives and stores it in
+ // accumulatedInput.
+ function() {
+ accumulatedInput += msg.value;
+ }
+
+ function payOut(address _recipient, uint _amount) returns (bool) {
+ if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner))
+ throw;
+ if (_recipient.call.value(_amount)()) {
+ PayOut(_recipient, _amount);
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+/*
+This file is part of the DAO.
+
+The DAO is free software: you can redistribute it and/or modify
+it under the terms of the GNU lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The DAO is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU lesser General Public License for more details.
+
+You should have received a copy of the GNU lesser General Public License
+along with the DAO. If not, see .
+*/
+
+
+/*
+ * Token Creation contract, used by the DAO to create its tokens and initialize
+ * its ether. Feel free to modify the divisor method to implement different
+ * Token Creation parameters
+*/
+
+
+contract TokenCreationInterface {
+
+ // End of token creation, in Unix time
+ uint public closingTime;
+ // Minimum fueling goal of the token creation, denominated in tokens to
+ // be created
+ uint public minTokensToCreate;
+ // True if the DAO reached its minimum fueling goal, false otherwise
+ bool public isFueled;
+ // For DAO splits - if privateCreation is 0, then it is a public token
+ // creation, otherwise only the address stored in privateCreation is
+ // allowed to create tokens
+ address public privateCreation;
+ // hold extra ether which has been sent after the DAO token
+ // creation rate has increased
+ ManagedAccount public extraBalance;
+ // tracks the amount of wei given from each contributor (used for refund)
+ mapping (address => uint256) weiGiven;
+
+ /// @dev Constructor setting the minimum fueling goal and the
+ /// end of the Token Creation
+ /// @param _minTokensToCreate Minimum fueling goal in number of
+ /// Tokens to be created
+ /// @param _closingTime Date (in Unix time) of the end of the Token Creation
+ /// @param _privateCreation Zero means that the creation is public. A
+ /// non-zero address represents the only address that can create Tokens
+ /// (the address can also create Tokens on behalf of other accounts)
+ // This is the constructor: it can not be overloaded so it is commented out
+ // function TokenCreation(
+ // uint _minTokensTocreate,
+ // uint _closingTime,
+ // address _privateCreation
+ // );
+
+ /// @notice Create Token with `_tokenHolder` as the initial owner of the Token
+ /// @param _tokenHolder The address of the Tokens's recipient
+ /// @return Whether the token creation was successful
+ function createTokenProxy(address _tokenHolder) payable returns (bool success);
+
+ /// @notice Refund `msg.sender` in the case the Token Creation did
+ /// not reach its minimum fueling goal
+ function refund();
+
+ /// @return The divisor used to calculate the token creation rate during
+ /// the creation phase
+ function divisor() constant returns (uint divisor);
+
+ event FuelingToDate(uint value);
+ event CreatedToken(address indexed to, uint amount);
+ event Refund(address indexed to, uint value);
+}
+
+
+contract TokenCreation is TokenCreationInterface, Token {
+ function TokenCreation(
+ uint _minTokensToCreate,
+ uint _closingTime,
+ address _privateCreation) {
+
+ closingTime = _closingTime;
+ minTokensToCreate = _minTokensToCreate;
+ privateCreation = _privateCreation;
+ extraBalance = new ManagedAccount(address(this), true);
+ }
+
+ function createTokenProxy(address _tokenHolder) payable returns (bool success) {
+ if (now < closingTime && msg.value > 0
+ && (privateCreation == 0 || privateCreation == msg.sender)) {
+
+ uint token = (msg.value * 20) / divisor();
+ extraBalance.call.value(msg.value - token)();
+ balances[_tokenHolder] += token;
+ totalSupply += token;
+ weiGiven[_tokenHolder] += msg.value;
+ CreatedToken(_tokenHolder, token);
+ if (totalSupply >= minTokensToCreate && !isFueled) {
+ isFueled = true;
+ FuelingToDate(totalSupply);
+ }
+ return true;
+ }
+ throw;
+ }
+
+ function refund() noEther {
+ if (now > closingTime && !isFueled) {
+ // Get extraBalance - will only succeed when called for the first time
+ if (extraBalance.balance >= extraBalance.accumulatedInput())
+ extraBalance.payOut(address(this), extraBalance.accumulatedInput());
+
+ // Execute refund
+ if (msg.sender.call.value(weiGiven[msg.sender])()) {
+ Refund(msg.sender, weiGiven[msg.sender]);
+ totalSupply -= balances[msg.sender];
+ balances[msg.sender] = 0;
+ weiGiven[msg.sender] = 0;
+ }
+ }
+ }
+
+ function divisor() constant returns (uint divisor) {
+ // The number of (base unit) tokens per wei is calculated
+ // as `msg.value` * 20 / `divisor`
+ // The fueling period starts with a 1:1 ratio
+ if (closingTime - 2 weeks > now) {
+ return 20;
+ // Followed by 10 days with a daily creation rate increase of 5%
+ } else if (closingTime - 4 days > now) {
+ return (20 + (now - (closingTime - 2 weeks)) / (1 days));
+ // The last 4 days there is a constant creation rate ratio of 1:1.5
+ } else {
+ return 30;
+ }
+ }
+}
+/*
+This file is part of the DAO.
+
+The DAO is free software: you can redistribute it and/or modify
+it under the terms of the GNU lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+The DAO is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU lesser General Public License for more details.
+
+You should have received a copy of the GNU lesser General Public License
+along with the DAO. If not, see .
+*/
+
+
+/*
+Standard smart contract for a Decentralized Autonomous Organization (DAO)
+to automate organizational governance and decision-making.
+*/
+
+
+contract DAOInterface {
+
+ // The amount of days for which people who try to participate in the
+ // creation by calling the fallback function will still get their ether back
+ uint constant creationGracePeriod = 40 days;
+ // The minimum debate period that a generic proposal can have
+ uint constant minProposalDebatePeriod = 2 weeks;
+ // The minimum debate period that a split proposal can have
+ uint constant minSplitDebatePeriod = 1 weeks;
+ // Period of days inside which it's possible to execute a DAO split
+ uint constant splitExecutionPeriod = 27 days;
+ // Period of time after which the minimum Quorum is halved
+ uint constant quorumHalvingPeriod = 25 weeks;
+ // Period after which a proposal is closed
+ // (used in the case `executeProposal` fails because it throws)
+ uint constant executeProposalPeriod = 10 days;
+ // Denotes the maximum proposal deposit that can be given. It is given as
+ // a fraction of total Ether spent plus balance of the DAO
+ uint constant maxDepositDivisor = 100;
+
+ // Proposals to spend the DAO's ether or to choose a new Curator
+ Proposal[] public proposals;
+ // The quorum needed for each proposal is partially calculated by
+ // totalSupply / minQuorumDivisor
+ uint public minQuorumDivisor;
+ // The unix time of the last time quorum was reached on a proposal
+ uint public lastTimeMinQuorumMet;
+
+ // Address of the curator
+ address public curator;
+ // The whitelist: List of addresses the DAO is allowed to send ether to
+ mapping (address => bool) public allowedRecipients;
+
+ // Tracks the addresses that own Reward Tokens. Those addresses can only be
+ // DAOs that have split from the original DAO. Conceptually, Reward Tokens
+ // represent the proportion of the rewards that the DAO has the right to
+ // receive. These Reward Tokens are generated when the DAO spends ether.
+ mapping (address => uint) public rewardToken;
+ // Total supply of rewardToken
+ uint public totalRewardToken;
+
+ // The account used to manage the rewards which are to be distributed to the
+ // DAO Token Holders of this DAO
+ ManagedAccount public rewardAccount;
+
+ // The account used to manage the rewards which are to be distributed to
+ // any DAO that holds Reward Tokens
+ ManagedAccount public DAOrewardAccount;
+
+ // Amount of rewards (in wei) already paid out to a certain DAO
+ mapping (address => uint) public DAOpaidOut;
+
+ // Amount of rewards (in wei) already paid out to a certain address
+ mapping (address => uint) public paidOut;
+ // Map of addresses blocked during a vote (not allowed to transfer DAO
+ // tokens). The address points to the proposal ID.
+ mapping (address => uint) public blocked;
+
+ // The minimum deposit (in wei) required to submit any proposal that is not
+ // requesting a new Curator (no deposit is required for splits)
+ uint public proposalDeposit;
+
+ // the accumulated sum of all current proposal deposits
+ uint sumOfProposalDeposits;
+
+ // Contract that is able to create a new DAO (with the same code as
+ // this one), used for splits
+ DAO_Creator public daoCreator;
+
+ // A proposal with `newCurator == false` represents a transaction
+ // to be issued by this DAO
+ // A proposal with `newCurator == true` represents a DAO split
+ struct Proposal {
+ // The address where the `amount` will go to if the proposal is accepted
+ // or if `newCurator` is true, the proposed Curator of
+ // the new DAO).
+ address recipient;
+ // The amount to transfer to `recipient` if the proposal is accepted.
+ uint amount;
+ // A plain text description of the proposal
+ string description;
+ // A unix timestamp, denoting the end of the voting period
+ uint votingDeadline;
+ // True if the proposal's votes have yet to be counted, otherwise False
+ bool open;
+ // True if quorum has been reached, the votes have been counted, and
+ // the majority said yes
+ bool proposalPassed;
+ // A hash to check validity of a proposal
+ bytes32 proposalHash;
+ // Deposit in wei the creator added when submitting their proposal. It
+ // is taken from the msg.value of a newProposal call.
+ uint proposalDeposit;
+ // True if this proposal is to assign a new Curator
+ bool newCurator;
+ // Data needed for splitting the DAO
+
+ SplitData[][] splitData2;
+ SplitData[] splitData;
+ // Number of Tokens in favor of the proposal
+ uint yea;
+ // Number of Tokens opposed to the proposal
+ uint nay;
+ // Simple mapping to check if a shareholder has voted for it
+ mapping (address => bool) votedYes;
+ // Simple mapping to check if a shareholder has voted against it
+ mapping (address => bool) votedNo;
+ // Address of the shareholder who created the proposal
+ address creator;
+ }
+
+ // Used only in the case of a newCurator proposal.
+ struct SplitData {
+ // The balance of the current DAO minus the deposit at the time of split
+ uint splitBalance;
+ // The total amount of DAO Tokens in existence at the time of split.
+ uint totalSupply;
+ // Amount of Reward Tokens owned by the DAO at the time of split.
+ uint rewardToken;
+ // The new DAO contract created at the time of split.
+ DAO newDAO;
+ }
+
+ // Used to restrict access to certain functions to only DAO Token Holders
+ modifier onlyTokenholders {_;}
+
+ /// @dev Constructor setting the Curator and the address
+ /// for the contract able to create another DAO as well as the parameters
+ /// for the DAO Token Creation
+ /// @param _curator The Curator
+ /// @param _daoCreator The contract able to (re)create this DAO
+ /// @param _proposalDeposit The deposit to be paid for a regular proposal
+ /// @param _minTokensToCreate Minimum required wei-equivalent tokens
+ /// to be created for a successful DAO Token Creation
+ /// @param _closingTime Date (in Unix time) of the end of the DAO Token Creation
+ /// @param _privateCreation If zero the DAO Token Creation is open to public, a
+ /// non-zero address means that the DAO Token Creation is only for the address
+ // This is the constructor: it can not be overloaded so it is commented out
+ // function DAO(
+ // address _curator,
+ // DAO_Creator _daoCreator,
+ // uint _proposalDeposit,
+ // uint _minTokensToCreate,
+ // uint _closingTime,
+ // address _privateCreation
+ // );
+
+ /// @notice Create Token with `msg.sender` as the beneficiary
+ /// @return Whether the token creation was successful
+ function ();
+
+
+ /// @dev This function is used to send ether back
+ /// to the DAO, it can also be used to receive payments that should not be
+ /// counted as rewards (donations, grants, etc.)
+ /// @return Whether the DAO received the ether successfully
+ function receiveEther() returns(bool);
+
+ /// @notice `msg.sender` creates a proposal to send `_amount` Wei to
+ /// `_recipient` with the transaction data `_transactionData`. If
+ /// `_newCurator` is true, then this is a proposal that splits the
+ /// DAO and sets `_recipient` as the new DAO's Curator.
+ /// @param _recipient Address of the recipient of the proposed transaction
+ /// @param _amount Amount of wei to be sent with the proposed transaction
+ /// @param _description String describing the proposal
+ /// @param _transactionData Data of the proposed transaction
+ /// @param _debatingPeriod Time used for debating a proposal, at least 2
+ /// weeks for a regular proposal, 10 days for new Curator proposal
+ /// @param _newCurator Bool defining whether this proposal is about
+ /// a new Curator or not
+ /// @return The proposal ID. Needed for voting on the proposal
+ function newProposal(
+ address _recipient,
+ uint _amount,
+ string _description,
+ bytes _transactionData,
+ uint _debatingPeriod,
+ bool _newCurator
+ ) onlyTokenholders returns (uint _proposalID);
+
+ /// @notice Check that the proposal with the ID `_proposalID` matches the
+ /// transaction which sends `_amount` with data `_transactionData`
+ /// to `_recipient`
+ /// @param _proposalID The proposal ID
+ /// @param _recipient The recipient of the proposed transaction
+ /// @param _amount The amount of wei to be sent in the proposed transaction
+ /// @param _transactionData The data of the proposed transaction
+ /// @return Whether the proposal ID matches the transaction data or not
+ function checkProposalCode(
+ uint _proposalID,
+ address _recipient,
+ uint _amount,
+ bytes _transactionData
+ ) constant returns (bool _codeChecksOut);
+
+ /// @notice Vote on proposal `_proposalID` with `_supportsProposal`
+ /// @param _proposalID The proposal ID
+ /// @param _supportsProposal Yes/No - support of the proposal
+ /// @return The vote ID.
+ function vote(
+ uint _proposalID,
+ bool _supportsProposal
+ ) onlyTokenholders returns (uint _voteID);
+
+ /// @notice Checks whether proposal `_proposalID` with transaction data
+ /// `_transactionData` has been voted for or rejected, and executes the
+ /// transaction in the case it has been voted for.
+ /// @param _proposalID The proposal ID
+ /// @param _transactionData The data of the proposed transaction
+ /// @return Whether the proposed transaction has been executed or not
+ function executeProposal(
+ uint _proposalID,
+ bytes _transactionData
+ ) returns (bool _success);
+
+ /// @notice ATTENTION! I confirm to move my remaining ether to a new DAO
+ /// with `_newCurator` as the new Curator, as has been
+ /// proposed in proposal `_proposalID`. This will burn my tokens. This can
+ /// not be undone and will split the DAO into two DAO's, with two
+ /// different underlying tokens.
+ /// @param _proposalID The proposal ID
+ /// @param _newCurator The new Curator of the new DAO
+ /// @dev This function, when called for the first time for this proposal,
+ /// will create a new DAO and send the sender's portion of the remaining
+ /// ether and Reward Tokens to the new DAO. It will also burn the DAO Tokens
+ /// of the sender.
+ function splitDAO(
+ uint _proposalID,
+ address _newCurator
+ ) returns (bool _success);
+
+ /// @dev can only be called by the DAO itself through a proposal
+ /// updates the contract of the DAO by sending all ether and rewardTokens
+ /// to the new DAO. The new DAO needs to be approved by the Curator
+ /// @param _newContract the address of the new contract
+ function newContract(address _newContract);
+
+
+ /// @notice Add a new possible recipient `_recipient` to the whitelist so
+ /// that the DAO can send transactions to them (using proposals)
+ /// @param _recipient New recipient address
+ /// @dev Can only be called by the current Curator
+ /// @return Whether successful or not
+ function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success);
+
+
+ /// @notice Change the minimum deposit required to submit a proposal
+ /// @param _proposalDeposit The new proposal deposit
+ /// @dev Can only be called by this DAO (through proposals with the
+ /// recipient being this DAO itself)
+ function changeProposalDeposit(uint _proposalDeposit) external;
+
+ /// @notice Move rewards from the DAORewards managed account
+ /// @param _toMembers If true rewards are moved to the actual reward account
+ /// for the DAO. If not then it's moved to the DAO itself
+ /// @return Whether the call was successful
+ function retrieveDAOReward(bool _toMembers) external returns (bool _success);
+
+ /// @notice Get my portion of the reward that was sent to `rewardAccount`
+ /// @return Whether the call was successful
+ function getMyReward() returns(bool _success);
+
+ /// @notice Withdraw `_account`'s portion of the reward from `rewardAccount`
+ /// to `_account`'s balance
+ /// @return Whether the call was successful
+ function withdrawRewardFor(address _account) internal returns (bool _success);
+
+ /// @notice Send `_amount` tokens to `_to` from `msg.sender`. Prior to this
+ /// getMyReward() is called.
+ /// @param _to The address of the recipient
+ /// @param _amount The amount of tokens to be transfered
+ /// @return Whether the transfer was successful or not
+ function transferWithoutReward(address _to, uint256 _amount) returns (bool success);
+
+ /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it
+ /// is approved by `_from`. Prior to this getMyReward() is called.
+ /// @param _from The address of the sender
+ /// @param _to The address of the recipient
+ /// @param _amount The amount of tokens to be transfered
+ /// @return Whether the transfer was successful or not
+ function transferFromWithoutReward(
+ address _from,
+ address _to,
+ uint256 _amount
+ ) returns (bool success);
+
+ /// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been
+ /// achieved in 52 weeks
+ /// @return Whether the change was successful or not
+ function halveMinQuorum() returns (bool _success);
+
+ /// @return total number of proposals ever created
+ function numberOfProposals() constant returns (uint _numberOfProposals);
+
+ /// @param _proposalID Id of the new curator proposal
+ /// @return Address of the new DAO
+ function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO);
+
+ /// @param _account The address of the account which is checked.
+ /// @return Whether the account is blocked (not allowed to transfer tokens) or not.
+ function isBlocked(address _account) internal returns (bool);
+
+ /// @notice If the caller is blocked by a proposal whose voting deadline
+ /// has exprired then unblock him.
+ /// @return Whether the account is blocked (not allowed to transfer tokens) or not.
+ function unblockMe() returns (bool);
+
+ event ProposalAdded(
+ uint indexed proposalID,
+ address recipient,
+ uint amount,
+ bool newCurator,
+ string description
+ );
+ event Voted(uint indexed proposalID, bool position, address indexed voter);
+ event ProposalTallied(uint indexed proposalID, bool result, uint quorum);
+ event NewCurator(address indexed _newCurator);
+ event AllowedRecipientChanged(address indexed _recipient, bool _allowed);
+}
+
+// The DAO contract itself
+contract DAO is DAOInterface, Token, TokenCreation {
+
+ // Modifier that allows only shareholders to vote and create new proposals
+ modifier onlyTokenholders {
+ if (balanceOf(msg.sender) == 0) throw;
+ _;
+ }
+
+ function DAO(
+ address _curator,
+ DAO_Creator _daoCreator,
+ uint _proposalDeposit,
+ uint _minTokensToCreate,
+ uint _closingTime,
+ address _privateCreation
+ ) TokenCreation(_minTokensToCreate, _closingTime, _privateCreation) {
+
+ curator = _curator;
+ daoCreator = _daoCreator;
+ proposalDeposit = _proposalDeposit;
+ rewardAccount = new ManagedAccount(address(this), false);
+ DAOrewardAccount = new ManagedAccount(address(this), false);
+ if (address(rewardAccount) == 0)
+ throw;
+ if (address(DAOrewardAccount) == 0)
+ throw;
+ lastTimeMinQuorumMet = now;
+ minQuorumDivisor = 5; // sets the minimal quorum to 20%
+ proposals.length = 1; // avoids a proposal with ID 0 because it is used
+
+ allowedRecipients[address(this)] = true;
+ allowedRecipients[curator] = true;
+ }
+
+ function () {
+/* if (now < closingTime + creationGracePeriod && msg.sender != address(extraBalance))
+ return createTokenProxy(msg.sender);
+ else
+ return receiveEther();*/
+ }
+
+
+ function receiveEther() returns (bool) {
+ return true;
+ }
+
+
+ function newProposal(
+ address _recipient,
+ uint _amount,
+ string _description,
+ bytes _transactionData,
+ uint _debatingPeriod,
+ bool _newCurator
+ ) onlyTokenholders returns (uint _proposalID) {
+
+ // Sanity check
+ if (_newCurator && (
+ _amount != 0
+ || _transactionData.length != 0
+ || _recipient == curator
+ || msg.value > 0
+ || _debatingPeriod < minSplitDebatePeriod)) {
+ throw;
+ } else if (
+ !_newCurator
+ && (!isRecipientAllowed(_recipient) || (_debatingPeriod < minProposalDebatePeriod))
+ ) {
+ throw;
+ }
+
+ if (_debatingPeriod > 8 weeks)
+ throw;
+
+ if (!isFueled
+ || now < closingTime
+ || (msg.value < proposalDeposit && !_newCurator)) {
+
+ throw;
+ }
+
+ if (now + _debatingPeriod < now) // prevents overflow
+ throw;
+
+ // to prevent a 51% attacker to convert the ether into deposit
+ if (msg.sender == address(this))
+ throw;
+
+ _proposalID = proposals.length++;
+ Proposal p = proposals[_proposalID];
+ p.recipient = _recipient;
+ p.amount = _amount;
+ p.description = _description;
+ p.proposalHash = sha3(_recipient, _amount, _transactionData);
+ p.votingDeadline = now + _debatingPeriod;
+ p.open = true;
+ //p.proposalPassed = False; // that's default
+ p.newCurator = _newCurator;
+ if (_newCurator)
+ p.splitData.length++;
+ p.creator = msg.sender;
+ p.proposalDeposit = msg.value;
+
+ sumOfProposalDeposits += msg.value;
+
+ ProposalAdded(
+ _proposalID,
+ _recipient,
+ _amount,
+ _newCurator,
+ _description
+ );
+ }
+
+
+ function checkProposalCode(
+ uint _proposalID,
+ address _recipient,
+ uint _amount,
+ bytes _transactionData
+ ) noEther constant returns (bool _codeChecksOut) {
+ Proposal p = proposals[_proposalID];
+ return p.proposalHash == sha3(_recipient, _amount, _transactionData);
+ }
+
+
+ function vote(
+ uint _proposalID,
+ bool _supportsProposal
+ ) onlyTokenholders noEther returns (uint _voteID) {
+
+ Proposal p = proposals[_proposalID];
+ if (p.votedYes[msg.sender]
+ || p.votedNo[msg.sender]
+ || now >= p.votingDeadline) {
+
+ throw;
+ }
+
+ if (_supportsProposal) {
+ p.yea += balances[msg.sender];
+ p.votedYes[msg.sender] = true;
+ } else {
+ p.nay += balances[msg.sender];
+ p.votedNo[msg.sender] = true;
+ }
+
+ if (blocked[msg.sender] == 0) {
+ blocked[msg.sender] = _proposalID;
+ } else if (p.votingDeadline > proposals[blocked[msg.sender]].votingDeadline) {
+ // this proposal's voting deadline is further into the future than
+ // the proposal that blocks the sender so make it the blocker
+ blocked[msg.sender] = _proposalID;
+ }
+
+ Voted(_proposalID, _supportsProposal, msg.sender);
+ }
+
+
+ function executeProposal(
+ uint _proposalID,
+ bytes _transactionData
+ ) noEther returns (bool _success) {
+
+ Proposal p = proposals[_proposalID];
+
+ uint waitPeriod = p.newCurator
+ ? splitExecutionPeriod
+ : executeProposalPeriod;
+ // If we are over deadline and waiting period, assert proposal is closed
+ if (p.open && now > p.votingDeadline + waitPeriod) {
+ closeProposal(_proposalID);
+ return;
+ }
+
+ // Check if the proposal can be executed
+ if (now < p.votingDeadline // has the voting deadline arrived?
+ // Have the votes been counted?
+ || !p.open
+ // Does the transaction code match the proposal?
+ || p.proposalHash != sha3(p.recipient, p.amount, _transactionData)) {
+
+ throw;
+ }
+
+ // If the curator removed the recipient from the whitelist, close the proposal
+ // in order to free the deposit and allow unblocking of voters
+ if (!isRecipientAllowed(p.recipient)) {
+ closeProposal(_proposalID);
+ p.creator.send(p.proposalDeposit);
+ return;
+ }
+
+ bool proposalCheck = true;
+
+ if (p.amount > actualBalance())
+ proposalCheck = false;
+
+ uint quorum = p.yea + p.nay;
+
+ // require 53% for calling newContract()
+ if (_transactionData.length >= 4 && _transactionData[0] == 0x68
+ && _transactionData[1] == 0x37 && _transactionData[2] == 0xff
+ && _transactionData[3] == 0x1e
+ && quorum < minQuorum(actualBalance() + rewardToken[address(this)])) {
+
+ proposalCheck = false;
+ }
+
+ if (quorum >= minQuorum(p.amount)) {
+ if (!p.creator.send(p.proposalDeposit))
+ throw;
+
+ lastTimeMinQuorumMet = now;
+ // set the minQuorum to 20% again, in the case it has been reached
+ if (quorum > totalSupply / 5)
+ minQuorumDivisor = 5;
+ }
+
+ // Execute result
+ if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) {
+ if (!p.recipient.call.value(p.amount)(_transactionData))
+ throw;
+
+ p.proposalPassed = true;
+ _success = true;
+
+ // only create reward tokens when ether is not sent to the DAO itself and
+ // related addresses. Proxy addresses should be forbidden by the curator.
+ if (p.recipient != address(this) && p.recipient != address(rewardAccount)
+ && p.recipient != address(DAOrewardAccount)
+ && p.recipient != address(extraBalance)
+ && p.recipient != address(curator)) {
+
+ rewardToken[address(this)] += p.amount;
+ totalRewardToken += p.amount;
+ }
+ }
+
+ closeProposal(_proposalID);
+
+ // Initiate event
+ ProposalTallied(_proposalID, _success, quorum);
+ }
+
+
+ function closeProposal(uint _proposalID) internal {
+ Proposal p = proposals[_proposalID];
+ if (p.open)
+ sumOfProposalDeposits -= p.proposalDeposit;
+ p.open = false;
+ }
+
+ function splitDAO(
+ uint _proposalID,
+ address _newCurator
+ ) noEther onlyTokenholders returns (bool _success) {
+
+ Proposal p = proposals[_proposalID];
+
+ // Sanity check
+
+ if (now < p.votingDeadline // has the voting deadline arrived?
+ //The request for a split expires XX days after the voting deadline
+ || now > p.votingDeadline + splitExecutionPeriod
+ // Does the new Curator address match?
+ || p.recipient != _newCurator
+ // Is it a new curator proposal?
+ || !p.newCurator
+ // Have you voted for this split?
+ || !p.votedYes[msg.sender]
+ // Did you already vote on another proposal?
+ || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) {
+
+ throw;
+ }
+
+ // If the new DAO doesn't exist yet, create the new DAO and store the
+ // current split data
+ if (address(p.splitData[0].newDAO) == 0) {
+ p.splitData[0].newDAO = createNewDAO(_newCurator);
+ // Call depth limit reached, etc.
+ if (address(p.splitData[0].newDAO) == 0)
+ throw;
+ // should never happen
+ if (this.balance < sumOfProposalDeposits)
+ throw;
+ p.splitData[0].splitBalance = actualBalance();
+ p.splitData[0].rewardToken = rewardToken[address(this)];
+ p.splitData[0].totalSupply = totalSupply;
+ p.proposalPassed = true;
+ }
+
+ // Move ether and assign new Tokens
+ uint fundsToBeMoved =
+ (balances[msg.sender] * p.splitData[0].splitBalance) /
+ p.splitData[0].totalSupply;
+ if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false)
+ throw;
+
+
+ // Assign reward rights to new DAO
+ uint rewardTokenToBeMoved =
+ (balances[msg.sender] * p.splitData[0].rewardToken) /
+ p.splitData[0].totalSupply;
+
+ uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved /
+ rewardToken[address(this)];
+
+ rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved;
+ if (rewardToken[address(this)] < rewardTokenToBeMoved)
+ throw;
+ rewardToken[address(this)] -= rewardTokenToBeMoved;
+
+ DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved;
+ if (DAOpaidOut[address(this)] < paidOutToBeMoved)
+ throw;
+ DAOpaidOut[address(this)] -= paidOutToBeMoved;
+
+ // Burn DAO Tokens
+ Transfer(msg.sender, 0, balances[msg.sender]);
+ withdrawRewardFor(msg.sender); // be nice, and get his rewards
+ totalSupply -= balances[msg.sender];
+ balances[msg.sender] = 0;
+ paidOut[msg.sender] = 0;
+ return true;
+ }
+
+ function newContract(address _newContract){
+ if (msg.sender != address(this) || !allowedRecipients[_newContract]) return;
+ // move all ether
+ if (!_newContract.call.value(address(this).balance)()) {
+ throw;
+ }
+
+ //move all reward tokens
+ rewardToken[_newContract] += rewardToken[address(this)];
+ rewardToken[address(this)] = 0;
+ DAOpaidOut[_newContract] += DAOpaidOut[address(this)];
+ DAOpaidOut[address(this)] = 0;
+ }
+
+
+ function retrieveDAOReward(bool _toMembers) external noEther returns (bool _success) {
+ DAO dao = DAO(msg.sender);
+
+ if ((rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) /
+ totalRewardToken < DAOpaidOut[msg.sender])
+ throw;
+
+ uint reward =
+ (rewardToken[msg.sender] * DAOrewardAccount.accumulatedInput()) /
+ totalRewardToken - DAOpaidOut[msg.sender];
+ if(_toMembers) {
+ if (!DAOrewardAccount.payOut(dao.rewardAccount(), reward))
+ throw;
+ }
+ else {
+ if (!DAOrewardAccount.payOut(dao, reward))
+ throw;
+ }
+ DAOpaidOut[msg.sender] += reward;
+ return true;
+ }
+
+ function getMyReward() noEther returns (bool _success) {
+ return withdrawRewardFor(msg.sender);
+ }
+
+
+ function withdrawRewardFor(address _account) noEther internal returns (bool _success) {
+ if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account])
+ throw;
+
+ uint reward =
+ (balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account];
+ if (!rewardAccount.payOut(_account, reward))
+ throw;
+ paidOut[_account] += reward;
+ return true;
+ }
+
+
+ function transfer(address _to, uint256 _value) returns (bool success) {
+ if (isFueled
+ && now > closingTime
+ && !isBlocked(msg.sender)
+ && transferPaidOut(msg.sender, _to, _value)
+ && super.transfer(_to, _value)) {
+
+ return true;
+ } else {
+ throw;
+ }
+ }
+
+
+ function transferWithoutReward(address _to, uint256 _value) returns (bool success) {
+ if (!getMyReward())
+ throw;
+ return transfer(_to, _value);
+ }
+
+
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
+ if (isFueled
+ && now > closingTime
+ && !isBlocked(_from)
+ && transferPaidOut(_from, _to, _value)
+ && super.transferFrom(_from, _to, _value)) {
+
+ return true;
+ } else {
+ throw;
+ }
+ }
+
+
+ function transferFromWithoutReward(
+ address _from,
+ address _to,
+ uint256 _value
+ ) returns (bool success) {
+
+ if (!withdrawRewardFor(_from))
+ throw;
+ return transferFrom(_from, _to, _value);
+ }
+
+
+ function transferPaidOut(
+ address _from,
+ address _to,
+ uint256 _value
+ ) internal returns (bool success) {
+
+ uint transferPaidOut = paidOut[_from] * _value / balanceOf(_from);
+ if (transferPaidOut > paidOut[_from])
+ throw;
+ paidOut[_from] -= transferPaidOut;
+ paidOut[_to] += transferPaidOut;
+ return true;
+ }
+
+
+ function changeProposalDeposit(uint _proposalDeposit) noEther external {
+ if (msg.sender != address(this) || _proposalDeposit > (actualBalance() + rewardToken[address(this)])
+ / maxDepositDivisor) {
+
+ throw;
+ }
+ proposalDeposit = _proposalDeposit;
+ }
+
+
+ function changeAllowedRecipients(address _recipient, bool _allowed) noEther external returns (bool _success) {
+ if (msg.sender != curator)
+ throw;
+ allowedRecipients[_recipient] = _allowed;
+ AllowedRecipientChanged(_recipient, _allowed);
+ return true;
+ }
+
+
+ function isRecipientAllowed(address _recipient) internal returns (bool _isAllowed) {
+ if (allowedRecipients[_recipient]
+ || (_recipient == address(extraBalance)
+ // only allowed when at least the amount held in the
+ // extraBalance account has been spent from the DAO
+ && totalRewardToken > extraBalance.accumulatedInput()))
+ return true;
+ else
+ return false;
+ }
+
+ function actualBalance() constant returns (uint _actualBalance) {
+ return this.balance - sumOfProposalDeposits;
+ }
+
+
+ function minQuorum(uint _value) internal constant returns (uint _minQuorum) {
+ // minimum of 20% and maximum of 53.33%
+ return totalSupply / minQuorumDivisor +
+ (_value * totalSupply) / (3 * (actualBalance() + rewardToken[address(this)]));
+ }
+
+
+ function halveMinQuorum() returns (bool _success) {
+ // this can only be called after `quorumHalvingPeriod` has passed or at anytime
+ // by the curator with a delay of at least `minProposalDebatePeriod` between the calls
+ if ((lastTimeMinQuorumMet < (now - quorumHalvingPeriod) || msg.sender == curator)
+ && lastTimeMinQuorumMet < (now - minProposalDebatePeriod)) {
+ lastTimeMinQuorumMet = now;
+ minQuorumDivisor *= 2;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function createNewDAO(address _newCurator) internal returns (DAO _newDAO) {
+ NewCurator(_newCurator);
+ return daoCreator.createDAO(_newCurator, 0, 0, now + splitExecutionPeriod);
+ }
+
+ function numberOfProposals() constant returns (uint _numberOfProposals) {
+ // Don't count index 0. It's used by isBlocked() and exists from start
+ return proposals.length - 1;
+ }
+
+ function getNewDAOAddress(uint _proposalID) constant returns (address _newDAO) {
+ return proposals[_proposalID].splitData[0].newDAO;
+ }
+
+ function isBlocked(address _account) internal returns (bool) {
+ if (blocked[_account] == 0)
+ return false;
+ Proposal p = proposals[blocked[_account]];
+ if (now > p.votingDeadline) {
+ blocked[_account] = 0;
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function unblockMe() returns (bool) {
+ return isBlocked(msg.sender);
+ }
+}
+
+contract DAO_Creator {
+ function createDAO(
+ address _curator,
+ uint _proposalDeposit,
+ uint _minTokensToCreate,
+ uint _closingTime
+ ) returns (DAO _newDAO) {
+
+ return new DAO(
+ _curator,
+ DAO_Creator(this),
+ _proposalDeposit,
+ _minTokensToCreate,
+ _closingTime,
+ msg.sender
+ );
+ }
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol-0.4.25.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol-0.4.25.zip
new file mode 100644
index 0000000000..5b8fb4fb7f
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol-0.4.25.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol
new file mode 100644
index 0000000000..3e407accb6
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol
@@ -0,0 +1,100 @@
+pragma solidity ^0.4.24;
+
+contract Reentrancy {
+ mapping (address => uint) userBalance;
+
+ function getBalance(address u) view public returns(uint){
+ return userBalance[u];
+ }
+
+ function addToBalance() payable public{
+ userBalance[msg.sender] += msg.value;
+ }
+
+ // Should not detect reentrancy in constructor
+ constructor() public {
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ if (!(msg.sender.call.value(userBalance[msg.sender])())) {
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance() public{
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed() public{
+ // To protect against re-entrancy, the state variable
+ // has to be change before the call
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ if( ! (msg.sender.call.value(amount)() ) ){
+ revert();
+ }
+ }
+
+ function withdrawBalance_fixed_2() public{
+ // send() and transfer() are safe against reentrancy
+ // they do not transfer the remaining gas
+ // and they give just enough gas to execute few instructions
+ // in the fallback function (no further call possible)
+ msg.sender.transfer(userBalance[msg.sender]);
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed_3() public{
+ // The state can be changed
+ // But it is fine, as it can only occur if the transaction fails
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ if( ! (msg.sender.call.value(amount)() ) ){
+ userBalance[msg.sender] = amount;
+ }
+ }
+ function withdrawBalance_fixed_4() public{
+ // The state can be changed
+ // But it is fine, as it can only occur if the transaction fails
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ if( (msg.sender.call.value(amount)() ) ){
+ return;
+ }
+ else{
+ userBalance[msg.sender] = amount;
+ }
+ }
+
+ function withdrawBalance_nested() public{
+ uint amount = userBalance[msg.sender];
+ if( ! (msg.sender.call.value(amount/2)() ) ){
+ msg.sender.call.value(amount/2)();
+ userBalance[msg.sender] = 0;
+ }
+ }
+
+}
+
+
+contract Called{
+ function f() public;
+}
+
+contract ReentrancyEvent {
+
+ event E();
+
+ function test(Called c) public{
+
+ c.f();
+ emit E();
+
+ }
+}
+
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol-0.4.25.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol-0.4.25.zip
new file mode 100644
index 0000000000..1fa87b48a7
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol-0.4.25.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol
new file mode 100644
index 0000000000..73dd3db00b
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol
@@ -0,0 +1,31 @@
+pragma solidity ^0.4.24;
+
+contract Token{
+ function transfer(address to, uint value) returns(bool);
+ function transferFrom(address from, address to, uint value) returns(bool);
+}
+
+contract Reentrancy {
+
+ mapping(address => mapping(address => uint)) eth_deposed;
+ mapping(address => mapping(address => uint)) token_deposed;
+
+ function deposit_eth(address token) payable{
+ eth_deposed[token][msg.sender] += msg.value;
+ }
+
+ function deposit_token(address token, uint value){
+ token_deposed[token][msg.sender] += value;
+ require(Token(token).transferFrom(msg.sender, address(this), value));
+ }
+
+ function withdraw(address token){
+ msg.sender.transfer(eth_deposed[token][msg.sender]);
+ require(Token(token).transfer(msg.sender, token_deposed[token][msg.sender]));
+
+ eth_deposed[token][msg.sender] = 0;
+ token_deposed[token][msg.sender] = 0;
+
+ }
+
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol-0.4.25.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol-0.4.25.zip
new file mode 100644
index 0000000000..ac63e710de
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol-0.4.25.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol
new file mode 100644
index 0000000000..7163934f8f
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol
@@ -0,0 +1,65 @@
+// pragma solidity ^0.5.0;
+
+contract Reentrancy {
+ mapping (address => uint) userBalance;
+
+ function getBalance(address u) view public returns(uint){
+ return userBalance[u];
+ }
+
+ function addToBalance() payable public{
+ userBalance[msg.sender] += msg.value;
+ }
+
+ // Should not detect reentrancy in constructor
+ constructor() public {
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ (bool ret, bytes memory mem) = msg.sender.call.value(userBalance[msg.sender])("");
+ if( ! ret ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance() public{
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ (bool ret, bytes memory mem) = msg.sender.call.value(userBalance[msg.sender])("");
+ if( ! ret ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed() public{
+ // To protect against re-entrancy, the state variable
+ // has to be change before the call
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ (bool ret, bytes memory mem) = msg.sender.call.value(amount)("");
+ if( ! ret ){
+ revert();
+ }
+ }
+
+ function withdrawBalance_fixed_2() public{
+ // send() and transfer() are safe against reentrancy
+ // they do not transfer the remaining gas
+ // and they give just enough gas to execute few instructions
+ // in the fallback function (no further call possible)
+ msg.sender.transfer(userBalance[msg.sender]);
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed_3() public{
+ // The state can be changed
+ // But it is fine, as it can only occur if the transaction fails
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ (bool ret, bytes memory mem) = msg.sender.call.value(amount)("");
+ if( ! ret ){
+ userBalance[msg.sender] = amount;
+ }
+ }
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol-0.5.16.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol-0.5.16.zip
new file mode 100644
index 0000000000..c105dd00e9
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol-0.5.16.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol
new file mode 100644
index 0000000000..b2f9ec884a
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol
@@ -0,0 +1,31 @@
+// pragma solidity ^0.4.24;
+
+contract Token{
+ function transfer(address to, uint value) public returns(bool);
+ function transferFrom(address from, address to, uint value) public returns(bool);
+}
+
+contract Reentrancy {
+
+ mapping(address => mapping(address => uint)) eth_deposed;
+ mapping(address => mapping(address => uint)) token_deposed;
+
+ function deposit_eth(address token) public payable{
+ eth_deposed[token][msg.sender] += msg.value;
+ }
+
+ function deposit_token(address token, uint value) public {
+ token_deposed[token][msg.sender] += value;
+ require(Token(token).transferFrom(msg.sender, address(this), value));
+ }
+
+ function withdraw(address token) public {
+ msg.sender.transfer(eth_deposed[token][msg.sender]);
+ require(Token(token).transfer(msg.sender, token_deposed[token][msg.sender]));
+
+ eth_deposed[token][msg.sender] = 0;
+ token_deposed[token][msg.sender] = 0;
+
+ }
+
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol-0.5.16.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol-0.5.16.zip
new file mode 100644
index 0000000000..90bd7283e2
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol-0.5.16.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol
new file mode 100644
index 0000000000..13843d7897
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol
@@ -0,0 +1,65 @@
+// pragma solidity ^0.5.0;
+
+contract Reentrancy {
+ mapping (address => uint) userBalance;
+
+ function getBalance(address u) view public returns(uint){
+ return userBalance[u];
+ }
+
+ function addToBalance() payable public{
+ userBalance[msg.sender] += msg.value;
+ }
+
+ // Should not detect reentrancy in constructor
+ constructor() public {
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ (bool ret, bytes memory mem) = msg.sender.call{value:userBalance[msg.sender]}("");
+ if( ! ret ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance() public{
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ (bool ret, bytes memory mem) = msg.sender.call.value(userBalance[msg.sender])("");
+ if( ! ret ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed() public{
+ // To protect against re-entrancy, the state variable
+ // has to be change before the call
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ (bool ret, bytes memory mem) = msg.sender.call.value(amount)("");
+ if( ! ret ){
+ revert();
+ }
+ }
+
+ function withdrawBalance_fixed_2() public{
+ // send() and transfer() are safe against reentrancy
+ // they do not transfer the remaining gas
+ // and they give just enough gas to execute few instructions
+ // in the fallback function (no further call possible)
+ msg.sender.transfer(userBalance[msg.sender]);
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed_3() public{
+ // The state can be changed
+ // But it is fine, as it can only occur if the transaction fails
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ (bool ret, bytes memory mem) = msg.sender.call.value(amount)("");
+ if( ! ret ){
+ userBalance[msg.sender] = amount;
+ }
+ }
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol-0.6.11.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol-0.6.11.zip
new file mode 100644
index 0000000000..b8ddf59eba
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol-0.6.11.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol
new file mode 100644
index 0000000000..d1067e362f
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol
@@ -0,0 +1,31 @@
+// pragma solidity ^0.4.24;
+
+abstract contract Token{
+ function transfer(address to, uint value) public virtual returns(bool);
+ function transferFrom(address from, address to, uint value) public virtual returns(bool);
+}
+
+contract Reentrancy {
+
+ mapping(address => mapping(address => uint)) eth_deposed;
+ mapping(address => mapping(address => uint)) token_deposed;
+
+ function deposit_eth(address token) public payable{
+ eth_deposed[token][msg.sender] += msg.value;
+ }
+
+ function deposit_token(address token, uint value) public {
+ token_deposed[token][msg.sender] += value;
+ require(Token(token).transferFrom(msg.sender, address(this), value));
+ }
+
+ function withdraw(address token) public {
+ msg.sender.transfer(eth_deposed[token][msg.sender]);
+ require(Token(token).transfer(msg.sender, token_deposed[token][msg.sender]));
+
+ eth_deposed[token][msg.sender] = 0;
+ token_deposed[token][msg.sender] = 0;
+
+ }
+
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol-0.6.11.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol-0.6.11.zip
new file mode 100644
index 0000000000..5aeda8ec13
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol-0.6.11.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol
new file mode 100644
index 0000000000..12af82cf94
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol
@@ -0,0 +1,65 @@
+// pragma solidity ^0.5.0;
+
+contract Reentrancy {
+ mapping (address => uint) userBalance;
+
+ function getBalance(address u) view public returns(uint){
+ return userBalance[u];
+ }
+
+ function addToBalance() payable public{
+ userBalance[msg.sender] += msg.value;
+ }
+
+ // Should not detect reentrancy in constructor
+ constructor() public {
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ (bool ret, bytes memory mem) = msg.sender.call{value:userBalance[msg.sender]}("");
+ if( ! ret ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance() public{
+ // send userBalance[msg.sender] ethers to msg.sender
+ // if mgs.sender is a contract, it will call its fallback function
+ (bool ret, bytes memory mem) = msg.sender.call{value:userBalance[msg.sender]}("");
+ if( ! ret ){
+ revert();
+ }
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed() public{
+ // To protect against re-entrancy, the state variable
+ // has to be change before the call
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ (bool ret, bytes memory mem) = msg.sender.call{value:amount}("");
+ if( ! ret ){
+ revert();
+ }
+ }
+
+ function withdrawBalance_fixed_2() public{
+ // send() and transfer() are safe against reentrancy
+ // they do not transfer the remaining gas
+ // and they give just enough gas to execute few instructions
+ // in the fallback function (no further call possible)
+ msg.sender.transfer(userBalance[msg.sender]);
+ userBalance[msg.sender] = 0;
+ }
+
+ function withdrawBalance_fixed_3() public{
+ // The state can be changed
+ // But it is fine, as it can only occur if the transaction fails
+ uint amount = userBalance[msg.sender];
+ userBalance[msg.sender] = 0;
+ (bool ret, bytes memory mem) = msg.sender.call{value:amount}("");
+ if( ! ret ){
+ userBalance[msg.sender] = amount;
+ }
+ }
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol-0.7.6.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol-0.7.6.zip
new file mode 100644
index 0000000000..24c706745b
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol-0.7.6.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol
new file mode 100644
index 0000000000..d1067e362f
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol
@@ -0,0 +1,31 @@
+// pragma solidity ^0.4.24;
+
+abstract contract Token{
+ function transfer(address to, uint value) public virtual returns(bool);
+ function transferFrom(address from, address to, uint value) public virtual returns(bool);
+}
+
+contract Reentrancy {
+
+ mapping(address => mapping(address => uint)) eth_deposed;
+ mapping(address => mapping(address => uint)) token_deposed;
+
+ function deposit_eth(address token) public payable{
+ eth_deposed[token][msg.sender] += msg.value;
+ }
+
+ function deposit_token(address token, uint value) public {
+ token_deposed[token][msg.sender] += value;
+ require(Token(token).transferFrom(msg.sender, address(this), value));
+ }
+
+ function withdraw(address token) public {
+ msg.sender.transfer(eth_deposed[token][msg.sender]);
+ require(Token(token).transfer(msg.sender, token_deposed[token][msg.sender]));
+
+ eth_deposed[token][msg.sender] = 0;
+ token_deposed[token][msg.sender] = 0;
+
+ }
+
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol-0.7.6.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol-0.7.6.zip
new file mode 100644
index 0000000000..34ba3bcdf8
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol-0.7.6.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol
new file mode 100644
index 0000000000..4bc02966dc
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol
@@ -0,0 +1,21 @@
+interface Receiver {
+ function send_funds() external payable;
+}
+
+contract TestWithBug {
+ mapping(address => uint) balances;
+
+ function withdraw(uint amount) public {
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+ // // slither-disable-start all
+ // function withdrawFiltered(uint amount) public {
+ // require(amount <= balances[msg.sender]);
+ // Receiver(msg.sender).send_funds{value: amount}();
+ // balances[msg.sender] -= amount;
+ // }
+ // // slither-disable-end all
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol-0.8.10.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol-0.8.10.zip
new file mode 100644
index 0000000000..f73bdbef23
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol-0.8.10.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol
new file mode 100644
index 0000000000..938738217e
--- /dev/null
+++ b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol
@@ -0,0 +1,151 @@
+interface Receiver{
+ function send_funds() payable external;
+}
+
+contract TestWithBug{
+
+ mapping(address => uint) balances;
+
+ modifier nonReentrant(){
+ _;
+ }
+
+ function withdraw(uint amount) nonReentrant public{
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+ function withdraw_all() public{
+ uint amount = balances[msg.sender];
+ balances[msg.sender] = 0;
+ Receiver(msg.sender).send_funds{value: amount}();
+ }
+
+}
+
+
+contract TestWithoutBug{
+
+ mapping(address => uint) balances;
+
+ modifier nonReentrant(){
+ _;
+ }
+
+ function withdraw(uint amount) nonReentrant public{
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+ function withdraw_all() nonReentrant public{
+ uint amount = balances[msg.sender];
+ balances[msg.sender] = 0;
+ Receiver(msg.sender).send_funds{value: amount}();
+ }
+
+}
+
+contract TestWithBugInternal{
+
+ mapping(address => uint) balances;
+
+ modifier nonReentrant(){
+ _;
+ }
+
+ function withdraw(uint amount) nonReentrant public{
+ withdraw_internal(amount);
+ }
+
+ function withdraw_internal(uint amount) internal{
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+ function withdraw_all() public{
+ withdraw_all_internal();
+ }
+
+ function withdraw_all_internal() internal {
+ uint amount = balances[msg.sender];
+ balances[msg.sender] = 0;
+ Receiver(msg.sender).send_funds{value: amount}();
+ }
+
+}
+
+contract TestWithoutBugInternal{
+
+ mapping(address => uint) balances;
+
+ modifier nonReentrant(){
+ _;
+ }
+
+ function withdraw(uint amount) nonReentrant public{
+ withdraw_internal(amount);
+ }
+
+ function withdraw_internal(uint amount) internal{
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+ function withdraw_all() nonReentrant public{
+ withdraw_all_internal();
+ }
+
+ function withdraw_all_internal() internal {
+ uint amount = balances[msg.sender];
+ balances[msg.sender] = 0;
+ Receiver(msg.sender).send_funds{value: amount}();
+ }
+
+}
+
+contract TestBugWithPublicVariable{
+
+ mapping(address => uint) public balances;
+
+ modifier nonReentrant(){
+ _;
+ }
+
+ function withdraw(uint amount) nonReentrant public{
+ withdraw_internal(amount);
+ }
+
+ function withdraw_internal(uint amount) internal{
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+}
+
+contract TestWithBugNonReentrantRead{
+
+ mapping(address => uint) balances;
+
+ modifier nonReentrant(){
+ _;
+ }
+
+ function withdraw(uint amount) nonReentrant public{
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
+ }
+
+ // Simulate a reentrancy that allows to read variable in a potential incorrect state during a reentrancy
+ // This is more likely to impact protocol like reentrancy
+ function read() public returns(uint){
+ uint amount = balances[msg.sender];
+ return amount;
+ }
+
+}
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol-0.8.10.zip b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol-0.8.10.zip
new file mode 100644
index 0000000000..cc3cef205c
Binary files /dev/null and b/tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol-0.8.10.zip differ
diff --git a/tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol b/tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol
index b036bd7fd0..4bc02966dc 100644
--- a/tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol
+++ b/tests/e2e/detectors/test_data/reentrancy-eth/0.8.10/reentrancy_filtered_comments.sol
@@ -1,22 +1,21 @@
-interface Receiver{
- function send_funds() payable external;
+interface Receiver {
+ function send_funds() external payable;
}
-contract TestWithBug{
+contract TestWithBug {
mapping(address => uint) balances;
- function withdraw(uint amount) public{
- require(amount <= balances[msg.sender]);
- Receiver(msg.sender).send_funds{value: amount}();
- balances[msg.sender] -= amount;
+ function withdraw(uint amount) public {
+ require(amount <= balances[msg.sender]);
+ Receiver(msg.sender).send_funds{value: amount}();
+ balances[msg.sender] -= amount;
}
- // slither-disable-start all
- function withdrawFiltered(uint amount) public{
- require(amount <= balances[msg.sender]);
- Receiver(msg.sender).send_funds{value: amount}();
- balances[msg.sender] -= amount;
- }
- // slither-disable-end all
+ // // slither-disable-start all
+ // function withdrawFiltered(uint amount) public {
+ // require(amount <= balances[msg.sender]);
+ // Receiver(msg.sender).send_funds{value: amount}();
+ // balances[msg.sender] -= amount;
+ // }
+ // // slither-disable-end all
}
-
diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py
index 38ae278d3e..0c01ac5986 100644
--- a/tests/e2e/detectors/test_detectors.py
+++ b/tests/e2e/detectors/test_detectors.py
@@ -367,6 +367,52 @@ def id_test(test_item: Test):
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"),
+ # ReentrancyEthDF tests - data flow based reentrancy detection
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy.sol",
+ "0.4.25",
+ ),
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy_indirect.sol",
+ "0.4.25",
+ ),
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy.sol",
+ "0.5.16",
+ ),
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy_indirect.sol",
+ "0.5.16",
+ ),
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy.sol",
+ "0.6.11",
+ ),
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy_indirect.sol",
+ "0.6.11",
+ ),
+ Test(all_detectors.ReentrancyEthDF, "reentrancy.sol", "0.7.6"),
+ Test(
+ all_detectors.ReentrancyEthDF,
+ "reentrancy_indirect.sol",
+ "0.7.6",
+ ),
+ # Test(
+ # all_detectors.ReentrancyEthDF,
+ # "DAO.sol",
+ # "0.4.25",
+ # ),
+ # Test the nonReentrant filtering
+ Test(all_detectors.ReentrancyEthDF, "reentrancy_with_non_reentrant.sol", "0.8.10"),
+ # Test parse_ignore_comments
+ Test(all_detectors.ReentrancyEthDF, "reentrancy_filtered_comments.sol", "0.8.10"),
Test(
all_detectors.UninitializedStorageVars,
"uninitialized_storage_pointer.sol",
@@ -1945,6 +1991,7 @@ def id_test(test_item: Test):
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
+
# pylint: disable=too-many-locals
@pytest.mark.parametrize("test_item", ALL_TESTS, ids=id_test)
def test_detector(test_item: Test, snapshot):