Solidity 学习 -练气期(十一)

call 函数 - 调用合约

call 时address 类型的底层函数,用来与其他合约交互。call 函数返回两个变量:第一个布尔值,代表调用结果成功与否;第二个是目标函数返回的数据。

使用场景:

  1. call 是 Solidity官方推荐的通过触发fallback 或 receive 回调函数发送ETH的方法
  2. 虽然call 可以调用另一个合约中的函数,但是不是最佳的方法。一旦用户调用了不安全的合约函数,相当于把主动权交给了对方。合理的做法,声明合约变量调用函数。
  3. 如果用户不知道对方的合约源代码或者ABI,就无法生成合约变量。但用户仍然可以通过call调用对方合约函数。
    语法:
    _Address.call(binaryCode);
    _Address 是目标合约的地址,binaryCode是发送给目标合约的字节码数据。
    数据通过abi.encodeWithSignature来获取,包含要调用的函数的具体信息。
    abi.encodeWithSignature(signatureString, parameter1,parameter2,...);
    signature 为函数的签名字符串,后面是一些参数用逗号分开
    abi.encodeWithSignature("f(uint256,address)", _x, _addr);
    另外,call函数在调用合约时,可以置顶交易发送ETH数额和gas:
    _Address.call{value: _Value, gas: _Gas}(binaryCode);

准备目标函数,OtherContract,和之前讲的合约一样,加入了fallback 虽然有警告,为了测试暂时不处理 :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract OtherContract {
    //状态变量
    uint256 private _x = 0;

    //收到ETH事件记录amount和gas
    event Log(uint256 amount, uint gas);
    
    fallback() external payable { }

    function getBalance() view public returns(uint) {
        return address(this).balance;
    }

    function setX(uint256 x) external payable {
        _x = x;
        if(msg.value > 0) {
            emit Log(msg.value, gasleft());
        }
    }

    // 读取_x
    function getX() external view  returns(uint x) {
        x = _x;
    }
}

用call函数调用目标合约

  1. Response 事件

写一个Call 合约 来调用目标合约的函数。首先定义一个Response事件,输出call返回的变量success和data。

  1. 调用setX函数
contract UseCall {
    //定义Response事件,输出call返回的结果success和data
    event Response(bool success, bytes data);

    function callSetX(address payable _addr, uint256 x) public payable {
        (bool success,bytes memory data) = _addr.call{value: msg.value}(
            abi.encodeWithSignature("setX(uint256)", x)
        );
        emit Response(success, data);
    }
}

测试:我们首先设置转入金额10wei,然后讲OtherConstract合约的地址和整数5作为参数传递给cassSetX。成功调用后回到OtherConstract查看_x的值是5,同时余额为10wei


image.png
  1. 调用getX函数
    定义callGetX()函数,调用目标合约的getX函数返回状态变量_x的值,类型uint256.利用abi.decode来解析call的返回值data读取数值。
    function callGetX(address _addr) external returns(uint256) {
        (bool success,bytes memory data) = _addr.call(abi.encodeWithSignature("getX()"));
        emit Response(success, data);
        return abi.decode(data, (uint256));
    }
  1. 调用不存在函数
    调用一个不存在于目标合约函数,目标合约的fallback回调会被触发
   function callNotExist(address _addr) external {
        (bool success,bytes memory data) = _addr.call(abi.encodeWithSignature("foo(uint256)"));
        emit Response(success, data);
    }

测试结果,success 返回true,实际调用目标函数fallback,返回data为空数据0x

delegatecall 函数

delegatecall和call函数类似,是地质类型的底层成员函数。委托了什么?

image.png

当用户A通过合约B,使用delegatecall函数调用合约C的时候,执行的是合约C的函数,但上下文仍然是B的。
msg.sender 的地址是A的地址,并且如果函数改变一些状态变量,产生的效果会作用于B的变量上。

打个比方:一个投资者(A用户)把它的资产(合约B的状态变量)交给一个风险投资代理(合约C)来大理。执行的是风险投资代理的函数,但是改变的是资产的状态。

image.png

delegatecall 语法:
_Address.delegatecall(binaryCode);
binaryCode为二进制编码。
数据通过abi.encodeWithSignature来获取,包含要调用的函数的具体信息。
abi.encodeWithSignature(signatureString, parameter1,parameter2,...);
signature 为函数的签名字符串,后面是一些参数用逗号分开
abi.encodeWithSignature("f(uint256,address)", _x, _addr);
和call函数不同的是,delegatecall 在调用合约时可以指定交易发送的gas,但不能指定发送ETH

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

delegatecall 的应用场景

(1)代理合约(proxy contract):用于将智能合约的存储合约和逻辑合约分开。代理合约存储所有相关的变量,并保存逻辑合约的地址,而所有函数在逻辑合约(logic contract)里,通过delegatecall执行。当升级时,只需要讲代理合约中的地址指向新的逻辑合约即可。
(2)EIP-2535 Diamonds(钻石):钻石是一个支持构建可在生产中扩展的模块化智能合约系统的标准。钻石是具有多个实施合同的代理合同。
它的核心思想是:将一个庞大复杂的合约拆分成多个“功能 facet(面)”,但对外仍表现为单一地址的合约,从而突破 Solidity 单合约 24KB 字节码大小限制,并实现灵活升级。

delegatecall 用法

  1. 被调用的目标合约 DelegateC
    一个函数,可以通过传入_num 的值,并将sender设置为msg.sender
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract DelegateC {
    uint public num;
    address public sender;
    function setVars(uint _num) public payable {
        num = _num;
        sender =msg.sender;
    } 
}
  1. 发起调用的合约DelegateB
    B和C合约变量存储布局必须相同 并且顺序num,sender。实现两个函数call和delegatecall来调用setVars,更好理解他们的区别。

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

delegatecallSetVars 函数通过delegatecall 来调用setVars,他有两个参数_addr 和_num分别对应合约C的地址和setVars的参数

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract DelegateB {
    uint public num;
    address public sender;

    function callSetVars(address _addr, uint _num) external payable {
        (bool success, bytes memory data) = _addr.call(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }

    function delegatecallSetVars(address _addr, uint _num) external payable {
        (bool success, bytes memory data) = _addr.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }
}

测试:
(1)把合约B 和 合约 C 部署好,注意记录合约B和合约C的地址,以及自己用户A的地址。
(2)检查合约C的状态变量初始值,合约B状态变量一样
(3)调用合约B中的callSetVars函数传入参数为合约C的地址和整数10
(4)调用后,检查合约B和合约C的变量,其中合约C中的变量num被改为10,sender被改为B的地址,而合约B的状态不变
(5)调用合约B中的delegatecallSetVars函数,传入参数为合约C的地址和整数100.
(6)调用后,检查合约B和合约C的变量,由于delegatecall,是下文合约B,于是合约B中的状态变量num被改为100,sender变为用户A的钱包地址,而合约C不会被修改

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容