EIP-7: DELEGATECALL — borrow the code, keep your context
Contracts wanted to reuse a shared library's logic without copy-pasting its bytecode into every one. But a normal CALL runs that logic in the library's own storage — useless for a library that's supposed to work on your data. DELEGATECALL runs borrowed code in the caller's context, and in doing so it quietly invented libraries, proxies, and upgradeable contracts.
- the EVM & the call model
- storage & context
We enter Part III at the beginning: Homestead, March 2016, Ethereum’s first planned upgrade. It shipped a small opcode with enormous consequences. By now (from the EVM chapter) you know a contract is code plus storage, and that one contract can call another. But calling raises a question the earliest EVM answered badly: if I want to reuse another contract’s code — a shared library of vetted logic — whose data does that code run on? Get the answer wrong and shared libraries are impossible; get it right and you unlock proxies and upgradeable contracts. EIP-7 is that right answer, DELEGATECALL. Let’s derive it.
The problem: shared logic, without copy-paste
Deploying a contract costs gas proportional to its code size, and the same routines — safe math, token transfers, access control — get written again and again. Copy-pasting a library’s bytecode into every contract is wasteful and unmaintainable: fix a bug and you’d have to redeploy everyone. The obvious wish is a single, audited library contract deployed once, whose code many contracts can borrow.
→ Step 2: watch a plain CALL land in the wrong storage.
Why a normal CALL can’t build a library
When your contract does a plain CALL to the library, the EVM switches into the library’s execution context The account whose storage, balance, and address the currently-running code reads and writes, plus msg.sender/msg.value. A plain CALL switches context to the callee; the code runs 'as' the callee. . The library’s code executes, but every SSTORE it does writes to the library’s storage, address(this) is the library, and msg.sender is your contract. So a library method like balances[msg.sender] += amount dutifully updates the library’s balances mapping — not yours. Your storage is never touched.
The code is perfectly good; it just ran in the wrong account’s world. What we actually want is the opposite wiring: the library’s code, but our context.
→ Step 3: DELEGATECALL.
DELEGATECALL: the callee’s code, the caller’s everything
DELEGATECALL An EVM call (EIP-7) that loads the callee's code and runs it entirely in the caller's context: the caller's storage and balance, the caller's address as this, and — unlike the older CALLCODE — the original msg.sender and msg.value preserved. 'Run this code as if it were mine.' is the exact wiring we wanted. It loads the target’s code but executes it in your context: reads and writes hit your storage, address(this) is you, the balance is yours. And it fixes CALLCODE’s flaw — it preserves the original msg.sender and msg.value, so the borrowed code sees the same caller and value your contract was called with. It is, precisely, “run this contract’s code as if it were part of mine.”
- 1 the instructions executed are the target library's bytecode
- 2 storage, balance, address(this), and the original msg.sender/value are the caller's
→ Step 4: separate state from logic, and upgrade.
The payoff: libraries, proxies, upgradeable contracts
Once code and context are separable, patterns fall out. Solidity library calls compile to DELEGATECALL, so shared logic lives once on-chain. More powerfully, you can split a system into a thin proxy A minimal contract that holds all the state and, on every call, DELEGATECALLs to a separate implementation contract for the logic. Because state lives in the proxy, swapping the implementation address upgrades behaviour without moving data — and the address users know never changes. that holds all the state and a separate implementation that holds the logic. Every call to the proxy is delegated to the implementation, which runs against the proxy’s storage. To upgrade, you just point the proxy at a new implementation — the data stays put and the address everyone uses never changes.