We manage your security to weather the AI storm →

← Resource Hub

UUPS vs Transparent Proxy: Security Tradeoffs Compared

By Owen Thurm Published 6 min read

UUPS vs Transparent Proxy: Security Tradeoffs Compared

Quick answer. UUPS suits most contracts: it deploys cheaper, and OpenZeppelin's recommendation is shifting to it. The upgrade code lives in your implementation, so one bad upgrade can lock the proxy for good. Transparent keeps upgrades in fixed proxy code.

Watch: UUPS vs Transparent Proxy: Security Tradeoffs Compared 1:41
Video transcript

UUPS suits most contracts and deploys cheaper, but its upgrade code lives in your implementation. One bad upgrade can lock the proxy for good.

The difference is where upgrade code lives: in the proxy for transparent, in your implementation for UUPS. Neither is safer by default.

Whoever can upgrade controls the contract. On transparent, that's the proxy admin's owner. On UUPS, it's whoever your authorize upgrade function allows.

Leave it open, and anyone can upgrade. Upgrade to code without upgrade logic, and the proxy is locked for good. Uninitialized implementations are a third risk.

In September 2021, OpenZeppelin warned that uninitialized UUPS implementations could be taken over and self-destructed. It initialized more than 150 and knew of no project that lost funds.

Call disable initializers in the implementation's constructor. Set proxy state in the initializer. Then deploy and initialize in one transaction.

Guardian's audit reports show failures in every pattern, from a proxy admin Safe with a threshold of one to an upgrade that could skip a beacon's 48-hour delay.

Use UUPS with access control you trust, transparent when upgrade code must stay outside the implementation, beacon for many identical instances, and no proxy for code that should never change.

Whichever you choose, keep upgrade authority in a multisig behind a timelock. Read the full article, or get your upgrade paths reviewed at guardian.security.

Most upgradeable contracts sit behind one of two OpenZeppelin proxies: transparent or UUPS. Both keep the implementation address in the same ERC-1967 storage slot.

The difference is where the upgrade code lives. That decides who can upgrade, what deployment costs, and how an upgrade goes wrong.

This guide uses OpenZeppelin Contracts 5.x, whose latest release is 5.7.0 (July 2026). For how delegatecall proxies work, start with upgradeable smart contracts explained.

How They Differ

A transparent proxy carries its own upgrade logic. Its constructor deploys a ProxyAdmin contract and stores that address as an immutable admin.

Calls from the admin can only upgrade. Calls from anyone else go to the implementation.

A UUPS proxy is a plain ERC1967Proxy, which OpenZeppelin notes "is not by itself upgradeable". The upgrade function comes from UUPSUpgradeable in your implementation, guarded by an _authorizeUpgrade you write.

UUPS takes its name from ERC-1822, now marked Stagnant. The slot layout both patterns use, ERC-1967, is Final.

Property Transparent UUPS
Upgrade logic lives inThe proxyThe implementation
Who can upgradeThe ProxyAdmin's ownerWhoever _authorizeUpgrade allows
Admin can use the appNo, admin calls can only upgradeYes
Deployment costHigher: admin logic plus a ProxyAdminLower
Per-call overheadCaller compared with an immutable adminNo admin check
Can lose upgradeabilityNot through an implementation upgradeYes, by upgrading to code without upgrade logic
Selector clashesHandled by routing on the callerThe proxy has no functions to clash

Selector clashes

A proxy with its own functions can shadow implementation functions that share a 4-byte selector. Transparent routing exists to stop that, by checking the caller instead of the selector.

OpenZeppelin's source warns against adding external functions to its transparent proxy, because the compiler will not check them for clashes.

Custom proxies can get this wrong. In Papertrade L-05, a transparent-style proxy exposed its admin functions to every caller, so a matching implementation function would become unreachable.

Gas

OpenZeppelin calls UUPS proxies "lightweight" and says the transparent proxy "is more expensive to deploy".

Per call, the v5 transparent proxy compares the caller with an immutable admin, a choice OpenZeppelin made to avoid a storage read before each call.

Where Upgrade Authority Lives

Whoever can upgrade controls the contract. Know exactly who that is.

Transparent: the ProxyAdmin's owner

In v5, the address you pass to the constructor does not become the admin. It becomes the owner of a new ProxyAdmin, and the admin address can never change.

To hand over upgrades, you transfer the ProxyAdmin's ownership. In Citrea L-01, the bridge's handover scripts never did.

UUPS: your _authorizeUpgrade

OpenZeppelin says _authorizeUpgrade "must be overridden to include access restriction to the upgrade mechanism". Leave it open and anyone can upgrade.

UUPS authority is as flexible as your access control: an owner, a role or a timelock. It is also as fragile. See five upgradeable contract vulnerabilities.

