智能合约在部署后,通常无法像传统软件一样直接进行修改或更新。这是因为区块链上的智能合约一旦被部署,就会被记录在区块链上,并且其代码是不可更改的。这种不可变性是区块链的一个重要特性,但也带来了一些问题,尤其是在智能合约的升级和维护方面。
具备可升级性的智能合约可以帮助开发者在合约的生命周期内进行必要的修改和优化,而不必担心修改后会破坏已有的协议或数据。具体来说,智能合约的可升级性主要有以下几个原因和优势:
升级智能合约是一个多步骤且容易出错的过程,因此为了尽量减少人为错误的可能性,使用一个尽可能自动化该过程的工具是理想的。OpenZeppelin提供了一系列的插件,可以帮助开发者在Solidity智能合约中实现可升级性。下面以Foundry为例,介绍如何使用OpenZeppelin插件进行智能合约升级。
# 使用vscode作为IDE,所以这里使用了 --vscode 参数
$ forge init foundry-upgrades --vscode && cd foundry-upgrades
# 安装依赖
$ forge install OpenZeppelin/openzeppelin-contracts-upgradeable
$ forge install OpenZeppelin/openzeppelin-foundry-upgrades
# for vscode
$ forge remappings > remappings.txt
在项目的配置文件中增加如下配置:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# 新增
# 启用构建信息输出,生成包含详细编译器信息和合约源码的 build-info 文件,便于合约升级工具(例如 OpenZeppelin 的 @openzeppelin/upgrades-core)使用和验证。
build_info = true
# 启用存储布局的输出,帮助开发者分析合约存储变量的布局,特别是在合约升级时确保存储布局一致性,避免数据丢失。
extra_output = ["storageLayout"]
# 启用 Solidity 合约的抽象语法树(AST)输出,提供合约源码的结构化表示,帮助进行静态分析、代码优化和合约安全检查。
ast = true
在src目录下新增ContractA合约,作为我们的基础合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract ContractA is Initializable{
uint256 public value;
function initialize(uint256 _setValue) public initializer {
value = _setValue;
}
}
然后新增ContractB合约,用作升级使用:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/// @custom:oz-upgrades-from ContractA
// 或者
/// @custom:oz-upgrades-from ./ContractA.sol:ContractA
contract ContractB is Initializable {
uint256 public value;
function initialize(uint256 _setValue) public initializer {
value = _setValue;
}
function increaseValue() public {
value += 10;
}
}
/// @custom:oz-upgrades-from标签是OpenZeppelin Upgrades插件中的一个自定义注释,它用于指定合约的来源版本,确保新的逻辑合约与旧版本合约兼容。在实现可升级合约时,这个标签帮助工具识别合约之间的继承关系和存储结构的兼容性,从而支持平滑、安全的合约升级。
下面需要对我们的合约进行测试,确保它们能正常运行。在test目录下新增Upgrades.t.sol测试合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "openzeppelin-foundry-upgrades/Upgrades.sol";
import "../src/ContractA.sol";
import "../src/ContractB.sol";
contract UpgradesTest is Test {
function testTransparent() public {
// 部署一个以 ContractA 作为实现的透明代理,并使用 10 作为 ContractA 的初始化参数
address proxy = Upgrades.deployTransparentProxy(
"ContractA.sol",
msg.sender,
abi.encodeCall(ContractA.initialize, (10))
);
// 获取合约实例
ContractA instance = ContractA(proxy);
// 获取代理的实现地址
address implAddrV1 = Upgrades.getImplementationAddress(proxy);
// 获取代理的 admin 地址
address adminAddr = Upgrades.getAdminAddress(proxy);
// 确保 admin 地址有效
assertFalse(adminAddr == address(0));
// 记录初始之
console.log("----------------------------------");
console.log("Value before upgrade --> ", instance.value());
console.log("----------------------------------");
// 验证是否符合预期
assertEq(instance.value(), 10);
// 将代理升级到 ContractB
Upgrades.upgradeProxy(proxy, "ContractB.sol", "", msg.sender);
// 获取升级后的新实现地址
address implAddrV2 = Upgrades.getImplementationAddress(proxy);
// 验证 admin 地址并未改变
assertEq(Upgrades.getAdminAddress(proxy), adminAddr);
// 验证实现地址发生了变化
assertFalse(implAddrV1 == implAddrV2);
// 调用 increaseValue
ContractB(address(instance)).increaseValue();
// 记录并验证升级后的新值
console.log("----------------------------------");
console.log("Value after upgrade --> ", instance.value());
console.log("----------------------------------");
assertEq(instance.value(), 20);
}
}
使用下面的命令进行验证测试:
$ forge clean && forge test -vvv --ffi --mt testTransparent
[⠊] Compiling...
[⠰] Compiling 63 files with Solc 0.8.28
[⠑] Solc 0.8.28 finished in 2.34s
Compiler run successful!
Ran 1 test for test/Upgrades.t.sol:UpgradesTest
[PASS] testTransparent() (gas: 42618471)
Logs:
----------------------------------
Value before upgrade --> 10
----------------------------------
----------------------------------
Value after upgrade --> 20
----------------------------------
----------------------------------
Value after upgrade --> 30
----------------------------------
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1166.35s (1166.35s CPU time)
Ran 1 test suite in 1166.37s (1166.35s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特
—