Solidity

0からのSolidity学習【CryptoZombies Lesson1】

2022年2月25日

手に職つけたい。これからの時代プログラミングスキルを身につけたい。

でもどの言語勉強したらいいかわからない。。。プログラミングスクールは高いし一歩が踏み出せない。。。

そんな風に考えたことありませんか??

そういう人に朗報です。今NFT,DeFi,Web3というワードをしょっちゅう耳にしますよね。

イーサリアムがもたらすスマートコントラクトによって新しいインターネットの時代が訪れようとしています。

そのイーサリアムブロックチェーン上でよく使われている言語がSolidity(ソリディティ)

まだSolidityをあつかえるエンジニアは少なく、プログラミングスクールでも取り扱っていない言語です。

どうやって勉強したらいいかわからない人が多いと思うので、

プログラミングの知識も経験もまったくの0のわたしがSolidityを独学で学んでいく過程を書いていきます。

今回は、プロゲートのSolidity版「CryptoZombies」レッスン1で学習していきます。

レッスン1を終えてできるようになること

・簡単なコントラクトが読めるようになる。

CryptoZombiesで遊びながらSolidityを学ぶ

レッスン1手順

ゾンビ軍団を生み出すためのコントラクト

コントラクトとは、イーサリアムアプリケーションのひとつの基本ブロック。

Solidityコードはコントラクト内にカプセル化して収まり、変数やファンクションもすべてコントラクトに属する。

ソースコードの始めには必ずコンパイラのバージョンを宣言する。

将来コンパイラのバージョンが原因でコードが壊れるのを防ぐため

pragma solidity ^0.4.19;

contract ZombieFactory {};
ひとし

一番最初に、このコントラクトを書いた時のバージョンの宣言を書くのね。

そしてコントラクトコードを開始。

16桁で決まるゾンビのDNA

状態変数はコントラクト内に永久に保存される。(イーサリアムブロックチェーン上に記載される。)

ゾンビのDNAは16桁の数字で決まる。

uint コマンドは、符号なし整数のデータ型。負数ではないということを示す。

int コマンドは、符号付き整数。

ゾンビのDNAをdnaDigitsという名前のuintで16桁で設定する。

注意

Solidityでは、 uintは256ビットの符号なし整数であるuint256のエイリアスです。 uint8、uint16、 uint32など、少ないビット数でuintを宣言することもできます。しかし、一般的には、後のレッスンで説明するような特定の場合を除いて、単にuintを使います。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
};
ひとし

コントラクトの中に、ゾンビのDNAは16桁でできてますよ。と書き込むのか。

ゾンビのDNAが16桁の数字だということを確認

Solidityでは他の言語同様に数式が使える。

  • 足し算 x + y
  • 引き算 x - y
  • 掛け算 x * y
  • 割り算 x / y
  • 余り x % y
  • 指数演算子(xの乗y) x ** y もしくは x^y

別のuintで10のdnaDigits乗と設定する。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
};

いろんな種類のゾンビを作るために構造体を使用する。

ゾンビ軍団には複数のゾンビが必要だ。

同じゾンビがたくさんいてもつまらないので、各々のゾンビのプロパティを変えて個性を出したい。

構造体を使えば、複数のプロパティを持つ複雑なデータ型を作成できる。

Zombieという名前のstruct(構造体)を作成

そのstruct(構造体)の中に2種類のプロパティを設定。名前 name(string)とDNA dna(uint)です。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }
};
ひとし

構造体の中にゾンビの特徴の設定を書き込むのね。

構造体の配列をつくり、ゾンビ軍団を公開する

コレクションをつくる時には、配列を利用する。

Solidityでは固定長配列と可変長配列の2種類があり、

固定長配列では決まった数を格納することができ、可変長配列ではいくらでも格納することができる。

パブリックの配列

配列をpublicで宣言するとSolidityがgetterメソッドを自動的に生成し、他のコントラクトからもこの配列を読めるようになる。

パブリックなZombie構造体の配列をつくり、配列の名前をzombiesとする。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;

};

ゾンビを何体もつくるために関数を使う。

ゾンビを何体もつくるので関数を使ってこれを実現する。

createZombieという名前の関数を作成し、関数にname(string)とdna(uint)の2つのパラメータを設定する。

ポイント

グローバル変数と区別をつけるために、関数パラメータの変数名にはアンダースコア(_)をつける。(必須ではない)

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  function createZombie(string_name, uint_dna){
  
  }

};

関数を実際に動かして新しいゾンビを作れるようにする。

新しいZombieを作れるように、先ほどの関数の中身を埋めてそれをzombies配列の中に格納する。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  function createZombie(string_name, uint_dna){
      zombies.push(Zombie(_name, _dna));
  }

};

関数をPublicからPrivateに変更する

Solidityでは関数はデフォルトでpublicになっている。

publicということは、誰でもコントラクトの関数を呼び出し実行できるということになり、悪意のある攻撃から脆弱になる。

公開してもかまわない関数だけpublicにして、自分が使う関数はprivateにしよう。

private関数を宣言すると、その関数はコントラクト内の他の関数からしか呼び出されない。

