patternjavascriptMajor
UUPS proxy pattern: upgrading smart contracts with OpenZeppelin
Viewed 0 times
OpenZeppelin Contracts Upgradeable 5.x, Hardhat Upgrades Plugin
UUPSupgradeableproxyERC-1967initializeOpenZeppelin upgrades
Error Messages
Problem
Smart contracts are immutable once deployed. UUPS (Universal Upgradeable Proxy Standard) allows upgrading logic while keeping state and the same contract address.
Solution
Inherit from UUPSUpgradeable, use initializer instead of constructor, and override _authorizeUpgrade with access control.
contract MyContractV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
function initialize(address owner) public initializer {
__Ownable_init(owner);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address) internal override onlyOwner {}
}Why
UUPS puts upgrade logic in the implementation (not the proxy), reducing proxy gas costs. The upgrade mechanism can be removed entirely if desired.
Gotchas
- Never use a constructor — use initialize() with the initializer modifier to prevent re-initialization
- Storage layout must be backward compatible when upgrading — never remove or reorder existing state variables
- Use OpenZeppelin's Upgrades plugin for Hardhat/Foundry to validate storage compatibility before upgrading
Code Snippets
Deploying a UUPS proxy with Hardhat upgrades plugin
const { ethers, upgrades } = require('hardhat');
async function main() {
const MyContract = await ethers.getContractFactory('MyContract');
// Initial deploy
const proxy = await upgrades.deployProxy(MyContract, [owner.address], {
kind: 'uups',
});
await proxy.waitForDeployment();
console.log('Proxy deployed to:', await proxy.getAddress());
// Upgrade
const MyContractV2 = await ethers.getContractFactory('MyContractV2');
const upgraded = await upgrades.upgradeProxy(await proxy.getAddress(), MyContractV2);
console.log('Upgraded to V2');
}Context
Deploying contracts that may need future logic upgrades without migrating state
Revisions (0)
No revisions yet.