OpenZeppelin可升级智能合约
  jQa8FW1vlwhZ 2023年11月02日 51 0

可升级智能合

今天分享下 openzeppelin 的 Upgradeable Smart Contract,就是可升级智能合约。

我们先看下如何用这个提供的插件来写智能合约,然后再来介绍下里面的原理和实现逻辑。

首先我们来看一个正常的合约,一个合约里有初始化构造方法,我们把这个构造方法替换成 initialize 方法。我们先不考虑为什么这么做,先看下使用,后面再去看原理。

OpenZeppelin可升级智能合约_智能合约

比如这个 MYcontract 合约,把它的构造方法换成 initialize,因为构造方法其实只能被调用一次,为了防止出现被错误调用,所以这里加了一些条件限制。

OpenZeppelin可升级智能合约_智能合约_02

很早以前某篇文章里介绍智能这种编程方式也叫,面向条件的编程。无论是普通合约逻辑的编写,还是在安全方面做的一些保护,比如防重入攻击,都有这个条件编程的思想。

我们接着介绍。

其实每次这样去写这个 initialize 的方法很麻烦。

OpenZeppelin可升级智能合约_智能合约_03

所以 openz 帮我们实现了一些逻辑,我们只要继承就好,平时写合约的时候,实际上还有一些合约继承的东西,这种情况就要稍微处理下:

OpenZeppelin可升级智能合约_智能合约_04

如果使用 erc20 这种合约呢?

其实 openz 也提供了标准可升级的 erc20 的实现,把原来继承 erc20,直接替换掉就好了。

OpenZeppelin可升级智能合约_智能合约_05

还要注意一点,就是在变量声明的时候,不要做初始化,Avoiding Initial Values in Field Declarations。

比如这个:

OpenZeppelin可升级智能合约_智能合约_06

这种相当于,我们写了一个构造方法,然后在构造方法里初始化这个 storage。需要把它改成下面这样的。

OpenZeppelin可升级智能合约_智能合约_07

还有一种是常量:

OpenZeppelin可升级智能合约_智能合约_08

这种实际上是编码在合约code里的。

我们在部署合约的时候,evm 实际上在完成构造方法后,构造方法这段代码实际上就没有保存在合约地址下的 code 的,因为反正只是在部署的时候用。

但是初始化的常量实际上会跟随这个合约 code,保存在合约地址下的代码里。这个初始化里构造的变量也是以 storage 的形式存下来的。在初始化合约的时候,可能在初始化方法中会创建一个新的合约实例。

比如这种:

OpenZeppelin可升级智能合约_智能合约_09

这里面的 erc20 合约实际上是不支持升级的,如果想要这个合约支持升级怎么处理呢?

实际就是先部署里面的合约,然后在初始化的时候把这个合约地址再传进去就好了:

OpenZeppelin可升级智能合约_智能合约_10

在升级合约的时候也要注意几点:

第一,不要更改原有的 storage。

比如原来是这样:

OpenZeppelin可升级智能合约_智能合约_11

我们不能把这个变量类型改掉。

OpenZeppelin可升级智能合约_智能合约_12

也不能换顺序。

OpenZeppelin可升级智能合约_智能合约_13

也不能在前面插入一个变量。

OpenZeppelin可升级智能合约_智能合约_14

能做的是,可以给这个变量换个名字,或者在后面追加一个变量,比如这个:

OpenZeppelin可升级智能合约_智能合约_15

就是加在最后。

当然还有其他的错误使用情况,大家可以看它们的官方文档。

这个原理的实现其实很简单,就是用了一个代理合约:

OpenZeppelin可升级智能合约_智能合约_16

用户调用的一直都是这个代理合约,具体的实现合约可以换掉,然后把地址绑定到这个代理合约之内。

我们看下这个代理合约的大致的逻辑思路:

OpenZeppelin可升级智能合约_智能合约_17

这个是 assembly 写的。

第一步就是把 calldata 拷贝出来,就是交易里的 data,第二步骤就是用这个 delegatecall,然后第三就是获取 return data,第四就是再把这个 returndata 返回。

我们主要看这个 delegatecall。

solidity 里有几种 call,大家可以对比下这几个的区别。

这个其实就是把目标合约的 code 在当前合约的环境下执行,使用当前合约的 storage,逻辑合约的代码其实是被执行了,但是逻辑合约的 storage 其实是没有用的,它用的是在代理合约的 storage。

所以合约在初始化的时候,如果用 solidity 的构造方法,storage 就留在逻辑合约里了,所以前面的把构造合约该成 init 就是这个道理,通过代理合约来调用 iinit,这些 storage 就留在这个代理合约里了。

后面就可以持续升级。

刚才讲到在代理合约里保存逻辑合约的 stroage,这里就有一个问题,就是原来代理合约因为也要保存逻辑合约的地址。比如在代理合约里声明了一个 stroage,因为在 solidity 实现的时候,它会把这个 storage 给一个 postion,然后实际上是按照它声明的位置来确定最终在合约下面的抽象模型里的 kv 里存的位置的。

比如我们声明了三个变量:

OpenZeppelin可升级智能合约_智能合约_18

这三个实际上存在哪里是跟他的 position 有关的,就是变量的顺序。

然后这里可能就有一个问题:

OpenZeppelin可升级智能合约_智能合约_19

Proxy 这里的 storage,比如第一个和逻辑合约里的第一个 storage,因为都是第一个,都存到 proxy 合约里,就冲突了。

这时,给它一个随即的 slot 就可以了。

OpenZeppelin可升级智能合约_智能合约_20

原理很简单,实际就是算一个随机的数,然后用 solidity 的 assembly 语言里的一个设置 stroage 的命令,设置这个值就好了,这样就解决了代理合约和实现合约里的 stroage 冲突的问题。

在实现合约里,因为 stroage 无论怎么升级都是公用的代理合约里的,所以这里不能修改原来的 stroage,要不然就冲突了。

OpenZeppelin可升级智能合约_智能合约_21

在使用上,openz 和 truffle 也好,还有和一些其他的合约开发工具都是做了集成,使用起来都还是比较方便的。

文章来源:溪塔科技

文章原标题:《技术沙龙 | 可升级智能合约》

如有侵权请与我们联系删除。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
jQa8FW1vlwhZ