Which Major Contracts Are Upgradeable Proxies: We Resolved 44 of Them

10 of the 44 most-used contracts on Ethereum and Base can swap their code without the address changing. The split is not random: upgradeability clusters where custody clusters.

evmquery team··6 min read
Share
Which major EVM contracts are upgradeable proxies: a census of 44 contracts on Ethereum and Base

The address you integrated against is not necessarily the code that runs. On many of the contracts you depend on, the bytecode at the address is a thin forwarder, and the real logic lives at an implementation address the owner can replace with a single admin transaction.

We wanted to know how widespread that actually is among the contracts people build on every day, so we resolved 44 of them and counted.

TL;DR

10 of 44 are delegatecall proxies, 33 are not, and 1 would not resolve. Upgradeability tracks almost perfectly with whether a contract holds other people’s money: both USDC deployments, stETH, Aave V3, Compound V3 and 5 of 7 bridge contracts can be upgraded. USDT, DAI, WETH and every Uniswap router through V4 cannot.

What we ran

One describe call per address against the evmquery API with resolution annotations turned on. This is the same call the proxy detector makes, and it returns the dispatch route: which pattern matched and which address the call ends up executing at. If you want the mechanics of that resolution step by step, we wrote them up separately in how evmquery resolves a contract read.

curl -X POST https://api.evmquery.com/api/v1/query/describe \
-H "content-type: application/json" \
-H "x-api-key: $EVMQUERY_API_KEY" \
-d '{
"chain": "evm_ethereum",
"include": ["resolution"],
"schema": { "contracts": { "c": { "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" } } }
}'

For USDC that comes back as:

{
"status": "verified",
"route": [
{ "kind": "zeppelinos", "to": "0x43506849D7C04F9138D1A2050bbF3A0c054402dd" }
]
}

An empty route means the address runs its own code. A non-empty one means it forwards, and each hop names the pattern and the destination.

Method

  • 44 contracts plus 3 externally owned accounts, on Ethereum and Base. Sample is hand-picked for usage, not random: major tokens, DeFi protocols, bridges, NFT infrastructure and core infra. - First run 2026-08-03, re-resolved 2026-08-09 with every route unchanged. Both runs are automated and diffed weekly, so a future change shows up as a diff rather than a re-count. - Labels are machine-verified: contracts with a known ticker were confirmed with a live symbol() read, so a mistyped address cannot produce a wrongly labelled row.

The 10 that can change

Contract Chain Pattern Currently executes at
USDC (Circle) Ethereum legacy OpenZeppelin 0x43506849D7C04F9138D1A2050bbF3A0c054402dd
USDC native Base legacy OpenZeppelin 0x2Ce6311ddAE708829bc0784C967b7d77D19FD779
stETH (Lido) Ethereum implementation getter 0x028271E30a695c0527A0C50cA30603feD004cDb0
Aave V3 Pool Ethereum EIP-1967 0x728a138A4823392C2EFA55e028d434F526fE03CF
Compound V3 USDC Comet Ethereum EIP-1967 0x83D491269720CE925f92C6bF9F66B7a0779A293a
Arbitrum Delayed Inbox Ethereum EIP-1967 0x7C058ad1D0Ee415f7e7f30e62DB1BCf568470a10
Optimism L1StandardBridge Ethereum EIP-1967 0xB37a11AadF167B2F0b8dD85372De4bC66CD4A891
Base L1StandardBridge Ethereum EIP-1967 0x61525EaaCDdB97D9184aFc205827E6A4fd0Bf62A
Polygon PoS RootChainManager Ethereum implementation getter 0xF0235dCa8fb0D3999685724dCBB9DD00c5d62DFa
Wormhole Core Bridge Ethereum EIP-1967 0x3c3d457f1522D3540AB3325Aa5f1864E34cBA9D0

Pattern distribution: EIP-1967 six times, legacy OpenZeppelin twice, implementation getter twice. No diamonds (EIP-2535) and no minimal proxies (EIP-1167) in this set, though both exist widely elsewhere. Every route was a single hop; nothing in this sample chained two proxies.

