ちょっくらブロックチェーンなアプリケーションを作りたいなと思って勉強してみた。EthereumやNEMなどいくつか選択肢がある中、古参のEthereum先生に弟子入りすることにした。
ネットの情報がまだあまり充実していないのと体系的にまとまっているものがないため、この参考書をコツコツやった↓
環境構築まわりから丁寧に解説されているので一番オススメ
この本ではSolidityという言語を使ってEthereumブロックチェーン上のスマートコントラクトを開発する方法が紹介されている。
ちなみにこういうアプリはÐappっていう。このÐってどうやってタイプするんだろう。
Ðappを作ろうと思うと、コントラクトとそれにアクセスするためのインターフェース(Webサービスなりスマホアプリなり)を作らないといけない。当然のこと。コントラクト側の開発はビジネスのコア部分に集中できる。手を動かす前には見えなかったことが見えてきたりしてめちゃめちゃ良い。
この本に書かれているコントラクトを写経するだけでもSolidityひいてはEthereumへの理解が深まるけれど、自分で考えて開発してみると更に深く身になるだろうなってことでスマートコントラクト10本ノックをやってみた↓
Contents
絶対に勝てないじゃんけん
カイジに出てきそうな極悪じゃんけんコントラクト。勝たせまへんえ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
pragma solidity ^0.4.8; contract JankenYouMustLose { enum HandSigns { Goo, Choki, Par, Undefined } function jankenPon(HandSigns _handSign) public constant returns(HandSigns) { if (_handSign == HandSigns.Goo) { return HandSigns.Par; } else if (_handSign == HandSigns.Choki) { return HandSigns.Goo; } else if (_handSign == HandSigns.Par) { return HandSigns.Choki; } else { return HandSigns.Undefined; } } } |
短歌をコントラクトに「刻む」
ブロックチェーンを歌集と見立て、そこに短歌を刻めたら?というよく分からない発想で作ったのがこちら。_contentに短歌、_createdAtに歌を詠んだ日を渡す。Solidityには日付型がないため、タイムスタンプで代用した。
1 2 3 4 5 6 7 8 9 10 11 |
pragma solidity ^0.4.19; contract Tanka { string public content; uint public createdAt; function Tanka(string _content, uint _createdAt) public { content = _content; createdAt = _createdAt; } } |
おみくじ
これはもうそのままおみくじを引くコントラクト。同じおみくじが出ても面白くないので、おみくじを引くごとに結果が変わるようにしている。ところがSolidityで(というかEthereumで)乱数を作るのはかなり面倒だったので疑似的におみくじ結果がバラけるようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
pragma solidity ^0.4.8; contract OmikujiBox { string[] private omikujis; event OnOmikujiAdded(address indexed sender, string omikuji); function addOmikuji(string _omikuji) public { omikujis.push(_omikuji); OnOmikujiAdded(msg.sender, _omikuji); } function drawOmikuji() public constant returns(string _omikuji) { uint256 omikujiIndex = uint256(block.blockhash(block.number-1)) % omikujis.length; _omikuji = omikujis[omikujiIndex]; } } |
ボトルメール
ブロックチェーンを介してどこかの誰かに手紙をやり取りできたら素敵やなあということで作ってみた。
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 |
pragma solidity ^0.4.8; contract BottleMail { struct Mail { string senderName; string message; address sender; } Mail[] mails; event OnMailSent(address indexed sender, Mail mail); function sendMail(string _senderName, string _message) public { Mail memory mail = Mail(_senderName, _message, msg.sender); mails.push(mail); OnMailSent(msg.sender, mail); } function receiveMail() public constant returns(string senderName, string message, address sender) { uint256 mailIndex = uint256(block.blockhash(block.number-1)) % mails.length; Mail memory mail = mails[mailIndex]; senderName = mail.senderName; message = mail.message; sender = mail.sender; } } |
One Wallet
複数の取引所を使ったりとかしてると、自分のウォレットアドレスが沢山出来てしまう。それをひとまとめにしよう+しかもブロックチェーン上でというもの。
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 |
pragma solidity ^0.4.19; pragma experimental ABIEncoderV2; contract OneWallet { enum Coin { Bitcoin, Ethereum, Monacoin } struct Wallet { Coin coin; string label; string walletAddress; string url; } mapping (address => Wallet[]) private walletsPerUser; mapping (address => string) private passwords; event OnWalletCreated(address indexed sender); event OnWalletAdded(address indexed sender, Wallet wallet); function createWallet(string _password) public { if (!equal(passwords[msg.sender], '')) { revert(); } passwords[msg.sender] = _password; OnWalletCreated(msg.sender); } function addWallet(Coin _coin, string _label, string _walletAddress, string _url) public { Wallet memory wallet = Wallet(_coin, _label, _walletAddress, _url); walletsPerUser[msg.sender].push(wallet); OnWalletAdded(msg.sender, wallet); } function getWalletLength(string _password) public constant returns(uint) { if (!equal(passwords[msg.sender], _password)) { revert(); } return walletsPerUser[msg.sender].length; } function getWallet(uint _walletIndex, string _password) public constant returns(Coin, string, string, string) { if (!equal(passwords[msg.sender], _password)) { revert(); } Wallet memory wallet = walletsPerUser[msg.sender][_walletIndex]; return (wallet.coin, wallet.label, wallet.walletAddress, wallet.url); } function equal(string _a, string _b) private constant returns (bool) { bytes memory a = bytes(_a); bytes memory b = bytes(_b); if (a.length != b.length) { return false; } for (uint i = 0; i < a.length; i ++) { if (a[i] != b[i]) { return false; } } return true; } } |
CryptoDog
CryptoKittiesで頭おかしいんじゃないかという値段の取引があるのを見て、高値で売るために猫ちゃん飼ってるんじゃないかと思ったりした。怖い怖い。ペットがトークンのようなものになっていてめちゃめちゃ面白いけど、ちょっと残酷だよね。てなわけでこちらはブロックチェーン上でワンちゃんを育てるコントラクト。飯を食うとわんちゃんが歳取ります。
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 |
pragma solidity ^0.4.19; contract CryptoDog { enum DogBreed { Bulldog, Doberman } string public name; DogBreed public breed; uint public age; // コンテンツのURLはWebクライアントで管理して、uuidを返すだけにした方がいいかもしれない event OnFoodPresented(address indexed sender, uint age); function CryptoDog(string _name, DogBreed _breed) public { name = _name; breed = _breed; age = 0; } function presentFood() public { OnFoodPresented(msg.sender, ++age); } } |
所有権
デジタルデータからハッシュを取り出し、そのハッシュとオーナーのアドレスをこいつに渡して所有権を証明する。Solidityにはmodifierという機能があって、処理を行える人を限定できる。このコントラクトでは、所有権の譲渡をオーナーにしか出来ないよう、オーナー権限のmodifierを使っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
pragma solidity ^0.4.19; contract Ownership { address public owner; string public contentHash; modifier onlyOwner() { if (msg.sender != owner) { revert(); } _; } function Ownership(string _contentHash) public { contentHash = _contentHash; owner = msg.sender; } function changeOwner(address _newOwner) public onlyOwner { owner = _newOwner; } } |
クイズ
クイズに正解するごとにポイントを貰えるコントラクト。こういうゲーム的なコントラクトも面白い。
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.19; contract QuizGrandPrix { struct Quiz { string question; string answer; } mapping(string => Quiz) private quizMap; mapping(address => uint) public points; event OnQuizAdded(address indexed sender, string uuid); event OnAnswer(address indexed sender, bool isCorrect, uint point); function addQuiz(string _uuid, string _question, string _answer) public { Quiz memory newQuiz = Quiz(_question, _answer); quizMap[_uuid] = newQuiz; OnQuizAdded(msg.sender, _uuid); } function answer(string _uuid, string _answer) public { bool isCorrect = equal(quizMap[_uuid].answer, _answer); points[msg.sender] += isCorrect ? 999 : 0; OnAnswer(msg.sender, isCorrect, points[msg.sender]); } function getQuestion(string _uuid) public constant returns (string) { return quizMap[_uuid].question; } function equal(string _a, string _b) private constant returns (bool) { bytes memory a = bytes(_a); bytes memory b = bytes(_b); if (a.length != b.length) { return false; } for (uint i = 0; i < a.length; i ++) { if (a[i] != b[i]) { return false; } } return true; } } |
ポイントカード
購入する度にポイントがたまるコントラクト。購入処理は payable を付けたメソッドで行っている。payableをつけるとEthereumを受け付けられる。
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 |
pragma solidity ^0.4.14; contract PointCard { struct Item { string name; uint ethPrice; } string public cardName; string public pointName; uint public pointPerEthPrice; mapping(address => uint) private points; mapping(string => Item) private items; event OnItemAdded(address indexed sender, string itemName, uint itemEthPrice); event OnItemPurchased(address indexed sender, string itemName, uint point); function PointCard(string _cardName, string _pointName, uint _pointPerEthPrice) public { cardName = _cardName; pointName = _pointName; pointPerEthPrice = _pointPerEthPrice; } function addItem(string _itemUuid, string _itemName, uint _ethPrice) public { items[_itemUuid] = Item(_itemName, _ethPrice); OnItemAdded(msg.sender, items[_itemUuid].name, items[_itemUuid].ethPrice); } function purchaseItem(string _itemUuid) public payable { if (items[_itemUuid].ethPrice > msg.value) { revert(); } points[msg.sender] += items[_itemUuid].ethPrice * pointPerEthPrice; OnItemPurchased(msg.sender, items[_itemUuid].name, points[msg.sender]); } } |
読まれた回数で価格変わるコンテンツ売買
ブログやメディアなんかの記事の値段が読まれた回数で変わったら面白いな〜ということで作ってみた。
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 |
pragma solidity ^0.4.19; contract Content { string public uuid; string private title; string private url; uint private initialEthPrice; uint private buyCount; uint private ethPriceIncrementPerBuy; // コンテンツのURLはWebクライアントで管理して、uuidを返すだけにした方がいいかもしれない event OnContentPurchased(address indexed sender, string uuid, string title, string url); function Content( string _uuid, string _title, string _url, uint _initialEthPrice, uint _ethPriceIncrementPerBuy ) public { uuid = _uuid; title = _title; url = _url; initialEthPrice = _initialEthPrice * 1 ether; buyCount = 0; ethPriceIncrementPerBuy = _ethPriceIncrementPerBuy * 1 ether; } function getEthPrice() public constant returns(uint) { return initialEthPrice + buyCount * ethPriceIncrementPerBuy; } function () public payable { if (msg.value < getEthPrice()) { revert(); } ++buyCount; OnContentPurchased(msg.sender, uuid, title, url); } } |