Ethereum(19) - IoTで利用するスイッチを制御するコントラクト(実装編)

スマートコントラクトはIoTの領域でも活用が期待されています。

カーシェアリングを例にとってみます。

カーシェアリングでは、利用者は車を利用するたびに利用時間に応じた金額を支払う必要があります。

スマートコントラクトを使うと、そのコントラクトに送金することで車が送金状況を確認してドアを自動で開けるようなことが可能になります。

実装

IoTで利用するスイッチを制御するコントラクトを実装します。

[ソースコード]

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
64
65
66
67
68
69
70
71
pragma solidity ^0.4.11;
contract SmartSwitch {
// スイッチ用の構造体
struct Switch {
address addr; // 利用者のアドレス
uint endTime; // 利用終了時刻(UnixTime)
bool status; // trueの場合は利用可能
}

address public owner; // サービスオーナーのアドレス
address public iot; // IoTのアドレス

mapping (uint => Switch) public switches; // Switchを格納するマップ
uint public numPaid; // 支払いが行われた回数

/// サービスオーナーの権限チェック
modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// IoTの権限チェック
modifier onlyIoT() {
require(msg.sender == iot);
_;
}

/// コンストラクタ
/// IoTのアドレスを登録
function SmartSwitch(address _iot) {
owner = msg.sender;
iot = _iot;
numPaid = 0;
}

/// 支払い時に呼ばれる
function payToSwitch() public payable {
// 1 etherでなければ処理を終了
require(msg.value == 1000000000000000000);

// Switchを設定
Switch s = switches[numPaid++];
s.addr = msg.sender;
s.endTime = now + 300; // 300秒⇒5分使用可能にする
s.status = true;
}

/// statusを変更する
/// 利用終了時刻になったら呼び出される。引数はswitchesのkey値
function updateStatus(uint _index) public onlyIoT {
// 対象のindexに対してSwitchが設定されていなければ処理を終了
require(switches[_index].addr != 0);

// 利用終了時刻に達していなければ処理を終了
require(now > switches[_index].endTime);

// statusを更新
switches[_index].status = false;
}

/// 支払われたetherを引き出す
function withdrawFunds() public onlyOwner {
if (!owner.send(this.balance))
throw;
}

/// コントラクトを破棄
function kill() public onlyOwner {
selfdestruct(owner);
}
}

このコンストラクトの仕様は次の通りです。

  • サービスオーナーがコンストラクタでIoTスイッチを登録します。
  • IoTスイッチの利用者はpayToSwitch関数でetherを支払います。
    1etherを支払うと5分間使用できるIoTスイッチを想定しています。
  • IoTスイッチは利用終了時刻になるとupdateStatus関数でstatusを更新してサービスを利用できなくします。
  • サービスオーナーはwithdrawFunds関数をコールし、支払われたetherを引き出すことができます。

次回から、このスマートコントラクトの動作を確認していきます。

Ethereum(18) - コントラクト名とアドレスを管理するスマートコントラクト(実行編②)

前回は、コントラクト名とアドレスを管理するスマートコントラクトの動作確認をMist Walletを使って実施しましたがうまくいきませんでした。

今回は、gethコンソールを使って動作確認を行ってみます。

事前準備

動作確認を行うために、下記のような準備をしておきます。

  • geth起動
  • マイニング開始
  • スマートコントラクトをデプロイ

コンストラクト名の登録

まずはコントラクト名の登録を行います。登録するコントラクト名はcon1です。

登録する前に、アドレスのロックを解除する必要があります。

[コントラクト名の登録]

1
2
3
4
5
6
7
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xec3b01f36b44182746ca109230567c4915512e35
Passphrase:
true

> nr.register.sendTransaction("con1",{from:eth.accounts[0], gas:5000000})
"0xc81cfecc89a0f698859249eb03a7a2b8560cdea437dcfa7df4a90bcfc5a11ea2"

問題なくトランザクションを発行できました。

ブロックに取り込まれるまで少し待って登録内容を確認します。

[登録内容の確認]

1
2
3
4
5
6
7
8
> nr.numContracts()
1

> nr.getOwner("con1")
"0xec3b01f36b44182746ca109230567c4915512e35"

> eth.accounts[0]
"0xec3b01f36b44182746ca109230567c4915512e35"

コントラクト名が1つ登録され、登録されたコントラクト名のオーナーアドレス(eth.accounts[0])であることが分かります。

アドレスの登録

次にコンストラクタ名にアドレスを結びつけます。

登録するアドレス情報はダミーです。

[アドレスの登録]

1
2
> nr.setAddr.sendTransaction("con1", "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",{from:eth.accounts[0], gas:5000000})
"0x68df1e01241aa57f5e12ba5f419400b03bbb6b993d95b8d9294ea9396ed76667"

