什么是fallback函数:
出处:http://me.tryblockchain.org/blockchain-solidity-fallback.html
回退函数是合约里的特殊函数,没有名字,不能有参数,没有返回值。当调用的函数找不到时,就会调用默认的fallback函数
⚠️Even though the fallback function cannot have arguments, one can still use msg.data
to retrieve any payload supplied with the call.
由于Solidity中,Solidity提供了编译期检查,所以我们不能直接通过Solidity调用一个不存在的函数。但我们可以使用Solidity的提供的底层函数address.call
来模拟这一行为,进行函数调用:
pragma solidity ^0.4.24;
contract ExecuteFallback{
//回退事件,会把调用的数据打印出来
event FallbackCalled(bytes data);
//fallback函数,注意是没有名字的,没有参数,没有返回值的
function() public{
emit FallbackCalled(msg.data);//在这里返回的是functionNotExist()函数签名0x69774a91
}
//调用已存在函数的事件,会把调用的原始数据,请求参数打印出来
event ExistFuncCalled(bytes data, uint256 para);
//一个存在的函数
function existFunc(uint256 para) public {
emit ExistFuncCalled(msg.data, para);
}
// 模拟从外部对一个存在的函数发起一个调用,将直接调用函数
function callExistFunc() public returns(bool){
bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)"));
return address(this).call(funcIdentifier, uint256(1));
}
//模拟从外部对一个不存在的函数发起一个调用,由于匹配不到函数,将调用回退函数
function callNonExistFunc() public returns(bool){
bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()"));
return address(this).call(funcIdentifier);
}
}
调用callExistFunc,返回:
调用callNonExistFunc,有调用fallback函数返回,而且要注意,这里call的返回值也为true:
⚠️一个没有定义一个回退函数的合约。如果接收ether,会触发异常,并返还ether(solidity v0.4.0开始)。所以合约要接收ether,必须实现回退函数。
- balance 和 transfer
可以通过地址的balance属性来查看一个地址的余额,发送以太币(单位为:wei)到一个地址可以使用 transfer方法
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);//将合约this中的10wei转到账户地址x
注意:如果x是一个合约地址,它的代码(如果存在的话,更明确是为 fallback 函数)将会和 transfer 调用一起被执行,send也一样(这是EVM的限制,是不可阻止的)。如果执行过程中gas不够或是失败,当前合约会终止抛出异常
1)send(),有返回值bool
举例说明:
pragma solidity ^0.4.24;
contract SendFallback{
//fallback函数及其事件
event FallbackTrigged(bytes data);
function() public payable{//一定要声明为payable,否则send()
执行结果将会始终为false
emit FallbackTrigged(msg.data);
}
//存入一些ether用于后面的测试
function deposit() public payable{
}
//查询当前的余额
function getBalance() public view returns(uint){
return address(this).balance;
}
event SendEvent(address to, uint value, bool result);
//使用send()发送ether,观察会触发fallback函数
function sendEther() public{
bool result = address(this).send(1);//从合约地址的余额中发送1wei给它自己,所以其balance不会变,只是会消耗msg.sender账户gas
emit SendEvent(this, 1, result);
}
}
一开始,调用getBalance函数得到合约地址中余额为0,这时候如果调用sendEther函数,result将为false,也没有调用fallback函数:
然后通过调用deposit函数传入value = 5,使得合约地址账户余额为5,这时候再调用sendEther函数,result将为true,并且调用了fallback函数,附带的数据是0x
(bytes类型的默认空值),空数据:
因为是address(this).send(1),所以调用getBalance函数发现并不会有改变,还是5
如果改为msg.sender.send(1),就不是自己给自己了,而是从合约账户中传1wei给msg.sender账户,调用getBalance函数值变为了4
2)transfer(),没有返回值
但是如果改成使用transfer的话,发现并没有调用fallback函数,是不是后面设置为transfer不调用fallback函数了???????:
pragma solidity ^0.4.24;
contract SendFallback{
...event SendEvent(address to, uint value);
//使用send()发送ether,观察会触发fallback函数
function sendEther() public{
msg.sender.transfer(1);
emit SendEvent(msg.sender, 1);
}
}
返回:
后面发现了原因,这里有一个概念没有搞明白,就是调用的fallback函数与你所指定的地址有关。比如上面的例子中,使用的是msg.sender.transfer(1),那么将意味着如果msg.sender为一个合约地址,就调用它里面写的fallback函数,如果不是合约地址,那么自然就没有fallback函数调用,所以这里的结果才会没有调用fallback函数,所以如果我们将其改成address(this).transfer(1),就会发现果然调用了:
3)call.value(),有返回值bool
改成msg.sender.call.value(1)():
pragma solidity ^0.4.24;
contract SendFallback{
...
event SendEvent(address to, uint value,bool result);
//使用send()发送ether,观察会触发fallback函数
function sendEther() public{
bool result = msg.sender.call.value(1)();
emit SendEvent(msg.sender, 1,result);
}
}
返回也没有触发fallback函数,这是因为这里调用的是msg.sender.call.value(1)(),而不是address(this).call.value(1)():
将msg.sender.call.value(1)改为address(this).call.value(1)()后,就会发现还是会触发fallback函数:
从上面我们可以看出这三个调用都会调用访问其的地址的fallback函数,这会有危险。
fallback中的限制
上面三个
函数总是会调用fallback,这个行为非常危险,著名的DAO被黑也与这有关。比如当我们对一系列帐户进行send()
操作,其中某个做恶意帐户中的fallback函数实现了一个无限循环,将因为gas耗尽,导致所有send()
失败。为解决这个问题,send()
函数当前即便gas充足,也只会附带限定的2300gas,故而fallback函数内除了可以进行日志操作外,你几乎不能做任何操作。
下述行为消耗的gas都将超过fallback函数限定的gas值:
- 向区块链中写数据( x =1;)
- 创建一个合约
- 调用一个external的函数
- 发送ether
所以一般,我们只能在fallback函数中进行一些日志操作
举例说明:
pragma solidity ^0.4.24;
contract Test {
event FallbackTrigged1(bytes data);
function() external { emit FallbackTrigged1(msg.data); }
function getBalance() public view returns(uint){
return address(this).balance;
}
}
contract Sink {
event FallbackTrigged2(bytes data);
function() external payable {emit FallbackTrigged2(msg.data); }
function getBalance() public view returns(uint){
return address(this).balance;
}
}
contract Caller {
function deposit() payable public{}
function callTest(Test test) public returns (bool) {
require(address(test).call(abi.encodeWithSignature("nonExistingFunction()")));//一般call进行调用都返回true,不管里面的函数是否存在
return address(test).send(2 ether);
}
}
在这个例子中callTest(Sink合约地址)会成功:
callTest(Test合约地址)失败,因为没有payable:
如果改为:
pragma solidity ^0.4.24;
contract Test {
function() external { x = 1;}
uint x;
function getBalance() public view returns(uint){
return address(this).balance;
}
}
contract Sink {
function() external payable { x = 1;}
uint x;
function getBalance() public view returns(uint){
return address(this).balance;
}
}
则两个都会失败,返回形如下面的结果:
说明gas的限制果然是起作用的
但是好像要是想要写复杂的操作也是可以的,但是没有查到呢,查到再补充?????????