Severity depends on reachable impact and deployment context. Listed tools are review aids; their applicability depends on language, version and configuration.
Reentrancy occurs when a contract hands control to external code before its state satisfies the invariants that later calls assume. The external code calls back into the original system and observes or changes that incomplete state. Ether transfers are one callback surface, but token hooks, safe NFT receivers, routers, and arbitrary contract calls can create the same condition.
This Solidity 0.8 example remains exploitable because each nested call reads the same credit and each frame writes zero only after sending. There is no arithmetic underflow to rescue it.
pragma solidity ^0.8.24;
contract VulnerableVault {
mapping(address => uint256) public credit;
function deposit() external payable {
credit[msg.sender] += msg.value;
}
function withdraw() external {
uint256 amount = credit[msg.sender];
require(amount != 0, "no credit");
// VULNERABLE: recipient controls execution before credit is cleared.
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "send failed");
credit[msg.sender] = 0;
}
}
An attacker contract deposits once, calls withdraw, and re-enters withdraw from its receiver while the vault has pooled funds. Cross-function reentrancy uses another entry point that shares the same state. Read-only reentrancy exposes an inconsistent getter to a consuming protocol. The Ethereum Foundation's DAO notice describes the canonical recursive-call incident without requiring a changing loss estimate.
Enumerate every external interaction, including interface calls that look routine. At each boundary, list the state invariants that must already hold and all public or external functions reachable during the callback. Review the whole shared-state surface, not just the function containing the call.
Adversarial tests should install receivers that re-enter the same function, a sibling function, and a dependent contract. Assert conservation, solvency, shares, debt, and authorization during nested execution. Also test revert paths and tokens with hooks. Static-analysis silence does not prove a callback is unreachable.
Firepan can inspect call ordering, shared-state access, and candidate callback paths in a submitted repository and commit, with best-effort tool corroboration where compatible. It does not continuously monitor deployments or prove the absence of every cross-contract path.
Apply Checks-Effects-Interactions where it preserves the protocol: validate inputs, commit internal effects, then interact. In the example, clear the credit before sending; a later revert rolls the effect back. Use pull-based claims to isolate recipients when appropriate.
OpenZeppelin's current import is @openzeppelin/contracts/utils/ReentrancyGuard.sol. Its nonReentrant modifier blocks nested entry while a protected call is active; it does not mean a function may run only once per transaction. Cover every entry point sharing the guarded invariant, and understand that separately guarded contracts do not share one lock.
Safe token-transfer wrappers handle return-value compatibility, not reentrancy. Treat any token or receiver callback as external execution and retain CEI, guard, and invariant testing as required.
Firepan
Run a free scan — results in minutes, no credit card required.
Run Free Scan →Understand missing and incorrect smart contract authorization checks, how to review privileged paths, and how to test role boundaries.
Review cross-chain message authentication, replay protection, finality, accounting, and operational trust boundaries.
Understand untrusted delegatecall targets, proxy upgrade boundaries, storage hazards, and practical review and test guidance.
Understand how atomic liquidity amplifies oracle, accounting, governance, and callback defects, with review and test guidance.
Understand governance capture, flash-loan voting, unsafe proposal execution, and the review and testing needed for on-chain governance systems.