Track 7 · The Breach Lab · lesson 2
Now make it hold
15 min
You drained that vault. Now make it hold.
The exploit you wrote last lesson is aimed at the vault below. Fix the vault, run the exploit against it, and keep going until the attack fails.
VulnerableVault
? ETH
Nudge
Show me the approach
Show me the code
Explain the exploit
The pattern that would have saved The DAO
Checks. Effects. Interactions. In that order, every time.
- Checks — validate everything. Caller, balance, inputs.
- Effects — update your own state.
- Interactions — only now call anything external.
The vault broke the order: it interacted (sent ETH) before applying the effect (zeroing the balance). Reversing those two lines closes the window entirely.
When the attacker re-enters, bal reads zero and require(bal > 0) reverts the
reentrant call. There is nothing to steal, because the books were already
updated before control ever left the contract.
The other fix
Ordering is the better fix when you can manage it — it costs no extra gas. But sometimes the logic genuinely cannot be reordered, and then you reach for a reentrancy guard:
modifier nonReentrant() {
require(!locked, "reentrant");
locked = true;
_;
locked = false;
}
This is the wrapping modifier from track 4, and it is exactly what
OpenZeppelin's ReentrancyGuard provides. It sets a flag on the way in and
refuses any call that arrives while the flag is set.
Predict
You add a reentrancy guard but STILL send ETH before updating the balance. Are you safe?
Check
Why is reordering preferred over a guard when both would work?
Worth remembering
- Checks-effects-interactions: validate, update your state, then call out — in that order.
- Reversing the effect and interaction closes the reentrancy window entirely.
- A reentrancy guard is the fallback when ordering cannot be fixed.
- Prefer reordering: it costs no gas and leaves no window.
- Read-only reentrancy can still bite a guarded contract with wrong ordering.