html 回退 函数,Solidity的fallback函数(二十三)|入门系列

fallback函数,回退函数,是合约里的特殊无名函数,有且仅有一个

回退函数

回退函数是合约里的特殊函数,没有名字,不能有参数,没有返回值。下面来看一个简单的回退函数例子。

pragma solidity ^0.4.0;

contract SimpleFallback{

function(){

//fallback function

}

}

调用函数找不到时

当调用的函数找不到时,就会调用默认的fallback函数。由于Solidity中,Solidity提供了编译期检查,所以我们不能直接通过Solidity调用一个不存在的函数。但我们可以使用Solidity的提供的底层函数address.call来模拟这一行为,关于call()函数详见:http://me.tryblockchain.org/Solidity-call-callcode-delegatecall.html 。我们来看个例子:

pragma solidity ^0.4.0;

contract ExecuteFallback{

//回退事件,会把调用的数据打印出来

event FallbackCalled(bytes data);

//fallback函数,注意是没有名字的,没有参数,没有返回值的

function(){

FallbackCalled(msg.data);

}

//调用已存在函数的事件,会把调用的原始数据,请求参数打印出来

event ExistFuncCalled(bytes data, uint256 para);

//一个存在的函数

function existFunc(uint256 para){

ExistFuncCalled(msg.data, para);

}

// 模拟从外部对一个存在的函数发起一个调用,将直接调用函数

function callExistFunc(){

bytes4 funcIdentifier = bytes4(keccak256("existFunc(uint256)"));

this.call(funcIdentifier, uint256(1));

}

//模拟从外部对一个不存在的函数发起一个调用,由于匹配不到函数,将调用回退函数

function callNonExistFunc(){

bytes4 funcIdentifier = bytes4(keccak256("functionNotExist()"));

this.call(funcIdentifier);

}

}

在上面的代码中,我们定义了一个fallback函数,和一个对应的显示请求原始数据的事件FallbackCalled。

当我们调用callExistFunc()时,由于函数实际存在,会直接触发existFunc()的调用,我们能看到ExistFuncCalled事件被触发,运行时将打印出ExistFuncCalled[ "0x42a788830000000000000000000000000000000000000000000000000000000000000001","1"]。其中第一个数据是调用该函数时,传过来的原始数据,前四个字节42a78883,是existFunc()的方法签名,指明是对该函数进行调用,紧跟其后的是函数的第一个参数0000000000000000000000000000000000000000000000000000000000000001,表示的是uin256值1(32字节的无符号整数值十六进制表示),数据格式说明详见:http://me.tryblockchain.org/Solidity-abi-abstraction.html。

当我们调用的函数找不到时才会触发对fallback函数的自动调用。当调用callNonExistFunc(),由于它调用的functionNotExist()函数在合约中实际并不存在。故而,实际会触发对fallback函数的调用,运行后会触发FallbackCalled事件,说明fallback被调用了。事件输出的数据是,FallbackCalled[ "0x69774a91"],0x69774a91是调用的原始数据,是调用的functionNotExist()函数的四字节的函数签名。

send()函数发送ether

当我们使用address.send(ether to send)向某个合约直接转帐时,由于这个行为没有发送任何数据,所以接收合约总是会调用fallback函数,我们来看看下面的例子:

pragma solidity ^0.4.0;

contract SendFallback{

//fallback函数及其事件

event fallbackTrigged(bytes data);

function() payable{fallbackTrigged(msg.data);}

//存入一些ether用于后面的测试

function deposit() payable{

}

//查询当前的余额

function getBalance() constant returns(uint){

return this.balance;

}

event SendEvent(address to, uint value, bool result);

//使用send()发送ether,观察会触发fallback函数

function sendEther(){

bool result = this.send(1);

SendEvent(this, 1, result);

}

}

在上述的代码中,我们先要使用deposit()合约存入一些ether,否则由于余额不足,调用send()函数将报错。存入ether后,我们调用sendEther(),使用send()向合约发送数据,将会触发下述事件:

SendEvent[

"0xc35f7ac1351648b0b8a699c5f07dd6a78f626714",

"1",

"true"

]

fallbackTrigged[

"0x"

]

可以看到,我们成功使用send()发送了1wei到合约,触发了fallback函数,附带的数据是0x(bytes类型的默认空值),空数据。

这里需要特别注意的是:

如果我们要在合约中通过send()函数接收,就必须定义fallback函数,否则会抛异常。

fallback函数必须增加payable关键字,否则send()执行结果将会始终为false。

fallback中的限制

send()函数总是会调用fallback,这个行为非常危险,著名的DAO被黑也与这有关。如果我们在分红时,对一系列帐户进行send()操作,其中某个做恶意帐户中的fallback函数实现了一个无限循环,将因为gas耗尽,导致所有send()失败。为解决这个问题,send()函数当前即便gas充足,也只会附带限定的2300gas,故而fallback函数内除了可以进行日志操作外,你几乎不能做任何操作。如果你还想做一些复杂的操作,解决方案看这里:http://me.tryblockchain.org/blockchain-solidity-fallback-bestpractice.html。

下述行为消耗的gas都将超过fallback函数限定的gas值:

向区块链中写数据

创建一个合约

调用一个external的函数

发送ether

所以一般,我们只能在fallback函数中进行一些日志操作:

pragma solidity ^0.4.0;

contract FallbackFailOnGasLimit{

uint someStorage;

event fallbackTrigged(bytes);

function() payable{

fallbackTrigged(msg.data);

//将因为写入操作失败,注释掉下面这行,将会执行成功

someStorage = 1;

}

function callFallback() returns (bool){

return this.send(0);

}

}

在上述代码中,fallback函数有写入操作,会消耗掉超过限定的gas,故会导致失败,注释掉someStorage = 1;后,执行callFallback()将会成功。

注意:上述仅对使用send()方式的有2300gas的限制,对使用call()方式没有这样的限制。

关于作者

专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。

参考资料

处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。

你可能感兴趣的:(html,回退,函数)