Writing and Testing Upgradeable Smart Contracts in Foundry : Welcome to Chapter 0x16 of our Foundry tutorial series! In this guide, we will dive deep into writing and testing upgradable smart contracts using Foundry. If you’re new to upgradeable contracts or proxy patterns, don’t worry—we’ll break down the concepts step by step. By the end of this tutorial, you’ll have a solid understanding of how to create and upgrade contracts securely using Foundry and OpenZeppelin.
Table of Contents
Let’s explore how smart contract Foundry upgrades with the OpenZeppelin work, how to interact with UUPS upgradeable contracts in Foundry, and best practices for testing upgrade paths.
Understanding Proxies and Implementation Contracts

What is a Proxy Contract?
A proxy contract is a smart contract that delegates calls to another contract, called the implementation contract. This allows for upgrades by deploying a new implementation contract while keeping the proxy contract’s address unchanged.
What is an Implementation Contract?
The implementation contract contains the actual logic and functionalities of your smart contract. The proxy interacts with this contract and forwards function calls to it.
Key Differences Between Proxy and Implementation Contracts
Feature | Proxy Contract | Implementation Contract |
---|---|---|
Holds State | Yes | No |
Contains Logic | No | Yes |
Upgradeable | Yes | No |
Interacts with Users | Yes | No |
By using this approach, we can upgrade contracts without losing data, making smart contracts more adaptable and sustainable.
Why Upgradeable Smart Contracts?

Smart contracts are immutable by default, meaning that once deployed, their code cannot be changed. However, in many real-world applications, there is a need to fix bugs, optimize gas usage, or add new features without changing the contract’s address. This is where upgradeable contracts come into play using proxies.
The Proxy Pattern
The proxy pattern separates the logic (implementation contract) from the storage (proxy contract). Instead of modifying the contract directly, we redirect function calls through a proxy to the latest version of the contract.
Two common types of proxies used in Solidity are:
- Transparent Proxy (used in OpenZeppelin)
- UUPS (Universal Upgradeable Proxy Standard)
Using Foundry to Explore Upgradeable Smart Contracts

Foundry is a powerful Solidity development framework that allows us to:
- Write upgradeable smart contracts
- Deploy and test proxies and implementation contracts
- Simulate upgrades and test different upgrade paths
- Use custom scripts to automate the upgrade process
Let’s get started by setting up our project!
Step 1: Setting Up Foundry
If you haven’t installed Foundry yet, run the following command:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Initialize a new Foundry project:
forge init foundry-upgradeable-contracts
cd foundry-upgradeable-contracts
To work with OpenZeppelin’s upgradeable contracts, install the required dependencies:
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
Now, let’s write our first upgradeable contract!
Step 2: Writing an Upgradeable Smart Contracts in Foundry

Using the OpenZeppelin upgradeable library, we create an upgradeable contract with the UUPS (Universal Upgradeable Proxy Standard).
Here’s a simple upgradeable contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract MyUpgradeableContract is UUPSUpgradeable, OwnableUpgradeable {
uint256 public value;
function initialize(uint256 _value) public initializer {
__Ownable_init();
__UUPSUpgradeable_init();
value = _value;
}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
Key Takeaways:
initializer
ensures the function runs only once (unlike constructors in regular contracts).UUPSUpgradeable
handles contract upgrades securely._authorizeUpgrade
restricts upgrades to the owner.
Step 3: Deploying and Interacting with the Smart Contract
Deploying the Proxy Contract
To deploy the proxy, run:
forge create --contracts MyUpgradeableContract.sol --rpc-url <YOUR_RPC_URL> --private-key <YOUR_PRIVATE_KEY>
Interacting with the Contract
After deployment, interact with it using Foundry’s CLI:
cast call <PROXY_ADDRESS> "value()"
cast send <PROXY_ADDRESS> "setValue(uint256)" 100
Step 4: Upgrading the Smart Contract
Let’s modify our contract by adding a new function:
function newFunction() public pure returns (string memory) {
return "Upgrade successful!";
}
Compile the updated contract:
forge build
Upgrade the contract with:
cast send <PROXY_ADDRESS> "upgradeTo(address)" <NEW_IMPLEMENTATION_ADDRESS>
Verify the upgrade:
cast call <PROXY_ADDRESS> "newFunction()"
Step 5: Testing Upgrade Paths in Foundry
Testing is crucial when working with upgradeable contracts. We can write a test using Foundry’s forge test
framework.
Example Test File
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import "MyUpgradeableContract.sol";
contract UpgradeTest is Test {
MyUpgradeableContract public contractV1;
function setUp() public {
contractV1 = new MyUpgradeableContract();
contractV1.initialize(42);
}
function testUpgrade() public {
assertEq(contractV1.value(), 42);
contractV1.setValue(100);
assertEq(contractV1.value(), 100);
}
}
Run tests with:
forge test
Custom Scripts in Foundry

To automate contract upgrades, we can write a custom Foundry script.
Example Upgrade Script
import "forge-std/Script.sol";
import "MyUpgradeableContract.sol";
contract UpgradeScript is Script {
function run() external {
vm.startBroadcast();
MyUpgradeableContract newImplementation = new MyUpgradeableContract();
vm.stopBroadcast();
}
}
Execute the script with:
forge script script/UpgradeScript.sol --rpc-url <YOUR_RPC_URL> --private-key <YOUR_PRIVATE_KEY>
Conclusion
Congratulations! You’ve learned how to write, deploy, test, and upgrade smart contracts using Foundry. We covered:
- Upgradeable smart contracts and proxy patterns
- Deploying and interacting with UUPS upgradeable contracts
- Testing upgrade paths in Foundry
- Automating upgrades with custom scripts
Foundry makes smart contract development easier and more efficient. Now, it’s your turn to experiment and build upgradeable contracts!
FAQs
1. What is the difference between a proxy contract and an implementation contract?
A proxy contract stores data and delegates function calls to the implementation contract, which contains the actual logic.
2. How does Foundry help in testing upgradeable contracts?
Foundry allows automated testing using forge test
, making it easy to test different upgrade paths.
3. What are the best practices for upgrading contracts securely?
✅ Use OpenZeppelin’s upgradeable libraries
✅ Restrict upgrades to authorized users
✅ Test upgrades thoroughly before deploying
Happy coding with Foundry!