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

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

回避策

攻撃を回避するためにはどのような仕様にすればよいでしょうか。

入札(bid)と同時に返金を行うと、返金時に問題が発生し入札自体が失敗してしまうことがあるので、入札と返金の処理を分ける仕様にしてみたいと思います。

実装

入札と返金の処理を分けたコードは次の通りです。

[ソースコード]

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
pragma solidity ^0.4.11;
contract AuctionWithdraw {
address public highestBidder; // 最高額提示アドレス
uint public highestBid; // 最高提示額
// 返金額を管理するマップ
mapping(address => uint) public userBalances;

/// コンストラクタ
function AuctionWithdraw() payable {
highestBidder = msg.sender;
highestBid = 0;
}

/// Bid用の関数
function bid() public payable {
// bidが現在の最高額よりも大きいことを確認
require(msg.value > highestBid);

// 最高額提示アドレスの返金額を更新する
userBalances[highestBidder] += highestBid;

// ステート更新
highestBid = msg.value;
highestBidder = msg.sender;
}

function withdraw() public{
// 返金額が0より大きいことを確認
require(userBalances[msg.sender] > 0);

// 最高額提示者でないことを確認
require(userBalances[msg.sender] < highestBid);

// 返金額を退避
uint refundAmount = userBalances[msg.sender];

// 返金額を更新
userBalances[msg.sender] = 0;

// 返金処理
if(!msg.sender.send(refundAmount)) {
throw;
}
}
}

このコードのポイントは、bid部分と返金部分が独立し、もしwithdrawで失敗したとしてもその後の入札(bid)に影響を与えないことです。

まとめ

etherの返金は、送り先のコントラクトに悪意や実装の問題があると予期せぬ動作を引き起こされる可能性があります。

返金をするときは、他の処理とは独立させ返金専用の関数を作り、etherを取りにきてもらうのが安全だということになります。