問題なくトランザクションを発行できました。

ブロックに取り込まれるまで少し待って登録内容を確認します。

[登録内容の確認]

1
2
> nr.getAddr("con1")
"0x00000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

問題なくアドレスが登録されています。

説明の登録

コントラクト名に対応する説明を登録します。

[説明の登録]

1
2
> nr.setDescription.sendTransaction("con1", "This is description of con1.",{from:eth.accounts[0], gas:5000000})
"0xe838840f9e5f8e37414b731a0d0a9065c6b0fdab04f10f6692570d6aa4f92bf4"

問題なくトランザクションを発行できました。

ブロックに取り込まれるまで少し待って登録内容を確認します。

[登録内容の確認]

1
2
3
4
5
> nr.getDescription("con1")
"0x54686973206973206465736372697074696f6e206f6620636f6e312e00000000"

> web3.toUtf8(nr.getDescription("con1"))
"This is description of con1."

こちらも問題なく登録されていることを確認できました。

オーナーの変更

オーナーの変更を行います。

accounts[0]からaccounts[1]へ変更します。

[オーナーの変更]

1
2
> nr.changeOwner.sendTransaction("con1", eth.accounts[1],{from:eth.accounts[0], gas:5000000})
"0x905b2a21fd1741b51c86fd11b56cae0b68b9e04985ae69473601ddabfda7ce3f"

問題なくトランザクションを発行できました。

ブロックに取り込まれるまで少し待って変更内容を確認します。

[オーナーの変更]

1
2
3
4
5
> nr.getOwner("con1")
"0x63f25b9bbd974fdfa07477cb99c60d2073cfe560"

> eth.accounts[1]
"0x63f25b9bbd974fdfa07477cb99c60d2073cfe560"

オーナー情報がeth.accounts[1]のアドレスに変更されていることを確認できました。

登録内容の解除

最後に登録内容の解除を行います。

オーナーがeth.accounts[1]に変更されているので、そのアドレスのロックを解除しておく必要があります。

[登録解除]

1
2
3
4
5
6
7
> personal.unlockAccount(eth.accounts[1])
Unlock account 0x63f25b9bbd974fdfa07477cb99c60d2073cfe560
Passphrase:
true

> nr.unregister.sendTransaction("con1",{from:eth.accounts[1], gas:5000000})
"0x9615b4f95c35c771f2b8f07dd7d1ebb61172497f0096a01dbeb6e3552f684ca1"

問題なくトランザクションを発行できました。

ブロックに取り込まれるまで少し待って内容を確認します。

[解除内容確認]

1
2
> nr.numContracts()
0

登録されているコントラクトがなくなっていることを確認できます。

前回Mist Walletでうまくいかなかった理由

gethコンソールでの動作確認では全く問題がありませんでした。

ではどうしてMist Walletでは同じコントラクタの動作確認ができなかったのでしょうか?

gethコンソールで正常に動作確認している最中に、Mist Walletでもコントラクト情報を参照してみたのですが、情報が全く反映されていませんでした・・・いやNum Contractsだけはきちんと反映されてました。

また、Mist Walletで各登録処理を行ってみたところ、Mist Wallet画面では登録内容が確認できませんでしたが、gethコンソール上では登録内容の確認ができました

つまりMist Walletだと構造体のステートや引数のある関数の情報は表示できないということになります。

まとめて情報を参照できるMist Walletは大変便利だと思っていたのですが、ちょっと残念です。


次回は、また別のスマートコントラクトを作成していきたいと思います。

Ethereum(17) - コントラクト名とアドレスを管理するスマートコントラクト(実行編)

前回、コントラクト名とアドレスを管理するスマートコントラクトを実装しました。

今回はそのスマートコントラクトのデプロイと動作確認を行います。

アカウントごとの役割

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

  • MAIN ACCOUNT (eth.accounts[0])
    オーナー
  • ACCOUNT1 (eth.accounts[1])
    利用者
  • ACCOUNT3 (eth.accounts[3])
    マイニングする

デプロイ

まずはデプロイを行います。

デプロイアカウントはMAIN ACCOUNTで、デプロイ時に必要なパラメータはありません。

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

コントラクト名の登録

register関数をコールして、コントラクト名を登録します。

コントラクト名はASCIIで指定する必要があります。

contract1を設定したいのでそれをASCII変換した0x636f6e747261637431を入力します。

[コントラクト名の登録]

登録内容の確認

コントラクト名が登録されていることを確認します。

[登録内容の確認]

早速問題が発生しています。

Num Contractsが1になっているので登録処理は実行されているのですが、コントラクト名contract1がどこにも見つかりません。

ContractsステートのOwnerのアドレスも0x0x0000000000000000000000000000000000000000となっており、オーナーのアドレスになっていません。


