abi 编解码函数及 low-level call

  • Q1ngying
  • 更新于 2024-10-31 21:58
  • 阅读 531

abi 编、解码;函数签名;函数选择器及 abi 编解码在 low-level call 中的应用。

abi 编码,解码函数

在 Solidity 中,内置的 abi 编解码函数有如下 2 个:

  • abi.decode(bytes memory encodedData, (...)) returns (...)

    • 根据传入的数据类型解码数据(encodeencodePacked均可解码)
    • 数据类型通过在括号中作为第二个参数给出。
    • example:(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
  • abi.encode(...) returns (bytes memory)

    • 对给定的数据进行 abi 编码
  • abi.encodePacked(...) returns (bytes memory)

    • 对给定的数据进行紧密编码(此编码可能会不明确)
  • abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)

    • 从第二个数据开始,对给定的参数进行 ABI 编码,并在前面添加给定的 4 bytes 的函数选择器
    • 通过该内置函数,我们可以构造我们的 calldata
  • abi.encodeCall(function functionPointer, (...) ) returns (bytes memory)

    • 使用元组中找到的参数对 functionPointer的调用进行 ABI 编码。
    • 执行完整的类型检查,确保类型与函数签名匹配。
    • 结果等于 abi.encodeWithSelector(functionPointer.selector, ...)
  • abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)

    • 相当于 abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)

看到这里可能会比较迷惑,其中涉及到很多小的问题。

函数签名,函数选择器部分部分内容来自 ethereum stakechange 回答:https://ethereum.stackexchange.com/questions/135205/what-is-a-function-signature-and-function-selector-in-solidity-and-evm-language

函数签名(function signature)

函数签名是函数名称和它将采用的参数类型的集合,组成的一个没有空格的字符串。

例如,我们在 solidity 中有一个函数:

function transfer(address sender, uint256 amount) public {
  // some code
}

那么这个函数的函数签名将是:

transfer(address,uint256)

函数签名十分重要,因为我们可以通过函数签名来获取下一个部分:函数选择器(function selector)

函数选择器(function selector)

函数选择器是函数调用的 calldata 的前四个字节,用于指定我们要调用的函数。函数选择器是同一函数签名的 keccak256 哈希的前四个字节。

当我们调用 EVM 智能合约时,智能合约需要知道其将执行的函数,控制这一点的代码叫做函数选择器。函数选择器看起来长这样:

0xa9059cbb

在 solidity 中,我们可以通过如下的代码来求解我们的函数选择器:

bytes4(keccak256(bytes(function_signature)))

我们也可以使用 Foundry 框架中的cast sigCLI 获取:

$ cast sig "transfer(address,uint256)"

函数引用

在 solidity 中,对于我们已经在合约中定义的函数,我们可以使用如下的方法进行调用:

function transfer(address sender, uint256 amount) public {
  // some code
}

function callTransfer() public {
  tranfer(address(0), 1000);
}

如果我们不加括号,那么便是对函数的引用。 也就是上面提到的 functionPointer(用于 abi.encodeCall

而对于函数的 functionPointer,其有一个属性(方法),可以直接获得这个函数的函数选择器。

在 solidity 代码中直接获取已声明函数的函数选择器

我们可以使用functionPointer.selector来直接获取函数选择器,比如:

function transfer(address sender, uint256 amount) public {
  // some code
}

function example() public {
  // some logic

  // get transfer selector
  tranfer.selector;

  // some logic
}

这样我们就可以在 Solidity 合约中直接获得已定义函数的函数选择器,无需手动计算。

对于函数选择器,我们将在 abi.encodeWithSelector中使用。

low-level call

在 solidity 中,调用函数可以通过直接实例化合约(或接口),直接调用该实例中的函数的方法直接调用函数(高级层面上)。同样,他还有一种调用函数的方法,这种方法,无需将对应地址实例化,只需知道我们要进行调用的地址,我们要调用的函数的函数选择器(函数签名)即可。这就是 low-level call。

low-level call 分为三种,本文仅讨论 call一种。

还是刚刚的例子,如果我们想调用地址 token上的 transfer()函数,使用 low-level call 的方式来调用的话,可以使用下面的方法:

token.call(data);

这就需要我们手动构建我们的调用数据(calldata)—— data。

calldata 实际上就是函数选择器加上函数的参数

构建 calldata 就可以使用我们上面提到的几个 abi 编码函数:

使用 abi.encodeWithSelector

使用该方法,我们就需要知道要调用的函数选择器:

// transfer(address sender, uint256 amount)
token.call(abi.encodeWithSelector(selector, address(1), 10));

在不知道函数选择器,但知道函数签名的情况下可以使用下面的方法(不过不如直接使用 abi.encodeWIthSignature

// transfer(address sender, uint256 amount)
token.call(abi.encodeWithSelector(bytes4(keccak256(bytes("transfer(address,uint256)"))), address(1), 10));

使用 abi.encodeWithSignature

使用该方法,我们需要知道函数签名(对于没实例化合约的情况下,使用这种方法居多)

// transfer(address sender, uint256 amount)
token.call(abi.encodeWithSelector("transfer(address,uint256)", address(1), 10));

使用 abi.encodeCall

使用该方法,需要能引用函数(有 functionPointer)下面是 Foundry cheatcode expectCall中使用的例子:

address alice = makeAddr("alice");
emit log_address(alice);
vm.expectCall(
  address(token), abi.encodeCall(token.transfer, (alice, 10)), 0
);
token.transferFrom(alice, address(0), 10);

expectCall 的两个参数是:预期的函数调用(calldata);预期出现的次数

使用 abi.decode 对 low-level call 的返回值进行解码

使用 low-level call 时,会有两个返回值:(bool, bytes)

  • 第一个 bool值表示调用是否成功
  • 第二个 bytes值为调用的返回值(经过 abi.encode 编码)

比如下面的例子:

(, bytes memory results) = addr.call(abi.encodeWithSignature("balanceOf(address)"));
uint256 addrBalance = abi.decode(results, (uint256));
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Q1ngying
Q1ngying
0x468F...68bf
本科在读,合约安全学习中......