← all simulations

slide 27 · contracts

Proxy contract approach

proxy
storage kept
delegatecall →
logic V1
logic V2

Upgrade logic from V1 to V2 through while survives.

  1. 1Call V1 through the proxy
  2. 2Upgrade to V2
  3. 3Check the storage survived
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.
Proxy address
0xPr0xy… (never changes)
Implementation
LogicV1
counter (proxy storage)
0
Increment step
+1

Interact

PROXY (storage)
slot 0 · implementation
slot 1 · admin
slot 2 · counter = 0
delegatecall
code borrowed,
storage stays home
LOGIC V1 (code only)
increment()
its own storage: unused, always 0

Proxy.sol

1contract Proxy {
2 // slot keccak256('eip1967.proxy.implementation') - 1
3 address implementation; // storage lives HERE
4 address admin;
5 uint256 public counter; // user data lives HERE too
6
7 function upgradeTo(address newImpl) external {
8 require(msg.sender == admin, "not admin");
9 implementation = newImpl;
10 }
11
12 fallback() external payable {
13 // run impl's CODE against THIS contract's storage
14 (bool ok,) = implementation.delegatecall(msg.data);
15 require(ok);
16 }
17}

LogicV1.sol

1contract LogicV1 {
2 address implementation; address admin; // layout must match!
3 uint256 public counter;
4
5 function increment() external { counter += 1; }
6}

Execution log

01 proxy deployed at 0xPr0xy… · implementation = LogicV1 · counter = 0

Rules of the proxy pattern

  • delegatecall executes another contract's code in your own storage and msg.sender context — that is the whole trick.
  • Never reorder or insert state variables; only append. Use EIP-1967 fixed slots for admin and implementation to avoid clashing with user data.
  • Logic contracts have no constructor — use an initialize() guarded by initializer, and initialize in the same transaction as deployment.
  • Flavours: Transparent proxy (admin routing), UUPS (upgrade logic in the implementation, cheaper), Beacon (upgrade many proxies at once), Diamond/EIP-2535 (multi-facet).
  • Upgradeability is centralisation. Whoever holds the admin key can replace the contract users trusted — put it behind a multisig plus a timelock, and say so publicly.

The trade-off in one line: is the security guarantee, and a proxy deliberately gives it up in exchange for the ability to fix bugs.