ポイント

関数のパラメータと同様、private関数にもアンダースコア(_)をつける。

createZombie関数もデフォルトでpublicになっているので、private関数に修正する。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  function _createZombie(string_name, uint_dna) private {
      zombies.push(Zombie(_name, _dna));
  }

};

文字列からランダムなDNA番号を生成するヘルパー関数を作る

Solidityでは関数の宣言に戻り値の型も含む。

関数の装飾子

view関数:データの読み取りはできるが編集はできない。

pure関数:アプリ内のデータにすらアクセスできず読み込むことはできない。戻り値が関数のパラメータにのみ依存する。

Solidityのコンパイラではどの関数でview/pureを使用するべきか警告してくれる。

_genarateRandomDnaという名前のpraivate関数を作成して、戻り値をuintに設定する。

この関数はコントラクトの変数を読み込むことはあるが、編集することはないので装飾子をviewとする。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  function _createZombie(string_name, uint_dna) private {
      zombies.push(Zombie(_name, _dna));
  }

  function _generateRandomDna(string _str) private view returns (uint){
  
  }

};

関数をランダムな値で返す設定

イーサリアムにはハッシュ関数SHA3のバージョンのひとつであるkeccak256)が組み込まれている。

ハッシュ関数は基本的に文字列をランダムな256ビットの16進数でマッピングする。

入力する文字列がほんの少し違うだけで戻り値がまったく別物になるので注意。

最初の行にkeccak256で_strのハッシュ値を所得して、疑似乱数16進数を生成。uintに型キャスト。さらにそれをrandというuintに格納する。

DNAは16桁にしたい。次の行で、dnaModulesによる剰余(%)をreturnするように設定する。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  function _createZombie(string_name, uint_dna) private {
      zombies.push(Zombie(_name, _dna));
  }

  function _generateRandomDna(string _str) private view returns (uint){
      uint rand = uint(keccak256(_str));
      return rand % dnaModules;
  }

};

すべてを結合するpublic関数を作成する

ゾンビの名前やユーザーの名前をインプットして、ランダムなDNAを返しゾンビを生成する。

createRandomZombieという名前のpublic関数を作成して、_name(string)というパラメータを設定する。

関数の最初の行で_nameで_generateRandomDnaを実行させてそれをrandDnaというuintに格納。

次の行で_createZombie関数を実行して、_nameとrandDnaを引数として渡す。

pragma solidity ^0.4.19;

contract ZombieFactory {
  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  function _createZombie(string_name, uint_dna) private {
      zombies.push(Zombie(_name, _dna));
  }

  function _generateRandomDna(string _str) private view returns (uint){
      uint rand = uint(keccak256(_str));
      return rand % dnaModules;
  }

  function createRandomZombie(string _name) public {
      uint randDna = _generateRandomDna(_name);
      _createZombie(_name, randDna);
  }

};

イベントの宣言

eventはブロックチェーンで何かが生じた時にコントラクトがフロントエンドに伝えることができる。

特定のイベントをlistening状態にして何かがあった時にアクションを起こすこともできる。

新しいゾンビを作るたびにそれをフロントエンドに伝えてアプリ上に表示させたい。

NewZombieという名前のeventを宣言して、ZombieId(uint)、name(string)、dna(uint)の値を渡す。

_createZombie関数を編集する。

array.push()は新しい長さのuint配列を返し、配列の最初のインデックスは0である。

なので、array.push()-1が追加したゾンビのインデックスとなる。

zombie.push()-1の結果をidというuintに格納する。

次の行でNewZombieというイベントで使用できるようにする。

pragma solidity ^0.4.19;

contract ZombieFactory {
  
  event NewZombie(uint ZombieId, string name, uint dna);  

  uint dnaDigits = 16;
  uint dnaModules = 10 ** dnaDigits;
  
  struct Zombie {
      string name;
      uint dna;
  }

  Zombie[] public zombies;
  
  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 % dnaModules;
  }

  function createRandomZombie(string _name) public {
      uint randDna = _generateRandomDna(_name);
      _createZombie(_name, randDna);
  }

};

Web3.js

ここまででSolidityのコントラクトが完成した。

イーサリアムにはWeb3.jsというJavaScriptライブラリがあり、コントラクトとやりとりする。

レッスン1ではサンプルコードの解説だけなのでこちらでは割愛。

今回作ったコントラクト、名前を入れたら16桁のDNAを返し個別のゾンビを作成する。

実際に自分の名前を入れてみるとどういう見た目になるのか。。。

https://share.cryptozombies.io/jp/lesson/1/share/Hitoshi

なんだかきもいぞ。。。ポニーテール具合。。。

まとめ

日本語対応はしているが、海外のサイトのためたまに日本語表記がおかしかったりして、

プログラミングの知識も経験も0のわたしにはレッスン1の段階でとても難しかったです。

記事にまとめながらやっているので、だいぶ時間はかかりましたが理解度と定着度は結構上がりました。

実際にひとつのブロックのコントラクトがどういう関数でどう返すのかというパターンが理解できました。

また次回レッスン2までしばしお待ちを!

-Solidity