The word “currently” in that last column is the entire point. Those addresses were true at the moment we read them and carry no guarantee beyond it.

Upgradeability clusters where custody clusters

Sort the 10 by what they do and the pattern is hard to miss. Both USDC deployments, stETH, the Aave lending pool, the Compound market, and five of the seven bridge contracts we checked. Every one of them either issues an asset or holds a pile of somebody else’s.

Now the other side. These 33 cannot be upgraded at all:

  • Tokens (13): USDT, DAI, WETH, WBTC, LINK, UNI, CRV, ENS, SHIB, PEPE, MATIC/POL, wstETH, rETH
  • DeFi (10): Uniswap V2 Router, V3 Factory, SwapRouter02, V4 PoolManager, Permit2, Curve 3pool, Morpho Blue, 1inch AggregationRouterV5, 0x Exchange Proxy, Convex Booster
  • Infra (5): Chainlink ETH/USD feed, ENS Registry, Multicall3, Beacon Deposit Contract, Safe Singleton 1.3.0
  • NFT (3): BAYC, CryptoPunks, Seaport 1.5
  • Bridge (2): LayerZero EndpointV2, Circle CCTP TokenMessenger

The two biggest stablecoins made opposite calls on the same question. USDC sits behind an upgradeable proxy on both chains we checked. USDT, deployed in 2017, cannot be upgraded at all. One issuer kept an exit hatch, the other welded the door shut.

The same split runs through DeFi. Uniswap ossifies deliberately, every router and factory through V4 immutable. Aave and Compound keep the ability to patch a live lending market. Neither is wrong, they are different answers to the same tradeoff between fixing bugs and being predictable.

Our read: the more a contract looks like a bank, the more likely someone kept the keys. The more it looks like plumbing, the more likely it was welded shut on purpose.

The name on the contract tells you nothing

Chainlink’s ETH/USD feed is literally named EACAggregatorProxy. It resolves with an empty route. It is not a delegatecall proxy: it forwards reads to the aggregator behind it by ordinary external call, and its own ABI is complete. The aggregator it points to can be repointed, which is a real thing to know about oracles, but no code swap happens underneath your feet at that address.

The 0x Exchange Proxy is the same story from the other direction: “proxy” in the name, empty route in the resolution.

Retire the name heuristic

If you have ever grepped a contract name for “proxy” to decide whether to worry, that check is wrong in both directions. Resolve the address instead.

EOAs now carry code too

Since Pectra shipped EIP-7702, “is this address a contract?” stopped being a yes-or-no question. An externally owned account can point at code that runs in its context. We checked three well-known EOAs:

EOA Delegation
vitalik.eth 0x5A7FC11397E9a8AD41BF10bf13F22B0a63f96f6d
Titan Builder 0x63c0c19a282a1B52b07dD5a65b58948A07DAE32B
beaverbuild none, plain EOA

Two of three delegate. Resolution reports these as eip7702-delegation hops, the same mechanism as a contract proxy route, so code that assumes “EOA means no code” is now wrong in a way that will not announce itself.

What did not resolve

EigenLayer’s DelegationManager returned Could not resolve ABI, and we are counting it honestly rather than guessing. The ABI source we reach for was not available for that address at read time; the contract itself is fine. One out of 44.

What this means if you integrate

An address is a stable identifier for a location, not for behaviour. For the 10 above, the code behind the address you pinned can be replaced without the address changing, without a new deployment for you to notice, and without anyone holding the asset signing anything.

Three things worth doing:

  1. Resolve before you trust. Check what an address actually dispatches to before you write it into a config. Naming, block explorer labels and token lists are all claims; the dispatch route is a fact.
  2. Record the implementation, not just the address. If you pin dependencies anywhere, pin what it resolved to and when.
  3. Watch the ones that can move. An implementation change on a contract you depend on is a silent event by design. Nothing emits “your dependency changed” to you.

We now re-resolve this list every week and diff it, because the interesting moment is not the census, it is the change. Anything that moves gets written up.

You can run the same check on any address, no API key needed, with the proxy detector.

Share

Resolve any address yourself

The proxy detector runs the same describe call used for this census. No key, no wallet, no signup.