fallbackとは
fallbackとはシステム稼働中に予期せぬエラーが発生しても機能制限や応答速度を低下させてシステムの稼働を継続することを言います。要はシステムを完璧に止めてしまうより少しでも動いていた方がいいよねという考え方です。
Solidityにはこの仕組みが言語仕様として組み込まれていて、少し複雑な挙動をするので説明したいと思います。
Solidityのfallback関数
Solidityのfallback関数は下記の特徴があります。
- 無名関数となる
- 引数を取ることができない
- 値を返すことができない
一番シンプルなfallback関数はこのようになります。
function () {}
もし、コントラクトでEtherを受け取る処理を実装したい場合はfallback関数にpayable修飾子をつけなければいけません。
function () payable {}
fallback関数でできないこと
fallback関数では2300 gas以上の処理ができないようになっています。下記は2300 gas以上を消費する処理なので実行することができません。
- storageへの書き込み
- コントラクトの作成
- 大量のgasを消費する外部関数の呼び出し
- Etherの送金
TheDAO事件の原因となったソースコード
これはwithdrawBalance()を何度も呼ぶことができるバグでReentrancyと呼ばれます。msg.sender.call.value(amountToWithdraw)で送金処理を行なっているのですが、msg.senderがコントラクトアドレスの場合、fallback関数を使ってもう1度withdrawBalance()を呼び出し、また送金しての繰り返しをすることができます。
このバグをつかれてThe DAOは65億円相当のトークンを引き出されてしまいました。
// INSECURE mapping (address => uint) private userBalances; function withdrawBalance() public { uint amountToWithdraw = userBalances[msg.sender]; require(msg.sender.call.value(amountToWithdraw)()); // At this point, the caller's code is executed, and can call withdrawBalance again userBalances[msg.sender] = 0; }
このバグを修正したコードが下記になります。msg.senderの残高を先に0にしてから送金処理をすることで何度も再実行されても大丈夫なようにしています。
mapping (address => uint) private userBalances; function withdrawBalance() public { uint amountToWithdraw = userBalances[msg.sender]; userBalances[msg.sender] = 0; require(msg.sender.call.value(amountToWithdraw)()); // The user's balance is already 0, so future invocations won't withdraw anything }
おすすめ書籍
Ethereumを使ったDApps開発を学びたいなら今だとこの1冊が1番良いです!開発環境の構築から使うべきツール、フレームワーク、実装方法・注意点まで網羅的に解説されている書籍なのでおすすめです。出版も2018年1月ということでかなり新しい本で、DMM Bitcoinを作っているネクストカレンシー所属の方が書いているので信頼できます。
ビットコインとブロックチェーンの詳細をしっかりと学びたい方にはこちらの書籍が非常におすすめです。ウォレットの仕組み、楕円曲線暗号、P2Pプロトコル、公開鍵暗号などビットコインを支える技術について詳細に解説されています。また、サンプルコードを通して実際に手を動かして学べるので非常に濃い内容となっています。
髙妻智一
最新記事 by 髙妻智一 (全て見る)
- Polkadot(Substrate)のアドレスとトランザクションについて - 2023-03-09
- 【無料公開】「Goで始めるBitcoin」3章 Bitcoinノードとの通信 技術書典8 - 2020-03-08
- エンジニアがゼロから技術ブログを書くための方法をまとめました - 2019-05-25
コメントを残す