什么是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的限制果然是起作用的
但是好像要是想要写复杂的操作也是可以的,但是没有查到呢,查到再补充?????????