可升级合约的原理-DelegateCall

可升级合约的原理-DelegateCall

在介绍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");
    }
}

我们在这里定义两个合约AB

A合约有一个msgsender状态变量和callFunc()方法

B合约有两个状态变量msgsenderamsgsender是为了验证我们的实验结果,a是存放合约A的实例引用,并且定义了两个方法,分别用来演示用B合约通过calldelegatecall两种方式调用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.sendermsg.value 不变。上下文就是运行环境,就包括了合约里的状态变量,所以当合约执行callFunc()的内容时,callFunc()方法是在B合约里执行的,修改的是B合约的状态变量,而A合约的msgsender却没有变化。

如果B合约的两个状态变量msgsendera在代码中互换位置,就是另一个故事了,这里涉及到另一个概念《合约数据存储布局

接下来我们点击B合约call_a_call按钮,然后再次点击A合约和B合约的msgsender按钮查询
在这里插入图片描述
现在A合约的msgsender有值了,B合约的msgsender值没有变化。

当调用B合约call_a_call方法时,B合约的状态变量没有发生变化,A合约的msgsender有值了,这说明callFunc()方法是在A合约的上下文环境中执行的,这里上下文发生了变化。
并且我们发现A合约的msgsender变成了B合约的地址,这说明在调用的过程中msg.sendermsg.value发生了变化,msg.sender 不再是我自己0x5B38Da6a....而是B合约的地址

总结

会顾我们前面写的可升级合约,当对代理合约发起调用时,代理合约逻辑合约交互就是用的委托调用(DelegateCall)逻辑合约的方法在代理合约的上下文执行,并且修改的状态变量也是代理合约的。所以合约数据一直都在代理合约里,当逻辑合约升级时并不会影响合约原来已有的数据

有问题,或者建议请留言,谢谢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值