Never put a UUPS implementation behind a transparent proxy. OpenZeppelin warns this "might allow non-admins to perform upgrade operations".

Bricking and Initialization

A UUPS proxy can lose its upgrade path. Upgrade to code without upgrade logic, and nothing can change the implementation again.

UUPSUpgradeable blocks upgrades to non-compliant code by checking proxiableUUID. OpenZeppelin warns the risk returns if a later upgrade keeps upgradeability but removes that check.

The mistake also happens at launch. In M0 L-02, contracts meant to be UUPS never inherited UUPSUpgradeable, so they could not be upgraded at all.

Initialization

Proxies run an initializer instead of a constructor. The implementation's own storage is never used, so lock it.

In September 2021, OpenZeppelin warned that uninitialized UUPS implementations (versions 4.1.0 to 4.3.1) could be taken over and self-destructed, bricking every proxy that used them.

OpenZeppelin initialized more than 150 such implementations and knew of no project that lost funds. The 4.3.2 fix made upgrade functions run only through a proxy.

On chains with EIP-6780, SELFDESTRUCT no longer deletes code outside the creating transaction. The basics still apply:

  • Call _disableInitializers() in the implementation's constructor.
  • Set proxy state in the initializer; constructor storage writes never reach the proxy.
  • Deploy and initialize in one transaction. Since 5.6.0, ERC1967Proxy reverts without initialization data.

More patterns are in initializer vulnerabilities in upgradeable contracts.

Beacon Proxies

A beacon proxy reads its implementation from a separate beacon contract. One upgrade on the beacon moves every proxy that follows it, in a single transaction.

Use it for many identical instances, such as vaults or wallets from a factory. The risk is the same feature: one bad upgrade reaches every instance at once.

Audit Findings by Pattern

These come from Guardian's public audit reports.

Pattern Finding What went wrong
TransparentUSDT0 L-01The ProxyAdmin's Safe had five owners and a threshold of one
TransparentVeranta I-08Upgrade scripts targeted the wrong proxy; nothing checks ABI compatibility
UUPSBracket H-02A UUPS vault set its state in a constructor
UUPSGinza Gaming M-01A new version dropped _disableInitializers from its constructor
UUPSOrderly L-34An overridden upgradeTo lacked onlyProxy
UUPSBracket M-03Variables inserted mid-layout reset used nonces, allowing signature replay
Beacon3Jane L-02A routine upgrade could block funding in every vault at once
BeaconEthena M-03A ProxyAdmin upgrade could skip the beacon's 48-hour delay
AnyEthos I-28Separate deploy and initialize steps let anyone initialize first

The Ethena finding shows a wider rule: layered delays are only as strong as the shortest path around them.

Which One to Use

Situation Use
A new contract, with access control you trustUUPS
Upgrade code must stay outside the implementationTransparent
Many identical instances from a factoryBeacon
Code that should never changeNo proxy

Whichever you choose:

  • Hold upgrade authority in a multisig, behind a timelock.
  • Test every upgrade on a fork, including storage layout.
  • Moving from OpenZeppelin v4? Read the v4 to v5 migration guide first.

How Guardian Helps

Guardian's smart contract audit runs three passes: a design review, an invariant suite, then senior researchers attacking what is left. With Vanguard, every update re-enters the same loop.

After launch, Managed Security monitors contracts on every block, including upgrades and role and ownership changes.

Its contract inventory for onchain teams lists every deployed address with its owner, its upgrade path, and whether the running code is the code that was reviewed.

Frequently Asked Questions

Is UUPS safer than a transparent proxy?

Neither is safer by default. UUPS puts the upgrade code in your implementation, so a weak _authorizeUpgrade or a bad upgrade can expose or lock the proxy. Transparent keeps that code in the proxy, at a higher deployment cost.

Can a UUPS proxy become non-upgradeable?

Yes. Upgrading to an implementation without upgrade logic locks the proxy for good. OpenZeppelin's UUPSUpgradeable blocks upgrades to non-compliant code, but an upgrade that removes that check brings the risk back.

Why can't the admin of a transparent proxy use the contract?

The proxy never forwards the admin's calls to the implementation; that routing is how it avoids selector clashes. In OpenZeppelin v5 the admin is a ProxyAdmin contract deployed with each proxy, so your accounts can still use the app.

When should I use a beacon proxy?

When you deploy many instances of the same contract, such as vaults or wallets from a factory. One upgrade on the beacon moves them all in a single transaction, which also means one bad upgrade reaches every instance.

Sources

Get your proxies and upgrade paths reviewed

Have Guardian check who can upgrade, how, and what one bad upgrade would break, before it ships.