メインコンテンツまでスキップ

29 Sending Ether - transfer, call, send

Ether を送りたい、どうしたらよいか? 3 つの方法がある

  • transfer (2300 gas limit で、失敗した場合はエラーを返す)
  • send (2300 gas limit で、 成功したかによって bool 値を返す)
  • call (gas limit を設定でき、成功したかによって bool 値を返す)

Ether もどうやってもらうか? コントラクトがイーサを受け取れるようにするには、少なくとも以下の関数のいずれかを持っていないといけない(solidity の built-in 関数)

  • receive() external payable
  • fallback() external payable

msg.dataが空っぽな場合は receive()関数が呼ばれ、それ以外の場合は fallback()関数が呼ばれる

おすすめのメソッドはどれか? 2019 年以降、re-entrancy対策をとったcallのメソッドが一番おすすめだという。

re-entrencyの対策としては

  • 他のコントラクトを呼び出す前に、状態の変更を実行してからにする。(いわゆる Chceck->effects->interactions)
  • re-entrancy対策の修飾子を使うこと

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

contract ReceiveEther {
/*
fallback() or receive()のどちらが呼ばれるかを簡単にまとめたチャート
Chart:
send Ether
|
msg.dataは空っぽ?
/ \
yes no
/ \
receive()あるか? fallback()
/ \
yes no
/ \
receive() fallback()
*/

// イーサをもらったときにmsg.dataが空っぽな場合、呼ばれる関数
receive() external payable {}

// msg.dataは空っぽではない場合、Fallback関数が呼ばれる。
fallback() external payable {}

function getBalance() public view returns (uint) {
return address(this).balance;
}
}

contract SendEther {
function sendViaTransfer(address payable _to) public payable {
// この関数はイーサを送るメソッドとしておすすめではなくなった
_to.transfer(msg.value);
}

function sendViaSend(address payable _to) public payable {
// bool値を返して、trueは成功でfalseは失敗
// この関数はイーサを送るメソッドとしておすすめではなくなった
bool sent = _to.send(msg.value);
require(sent, "Failed to send Ether");
}

function sendViaCall(address payable _to) public payable {
// bool値を返して、trueは成功でfalseは失敗
// この関数はイーサを送るメソッドとしておすすめになる。
// ("")はデータを送りたいときに埋めるところなので、イーサを送るだけだといらないから空っぽにしている→最初はまだ理解しなくてよい
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
}

tip

msg.sender, msg.value, msg.dataとは

当該トランザクションに紐付く変数で、msg.sender はトランザクションの発行者で、msg.value はトランザクションに付随するイーサの量で、msg.dataはトランザクションに付随するデータ。

Remixで試す