Upgradeable Proxies Explained in Detail
An upgradeable proxy is a contract that stores state and delegates execution to a separate implementation contract. Users call the proxy address. The proxy uses delegatecall, so implementation code runs against the proxy storage.
Smart contract example
A token proxy points to TokenV1. Later, governance upgrades the proxy to TokenV2.
The token balances remain in the proxy storage. Only the implementation address changes.
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let ok := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch ok
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
Upgradeable Proxies in Auditing
Upgradeability changes the trust model: safe logic today can be replaced with unsafe logic later.
Auditors need to review who can upgrade, how upgrades are validated, whether initializers are safe, and whether storage layout remains compatible.
Red flags in code
-
Upgrade function callable by weak or unclear authority.
-
Proxy admin is an EOA with no timelock or multisig.
-
Implementation contains unsafe
delegatecall. -
Proxy and implementation use custom storage slots without clear EIP-1967 compatibility.
-
No event emitted on upgrades.
-
Users interact directly with the implementation contract.
How to test or review it
-
Check the proxy type first: transparent, UUPS, beacon, or custom.
-
Verify the implementation slot, admin slot, upgrade authorization, and initialization path.
-
Compare storage layouts across all versions and check for storage collision.
-
Test that unauthorized users cannot upgrade.
-
Test that the implementation cannot be initialized directly.