接收以太币 ETH

本章讲解在 Solidity 中,智能合约如何接收以太币 ETH

官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

智能合约可以用来存储以太币 ETH,它可以接收外部账户发来的 ETH。但是,并不是任何合约都具备接收 ETH 的能力。

一个智能合约想要接收 ETH,就必须实现 receive 或者 fallback 函数。

我们在学习智能合约的接收函数 receive 之前,必须先理解以太坊中的账户类型。如果一个智能合约中这两个函数都没有定义,那么它就不能接收以太币。

按照 solidity 语言规范,推荐使用 receive 函数。因为 receive 函数简单明了、目的明确,而 fallback  函数主要用来处理未知函数时调用。

1. receive 函数

定义 receive 函数的格式如下:

receive() external payable {
    // 这里可以添加自定义的处理逻辑,但也可以为空
}

receive 函数有如下几个特点:

  • 1)无需使用 function 声明。
  • 2)参数为空。
  • 3)可见性必须设置为 external
  • 4)状态可变性必须设置为 payable

当外部地址向智能合约地址发送以太币时,将触发执行 receive 函数。

我们可以在函数体内不写任何自定义的处理逻辑,它依然能够接收以太币,这也是最常见的使用方式。

如果必须在 receive 的函数体内添加处理语句的话,最好不要添加太多的业务逻辑。

因为外部调用 sendtransfer 方法进行转账的时候,为了防止重入攻击,gas 会限制在 2300。如果 receive 的函数太复杂,就很容易会耗尽 gas,从而触发交易回滚。

receive 函数里通常会执行一些简单记录日志的动作,比如触发 event

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FuncReceive {
  // 定义接收事件
  event Received(address sender, uint amount);
 
  // 接收 ETH 时,触发 Received 事件
  receive() external payable {
    emit Received(msg.sender, msg.value);
} }

我们把合约代码复制到 Remix,进行编译,并部署到区块链上:

 

部署完成后,在 Value 中输入要存入的 ETH 数量,比如 100 wei。然后点击下方的 Transact,就会把当前账户中的 100 wei 转入到合约中,你可以看到当前合约的 Balance 变为 0.0000000000000001 ETH。 另外,查看右下方控制台的输出日志中,我们可以看到 Received 事件被触发。

2. fallback 函数

Solidity 语言中,fallback 是一个预定义的特殊函数,用于在处理未知函数接收以太币 ETH 时调用。

定义 fallback 函数的格式如下:

fallback () external [payable] {
  // 这里可以添加自定义的处理逻辑,但也可以为空
}

fallback 函数有如下几个特点:

  • 1)无需使用 function声明。
  • 2)参数为空。
  • 3)可见性必须设置为 external
  • 4)状态可变性可以为空,或者设置为 payable

2.1 fallback 调用条件

fallback 会在两种情况下,被外部事件触发而执行:

1) 外部调用了智能合约中不存在的函数

在这种情况下,函数声明中无需设置状态可变性,函数形式如下:

fallback () external {
}

2) 外部向智能合约中存入以太币,并且当前合约中不存在 receive 函数

在这种情况下,函数声明中必须设置状态可变性为 payable,函数形式如下:

fallback () external payable {
}

如果合约中已经定义了 receive函数,那么向这个合约中存入以太币,将会优先调用 receive 函数,而不会执行 fallback 函数。

所以,如果一个智能合约允许存入以太币,那么它就必须实现 receive 或者 fallback 函数,而且函数的状态可变性设置为 payable

如果一个智能合约没有定义这两个函数中的任何一个,那么它就不能接收以太币。

2.2 receive 和 fallback 工作流程

receivefallback 的触发条件,可以参考以下流程:

 

 

当参数 msg.data 为空时,就意味着:外部向合约进行转账,存入以太币。

当参数 msg.data 不为空时,就意味着:外部在调用合约中的函数。

我们在左边的分支可以看到,receivefallback 函数都能够用于接收以太币 ETH

一个智能合约在接收 ETH 时:

  • 如果存在着 receive 函数,就会触发 receive
  • 当不存在 receive 函数,但存在 fallback 函数时,就会触发 fallback
  • 而当两者都不存在时,交易就会 revert,存入 ETH 失败。

2.3 测试和验证

我们来编写几个合约来测试和验证 fallback 函数。

a) 第一种情况

智能合约中只定义 fallback 函数,而且状态可变性为 payable

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FuncFallback {
  // 定义回退事件
  event Fallback();

  fallback() external payable {
    emit Fallback();
  }
}

当我们向合约中存入以太币时,将会执行 fallback 函数,从而触发里面的 Fallback 事件。

我们把这个合约部署在 Remix 上。然后,在 "VALUE" 栏中填入要发送给合约的金额(单位是 Wei),再点击 "Transact", 存入以太币。

我们可以看到交易成功,并且触发了 Fallback 事件。

b) 第二种情况

智能合约中同时定义了 receivefallback 函数,而且两者的状态可变性都为 payable

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FuncFallback {
  // 定义接收事件
  event Receive();
  // 定义回退事件
  event Fallback();

  receive() external payable {
    emit Receive();
  }
  fallback() external payable {
    emit Fallback();
  }
}

当我们向合约中存入以太币时,将会执行 receive 函数,从而触发里面的 Receive 事件。

我们把这个合约部署在 Remix 上。然后,在 "VALUE" 栏中填入要发送给合约的金额,再点击 "Transact", 存入以太币。

我们可以看到交易成功,并且触发了 Receive 事件,但没有触发 Fallback 事件。

 

c) 第三种情况

依然使用上面的合约,当我们调用一个不存在的函数,将会触发 Fallback 事件。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FuncFallback {
  // 定义接收事件
  event Receive();
  // 定义回退事件
  event Fallback();

  receive() external payable {
    emit Receive();
  }
  fallback() external payable {
    emit Fallback();
  }
}

我们在 "CALLDATA" 栏中填入随意编写的 msg.data 数据,使之不为空,再点击 "Transact"

我们可以看到交易成功,并且触发了 Fallback 事件。

2.4 fallback 使用场景

按照 Solidity 语言新的规范,如果只是为了让合约账户能够存入以太币,推荐使用 receive 函数,而不是使用 fallback 函数。

这样做的好处就是,从函数命名就可以知其用途,职责划分明确,防止引起混乱,导致误用。

receive 函数,只用于接收以太币。而 fallback,只用于调用了不存在的合约函数。

fallback 和 receive 函数的使用场景:

a) 空投

利用 receive 或者 fallback 函数,用户只需要使用钱包向空投合约发送 0 金额的转账,空投合约就可以向该地址进行空投。

b) 锁仓

用户使用钱包将代币转账到锁仓合约中,锁仓合约利用 receive 或者 fallback 函数接收到请求,就可以执行锁仓逻辑了。

c) 兑换

ERC20 代币 WETH 合约中,利用 receive 或者 fallback 函数,在收到 ETH 后,自动兑换为 WETH 代币。

关于这方面的应用,可以参考 BinSchool 网站中《Solidity 常用合约》章节。