shinjuku-mokumoku会において試して学ぶ スマートコントラクト開発を読み進めていますが、チャプター2でCryptoZombiesによるSolidityの学習があったので、SryptoZombiesを進めました。
SolidityのLesson3までの内容になっています。
Lesson1
ここではSolidityの基本的な操作と関数、構造体の話をしています。
Solidityの文法はJavascriptに似ているので、Web系のことをやったことがあれば比較的とっつきやすいかもしれません。
以下にLesson1完了時点でのソースコードを載せて、適宜コメントを入れています。
pragma solidity ^0.4.19; //バージョン指定。教材作成時点での最新バージョンなため、現在の最新バージョンではありません。
contract ZombieFactory { //コンストラクトの定義。
event NewZombie(uint zombieId, string name, uint dna); // イベントの定義
uint dnaDigits = 16; // 256ビットの符号なし整数
uint dnaModulus = 10 ** dnaDigits; // 10^16(10の16乗)
struct Zombie { // 構造体
string name;
uint dna;
}
Zombie[] public zombies; // パブリックな配列。要素はZombie
function _createZombie(string _name, uint _dna) private { // プライベート関数
uint id = zombies.push(Zombie(_name, _dna)) - 1;
NewZombie(id, _name, _dna); // イベントの呼び出し
}
function _generateRandomDna(string _str) private view returns (uint) { // プライベート関数で返り値は整数型。読み取り専用。書き込まない
uint rand = uint(keccak256(_str));
return rand % dnaModulus;
}
function createRandomZombie(string _name) public { // パブリック関数
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}
イベントはフロントエンドを接続待ち状態にすることができます。JavaScriptで実装すると
YourContract.NewZombie(function(error, result) {
// なんか処理
}
と書くことができます。
Lesson2
餌(人間やらなんやら)を襲ってゾンビをつくる部分をより綿密に作る章です。
マッピング型とアドレス型
Addressは銀行口座のようなものを指します。
Mappingはデータを格納するときにSolidityで使える方法のひとつ。
Msg.sender
その関数を呼び出したユーザー(またはスマートコントラクト)のaddressを参照することができます
Require
require內部にある条件を満たさない場合はエラーを投げて実行を止めることができます。
function sayHiToVitalik(string _name) public returns (string) {
require(keccak256(_name) == keccak256("Vitalik"));
return "Hi!";
}
上の関数のとき、 _name
と "Vitalik"
が同値であるかどうかを判定し、同値でない場合はエラーを返します。(Solidityは単なる文字列で比較することができないので keccak256
を使用する)
継承
コントラクトは継承することができます。
contract Doge {
function catchphrase() public returns (string) {
return "So Wow CryptoDoge";
}
}
contract BabyDoge is Doge {
function anotherCatchphrase() public returns (string) {
return "Such Moon BabyDoge";
}
}
BabyDogeはDogから継承したもので、ビルドしてBabyDogeを実行すると anotherCatchPhrase()
にも catchphrase()
にもアクセスすることができます。
internal関数、external関数
internalはprivateと同じようなもの。ただ、このコントラクトから継承したコントラクトにもアクセスできるようになります。
externalは publicと同じようなもの。ただ、コントラクトの外からだけ呼び出すことができます。つまりコントラクト内部の別の関数では呼び出すことができません。
externalとpublicの使い分けは別途解説されるらしいです。
L3
Solidityのコンセプト的なお話で割と地味だけど重要な部分です。
コントラクトはイミュータブル
Ethereum上に一回デプロイすると、これ以降更新することができません。なにか問題があっても更新できないので、新たにデプロイしてそっちを使ってもらうように誘導するしhかありません。。
これはコントラクトがブロックチェーン上にデプロイされるので、もともとのチェーンに変更があってはならないためです。
https://docs.ethhub.io/questions-about-ethereum/is-ethereum-immutable/
Ownable
制限しないといつでもだれでもだれかのゾンビを作成することができますが、それは挙動としておかしいので修正する必要があります。。
オーナーシップを定義することでアドレスを制限し、ちゃんとそのアドレスと本人がマッチしているかをみる。
function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/ownership/Ownable.sol
このあたりのやつを使うとよいかもしれません。。
ガス
Solidityでは、ユーザーが関数を使用するたびに、ガスと呼ばれる通貨を支払うことになっています。ユーザーはEther(イーサと呼ぶ。イーサリアムの通貨)でガスを買い、アプリの関数を実行します。
関数を実行するために必要なガスの量は関数のロジックの複雑さよるので、複雑すぎるとめっちゃコストがかかってしまいます。このコストは上記にもあるとおり開発者ではなくユーザが払うので、変なことをさせてはいけません。
そもそもコストがかかる理由は、関数を実行する時にネットワーク上で必要になるすべてのノードで同じ関数が実行されて、出力が正しいことを検証するのでリソースをかなり食うことになります。例えば無限ループでリソース食いつぶされるとかあったらめっちゃ困ります。
ただの整数型だとコストに差はないけども、構造体の中にあるuintはコストが高いです。。節約するためには uint8やuint16を使っていったほうがよいですが、オーバーフローにも注意しなければ行けないので開発設計はかなり慎重にやったほうがよさそうです。。。
あとstorageへの書き込みはかなりコストが高いです。世界中の何千個というノードがすべてそのデータをハードドライブに書き込む必要があり、そのデータ容量はブロックチェーンが成長すればするほど大きくなる。。。
節約するためには絶対に必要な場合を除いてデータをstorageに書き込まないようにしましょう。そのため、一見非効率的なロジックを作ることもある。例えば、単純に配列を変数に保存するかわりに、関数を呼び出す毎にmemory上の配列を再構築するとか。
余談
考え方や概念がいままでの自分の経験(Ruby on RailsやJSを使用してのWebサービス開発など)になかったものがいろいろ登場してかなり面白かったです。近いうちに続きを進めます。
Top comments (0)