新闻列表/

技术沙龙 | 可升级智能合约

溪塔科技溪塔科技2022-08-16
Img106001


可升级智能合约

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


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


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

Img106002


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

Img106003


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


我们接着介绍。


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

Img106004


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

Img106005


如果使用 erc20 这种合约呢?


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

Img106006


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

比如这个:

Img106007


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

Img106008


还有一种是常量:

Img106009


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


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


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


比如这种:

Img106010


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


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

Img106011


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


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


比如原来是这样:

Img106012


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

Img106013


也不能换顺序。

Img106014


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

Img106015


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

Img106016


就是加在最后。


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


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

Img106017

   

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


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

Img106018


这个是 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 里存的位置的。


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

Img106019


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


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

Img106020


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


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

Img106021


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


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

Img106022


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


Img106023


往期回顾

Img106024
Img106025
Img106026
Img106027
Img106028


杭州溪塔科技有限公司 | (Rivtower Techonology Co.,Ltd.)是以开源为内核领先的区块链底层技术公司。公司自主研发设计了包括开源联盟链底层 CITA 、云原生区块链开发框架 CITA-Cloud、企业级区块链管理平台 RivSpace 、 数据要素确权流转平台 RivTrust 等区块链底层产品生态,并在金融,政务,能源,工业互联网领域广泛应用,是国家级新型数字基础设施星火•链网核心合作伙伴,在共识算法、智能合约、隐私保护等领域申请 200 余项发明专利。


Img106029
扫码联系溪塔小助手
溪塔科技
Copyright © 2019-2023 Rivtower
杭州溪塔科技有限公司
Tel:19357216397
核心产品
RivSpace - 企业级WEB3基础设施管理平台
RivDAP - 数字化商品管理平台
MagiPock - Web3.0时代最具价值的文旅互动分享平台
联系我们
杭州总部
北京子公司
上海子公司
友情链接
星火链网
数文链
DIDA
企业公众号
企业公众号
市场部小助手
市场部小助手