代理合约
原文章:https://www.wtf.academy/solidity-application/ProxyContract/
代理模式
Solidity
合约部署在链上之后,代码时不可变的(immutable)。这既有优点,也有缺点。
- 优点:安全,用户知道会发生什么(大部分时候)
- 坏处:就算合约中存在 bug,也不能修改或升级,只能部署新合约。但是新合约的地址与旧的不一样
可以通过代理模式在合约部署后进行修改或升级。
代理模式将合约数据和逻辑分开,分别保存在不同合约中。
我们拿上图中简单的代理合约为例:数据(状态变量)存储在代理合约中,而逻辑(函数)保存在另一个逻辑合约中。代理合约(Proxy)通过delegatecall
,将函数调用全权委托给逻辑合约(Implementation)执行,再把最终的结果返回给调用者(Caller)。
代理模式主要有两个好处:
- 可升级:当我们需要升级合约的逻辑时,只需要将代理合约指向新的逻辑合约。
- 省 gas:如果多个合约复用一套逻辑,我们只需要部署一个逻辑合约,然后再部署多个只保存数据的代理合约,指向逻辑合约
代理合约
下面介绍一个简单的代理合约,它由 OpenZeppelin 的 Proxy合约简化而来。他有三个部分:**代理合约Proxy
,逻辑合约Logic
**,和一个调用示例Caller
。他的逻辑并不复杂:
- 首先部署逻辑合约
Logic
- 创建代理合约
Proxy
,状态变量implementation
记录Logic
合约地址。 Proxy
合约利用回调函数fallback
,将所有调用委托给Logic
合约- 最后部署调用示例
Caller
合约,调用Proxy
合约
注意:logic
合约和Proxy
合约的状态变量存储结构相同,不然delegatecall
会产生意想不到的行为,有安全隐患。
代理合约Proxy
Proxy
合约不长,但是用到了内联汇编。它只有一个状态变量,一个构造函数,和一个回调函数。状态变量implementation
,在构造函数中初始化,用于保存Logic
合约地址。
1 | contract Proxy { |
Proxy
的回调函数将外部对本合约的调用委托给Logic
合约。这个回调函数很别致,它利用内联汇编(inline assembly),让本来不能有返回值的回调函数有了返回值。其中用到的内联汇编操作码:
calldatacopy(t, f, s)
:将 calldata (输入数据)从位置f
开始复制s
字节到 mem(内存)的位置t
。delegatecall(g, a, in, insize, out, outsize)
:调用地址a
的合约,输入mem[in..(in+insize)]
,输出为mem[out..(out+outsize))
,提供g
wei的以太坊 gas。这个操作码咋子错误时返回0
,在成功时返回1
returndatacopy(t, f, s)
:将 returndata(输出数据)从位置f
开始复制s
字节到 mem (内存)的位置t
。switch(p, s)
:基础版if/else
,不同的情况case
返回不同值。可以有一个默认的default
情况。return(p, s)
:终止函数执行,返回数据mem[p..p+s))
。revert(p, s)
:终止函数执行,回滚状态,返回数据mem[p..(p+s))
。
1 | /** |
逻辑合约Logic
这是一个非常简单的逻辑合约,只是为了演示代理合约。它包含 2 个变量,1 个事件, 1 个函数:
implementation
:占位变量,与Proxy
合约保持一致,防止插槽冲突x
:uint
变量,被设置为99
。CallSuccess
事件:在调用成功时释放。increment()
函数:会被Proxy
合约调用,释放CallSuccess
事件,并返回一个uint
,它的selector
为0xd09de08a
。如果直接调用increment()
会返回100
,但是通过Proxy
调用他会返回1
。
1 | /** |
调用者合约Caller
Caller
合约会演示如何调用一个代理合约,它也非常简单。但是要理解它,你需要先学习本教程的第22讲 Call和第27讲 ABI编码。
它有1
个变量,2
个函数:
proxy
:状态变量,记录代理合约地址。- 构造函数:在部署合约时初始化
proxy
变量。 increase()
:利用call
来调用代理合约的increment()
函数,并返回一个uint
。在调用时,我们利用abi.encodeWithSignature()
获取了increment()
函数的selector
。在返回时,利用abi.decode()
将返回值解码为uint
类型。
1 | /** |