Solidity 智能合约 UUPS 代理升级实战
代理升级模式包括不同的实现方式,主要有透明代理、UUPS 代理和 Beacon 代理。本文主要讲解 UUPS 代理的特点和代码实战。
一.UUPS 代理升级介绍
UUPS 代理是 EIP-1822 提出的标准,它改进了传统的透明代理模式,减少了代理合约的复杂性和 gas 消耗。UUPS 代理合约只包含代理逻辑和升级逻辑,且升级合约的权限控制嵌入到逻辑合约中; UUPS 代理升级的特点如下:
- UUPS 代理模式允许升级逻辑在逻辑合约中控制,减少了代理合约的复杂度。
- 代理合约不再包含业务逻辑,减少了 gas 成本。
- 逻辑合约中需要实现 _authorizeUpgrade 方法来控制升级权限。
二. UUPS 代理升级实战
下面我们写两个简单的合约:UUPSContractV1 和 UUPSContractV2,UUPSContractV1 合约里面写一个简单的 setValue 函数,UUPSContractV2 升级之后加入两个函数,分别是 incrementValue 和 upgradeCall。
1.合约代码细节
- UUPSContractV1 合约代码如下
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract UUPSContractV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
uint256 public value;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
value = 10000;
}
function setValue(uint256 _value) public {
value = _value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
- UUPSContractV2 合约代码如下
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/// @custom:oz-upgrades-from UUPSContractV1
contract UUPSContractV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
uint256 public value;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(){
}
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
value = 10;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
function setValue(uint256 _value) public onlyOwner {
value = _value;
}
function incrementValue() public onlyOwner {
value += 1;
}
function upgradeCall() public onlyOwner {
value = 100;
}
}
UUPSContractV2 里面我们写了
/// @custom:oz-upgrades-from UUPSContractV1
这个是必要的,因为我们升级脚本里面使用了 @openzeppelin-foundry-upgrades/Upgrades.sol 这个代码库,如果不加这段代码,升级部署的时候会报错。
2.部署脚本细节
- 部署 UUPSContractV1 的脚本 DeployUUPSProxyScript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "forge-std/Script.sol";
import "../src/UUPSContractV1.sol";
contract DeployUUPSProxyScript is Script {
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAddress = vm.addr(deployerPrivateKey);
console.log("Deploying contracts with the account:", deployerAddress);
vm.startBroadcast(deployerPrivateKey);
UUPSContractV1 implementation = new UUPSContractV1();
console.log("UUPSContractV1 address:", address(implementation));
bytes memory data = abi.encodeCall(implementation.initialize, deployerAddress);
ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), data);
vm.stopBroadcast();
console.log("UUPS Proxy Address:", address(proxy));
}
}
执行该命令进行部署 V1 的合约
forge script script/DeployUUPSProxy.sol:DeployUUPSProxyScript --rpc-url http://127.0.0.1:8545 --private-key $PRIVATE_KEY --broadcast -vvvv
- 将 UUPSContractV1 升级到 V2 的升级脚本
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import { Upgrades } from "@openzeppelin-foundry-upgrades/Upgrades.sol";
import "../src/UUPSContractV2.sol";
contract UUPSContractV2Script is Script {
address public proxy = 0xEd63674ebAEd5D5fe567b41Bab2ac16e2f9c1386;
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAddress = vm.addr(deployerPrivateKey);
console.log("Deploying Address:", deployerAddress);
vm.startBroadcast(deployerPrivateKey);
Upgrades.upgradeProxy(address(proxy), "UUPSContractV2.sol:UUPSContractV2", "", deployerAddress);
(bool successful,) = address(proxy).call(abi.encodeWithSelector(UUPSContractV2.incrementValue.selector));
console.log("incrementValue success:", successful);
vm.stopBroadcast();
}
}
执行下面部署命令升级合约
forge script script/UUPSContractV2.s.sol:UUPSContractV2Script --rpc-url http://127.0.0.1 --private-key $PRIVATE_KEY --broadcast -vvvv
执行这个命令如果遇到让您 clean 合约的错误,可以执行 forge clean & forge build 之后再执行上面的部署命令可以解决问题。
以上就是 UUPS 代理合约升级的实战过程,如果您想深入学习 Web3 技术,可以联系 The Web 社区,我们提供一站式的区块链技术培训服务。
三. The Web3 社区简介
The Web3 是一个专注 Web3 技术解决方案设计与开发、技术教程设计与开发、Web3 项目投研分析和 Web3 项目孵化,旨在将开发者,创业者,投资者和项目方联系在一起的社区。
The web3 业务范围
- 技术服务:提供交易所钱包,HD 钱包,硬件钱包,MPC 托管钱包,Dapps, 质押协议,L1,L2 ,L3 公链,数据可用层(DA)和中心化交易所技术开发服务。
- 技术培训:提供个人技术成长和企业技术培训服务
- 开发者活动承接:各种线下线上黑客松和开发者 meetup 活动承接
- 除此之外,我们还和 "磐石安全实验室" 深入合作,开展去中心化安全审计服务
四.The Web3 社区官方链接
- github: https://github.com/the-web3
- X: https://twitter.com/0xtheweb3cn
- telegram: https://t.me/+pmoh3D4uTAFjNWM1
- discord: https://discord.gg/muhuXRsK
- the web3 官网:https://thewebthree.xyz/
- the web3 技术服务网站:https://web.thewebthree.xyz/