Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 3 min read

5 reentrancy patterns I keep finding in small DeFi vaults

Almost every small vault I review has a nonReentrant modifier on withdraw(). And almost every one still has a reentrancy issue somewhere else. A single guard on the obvious function creates a false sense of safety. Reen

Almost every small vault I review has a nonReentrant modifier on withdraw(). And almost every one still has a reentrancy issue somewhere else.

A single guard on the obvious function creates a false sense of safety. Reentrancy is about state that's inconsistent during an external call β€” and that happens in more places than the one function everyone remembers to protect. Here are five patterns I keep finding, and how to catch each.

1. Cross-function reentrancy

The classic. withdraw() is guarded, but it makes an external call before updating a shared balance β€” and during that call the attacker re-enters a different, unguarded function that reads the stale balance.

function withdraw(uint256 amount) external nonReentrant {
    (bool ok,) = msg.sender.call{value: amount}("");   // external call first
    require(ok);
    balances[msg.sender] -= amount;                     // state updated after
}

function transfer(address to, uint256 amount) external {  // NOT guarded
    balances[msg.sender] -= amount;  // still sees the pre-withdraw balance
    balances[to] += amount;
}

A nonReentrant on withdraw doesn't help: the re-entry lands in transfer. Fix: effects before interactions β€” update balances before the call β€” and apply the guard to every function that touches shared state, not just one.

2. Read-only reentrancy

The one that bit Curve/Balancer integrators. A view function (say getPrice() or totalAssets()) reads state that's temporarily inconsistent mid-transaction. Your vault calls that view during someone else's external call and gets a manipulated value β€” even though nothing "wrote" during your call.

Views can't be nonReentrant (they don't write), so the guard doesn't apply. Fix: don't consume an external protocol's price/reserves view without checking whether it can be entered mid-operation; use a reentrancy-aware read (some protocols expose a "check" you can call), or a manipulation-resistant source.

3. ERC-777 / ERC-677 / callback-token hooks

You assumed the token is plain ERC-20. But ERC-777 calls tokensReceived on the recipient, and ERC-677/1363 call hooks on transfer. If your accounting isn't settled before transferFrom/transfer, that hook is a re-entry point into your vault.

Fix: strict checks-effects-interactions even around token transfers, and be explicit about which token standards you support (documented assumptions are spec, not luck).

4. Cross-contract reentrancy

Your vault and your strategy/adapter share state through storage or a shared accounting contract. Vault is guarded, Strategy is guarded β€” but a call chain Vault β†’ external β†’ Strategy re-enters while the shared state is half-updated. Per-contract guards don't compose across contracts.

Fix: treat the trust boundary as the whole system. Settle shared accounting before any external call anywhere in the chain, and consider a shared reentrancy lock for tightly-coupled contracts.

5. The root cause: CEI violations

Patterns 1–4 are symptoms. The disease is Checks-Effects-Interactions ordering: any external call that happens before the contract's state reflects what just occurred. Once you internalize CEI, most reentrancy disappears β€” the guard becomes a backstop, not the plan.

A quick self-audit: search your contracts for .call, .transfer, safeTransfer, and any external interface call, and ask for each β€” "is all my state already updated at this line?" If not, that's a candidate.

Catch them automatically

My free scanner, OpenClaw Audit, has dedicated detectors for cross-function reentrancy and CEI-ordering candidates (and it's calibrated to stay quiet on correct code β€” Morpho Blue's formally-verified safeTransfers don't trip it). One command:

pipx run --spec git+https://github.com/juan23z/openclaw-audit openclaw-audit <your-repo>

Findings are heuristic candidates β€” verify each. But it points your eyes at the right lines.

I review small DeFi protocols for exactly these issues β€” real, exploitable findings only, no padded reports. A hand-verified Quick Scan is $49 (one contract, 48h, and if it's not useful you don't pay).

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.