← Protocols
ERC-1822 — Universal Upgradeable Proxy Standard (UUPS)
Standard / EIP·EVM

ERC-1822 — Universal Upgradeable Proxy Standard (UUPS)

01Description

Stagnant ERC defining the UUPS proxy pattern, where upgrade logic lives in the implementation contract (not the proxy) and the implementation is stored at `keccak256('PROXIABLE')`. The widely deployed pattern via OpenZeppelin's `UUPSUpgradeable` (which uses ERC-1967 storage slots in practice).

02Best for
  • 01minimal upgradeable proxies
  • 02lower per-call gas vs transparent proxies
  • 03single-implementation upgrade flows
  • 04OpenZeppelin upgradeable contracts
03Install
  • pnpm add @openzeppelin/contracts-upgradeable @openzeppelin/contracts
  • pnpm add -D @openzeppelin/hardhat-upgrades
05Prompt snippet
Inherit `UUPSUpgradeable` and `Initializable` from `@openzeppelin/contracts-upgradeable`, replace constructors with `initialize()` guarded by `initializer`, and override `_authorizeUpgrade(address newImplementation)` to gate upgrades (typically with `onlyOwner` or an `AccessControl` role). The proxy delegates `upgradeTo`/`upgradeToAndCall` into the implementation, which checks `proxiableUUID()` returns ERC-1967's implementation slot before swapping. Always deploy via `@openzeppelin/hardhat-upgrades` `deployProxy` / `upgradeProxy` so storage-layout linting runs.
06Gotchas
  • UUPS upgrade authorization lives in the IMPLEMENTATION — if you forget to override `_authorizeUpgrade`, anyone can upgrade the proxy and drain it; if a new implementation forgets to inherit `UUPSUpgradeable` you brick the proxy permanently.
  • Constructors do not run on the proxy — any constructor state ends up only on the implementation contract and is invisible behind the proxy. Use `initialize()` + `initializer` modifier instead.
  • Storage layout is append-only across upgrades — never reorder, remove, or change types of state variables; use `__gap` uint256 arrays to reserve slots in inherited upgradeable contracts.
  • Calling `selfdestruct` or `delegatecall` to a destructible contract from the implementation can permanently break the proxy — OpenZeppelin's `UUPSUpgradeable` includes a `_disableInitializers()` guard that should be called in the implementation's constructor.
  • ERC-1822's original `keccak256('PROXIABLE')` slot differs from ERC-1967's implementation slot — modern OZ UUPS uses the ERC-1967 slot for explorer compatibility; mixing the two breaks detection.
07Alternatives