← all simulations

slide 28 · security

Smart contract vulnerabilities

attacker
↺ withdraw()↺ withdraw()↺ withdraw()
vault

Exploit , /underflow and 'private' data — then patch them.

  1. 1Run the exploit
  2. 2Read what the attacker did
  3. 3Apply the fix and re-run
live · 0 players in this room

send coins to another player

10.0000 BTC10.0000 ETH
recipient

waiting for another player to join this room… open this page in a second tab or share the link with the class.

fee 0.0005 · total 0.5005 BTC
live transfers in this room
room ledger · tap a row to replay its steps
  • no transfers yet — be the first to pay a classmate.
Vault balance
10 ETH
Attacker balance
1 ETH
Guard
none

Vulnerable contract

1mapping(address => uint) public balances;
2
3function withdraw() external {
4 uint bal = balances[msg.sender];
5 require(bal > 0);
6 // ❌ interaction BEFORE effect
7 (bool ok,) = msg.sender.call{value: bal}("");
8 require(ok);
9 balances[msg.sender] = 0; // never reached in time
10}

Exploit trace

waiting…

Re-entrancy

msg.sender.call hands execution to the attacker's contract while your storage is still stale. Its receive() function calls withdraw() again, and again, each time passing the balance check. Fix: checks-effects-interactions ordering, plus a nonReentrant mutex.

Seen in the wild: The DAO, 2016 — ~3.6M ETH drained, and the reason Ethereum Classic exists. Defence in depth: checks-effects-interactions, audited OpenZeppelin bases, Solidity 0.8+, Slither and Mythril in CI, Foundry fuzz and invariant tests, a bug bounty, and a timelocked pause switch for when all of that still misses one.