From 19e0577f60f122132c1eb79e43dfb5d791a17e70 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Mon, 18 Aug 2025 14:22:22 -0400 Subject: [PATCH 01/17] Adding domain.py --- slither/analyses/data_flow/engine/domain.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 slither/analyses/data_flow/engine/domain.py diff --git a/slither/analyses/data_flow/engine/domain.py b/slither/analyses/data_flow/engine/domain.py new file mode 100644 index 0000000000..7015dda3e5 --- /dev/null +++ b/slither/analyses/data_flow/engine/domain.py @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod +from typing import Self + + +class Domain(ABC): + @abstractmethod + def top(cls) -> Self: + """The top element of the domain""" + pass + + @abstractmethod + def bottom(cls) -> Self: + """The bottom element of the domain""" + pass + + @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.""" + pass From b867a8b0ab0c4c10b7f73875b4c59d896f431288 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Mon, 18 Aug 2025 14:26:58 -0400 Subject: [PATCH 02/17] Adding direction and analysis --- slither/analyses/data_flow/engine/analysis.py | 46 +++ .../analyses/data_flow/engine/direction.py | 273 ++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 slither/analyses/data_flow/engine/analysis.py create mode 100644 slither/analyses/data_flow/engine/direction.py diff --git a/slither/analyses/data_flow/engine/analysis.py b/slither/analyses/data_flow/engine/analysis.py new file mode 100644 index 0000000000..90fee3d0ef --- /dev/null +++ b/slither/analyses/data_flow/engine/analysis.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod +from typing import Generic, List, 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.core.declarations.function import Function +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, functions: List[Function] + ): + 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..9471517ebc --- /dev/null +++ b/slither/analyses/data_flow/engine/direction.py @@ -0,0 +1,273 @@ +from abc import ABC, abstractmethod +from decimal import Decimal +from typing import TYPE_CHECKING, Deque, Dict, List, Optional, Set, Union + +if TYPE_CHECKING: + + from slither.core.compilation_unit import SlitherCompilationUnit + from slither.core.declarations import Contract + + +from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState +from slither.analyses.data_flow.engine.domain import Domain +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations.function import Function +from slither.slithir.operations.binary import Binary, BinaryType + + +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]"], + functions: List[Function], + ): + pass + + +class Forward(Direction): + def __init__(self): + self._numeric_literals_extracted = False + self._loop_iteration_counts: Dict[int, int] = {} + self._loop_previous_states: Dict[int, Domain] = {} # Track previous state for each loop + self._set_b_cardinality: int = 0 + self._set_b: Set[int] = set() + + @property + def IS_FORWARD(self) -> bool: + return True + + def _extract_condition(self, node: Node) -> Optional[Binary]: + """Extract comparison condition from IF node.""" + if node.type != NodeType.IF: + return None + + for operation in node.irs or []: + if isinstance(operation, Binary) and operation.type in [ + BinaryType.GREATER, + BinaryType.GREATER_EQUAL, + BinaryType.LESS, + BinaryType.LESS_EQUAL, + BinaryType.EQUAL, + BinaryType.NOT_EQUAL, + ]: + return operation + return None + + def _is_bottom_domain(self, domain: Domain) -> bool: + """Check if the domain is BOTTOM (unreachable state).""" + return ( + hasattr(domain, "variant") + and hasattr(domain.variant, "name") + and domain.variant.name == "BOTTOM" + ) + + def _handle_ifloop_node( + self, + node: Node, + current_state: "AnalysisState", + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + analysis: "Analysis", + ) -> bool: + """Handle IFLOOP nodes with iteration limiting.""" + if node.type != NodeType.IFLOOP: + return False + + loop_id = node.node_id + current_iteration = self._loop_iteration_counts.setdefault(loop_id, 0) + + # Check if we've exceeded maximum iterations (allow more iterations for widening to converge) + max_iterations = max( + self._set_b_cardinality * 3, 10 + ) # Allow more iterations for convergence + if current_iteration >= max_iterations: + self._exit_loop(node, current_state.pre, worklist, global_state, loop_id) + return True + + # Handle state tracking and widening + if current_iteration == 0: + # First iteration: save current state as previous + self._loop_previous_states[loop_id] = current_state.pre.deep_copy() + else: + # Apply widening with previous state on every iteration after the first + print(f"Widening iteration {current_iteration} with previous state") + previous_state = self._loop_previous_states[loop_id] + widened_state = analysis.apply_widening(current_state.pre, previous_state, self._set_b) + current_state.pre = widened_state + + # Check for convergence (if state hasn't changed significantly) + if self._has_converged(previous_state, current_state.pre): + print(f"🔄 Widening converged after {current_iteration} iterations") + self._exit_loop(node, current_state.pre, worklist, global_state, loop_id) + return True + + # Update previous state for next iteration + self._loop_previous_states[loop_id] = current_state.pre.deep_copy() + + # Continue loop iteration + self._continue_loop(node, current_state.pre, worklist, global_state, loop_id) + return True + + def _exit_loop( + self, + node: Node, + state: Domain, + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + loop_id: int, + ) -> None: + """Exit loop by propagating to exit node and resetting counter.""" + if len(node.sons) > 1: + self._propagate_to_successor(node, node.sons[1], state, worklist, global_state) + self._loop_iteration_counts[loop_id] = 0 + # Clear previous state when exiting loop + if loop_id in self._loop_previous_states: + del self._loop_previous_states[loop_id] + + def _continue_loop( + self, + node: Node, + state: Domain, + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + loop_id: int, + ) -> None: + """Continue loop by propagating to body node and incrementing counter.""" + self._loop_iteration_counts[loop_id] += 1 + if node.sons: + self._propagate_to_successor(node, node.sons[0], state, worklist, global_state) + + def _propagate_to_successor( + self, + node: Node, + successor: Node, + filtered_state: Domain, + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + ) -> None: + """Propagate state to a single successor.""" + if not successor or successor.node_id not in global_state: + return + + # Skip unreachable branches + if self._is_bottom_domain(filtered_state): + self._handle_unreachable_branch(successor, filtered_state, worklist, global_state) + return + + # Join states and update worklist + son_state = global_state[successor.node_id] + if son_state.pre.join(filtered_state) and successor not in worklist: + worklist.append(successor) + + def _handle_unreachable_branch( + self, + successor: Node, + bottom_state: Domain, + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + ) -> None: + """Handle propagation to unreachable branches.""" + + # Remove from worklist (except ENDIF nodes) + if successor in worklist and successor.type != NodeType.ENDIF: + worklist.remove(successor) + + # Mark as unreachable + global_state[successor.node_id].pre = bottom_state + + def _propagate_conditional( + self, + node: Node, + current_state: "AnalysisState", + condition: Binary, + analysis: "Analysis", + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + ) -> None: + """Handle conditional propagation for IF nodes.""" + if len(node.sons) < 2: + return + + true_successor, false_successor = node.sons[0], node.sons[1] + + # Apply conditions and propagate + true_state = analysis.apply_condition(current_state.pre, condition, True) + false_state = analysis.apply_condition(current_state.pre, condition, False) + + self._propagate_to_successor(node, true_successor, true_state, worklist, global_state) + self._propagate_to_successor(node, false_successor, false_state, worklist, global_state) + + def _propagate_regular( + self, + node: Node, + current_state: "AnalysisState", + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + ) -> None: + """Handle regular (non-conditional) propagation.""" + for successor in node.sons: + self._propagate_to_successor(node, successor, current_state.pre, worklist, global_state) + + def apply_transfer_function( + self, + analysis: "Analysis", + current_state: "AnalysisState", + node: Node, + worklist: Deque[Node], + global_state: Dict[int, "AnalysisState[A]"], + functions: List[Function], + ): + + # Apply transfer function to current node + for operation in node.irs or [None]: + analysis.transfer_function( + node=node, domain=current_state.pre, operation=operation, functions=functions + ) + + # Set post state + global_state[node.node_id].post = current_state.pre + + # Handle IFLOOP nodes specially + if self._handle_ifloop_node(node, current_state, worklist, global_state, analysis): + return + + # Handle propagation based on node type + condition = self._extract_condition(node) + if condition and len(node.sons) >= 2: + self._propagate_conditional( + node, current_state, condition, analysis, worklist, global_state + ) + else: + self._propagate_regular(node, current_state, worklist, global_state) + + def _has_converged(self, previous_state: "Domain", current_state: "Domain") -> bool: + """Check if the widening has converged by comparing states.""" + # Simple convergence check: if states are equal, we've converged + return previous_state == current_state + + +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]"], + functions: List[Function], + ): + raise NotImplementedError("Backward transfer function hasn't been developed yet") From 7f8b3395546fefdf75fca7d096f9be740b1ebb59 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Mon, 18 Aug 2025 14:28:04 -0400 Subject: [PATCH 03/17] Adding engine --- slither/analyses/data_flow/engine/engine.py | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 slither/analyses/data_flow/engine/engine.py diff --git a/slither/analyses/data_flow/engine/engine.py b/slither/analyses/data_flow/engine/engine.py new file mode 100644 index 0000000000..b61f6db4b3 --- /dev/null +++ b/slither/analyses/data_flow/engine/engine.py @@ -0,0 +1,62 @@ +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 import Contract +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.functions: List[Function] + + @classmethod + def new(cls, analysis: Analysis, functions: List[Function]): + engine = cls() + engine.analysis = analysis + engine.functions = functions + + # create state mapping using node.node_id directly + for function in functions: + 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: + worklist.extend(self.nodes) + 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, + functions=self.functions, + ) + + def result(self) -> Dict[Node, AnalysisState[A]]: + result = {} + for node in self.nodes: + result[node] = self.state[node.node_id] + return result From 5d7d20c4883acb9197064523a24b0ac3955ed395 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Tue, 19 Aug 2025 13:43:29 -0400 Subject: [PATCH 04/17] Adding reentrancy --- .../analyses/reentrancy/analysis/analysis.py | 143 ++++++++ .../analyses/reentrancy/analysis/domain.py | 59 ++++ .../analyses/reentrancy/core/state.py | 118 +++++++ .../data_flow/analyses/reentrancy/safe.py | 319 ++++++++++++++++++ .../analyses/data_flow/engine/direction.py | 5 +- slither/detectors/all_detectors.py | 2 + slither/detectors/reentrancy/reentrancy.py | 9 +- .../reentrancy_df/reentrancy_eth_df.py | 289 ++++++++++++++++ .../0.8.10/reentrancy_filtered_comments.sol | 29 +- 9 files changed, 950 insertions(+), 23 deletions(-) create mode 100644 slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py create mode 100644 slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py create mode 100644 slither/analyses/data_flow/analyses/reentrancy/core/state.py create mode 100644 slither/analyses/data_flow/analyses/reentrancy/safe.py create mode 100644 slither/detectors/reentrancy_df/reentrancy_eth_df.py 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..f46c6f8fc8 --- /dev/null +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py @@ -0,0 +1,143 @@ +from typing import List, 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, functions: List[Function] + ): + self.transfer_function_helper(node, domain, operation, functions) + + def transfer_function_helper( + self, + node: Node, + domain: ReentrancyDomain, + operation: Operation, + functions: List[Function], + 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, functions, private_functions_seen + ) + elif domain.variant == DomainVariant.STATE: + self._analyze_operation_by_type( + operation, domain, node, functions, private_functions_seen + ) + + def _analyze_operation_by_type( + self, + operation: Operation, + domain: ReentrancyDomain, + node: Node, + functions: List[Function], + 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): + for var in node.state_variables_read: + if isinstance(var, StateVariable): + domain.state.reads[var.canonical_name].add(node) + for var in node.state_variables_written: + if isinstance(var, StateVariable) and var.is_stored: + domain.state.written[var.canonical_name].add(node) + + def _update_writes_after_calls(self, domain: ReentrancyDomain, node: Node): + # Track state writes after external calls + if node in domain.state.calls: + for var_name, write_nodes in domain.state.written.items(): + if write_nodes: + domain.state.writes_after_calls[var_name].update(write_nodes) + + 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, + [function], + private_functions_seen, + ) + # Mark cross-function reentrancy + for var_name in domain.state.written.keys(): + domain.state.cross_function[var_name].add(function) + + def _handle_abi_call_contract_operation( + self, + operation: Union[LowLevelCall, HighLevelCall, Send, Transfer], + domain: ReentrancyDomain, + node: Node, + ): + domain.state.calls[node].add(operation.node) + vars_read_before_call = set(domain.state.reads.keys()) + domain.state.reads_prior_calls[node] = vars_read_before_call + + if operation.can_send_eth: + if isinstance(operation, (Send, Transfer)): + domain.state.safe_send_eth[node].add(operation.node) + else: + domain.state.send_eth[node].add(operation.node) + + def _handle_event_call_operation(self, operation: EventCall, domain: ReentrancyDomain): + calls_before_events = set() + for calls_set in domain.state.calls.values(): + calls_before_events.update(calls_set) + domain.state.events[operation].add(operation.node) + for call_node in calls_before_events: + domain.state.calls[operation.node].add(call_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..bf6078276e --- /dev/null +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py @@ -0,0 +1,59 @@ +from typing import Optional +from slither.analyses.data_flow.analyses.reentrancy.core.state import State +from slither.analyses.data_flow.engine.domain import Domain +from enum import Enum, auto + + +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..f266d7884c --- /dev/null +++ b/slither/analyses/data_flow/analyses/reentrancy/core/state.py @@ -0,0 +1,118 @@ +from collections import defaultdict +import copy +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) + + # New attributes + self.writes_after_calls: Dict[str, Set[Node]] = defaultdict(set) + self.cross_function: Dict[StateVariable, Set[Function]] = defaultdict(set) + + @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 + + 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_info = State() + new_info._send_eth = copy.deepcopy(self._send_eth) + new_info._safe_send_eth = copy.deepcopy(self._safe_send_eth) + new_info._calls = copy.deepcopy(self._calls) + new_info._reads = copy.deepcopy(self._reads) + new_info._reads_prior_calls = copy.deepcopy(self._reads_prior_calls) + new_info._events = copy.deepcopy(self._events) + new_info._written = copy.deepcopy(self._written) + new_info.writes_after_calls = copy.deepcopy(self.writes_after_calls) + new_info.cross_function = copy.deepcopy(self.cross_function) + return new_info diff --git a/slither/analyses/data_flow/analyses/reentrancy/safe.py b/slither/analyses/data_flow/analyses/reentrancy/safe.py new file mode 100644 index 0000000000..d0bfd48f65 --- /dev/null +++ b/slither/analyses/data_flow/analyses/reentrancy/safe.py @@ -0,0 +1,319 @@ +from enum import Enum, auto +from typing import Dict, List, Optional, Set, Union +from collections import defaultdict +import copy + + +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 import ( + EventCall, + HighLevelCall, + InternalCall, + LowLevelCall, + Operation, + Send, + Transfer, +) + + +class ReentrancyInfo: + 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) + + @property + def send_eth(self) -> Dict[Node, Set[Node]]: + """Return the list of calls sending value (unsafe calls only)""" + return self._send_eth + + @property + def safe_send_eth(self) -> Dict[Node, Set[Node]]: + """Return the list of safe ETH transfers (Send/Transfer operations)""" + return self._safe_send_eth + + @property + def all_eth_calls(self) -> Dict[Node, Set[Node]]: + """Return all ETH-sending calls (both safe and unsafe) - for other analyses""" + 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 the list of calls that can callback""" + return self._calls + + @property + def reads(self) -> Dict[str, Set[Node]]: + """Return of variables that are read""" + return self._reads + + @property + def written(self) -> Dict[str, Set[Node]]: + """Return of variables that are written""" + return self._written + + @property + def reads_prior_calls(self) -> Dict[Node, Set[str]]: + """Return the dictionary node -> variables read before any call""" + return self._reads_prior_calls + + @property + def events(self) -> Dict[EventCall, Set[Node]]: + """Return the list of events""" + return self._events + + def __eq__(self, other): + if not isinstance(other, ReentrancyInfo): + 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 + ) + + 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()), + ) + ) + + def __str__(self): + return ( + f"ReentrancyInfo(\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")" + ) + + def deep_copy(self) -> "ReentrancyInfo": + """Create a deep copy of this ReentrancyInfo object""" + new_info = ReentrancyInfo() + new_info._send_eth = copy.deepcopy(self._send_eth) + new_info._safe_send_eth = copy.deepcopy(self._safe_send_eth) + new_info._calls = copy.deepcopy(self._calls) + new_info._reads = copy.deepcopy(self._reads) + new_info._reads_prior_calls = copy.deepcopy(self._reads_prior_calls) + new_info._events = copy.deepcopy(self._events) + new_info._written = copy.deepcopy(self._written) + return new_info + + +class DomainVariant(Enum): + BOTTOM = auto() + TOP = auto() + STATE = auto() + + +class ReentrancyDomain(Domain): + def __init__(self, variant: DomainVariant, state: Optional[ReentrancyInfo] = None): + self.variant = variant + self.state = state or ReentrancyInfo() + + @classmethod + def bottom(cls) -> "ReentrancyDomain": + return cls(DomainVariant.BOTTOM) + + @classmethod + def top(cls) -> "ReentrancyDomain": + return cls(DomainVariant.TOP) + + @classmethod + def with_state(cls, info: ReentrancyInfo) -> "ReentrancyDomain": + return cls(DomainVariant.STATE, info) + + def join(self, other: "ReentrancyDomain") -> bool: + # TOP || BOTTOM + 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() + + 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) + + return True + + else: + self.variant = DomainVariant.TOP + + return True + + +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, functions: List[Function] + ): + self.transfer_function_helper(node, domain, operation, functions) + + def transfer_function_helper( + self, + node: Node, + domain: ReentrancyDomain, + operation: Operation, + functions: List[Function], + 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 = ReentrancyInfo() + self._analyze_operation_by_type( + operation, domain, node, functions, private_functions_seen + ) + return + elif domain.variant == DomainVariant.TOP: + return + elif domain.variant == DomainVariant.STATE: + self._analyze_operation_by_type( + operation, domain, node, functions, private_functions_seen + ) + + def _analyze_operation_by_type( + self, + operation: Operation, + domain: ReentrancyDomain, + node: Node, + functions: List[Function], + private_functions_seen: Set[Function], + ): + + # events -- second case in caracal + if isinstance(operation, EventCall): + self._handle_event_call_operation(operation, domain) + + # internal calls -- third case in caracal + elif isinstance(operation, InternalCall): + self._handle_internal_call_operation(operation, domain, private_functions_seen) + + # abi calls -- fourth case in caracal + elif isinstance(operation, (HighLevelCall, LowLevelCall, Transfer, Send)): + self._handle_abi_call_contract_operation(operation, domain, node) + + self._handle_storage(domain, node) + + def _handle_storage(self, domain: ReentrancyDomain, node: Node): + for var in node.state_variables_read: + if isinstance(var, StateVariable): + domain.state.reads[var.canonical_name].add(node) + for var in node.state_variables_written: + if isinstance(var, StateVariable) and var.is_stored: + domain.state.written[var.canonical_name].add(node) + + 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, + [function], + private_functions_seen, + ) + + def _handle_abi_call_contract_operation( + self, + operation: Union[LowLevelCall, HighLevelCall, Send, Transfer], + domain: ReentrancyDomain, + node: Node, + ): + + domain.state.calls[node].add(operation.node) + + # Track variables read before this specific call + vars_read_before_call: set[str] = set() + for var in domain.state.reads.keys(): + vars_read_before_call.add(var) + + domain.state.reads_prior_calls[node] = vars_read_before_call + + # Check if the call sends ETH + + if operation.can_send_eth: + if isinstance(operation, (Send, Transfer)): + domain.state.safe_send_eth[node].add(operation.node) + else: + domain.state.send_eth[node].add(operation.node) + + def _handle_event_call_operation(self, operation: EventCall, domain: ReentrancyDomain): + calls_before_events = set() + for calls_set in domain.state.calls.values(): + calls_before_events.update(calls_set) + domain.state.events[operation].add(operation.node) + + if calls_before_events: + for call_node in calls_before_events: + domain.state.calls[operation.node].add(call_node) diff --git a/slither/analyses/data_flow/engine/direction.py b/slither/analyses/data_flow/engine/direction.py index 9471517ebc..d65741b22f 100644 --- a/slither/analyses/data_flow/engine/direction.py +++ b/slither/analyses/data_flow/engine/direction.py @@ -3,12 +3,9 @@ from typing import TYPE_CHECKING, Deque, Dict, List, Optional, Set, Union if TYPE_CHECKING: - from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.declarations import Contract - - -from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState + from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState from slither.analyses.data_flow.engine.domain import Domain from slither.core.cfg.node import Node, NodeType from slither.core.declarations.function import Function 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..6dbbe61936 --- /dev/null +++ b/slither/detectors/reentrancy_df/reentrancy_eth_df.py @@ -0,0 +1,289 @@ +""" " +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]]: + """ + Per-function reentrancy detection using data flow analysis information. + The data flow analysis already tracks: + - Variables read before calls (reads_prior_calls) + - Variables written (written) + - Calls that send ETH (send_eth) + """ + result: Dict[FindingKey, Set[FindingValue]] = defaultdict(set) + + for contract in self.contracts: + variables_used_in_reentrancy = contract.state_variables_used_in_reentrant_targets + + # Get all implemented functions for this contract + functions = [ + f + for f in contract.functions_and_modifiers_declared + if f.is_implemented and not f.is_constructor + ] + + for f in functions: + + # Run separate analysis for each function + engine = Engine.new(analysis=ReentrancyAnalysis(), functions=[f]) + engine.run_analysis() + engine_result = engine.result() + + vulnerable_findings: Set[FindingValue] = set() + function_calls = {} + function_send_eth = {} + + # Process results for this specific function + 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 + + # Collect call information + for call_node, call_destinations in state.calls.items(): + if call_node not in function_calls: + function_calls[call_node] = set() + function_calls[call_node].update(call_destinations) + + for send_node, send_destinations in state.send_eth.items(): + if send_node not in function_send_eth: + function_send_eth[send_node] = set() + function_send_eth[send_node].update(send_destinations) + + # Check for reentrancy vulnerabilities using data flow information + if ( + (state.send_eth or state.safe_send_eth) + and state.written + and state.reads_prior_calls + ): + # For each variable that was read before a call + for call_node, vars_read_before_call in state.reads_prior_calls.items(): + for var_canonical_name in vars_read_before_call: + # Find the actual StateVariable by canonical name + var = None + for state_var in contract.state_variables: + if state_var.canonical_name == var_canonical_name: + var = state_var + break + + if not var or not isinstance(var, StateVariable): + continue + + # Check if this variable is written anywhere + if var_canonical_name in state.written: + writing_nodes = state.written[var_canonical_name] + + # Filter out entry points + non_entry_writing_nodes = { + node for node in writing_nodes if node != f.entry_point + } + + if not non_entry_writing_nodes: + continue + + cross_functions = variables_used_in_reentrancy.get(var, []) + if isinstance(cross_functions, set): + cross_functions = list(cross_functions) + + # Use the first writing node as the main node + main_node = min( + non_entry_writing_nodes, key=lambda x: x.node_id + ) + + 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))), + ) + vulnerable_findings.add(finding_value) + + if vulnerable_findings: + for finding in vulnerable_findings: + print(f"Variable: {finding.variable.name}") + print(f"Node: {finding.node.node_id}") + print(f"Nodes: {len(finding.nodes)}") + for node in finding.nodes: + print(f"\t{node.node_id}") + print(f"Cross functions: {len(finding.cross_functions)}") + for cross_function in finding.cross_functions: + print(f"\t{cross_function.name}") + + print("--------------------------------") + + 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]: # pylint: disable=too-many-branches,too-many-locals + """""" + super()._detect() + + reentrancies = self.find_reentrancies() + + results = [] + + result_sorted = sorted(list(reentrancies.items()), key=lambda x: x[0].function.name) + varsWritten: List[FindingValue] + varsWrittenSet: Set[FindingValue] + for (func, calls, send_eth), varsWrittenSet in result_sorted: + calls = sorted(list(set(calls)), key=lambda x: x[0].node_id) + send_eth = sorted(list(set(send_eth)), 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_info, calls_list in calls: + info += ["\t- ", call_info, "\n"] + for call_list_info in calls_list: + if call_list_info != call_info: + info += ["\t\t- ", call_list_info, "\n"] + if calls != send_eth and send_eth: + info += ["\tExternal calls sending eth:\n"] + for call_info, calls_list in send_eth: + info += ["\t- ", call_info, "\n"] + for call_list_info in calls_list: + if call_list_info != call_info: + info += ["\t\t- ", call_list_info, "\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"] + + # Create our JSON result + res = self.generate_result(info) + + # Add the function with the re-entrancy first + res.add(func) + + # Add all underlying calls in the function which are potentially problematic. + for call_info, calls_list in calls: + res.add(call_info, {"underlying_type": "external_calls"}) + for call_list_info in calls_list: + if call_list_info != call_info: + res.add( + call_list_info, + {"underlying_type": "external_calls_sending_eth"}, + ) + + # If the calls are not the same ones that send eth, add the eth sending nodes. + if calls != send_eth: + for call_info, calls_list in send_eth: + res.add(call_info, {"underlying_type": "external_calls_sending_eth"}) + for call_list_info in calls_list: + if call_list_info != call_info: + res.add( + call_list_info, + {"underlying_type": "external_calls_sending_eth"}, + ) + + # Add all variables written via nodes which write them. + 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, + }, + ) + + # Append our result + results.append(res) + + return results 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 } - From a4d8582c58baae2b21cd8cf8af72ec980daa3a88 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Tue, 19 Aug 2025 13:43:53 -0400 Subject: [PATCH 05/17] Cleaning --- .../data_flow/analyses/reentrancy/safe.py | 319 ------------------ 1 file changed, 319 deletions(-) delete mode 100644 slither/analyses/data_flow/analyses/reentrancy/safe.py diff --git a/slither/analyses/data_flow/analyses/reentrancy/safe.py b/slither/analyses/data_flow/analyses/reentrancy/safe.py deleted file mode 100644 index d0bfd48f65..0000000000 --- a/slither/analyses/data_flow/analyses/reentrancy/safe.py +++ /dev/null @@ -1,319 +0,0 @@ -from enum import Enum, auto -from typing import Dict, List, Optional, Set, Union -from collections import defaultdict -import copy - - -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 import ( - EventCall, - HighLevelCall, - InternalCall, - LowLevelCall, - Operation, - Send, - Transfer, -) - - -class ReentrancyInfo: - 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) - - @property - def send_eth(self) -> Dict[Node, Set[Node]]: - """Return the list of calls sending value (unsafe calls only)""" - return self._send_eth - - @property - def safe_send_eth(self) -> Dict[Node, Set[Node]]: - """Return the list of safe ETH transfers (Send/Transfer operations)""" - return self._safe_send_eth - - @property - def all_eth_calls(self) -> Dict[Node, Set[Node]]: - """Return all ETH-sending calls (both safe and unsafe) - for other analyses""" - 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 the list of calls that can callback""" - return self._calls - - @property - def reads(self) -> Dict[str, Set[Node]]: - """Return of variables that are read""" - return self._reads - - @property - def written(self) -> Dict[str, Set[Node]]: - """Return of variables that are written""" - return self._written - - @property - def reads_prior_calls(self) -> Dict[Node, Set[str]]: - """Return the dictionary node -> variables read before any call""" - return self._reads_prior_calls - - @property - def events(self) -> Dict[EventCall, Set[Node]]: - """Return the list of events""" - return self._events - - def __eq__(self, other): - if not isinstance(other, ReentrancyInfo): - 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 - ) - - 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()), - ) - ) - - def __str__(self): - return ( - f"ReentrancyInfo(\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")" - ) - - def deep_copy(self) -> "ReentrancyInfo": - """Create a deep copy of this ReentrancyInfo object""" - new_info = ReentrancyInfo() - new_info._send_eth = copy.deepcopy(self._send_eth) - new_info._safe_send_eth = copy.deepcopy(self._safe_send_eth) - new_info._calls = copy.deepcopy(self._calls) - new_info._reads = copy.deepcopy(self._reads) - new_info._reads_prior_calls = copy.deepcopy(self._reads_prior_calls) - new_info._events = copy.deepcopy(self._events) - new_info._written = copy.deepcopy(self._written) - return new_info - - -class DomainVariant(Enum): - BOTTOM = auto() - TOP = auto() - STATE = auto() - - -class ReentrancyDomain(Domain): - def __init__(self, variant: DomainVariant, state: Optional[ReentrancyInfo] = None): - self.variant = variant - self.state = state or ReentrancyInfo() - - @classmethod - def bottom(cls) -> "ReentrancyDomain": - return cls(DomainVariant.BOTTOM) - - @classmethod - def top(cls) -> "ReentrancyDomain": - return cls(DomainVariant.TOP) - - @classmethod - def with_state(cls, info: ReentrancyInfo) -> "ReentrancyDomain": - return cls(DomainVariant.STATE, info) - - def join(self, other: "ReentrancyDomain") -> bool: - # TOP || BOTTOM - 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() - - 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) - - return True - - else: - self.variant = DomainVariant.TOP - - return True - - -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, functions: List[Function] - ): - self.transfer_function_helper(node, domain, operation, functions) - - def transfer_function_helper( - self, - node: Node, - domain: ReentrancyDomain, - operation: Operation, - functions: List[Function], - 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 = ReentrancyInfo() - self._analyze_operation_by_type( - operation, domain, node, functions, private_functions_seen - ) - return - elif domain.variant == DomainVariant.TOP: - return - elif domain.variant == DomainVariant.STATE: - self._analyze_operation_by_type( - operation, domain, node, functions, private_functions_seen - ) - - def _analyze_operation_by_type( - self, - operation: Operation, - domain: ReentrancyDomain, - node: Node, - functions: List[Function], - private_functions_seen: Set[Function], - ): - - # events -- second case in caracal - if isinstance(operation, EventCall): - self._handle_event_call_operation(operation, domain) - - # internal calls -- third case in caracal - elif isinstance(operation, InternalCall): - self._handle_internal_call_operation(operation, domain, private_functions_seen) - - # abi calls -- fourth case in caracal - elif isinstance(operation, (HighLevelCall, LowLevelCall, Transfer, Send)): - self._handle_abi_call_contract_operation(operation, domain, node) - - self._handle_storage(domain, node) - - def _handle_storage(self, domain: ReentrancyDomain, node: Node): - for var in node.state_variables_read: - if isinstance(var, StateVariable): - domain.state.reads[var.canonical_name].add(node) - for var in node.state_variables_written: - if isinstance(var, StateVariable) and var.is_stored: - domain.state.written[var.canonical_name].add(node) - - 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, - [function], - private_functions_seen, - ) - - def _handle_abi_call_contract_operation( - self, - operation: Union[LowLevelCall, HighLevelCall, Send, Transfer], - domain: ReentrancyDomain, - node: Node, - ): - - domain.state.calls[node].add(operation.node) - - # Track variables read before this specific call - vars_read_before_call: set[str] = set() - for var in domain.state.reads.keys(): - vars_read_before_call.add(var) - - domain.state.reads_prior_calls[node] = vars_read_before_call - - # Check if the call sends ETH - - if operation.can_send_eth: - if isinstance(operation, (Send, Transfer)): - domain.state.safe_send_eth[node].add(operation.node) - else: - domain.state.send_eth[node].add(operation.node) - - def _handle_event_call_operation(self, operation: EventCall, domain: ReentrancyDomain): - calls_before_events = set() - for calls_set in domain.state.calls.values(): - calls_before_events.update(calls_set) - domain.state.events[operation].add(operation.node) - - if calls_before_events: - for call_node in calls_before_events: - domain.state.calls[operation.node].add(call_node) From 17c9c7c0495affd4cbfc0adad390f1e5df4b6ce1 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Wed, 20 Aug 2025 13:57:20 -0400 Subject: [PATCH 06/17] Adding --- .../analyses/reentrancy/analysis/analysis.py | 59 ++--- .../analyses/reentrancy/core/state.py | 57 +++-- .../reentrancy_df/reentrancy_eth_df.py | 218 +++++++----------- 3 files changed, 160 insertions(+), 174 deletions(-) diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py index f46c6f8fc8..fd9f9b0366 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py @@ -51,13 +51,8 @@ def transfer_function_helper( if domain.variant == DomainVariant.BOTTOM: domain.variant = DomainVariant.STATE domain.state = State() - self._analyze_operation_by_type( - operation, domain, node, functions, private_functions_seen - ) - elif domain.variant == DomainVariant.STATE: - self._analyze_operation_by_type( - operation, domain, node, functions, private_functions_seen - ) + + self._analyze_operation_by_type(operation, domain, node, functions, private_functions_seen) def _analyze_operation_by_type( self, @@ -78,19 +73,26 @@ def _analyze_operation_by_type( 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): - domain.state.reads[var.canonical_name].add(node) + 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.written[var.canonical_name].add(node) + domain.state.add_written(var, node) def _update_writes_after_calls(self, domain: ReentrancyDomain, node: Node): - # Track state writes after external calls + # Writes after any external call if node in domain.state.calls: for var_name, write_nodes in domain.state.written.items(): - if write_nodes: - domain.state.writes_after_calls[var_name].update(write_nodes) + 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, @@ -114,9 +116,9 @@ def _handle_internal_call_operation( [function], private_functions_seen, ) - # Mark cross-function reentrancy + # Mark cross-function reentrancy for written variables for var_name in domain.state.written.keys(): - domain.state.cross_function[var_name].add(function) + domain.state.add_cross_function(var_name, function) def _handle_abi_call_contract_operation( self, @@ -124,20 +126,25 @@ def _handle_abi_call_contract_operation( domain: ReentrancyDomain, node: Node, ): - domain.state.calls[node].add(operation.node) - vars_read_before_call = set(domain.state.reads.keys()) - domain.state.reads_prior_calls[node] = vars_read_before_call + # 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 isinstance(operation, (Send, Transfer)): - domain.state.safe_send_eth[node].add(operation.node) - else: - domain.state.send_eth[node].add(operation.node) + 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): - calls_before_events = set() + # 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()) for calls_set in domain.state.calls.values(): - calls_before_events.update(calls_set) - domain.state.events[operation].add(operation.node) - for call_node in calls_before_events: - domain.state.calls[operation.node].add(call_node) + for call_node in calls_set: + if call_node not in existing_calls: + domain.state.add_call(operation.node, call_node) + domain.state.add_event(operation, operation.node) diff --git a/slither/analyses/data_flow/analyses/reentrancy/core/state.py b/slither/analyses/data_flow/analyses/reentrancy/core/state.py index f266d7884c..50ecbc7f53 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/core/state.py +++ b/slither/analyses/data_flow/analyses/reentrancy/core/state.py @@ -1,6 +1,6 @@ from collections import defaultdict import copy -from typing import Dict, Set +from typing import Dict, Set, Union from slither.core.cfg.node import Node from slither.core.declarations.function import Function @@ -17,11 +17,38 @@ def __init__(self): 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) - - # New attributes 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): + self._written[var.canonical_name].add(node) + + def add_read(self, var: StateVariable, node: Node): + 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 @@ -59,6 +86,7 @@ def reads_prior_calls(self) -> Dict[Node, Set[str]]: def events(self) -> Dict[EventCall, Set[Node]]: return self._events + # -------------------- Utilities -------------------- def __eq__(self, other): if not isinstance(other, State): return False @@ -105,14 +133,15 @@ def __str__(self): ) def deep_copy(self) -> "State": - new_info = State() - new_info._send_eth = copy.deepcopy(self._send_eth) - new_info._safe_send_eth = copy.deepcopy(self._safe_send_eth) - new_info._calls = copy.deepcopy(self._calls) - new_info._reads = copy.deepcopy(self._reads) - new_info._reads_prior_calls = copy.deepcopy(self._reads_prior_calls) - new_info._events = copy.deepcopy(self._events) - new_info._written = copy.deepcopy(self._written) - new_info.writes_after_calls = copy.deepcopy(self.writes_after_calls) - new_info.cross_function = copy.deepcopy(self.cross_function) - return new_info + new_state = State() + new_state._send_eth = copy.deepcopy(self._send_eth) + new_state._safe_send_eth = copy.deepcopy(self._safe_send_eth) + new_state._calls = copy.deepcopy(self._calls) + new_state._reads = copy.deepcopy(self._reads) + new_state._reads_prior_calls = copy.deepcopy(self._reads_prior_calls) + new_state._events = copy.deepcopy(self._events) + new_state._written = copy.deepcopy(self._written) + new_state.writes_after_calls = copy.deepcopy(self.writes_after_calls) + new_state.cross_function = copy.deepcopy(self.cross_function) + return new_state + diff --git a/slither/detectors/reentrancy_df/reentrancy_eth_df.py b/slither/detectors/reentrancy_df/reentrancy_eth_df.py index 6dbbe61936..4e105d1d77 100644 --- a/slither/detectors/reentrancy_df/reentrancy_eth_df.py +++ b/slither/detectors/reentrancy_df/reentrancy_eth_df.py @@ -1,10 +1,9 @@ -""" " +""" 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 @@ -12,8 +11,6 @@ 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 @@ -29,53 +26,42 @@ class ReentrancyEthDF(AbstractDetector): 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`)""" + 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; + 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; } - 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.""" + 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)." - + 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]]: - """ - Per-function reentrancy detection using data flow analysis information. - The data flow analysis already tracks: - - Variables read before calls (reads_prior_calls) - - Variables written (written) - - Calls that send ETH (send_eth) - """ + """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 - - # Get all implemented functions for this contract functions = [ f for f in contract.functions_and_modifiers_declared @@ -83,17 +69,13 @@ def find_reentrancies(self) -> Dict[FindingKey, Set[FindingValue]]: ] for f in functions: - - # Run separate analysis for each function engine = Engine.new(analysis=ReentrancyAnalysis(), functions=[f]) engine.run_analysis() engine_result = engine.result() - vulnerable_findings: Set[FindingValue] = set() function_calls = {} function_send_eth = {} - # Process results for this specific function for node in f.nodes: if node not in engine_result: continue @@ -103,86 +85,63 @@ def find_reentrancies(self) -> Dict[FindingKey, Set[FindingValue]]: analysis.post, ReentrancyDomain ): continue - if analysis.post.variant != DomainVariant.STATE: continue state = analysis.post.state - # Collect call information + # Track calls and sends for call_node, call_destinations in state.calls.items(): - if call_node not in function_calls: - function_calls[call_node] = set() - function_calls[call_node].update(call_destinations) - + function_calls.setdefault(call_node, set()).update(call_destinations) for send_node, send_destinations in state.send_eth.items(): - if send_node not in function_send_eth: - function_send_eth[send_node] = set() - function_send_eth[send_node].update(send_destinations) + function_send_eth.setdefault(send_node, set()).update(send_destinations) - # Check for reentrancy vulnerabilities using data flow information + # Detect variables read before calls and written after if ( (state.send_eth or state.safe_send_eth) and state.written and state.reads_prior_calls ): - # For each variable that was read before a call for call_node, vars_read_before_call in state.reads_prior_calls.items(): for var_canonical_name in vars_read_before_call: - # Find the actual StateVariable by canonical name - var = None - for state_var in contract.state_variables: - if state_var.canonical_name == var_canonical_name: - var = state_var - break - + var = next( + ( + sv + for sv in contract.state_variables + if sv.canonical_name == var_canonical_name + ), + None, + ) if not var or not isinstance(var, StateVariable): continue - - # Check if this variable is written anywhere if var_canonical_name in state.written: writing_nodes = state.written[var_canonical_name] - - # Filter out entry points non_entry_writing_nodes = { - node for node in writing_nodes if node != f.entry_point + n for n in writing_nodes if n != f.entry_point } - if not non_entry_writing_nodes: continue - cross_functions = variables_used_in_reentrancy.get(var, []) - if isinstance(cross_functions, set): - cross_functions = list(cross_functions) - - # Use the first writing node as the main node - main_node = min( - non_entry_writing_nodes, key=lambda x: x.node_id - ) - - 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))), - ) - vulnerable_findings.add(finding_value) + if f.is_reentrant or var in variables_used_in_reentrancy: + cross_functions = variables_used_in_reentrancy.get(var, []) + if isinstance(cross_functions, set): + cross_functions = list(cross_functions) + main_node = min( + non_entry_writing_nodes, key=lambda x: x.node_id + ) + 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))), + ) + vulnerable_findings.add(finding_value) if vulnerable_findings: - for finding in vulnerable_findings: - print(f"Variable: {finding.variable.name}") - print(f"Node: {finding.node.node_id}") - print(f"Nodes: {len(finding.nodes)}") - for node in finding.nodes: - print(f"\t{node.node_id}") - print(f"Cross functions: {len(finding.cross_functions)}") - for cross_function in finding.cross_functions: - print(f"\t{cross_function.name}") - - print("--------------------------------") - finding_key = FindingKey( function=f, calls=to_hashable(function_calls), @@ -192,36 +151,42 @@ def find_reentrancies(self) -> Dict[FindingKey, Set[FindingValue]]: return result - def _detect(self) -> List[Output]: # pylint: disable=too-many-branches,too-many-locals - """""" + 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) - varsWritten: List[FindingValue] - varsWrittenSet: Set[FindingValue] + for (func, calls, send_eth), varsWrittenSet in result_sorted: - calls = sorted(list(set(calls)), key=lambda x: x[0].node_id) - send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id) + 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_info, calls_list in calls: - info += ["\t- ", call_info, "\n"] - for call_list_info in calls_list: - if call_list_info != call_info: - info += ["\t\t- ", call_list_info, "\n"] - if calls != send_eth and send_eth: + 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 call_info, calls_list in send_eth: - info += ["\t- ", call_info, "\n"] - for call_list_info in calls_list: - if call_list_info != call_info: - info += ["\t\t- ", call_list_info, "\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"] @@ -237,34 +202,21 @@ def _detect(self) -> List[Output]: # pylint: disable=too-many-branches,too-many for cross in finding_value.cross_functions: info += ["\t- ", cross, "\n"] - # Create our JSON result res = self.generate_result(info) - - # Add the function with the re-entrancy first 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"}) - # Add all underlying calls in the function which are potentially problematic. - for call_info, calls_list in calls: - res.add(call_info, {"underlying_type": "external_calls"}) - for call_list_info in calls_list: - if call_list_info != call_info: - res.add( - call_list_info, - {"underlying_type": "external_calls_sending_eth"}, - ) - - # If the calls are not the same ones that send eth, add the eth sending nodes. - if calls != send_eth: - for call_info, calls_list in send_eth: - res.add(call_info, {"underlying_type": "external_calls_sending_eth"}) - for call_list_info in calls_list: - if call_list_info != call_info: - res.add( - call_list_info, - {"underlying_type": "external_calls_sending_eth"}, - ) - - # Add all variables written via nodes which write them. for finding_value in varsWritten: res.add( finding_value.node, @@ -282,8 +234,6 @@ def _detect(self) -> List[Output]: # pylint: disable=too-many-branches,too-many "variable_name": finding_value.variable.name, }, ) - - # Append our result results.append(res) return results From 7805cabe195bef58808aaff35027935e42f89050 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Wed, 20 Aug 2025 14:08:21 -0400 Subject: [PATCH 07/17] Cleaning and commenting --- .../reentrancy_df/reentrancy_eth_df.py | 99 +++++++++++-------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/slither/detectors/reentrancy_df/reentrancy_eth_df.py b/slither/detectors/reentrancy_df/reentrancy_eth_df.py index 4e105d1d77..70ed920cc4 100644 --- a/slither/detectors/reentrancy_df/reentrancy_eth_df.py +++ b/slither/detectors/reentrancy_df/reentrancy_eth_df.py @@ -96,50 +96,69 @@ def find_reentrancies(self) -> Dict[FindingKey, Set[FindingValue]]: for send_node, send_destinations in state.send_eth.items(): function_send_eth.setdefault(send_node, set()).update(send_destinations) - # Detect variables read before calls and written after - if ( + # 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 ): - for call_node, vars_read_before_call in state.reads_prior_calls.items(): - for var_canonical_name in vars_read_before_call: - var = next( - ( - sv - for sv in contract.state_variables - if sv.canonical_name == var_canonical_name - ), - None, - ) - if not var or not isinstance(var, StateVariable): - continue - if var_canonical_name in state.written: - writing_nodes = state.written[var_canonical_name] - non_entry_writing_nodes = { - n for n in writing_nodes if n != f.entry_point - } - if not non_entry_writing_nodes: - continue - - if f.is_reentrant or var in variables_used_in_reentrancy: - cross_functions = variables_used_in_reentrancy.get(var, []) - if isinstance(cross_functions, set): - cross_functions = list(cross_functions) - main_node = min( - non_entry_writing_nodes, key=lambda x: x.node_id - ) - 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))), - ) - vulnerable_findings.add(finding_value) + 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( From 0a3c445903bc56a6c76dca866763f0d0a77cc9a9 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Thu, 21 Aug 2025 08:35:10 -0400 Subject: [PATCH 08/17] Mininmal direction --- .../analyses/data_flow/engine/direction.py | 202 +----------------- 1 file changed, 7 insertions(+), 195 deletions(-) diff --git a/slither/analyses/data_flow/engine/direction.py b/slither/analyses/data_flow/engine/direction.py index d65741b22f..ad6c1e5ed5 100644 --- a/slither/analyses/data_flow/engine/direction.py +++ b/slither/analyses/data_flow/engine/direction.py @@ -33,188 +33,12 @@ def apply_transfer_function( class Forward(Direction): def __init__(self): - self._numeric_literals_extracted = False - self._loop_iteration_counts: Dict[int, int] = {} - self._loop_previous_states: Dict[int, Domain] = {} # Track previous state for each loop - self._set_b_cardinality: int = 0 - self._set_b: Set[int] = set() + pass @property def IS_FORWARD(self) -> bool: return True - def _extract_condition(self, node: Node) -> Optional[Binary]: - """Extract comparison condition from IF node.""" - if node.type != NodeType.IF: - return None - - for operation in node.irs or []: - if isinstance(operation, Binary) and operation.type in [ - BinaryType.GREATER, - BinaryType.GREATER_EQUAL, - BinaryType.LESS, - BinaryType.LESS_EQUAL, - BinaryType.EQUAL, - BinaryType.NOT_EQUAL, - ]: - return operation - return None - - def _is_bottom_domain(self, domain: Domain) -> bool: - """Check if the domain is BOTTOM (unreachable state).""" - return ( - hasattr(domain, "variant") - and hasattr(domain.variant, "name") - and domain.variant.name == "BOTTOM" - ) - - def _handle_ifloop_node( - self, - node: Node, - current_state: "AnalysisState", - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - analysis: "Analysis", - ) -> bool: - """Handle IFLOOP nodes with iteration limiting.""" - if node.type != NodeType.IFLOOP: - return False - - loop_id = node.node_id - current_iteration = self._loop_iteration_counts.setdefault(loop_id, 0) - - # Check if we've exceeded maximum iterations (allow more iterations for widening to converge) - max_iterations = max( - self._set_b_cardinality * 3, 10 - ) # Allow more iterations for convergence - if current_iteration >= max_iterations: - self._exit_loop(node, current_state.pre, worklist, global_state, loop_id) - return True - - # Handle state tracking and widening - if current_iteration == 0: - # First iteration: save current state as previous - self._loop_previous_states[loop_id] = current_state.pre.deep_copy() - else: - # Apply widening with previous state on every iteration after the first - print(f"Widening iteration {current_iteration} with previous state") - previous_state = self._loop_previous_states[loop_id] - widened_state = analysis.apply_widening(current_state.pre, previous_state, self._set_b) - current_state.pre = widened_state - - # Check for convergence (if state hasn't changed significantly) - if self._has_converged(previous_state, current_state.pre): - print(f"🔄 Widening converged after {current_iteration} iterations") - self._exit_loop(node, current_state.pre, worklist, global_state, loop_id) - return True - - # Update previous state for next iteration - self._loop_previous_states[loop_id] = current_state.pre.deep_copy() - - # Continue loop iteration - self._continue_loop(node, current_state.pre, worklist, global_state, loop_id) - return True - - def _exit_loop( - self, - node: Node, - state: Domain, - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - loop_id: int, - ) -> None: - """Exit loop by propagating to exit node and resetting counter.""" - if len(node.sons) > 1: - self._propagate_to_successor(node, node.sons[1], state, worklist, global_state) - self._loop_iteration_counts[loop_id] = 0 - # Clear previous state when exiting loop - if loop_id in self._loop_previous_states: - del self._loop_previous_states[loop_id] - - def _continue_loop( - self, - node: Node, - state: Domain, - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - loop_id: int, - ) -> None: - """Continue loop by propagating to body node and incrementing counter.""" - self._loop_iteration_counts[loop_id] += 1 - if node.sons: - self._propagate_to_successor(node, node.sons[0], state, worklist, global_state) - - def _propagate_to_successor( - self, - node: Node, - successor: Node, - filtered_state: Domain, - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - ) -> None: - """Propagate state to a single successor.""" - if not successor or successor.node_id not in global_state: - return - - # Skip unreachable branches - if self._is_bottom_domain(filtered_state): - self._handle_unreachable_branch(successor, filtered_state, worklist, global_state) - return - - # Join states and update worklist - son_state = global_state[successor.node_id] - if son_state.pre.join(filtered_state) and successor not in worklist: - worklist.append(successor) - - def _handle_unreachable_branch( - self, - successor: Node, - bottom_state: Domain, - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - ) -> None: - """Handle propagation to unreachable branches.""" - - # Remove from worklist (except ENDIF nodes) - if successor in worklist and successor.type != NodeType.ENDIF: - worklist.remove(successor) - - # Mark as unreachable - global_state[successor.node_id].pre = bottom_state - - def _propagate_conditional( - self, - node: Node, - current_state: "AnalysisState", - condition: Binary, - analysis: "Analysis", - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - ) -> None: - """Handle conditional propagation for IF nodes.""" - if len(node.sons) < 2: - return - - true_successor, false_successor = node.sons[0], node.sons[1] - - # Apply conditions and propagate - true_state = analysis.apply_condition(current_state.pre, condition, True) - false_state = analysis.apply_condition(current_state.pre, condition, False) - - self._propagate_to_successor(node, true_successor, true_state, worklist, global_state) - self._propagate_to_successor(node, false_successor, false_state, worklist, global_state) - - def _propagate_regular( - self, - node: Node, - current_state: "AnalysisState", - worklist: Deque[Node], - global_state: Dict[int, "AnalysisState[A]"], - ) -> None: - """Handle regular (non-conditional) propagation.""" - for successor in node.sons: - self._propagate_to_successor(node, successor, current_state.pre, worklist, global_state) - def apply_transfer_function( self, analysis: "Analysis", @@ -224,7 +48,6 @@ def apply_transfer_function( global_state: Dict[int, "AnalysisState[A]"], functions: List[Function], ): - # Apply transfer function to current node for operation in node.irs or [None]: analysis.transfer_function( @@ -234,23 +57,12 @@ def apply_transfer_function( # Set post state global_state[node.node_id].post = current_state.pre - # Handle IFLOOP nodes specially - if self._handle_ifloop_node(node, current_state, worklist, global_state, analysis): - return - - # Handle propagation based on node type - condition = self._extract_condition(node) - if condition and len(node.sons) >= 2: - self._propagate_conditional( - node, current_state, condition, analysis, worklist, global_state - ) - else: - self._propagate_regular(node, current_state, worklist, global_state) - - def _has_converged(self, previous_state: "Domain", current_state: "Domain") -> bool: - """Check if the widening has converged by comparing states.""" - # Simple convergence check: if states are equal, we've converged - return previous_state == current_state + # 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): From 9bce0fcc95b6f18ff95e69e3fef77dca2c34a3bf Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Thu, 21 Aug 2025 08:58:36 -0400 Subject: [PATCH 09/17] Adding test files --- ...ctor_ReentrancyEthDF_0_4_25_DAO_sol__0.txt | 72 + ...thDF_0_4_25_reentrancy_indirect_sol__0.txt | 15 + ...entrancyEthDF_0_4_25_reentrancy_sol__0.txt | 32 + ...thDF_0_5_16_reentrancy_indirect_sol__0.txt | 15 + ...entrancyEthDF_0_5_16_reentrancy_sol__0.txt | 28 + ...thDF_0_6_11_reentrancy_indirect_sol__0.txt | 15 + ...entrancyEthDF_0_6_11_reentrancy_sol__0.txt | 28 + ...EthDF_0_7_6_reentrancy_indirect_sol__0.txt | 15 + ...eentrancyEthDF_0_7_6_reentrancy_sol__0.txt | 28 + ...10_reentrancy_filtered_comments_sol__0.txt | 9 + ...0_reentrancy_with_non_reentrant_sol__0.txt | 32 + .../reentrancy-eth-df/0.4.25/DAO.sol | 1242 +++++++++++++++++ .../0.4.25/DAO.sol-0.4.25.zip | Bin 0 -> 59007 bytes .../reentrancy-eth-df/0.4.25/reentrancy.sol | 100 ++ .../0.4.25/reentrancy.sol-0.4.25.zip | Bin 0 -> 6493 bytes .../0.4.25/reentrancy_indirect.sol | 31 + .../0.4.25/reentrancy_indirect.sol-0.4.25.zip | Bin 0 -> 4183 bytes .../reentrancy-eth-df/0.5.16/reentrancy.sol | 65 + .../0.5.16/reentrancy.sol-0.5.16.zip | Bin 0 -> 5372 bytes .../0.5.16/reentrancy_indirect.sol | 31 + .../0.5.16/reentrancy_indirect.sol-0.5.16.zip | Bin 0 -> 4199 bytes .../reentrancy-eth-df/0.6.11/reentrancy.sol | 65 + .../0.6.11/reentrancy.sol-0.6.11.zip | Bin 0 -> 5304 bytes .../0.6.11/reentrancy_indirect.sol | 31 + .../0.6.11/reentrancy_indirect.sol-0.6.11.zip | Bin 0 -> 4202 bytes .../reentrancy-eth-df/0.7.6/reentrancy.sol | 65 + .../0.7.6/reentrancy.sol-0.7.6.zip | Bin 0 -> 5194 bytes .../0.7.6/reentrancy_indirect.sol | 31 + .../0.7.6/reentrancy_indirect.sol-0.7.6.zip | Bin 0 -> 4119 bytes .../0.8.10/reentrancy_filtered_comments.sol | 21 + ...eentrancy_filtered_comments.sol-0.8.10.zip | Bin 0 -> 3423 bytes .../0.8.10/reentrancy_with_non_reentrant.sol | 151 ++ ...entrancy_with_non_reentrant.sol-0.8.10.zip | Bin 0 -> 9260 bytes tests/e2e/detectors/test_detectors.py | 47 + 34 files changed, 2169 insertions(+) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_DAO_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_indirect_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_4_25_reentrancy_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_indirect_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_5_16_reentrancy_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_indirect_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_6_11_reentrancy_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_indirect_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_7_6_reentrancy_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_filtered_comments_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_ReentrancyEthDF_0_8_10_reentrancy_with_non_reentrant_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/DAO.sol-0.4.25.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy.sol-0.4.25.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.4.25/reentrancy_indirect.sol-0.4.25.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy.sol-0.5.16.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.5.16/reentrancy_indirect.sol-0.5.16.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy.sol-0.6.11.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.6.11/reentrancy_indirect.sol-0.6.11.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy.sol-0.7.6.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.7.6/reentrancy_indirect.sol-0.7.6.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_filtered_comments.sol-0.8.10.zip create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol create mode 100644 tests/e2e/detectors/test_data/reentrancy-eth-df/0.8.10/reentrancy_with_non_reentrant.sol-0.8.10.zip 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 0000000000000000000000000000000000000000..5b8fb4fb7f1f75ce422aba820b74ca7b24b889c4 GIT binary patch literal 59007 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

7$y>tM0|eG;)5=E*5&mQP>QO;R_saZRK=XC+s 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 M0000W<^TWy0KMGJD*ylh literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1fa87b48a7e165a4227639365816396c8908380c GIT binary patch literal 6493 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 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 0000000000000000000000000000000000000000..ac63e710dee05e3a7c5125cd8d43ec70a8d75608 GIT binary patch literal 4183 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 K`S(5m0R9i#{{F)N literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c105dd00e994c379f100561ee7be0a812a74596c GIT binary patch literal 5372 zcmai&MOYLJptT2S7*e{sySr1A?#2OuVL)o=knYZ*1f)Ycq!k(I4jH;b!Xc#L|L(uP z&pl`HZqD+oUR^CzG^$B4suq}icZ*$s!Orq~1@c}EnnfG>gfmZ0L@UGJ}m zZ!~`}?1EZywl0esXMVN$*&f>k?BEBV<+h(>cIT>ue-^$U zwSFYtBsQmFJ2!73%x98HRJCy6HU?fWQ$CRF3brnxAzHxk*58c9PCN5QAIiT?tsaLN zv)3f_3pf-tp^Wo9wP9qRVQR33}?XOHfMXe#HE~X$b zwypK(AJ;MA_di;on>$@CI50BHLOnt#PotUC!|08Z`%ny{+)RHsvk@-8%c&C;^fmjC zmNUxBYLSwT5zgJ6J5+i!%Vx?xM9K&zcKIPIZdT$JZDJMwO)(V z^&*8MfreT+1 zTU`k#G8#E=VM)%eg2tSUDp(b53Bl8DM$TC^oa7W&WEejZ0s_B;Ug8`6{V0~!*uLil zZg=i1{a@?qCBq?fGeb6m5>W_q&yp5lW?|=u_YrlRE$*FB1JnD9F12_onETJ;cjHXQ6>5m8PaVcPOs>^A$R;eR#RgCRU!i!3JV4^VF=w- zW?b9=8{WiZYJC+$T16!F=Hb)9zDV7xI^x?wjPUCvCXqbPKeGmC<=X^(5p#p9P zR}!3&?B8^ynKk8Hh|#mWb#}h0Izzc+lmUrVrp3=ignG?8`M6C4E6I?bVXI zy{i#wfg_~wz~r$02Q-4x0*rVww`_xXJ#MD13&di+!%hl8i@%wPGkwpA+tV_%jtRA& z!Nq!9V0wKFFxTF)3i{?0Hzo2-2w%1&ysW+%(OgCidQw`0NLU&;L~BkN&7Z$r&I z$0YwV)wX{#cTN=TAG6W$;-32He{I-ul)j$z!h=fo_{PgCeVWb`d|zZYg?#+T(sh*b zL3niL2MLo)bJft(0SGskezE25?BVI$o(D!U@sexJi5uA{RPZKYX@AMJq!y|(Z=8bE zlGFYP{e(p*&oG3-YWF({r?6#hR9U`jHu0_d^n}jM_9{=_?E3Q@Jf%kEPB!j4+%Mnc z===z;mLz!lB*s!j9B44d$&R8kPm8=<_Tt8Q6o$6+O+fI*jp9oJV&E1V2@VnLiwgGb z1RJV76D>vRKBGjd;UeN! z!+yxBkK$K47l;nenq+wBmHyY{>urXcl*8?}wNNC&VBom;xjPb3XW6~|)i>))sbPPU zy#Ix3Dx=%rI#<2Sv<^D0Sa`0L&E58t@`Y1#PHO;(SF`EH&nO5vQ*cnu=gK*PQp(_2 zOp9n+zDzXD>8}y50CfCEYj?cyGs}z|GtojjB{h?cU3x0eU&=Nrb4k)7A#l~gajEMZemOYtLWHL@uwwl6 z4-?S|Og){u#;>1ah=$V>R)0fLo zeT`-8w&-%POc{Q@9v%@Z5?i@pg7umGGMf!m*G)B(fxk?`m_bdNgd^Wq-!LR_8 z^}<=pL1pgX?<1WVkx3&5OI)mb65>1?OMEr*o_(|4_7J_KbhFpwh)K4u>4j{&KDU1( z&I%@B7MWtAxt_?97Jm_RR`)N?ZH^r#gBIq%&m9foj-6|;@BA17s1-3{uB)8gpMN!H zPQLhFeS06BBBR|+)$r7o@@}YH88>99b)2RGtT-uz6KA7~mH@fzBrY92hI^2(+lBkN zsokZ|(>B6-F1#R6NR-qZ%TbIoFbgG3x6sO36!S6;WiIOW8s9!#K;nBKn%OI-^7WNr zNrvf#l<>o4oOAd4h9pj&5MtiOlIi!8!9H~#xYd3RUec}s^35>GrPSR0tZ2uA;1ajJ8NK{TT(AB!M#l@eq4mj)|w)kvP`iTsxJ=_ z=gMPVnwTEl9X=Ylg@WbQ?wzE#k=s*qGOp?iWR*34DIiH|TRig;mBzpzsKX?8W^--H zpJQ}jC_4@g9gzH2Bsmmhl}3kC9APx3Oyn?};r7Tg^xN%~5DfR%1?J|1obE9)NrGhk z6Y39lmLF-XY^mb9Z(uyX^EA?xyNg%O7jW+344iKy@}0;`3C)CVMiq~X5?&{9h|VAX zYmP+mK~o^ST+`EufEhJ+8FG%%lu4e%RyZM&+l$l+b}+ANp1V11Fz)K>t>a8W7XXD> zW?yPX`DX5#h&VU)dJuSr?c(de`DRIkUeV?6ZFOLdw*36S%>SgjKD*e zY&pqkkXEgjOouG}8fD4tDH(MFcQ&?{q&>%Hw$RI;ZB^Ui{EZBzlUGhy2Ce4Dl8Z|K zNJ2$ffHsW<$vvN$uqGP|m-#>Y?6_BB?gd0G+9om;w;@vL07F?8>iG|rE2mbvqD-t1 zC-7z`&O7?j&zr*OqtVQcg?;3xBRicQZP!@Tn3iPSj;eh8~>cDw5vtr4_n5DTZ%`GPbwWxcD7d~ z4<~;3u$yDjo{$>G=EO>QG3QickeY5?9W;K-d4g$-}J1{H#BVcWnO* zYBCJFIvxX^boNu+514%Mbu1uqT(Sg<(yyhhCJFTmMI3|VMDkzQ3SE>!!Nhs=YxrH( zj=^<)YMRFeC)m5_H{j+NvBS315c(MgWs82c{R@KgSVr2n(8oXt!DMO9(J<-g0iYYU zU)0Uosf8^R%l(r;6w^T|e~;Mr?DY>(!1*wL0s*9! zu4jc1OihKz_)aG7`h#t9i>Hz&t`j)d`BEsL`&I+V_E|0NN z=PbIQ<{8*&JM|=>;%2sNN&V;^n39S4NDzpf;Jo zC#29+V4jmT57!j52)U2+?0-*48dFPpPE<7eD^2 z-{tZ_j0$3B622tpUoh_3zMLU4Jj*J(j>;LG;197k=f+GEtjv%PX~B^B>R-EN62-SPqoEbl|=|7O_0{cdP3F-e%oMTJWN30sLI$F(bpXX}45- zLUI4tsgAG3lzEYm5EX;*G4d!TlXa?gqbKNOz!fJGb>x}m1|pUXbG4A*%;=Y(clu1P zHKPF08nBVjo&Aw1nMvlV<4h8~6C58*kJEwvCi#FABfwyYA?4Hx50`32M9;(Bup{5r z@L#EBY=iIW0-dQ#YJ7b_oG-5Z;;N4FTvQXMardrz2L@7YG@|N!tQ-6mjDW-396n+-b74;mAIRup)I6;{wZ{Mn$}}8dt7ucFGFR=WW>AlqV0CmAs=?5Z{?m+cSRx zY?(t^cod8aWtZ@43@M#Xd4*b0ov(@nrjTB5h+|A7H$~gyB5@!YfUHBpY`bYRJU!tTnS@8v|vOd58KS7vfm+dHX^S-EP1Y}Yl= zZ_qIQH2|%LgVrSPpcq~{dvY6wV}TnI(gnRzbPP5i*I>`KdC;yp(j?{evKlt(ESCut zGHz8{DFGEAD<)It~|!){>cYMClGu`vG6-hzw)Q6{EF+B z{y@{jKsJSutMRG1Dg3~52zveCT@}_ji0Fp(!k2V2lVne|*k(cW`vJni*dqW6WCWzr%V!4s9&aw3bx2W4I506XaV6xtbo_CO*4c z1y|g(b*s6R1KgU}{xbCYmJ^Bk?XUc+cB7PHXE%Wh~p%itO=6sa|zrX^B z9beM2LU-kT$FKLICpzyK&yd2=uHhMv0v->CyS@n=SygEpEalNLO?*Q8S@i9?cT)KKDE?Mo+5jP1yWMi+{`yp$2QbnQ0Ads#aIZEy=%x5ax zrKpuh42_h9yn2zyfKoHa_=fUX7OJVMh4M-s^?&!0|EbCUpHC?Nga6x7>T025{Ld2Q Pzv}+S?Ef4m0Pz0+CrMID literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..90bd7283e2663523775aa54ed0e4f59909b10e87 GIT binary patch literal 4199 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 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 0000000000000000000000000000000000000000..b8ddf59ebaaa0b782dd15f6edbfe47bf88c47e34 GIT binary patch literal 5304 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5aeda8ec132bae19e1004cec7424be8419e9aa5d GIT binary patch literal 4202 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 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 0000000000000000000000000000000000000000..24c706745bcf787e969ef42c5f254272bc74e5fb GIT binary patch literal 5194 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..34ba3bcdf8ea4f7bfe78aad36c5b1b93c8dfd933 GIT binary patch literal 4119 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 At^fc4 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f73bdbef2314e44a87e6e2920ea0177b3f71070f GIT binary patch literal 3423 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 afAGI(4@{Sg{6AmdzuNud`ag{V0R9j7G?DTE literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cc3cef205ca8306001b65c03540baeb2dc6d00f8 GIT binary patch literal 9260 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 Date: Mon, 1 Sep 2025 09:18:21 -0400 Subject: [PATCH 10/17] fix(engine): handle single function CFG instead of list of functions --- .../analyses/reentrancy/analysis/analysis.py | 13 +- slither/analyses/data_flow/engine/analysis.py | 8 +- .../analyses/data_flow/engine/direction.py | 11 +- slither/analyses/data_flow/engine/engine.py | 23 +- .../output/ToyBox_output.json | 542 ++++++++++++++++++ .../reentrancy_df/reentrancy_eth_df.py | 2 +- 6 files changed, 564 insertions(+), 35 deletions(-) create mode 100644 slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py index fd9f9b0366..4bbd120633 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Set, Union +from typing import Optional, Set, Union from slither.analyses.data_flow.analyses.reentrancy.analysis.domain import ( DomainVariant, ReentrancyDomain, @@ -32,17 +32,14 @@ def direction(self) -> Direction: def bottom_value(self) -> Domain: return ReentrancyDomain.bottom() - def transfer_function( - self, node: Node, domain: ReentrancyDomain, operation: Operation, functions: List[Function] - ): - self.transfer_function_helper(node, domain, operation, functions) + 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, - functions: List[Function], private_functions_seen: Optional[Set[Function]] = None, ): if private_functions_seen is None: @@ -52,14 +49,13 @@ def transfer_function_helper( domain.variant = DomainVariant.STATE domain.state = State() - self._analyze_operation_by_type(operation, domain, node, functions, private_functions_seen) + self._analyze_operation_by_type(operation, domain, node, private_functions_seen) def _analyze_operation_by_type( self, operation: Operation, domain: ReentrancyDomain, node: Node, - functions: List[Function], private_functions_seen: Set[Function], ): if isinstance(operation, EventCall): @@ -113,7 +109,6 @@ def _handle_internal_call_operation( node, domain, internal_operation, - [function], private_functions_seen, ) # Mark cross-function reentrancy for written variables diff --git a/slither/analyses/data_flow/engine/analysis.py b/slither/analyses/data_flow/engine/analysis.py index 90fee3d0ef..43391402ba 100644 --- a/slither/analyses/data_flow/engine/analysis.py +++ b/slither/analyses/data_flow/engine/analysis.py @@ -1,11 +1,11 @@ from abc import ABC, abstractmethod -from typing import Generic, List, TypeVar +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.core.declarations.function import Function + from slither.slithir.operations.operation import Operation @@ -19,9 +19,7 @@ def direction(self) -> Direction: pass @abstractmethod - def transfer_function( - self, node: Node, domain: Domain, operation: Operation, functions: List[Function] - ): + def transfer_function(self, node: Node, domain: Domain, operation: Operation): pass @abstractmethod diff --git a/slither/analyses/data_flow/engine/direction.py b/slither/analyses/data_flow/engine/direction.py index ad6c1e5ed5..3606e19470 100644 --- a/slither/analyses/data_flow/engine/direction.py +++ b/slither/analyses/data_flow/engine/direction.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from decimal import Decimal -from typing import TYPE_CHECKING, Deque, Dict, List, Optional, Set, Union +from typing import TYPE_CHECKING, Deque, Dict, Optional, Set, Union if TYPE_CHECKING: from slither.core.compilation_unit import SlitherCompilationUnit @@ -8,7 +8,7 @@ from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState from slither.analyses.data_flow.engine.domain import Domain from slither.core.cfg.node import Node, NodeType -from slither.core.declarations.function import Function + from slither.slithir.operations.binary import Binary, BinaryType @@ -26,7 +26,6 @@ def apply_transfer_function( node: Node, worklist: Deque[Node], global_state: Dict[int, "AnalysisState[A]"], - functions: List[Function], ): pass @@ -46,13 +45,10 @@ def apply_transfer_function( node: Node, worklist: Deque[Node], global_state: Dict[int, "AnalysisState[A]"], - functions: List[Function], ): # Apply transfer function to current node for operation in node.irs or [None]: - analysis.transfer_function( - node=node, domain=current_state.pre, operation=operation, functions=functions - ) + analysis.transfer_function(node=node, domain=current_state.pre, operation=operation) # Set post state global_state[node.node_id].post = current_state.pre @@ -77,6 +73,5 @@ def apply_transfer_function( node: Node, worklist: Deque[Node], global_state: Dict[int, "AnalysisState[A]"], - functions: List[Function], ): raise NotImplementedError("Backward transfer function hasn't been developed yet") diff --git a/slither/analyses/data_flow/engine/engine.py b/slither/analyses/data_flow/engine/engine.py index b61f6db4b3..d2e769b477 100644 --- a/slither/analyses/data_flow/engine/engine.py +++ b/slither/analyses/data_flow/engine/engine.py @@ -13,21 +13,21 @@ def __init__(self): self.state: Dict[int, AnalysisState[A]] = {} self.nodes: List[Node] = [] self.analysis: Analysis - self.functions: List[Function] + self.function: Function # Single function being analyzed @classmethod - def new(cls, analysis: Analysis, functions: List[Function]): + def new(cls, analysis: Analysis, function: Function): engine = cls() engine.analysis = analysis - engine.functions = functions - - # create state mapping using node.node_id directly - for function in functions: - for node in function.nodes: - engine.nodes.append(node) - engine.state[node.node_id] = AnalysisState( - pre=analysis.bottom_value(), post=analysis.bottom_value() - ) + 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 @@ -52,7 +52,6 @@ def run_analysis(self): node=node, worklist=worklist, global_state=self.state, - functions=self.functions, ) def result(self) -> Dict[Node, AnalysisState[A]]: diff --git a/slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json b/slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json new file mode 100644 index 0000000000..51d41335a1 --- /dev/null +++ b/slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json @@ -0,0 +1,542 @@ +{ + "file_path": "../contracts/src/ToyBox.sol", + "timestamp": "2025-08-27T14:28:33.886562", + "functions": [ + { + "name": "foo", + "nodes": [ + { + "expression": "None", + "variables": [] + }, + { + "expression": "None", + "variables": [] + }, + { + "expression": "None", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "userInput_foo_asm_0 = calldataload(uint256)(data + 0x20)", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "None", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).ptr_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "ptr_foo_asm_0 = mload(uint256)(0x40)", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).ptr_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "None", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).ptr_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).offset_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "offset_foo_asm_0 = ptr_foo_asm_0 + userInput_foo_asm_0", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).ptr_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).offset_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": true, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_3", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": true, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "mstore(uint256,uint256)(offset_foo_asm_0,caller()())", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).ptr_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).offset_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": true, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_3", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": true, + "has_underflow": false, + "var_type": "uint256" + } + ] + }, + { + "expression": "None", + "variables": [ + { + "name": "ToyBox.foo(bytes).userInput_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_0", + "interval_ranges": [], + "valid_values": { + "values": [ + "32" + ] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).ptr_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": false, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "ToyBox.foo(bytes).offset_foo_asm_0", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": true, + "has_underflow": false, + "var_type": "uint256" + }, + { + "name": "TMP_3", + "interval_ranges": [ + { + "lower_bound": "0", + "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" + } + ], + "valid_values": { + "values": [] + }, + "invalid_values": { + "values": [] + }, + "has_overflow": true, + "has_underflow": false, + "var_type": "uint256" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/slither/detectors/reentrancy_df/reentrancy_eth_df.py b/slither/detectors/reentrancy_df/reentrancy_eth_df.py index 70ed920cc4..5aec2a4efc 100644 --- a/slither/detectors/reentrancy_df/reentrancy_eth_df.py +++ b/slither/detectors/reentrancy_df/reentrancy_eth_df.py @@ -69,7 +69,7 @@ def find_reentrancies(self) -> Dict[FindingKey, Set[FindingValue]]: ] for f in functions: - engine = Engine.new(analysis=ReentrancyAnalysis(), functions=[f]) + engine = Engine.new(analysis=ReentrancyAnalysis(), function=f) engine.run_analysis() engine_result = engine.result() vulnerable_findings: Set[FindingValue] = set() From 62b40a20e3b95355db45dd25258d23f6353e0d9a Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Mon, 1 Sep 2025 09:24:32 -0400 Subject: [PATCH 11/17] Cleaning --- .../output/ToyBox_output.json | 542 ------------------ 1 file changed, 542 deletions(-) delete mode 100644 slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json diff --git a/slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json b/slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json deleted file mode 100644 index 51d41335a1..0000000000 --- a/slither/analyses/data_flow/interval_enhanced/output/ToyBox_output.json +++ /dev/null @@ -1,542 +0,0 @@ -{ - "file_path": "../contracts/src/ToyBox.sol", - "timestamp": "2025-08-27T14:28:33.886562", - "functions": [ - { - "name": "foo", - "nodes": [ - { - "expression": "None", - "variables": [] - }, - { - "expression": "None", - "variables": [] - }, - { - "expression": "None", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "userInput_foo_asm_0 = calldataload(uint256)(data + 0x20)", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "None", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).ptr_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "ptr_foo_asm_0 = mload(uint256)(0x40)", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).ptr_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "None", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).ptr_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).offset_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "offset_foo_asm_0 = ptr_foo_asm_0 + userInput_foo_asm_0", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).ptr_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).offset_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": true, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_3", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": true, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "mstore(uint256,uint256)(offset_foo_asm_0,caller()())", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).ptr_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).offset_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": true, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_3", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": true, - "has_underflow": false, - "var_type": "uint256" - } - ] - }, - { - "expression": "None", - "variables": [ - { - "name": "ToyBox.foo(bytes).userInput_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_0", - "interval_ranges": [], - "valid_values": { - "values": [ - "32" - ] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).ptr_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "115792089237316195423570985008687907853269984665640564039457584007913129639935" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": false, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "ToyBox.foo(bytes).offset_foo_asm_0", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": true, - "has_underflow": false, - "var_type": "uint256" - }, - { - "name": "TMP_3", - "interval_ranges": [ - { - "lower_bound": "0", - "upper_bound": "231584178474632390847141970017375815706539969331281128078915168015826259279870" - } - ], - "valid_values": { - "values": [] - }, - "invalid_values": { - "values": [] - }, - "has_overflow": true, - "has_underflow": false, - "var_type": "uint256" - } - ] - } - ] - } - ] -} \ No newline at end of file From 2a7b0399d3ec9cb9478f3cf305c77812a55ed99b Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Mon, 1 Sep 2025 09:27:50 -0400 Subject: [PATCH 12/17] Cleaning imports --- .../data_flow/analyses/reentrancy/analysis/analysis.py | 1 + .../data_flow/analyses/reentrancy/analysis/domain.py | 3 ++- .../analyses/data_flow/analyses/reentrancy/core/state.py | 4 ++-- slither/analyses/data_flow/engine/analysis.py | 2 -- slither/analyses/data_flow/engine/direction.py | 9 ++------- slither/analyses/data_flow/engine/domain.py | 3 --- slither/analyses/data_flow/engine/engine.py | 2 -- 7 files changed, 7 insertions(+), 17 deletions(-) diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py index 4bbd120633..a238494f3d 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py @@ -1,4 +1,5 @@ from typing import Optional, Set, Union + from slither.analyses.data_flow.analyses.reentrancy.analysis.domain import ( DomainVariant, ReentrancyDomain, diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py index bf6078276e..0b095b3d00 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/domain.py @@ -1,7 +1,8 @@ +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 -from enum import Enum, auto class DomainVariant(Enum): diff --git a/slither/analyses/data_flow/analyses/reentrancy/core/state.py b/slither/analyses/data_flow/analyses/reentrancy/core/state.py index 50ecbc7f53..a45608ef8d 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/core/state.py +++ b/slither/analyses/data_flow/analyses/reentrancy/core/state.py @@ -1,6 +1,6 @@ -from collections import defaultdict import copy -from typing import Dict, Set, Union +from collections import defaultdict +from typing import Dict, Set from slither.core.cfg.node import Node from slither.core.declarations.function import Function diff --git a/slither/analyses/data_flow/engine/analysis.py b/slither/analyses/data_flow/engine/analysis.py index 43391402ba..5793333fec 100644 --- a/slither/analyses/data_flow/engine/analysis.py +++ b/slither/analyses/data_flow/engine/analysis.py @@ -1,11 +1,9 @@ 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 diff --git a/slither/analyses/data_flow/engine/direction.py b/slither/analyses/data_flow/engine/direction.py index 3606e19470..6b63dbdeac 100644 --- a/slither/analyses/data_flow/engine/direction.py +++ b/slither/analyses/data_flow/engine/direction.py @@ -1,15 +1,10 @@ from abc import ABC, abstractmethod -from decimal import Decimal -from typing import TYPE_CHECKING, Deque, Dict, Optional, Set, Union +from typing import TYPE_CHECKING, Deque, Dict if TYPE_CHECKING: - from slither.core.compilation_unit import SlitherCompilationUnit - from slither.core.declarations import Contract from slither.analyses.data_flow.engine.analysis import A, Analysis, AnalysisState -from slither.analyses.data_flow.engine.domain import Domain -from slither.core.cfg.node import Node, NodeType -from slither.slithir.operations.binary import Binary, BinaryType +from slither.core.cfg.node import Node class Direction(ABC): diff --git a/slither/analyses/data_flow/engine/domain.py b/slither/analyses/data_flow/engine/domain.py index 7015dda3e5..07e79ca787 100644 --- a/slither/analyses/data_flow/engine/domain.py +++ b/slither/analyses/data_flow/engine/domain.py @@ -6,15 +6,12 @@ class Domain(ABC): @abstractmethod def top(cls) -> Self: """The top element of the domain""" - pass @abstractmethod def bottom(cls) -> Self: """The bottom element of the domain""" - pass @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.""" - pass diff --git a/slither/analyses/data_flow/engine/engine.py b/slither/analyses/data_flow/engine/engine.py index d2e769b477..5099cc6170 100644 --- a/slither/analyses/data_flow/engine/engine.py +++ b/slither/analyses/data_flow/engine/engine.py @@ -1,10 +1,8 @@ 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 import Contract from slither.core.declarations.function import Function From 59f2e49f194e70b6fa52a515fe01b1beff0787b6 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Mon, 1 Sep 2025 09:31:44 -0400 Subject: [PATCH 13/17] Updating reentrancy storage condition --- .../analyses/data_flow/analyses/reentrancy/analysis/analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py index a238494f3d..7c22a45600 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py @@ -72,7 +72,7 @@ def _analyze_operation_by_type( def _handle_storage(self, domain: ReentrancyDomain, node: Node): # Track state reads for var in node.state_variables_read: - if isinstance(var, StateVariable): + if isinstance(var, StateVariable) and var.is_stored: domain.state.add_read(var, node) # Track state writes for var in node.state_variables_written: From fa49ec246183ee55abd251815af7944d1db08ee8 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Tue, 9 Sep 2025 09:37:37 -0400 Subject: [PATCH 14/17] reentrancy-eth-df: bug found with deep copy resulting in infinite recursion --- .../analyses/reentrancy/analysis/analysis.py | 10 ++++- .../analyses/reentrancy/core/state.py | 39 +++++++++++++------ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py index 7c22a45600..4ab0f2f03f 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py +++ b/slither/analyses/data_flow/analyses/reentrancy/analysis/analysis.py @@ -139,8 +139,16 @@ def _handle_event_call_operation(self, operation: EventCall, domain: ReentrancyD # 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: - domain.state.add_call(operation.node, call_node) + 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/core/state.py b/slither/analyses/data_flow/analyses/reentrancy/core/state.py index a45608ef8d..87e3c18540 100644 --- a/slither/analyses/data_flow/analyses/reentrancy/core/state.py +++ b/slither/analyses/data_flow/analyses/reentrancy/core/state.py @@ -31,10 +31,20 @@ 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): - self._written[var.canonical_name].add(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): - self._reads[var.canonical_name].add(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) @@ -134,14 +144,19 @@ def __str__(self): def deep_copy(self) -> "State": new_state = State() - new_state._send_eth = copy.deepcopy(self._send_eth) - new_state._safe_send_eth = copy.deepcopy(self._safe_send_eth) - new_state._calls = copy.deepcopy(self._calls) - new_state._reads = copy.deepcopy(self._reads) - new_state._reads_prior_calls = copy.deepcopy(self._reads_prior_calls) - new_state._events = copy.deepcopy(self._events) - new_state._written = copy.deepcopy(self._written) - new_state.writes_after_calls = copy.deepcopy(self.writes_after_calls) - new_state.cross_function = copy.deepcopy(self.cross_function) + # 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 - From ff349ee4b21362eec48d92dfba1e85e8e242c479 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Thu, 6 Nov 2025 09:44:33 -0500 Subject: [PATCH 15/17] Analysis should begin with function entry point --- slither/analyses/data_flow/engine/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/analyses/data_flow/engine/engine.py b/slither/analyses/data_flow/engine/engine.py index 5099cc6170..e932b377e0 100644 --- a/slither/analyses/data_flow/engine/engine.py +++ b/slither/analyses/data_flow/engine/engine.py @@ -33,7 +33,7 @@ def run_analysis(self): worklist: Deque[Node] = deque() if self.analysis.direction().IS_FORWARD: - worklist.extend(self.nodes) + worklist.extend(self.function.entry_point) else: raise NotImplementedError("Backward analysis is not implemented") From c7df05c2ac1259bb0ea3eb4142012d71788af070 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Thu, 13 Nov 2025 13:25:46 -0500 Subject: [PATCH 16/17] Fixing logic --- setup.py | 1 + slither/analyses/data_flow/engine/domain.py | 6 +++++- slither/analyses/data_flow/engine/engine.py | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0f95e40ffe..690f430252 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ "eth-abi>=5.0.1", "eth-typing>=5.0.0", "eth-utils>=5.0.0", + "typing_extensions>=4.0.0", ], extras_require={ "lint": [ diff --git a/slither/analyses/data_flow/engine/domain.py b/slither/analyses/data_flow/engine/domain.py index 07e79ca787..896d8b88c2 100644 --- a/slither/analyses/data_flow/engine/domain.py +++ b/slither/analyses/data_flow/engine/domain.py @@ -1,5 +1,9 @@ from abc import ABC, abstractmethod -from typing import Self + +try: + from typing import Self +except ImportError: + from typing_extensions import Self class Domain(ABC): diff --git a/slither/analyses/data_flow/engine/engine.py b/slither/analyses/data_flow/engine/engine.py index e932b377e0..5db09391a9 100644 --- a/slither/analyses/data_flow/engine/engine.py +++ b/slither/analyses/data_flow/engine/engine.py @@ -33,7 +33,8 @@ def run_analysis(self): worklist: Deque[Node] = deque() if self.analysis.direction().IS_FORWARD: - worklist.extend(self.function.entry_point) + if self.function.entry_point is not None: + worklist.append(self.function.entry_point) else: raise NotImplementedError("Backward analysis is not implemented") From 9be874d279dc6731ff7ee4316adc64b6e52c5e33 Mon Sep 17 00:00:00 2001 From: Marcelo Morales Date: Thu, 13 Nov 2025 13:26:37 -0500 Subject: [PATCH 17/17] Removing entry from setup.py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 690f430252..0f95e40ffe 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,6 @@ "eth-abi>=5.0.1", "eth-typing>=5.0.0", "eth-utils>=5.0.0", - "typing_extensions>=4.0.0", ], extras_require={ "lint": [