solidity进阶第一课——接收和发送ETH

一,接收ETH

solidity里支持两种特殊的回调函数,receive()和fallback(),他们主要在接收ETH和处理合约中不存在的函数调用。在solidity0.6版本之前,语法只有fallback()函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。在0.6版本后,solidity才将fallback()函数拆分成receive()和fallback()两个函数。

receive()只用于处理接收ETH。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键词:receive() external payable{ }。receive()函数不能有任何的参数,不能返回任何值,必须包含external和payable。当合约接收ETH的时候,receive()会被触发。receive()最好不要执行太多的逻辑因为如果别人用send和transfer方法发送ETH时,gas会被限制,receive()太复杂可能会触发out of gas报错。如果用call就可以自定义gas执行更复杂的逻辑。我们可以在receive()里发送一个event,如:

    // 定义事件
    event Received(address Sender, uint Value);
    // 接收ETH时释放Received事件
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }

有些恶意合约,会在receive() 函数(老版本的话,就是 fallback() 函数)嵌入恶意消耗gas的内容或者使得执行故意失败的代码,导致一些包含退款和转账逻辑的合约不能正常工作,因此写包含退款等逻辑的合约时候,一定要注意这种情况。

fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract。fallback()声明时不需要function关键字,必须由external修饰,一般也会用payable修饰,用于接收ETH:fallback() external payable { }。我们定义一个fallback()函数,被触发时候会释放fallbackCalled事件,并输出msg.sender,msg.value和msg.data:

    // fallback函数
    fallback() external payable{
        emit fallbackCalled(msg.sender, msg.value, msg.data);
    }

receive()和fallback()函数的区别

receive()和fallback()都能用于接收ETH,他们的触发规则如下:

触发fallback() 还是 receive()?
           接收ETH
              |
         msg.data是空?
            /  \
          是    否
          /      \
receive()存在?   fallback()
        / \
       是  否
      /     \
receive()   fallback()

二,发送ETH

我们将实现三种方法实现向ReceiveETH合约发送ETH。首先,先在发送ETH合约SendETH中实现payable的构造函数和receive(),让我们能够在部署时和部署后向和部署后向合约转帐。

contract SendETH {
    // 构造函数,payable使得部署的时候可以转eth进去
    constructor() payable{}
    // receive方法,接收eth时被触发
    receive() external payable{}
}

transfer

  • 用法是接收方地址.transfer(发送ETH数额)
  • transfer()gas限制是2300,足够用于转账,但对方合约的fallback()receive()函数不能实现太复杂的逻辑。
  • transfer()如果转账失败,会自动revert

代码样例,注意里面的_toReceiveETH合约的地址,amountETH转账金额:

// 用transfer()发送ETH
function transferETH(address payable _to, uint256 amount) external payable{
    _to.transfer(amount);
}

部署SendETH合约后,对ReceiveETH合约发送ETH,此时amount为10,value为0,amount>value,转账失败,发生revert。

send

  • 用法是接收方地址.send(发送ETH数额)
  • send()gas限制是2300,足够用于转账,但对方合约的fallback()receive()函数不能实现太复杂的逻辑。
  • send()如果转账失败,不会revert
  • send()的返回值是bool,代表着转账成功或失败,需要额外代码处理一下。

代码样例:

// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
    // 处理下send的返回值,如果失败,revert交易并发送error
    bool success = _to.send(amount);
    if(!success){
        revert SendFailed();
    }
}

ReceiveETH合约发送ETH,此时amount为10,value为0,amount>value,转账失败,因为经过处理,所以发生revert

call

  • 用法是接收方地址.call{value: 发送ETH数额}("")
  • call()没有gas限制,可以支持对方合约fallback()receive()函数实现复杂逻辑。
  • call()如果转账失败,不会revert
  • call()的返回值是(bool, data),其中bool代表着转账成功或失败,需要额外代码处理一下。
// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
    // 处理下call的返回值,如果失败,revert交易并发送error
    (bool success,) = _to.call{value: amount}("");
    if(!success){
        revert CallFailed();
    }
}

 我们介绍solidity三种发送ETH的方法:transfersendcall

  • call没有gas限制,最为灵活,是最提倡的方法;
  • transfer2300 gas限制,但是发送失败会自动revert交易,是次优选择;
  • send2300 gas限制,而且发送失败不会自动revert交易,几乎没有人用它。

猜你喜欢

转载自blog.csdn.net/qq_52708261/article/details/127061685
今日推荐