次回は、コントラクト名やオーナーアドレスがきちんと登録されるように調査・改善していきたいと思います。

Ethereum(16) - コントラクト名とアドレスを管理するスマートコントラクト(実装編)

これまではクラウドファンディングを実現するスマートコントラクトを試してきましたが、今回からは別のスマートコントラクトを作成してみます。

コントラクト名とアドレスを管理するスマートコントラクト

コントラクト名とそのアドレスを管理するスマートコントラクトを作成します。

コントラクトを一意で認識するためには、そのアドレスを使わなければなりません。

ただこのアドレスは16進数の長い文字列で認識しにくいため、別名(エイリアス)をつけた方が直感的で管理しやすくなります。


生成したコントラクトを誰かに教える場合、アドレスではなくコントラクトの別名を公開し、今回作成するコントラクトを通してアドレスを伝えた方が便利です。

また、仮にコントラクトに問題があり差し替える必要がある場合、別名(エイリアス)がアドレスへのポインタとなっているので移行がスムーズになるというメリットもあります。

ソースコード

コントラクト名とアドレスを管理するスマートコントラクトを実現するソースコードは、次のようになります。

各定義・処理に関しては、ソースコード内のコメントをご参照下さい。

[ソースコード]

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
pragma solidity ^0.4.11;
contract NameRegistry {
// コントラクト用の構造体
struct Contract {
address owner; // オーナー
address addr; // コントラクトのアドレス
bytes32 description; // コントラクトの説明
}

uint public numContracts; // 登録済みのレコード数

// コントラクトを保持するマップ
mapping (bytes32 => Contract) public contracts;

/// コンストラクタ
function NameRegistry() {
numContracts = 0;
}
//------------------------------------------------------
/// コントラクトの登録
function register(bytes32 _name) public returns (bool){
// 名前が利用されていなければ登録
if (contracts[_name].owner == 0) {
Contract con = contracts[_name];
con.owner = msg.sender;
numContracts++;
return true;
} else {
return false;
}
}

/// コントラクトの削除
function unregister(bytes32 _name) public returns (bool) {
if (contracts[_name].owner == msg.sender) {
contracts[_name].owner = 0;
numContracts--;
return true;
} else {
return false;
}
}
//------------------------------------------------------
/// コントラクトのオーナー変更
function changeOwner(bytes32 _name, address _newOwner) public onlyOwner(_name) {
contracts[_name].owner = _newOwner;
}

/// コントラクトのオーナー取得
function getOwner(bytes32 _name) constant public returns (address) {
return contracts[_name].owner;
}
//------------------------------------------------------
/// コントラクトのアドレス設定
function setAddr(bytes32 _name, address _addr) public onlyOwner(_name) {
contracts[_name].addr = _addr;
}

/// コントラクトのアドレス取得
function getAddr(bytes32 _name) constant public returns (address) {
return contracts[_name].addr;
}
//------------------------------------------------------
/// コントラクトの説明を設定
function setDescription(bytes32 _name, bytes32 _description) public onlyOwner(_name) {
contracts[_name].description = _description;
}

/// コントラクトの説明を取得
function getDescription(bytes32 _name) constant public returns (bytes32) {
return contracts[_name].description;
}
//------------------------------------------------------
/// 関数呼出し前に呼び出される処理であるmodifier(オーナーのみ実行可能)
modifier onlyOwner(bytes32 _name) {
require(contracts[_name].owner == msg.sender);
_;
}
}

データ型のbyte32は固定長の任意型で、任意の値を保管できます。”32”はサイズを指定しています。

今回の処理では、文字列を保管するのに使用しています。

全体的に、値を設定したり取得するだけなのでとても簡単なコントラクトになっていると思います。


次回から、Mist Walletを使ってこのスマートコントラクトの動作を確認していきます。

Ethereum(15) - クラウドファンディング用のスマートコントラクト(キャンペーン失敗)

前回、Mist Walletを使って、キャンペーンが成功するケースの動作確認を行いました。

今回はキャンペーンが失敗するケース、つまり投資総額が目標金額に達しなかった場合の動作確認を行います。

デプロイ時の設定

デプロイ時(コンストラクタのコール時)の設定値は下記の通りです。

  • FROM
    Main Accountを選択
  • SELECT CONTRACT TO DEPLOY
    ‘Crowd Funding’を選択。
  • _duration
    テスト用に15分とする。秒指定のため 900 を設定。
  • _goal amount
    目標額を20etherとする。
    Mist Walletで設定する場合はwei表記のため 20000000000000000000 を設定。

[デプロイ時の設定値イメージ]

アカウントごとの役割

アカウントの役割は次のようにします。

  • MAIN ACCOUNT (eth.accounts[0])
    オーナー
  • ACCOUNT1 (eth.accounts[1])
    投資家1
  • ACCOUNT3 (eth.accounts[3])
    マイナー(マイニングするアカウント)

