Solidity极简入门: 23. Delegatecall

我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

欢迎关注我的推特:@0xAA_Science

WTF技术社群discord,内有加微信群方法:链接

所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity


delegatecall

delegatecallcall类似,是solidity中地址类型的低级成员函数。delegate中是委托/代表的意思,那么delegatecall委托了什么?

当用户A通过合约Bcall合约C的时候,执行的是合约C的函数,语境(Context,可以理解为包含变量和状态的环境)也是合约C的:msg.senderB的地址,并且如果函数改变一些状态变量,产生的效果会作用于合约C的变量上。

call的语境
call的语境

而当用户A通过合约Bdelegatecall合约C的时候,执行的是合约C的函数,但是语境仍是合约B的:msg.senderA的地址,并且如果函数改变一些状态变量,产生的效果会作用于合约B的变量上。

delegatecall的语境
delegatecall的语境

大家可以这样理解:一个富商把它的资产(状态变量)都交给一个VC代理(目标合约的函数)来打理。执行的是VC的函数,但是改变的是富商的状态。

delegatecall语法和call类似,也是:

目标合约地址.delegatecall(二进制编码);

其中二进制编码利用结构化编码函数abi.encodeWithSignature获得:

abi.encodeWithSignature("函数签名", 逗号分隔的具体参数)

函数签名"函数名(逗号分隔的参数类型)"。例如abi.encodeWithSignature("f(uint256,address)", _x, _addr)

call不一样,delegatecall在调用合约时可以指定交易发送的gas,但不能指定发送的ETH数额

注意delegatecall有安全隐患,使用时要保证当前合约和目标合约的状态变量存储结构相同,并且目标合约安全,不然会造成资产损失。

什么情况下会用到delegatecall?

目前delegatecall主要有两个应用场景:

  1. 代理合约(Proxy Contract):将智能合约的存储合约和逻辑合约分开:代理合约(Proxy Contract)存储所有相关的变量,并且保存逻辑合约的地址;所有函数存在逻辑合约(Logic Contract)里,通过delegatecall执行。当升级时,只需要将代理合约指向新的逻辑合约即可。
  2. EIP-2535 Diamonds(钻石):钻石是一个支持构建可在生产中扩展的模块化智能合约系统的标准。钻石是具有多个实施合同的代理合同。 更多信息请查看:钻石标准简介

delegatecall例子

调用结构:你(A)通过合约B调用目标合约C

被调用的合约C

我们先写一个简单的目标合约C:有两个public变量:numsender,分别是uint256address类型;有一个函数,可以将num设定为传入的_num,并且将sender设为msg.sender

// 被调用的合约C
contract C {
    uint public num;
    address public sender;

    function setVars(uint _num) public payable {
        num = _num;
        sender = msg.sender;
    }
}

发起调用的合约B

首先,合约B必须和目标合约C的变量存储布局必须相同,两个变量,并且顺序为numsender

contract B {
    uint public num;
    address public sender;

接下来,我们分别用calldelegatecall来调用合约CsetVars函数,更好的理解它们的区别。

callSetVars函数通过call来调用setVars。它有两个参数_addr_num,分别对应合约C的地址和setVars的参数。

    // 通过call来调用C的setVars()函数,将改变合约C里的状态变量
    function callSetVars(address _addr, uint _num) external payable{
        // call setVars()
        (bool success, bytes memory data) = _addr.call(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }

delegatecallSetVars函数通过delegatecall来调用setVars。与上面的callSetVars函数相同,有两个参数_addr_num,分别对应合约C的地址和setVars的参数。

    // 通过delegatecall来调用C的setVars()函数,将改变合约B里的状态变量
    function delegatecallSetVars(address _addr, uint _num) external payable{
        // delegatecall setVars()
        (bool success, bytes memory data) = _addr.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }
}

运行结果

我们把合约BC都部署好,然后调用合约C中的callSetVars,传入参数为合约B地址和10。运行后,合约C中的状态变量将被修改:num被改为10sender变为合约B的地址。

接下来,我们调用合约C中的delegatecallSetVars,传入参数为合约B地址和100。由于是delegatecall,语境为合约B。在运行后,合约B中的状态变量将被修改:num被改为100sender变为合约你的钱包地址。

总结

这一讲我们介绍了solidity中的另一个低级函数delegatecall。与call类似,它可以用来调用其他合约;不同点在于运行的语境,B call C,语境为C;而B delegatecall C,语境为B。目前delegatecall最大的应用是代理合约和EIP-2535 Diamonds(钻石)。

Subscribe to 0xAA
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.