Sample report — Vault.sol classic reentrancy
The canonical contract demoed on the homepage, with the full Founder Pro report attached. 1 HIGH + 2 INFO. The reentrancy is real — every dev has seen it, plenty still ship it.
Ship-blocker: the reentrancy in withdraw() is the DAO-era bug. Apply checks-effects-interactions (state update before external call) before mainnet. Both informational findings (pragma range and low-level call) are intentional design choices in modern Solidity 0.8.x and can be acknowledged rather than fixed — note them in the project README so the next reviewer doesn't re-flag.
- HIGHF-01 · reentrancy-ethVault.sol:10–16
Reentrancy in withdraw() lets an attacker drain the vault
withdraw() sends ETH via msg.sender.call{value: amount}('') BEFORE zeroing balances[msg.sender]. A malicious contract can re-enter withdraw() inside its fallback / receive() function, see its balance still positive, and pull funds again. Slither flags this as reentrancy-eth with cross-function reach because balances is also read by deposit(). This is the DAO 2016 bug template — still in the wild because tutorials lag the fix by a year and beginners copy-paste.
Vault.solvulnerablefunction withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0, "no balance"); (bool ok, ) = msg.sender.call{value: amount}(""); // ⚠ external call BEFORE state update require(ok, "transfer failed"); balances[msg.sender] = 0; // state update happens too late }suggested patch drop-in fix--- a/Vault.sol +++ b/Vault.sol @@ function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0, "no balance"); - (bool ok, ) = msg.sender.call{value: amount}(""); + balances[msg.sender] = 0; + (bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "transfer failed"); - balances[msg.sender] = 0; }Fix: Apply checks-effects-interactions: zero the balance BEFORE the external call. Optionally add OpenZeppelin's ReentrancyGuard for belt-and-suspenders, but the ordering fix alone is enough for this case.reentrancy-ethCWE-841SWC-107slither - INFOF-02 · solc-versionVault.sol:2
Pragma allows any 0.8.x — pin the range
pragma solidity ^0.8.0 accepts every 0.8.x release, including older versions with known compiler bugs (ABIReencodingHeadOverflowWithStaticArrayCleanup, NestedCalldataArrayAbiReencodingSizeValidation, KeccakCaching). Slither flags this as informational because nothing in this contract triggers those bugs — but pinning to a known-good version (^0.8.21 or =0.8.25) eliminates the class entirely.
Vault.solvulnerablepragma solidity ^0.8.0; // accepts 0.8.0 through 0.8.x — broadFix: Pin to a recent stable: `pragma solidity 0.8.25;` (exact) for production, `^0.8.21` if you want patch flexibility. The Hardhat / Foundry compile output won't change for this contract; the pragma is purely a declaration.solc-versioninformationalslither - INFOF-03 · low-level-callsVault.sol:10–16
Low-level call usage — intentional but worth documenting
msg.sender.call{value: amount}('') is a low-level call. Slither flags any low-level call because they bypass the type system and don't propagate revert reasons. In this case the call is correct (forwarding ETH to an EOA or contract, with the return value checked) — but the next reviewer will re-flag it unless you add a comment explaining why .transfer() / .send() aren't used (2300-gas stipend doesn't suffice for many proxies post-Istanbul).
Vault.solvulnerable(bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "transfer failed");Fix: Leave the .call() as is, but add a one-line comment explaining the 2300-gas stipend issue. Optional belt: assert that the call gas forwarded is reasonable (≥ 21,000) to defend against malicious receivers that try to grief the caller.low-level-callsinformationalslither
Get one of these for your contract.
Free for the first scan. Founder Pro €29/mo with a 7-day free trial unlocks unlimited scans, priority queue, and a human triaging every report — rate locked for life.