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

delegatecall · context preservation

SoliaVSThe Reentrancy Reaper
Solia saysDELEGATECALL runs another contract’s code in the caller’s storage and msg.sender context — the basis of proxies, libraries, and the diamond pattern.

CALL executes the target contract in the target's context: target storage is written, msg.sender becomes the caller. DELEGATECALL executes the target's code in the caller's context: the caller's storage is written, and msg.sender stays whoever called the proxy. This is what makes proxy upgradeability work — a thin proxy contract delegatecalls into a logic contract, but storage lives in the proxy.

The risk is real: if the logic contract has a different storage layout than the proxy, delegatecalls write to the wrong slots and corrupt state. That bug class — proxy storage collisions — has burned more than one protocol. The demo contrasts the two call modes side by side.

Power-ups you unlock

The Reentrancy Reaper attacks — common mistakes

Boss battleShow msg.sender preserved through delegatecall and corrupted state when the target writes to slot 0.

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>
// proxy P delegatecalls into logic L; storage lives in P.
const P = { addr:'0xProxy',  storage:{ slot0:'PROXY-owner', slot1:42 } };
const L = { addr:'0xLogic',  storage:{ slot0:'LOGIC-owner', slot1:99 }, code:'storage[0] = msg.sender' };
const EOA = '0xUser';
function CALL(caller, target, msgSender){
  target.storage.slot0 = msgSender;
  return { ran_in:target.addr + ' context', msg_sender:msgSender, P:JSON.stringify(P.storage), L:JSON.stringify(L.storage) };
}
function DELEGATECALL(caller, target, originalSender){
  caller.storage.slot0 = originalSender;          // caller's storage written by target's code
  return { ran_in:caller.addr + ' context', msg_sender:originalSender + ' (PRESERVED)', P:JSON.stringify(caller.storage), L:JSON.stringify(L.storage) };
}
const r1 = CALL(EOA, L, EOA);
const r2 = DELEGATECALL(P, L, EOA);
document.getElementById('o').textContent = [
  'CALL ' + EOA + ' → L:',
  '  ran in:    ' + r1.ran_in,
  '  msg.sender:' + r1.msg_sender,
  '  L storage: ' + r1.L,
  '  P storage: ' + r1.P + '   (untouched)',
  '',
  'DELEGATECALL ' + EOA + ' → P → L (code runs in P):',
  '  ran in:    ' + r2.ran_in,
  '  msg.sender:' + r2.msg_sender,
  '  P storage: ' + r2.P + '   (slot0 OVERWRITTEN by L code)',
  '  L storage: ' + r2.L,
  '',
  '→ proxy delegatecalls let logic upgrade, but storage layout MUST match'
].join('\n');
</script></body></html>
▶ Open the interactive comic issue
‹ Create Vs Create2 · Deterministic AddressesTransient Storage · Eip-1153 ›