キャンペーンに失敗するケース

Mist Walletを使って全ての操作と確認を行っていきます。

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

[コンストラクト]

まだ投資総額(Total Amount)が0であることや、キャンペーンが終了していないことが確認できます。


次に、アカウントの残高を確認します。

[残高]

赤枠で囲まれたアカウントが、スマートコントラクタの動作確認に使用するアドレスです。

投資

投資家1から、fund関数を選択しコントラクトに19ether投資します。

[投資1]



確認画面ではパスワードを入力します。

コントラクタの状態確認

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

投資総額(Total Amount)が19etherになっていることと、まだ締め切り期限になっていないことが分かります。

[コントラクト]

キャンペーンの終了

締め切り期限が過ぎるのを待って、オーナーからcheckGoalReached関数を呼び出しキャンペーンを終了させます。

[キャンペーンの終了]



確認画面ではパスワードを入力します。


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

[コントラクト]

キャンペーンが終了しており、statusからキャンペーンが失敗していることを確認できます。


最後に各アカウントの残高を確認します。

[残高]

投資家1のアカウントからは、投資した額の19etherが戻されているためもとの残高に戻っています。

オーナーの残高もクラウドファンディングの金額が振り込まれることなく、残高に増減はありません。

想定通りの結果となりました。

Ethereum(14) - クラウドファンディング用のスマートコントラクト(キャンペーン成功・4回目)

前回、Mist Walletを使って、キャンペーンが成功するケースの動作確認を行いました。

ほとんどうまくいったのですが、最後にキャンペーンが終了できないという問題にぶつかりました。

キャンペーンが終了できない原因

キャンペーンが終了できない原因ですが、とても単純なことでした。

コンストラクトをデプロイする時にMain AccountではなくAccount2を間違って指定していたのです。

キャンペーンの終了は、オーナー(デプロイしたアカウント)だけが行えるようにコーディングしているので、それ以外のユーザではエラーになっていたという訳です。

凡ミスですね。

これまで発生した問題まとめ

これまでにつまづいた問題と対応は以下の通りです。

  • 終了期限の単位を勘違い
    (対策)分単位ではなく秒単位で指定する。
  • マイニングしてしまって残高の増減が分からなくなった
    (対策)オーナー、投資家以外のアカウントでマイニングを行う。
  • コンストラクタのデプロイユーザ間違い
    (対策)デプロイ時のFROMにMain Accountを指定する。

以上、3点に気を付けながらもう一度、キャンペーン成功のケースを実行していきます。

デプロイ時の設定

デプロイ時(コンストラクタのコール時)の設定値は下記のようになります。

各処理を実行してブロックに取り込まれるまでに意外と時間がかかるので、期限(duration)を15分に増やしました。

  • FROM
    Main Accountを選択
  • SELECT CONTRACT TO DEPLOY
    ‘Crowd Funding’を選択。
  • _duration
    テスト用に15分とする。秒指定のため 900 を設定。
  • _goal amount
    目標額を20etherとする。
    Mist Walletで設定する場合はwei表記のため 20000000000000000000 を設定。

[デプロイ時の設定値イメージ]

アカウントごとの役割

4つのアカウントの役割は前回同様です。

  • MAIN ACCOUNT (eth.accounts[0])
    オーナー
  • ACCOUNT1 (eth.accounts[1])
    投資家1
  • ACCOUNT2 (eth.accounts[2])
    投資家2
  • ACCOUNT3 (eth.accounts[3])
    マイナー(マイニングするアカウント)

キャンペーンに成功するケース(4回目)

Mist Walletを使って全ての操作と確認を行っていきます。

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

[コンストラクト]

まだ投資総額(Total Amount)が0であることや、キャンペーンが終了していないことが確認できます。


次に、アカウントの残高を確認します。

[残高]

赤枠で囲まれたアカウントが今回使用するアドレスです。

投資

投資家1から、fund関数を選択しコントラクトに10ether投資します。

確認画面ではパスワードを入力します。

[投資1]


投資家2からは、fund関数を選択しコントラクトに20ether投資します。

確認画面ではパスワードを入力します。

[投資2]

コントラクタの状態確認

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

投資総額(Total Amount)が30etherになっていることと、まだ締め切り期限になっていないことが分かります。

[コントラクト]

キャンペーンの終了

締め切り期限が過ぎるのを待って、オーナーからcheckGoalReached関数を呼び出しキャンペーンを終了させます。

[キャンペーンの終了]


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

[コントラクト]

キャンペーンが終了しており、statusからキャンペーンが成功していることを確認できます。


最後に各アカウントの残高を確認します。

[残高]

投資家1のアカウントからは投資した額の10etherが減り、投資家2のアカウントからは投資した額の20etherが減っています。

