A simple example project demonstrating UUPS (Universal Upgradeable Proxy Standard) proxy pattern using Foundry and OpenZeppelin Contracts.
This project implements an upgradeable smart contract system where:
- BoxV1: Initial implementation with a
getNumber()function - BoxV2: Upgraded version that adds a
setNumber()function - Both versions use the same proxy, allowing upgrades without losing state
foundry-upgrades/
├── src/
│ ├── BoxV1.sol # Initial implementation (version 1)
│ └── BoxV2.sol # Upgraded implementation (version 2)
├── script/
│ ├── DeployBox.s.sol # Deployment script
│ └── UpgradeBox.s.sol # Upgrade script
├── test/
│ └── DeployAndUpgradeTest.t.sol # Test suite
└── foundry.toml # Foundry configuration
- Foundry
- Solidity ^0.8.19
# Install dependencies
forge install
# Build the project
forge buildDeploy the initial BoxV1 implementation with a proxy
forge script script/DeployBox.s.sol:DeployBox --rpc-url <YOUR_RPC_URL> --private-key <YOUR_PRIVATE_KEY> --broadcastUpgrade the proxy to BoxV2:
# Set the proxy address
export PROXY_ADDRESS=<your_proxy_address>
# Run the upgrade script
forge script script/UpgradeBox.s.sol:UpgradeBox --rpc-url <YOUR_RPC_URL> --private-key <YOUR_PRIVATE_KEY> --broadcastRun the test suite:
forge testOr run with verbose output:
forge test -vvv-
Deployment:
DeployBoxdeploys theBoxV1implementation and creates anERC1967Proxythat points to it. The proxy is initialized with an owner. -
Upgrade:
UpgradeBoxdeploys a newBoxV2implementation and upgrades the existing proxy to point to it. The upgrade is authorized by the owner. -
State Preservation: Since the proxy storage is separate from the implementation, all state (like
number) is preserved across upgrades.
- ✅ UUPS proxy pattern (upgradeable by implementation)
- ✅ Owner-based access control for upgrades
- ✅ State preservation across upgrades
- ✅ Comprehensive test coverage
getNumber(): Returns the stored numberversion(): Returns version 1initialize(address): Initializes the contract with an owner
getNumber(): Returns the stored number (inherited)setNumber(uint256): Sets a new number valueversion(): Returns version 2_authorizeUpgrade(address): Authorizes upgrades (no restrictions in this example)
- Restrict
_authorizeUpgrade()to only authorized addresses (e.g., usingonlyOwner) - Thoroughly test upgrades before deploying
- Use a multisig wallet for upgrade authorization
- Verify implementation contracts before upgrading
MIT