Ethoccer : Ethereum上で動く、ワールドカップ試合結果予測ゲームをSolidityとWeb3.jsで作る方法
はじめに
Ethoccerは、ブロックチェーン上で動くFIFA World Cup 2018 試合結果予測ゲームです。 EthoccerはRopsten Test Network上にデプロイされているため、ユーザーは無料で遊ぶことができます。 さらに、W杯終了時に成績が上位だったユーザーは、Main NetworkのETHを得ることができます。
- 無料で遊べる
- 賞金がもらえる
詳しくは、以下のページをご覧ください。
※ Main Networkとは”本物の”Ethereumが動作しているネットワークで、Test Networkは”開発者用の”Ethereumが動作しているネットワークを指します。Test Network上のETHは無料で手に入れることができ、開発者はこのネットワーク上にsmart contractをデプロイすることで、まずは無料で動作を確認してみることができます。
本記事はEthoccerのシステムを解説するもので、主にDApps開発者、特にこれから何かを作ってみようとしている開発者を対象としています。
また、本記事で紹介するのは主にSolidityのコードで、フロント部分については、この記事の反応も見つつ次回以降に紹介していければと考えています。
システム概要
- Solidity
Ethereum上で動くsmart contractを記述する言語。 - Truffle
solidityで書かれたsmart contractをテスト・デプロイするためのframework。 - Metamask
Ethereumのwalletで、chromeのアドオンとして実装されている。 - Trust
Ethereumのwalletとブラウザがセットになったもので、スマートフォンアプリとして実装されている。 - Web3.js
Javascriptからsmart contractにアクセスするためのLibrary。 - Ganache (not in above figure)
ローカル環境で動くテストネットワークとwalletがセットになったもので、開発効率を上げてくれる。
Solidityコード全文
コード解説
Contractの継承関係
contract StringHelpercontract MultiOwnable
contract Pausable is MultiOwnablecontract EthoccerType
contract EthoccerWeb3Interface is EthoccerTypecontract Ethoccer is EthoccerWeb3Interface, Pausable, StringHelper
StringHelper
Solidityはstringの比較ができないため、このコントラクトで、stringを使用する際に役立つ関数を定義しています。
MultiOwnable
Ethoccerコントラクトには、createGameやupdateScoresなど、ownerだけが実行できる関数があります。MultiOwnableには、そのための設定が記述されています。
このコントラクトは、OpenZeppelinのOwnableを、複数のownerを持てるように改良したものです。
Pausable
万一コントラクトにバグが発覚した場合、その対処のため、一時的にコントラクトを停止させる必要があると考えました。Pausableにはそのための設定が記述されています。
こちらも、OpenZeppelinのPausableを参考にしています。
EthoccerType
Ethoccerで使用するstructやenumを定義しています。
EthoccerWeb3Interface
このコントラクトには、Web3.jsを通じてアクセスできる関数・イベントがまとめてあります。
主な目的は、ブロックチェーンエンジニア以外がSolidityの実装を見ないでよくするためで、フロントエンジニアはここを見るだけで使用可能な関数を見つけることができます。
Ethoccer
ここまでに紹介した全てのコントラクトを継承している、Ethoccerのメインとなるコントラクトです。
Ethoccerコントラクトは内部にgamesとusersを持っていて、この2つのstateの値をEthoccerWeb3Interfaceで定義した関数で変化させることで動作しています。
// @dev Game
mapping(string => Game) internal games;
string[] internal gameList; // list of game IDs// @dev User
mapping(address => User) internal users;
address[] internal userList; // list of user addresses
GameとUserはEthoccerTypeで以下のように定義されています。
struct Game {
string id;
string country1;
string country2;
uint8 numberGoalCountry1;
uint8 numberGoalCountry2;
string time;
string group;
bool isOpen;
bool hasResult;
uint256[3] pointsPlaced;
uint256 betsLength;
mapping(uint256 => Bet) bets;
bool initiated;
}struct User {
uint256 point;
uint256 userElemsLength;
mapping (uint256 => UserElem) userElems;
bool initiated;
}struct UserElem {
string gameId;
uint256 betId;
}
- games
gamesには全ての試合情報が格納されています。要素であるGame structは、ownerがcreateGameを呼ぶことで追加されます。
gameがopenの状態では、ユーザーはgameに対してbet (vote) することができます。betされたポイントはpointsPlaced内に保存され、1つ1つのbetはbetsというmapping内に保存されます。
ownerがupdateScoresを呼びgameに試合結果を与えると、これらのデータを元に、ユーザーに配当ポイントが配布されます。 - users
usersには全てのユーザー情報が格納されています。要素であるUser structは、ユーザーが初めてEthoccerを使用したときに追加されます。このとき内部ではcreateUserが呼ばれています。
Userのpointの初期値は100で、betするたびにマイナスされ、配当ポイントを得たときにプラスされます。また、Userは自分がbetした全ての情報をuserElemsという形で保持しています。
まとめ
本記事では、Ethoccerのシステムについて紹介しました。
Ethoccerは実験的なプロジェクトであり、コードにはまだまだ改善の余地があります。
例えば、User structにbetの情報は持たせる必要はなさそうです。これは元々、ユーザーのaddressから全てのbet情報を返せるように実装したのですが、このように「getterのためだけの情報 (smart contract内部で参照する必要がない情報)」は、Eventとして実装したほうがgasの節約になります。過去の全てのEventは、web3.jsを用いると以下のように簡単に参照することができます。
getPlaceBetEvents() {
return new Promise((resolve, reject) => {
const events = this.instance.PlaceBetEvent({}, {fromBlock: 0, toBlock: 'latest'})
events.get((err, logs) => {
if (err) return reject(err)
return resolve(logs)
})
})
}
このように、まだまだ改善の余地がある一方で、我々はEthoccerの開発によって、DApps開発における多くのノウハウを獲得できたと実感しています。例えば以下のようなことは、実際に開発をしてユーザーのフィードバックを得ることで初めて感覚が掴めることだと思います。
- gasの減らし方
- pendingやRejectを考慮した実装
- データ移行やUpdateを考慮した実装
- 非DApssユーザーに向けたチュートリアル
今回、ユーザーからいただいたフィードバックや、ユーザーの動向を解析することで得られた知見は、これからの実証実験やサービスでDApps開発者コミュニティに還元していけたらと思います。
我々はDappsユーザーが増えることと同時に、DApps開発者が増えることも望んでいます。 この記事が少しでもみなさんの助けになれば幸いです。
Originally published at medium.com on July 4, 2018.