オーナーの残高にはクラウドファンディングで成功した額(30ether)が増えていることが分かります。

今回は無事にキャンペーンを成功させることができました😅


Mist Walletを使って全ての操作を行いましたが、gethコマンドを使うより操作が簡単ですし、情報をまとめて見れるので便利だと思いました。


次回は、キャンペーンに失敗するケースを試したいと思います。

Ethereum(13) - クラウドファンディング用のスマートコントラクト(キャンペーン成功・3回目)

前回、クラウドファンディング用のスマートコントラクトに対して、キャンペーンが成功するケースの動作確認ができました。

ただ、オーナーアカウントでマイニングをしていたために、キャンペーンが成功していくらetherが増えたのかを確認できませんでした。

今回は、オーナーと投資家以外のアカウントを使ってマイニングを行い、キャンペーン成功でどのくらいetherが増えたのかを確認します。

デプロイ時の設定

デプロイ時(コンストラクタのコール時)の設定値は前回と同様です。

  • SELECT CONTRACT TO DEPLOY
    ‘Crowd Funding’を選択。
  • _duration
    テスト用に10分に設定。秒指定のため 600 を設定。
  • _goal amount
    目標額に20etherを設定。
    Mist Walletで設定する場合はwei表記のため 20000000000000000000 を設定。

[デプロイ時の設定値イメージ]

アカウントごとの役割

3つのアカウントの役割は前回同様です。

今回は3つのアカウントとは別にマイニングするアカウント(ACCOUNT3)を用意しました。

  • MAIN ACCOUNT (eth.accounts[0])
    オーナー
  • ACCOUNT1 (eth.accounts[1])
    投資家1
  • ACCOUNT2 (eth.accounts[2])
    投資家2
  • ACCOUNT3 (eth.accounts[3])
    マイニングする

キャンペーンに成功するケース(3回目)

これまではgethコンソールを使って動作確認していましたが、今回は試しに全ての操作をMist Walletを使って行ってみます。

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

[コンストラクト]

まだ投資総額(Total Amount)が0であることや、キャンペーンが終了していないことが確認できます。


次に、アカウントごとの残高を確認します。

[残高]

赤枠で囲まれたアカウントが今回使用するアドレスです。

投資

投資家1から、fund関数を選択しコントラクトに10ether投資します。

確認画面ではパスワードを入力します。

[投資1]


投資家2からも、fund関数を選択しコントラクトに10ether投資します。

確認画面ではパスワードを入力します。

[投資2]


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

投資総額(Total Amount)が20etherになっていることと、もう締め切り期限になっていることが分かります。

[コントラクト]

キャンペーンの終了

締め切り期限が過ぎているので、オーナーからcheckGoalReached関数を呼び出しキャンペーンを終了させます。

(確認画面でエラーが表示されているのが気になりますが・・・・)

[キャンペーンの終了]


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

[コントラクト]

・・・だめですね。

キャンペーンが終了になっていません。(Endedが「No」でStatusが「Funding」のまま)

関数の選択(checkGoalReached)アカウントの選択(MAIN ACCOUNT)は間違っていないはずなんですが・・・・


次回はこの「キャンペーンが終了しない」問題を調査・解決したいと思います。

Ethereum(12) - クラウドファンディング用のスマートコントラクト(キャンペーン成功・2回目)

前回、クラウドファンディング用のスマートコントラクトに対して、キャンペーンが成功するケースでテストしてみましたが、うまく動作しませんでした。

今回はその原因を調べてみます。

原因調査①

設定値を見直したところ1点、勘違いしていたことが分かりました。

それは設定値の締め切り(deadline)の単位は分ではなく秒ということです。

とりあえずこれだけ変更してもう一度トライしてみます。

デプロイ時の設定

デプロイ時(コンストラクタのコール時)の設定値は下記のように設定しました。

  • SELECT CONTRACT TO DEPLOY
    ‘Crowd Funding’を選択。
  • _duration
    テスト用に10分に設定。秒指定のため 600 を設定。
  • _goal amount
    目標額に20etherを設定。
    Mist Walletで設定する場合はwei表記のため 20000000000000000000 を設定。

[デプロイ時の設定値イメージ]

アカウントごとの役割

3つのアカウントの役割は前回同様です。

  • MAIN ACCOUNT (eth.accounts[0])
    オーナー
  • ACCOUNT1 (eth.accounts[1])
    投資家1
  • ACCOUNT2 (eth.accounts[2])
    投資家2

キャンペーンに成功するケース(2回目)

geth上でcfという変数に、スマートコントラクトを割り当てます。

アドレスとインタフェースはMist Walletから取得しておきます。

[コマンド]

