本文详细介绍了Solidity中两种调用合约的方法:通过合约接口的高级调用和使用call
方法的低级调用,解释了为什么低级调用不会回滚而高级调用可能会回滚,并对比了这两种方法在调用空地址时的不同行为。
在 Solidity 中,一个合约可以通过两种方法调用其他合约:通过合约接口,这被认为是高级调用,或使用 call
方法,这是一种低级方法。
尽管这两种方法都使用 CALL
操作码,但 Solidity 对它们的处理方式不同。
在本文中,我们将比较这两者:为什么低级调用从不回滚,而高级调用可能会回滚;以及为什么对空地址的低级调用被视为成功,而对不存在的合约的高级调用会回滚。
在解释原因之前,让我引用一下 Solidity 文档中对此问题的说明。
当子调用发生异常时,它们会自动“冒泡”(即异常会重新抛出),除非在 try/catch 语句 中捕获。对此规则的例外是 send 和低级函数 call、delegatecall 和 staticcall:它们在发生异常时将返回 false 作为其第一个返回值,而不是“冒泡”。
下面我们展示一个高级调用和一个低级 call
,以比较它们的行为。我将在下面的示例中使用 call
方法,但相同的原则可以扩展到 delegatecall
。
Caller
可以通过两种方式调用 Called
中的 ops()
。请注意,ops()
总是会回滚:
pragma solidity ^0.8.0;
contract Caller {
// 第一次调用 ops()
function callByCall(address _address) public returns (bool success) {
(success, ) = _address.call(abi.encodeWithSignature("ops()"));
}
// 第二次调用 ops()
function callByInterface(address _address) public {
Called called = Called(_address);
called.ops();
}
}
contract Called {
// ops() 总是回滚
function ops() public {
revert();
}
}
尽管这两种方法用来调用相同的函数,并且这两种方法都使用操作码 CALL
,但 Solidity 编译器生成的字节码 处理失败情况的方式不同。在 Caller
合约中执行这两个函数将揭示 Caller.callByInterface
会回滚,而 Caller.callByCall
不会。
在 EVM 级别,CALL
操作码返回一个布尔值,指示调用是否成功,并将此返回值放置在栈上。操作码本身不会触发回滚。
当通过合约接口进行调用时,Solidity 为我们处理这个返回值。它明确检查返回值是否为 false,并在调用不是在 try/catch
块中进行时发起回滚。
然而,在使用低级调用时,我们需要手动处理这个返回布尔值,并在需要时明确触发回滚。
contract Caller {
//...
function callByCall(address address) public returns (bool success) {
(success, ) = address.call(abi.encodeWithSignature("ops()"));
if (!success) {
revert("发生错误");
}
}
//...
}
下面的图示例说明了高级调用和低级调用之间在处理回滚时的区别。
Solidity 的低级 call
方法没有先检查被调用地址是否对应一个合约。合约可以使用 EXTCODESIZE
来检查地址是否为智能合约,这在幕后是 address.code.length
的操作码。如果大小为零,表示该地址没有部署合约。然而,call
方法没有包含这个检查;它直接执行 CALL
操作码。
使用接口时会检查目标的代码大小。换句话说,在为 callByInterface
函数生成的字节码中,在执行 CALL
操作码之前,会在指定地址执行 EXTCODESIZE
操作码。如果 EXTCODESIZE
返回的大小为零,表示该地址上没有合约,则该函数会在执行 CALL
操作码之前回滚。这就解释了为什么在使用不存在的合约地址执行时,callByInterface
函数会回滚,而 callByCall
不会。
下面的图示例说明了低级调用和高级调用如何与空合约交互的区别。
从根本上说,当执行时遇到 REVERT
操作码、耗尽Gas或尝试一些被禁止的操作(如除以零)时,执行可能会回滚。当对空地址进行调用时,以上条件均不会发生。
如果你是 Solidity 新手,请查看我们的免费 Solidity 课程。如果你是经验丰富的 Solidity 程序员,请查看我们的高级 Solidity 训练营。
本文由 João Paulo Morais 与 RareSkills 合作撰写。
最初发布于 2024年5月1日
- 原文链接: rareskills.io/post/low-l...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!