# WTF Solidity极简入门: 49. 通用可升级代理 **Published by:** [0xAA](https://paragraph.com/@wtfacademy/) **Published on:** 2022-09-25 **URL:** https://paragraph.com/@wtfacademy/wtf-solidity-49 ## Content 我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍代理合约中选择器冲突(Selector Clash)的另一个解决办法:通用可升级代理(UUPS,universal upgradeable proxy standard)。教学代码由OpenZepplin的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)[https://abi.hashex.org/]获得二进制编码,调用升级函数upgrade(),将implementation地址指向新逻辑合约UUPS2。5. 利用选择器0xc2985578,在代理合约中调用新逻辑合约UUPS2的foo()函数,将words的值改为"new"。总结这一讲,我们介绍了代理合约“选择器冲突”的另一个解决方案:UUPS。与透明代理不同,UUPS将升级函数放在了逻辑合约中,从而使得"选择器冲突"不能通过编译。相比透明代理,UUPS更复杂,但是也更省gas。 ## Publication Information - [0xAA](https://paragraph.com/@wtfacademy/): Publication homepage - [All Posts](https://paragraph.com/@wtfacademy/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@wtfacademy): Subscribe to updates - [Twitter](https://twitter.com/0xAA_Science): Follow on Twitter