🏭

【Solidity 超入門】CryptoZombiesを通してdAppを作る 【part2】

2023/01/02に公開

Block Chainを勉強しようと志す人はスマートコントラクトの実装を避けては通れないと思います。solidityというプログラミング言語でスマートコントラクトを書くのがデファクトスタンダードですので、まずsolidiyを学習する方が多いのではないしょうか?
solidityを学習しようと思い、ググってみると大体「CryptoZombies」なるサービスを推している人や記事が多くヒットします。ですが実際にサイトを見ると英語やん...日本語もあまりちゃんと翻訳されないし、取っ付きづらいと思われる方も多いと思います。今回はCryptoZombiesの取っ付きづらさをこの記事で可能な限り払拭でき、solidity学習で躓いていた方の助けになればと思います。

前回の記事はこちら

対象読者

JavaScript等の他の言語でのプログラミングの経験がある方。
ですが、そこまで文法的には難しくないのでわからない用語があれば都度ググっていただければ読み進めることはできると思います。

solidityとは

solidityはイーサリアム上で動作するスマートコントラクトを実装する為の開発言語で、JavaScriptと似た性質を持った「コントラクト指向言語」と呼ばれるものです。

CryptoZombiesとは??

CryptoZombiesは、暗号からゾンビを生み出すゲームの開発を通じて、Solidityでスマートコントラクトの構築を学習できる、インタラクティブなオンラインレッスンです。

Making the Zombie Factory

ゾンビ兵士の工場を作りながら、solidityの基礎を学んでいきます。最終的に名前を受け取り、それを使ってランダムなゾンビの兵士を生成し、ブロックチェーン上のアプリのゾンビデータベースに生成したゾンビを追加する関数が完成します。

それでは続きから見ていきましょう。

Chapter5

このチャプターでは構造体についてみていきましょう。

Structs

solidityは構造体を提供しています。solidity以外の他の言語に触れたことのある方ならよく見るものと思います。C言語をはじめとして様々な言語で似たような仕様があります。構造体のおかげで複数のプロパティを持つより複雑なデータ型を作成することができます。

uint型のage、string型のnameというプロパティを持つPersonという構造体は以下で表せられます。

struct Person {
  uint age;
  string name;
}

Chapter5の答え

Making the Zombie Factoryではゾンビの兵士を作成するものですので、もちろん今回はゾンビの兵士を構造体として作ります。
ゾンビの兵士は複数のプロパティを持つので、構造体の最適な使用例ですね。
このZombie構造体は、string型のnameとuint型のdnaの2つのプロパティを持つZombieという名前の構造体を作成してみましょうというお題ですね。

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }
}

上記のようになります。

Chapter6

このチャプターでは配列についてみていきましょう。

配列

何かのコレクションが欲しいとき、配列を使用することができます。こう書くとわかりにくいですがデータといった塊を何個も格納できる箱というイメージがあれば大丈夫です。Solidity の配列には、固定長配列と**可変長配列(動的配列)**の2種類があります。

固定長配列

固定長配列は配列の数が固定のものです。ですので指定した要素数以上の要素を格納することはできません。固定長の2要素の配列は、

uint[2] fixedArray;

と書けます。

string型の固定長の5要素の配列は、

string[5] stringArray;

と書けます。

可変長配列(動的配列)

可変長配列は配列の数が可変なものです。ですので固定長配列とは異なり要素の数に制限はありません。

uint[] dynamicArray;

構造体の配列も作成できます。Chapter5のPerson構造体を配列で書いてみると

Person[] people;

のようになります。

Public Array

配列をpublicと宣言すると、solidityは自動的にその配列のgetterメソッドを作成します。

Person[] public people;

他のコントラクトは、この配列から読み取ることはできても、書き込むことはできません。コントラクトにパブリックデータを格納する時はこのようにPublic Arrayを使用しましょう。

Chapter6の答え

問題では、ゾンビの軍団を保存しましょう。そして、すべてのゾンビを他のアプリケーションでも見せびらかしたいので、公開設定にしましょうと言っています。
Zombie構造体のpublicな配列を作成し、zombiesと名付け定義しましょう。

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies; 
}

Chapter7

関数宣言について学ぶチャプターです。一部関連してデータ型についても言及しています。

関数宣言

solidityにおける関数宣言の書き方をみていきましょう。まず、ハンバーガーを食べるぞ関数(eatHamburgers)を作ってみます。functionを接頭につけ、次に関数名、そして引数の順に書きます。

