freecoding.school100% FREE · NO SIGNUP
The EVM CoreISSUE #39 of 48

pull over push · the withdrawal pattern

SoliaVSThe Reentrancy Reaper
Solia saysPull-over-push records what each recipient is owed and lets them withdraw — so one malicious recipient that reverts on receive cannot block payouts to everyone else.

Pushing payouts in a loop (contract sends to N recipients) creates a single point of failure: if any one recipient is a contract that reverts in receive(), the entire batch reverts. Pull-over-push inverts the flow: the contract records each recipient's owed amount, and each recipient calls withdraw() to pull their own funds. A malicious recipient can only block themselves.

The pattern shows up in DeFi (Compound rewards), governance (timelock payouts), and games (per-player rewards). The demo shows a push failing on a malicious recipient while a pull-payout serves the other winners cleanly.

Power-ups you unlock

The Reentrancy Reaper attacks — common mistakes

Boss battleShow a push payout failing when one recipient reverts, and the pull-payout variant serving the other recipients cleanly.

Example code

<!doctype html><html><head><meta charset="utf-8"></head>
<body style="background:#06040d;color:#e6e0ff;font-family:monospace;padding:20px"><pre id="o"></pre>
<script>
const recipients = [
  { name:'alice',   owed:10 },
  { name:'mallory', owed:10, willRevert:true },
  { name:'bob',     owed:10 }
];
// PUSH: contract sends in a loop
function push(rs){
  for(const r of rs){
    if(r.willRevert) throw new Error('push failed at ' + r.name);
    r.received = r.owed;
  }
}
let pushErr = null;
try { push(recipients.map(r => ({ ...r }))); } catch(e){ pushErr = e.message; }
// PULL: each recipient withdraws their own
const balances = Object.fromEntries(recipients.map(r => [r.name, r.owed]));
const aliceGot = balances['alice']; delete balances['alice'];
const bobGot   = balances['bob'];   delete balances['bob'];
// mallory cannot withdraw (reverts on receive) — only blocks herself
document.getElementById('o').textContent = [
  'PUSH attempt: ' + pushErr + '   (alice and bob also blocked!)',
  '',
  'PULL pattern:',
  '  alice withdrew: ' + aliceGot + '   ✓',
  '  bob   withdrew: ' + bobGot + '   ✓',
  '  mallory still owed: ' + (balances['mallory'] || 0) + '   (only she is blocked)'
].join('\n');
</script></body></html>
▶ Open the interactive comic issue
‹ Reentrancy Guards · The Mutex PatternAccess Control · Ownable · Roles · Accessmanager ›