接收以太币 ETH

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

推特@Hita_DAO    DiscordHitaDAO

智能合约可以用来存储以太币 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 常用合约》章节。