全面掌握Solidity智能合约开发

2024年09月25日更新 796 人订阅
原价: ¥ 46 限时优惠
专栏简介 跟我学 Solidity :开发环境 跟我学 Solidity:关于变量 跟我学 Solidity : 变量的存储 跟我学 Solidity :引用变量 跟我学 Solidity :函数 跟我学 Solidity :合约的创建和继承 跟我学 Solidity :工厂模式 用Web3.js构建第一个Dapp 跟我学Solidity:事件 Solidity 中 immutable (不可变量)与constant(常量) [译] Solidity 0.6.x更新:继承 解析 Solidity 0.6 新引入的 try/catch 特性 探究新的 Solidity 0.8 版本 探索以太坊合约委托调用(DelegateCall) 停止使用Solidity的transfer() 使用工厂提高智能合约安全性 Solidity 怎样写出最节省Gas的智能合约[译] Solidity 优化 - 编写 O(1) 复杂度的可迭代映射 Solidity 优化 - 控制 gas 成本 Solidity 优化 - 减少智能合约的 gas 消耗的8种方法 Solidity 优化 - 如何维护排序列表 Solidity 优化:打包变量优化 gas 使用 Solidity 瞬态存储操作码 在 Solidity中使用值数组以降低 gas 消耗 Gas 优化:Solidity 中的使用动态值数组 计算Solidity 函数的Gas 消耗 Solidity 技巧:如何减少字节码大小及节省 gas 一些简单的 Gas 优化基础 "Stack Too Deep(堆栈太深)" 解决方案 智能合约Gas 优化的几个技术 合约实践:避免区块Gas限制导致问题 如何缩减合约以规避合约大小限制 Solidity 类特性 无需gas代币和ERC20-Permit还任重而道远 智能合约实现白名单的3个机制 Solidity智能合约安全:防止重入攻击的4种方法 Solidity 十大常见安全问题 [译]更好Solidity合约调试工具: console.log 智能合约开发的最佳实践 - 强烈推荐 全面理解智能合约升级 Solidity可升级代理模式: 透明代理与UUPS代理 使用OpenZeppelin编写可升级的智能合约 实战:调整NFT智能合约,减少70%的铸币Gas成本 Solidity 优化 - 隐藏的 Gas 成本 Gas 技巧:Solidity 中利用位图大幅节省Gas费 Solidity Gas 优化 - 理解不同变量 Gas 差异 关于Solidity 事件,我希望早一点了解到这些 Solidity 编码规范推荐标准 深入了解 Solidity bytes OpenZeppelin Contracts 5.0 版本发布 Solidity Gas优化:高效的智能合约策略 智能合约安全的新最低测试标准:Fuzz / Invariant Test 智能合约的白名单技术 模糊测试利器 - Echidna 简介 智能合约设计模式:代理 离线授权 NFT EIP-4494:ERC721 -Permit

Solidity可升级代理模式: 透明代理与UUPS代理

Solidity有两种模式实现升级:透明代理与UUPS代理, 他们的实现由细微差别,本文一起来看一看。

在你进一步行动之前,我假设你已经有一些Solidity的经验,并且知道存储槽在合约中是如何工作的。

为什么要用代理?

如果你有关注区块链和智能合约,你一定遇到过这个词--不可变性(immutable)。嗯,智能合约是不可变的。是的,你不能在该地址调整合约的任何功能。你只能与它交互。就是这样,而且是为了它的更好的发展! 否则,如果说有一天某个控制者突然对合约中的规则做出了有利于他们的规定,那么它就不会那么 值得信赖了。这与传统系统有着鲜明的区别,在传统的系统中,每天都会被修复措施推动着。

代理可以做什么?

鼓声响起来......代理 - 横空出世!代理已经有很多人研究,让代理模式成为目前升级智能合约的事实(标准)。

但是,等等,你刚才不是说合约是不可变的,不能被改变的吗!?

当然了! 当然!而且这个不可更改的部分仍然是正确的。但是代理可以解决这个问题。 尽管区块链的不可变性有很多好处,但在多个版本中推送bug修复和补丁是不能忽视的,而且非常需要修补bug和安全漏洞。代理模式解决了这个问题。让我们来看看代理如何工作的。

代理如何工作

这里的基本想法是有一个代理合约。在继续前行之前,先说一下背景术语:

代理合约 - 一个作为代理的合约,将所有调用委托给它所代理的合约。在这里,它也会被称为存储层

实现合约 - 你想升级或修补的合约。这是代理合约被代理的合约。在这种情况下,它也是逻辑层

代理合约将执行或逻辑层的合约地址存储为一个状态变量。与普通合约不同的是,用户实际上并不直接向逻辑层发送调用--那是我们的原始合约。相反,所有的调用都要经过代理,这个代理将调用委托给这个逻辑层(它是在代理地址上存储的具体实现合约),并把从逻辑层收到的任何数据返回给调用者,或者对错误进行回退。

delegatecall
User ---------->  Proxy  -----------> Implementation
             (storage layer)          (logic layer)

这里需要注意的关键是,代理通过delegatecall函数调用逻辑合约。因此,实际上是由代理合约来存储状态变量,即它是存储层。这就像你只是从执行合约(逻辑层!)借用逻辑,并在代理的上下文中执行,并影响代理合约在存储中的状态变量。

举个例子,考虑一个简单的Box(实现)合约,以及BoxProxy(代理)合约。

contract Box {
    uint256 private _value;

    function store(uint256 value) public {
        _value = value;
    }

    function retrieve() public view returns (uint256) {
        return _value;
    }
}

contract BoxProxy {

     function _delegate(address implementation) internal virtual {
         // delegating logic call to boxImpl...
     }

     function getImplementationAddress() public view returns (address) {
         // Returns the address of the implementation contract
     }

     fallback() external {
         _delegate(getImplementationAddress());
     }
}

尽管Box定义了一个uint256状态变量_value,但实际上是BoxProxy合约存储了与_value相关的值(在槽0中,根据存储布局规则 - 中文文档)。

委托代码通常被放在代理的fallback函数中。

因此升级机制可以理解为:授权改变代理合约存储变量的实现合约地址,以指向整个新部署的、升级的实现合约。这样,升级就完成了。代理现在将调用委托给这个新合约。虽然那个旧合约会永远存在。

upgrade call
Admin -----------> Proxy --x--> Implementation_v1
                     |
                      --------> Implementation_v2

很简单吧?但是有一些问题,比如代理和实现合约之间潜在的、由delegatecall引起存储碰撞,。

代理合约和实现合约之间的存储碰撞

我们不能简单地在代理合约中声明 address implementation,因为这会引起与实现合约的存储发生冲突,即与实现合约中的多个变量在存储槽中有重叠。

|Proxy                   |Implementation |
|------------------------|---------------|
|address implementation  |address var1   | <- 碰撞!
|                        |mapping var2   |
|                        |uint256 var3   |
|                        |...            |

对实现中的var1的任何写入,实际上都会写入Proxy中的implementation(存储层!)。

解决方案是选择一个伪随机槽,并将 `impleme...

剩余50%的内容订阅专栏后可查看

点赞 11
收藏 14
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论