在介绍DelegateCall
时,我们需要带上Call
方法一起介绍,并做对比。
先说概念吧!
DelegateCall:有一种特殊类型的消息调用,被称为 委托调用(delegatecall)
。它和一般的消息调用(call
)的区别在于,目标地址的代码将在发起调用的合约的上下文中执行,并且 msg.sender
和 msg.value
不变。 这意味着一个合约可以在运行时从另外一个地址动态加载代码。
我不喜欢一上来就讲概念,毕竟太难理解。还是上代码演示吧
Remix IDE:Remix是基于浏览器的 IDE,集成了编译器和 Solidity 运行时环境,不需要服务端组件,支持网页在线编写、部署和测试智能合约。
本章主要是让大家快速了解DelegateCall的特性,所以选择基于Remix来演示。
我们在contracts
目录下新建delegatecall.sol
文件,并接下来把下面演示代码粘贴进delegatecall.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract A {
address public msgsender;
function callFunc() public {
msgsender = msg.sender;
}
}
contract B {
address public msgsender;
address public a;
constructor(address _a) {
a = _a;
}
function call_a_call() public{
// isOk 用来接收调用是否成功
(bool isOk,bytes memory result) = a.call(abi.encodeWithSignature("callFunc()"));
// 如果失败,报异常
require(isOk,"call faild");
}
function delegatecall_a_delegatecall() public{
(bool isOk,bytes memory result) = a.delegatecall(abi.encodeWithSignature("callFunc()"));
require(isOk,"call faild");
}
}
我们在这里定义两个合约A
和B
A
合约有一个msgsender
状态变量和callFunc()
方法
B
合约有两个状态变量msgsender
和a
,msgsender
是为了验证我们的实验结果,a
是存放合约A
的实例引用,并且定义了两个方法,分别用来演示用B
合约通过call
和delegatecall
两种方式调用A
合约的callFunc()
然后我们按Ctrl + s
快捷键,这里会保存合约代码,编译器会自动帮我们编译
点击下面的按钮跳转到部署与调试
页面
先部署合约A
,再部署合约B
,因为B
中的状态变量a
引用了A
的地址
A
B
B
时需要传入A
的地址我们先点击>
按钮展开我们的合约,并点击A
合约和B
合约的msgsender
按钮查询(点击按钮会自动调用msgsender
的查询方法)当前状态变量的值,这里我们可以看到都为0
接下来我们点击B
合约delegatecall_a_delegatecall
按钮(调用delegatecall_a_delegatecall
方法),然后再次点击A
合约和B
合约的msgsender
按钮查询
奇怪的事情发生了!!!
A
合约的msgsender
没有值,但是B
合约的msgsender
变成了我自己的地址0x5B38Da6a....
好了,我们可以结合上面的实验结果,再来理解文章开头的所说的概念
目标地址A
合约的代码将在发起调用的B
合约的上下文
中执行,并且 msg.sender
和 msg.value
不变。上下文
就是运行环境,就包括了合约里的状态变量
,所以当合约执行callFunc()
的内容时,callFunc()
方法是在B
合约里执行的,修改的是B
合约的状态变量,而A
合约的msgsender
却没有变化。
如果B
合约的两个状态变量msgsender
和a
在代码中互换位置,就是另一个故事了,这里涉及到另一个概念《合约数据存储布局》
接下来我们点击B
合约call_a_call
按钮,然后再次点击A
合约和B
合约的msgsender
按钮查询
现在A
合约的msgsender
有值了,B
合约的msgsender
值没有变化。
当调用B
合约call_a_call
方法时,B
合约的状态变量没有发生变化,A
合约的msgsender
有值了,这说明callFunc()
方法是在A
合约的上下文
环境中执行的,这里上下文
发生了变化。
并且我们发现A
合约的msgsender
变成了B
合约的地址,这说明在调用的过程中msg.sender
和 msg.value
发生了变化,msg.sender
不再是我自己0x5B38Da6a....
而是B
合约的地址
会顾我们前面写的可升级合约,当对代理合约
发起调用时,代理合约
与逻辑合约
交互就是用的委托调用(DelegateCall)
。逻辑合约
的方法在代理合约
的上下文执行,并且修改的状态变量
也是代理合约
的。所以合约数据一直都在代理合约
里,当逻辑合约
升级时并不会影响合约原来已有的数据
有问题,或者建议请留言,谢谢。