29. solidity 函数选择器 selector

29. 函数选择器 selector

当我们调用智能合约时,本质上是向目标合约发送了一段calldata,在remix中发送一次交易后,可以在详细信息中看见input即为此次交易的calldata

tx input in remix

发送的calldata中前4个字节是selector(函数选择器)。这一讲,我们将介绍selector是什么,以及如何使用。

msg.data

msg.datasolidity中的一个全局变量,值为完整的calldata(调用函数时传入的数据)。

在下面的代码中,我们可以通过Log事件来输出调用mint函数的calldata

    // event 返回msg.data
    event Log(bytes data);

    function mint(address to) external{
        emit Log(msg.data);
    }

当参数为0x2c44b726ADF1963cA47Af88B284C06f30380fC78时,输出的calldata

0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78

这段很乱的字节码可以分成两部分:

前4个字节为函数选择器selector:
0x6a627842

后面32个字节为输入的参数:
0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78

其实calldata就是告诉智能合约,我要调用哪个函数,以及参数是什么。

method idselector函数签名

method id定义为函数签名Keccak哈希后的前4个字节,当selectormethod id相匹配时,即表示调用该函数,那么函数签名是什么?

其实在第21讲中,我们简单介绍了函数签名,为"函数名(逗号分隔的参数类型)"。举个例子,上面代码中mint的函数签名为"mint(address)"。在同一个智能合约中,不同的函数有不同的函数签名,因此我们可以通过函数签名来确定要调用哪个函数。

注意,在函数签名中,uintint要写为uint256int256

我们写一个函数,来验证mint函数的method id是否为0x6a627842。大家可以运行下面的函数,看看结果。

    function mintSelector() external pure returns(bytes4 mSelector){
        return bytes4(keccak256("mint(address)"));
    }

结果正是0x6a627842

method id in remix

使用 selector

我们可以利用selector来调用目标函数。例如我想调用mint函数,我只需要利用abi.encodeWithSelectormint函数的method id作为selector和参数打包编码,传给call函数:

    function callWithSignature() external returns(bool, bytes memory){
        (bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"));
        return(success, data);
    }

在日志中,我们可以看到mint函数被成功调用,并输出Log事件。

logs in remix

习题

  1. 函数选择器有4个字节,8个16进制位

  2. 如果一笔调用智能合约的交易的calldata为

    0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc22

    ,被调用函数的选择器是:0x6a627842

  3.  transfer函数的函数签名是?
    
    function transfer(address recipient, uint amount) external override returns (bool) {
            balanceOf[msg.sender] -= amount;
            balanceOf[recipient] += amount;
            emit Transfer(msg.sender, recipient, amount);
            return true;
        }
        
    "transfer(address,uint256)"
    
  4. 上一题中transfer函数的选择器为:0xa9059cbb

  5. 我想调用transfer函数将合约中的100枚 $PEOPLE 代币转给 0 地址,
    已知:
    1. 用pp指代 $PEOPLE 的ERC20合约地址。
    2. 从合约转账代币调用的是 transfer(address sender, uint256 amount) 函数
    
    代码实现可以是:
    
    pp.call(abi.encodeWithSelector(0xa9059cbb, address(0), uint256(100)));
    

猜你喜欢

转载自blog.csdn.net/qq_42465670/article/details/129843138
今日推荐