【从0学习Solidity】49. 通用可升级代理
- 博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。
- 关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!
- 本文收录于 不写代码没饭吃 的学习汇报系列,大家有兴趣的可以看一看。
- 欢迎访问我们的微信公众号:不写代码没饭吃,获取更多精彩内容、实用技巧、行业资讯等。您关注的是我们前进的动力!
这一讲,我们将介绍代理合约中选择器冲突(Selector Clash)的另一个解决办法:通用可升级代理(UUPS,universal upgradeable proxy standard)。教学代码由OpenZeppelin
的UUPSUpgradeable
简化而成,不应用于生产。
UUPS
我们在上一讲已经学习了"选择器冲突"(Selector Clash),即合约存在两个选择器相同的函数,可能会造成严重后果。作为透明代理的替代方案,UUPS也能解决这一问题。
UUPS(universal upgradeable proxy standard,通用可升级代理)将升级函数放在逻辑合约中。这样一来,如果有其它函数与升级函数存在“选择器冲突”,编译时就会报错。
下表中概括了普通可升级合约,透明代理,和UUPS的不同点:
UUPS合约
首先我们要复习一下WTF Solidity极简教程第23讲:Delegatecall。如果用户A通过合约B(代理合约)去delegatecall
合约C(逻辑合约),上下文仍是合约B的上下文,msg.sender
仍是用户A而不是合约B。因此,UUPS合约可以将升级函数放在逻辑合约中,并检查调用者是否为管理员。
UUPS的代理合约
UUPS的代理合约看起来像是个不可升级的代理合约,非常简单,因为升级函数被放在了逻辑合约中。它包含3
个变量:
-
implementation
:逻辑合约地址。 -
admin
:admin地址。 -
words
:字符串,可以通过逻辑合约的函数改变。
它包含2
个函数
- 构造函数:初始化admin和逻辑合约地址。
-
fallback()
:回调函数,将调用委托给逻辑合约。
contract UUPSProxy {
address public implementation; // 逻辑合约地址
address public admin; // admin地址
string public words; // 字符串,可以通过逻辑合约的函数改变
// 构造函数,初始化admin和逻辑合约地址
constructor(address _implementation){
admin = msg.sender;
implementation = _implementation;
}
// fallback函数,将调用委托给逻辑合约
fallback() external payable {
(bool success, bytes memory data) = implementation.delegatecall(msg.data);
}
}
UUPS的逻辑合约
UUPS的逻辑合约与第47讲中的不同是多了个升级函数。UUPS逻辑合约包含3
个状态变量,与保持代理合约一致,防止插槽冲突。它包含2
个
-
upgrade()
:升级函数,将改变逻辑合约地址implementation
,只能由admin
调用。 -
foo()
:旧UUPS逻辑合约会将words
的值改为"old"
,新的会改为"new"
。
// UUPS逻辑合约(升级函数写在逻辑合约内)
contract UUPS1{
// 状态变量和proxy合约一致,防止插槽冲突
address public implementation;
address public admin;
string public words; // 字符串,可以通过逻辑合约的函数改变
// 改变proxy中状态变量,选择器: 0xc2985578
function foo() public{
words = "old";
}
// 升级函数,改变逻辑合约地址,只能由admin调用。选择器:0x0900f010
// UUPS中,逻辑合约中必须包含升级函数,不然就不能再升级了。
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
}
// 新的UUPS逻辑合约
contract UUPS2{
// 状态变量和proxy合约一致,防止插槽冲突
address public implementation;
address public admin;
string public words; // 字符串,可以通过逻辑合约的函数改变
// 改变proxy中状态变量,选择器: 0xc2985578
function foo() public{
words = "new";
}
// 升级函数,改变逻辑合约地址,只能由admin调用。选择器:0x0900f010
// UUPS中,逻辑合约中必须包含升级函数,不然就不能再升级了。
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
}
Remix
实现
- 部署UUPS新旧逻辑合约
UUPS1
和UUPS2
。
- 部署UUPS代理合约
UUPSProxy
,将implementation
地址指向旧逻辑合约UUPS1
。
- 利用选择器
0xc2985578
,在代理合约中调用旧逻辑合约UUPS1
的foo()
函数,将words
的值改为"old"
。
- 利用在线ABI编码器HashEx获得二进制编码,调用升级函数
upgrade()
,将implementation
地址指向新逻辑合约UUPS2
。
- 利用选择器
0xc2985578
,在代理合约中调用新逻辑合约UUPS2
的foo()
函数,将words
的值改为"new"
。
总结
这一讲,我们介绍了代理合约“选择器冲突”的另一个解决方案:UUPS。与透明代理不同,UUPS将升级函数放在了逻辑合约中,从而使得"选择器冲突"不能通过编译。相比透明代理,UUPS更省gas,但也更复杂。
文章来源:https://www.uudwc.com/A/6znZm/
如果这份博客对大家有帮助,希望各位给作者一个免费的点赞?作为鼓励,并评论收藏一下⭐,谢谢大家!!!
制作不易,如果大家有什么疑问或给作者的意见,欢迎评论区留言。文章来源地址https://www.uudwc.com/A/6znZm/