目录
事件起因
项目实训内容中涉及fallback函数,被我的专业老师(兼高级工程师=_=)出难题了,当时他只留下了一句话,“这就是你们和我的差距!”(藐视<-<),随后我便通宵达旦,好好研究了一番......
问题描述
- 为什么需要fallback函数,它究竟起什么作用?
- 合约中只有fallback函数,如何进行调用测试?
- receive函数必须写吗?什么情况才会调用?(0.6.x引入)
问题分析
带着这些让人头皮发麻的questions,我的脑细胞们已披甲持戈,准备浴血奋战了。
据官方解释,简单说fallback是一个不接受任何参数且不返回任何内容的函数。
它的执行场景有以下两个方面:
- 调用一个不存在的函数时触发。
- 发送Ether到一个合约地址中,但是receive()不存在或msg.data不为空。
另外,当通过transfer或send方法调用时,fallback函数有2300 gas的限制。
问题探究
这里为了体现与时俱进的创作理念,示例合约选取了最新0.8.x编译版本进行测试,合约编辑器使用的是remix-IDE desktop app版。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract fallbackTest {
fallback() payable external {}
}
在fallbackTest合约中呢,只写了个fallback函数,编译部署成功后截图如下:
那可以看到部署后,我们并没有相应的方法按钮去调用,怎么办呐。。。别急,这里提供了CALLDATA(low level interaction)一种别样的调用方式。那么问题来了,在 CALLDATA 下面的框框里应该填什么?(有兴趣的同学可以研究一下,这里我们只是测试fallback函数,里面啥都不用填)同时为了体现payable修饰符的作用,我们在部署界面找到value输入框,例如下图:
伴随着调用fallback函数,附加了发送1个以太(默认单位 Wei),然后只需点击CALLDATA框右边的 Transact 按钮就好了。那么这笔钱发送给谁了呢?其实是默认存入当前的合约账户里了。
问题进阶探究
下面的内容比较烧脑,无妨,我先抛个砖。
Which function is called, fallback() or receive()?
send Ether
|
msg.data is empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
参考合约示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
contract ReceiveEther {
// Function to receive Ether. msg.data must be empty
receive() external payable {}
// Fallback function is called when msg.data is not empty
fallback() external payable {}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
contract SendEther {
function sendViaTransfer(address payable _to) public payable {
_to.transfer(msg.value);
}
function sendViaSend(address payable _to) public payable {
bool sent = _to.send(msg.value);
require(sent, "Failed to send Ether");
}
function sendViaCall(address payable _to) public payable {
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}
好奇心十分强大的同学可以对此合约进行测试,从中体会fallback与receive函数的调用时机。因为本人困得一个字都码不来了~嗯,如果学习过程中,存在疑问或解释不当之处,还望各位道友加以批评指正,3Q!!!:)