Intermediate
Proxies & Upgradeable Contracts — Kudos build — upgrade path
Understand how upgrades work.
Developer Intermediate
5/6 — Proxies & Upgradeable Contracts — Kudos build — upgrade path
Objective Put the Kudos token/badge behind UUPS proxies so we can ship new features without changing addresses.
Flow Create upgradeable versions, guard _authorizeUpgrade, expose a version getter, and rehearse an upgrade script.
What to write
- Upgradeable scaffold
Initialize roles, token, and permit.contract KudosUpgradeable is Initializable, UUPSUpgradeable, AccessControlUpgradeable, ERC20Upgradeable, ERC20PermitUpgradeable { bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); function initialize() public initializer { __ERC20_init("Kudos", "KUDOS"); __ERC20Permit_init("Kudos"); __AccessControl_init(); __UUPSUpgradeable_init(); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(UPGRADER_ROLE, msg.sender); _mint(msg.sender, 1_000_000 ether); }Initializers replace constructors behind proxies; roles stay intact.
- Guard upgrades
Restrict _authorizeUpgrade and expose version.function _authorizeUpgrade(address) internal override onlyRole(UPGRADER_ROLE) {} function version() external pure returns (string memory) { return "v2"; } }Locks upgrades to a role and exposes versioning for the UI.
- Deploy script
Proxy deploy + initialize.// Hardhat example const impl = await ethers.deployContract("KudosUpgradeable"); const proxy = await upgrades.deployProxy(impl, [], { initializer: "initialize" }); console.log("proxy", await proxy.getAddress());Ensures initialize runs once and addresses stay stable; verify implementation/admin slots after deploy.
Why Proxies keep addresses stable, version() gives visibility, and guarded upgrades plus rehearsals prevent bricking user balances.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts/access/AccessControlUpgradeable.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol";
import {ERC20PermitUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
contract KudosUpgradeable is Initializable, UUPSUpgradeable, AccessControlUpgradeable, ERC20Upgradeable, ERC20PermitUpgradeable {
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
function initialize() public initializer {
__ERC20_init("Kudos", "KUDOS");
__ERC20Permit_init("Kudos");
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(UPGRADER_ROLE, msg.sender);
_mint(msg.sender, 1_000_000 ether);
}
function _authorizeUpgrade(address) internal override onlyRole(UPGRADER_ROLE) {}
function version() external pure returns (string memory) { return "v2"; }
}
Next Lock this stack down with thorough tests.