🧟‍♀️

cryptozombiesでSolidityについて学ぶ

2025/02/24に公開

はじめに

以前「シリコンバレーのエンジニアはWeb3の未来に何を見るのか」を読み、その中で筆者がweb3アプリケーション開発のチュートリアルとしてcryptozombiesというサイトを用いていたので、私もcryptozombiesに実際に取り組んでみて、スマートコンストラクト開発のための言語であるSolidityについて学んでいこうと思います。

https://cryptozombies.io/jp/

Solidityとは?

まず、Solidityとは主にイーサリアムブロックチェーン上でスマートコントラクト(ブロックチェーン上にデプロイされたサービスに発生する契約の締結や売買等の全ての取引を人を使わず自動で行わせる仕組み)を実装するために設計されたプログラミング言語です。

書き方は全く同じというわけではないですが、Javascriptに似てるのでJavascriptに触れたことある人なら理解が早いと思います。

基本構文

では、cryptozombiesを通して学んだsolidityの基本構文についてまとめていきます。また、深掘りしたほうが良さそうだなと思ったところは公式ドキュメントを見て学びました。

https://docs.soliditylang.org/ja/latest/index.html

コントラクト

イーサリアムアプリケーションの基本ブロックのことです。

Solidityのコードはコントラクトの中に記載していきます。

contract HelloContract {
  // この中で実装していく
}

バージョンのプラグマ

Solidityのソースコードは全て pragma solditiy solidityのバージョンで始まります。Solidityのコンパイラのバージョンを宣言し、将来コンパイラのバージョンが原因でコードが破壊されることを防ぐために必要です。

※ pragma(プラグマ)はギリシャ語で行為、事実、問題などを意味するらしいです。

// Solidityのバージョンを指定 (2025/02/23時点で最新バージョンは0.8.29)
pragma solidity ^0.8.29;

contract HelloContract {

}

データ型

Solidityのデータ型は大きく分けて「値型」と「参照型」に分類されます。

1. 値型(Value Types)

値型は、スタック(stack)に格納され、コピーが渡される 型です。また、undefinednullの値の概念はありませんが、新しく宣言された変数は常にその型に依存したデフォルト値 を持ちます。

データ型 説明
bool true または false を保持 bool isActive = true;
uint 符号なし整数 (uint8 ~ uint256) uint256 count = 100;
int 符号あり整数 (int8 ~ int256) int256 balance = -50;
address イーサリアムのアドレス address owner = msg.sender;
address payable 送金可能なアドレス address payable recipient = payable(msg.sender);
bytes1 ~ bytes32 固定長のバイト配列 bytes32 data = "0xabc123";
enum 列挙型(複数の状態を定義) enum Status { Active, Inactive }

2. 参照型(Reference Types)

参照型は、ストレージ(ブロックチェーンに永続的に保存する場所)やメモリ(一時的な保存場所)に格納され、データのある場所(ストレージorメモリ)のアドレスを参照する型です。

データ型 説明
string 文字列型 string name = "Hello";
bytes 可変長のバイト配列 bytes data = "0xabcdef";
array 配列(固定長 or 可変長) uint[] numbers = [1, 2, 3];
mapping キーと値のペアを持つ連想配列 mapping(address => uint) balances;
struct 複数のデータをまとめた構造体 struct Person { string name; uint age; }

3. 特殊な型

Solidityには、特殊な型も存在します。

データ型 説明
function 関数型(関数を変数として格納) function example(uint) {}

型ごとの特徴

整数型

  • int (符号あり整数)、 uint(符号なし整数)がある。
  • またuintは、256ビットの符号なし整数であるuint256のエイリアスになる。
  • 整数型の演算子はJavascriptと大体同じ。
uint8 smallNumber = 255;
uint256 largeNumber = 1000000000000;
int256 negativeNumber = -100;

文字列型 (string)

  • 任意の長さのUTF-8データに使用。
  • 文字列を格納するが、直接比較はできない。
  • keccak256 という256ビットの16進数にマッピングするライブラリはあるのでそれで文字列の比較(keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))) や2つの文字列の連結(string.concat(s1, s2) )は可能

アドレス型

  • addressは20バイトの値(イーサリアムのアドレスのサイズ)を保持
  • address paybaleaddressと同じ機能がありますがaddress payableではイーサリアムの送受信が可能になる。
address myAddress = msg.sender;
address payable recipient = payable(myAddress);

配列型(Array)

  • 固定長配列と可変長配列がある
  • ストレージ / メモリーに格納可能
  • 要素追加はpushを使用
  • 配列の要素数はlengthで取得
// 可変長配列
uint[] public numbers; 

// 固定長配列
uint[5] public fixedArray; 

// 要素の追加はpushを使用する
numbers.push(10);

// 配列の要素数はlengthで取得
uint arrayLength = numbers.length;

マッピング型

  • キーと値のペア を管理
  • ストレージにのみ格納可能(メモリーでは使えない)

mapping(address => uint) public balances;

関数型

  • 内部(internal)関数と外部(external)関数の2種類がある。
  • グローバル変数や状態変数と区別するために関数の引数にはアンダースコア(_)をつけるのが慣例。
  • 関数の状態修飾子として、pure修飾子(コントラクトの状態を読み取らず、変更もしない)とview修飾子(コントラクトの状態変数にアクセスできるが変更はしない、読み取り専用)がある。
contract HelloContract {
    // pure修飾子の例
    function multiply(uint _a, uint _b) public pure returns (uint) {
        // 状態変数にアクセスしない
        return a * b; 
    }
    
    uint public num = 42;

    // view修飾子の例
    function getNum() public view returns (uint) {
        // 状態変数 numを読み取る
        return num;
    }
}

