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.
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 in | The proxy | The implementation |
| Who can upgrade | The ProxyAdmin's owner | Whoever _authorizeUpgrade allows |
| Admin can use the app | No, admin calls can only upgrade | Yes |
| Deployment cost | Higher: admin logic plus a ProxyAdmin | Lower |
| Per-call overhead | Caller compared with an immutable admin | No admin check |
| Can lose upgradeability | Not through an implementation upgrade | Yes, by upgrading to code without upgrade logic |
| Selector clashes | Handled by routing on the caller | The 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,
ERC1967Proxyreverts 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 |
|---|---|---|
| Transparent | USDT0 L-01 | The ProxyAdmin's Safe had five owners and a threshold of one |
| Transparent | Veranta I-08 | Upgrade scripts targeted the wrong proxy; nothing checks ABI compatibility |
| UUPS | Bracket H-02 | A UUPS vault set its state in a constructor |
| UUPS | Ginza Gaming M-01 | A new version dropped _disableInitializers from its constructor |
| UUPS | Orderly L-34 | An overridden upgradeTo lacked onlyProxy |
| UUPS | Bracket M-03 | Variables inserted mid-layout reset used nonces, allowing signature replay |
| Beacon | 3Jane L-02 | A routine upgrade could block funding in every vault at once |
| Beacon | Ethena M-03 | A ProxyAdmin upgrade could skip the beacon's 48-hour delay |
| Any | Ethos I-28 | Separate 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 trust | UUPS |
| Upgrade code must stay outside the implementation | Transparent |
| Many identical instances from a factory | Beacon |
| Code that should never change | No 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
- OpenZeppelin: Contracts 5.x Proxy API, including Transparent vs UUPS Proxies
- GitHub, OpenZeppelin: openzeppelin-contracts v5.7.0 release (July 29, 2026)
- GitHub, OpenZeppelin: TransparentUpgradeableProxy.sol (v5.7.0)
- GitHub, OpenZeppelin: UUPSUpgradeable.sol (v5.7.0)
- GitHub, OpenZeppelin: CHANGELOG, 4.3.2 (September 14, 2021) and 5.6.0 (February 25, 2026) entries
- Ethereum Improvement Proposals: ERC-1967, Proxy Storage Slots (Final)
- Ethereum Improvement Proposals: ERC-1822, Universal Upgradeable Proxy Standard (Stagnant)
- Ethereum Improvement Proposals: EIP-6780, SELFDESTRUCT only in same transaction (Final)
- GitHub Advisory Database: UUPSUpgradeable vulnerability in @openzeppelin/contracts (September 15, 2021)
- OpenZeppelin Forum: UUPSUpgradeable Vulnerability Post-mortem (September 16, 2021)
- Guardian: Public audit portfolio, 320 reports (as of November 6, 2026)
Get your proxies and upgrade paths reviewed
Have Guardian check who can upgrade, how, and what one bad upgrade would break, before it ships.