Standard / EIP·EVM
ERC-1822 — Universal Upgradeable Proxy Standard (UUPS)
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).
- 01minimal upgradeable proxies
- 02lower per-call gas vs transparent proxies
- 03single-implementation upgrade flows
- 04OpenZeppelin upgradeable contracts
- pnpm add @openzeppelin/contracts-upgradeable @openzeppelin/contracts
- pnpm add -D @openzeppelin/hardhat-upgrades
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.
- ⚑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.