1
var cf = eth.contract([ { "constant": false, "inputs": [], "name": "checkGoalReached", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "ended", "outputs": [ { "name": "", "type": "bool", "value": false } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "numInvestors", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "totalAmount", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "status", "outputs": [ { "name": "", "type": "string", "value": "Funding" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "goalAmount", "outputs": [ { "name": "", "type": "uint256", "value": "20000000000000000000" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "deadline", "outputs": [ { "name": "", "type": "uint256", "value": "1625372072" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "uint256" } ], "name": "investors", "outputs": [ { "name": "addr", "type": "address", "value": "0x0000000000000000000000000000000000000000" }, { "name": "amount", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "kill", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address", "value": "0xec3b01f36b44182746ca109230567c4915512e35" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "fund", "outputs": [], "payable": true, "type": "function" }, { "inputs": [ { "name": "_duration", "type": "uint256", "index": 0, "typeShort": "uint", "bits": "256", "displayName": "&thinsp;<span class=\"punctuation\">_</span>&thinsp;duration", "template": "elements_input_uint", "value": "600" }, { "name": "_goalAmount", "type": "uint256", "index": 1, "typeShort": "uint", "bits": "256", "displayName": "&thinsp;<span class=\"punctuation\">_</span>&thinsp;goal Amount", "template": "elements_input_uint", "value": "20000000000000000000" } ], "payable": false, "type": "constructor" } ]).at('0xADd563073f3178AbFCDbDd3Ec707e5cd0D280f97')


deadline(終了時間)とキャンペーンのステータスを確認します。

[gethコンソール]

1
2
3
4
5
> cf.deadline()
1625372072

> cf.ended()
false


fund関数をトランザクションで呼び出します。

投資家Aと投資家Bからそれぞれ10etherを送金します。

アカウントごとにロックを解除する必要があります。

[gethコンソール]

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

> cf.fund.sendTransaction({from:eth.accounts[1], gas:5000000, value:web3.toWei(10, "ether")})
"0x8895b8f141d011453478fdfa1da51e69814cbd36e171faa60796bc8512b2eebb"

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

> cf.fund.sendTransaction({from:eth.accounts[2], gas:5000000, value:web3.toWei(10, "ether")})
"0x7326a3446fec2628b87e1c5c18656be47a5bca238326c73834c25b5281f59b00"


投資家Aからの投資額を確認します。

[gethコンソール]

1
2
3
4
5
> cf.investors(0)[0]
"0x63f25b9bbd974fdfa07477cb99c60d2073cfe560"

> web3.fromWei(cf.investors(0)[1], "ether")
10


投資家Bからの投資額を確認します。

[gethコンソール]

1
2
3
4
5
> cf.investors(1)[0]
"0x0000000000000000000000000000000000000000"

> web3.fromWei(cf.investors(1)[1], "ether")
10

それぞれ10etherずつ投資していることが確認できます。


投資の総額を確認します。

[gethコンソール]

1
2
> web3.fromWei(cf.totalAmount(), "ether")
20


コントラクトの残高を確認します。

[gethコンソール]

1
2
> web3.fromWei(eth.getBalance(cf.address), "ether")
20

目標金額の20etherに達していることが確認できます。


キャンペーンが終了する前のオーナーの残高を確認しておきます。

[gethコンソール]

1
2
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
33734.985904966


あとは、終了時間まで待ってcheckGoalReached関数を呼び出せば、投資された20etherがオーナーのアドレスに振り込まれるはずです。

Mist Walletの画面でコントラクトを表示するとあと何分後かを簡単に確認できます。

[コントラクト確認画面]

キャンペーンの結果確認

キャンペーンが成功したかどうか(目標金額に到達したかどうか)を確認します。

checkGoalReached関数を実行するためには手数料(gas)が必要となりますので、オーナーのアカウントをアンロックしておく必要があります。

[gethコンソール]

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

> cf.checkGoalReached.sendTransaction({from:eth.accounts[0], gas:5000000})
"0x674aae7f69819ec1e4112bd4694bb027d35a9c8f60b47eb160bcc5073e149878"

> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
33834.985904966

> cf.ended()
true

> cf.status()
"Campaign Succeeded"

Mist Walletでも結果を確認してみます。

[キャンペーン成功結果]

今回はうまくいったようです。

キャンペーンが正常に終了し、オーナーのアドレスが20ether増えて・・・あ、失敗しました😨😨😨

オーナーアカウントでマイニングしていたために、キャンペーンの成功でetherがいくら増えたのか確認できません。

次回は、マイニングアカウントを別にしてもう一度トライしてみます。

(3度目の正直という事で・・・・😅)

Ethereum(11) - クラウドファンディング用のスマートコントラクト(キャンペーン成功)

前回は、クラウドファンディング用のスマートコントラクトを作成とデプロイを行いました。

今回は、このスマートコントラクトに対して、キャンペーンが成功するケースでテストしていきます。

コンストラクタ

デプロイ時(コンストラクタのコール時)の設定値は下記のように設定しました。

  • SELECT CONTRACT TO DEPLOY
    ‘Crowd Funding’を選択。
  • _duration
    テスト用に10分に設定。
  • _goal amount
    目標額に20etherを設定。
    Mist Walletで設定する場合はwei表記のため 20000000000000000000 を設定。

[入力イメージ画面]

アカウントごとの役割

アカウントを3つ用意し、次のように役割分担をしておきます。

  • MAIN ACCOUNT (eth.accounts[0])
    オーナー
  • ACCOUNT1 (eth.accounts[1])
    投資家1
  • ACCOUNT2 (eth.accounts[2])
    投資家2

投資家1と投資家2のアカウントからは、etherを送る必要がありますのでマイニングするなどしてetherを増やしておいてください。

[アカウントイメージ]

キャンペーンに成功するケース

geth上でcfという変数に、スマートコントラクトを割り当てます。

アドレスとインタフェースはMist Walletから取得しておきます。

[コマンド]

1
var cf = eth.contract([ { "constant": false, "inputs": [], "name": "checkGoalReached", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "ended", "outputs": [ { "name": "", "type": "bool", "value": false } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "numInvestors", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "totalAmount", "outputs": [ { "name": "", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "status", "outputs": [ { "name": "", "type": "string", "value": "Funding" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "goalAmount", "outputs": [ { "name": "", "type": "uint256", "value": "20000000000000000000" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "deadline", "outputs": [ { "name": "", "type": "uint256", "value": "1625274319" } ], "payable": false, "type": "function" }, { "constant": true, "inputs": [ { "name": "", "type": "uint256" } ], "name": "investors", "outputs": [ { "name": "addr", "type": "address", "value": "0x0000000000000000000000000000000000000000" }, { "name": "amount", "type": "uint256", "value": "0" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "kill", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "owner", "outputs": [ { "name": "", "type": "address", "value": "0x63f25b9bbd974fdfa07477cb99c60d2073cfe560" } ], "payable": false, "type": "function" }, { "constant": false, "inputs": [], "name": "fund", "outputs": [], "payable": true, "type": "function" }, { "inputs": [ { "name": "_duration", "type": "uint256", "index": 0, "typeShort": "uint", "bits": "256", "displayName": "&thinsp;<span class=\"punctuation\">_</span>&thinsp;duration", "template": "elements_input_uint", "value": "" }, { "name": "_goalAmount", "type": "uint256", "index": 1, "typeShort": "uint", "bits": "256", "displayName": "&thinsp;<span class=\"punctuation\">_</span>&thinsp;goal Amount", "template": "elements_input_uint", "value": "" } ], "payable": false, "type": "constructor" } ]).at('0xF9376fc03ebdF73713d757cB8d1cd10448D1Af2f')


deadline(終了時間)とキャンペーンのステータスを確認します。

[gethコンソール]

1
2
3
4
5
> cf.deadline()
1625274319

> cf.ended()
false


fund関数をトランザクションで呼び出します。

投資家Aと投資家Bからそれぞれ10etherを送金します。

アカウントごとにロックを解除する必要があります。

[gethコンソール]

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

> cf.fund.sendTransaction({from:eth.accounts[1], gas:5000000, value:web3.toWei(10, "ether")})
"0x996b426f48dca2f4d47728863589748ab578cce7cd9267c511d66a476b582ca9"

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

> cf.fund.sendTransaction({from:eth.accounts[2], gas:5000000, value:web3.toWei(10, "ether")})
"0x65f7cee0c725421ffeeb4265569948e1598337fe4b9c0916737e26bf87e760f2"


投資家Aからの投資額を確認します。

[gethコンソール]

1
2
3
4
5
> cf.investors(0)[0]
"0x63f25b9bbd974fdfa07477cb99c60d2073cfe560"

> web3.fromWei(cf.investors(0)[1], "ether")
10


投資家Bからの投資額を確認します。

[gethコンソール]

1
2
3
4
5
> cf.investors(1)[0]
"0xd5adf1e9fbc1ed869ed4c7372961238fddc760a5"

> web3.fromWei(cf.investors(1)[1], "ether")
10

それぞれ10etherずつ投資していることが確認できます。


投資の総額を確認します。

[gethコンソール]

1
2
> web3.fromWei(cf.totalAmount(), "ether")
20


コントラクトの残高を確認します。

[gethコンソール]

1
2
> web3.fromWei(eth.getBalance(cf.address), "ether")
20

目標金額の20etherに達していることが確認できます。


キャンペーンが終了する前のオーナーの残高を確認しておきます。

[gethコンソール]

1
2
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
27555


あとは、終了時間まで待ってcheckGoalReached関数を呼び出せば、投資された20etherがオーナーのアドレスに振り込まれるはずです。

checkGoalReached関数を実行するためには手数料(gas)が必要となりますので、オーナーのアカウントをアンロックしておく必要があります。

[gethコンソール]

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

> cf.checkGoalReached.sendTransaction({from:eth.accounts[0], gas:5000000})
"0xf3b15cbacb1b9aeaad5a646d924fe54c78f4e34b6eda37321f8b6438626b77ef"

> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
27554.985

> cf.ended()
false

・・・なにかおかしいです。

まずcheckGoalReached関数の呼び出し自体はうまくいっているようですが、オーナーのetherが増えていません。(手数料だけはちゃんと引き落とされてます・・・)

あと修了確認のcf.ended関数を呼び出しても、キャンペーンが終わっていません。

Mist Walletで確認しても、終了にはなっておらず、投資された20etherもそのままです。
(本来であれば、目標金額に達しているのでオーナーに20etherが送金され、コントラクト上は0etherになっているはず・・)



またDeadlineを確認すると、27分前に締め切りは過ぎているようです。



今回の動きがおかしい件はスマートコントラクトの処理が問題だと思われますので、次回はデバッグを行い想定通りの処理ができるようにしたいと思います。

Ethereum(10) - クラウドファンディング用のスマートコントラクト

今回は、クラウドファンディング用のスマートコントラクトを作成します。

クラウドファンディングの仕様

スマートコントラクトの仕様は次の通りです。

  • etherで資金を募る。
  • 投資家はスマートコントラクトに対して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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
pragma solidity ^0.4.11;
contract CrowdFunding {
// 投資家
struct Investor {
address addr; // アドレス
uint amount; // 投資額
}

address public owner; // コントラクトのオーナー
uint public numInvestors; // 投資家の数
uint public deadline; // 締め切り(UnixTime)
string public status; // キャンペーンのステータス
bool public ended; // キャンペーンが終了しているかどうか
uint public goalAmount; // 目標額
uint public totalAmount; // 投資の総額
mapping (uint => Investor) public investors; // 投資家管理用のマップ

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

/// コンストラクタ
function CrowdFunding(uint _duration, uint _goalAmount) {
owner = msg.sender;

// 締め切りを設定(UnixTime)
deadline = now + _duration;

goalAmount = _goalAmount;
status = "Funding";
ended = false;

numInvestors = 0;
totalAmount = 0;
}

/// 投資する際に呼び出される関数
function fund() payable {
// キャンペーンが終わっていれば処理を中断
require(!ended);

Investor inv = investors[numInvestors++];
inv.addr = msg.sender;
inv.amount = msg.value;
totalAmount += inv.amount;
}

/// 目標額に達したかどうかを確認する
/// キャンペーンの成功/失敗に応じたetherの返金をする
function checkGoalReached () public onlyOwner {
// キャンペーンが終わっていれば処理を中断
require(!ended);

// 締め切り前の場合は処理を中断
require(now >= deadline);

if(totalAmount >= goalAmount) { // キャンペーンに成功
status = "Campaign Succeeded";
ended = true;
// オーナーにコントラクト内のすべてのetherを送金する
if(!owner.send(this.balance)) {
throw;
}
} else { // キャンペーンに失敗
uint i = 0;
status = "Campaign Failed";
ended = true;

// 投資家毎にetherを返金する
while(i <= numInvestors) {
if(!investors[i].addr.send(investors[i].amount)) {
throw;
}
i++;
}
}
}

/// コントラクトを破棄
function kill() public onlyOwner {
selfdestruct(owner);
}
}
  • struct(4-7行目)
    構造体です。
    複数の変数をまとめて宣言することができます。
  • mapping(16行目)
    キーとバリューをとるデータ構造を表現します。
    キーには投資額(uint型)、バリューには投資家(Investor型)を宣言しています。
  • コンストラクタ(24-36行目)
    締め切り(deadline)、目標額(goalAmount)などの初期値を設定します。
  • fund関数(39-47行目)
    投資するときに呼び出される関数で、etherの送金を伴う形で呼び出されます。
    キャンペーンが終了していなければ、Investorを生成してマップに登録し、投資総額を更新します。
    etherを受け取る関数のためpayableを付与しています。
  • checkGoalReached(51-78行目)
    キャンペーン終了時にオーナーによってのみ呼び出される関数です。
    キャンペーンが終了し、目標額に達している場合はオーナーに集められた投資額が送金されます。
    目標額に満たなかった場合は、投資家にetherが返金されます。

デプロイ

Mist Walletでスマートコントラクトをデプロイします。

デプロイの手順は下記の記事をご参照ください。

デプロイの参考記事 - Ethereum(8) - スマートコントラクト①


次回は、キャンペーンに成功するケースで処理を実行してみます。