初心者が一からブロックチェーンを利用したアプリケーション(Dapps)を作ってみる(②コントラクト基礎編)
他の記事
第一回 環境構築編
第三回 コントラクト中級編
第四回 セキュリティ、ガスなど編
第五回 ERC721トークン、セキュリティ続き編
前回、自分のPC上でTruffleの開発用ブロックチェーンを動かし、そこに用意されたアカウントの確認まで行いました。
そこで、今回は簡単なコントラクトを試しに作成し、Truffleの開発用ブロックチェーンにデプロイするところまでやってみたいと思います。
Solidityを使ってイーサリアムのコントラクトを作るには、何通りかの方法があります。手軽な方法の一つに、ブラウザで動くSolidityのIDE(統合開発環境)があります。→今回は使用しません。
Remixでは、コーディング、コントラクトのコンパイル、コントラクトのデプロイ(migrate)を全てブラウザ上のGUIで実行することができます。
また別の方法として、自分の好きなテキストエディタでコーディングし、Truffleを使ってコンパイルし、開発用ブロックチェーンにデプロイ(migrate)することもできます。今回はIDEを使用しない方法を紹介します。
目次
①バージョン指定
②コントラクトの定義
③状態変数
④数式演算
⑤構造体
⑥配列
⑦関数
⑧状態変数と関数の可視性
⑨イベント
⑩コントラクトのデプロイ
では実際にコントラクトを作って行きましょう。この記事ではポ○モンのような対戦ゲームの仕組みを作ります。まず、ゲームの最初には相棒が欲しくなりますよね。相棒を作成するコントラクトを書いて行きましょう。
前回、truffle initした時にこのようなディレクトリ構成ができたと思います。この中のcontractsのディレクトリに先ほどのコントラクトコードのファイルを保存します。(拡張子は.solです)
// cryptomongenerator.sol
pragma solidity ^0.4.19; // ①バージョン指定
contract CryptomonGenerator { // ②コントラクトの定義
event NewMonster(uint monsterId, string name, uint dna); // ⑨イベント
uint dnaDigits = 18; // ③状態変数
uint dnaModulus = 10 ** dnaDigits; // ④数式演算
struct Monster { // ⑤構造体
string name;
uint dna;
}
Monster[] public monsters; // ⑥配列
mapping (uint => address) public monsterToOwner;
mapping (address => uint) ownerMonsterCount;
function _createMonster(string _name, uint _dna) internal { // ⑧状態変数と関数の可視性
uint id = monsters.push(Monster(_name, _dna)) - 1;
monsterToOwner[id] = msg.sender;
ownerMonsterCount[msg.sender]++;
NewMonster(id, _name, _dna);
}
function _generateRandomDna(string _str) private view returns (uint) { // ⑦関数
uint rand = uint(keccak256(_str));
return rand % dnaModulus; // ④数式演算
}
function createRandomMonster(string _name) public {
require(ownerMonsterCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createMonster(_name, randDna);
}
}
上のコードが最初の相棒を作成するコントラクトです。
①バージョン指定
pragma solidity ^0.4.19;
これはsolidityのコンパイラのバージョンを指定する記述です。solidityはまだ開発途上なので、将来どんな変更があるかわかりません。solidityのバージョンアップでコントラクトが動かなくなることを防ぐため記述します。
②コントラクトの定義
contract CryptomonGenerator {
}
これが相棒を作るコントラクトの外枠になります。
この記述でコントラクトを定義します。ブロックチェーンにはコントラクト単位でアプリをデプロイすることになります。
③状態変数
uint dnaDigits = 18;
相棒のモンスターの特徴は18桁の16進数をdnaとして表すことにしました。最初の3桁が属性、次が頭の形、、みたいな感じです。
上のように変数を定義することができます。このように定義された変数は状態変数(state variable)といい、ブロックチェーンの中の(Ethereum Virtual Machineの中の?)コントラクトストレージの中に永久に保存されます。↓参考:公式ドキュメント
状態変数の中にデータを入れ、データベースのように使うこともできます。(今回のコードでもそうしています)
また、SolidityはJavaScriptに似ていますが、静的型付け言語なので変数の宣言の際に型も宣言しなければいけません。今回は256bitの符号なし整数であるuintを使用しています。↓参考:公式ドキュメント
④数式演算
uint dnaModulus = 10 ** dnaDigits;
return rand % dnaModulus;
ここでは文字列から作ったハッシュ(rand)をdnaと同じ18桁に整形しています。
Solidityでも他のプログラミング言語と同じように数式演算ができます。
この場合、dnaDigitsが18なのでdnaModulusは10^18です。randは18桁以上のuintなので、10^18で割った余りの18桁のuintを返しています。
↓参考:公式ドキュメント
⑤構造体
struct Monster {
string name;
uint dna;
}
相棒のモンスターの情報を構造体として定義しています。
複雑なデータ型を定義するために、Solidityには構造体が用意されています。ここでは名前とDNAを持つMonsterという構造体を定義しています。
⑥配列
Monster[] public monsters;
ここではmonstersという配列を定義しています。複数のユーザーが作ったモンスターたちをこの配列に格納します。Solidityで配列を宣言する場合はこのように宣言することができます。
// 2要素の固定長の配列
uint[2] fixedArray;
// 文字列の配列
string[5] stringArray;
// 可変長配列
uint[] dynamicArray;
また、構造体の配列を作ることもできます。上のコードではMonsterの構造体の配列を作っています。
⑦関数
function _generateRandomDna(string _str) private view returns (uint) {
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}
ここでは相棒のdnaを名前から生成しています。
このように関数を定義することができます。関数の引数を定義するときも型の宣言を忘れないようにしてください。また、返り値にも型の宣言が必要です。
先ほど状態変数の説明をしましたが、関数内で使用する変数は初期状態では状態変数に含まれないため、コントラクトストレージには保存されません。
関数名の最初がアンダースコア( _ )で始まっているのは、関数の引数やprivate関数などはアンダースコアから始めるという慣例によるものです。
⑧状態変数と関数の可視性
これまでのコードにprivateだとかviewだとか書いてあるのに気づいた人もいるかもしれません。これらは変数と関数につけることができます。(view、pureは関数のみ、constatntは変数のみ)
external:外部のコントラクトやトランザクションから呼び出される
public:コントラクト内部やメッセージ経由で外部から呼び出される
internal:コントラクト内部やそのコントラクトから派生したコントラクトから呼び出される
private:コントラクト内部からのみ呼び出される
constant:変更できない変数を宣言する
view:状態変数を参照するだけで変更、書き込みしない関数を宣言する
pure:状態変数などの参照すらしない、引数のみに依存した関数を宣言する
⑨イベント
event NewMonster(uint monsterId, string name, uint dna); // イベントの定義
・
・
・
NewMonster(id, _name, _dna); // イベントの呼び出し
この記述でイベントを定義します。イベントとは、コントラクトが実行されたことやその結果などをアプリなどに通知するためのものです。
...ここまででSolidityの基礎を説明してきました。一つの記事でたくさん説明しても読みづらくなるだけなので、一番上のコードの全ては説明していませんが、試しにTruffleの開発用チェーンにこのコントラクトをデプロイしてみましょう。
⑩コントラクトのデプロイ
// 2_deploy_cryptomon_generator.js
var NoteDapp = artifacts.require("cryptomongenerator");
module.exports = function(deployer) {
deployer.deploy(NoteDapp);
}
次に、migrate(デプロイ)のためのマイグレーションファイルを記述します。マイグレーションファイルはSolidityではなく、JavaScriptで記述します。 artifacts.require("cryptomongenerator"); のところはデプロイしたいコントラクトの名称を入れてください。
マイグレーションファイルの命名規則は、番号から開始し、アンダースコア区切りで作業内容、コントラクト名称の順に入れるのが一般的です。
このファイルをmigrationsディレクトリに保存してください。
その次に、Truffleの対話式コンソールを起動するコマンドTruffle developを実行し、コンソールに入ります。
truffle(develop)> compile
最初にcompileコマンドを実行すると、コードが評価され、エラーがなければ無事コントラクトはコンパイルされます。
truffle(develop)> migrate
次にmigrateコマンドを実行すると、マイグレーションファイルに問題ない限りマイグレーションが実行され、開発用チェーンにコントラクトがデプロイされます。
デプロイされるとこのようにコントラクトアドレスなどが表示されます。コントラクト名の右のコントラクトアドレスをコピーし、
truffle(develop)> generator = CryptomonGenerator.at("0xecfcab0a285d3380e488a39b4bb21e777f8a4eac")
こうするとgeneratorという変数を使ってコントラクトにアクセスすることができるようになります。
試しに相棒を作ってみましょう。
truffle(develop)> generator.createRandomMonster('calano');
truffle(develop)> generator.monsters(0);
[ 'calano',
BigNumber { s: 1, e: 17, c: [ 4405, 74662455189600 ] } ]
名前を入れて相棒を作り、相棒が入っている状態変数にアクセスすることができました。nameが'calano', dnaがBigNumber { s: 1, e: 17, c: [ 4405, 74662455189600 ] }です。BigNumberは大きな数字を安全に扱うためのJavaScriptのライブラリです。少しの値の違いもブロックチェーンにおいては大きな問題となるので、コンソールで使われているweb3.jsはBigNumberを使用しています。この場合、dnaは440574662455189600です。
次回はもう少し高度なコントラクトの文法を紹介していこうと思います。
書いているのは初心者のため、間違い、浅い理解などあるかもしれません。その場合はぜひ指摘してもらえればと思います。喜んで修正します。
参考にしたもの:
↑ゲーム感覚でスマートコントラクトの書き方が学べます。
↑ブロックチェーンで開発する際必要なことはだいたい網羅されていると思います。とても勉強になります。
この記事を書いた人:
株式会社Calano
インターン 島村大
学部4年生で大学では機械学習と数理最適化、CalanoではアプリやGCPなどを担当。最近ブロックチェーン始めました。