try catch and ...release
ページ
ホーム
Chromeアプリ開発Tips
公開アプリ
Ubuntu
Linuxコマンド
#!/bin/bash
ブロックチェーンと暗号通貨
2016年12月22日木曜日
Ethereum でクラウドセール・コントラクトを作ってみる
[前回は独自コインを発行するコントラクトを作ってみました。](https://trycatchand.blogspot.jp/2016/12/issue-your-coin-on-ethereum.html) 今回はさらに発展させて、**クラウドセール・コントラクト**を作ってみましょう。クラウドセールとは、簡単に言うと独自トークン(コイン)を発行して売りに出し資金調達することです。クラウドセール・コントラクトは、その目的をEthereumネットワーク上で実現するためのコントラクトです。
## 目次 (index) + [処理の流れ](#flow) + [クラウドセール・コントラクトのコード](#crowdsale-contract-code) + [コントラクトをデプロイ](#deploy-contract) + [残高チェック(出資前)](#check-balance-pre-fund) + [出資](#fund) + [残高チェック(出資後)](#check-balance-post-fund) + [クラウドセール終了トリガーを引く](#trigger-end-crowdsale) + [終了後の残高チェック](#check-balance-after-crowdsale) + [手に入れたコインを譲渡](#transfer-coin) [⤒](#index) 今回作るクラウドセール・コントラクトの処理の流れは、ざっくりと以下のような感じになります。 ## 処理の流れ (flow) + コンストラクタで独自コインの総量などを決定 + 出資者からの資金提供と引き換えに独自コインを渡す + 募集期限終了後、誰でもクラウドセール終了のトリガーを引ける - 調達額が目標に届いた場合: - 終了のトリガーを引いたのがオーナーだった場合: + オーナーは調達資金を手に入れる + 成功フラグを立てる - 終了のトリガーを引いたのがオーナー*以外*だった場合: + 成功フラグを立てる - 調達が目標に届かなかった場合: - 終了のトリガーを引いたのが誰であろうと: + 出資者はオーナーにコインを返却 + 出資者はオーナーから資金を取り戻す + 成功フラグを倒す [⤒](#index) ## クラウドセール・コントラクトのコード (crowdsale-contract-code) [Crowdsale.sol](https://gist.github.com/akirattii/f9c787eeb50758ce600d70cf0db2ed67) ``` pragma solidity ^0.4.7; // ======================================================= // クラウドセール・コントラクトのおおまかな処理 // ======================================================= // + コンストラクタで独自コインの総量などを決定 // // + 出資者からの資金提供と引き換えに独自コインを渡す // // + 募集期限終了後、誰でもクラウドセール終了のトリガーを引ける // // - 調達額が目標に届いた場合: // - 終了のトリガーを引いたのがオーナーだった場合: // + オーナーは調達資金を手に入れる // + 成功フラグを立てる // - 終了のトリガーを引いたのがオーナー*以外*だった場合: // + 成功フラグを立てる // // - 調達が目標に届かなかった場合: // - 終了のトリガーを引いたのが誰であろうと: // + 出資者はオーナーにコインを返却 // + 出資者はオーナーから資金を取り戻す // + 成功フラグを倒す // // クラウドセール・コントラクト contract Crowdsale { // クラウドセールの成功フラグ bool public succeeded; // コイン保有者一覧 mapping (address => uint) public coinBalanceOf; // 出資者に対して発行するコインの単価(ether) uint public coinPrice; // コントラクトの作成者 address public owner; // 調達目標額 uint public fundingGoal; // 調達額 uint public amountRaised; // 募集期限 uint public deadline; // 出資者 Funder[] public funders; // 出資者の構造体 struct Funder { address addr; uint amount; } // コイン送金イベント event CoinTransfer(address sender, address receiver, uint amount); // 資金送信イベント event FundTransfer(address backer, uint amount, bool isContribution); // デバッグログイベント event Log(string method, address sender, string message); // このコントラクトのコンストラクタ function Crowdsale( uint _fundingGoal, uint _duration, uint _coinPrice, uint _coinSupply ) { Log("Constructor", msg.sender, ""); succeeded = false; owner = msg.sender; // オーナーはコントラクト作成者 fundingGoal = _fundingGoal; deadline = now + _duration * 1 minutes; coinPrice = _coinPrice; coinBalanceOf[msg.sender] = _coinSupply; } // 無名関数 // Note: 誰かが資金を送信する度に実行されます function() payable { Log("Payable anonymous function", msg.sender, ""); uint amount = msg.value; // 資金送信者を出資者として登録 funders[funders.length++] = Funder({addr: msg.sender, amount: amount}); // 出資額に加算 amountRaised += amount; // 出資額に見合った数のコインを出資者に発行 sendCoin(owner, msg.sender, amount / coinPrice); // 資金送信イベント発火 FundTransfer(msg.sender, amount, true); } // コイン送金 // @param {address} コイン送金人 // @param {address} コイン受取人 // @param {uint} 数量 function sendCoin(address from, address to, uint amount) returns(bool sufficient) { Log("sendCoin", msg.sender, ""); if (coinBalanceOf[from] < amount) return false; // コインを移転 coinBalanceOf[from] -= amount; coinBalanceOf[to] += amount; // コイン送信イベント発火 CoinTransfer(from, to, amount); return true; } // 「募集期限満了後」のみ関数を実行するためのmodifier modifier afterDeadline() { if (now >= deadline) _; } // 「コントラクトの作成者の場合」のみ関数を実行するためのmodifier modifier onlyOwner() { if (owner == msg.sender) _; } // 目標到達をチェックする関数 // Note: 募集期限満了後に実行できます。 function checkGoalReached() afterDeadline { Log("checkGoalReached", msg.sender, ""); uint i; if (amountRaised >= fundingGoal){ // ***************************************** // 目標達成 // ***************************************** // オーナーによる実行の場合は、オーナーへ調達額を送金 if(msg.sender == owner && owner.send(amountRaised)){ // sendに成功したら資金送金イベント発火 FundTransfer(owner, amountRaised, false); } // 成功フラグを立てる succeeded = true; Log("checkGoalReached", msg.sender, "Fundraise succeeded!"); } else { // ***************************************** // 目標未達 // ***************************************** // FundTransfer(0, 11, false); // ??? // 全出資者に対してループ for (i = 0; i < funders.length; ++i) { // 予め受け取っていたトークンをオーナーに返却 if (sendCoin(funders[i].addr, owner, coinBalanceOf[funders[i].addr])) { // トークンを戻すことに成功したら、出資者に返金 if (funders[i].addr.send(funders[i].amount)) { // 返金に成功したら資金送金イベント発火 FundTransfer(funders[i].addr, funders[i].amount, false); } } } // 成功フラグを倒す(つまり失敗) succeeded = false; Log("checkGoalReached", msg.sender, "Fundraise failed :("); } } } ``` [⤒](#index) ## コントラクトをデプロイ (deploy-contract) 例によって Gethを起動し: ``` $ cd ~/.ethereum_private_testnet $ geth --rpc --rpcapi "eth,net,web3,personal" --rpccorsdomain "*" --datadir=./ --dev --mine --minerthreads=1 --nodiscover --etherbase=0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 ``` 別ターミナルから Geth Console を開きます: ``` $ cd ~/.ethereum_private_testnet $ geth attach ./geth.ipc ``` Geth Console上でコントラクトをデプロイ: ``` var crowdsaleCompiled = eth.compile.solidity('pragma solidity ^0.4.7;contract Crowdsale { bool public succeeded; mapping (address => uint) public coinBalanceOf; uint public coinPrice; address public owner; uint public fundingGoal; uint public amountRaised; uint public deadline; Funder[] public funders; struct Funder { address addr; uint amount; } event CoinTransfer(address sender, address receiver, uint amount); event FundTransfer(address backer, uint amount, bool isContribution); event Log(string method, address sender, string message); function Crowdsale( uint _fundingGoal, uint _duration, uint _coinPrice, uint _coinSupply ) { Log("Constructor", msg.sender, ""); succeeded = false; owner = msg.sender; fundingGoal = _fundingGoal; deadline = now + _duration * 1 minutes; coinPrice = _coinPrice; coinBalanceOf[msg.sender] = _coinSupply; } function() payable { Log("Payable anonymous function", msg.sender, ""); uint amount = msg.value; funders[funders.length++] = Funder({addr: msg.sender, amount: amount}); amountRaised += amount; sendCoin(owner, msg.sender, amount / coinPrice); FundTransfer(msg.sender, amount, true); } function sendCoin(address from, address to, uint amount) returns(bool sufficient) { Log("sendCoin", msg.sender, ""); if (coinBalanceOf[from] < amount) return false; coinBalanceOf[from] -= amount; coinBalanceOf[to] += amount; CoinTransfer(from, to, amount); return true; } modifier afterDeadline() { if (now >= deadline) _; } modifier onlyOwner() { if (owner == msg.sender) _; } function checkGoalReached() afterDeadline { Log("checkGoalReached", msg.sender, ""); uint i; if (amountRaised >= fundingGoal){ if(msg.sender == owner && owner.send(amountRaised)){ FundTransfer(owner, amountRaised, false); } succeeded = true; Log("checkGoalReached", msg.sender, "Fundraise succeeded!"); } else { for (i = 0; i < funders.length; ++i) { if (sendCoin(funders[i].addr, owner, coinBalanceOf[funders[i].addr])) { if (funders[i].addr.send(funders[i].amount)) { FundTransfer(funders[i].addr, funders[i].amount, false); } } } succeeded = false; Log("checkGoalReached", msg.sender, "Fundraise failed :("); } }}'); var crowdsaleAbi = crowdsaleCompiled.Crowdsale.info.abiDefinition; var crowdsaleContract = web3.eth.contract(crowdsaleAbi); // パラメータ設定 var _fundingGoal = web3.toWei(5, "ether"); // 調達目標 var _duration = 10; // 募集期限(分) var _coinPrice = web3.toWei(1, "ether"); // 1コインのEther価格(分かりやすいように1coin/etherで設定) var _coinSupply = 100; // コイン総量 // 登場人物 var owner = eth.accounts[1]; // オーナー(コントラクトの作成者) var funderA = eth.accounts[0]; // 出資者A var funderB = eth.accounts[2]; // 出資者B // オーナーのアドレスをアンロック web3.personal.unlockAccount(owner, "
", 60000); // コントラクトをデプロイ var crowdsale = crowdsaleContract.new( _fundingGoal, _duration, _coinPrice, _coinSupply, { from: owner, data: crowdsaleCompiled.Crowdsale.code, gas: 1000000 }, function(e, contract){ if(!e) { if(!contract.address) { console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined..."); } else { console.log("Contract mined! Address: " + contract.address); console.log(contract); } } }) ``` [⤒](#index) ## イベントを監視 (watch-event) ``` // 資金送信イベントの監視 var eventFundTransfer = crowdsale.FundTransfer({}, '', function(error, result) { if (!error) { if (result.args.isContribution) { // 資金提供のための送金の場合: console.log("\n New backer! Received " + web3.fromWei(result.args.amount, "ether") + " ether from " + result.args.backer) console.log("\n The current funding at " + (100 * crowdsale.amountRaised.call() / crowdsale.fundingGoal.call()) + "% of its goals. Funders have contributed a total of " + web3.fromWei(crowdsale.amountRaised.call(), "ether") + " ether."); var timeleft = Math.floor(Date.now() / 1000) - crowdsale.deadline(); if (timeleft > 3600) { console.log("Deadline has passed, " + Math.floor(timeleft / 3600) + " hours ago") } else if (timeleft > 0) { console.log("Deadline has passed, " + Math.floor(timeleft / 60) + " minutes ago") } else if (timeleft > -3600) { console.log(Math.floor(-1 * timeleft / 60) + " minutes until deadline") } else { console.log(Math.floor(-1 * timeleft / 3600) + " hours until deadline") } } else { // 資金提供以外の送金の場合(終了後の資金戻し or 受益者への資金渡し): console.log("Funds transferred from crowdsale account: " + web3.fromWei(result.args.amount, "ether") + " ether to " + result.args.backer) } } else { // Error occured! console.log("error:", error); } }); // コイン送信イベントの監視 var eventCoinTransfer = crowdsale.CoinTransfer({}, '', function(err, result){ console.log("Coin sent " + result.args.amount + " from: " + result.args.sender + " to: " + result.args.receiver); }); // デバッグログイベントの監視 var eventLog = crowdsale.Log({}, '', function(err, result){ console.log("[" + result.args.method + "] called."+ " sender: " + result.args.sender + " message: " + result.args.message); }); ``` [⤒](#index) ## 残高チェック(出資前) (check-balance-pre-fund) さて、これからいよいよクラウドセールへの資金提供を実施してみようと思いますが、ちょっとその前にまずはEtherと独自コインの残高をチェックしておきましょう。以下のコマンドで確認することができます: ``` // 各アドレスのEther残高 eth.accounts.forEach(function(addr){console.log(addr, web3.fromWei(eth.getBalance(addr),"ether"))}); // 各アドレスのコイン残高 crowdsale.coinBalanceOf(owner) crowdsale.coinBalanceOf(funderA) crowdsale.coinBalanceOf(funderB) ``` Ether残高: owner: 26.82955501999997 funderA: 28195.19171436 funderB: 84.97873062000003 コイン残高: owner: 100 funderA: 0 funderB: 0 [⤒](#index) ## 出資 (fund) それではいよいよ出資してみます。 funderA から 3 Ether 提供後、さらに funderB からも 7 Ether 提供し、パラメータで設定した調達目標の 5 Ether を達成させてみましょう: ``` // funderAからクラウドセールへ3Ether資金提供 web3.personal.unlockAccount(funderA, "
", 60000); eth.sendTransaction({from: funderA, to: crowdsale.address, value: web3.toWei(3, "ether"), gas: 1000000}) // funderBからクラウドセールへ7Ether資金提供し、調達目標に到達させる web3.personal.unlockAccount(funderB, "
", 60000); eth.sendTransaction({from: funderB, to: crowdsale.address, value: web3.toWei(7, "ether"), gas: 1000000}) ``` **Tips:** 資金提供(上記の`sendTransaction(...)`)を実行後、数秒後に採掘が完了すると、以下のようなログが出力されて現在の調達状況や残り時間などが表示されます: ``` New backer! Received 3 ether from 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 The current funding at 60% of its goals. Funders have contributed a total of 3 ether. 7 minutes until deadline Coin sent 3 from: 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 to: 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 [Payable anonymous function] called. sender: 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 message: [sendCoin] called. sender: 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 message: ``` [⤒](#index) ## 残高チェック(出資後) (check-balance-post-fund) 出資後の残高は以下のようになります。 Ether残高: owner: 26.82955501999997 <= まだ報酬を得ていないので変わらず funderA: 28267.1938289 <= 継続的にマイニング報酬を得ているアカウントなのでEtherは増加している funderB: 77.97661608000003 <= (出資分の7Ether + gas) 減少 コイン残高: owner: 90 <= 10減少 funderA: 3 <= 3コイン増加(3Ether出資の見返り) funderB: 7 <= 7コイン増加(7Ether出資の見返り) [⤒](#index) ## クラウドセール終了のトリガーを引く (trigger-end-crowdsale) 募集期限が満了後、コントラクトのfunction`checkGoalReached`を実行することで、このクラウドセールは終了となります。終了のトリガーは誰でも引くことができますが、目標達成した場合の調達資金の授受を行う場合はオーナーによる実行が必要となります: ``` // クラウドセール募集期限満了(開始から10分後)、オーナーがクラウドセール終了のトリガーを引く: crowdsale.checkGoalReached.sendTransaction({from: owner, gas: 2000000}) ``` #### 目標を達成していた場合は以下のようなログが表示されます: ``` > Funds transferred from crowdsale account: 10 ether to 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 [checkGoalReached] called. sender: 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 message: Fundraise succeeded! ``` 調達資金がオーナーに渡されたことが分かります。 #### 逆に目標未達だった場合は以下のようなログが表示されます: ``` > Funds transferred from crowdsale account: 3 ether to 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 Coin sent 3 from: 0x4a1c11eaec40197beb6e8607ee61953e7d6d8731 to: 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 [checkGoalReached] called. sender: 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 message: [sendCoin] called. sender: 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 message: [checkGoalReached] called. sender: 0x5855a19b64e1068a1edc39bb817647e16a1c57e7 message: Fundraise failed :( ``` 調達資金が出資者に返却されていることが分かります。 [⤒](#index) ## 終了後の残高チェック (check-balance-after-crowdsale) #### 目標を達成していた場合の残高: Ether残高: owner: 36.82800921999997 <= +10Ether(調達資金の10Etherゲット) funderA: 28447.1953747 funderB: 77.97661608000003 コイン残高: owner: 90 <= 合計10コイン振り出したので -10 コイン funderA: 3 <= 見返り受け取った3コインがこのまま自分のものに funderB: 7 <= 見返り受け取った7コインがこのまま自分のものに **Note:** 目標未達だった場合は、Etherが出資者に戻される一方で、出資者が一時的に保有していたコインはオーナーに返却されます。 [⤒](#index) ## 手に入れたコインを譲渡 (transfer-coin) 入手したコインは他者に譲渡することもできます: ``` // 3コインを funderA から funderB へ譲渡: crowdsale.sendCoin(funderA, funderB, 3, { from: funderA }) // funderAのコイン残高: crowdsale.coinBalanceOf(funderA) 0 <= -3コイン // funderBのコイン残高: crowdsale.coinBalanceOf(funderB) 10 <= +3コイン ``` [⤒](#index)
0 件のコメント:
コメントを投稿
次の投稿
前の投稿
ホーム
登録:
コメントの投稿 (Atom)
0 件のコメント:
コメントを投稿