function eatHamburgers(string memory _name, uint _amount) public {

}

このeatHamburgersは引数としてstring型のnameとuint型のamountを受け取れるようになっています。またこの関数はpublicです。呼び出す際は

eatHamburgers("vitalik", 100);

のように書きます。

変数の命名についてですが慣例としてグローバル変数と区別をつける為に関数の引数はアンダースコア(_)をつけることが多いそうです。それだけでなく上記の例で言うと _nameはデータの保存場所としてmemoryに保存することが明示的になっています。配列、構造体、マッピング、stringなどのすべての参照型にこの指定は必要になります。関連してデータ型についてみていきましょう。

データ型

カテゴリ 定義 意味
値型 bool 論理型
int 符号付き整数
uint 符合なし整数
string 文字列型
address アドレス専用のデータ型
bytes バイト配列
enum 列挙
function 関数
参照型 array 配列(固定・可変)
structs 構造体
その他 mapping マッピング

上記の表を見ていただければ分かる通り、データ型には値型と参照型があります。関数の引数はこのどちらかの方法で渡されることになります。値型と参照型についてみていくと、

  • 値型
    solidityの値型では、変数に指定した値が直接メモリ領域に確保され、値そのものが変数に代入されるということです。つまり、値によってsolidityコンパイラーは引数の値の新しいコピーを作成して、関数に渡します。これにより、関数は最初の引数の値が変更されることを気にせずに値を変更することができます。

  • 参照型
    「値を格納するメモリ上のアドレス」が変数に代入されます。なので関数が受け取った変数の値を変更した場合、元の変数の値も変更されます。また、参照型は、データの保存場所(Data location)という概念がある。

データ保存場所(Data location)

string memory _nameと指定していたように、変数のデータの保存場所はstoragememoryの2つが存在します。

  • storage
    コストは高いけど処理後、ブロックチェーンに永続的に保持することできる。

  • memory
    その関数が実行されてる時にしか状態が残らない揮発性のある変数。

保存先を明示しない場合どちらに保存されるのか、(calldataというデータ位置も存在する)実際に関数の中で変数を用いてデータ保存場所がどのように決まり、使われていくのかに関しても別記事で深堀たいなぁと思っていますが、一旦入門of入門という設定なので簡略します。

Chapter7の答え

説明が長くなってしまいましたが今回のチャプターはcreateZombieという名前のpublic関数を作成してねという問題です。この関数には name (string) と _dna (uint) の2つの引数を取るようにしてね。ちなみに最初の引数は、memory キーワードを使用して値で渡すことを忘れないでね、関数内は今は空白にしててね、と言うことらしいです。なので書いてみると、

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;
    function createZombie(string memory _name, uint _dna) public {}
}

になります。

Chapter8

構造体と配列の操作についてみていくチャプターです。

配列を初期化し、要素を追加していく

Chapter5で扱ったPerson構造体でみてみましょう。以下ですね。

struct Person {
  uint age;
  string name;
}

Person[] public people;

では、新しいPersonを作成してpeople配列に追加していきましょう。

// 新しいPersonを作成
Person jhcoder = Person(172, "Jhcoder");

// そのPersonをArrayに追加します。
people.push(jhcoder);

ワンライナーで書くと

people.push(Person(16, "Vitalik"));

と書けます。

array.push()は配列の末尾に要素を追加するので、追加した順番に要素が並んでいることに注意しましょう。

uint[] numbers;
numbers.push(5);
numbers.push(10);
numbers.push(15);

// 結果、numbersは[5, 10, 15]となる。

Chapter8の答え

新しいゾンビを作成し、zombies配列に作成したゾンビを追加してね。新しいゾンビの名前とDNAは、関数の引数から取得してね。これを1行のコードにまとめて、すっきり書いてね、ということなので

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }
    
    Zombie[] public zombies;
    
    function createZombie (string memory _name, uint _dna) public {
        zombies.push(Zombie(_name,_dna));
    }
}

となります。

part2の最後に

お疲れ様でした。今回はChapter5~8までをまとめてみました。なるべく「Making the Zombie Factory」は1記事にまとめたかったですが、分量が多くなりそうなのでpart分けしております。

References

https://cryptozombies.io
https://qiita.com/sho11hei12-1998/items/31ed7c5d4c2f34409223

Discussion