public / internal / private / external について

Solidityには4つのアクセス修飾子が存在します。

それぞれの特徴はこちらです。

public

  • どこからでも呼び出せる関数。(コントラクト内 / 他のコントラクト / 外部)
  • publicな状態変数を定義すると、自動的にゲッター関数が作成される。
contract HelloContract {
  // number() というゲッター関数が自動で生成される
  uint public number = 42;
}

internal

  • コントラクト内と継承先でのみ使用できる関数
  • 外部では呼び出せない
  • 状態変数のアクセス修飾子はデフォルトがinternal
contract Parent {
		// デフォルトで`internal` なので継承先で使える
    uint secretNumber = 777; 
}

contract Child is Parent {
    function getSecret() public view returns (uint) {
		    // 継承先でも使える
        return secretNumber; 
    }
}

private

  • コントラクト内だけでアクセス可能
  • 継承先のコンストラクトからはアクセスできない
contract Example {
    uint private mySecret = 1234;

    function getSecret() public view returns (uint) {
	      // コントラクト内ではOK
        return mySecret; 
    }
}

contract Child is Example {
    function tryAccessSecret() public view returns (uint) {
        // エラー! `private` なので継承先では使えない
        return mySecret; 
    }
}

external

  • コンストラクト外からのみ呼び出せる
  • 関数専用の修飾子であり、状態変数では使用できない
contract Example {
		// コンパイルエラー
    uint external myNumber = 42; 
}

それぞれの特徴をまとめた表になります。

アクセス修飾子 コントラクト内 継承先 外部(ウォレットや他のコントラクト) 特徴
public ⭕️ ⭕️ ⭕️ どこからでもアクセス可能
internal ⭕️ ⭕️ コントラクト内と継承先のみ使用可能
private ⭕️ コントラクト内のみ使用可能
external ⭕️ コントラクト外からのみ呼び出せる

型キャスト(型変換)

Solidityでは基本的な型の間での明示的なキャストが可能です。uint256uint8にキャストすることはできますが、その際のデータの損失には注意が必要です。逆にuint8uint256にキャストするのは問題ないです。

uint8 a = 5;
uint b = 6;
// a * b はuint8ではなくuintで返すから、エラーになる
uint8 c = a * b; 
// 正しく動作させるために、bをuint8に型キャストさせる(数値が大きすぎると失敗する可能性はある)
uint8 c = a * uint8(b); 

また、型キャストできないケースもあります。

型の不一致

  • uintaddressのような異なるカテゴリの型間では直接キャストできない(直接は無理ですがaddressuint160にキャストすることでuint型にはできるみたいです)
  • bool 型を数値型にキャストはできない
  • bytes 型やstring型のキャストにも制限がある

https://solidity-ja.readthedocs.io/ja/latest/types.html#index-34

イベント

イベントはスマートコントラクトがブロックチェーン上で発生した重要な状態変化やアクションを外部に通知するための仕組みです。アプリのフロントエンドを接続待ち状態にもでき、何が起きたかをリアルタイムで監視し、反映させることもできます。

イベントの使用方法

  1. イベントの宣言

イベントはeventキーワードを使ってスマートコントラクト内で宣言されます。引数の型はuintadderessstringなどSolidityのデータ型を使用します。

contract MyContract {
		// イベントを定義
		// from(送信者アドレス) to(受信者アドレス) amount(送金額)
    event Transfer(address indexed from, address indexed to, uint256 amount);
}
  1. イベントの発行

イベントを発行するにはemitキーワードを使ってイベントをトリガーします。emitを使うことによってイベントがブロックチェーンに書き込まれ、外部に通知されます。

contract MyContract {
    event Transfer(address indexed from, address indexed to, uint256 amount);

    function transfer(address to, uint256 amount) public {
        address from = msg.sender;
        
        // ロジック: 送金処理

        // イベントを発行
        // 送金者、受取人、送金額がログに記録される
        emit Transfer(from, to, amount);
    }
}
  1. イベントのログ

イベントは、ブロックチェーン上で「ログ」として記録され、外部のアプリケーションやウォレットがそれを監視することができます。ブロックチェーンに記録されたイベントは、トランザクションと一緒に記録されますが、ストレージに影響を与えることなく、検索可能なログとして残ります。

イベントの引数にindexedを入れることでログをフィルタリングしやすくなります。(最大3つまで)

// この場合fromとtoにインデックスがついているのでログ検索でフィルタリング可能になる
event Transfer(address indexed from, address indexed to, uint256 amount);

これで外部アプリケーションからweb3.jsというJavascriptライブラリを用いてSolidityのイベントを取得できるようになります。


const abi = /* abi */;
// コントラクトのアドレス
const contractAddress = '...'; 

const contract = web3.eth.Contract(abi, contractAddress);

// イベントの監視
contract.events.Transfer({
    fromBlock: 'latest'
}, (error, event) => {
    if (error) {
        console.error(error);
    } else {
        console.log('Transfer Event:', event);
    }
});

まとめ

cryptozombiesでサラッとパート1をやる分には1時間くらいで終わるのですが、気になったことを調べながら取り組むと思ったよりかなり時間がかかりました😇

何事も基礎は大事だと思うので雑にやらず、時間をかけてしっかり学んでいこうと思います。

パート1の内容を深掘りしただけでも結構な量になってしまったので次の記事でパート2以降の内容をまとめていきます。ここまで読んでいただきありがとうございました🙇🏽‍♀️

Discussion