Deploying Upgradeable Contracts using Transparent Proxy with Foundry
简介
This tutorial demonstrates how to deploy and upgrade smart contracts using the transparent proxy pattern with Foundry. The transparent proxy pattern allows you to upgrade your smart contracts while maintaining the same address and state.
Project Setup
- Create a new Foundry project:
forge init transparent-proxy-foundry-demo
cd transparent-proxy-foundry-demo
- Install OpenZeppelin contracts:
forge install OpenZeppelin/openzeppelin-contracts
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
- Add the following to
remappings.txt
:
@openzeppelin/=lib/openzeppelin-contracts/
@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/
- Update
foundry.toml
:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.24"
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/"
]
Writing Smart Contracts
- Create the initial Box contract in
src/Box.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract Box is Initializable {
uint256 private _value;
event ValueChanged(uint256 value);
function initialize(uint256 initialValue) public initializer {
_value = initialValue;
emit ValueChanged(initialValue);
}
function store(uint256 value) public {
_value = value;
emit ValueChanged(value);
}
function retrieve() public view returns (uint256) {
return _value;
}
}
- Create BoxV2 contract in
src/BoxV2.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract BoxV2 is Initializable {
uint256 private _value;
event ValueChanged(uint256 value);
function initialize(uint256 initialValue) public initializer {
_value = initialValue;
}
function store(uint256 value) public {
_value = value;
emit ValueChanged(value);
}
function retrieve() public view returns (uint256) {
return _value;
}
// New added function
function increment() public {
_value = _value + 1;
emit ValueChanged(_value);
}
}
Deployment Scripts
- Create the deployment script in
script/DeployBox.s.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "../src/Box.sol";
contract DeployBox is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
// Deploy implementation contract
Box box = new Box();
// Encode initialization data
bytes memory data = abi.encodeWithSelector(Box.initialize.selector, 42);
// Deploy proxy contract
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
address(box),
deployer,
data
);
// Get actual ProxyAdmin address
address proxyAdminAddress = address(uint160(uint256(vm.load(
address(proxy),
bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)
))));
vm.stopBroadcast();
console.log("Box implementation deployed to:", address(box));
console.log("Proxy deployed to:", address(proxy));
console.log("ProxyAdmin deployed to:", proxyAdminAddress);
}
}
- Create the upgrade script in
script/UpgradeBox.s.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "../src/Box.sol";
import "../src/BoxV2.sol";
contract UpgradeBox is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address proxyAddress = vm.envAddress("PROXY_ADDRESS");
address adminAddress = vm.envAddress("ADMIN_ADDRESS");
// Test before upgrade
console.log("============ Before Upgrade ============");
Box box = Box(proxyAddress);
uint256 valueBefore = box.retrieve();
console.log("Current value:", valueBefore);
vm.startBroadcast(deployerPrivateKey);
// Deploy new implementation
BoxV2 boxV2 = new BoxV2();
console.log("\n============ Deploying New Implementation ============");
console.log("New implementation:", address(boxV2));
// Upgrade using ProxyAdmin
ProxyAdmin proxyAdmin = ProxyAdmin(adminAddress);
proxyAdmin.upgradeAndCall(
ITransparentUpgradeableProxy(proxyAddress),
address(boxV2),
""
);
vm.stopBroadcast();
// Test after upgrade
console.log("\n============ After Upgrade ============");
BoxV2 upgradedBox = BoxV2(proxyAddress);
uint256 valueAfter = upgradedBox.retrieve();
console.log("Value after upgrade:", valueAfter);
console.log("Testing new increment function...");
vm.startBroadcast(deployerPrivateKey);
upgradedBox.increment();
vm.stopBroadcast();
uint256 valueAfterIncrement = upgradedBox.retrieve();
console.log("Value after increment:", valueAfterIncrement);
// Verify upgrade results
require(valueAfter == valueBefore, "State verification failed: Value changed during upgrade");
require(valueAfterIncrement == valueAfter + 1, "Function verification failed: Increment not working");
console.log("\n============ Upgrade Successful ============");
console.log("1. State preserved: Initial value maintained after upgrade");
console.log("2. New function working: Increment successfully added");
}
}
Environment Setup
- Create a
.env
file:
PRIVATE_KEY=your_private_key_here
RPC_URL=https://evmtestnet.confluxrpc.com