Solidity进阶:call、delegatecall

前两节介绍了通过合约的源码和接口两种方法调用合约,现在使用call和delegatecall来调用。

这两个函数都是低级函数,应谨慎使用。 具体来说,任何未知的合约都可能是恶意的,我们在调用一个合约的同时就将控制权交给了它,而合约又可以回调合约,所以要准备好在调用返回时改变相应的状态变量(可参考 可重入 ), 与其他合约交互的常规方法是在合约对象上调用函数(x.f())。

call

call 是address类型的低级成员函数,它用来与其他合约交互。它的返回值为(bool, data),分别对应call是否成功以及目标函数的返回值。

  • call是solidity官方推荐的通过触发fallback或receive函数发送ETH的方法。
  • 当我们不知道对方合约的源代码或ABI,就没法生成合约变量;这时,我们仍可以通过call调用对方合约的函数。

先定义一个合约:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract TargetContract{
    uint256 private _x = 0;
    
    event Log(string message);

    fallback() external payable {
        emit Log("function not exist");
    }

    function setX(uint x) external payable{
        _x = x;
    }

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

call的使用规则:

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

call在调用有payable的合约函数时可以指定交易发送的ETH数额和gas:

目标合约地址.call{value:发送数额, gas:gas数额}(二进制编码);

函数 abi.encode,abi.encodePacked,abi.encodeWithSelector 和 abi.encodeWithSignature 可用于编码结构化数据。

call调用目标合约
定义状态变量接收call返回值:

bytes public data;

调用setX()函数,转入msg.value数额的ETH,uint类型在abi编码传参中要写成uint256:

function callSetX(address _targetContractAddr, uint x) external payable{
	(bool ok, bytes memory _data) = _targetContractAddr.call{value: msg.value}(abi.encodeWithSignature("setX(uint256)", x));
	require(ok, "call setX failed");
	data = _data;
}

调用getX()函数

function callGetX(address _targetContractAddr) external payable{
	(bool ok, bytes memory _data) = _targetContractAddr.call(abi.encodeWithSignature("getX()"));
	require(ok, "call getXfailed");
	data = _data;
}

调用不存在的函数,因为TargetContract合约中写了fallback()函数,会触发fallback()函数:

function callNotExist(address _targetContractAddr) external{
	(bool ok,) = _targetContractAddr.call(abi.encodeWithSignature("notExist()"));
	require(ok, "call notExist failed");
}

abi.decode解码返回值:

function abiDecode() external view returns(uint256) {
	return abi.decode(data,(uint256));
}

委托调用 delegatecall

delegatecall与call类似,也是solidity中地址类型的低级成员函数。delegate中是委托/代表的意思。
委托合约通俗理解就是调用其他合约的函数来改变自身的状态变量。
定义一个目标合约:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract TargetContract {
    address public owner;
    uint public x;
    uint public balance;
    
    function getBalance() external payable{
        balance = address(this).balance;
    }

    function setX(uint _x) external payable {
        x = _x;
    }

    function setOwner(address _owner) external {
        owner = _owner;
    }
}

调用目标合约:
本合约中使用了abi.encodeWithSelector为二进制编码,与abi.encodeWithSignature是一样的,二选一即可。

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract DelegateCall {
    address public owner;
    uint public x;
    uint public balance;

    address public targetContractAddr;
    constructor(address _addr) {
        targetContractAddr = _addr;
    }

    function callGetBalance() external payable{
        (bool ok,) = targetContractAddr.delegatecall(abi.encodeWithSelector(TargetContract.getBalance.selector));
        require(ok, "call getBalance failed");
    }

    function callSetX(uint _x) external payable{
        (bool ok,) = targetContractAddr.delegatecall(abi.encodeWithSelector(TargetContract.setX.selector, _x));
        require(ok, "call setX failed");
    }

    function callSetOwner(address _owner) external{
        (bool ok,) = targetContractAddr.delegatecall(abi.encodeWithSelector(TargetContract.setOwner.selector, _owner));
        require(ok, "call setOwner failed");
    }
}

Remix验证发现TargetContract的状态变量没有任何改变,DelegateCall 已经改变。
在这里插入图片描述
在这里插入图片描述

call和delegatecall的区别

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值