# 深入理解合约升级(3) - call 与 delegatecall **Published by:** [xyyme.eth](https://paragraph.com/@xyyme/) **Published on:** 2022-05-18 **URL:** https://paragraph.com/@xyyme/3-call-delegatecall ## Content call 与 delegatecall 的区别call 和 delegatecall 是 Solidity 中调用外部合约的方法,但是它俩却有挺大的区别。假设 A 合约调用 B 合约,当在 A 合约中使用 call 调用 B 合约时,使用的是 B 合约的上下文,修改的是 B 合约的内存插槽值。而在如果在 A 合约中使用 delegatecall 调用 B 合约,那么在 B 合约的函数执行过程中,使用的是 A 合约的上下文,同时修改的也是 A 合约的内存插槽值。这么说有些抽象,我们来看一个简单的示意图:通过 call 调用通过 delegatecall 调用从上面的图中我们可以看出,在使用 call 调用时,B 合约使用的上下文数据均是 B 本身的。而当使用 delegatecall 调用时,B 合约使用了 A 合约中的上下文数据。我们来写段代码测试一下:pragma solidity 0.8.13; contract A { address public b; constructor(address _b) { b = _b; } function foo() external { (bool success, bytes memory data) = b.call(abi.encodeWithSignature("foo()")); require(success, "Tx failed"); } } contract B { event Log(address sender, address me); function foo() external { emit Log(msg.sender, address(this)); } } 上面代码中,我们在 A 合约中使用 call 调用 B 合约,通过 Log 事件记录一些信息。先部署 B 合约,然后将其地址作为参数部署 A 合约,接着我们调用 foo 函数,可以获取到 Log 事件的内容为:通过 call 调用与我们前面的说的规则一致,使用 call 调用时,使用的是 B 本身的上下文。接下来我们将 call 改成 delegatecall:function foo() external { (bool success, bytes memory data) = b.delegatecall(abi.encodeWithSignature("foo()")); require(success, "Tx failed"); } 再来看看执行结果:通过 delegatecall 调用可以看到当使用了 delegatecall 调用时,使用了 A 合约的上下文。 上面我们还提到,当使用 delegatecall 时,修改的是调用合约的内存插槽值,这是什么意思呢,我们来看一个例子:pragma solidity 0.8.13; contract A { uint256 public alice; uint256 public bob; address public b; constructor(address _b) { b = _b; } function foo(uint256 _alice, uint256 _bob) external { (bool success, bytes memory data) = b.delegatecall(abi.encodeWithSignature("foo(uint256,uint256)", _alice, _bob)); require(success, "Tx failed"); } } contract B { uint256 public alice; uint256 public bob; function foo(uint256 _alice, uint256 _bob) external { alice = _alice; bob = _bob; } } 这段代码中,我们使用 delegatecall 来调用 foo 函数,foo 函数的作用是给 B 合约的两个变量赋值。但是实际调用后的结果是,A 合约的两个变量被赋值,而 B 中的变量仍为空。这就是我们前面说的,delegatecall 会修改调用合约的内存插槽值,我们来看一个图示:内存插槽在 A 合约中有三个状态变量,B 合约中有两个状态变量。当 A 合约使用 delegatecall 调用 B 合约时,对 B 合约状态变量的赋值会通过插槽顺序分别影响 A 合约的各个变量。也就是说,对 B 合约插槽 0 的变量 alice 赋值,实际上是把值赋给了 A 合约插槽 0 的变量 alice。同理,对 B 合约的第 n 个插槽赋值,实际上会对 A 合约的第 n 个插槽赋值。注意,这里仅仅和插槽顺序有关,而和变量名无关。如果我们将 B 合约改为:contract B { // 调换了变量声明顺序 uint256 public bob; uint256 public alice; function foo(uint256 _alice, uint256 _bob) external { // 调换了赋值内容 bob = _alice; alice = _bob; } } 这段代码中,虽然变量声明以及赋值的顺序调换,但是 foo 的内容仍然是将 _alice 赋值给插槽 0 的变量,将 _bob 赋值给插槽 1 的变量,因此 A 合约的结果不变。delegatecall 在合约升级方面的应用学习理解 delegatecall 是我们后面学习合约升级的基础,合约升级的原理就是代理合约通过 delegatecall 调用逻辑合约,此时逻辑合约的上下文以及数据都是来自于代理合约,那么即使升级,更换了逻辑合约,所有的数据仍然存在于代理合约中,没有影响。可升级合约还有一个限制是,在升级合约时,不能更改已有的状态变量的顺序,如果需要新添变量,只能放在当前所有变量之后,不能在其中插入,原因就是这会改变插槽对应关系,使变量内容混乱。例如,若升级前的插槽为:此时,变量 a 和 b 的值分别存储在代理合约的插槽 0,1 中。若添加变量 c,将其放在 a 和 b 中间,那么后续对于 c 的修改实际修改的是 b 的插槽,而对于 b 的修改则是在一个新的插槽上操作,造成数据混乱。总结delegatecall 会在被调用合约中使用调用合约的上下文,同时影响的是调用合约的内存插槽,这有时会对合约开发带来一些困扰。在使用时,一定要多考虑各方面的影响。同时,delegatecall 也是代理合约升级模式的基石,要理解合约升级,必须要明白这种调用方式的方方面面。合约升级系列文章深入理解合约升级(1) - 概括深入理解合约升级(2) - Solidity 内存布局深入理解合约升级(3) - call 与 delegatecall深入理解合约升级(4) - 合约升级原理的代码实现深入理解合约升级(5) - 部署一个可升级合约关于我欢迎和我交流参考DelegateCall() - Blockchain AcademyThis topic focuses on the functionality of delegate calls in Solidity.https://blockchain-academy.hs-mittweida.deSolidity中的delegatecall杂谈-安全KER - 安全资讯平台安全KER - 安全资讯平台https://www.anquanke.comDelegatecall and Upgradeable ContractUUPS ( EIP-1822) 和 Transparent Proxy Pattern 基本相似,区别只在于: 对于 UUPS 来说,升级相关代码位于 Implementation 合约(或称 Logic 合约)中,而对于 Transparent Proxy Pattern 来说,升级相关代码位于 Proxy 合约中。 下面是 UUPS 的例子(摘自 https://eips.ethereum.org/EIPS/eip-1822#erc-20-token )。其中 Proxy 合约为: pragma solidity ^0.5.1; contract Proxy { // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" constructor(bytes memory constructData, address contractLogic) public {http://aandds.com ## Publication Information - [xyyme.eth](https://paragraph.com/@xyyme/): Publication homepage - [All Posts](https://paragraph.com/@xyyme/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xyyme): Subscribe to updates - [Twitter](https://twitter.com/xyymeeth): Follow on Twitter