Solidity

[Solidity 入門] データ型 徹底攻略!

この記事で分かること

  • Solidityの構成要素 (license identifier / pragma / import / comment / contract) など
  • Solidityのデータ型 (bool / int / uint / address / bytesN / enum) など
  • データ型の値型、参照型について

この記事では、Solidity言語入門と称してSolidityの基礎的な内容や、覚えておきたいテクニックなどを紹介しています。

Solidityとは、イーサリアムブロックチェーンの機能を拡張するために利用されているプログラミング言語で、スマートコントラクトと呼ばれる取引の契約書に当たるものを記述できる言語です。
例えば、NFTを譲渡した際に、誰にどれだけのETHを分配するかを予めプログラムで定義しておくような用途が考えられます。

今回の記事は、主にSolidityファイルの構成要素とSolidityで使われるデータ型、変数の説明になります。
SolidityはC++やJavaと同じ静的型付け言語であり、コンパイラでバイトコードに変換してから、EVM(Ethereum Virtual Machine)上で実行される言語です。

ブラウザベースのIDEである Remix IDE も無料で使えるように公開されているので、気になったらぜひ気軽に試してみて下さい!

ゴイチ

今後、ブロックチェーンやNFTが流行る可能性があるので、今のうちに勉強しておくのはアリかなと思います

Solidityファイルの構成要素

Solidityのソースコードの拡張子は、「.sol」です。

ソース ファイルには、任意の数のコントラクト定義インポート ディレクティブプラグマ ディレクティブ構造体列挙型関数エラー定数変数の定義を含めることができます。

ファイルのトップレベルに定義するものとして以下のものがあります。

  • SPDX ライセンス識別子
  • Pragma
  • Import
  • Comments
  • Contract

SPDX ライセンス識別子

Solidity コンパイラーは機械読み取り可能な SPDX ライセンス識別子を使用することを推奨しています。

すべてのソース ファイルは、そのライセンスを示すコメントで始まる必要があります。

記述例

// SPDX-License-Identifier: MIT

SPDXライセンス識別子の使用方法の詳細については、SPDXWebサイトを参照してください。

Pragma

pragma ディレクティブは、Solidityファイルに使用されるコンパイラのバージョンを指定します。

pragma ディレクティブは常にソース・ファイルに局所的なので、プロジェクト全体で有効化したい場合は、すべてのファイルに pragma を追加する必要があります。
他のファイルをインポートしても、そのファイルの pragma はインポート先のファイルには自動的に適用されません。

構文

pragma solidity <<version number>> ;

大文字と小文字は区別されます

記述例

pragma solidity 0.5.2
pragma solidity ^0.5.2;
pragma solidity >=0.8.2 < 0.9.0;
  • メジャー番号 / マイナー番号で構成されます(上記例では、5がメジャー番号、2がマイナー番号)
  • ^文字は キャレット とも呼ばれ、バージョン番号につけてもつけなくても構いません
  • ^文字はメジャー番号の最新を表します。したがって、^0.5.2は、メジャービルド番号5の中での最新バージョンを参照します
  • ^文字は指定されたメジャービルドとは別のメジャービルドを対象に含めません

Import

import キーワードは、他のSolidityファイルを利用可能にし、現在のSolidityファイルから外部のSolidityコードへアクセスすることができるようになります。
共通ライブラリなどをインポートする際に使うことができます。

構文

import <<filename>> ;

記述例

import "CommonLibrary.sol";

上記の記述方法は、予測不可能に名前空間を汚染するため、使用は推奨されません。
代わりに、以下のように特定のシンボルを明示的にインポートする方がよいでしょう。

import * as symbolName from "CommonLibrary.sol";
import "CommonLibrary.sol" as symbolName;

上記2つの書き方は同じ意味になります。
詳しくは、Layout of a Solidity Source File — Solidity 0.8.13 documentation (soliditylang.org) を参照してください。

Comments

コメントは以下の種類が使用可能です。

  • 単一行コメント(//
  • 複数行コメント(/*...*/
  • Natspecコメント(///)or (/** ... */

Natspecコメントとは、Ethereum Natural Specificationのことで、ドキュメント化を目的に使用されており、独自の使用が含まれます。
仕様の詳細は、NatSpec Format — Solidity 0.8.13 documentation (soliditylang.org) を参照してください。

記述例

// 単一行コメント

/*
複数行
コメント
*/

// ↓Natspecコメント
/// @title A simulator for trees
/// @author Larry A. Gardner
/// @notice You can use this contract for only the most basic simulation
/// @dev All function calls are currently implemented without side effects
/// @custom:experimental This is an experimental contract.
contract Tree {
    /// @notice Calculate tree age in years, rounded up, for live trees
    /// @dev The Alexandr N. Tetearing algorithm could increase precision
    /// @param rings The number of rings from dendrochronological sample
    /// @return Age in years, rounded up for partial years
    function age(uint256 rings) external virtual pure returns (uint256) {
        return rings + 1;
    }

    /// @notice Returns the amount of leaves the tree has.
    /// @dev Returns only a fixed number.
    function leaves() external virtual pure returns(uint256) {
        return 2;
    }
}

Contract

コントラクトとはSolidityの最も重要な概念で、主な目的はイーサリアムのスマートコントラクトを記述することです。

contract / library / interface などを定義することができますが、内容が多岐に渡るため別の記事で解説する予定です。
ここでは記述例のみを記載します。

記述例

pragma solidity 0.4.19;

contract firstContract {
}
contract secondContract {
}
library stringLibrary {
}
library mathLibrary {
}
interface IBank {
}
interface IAccount {
}

データ型

Solidityのデータ型は、大きく2つのタイプに分類されます。

  • 値型
  • 参照型

これらのデータ型は一般的なプログラミング言語の概念と同じものと考えて良いと思います。

値型

  • bool
  • int / uint
  • address
  • bytesN
  • enum

bool

ブール値 を表します。設定可能な値は定数truefalseです。
デフォルト値は false になります。

演算子:

  • !(論理否定、NOT)
  • &&(論理積、AND)
  • ||(論理和、OR)
  • ==(一致)
  • !=(不一致)

int / uint

整数値 を表します。 さまざまなサイズの符号付きおよび符号なし整数を表せます。
デフォルト値は 0 になります。
uint8 から uint256まで8bit単位で自由にサイズを決めることができます。
単に、int / uint と書いた場合は、それぞれ int256 / uint256と同じ意味になります。

演算子:

  • 比較演算子:<=<==!=>=> (bool値になる)
  • ビット演算子:&(論理積),|(論理和),^(ビット単位の排他的論理和),~(ビット単位の否定)
  • シフト演算子:<<(左シフト),>>(右シフト)
  • 算術演算子:+,-,*,/, 単項-(符号付き整数の場合のみ),%(剰余),**(べき乗)

type(X).minおよびtype(X).maxで、型で表すことができる最小値と最大値にアクセスできます。

address

アドレス型 を表します。アドレスタイプには2つの種類があり、ほとんど同じです。

  • address:20バイトの値(イーサリアムアドレスのサイズ)を保持します。
  • address payable:addressと同じだが、transferとsendというメンバーが追加されている。

演算子:

  • 比較演算子:<=<==!=>=>

bytesN

固定サイズのバイト配列 を表します。bytesNは値型とみなされます。
bytes1bytes2bytes3, …, bytes32は1バイトから最大32バイトまでのバイト列を保持します。

演算子:

  • 比較演算子:<=<==!=>=> (bool値になる)
  • ビット演算子:&(論理積),|(論理和),^(ビット単位の排他的論理和),~(ビット単位の否定)
  • シフト演算子:<<(左シフト),>>(右シフト)
  • インデックスアクセス:x がbytesN型として、x[k] (0 <= k < N) は kバイト目のバイト値を返します(読み取り専用)

enum

列挙型 を表します。
列挙型には少なくとも1つのメンバーが必要であり、宣言されたときのデフォルト値は最初のメンバーです。

列挙型には256を超えるメンバーを含めることはできません。

列挙型はすべての整数型との間で明示的に変換可能ですが、暗黙的な変換は許可されていません。
整数からの明示的な変換は、実行時に値が列挙型の範囲内にあることを確認し、そうでない場合は パニックエラーを引き起こします。

参照型

  • Arrays
  • Structs
  • Mappings

Arrays

配列型 を表します。

配列は、コンパイル時に固定サイズにすることも、動的サイズにすることもできます。
固定サイズ配列のサイズkと型Tは、T[k]と記述され、動的サイズの配列はとT[]記述されます。

固定配列

固定配列とは、宣言時にあらかじめ定義されたサイズの配列を指します。
固定配列のサンプルは次の通りです。

int[5] intArrays; // 5つのサイズが割り当てられたint型固定配列
byte[128] flags; // 128のサイズが割り当てられたbyte型固定配列

固定配列は newキーワードで初期化することができません。
以下のコードのように、インラインでのみ初期化ができます。

// 宣言時に初期化するパターン
int[5] intArrays = [int(10),20,30,40,50];

// 変数宣言と初期化を分けるパターン
int[5] intArrays;
intArrays = [int(10),20,30,40,50];

ポイント

配列初期化時に1要素目を int()でキャストしているのは、配列の要素の型を明示するためのようです。
型を明示しない場合、数値の配列の型は uint8 になるようです。(コンパイラ v0.8.7 で確認)

しかし、この辺の挙動はコンパイラのバージョンによって変わっていそうです。

動的配列

動的配列とは、宣言時にあらかじめ決められたサイズをもたない配列のことを指し、実行時にサイズが決定されます。
動的配列のサンプルは次の通りです。

int[] intArrays; // int型動的配列の宣言
byte[] flags; // byte型動的配列の宣言

動的配列は、インラインで初期化することも、new演算子を使用して初期化することもできます。

// 宣言時に初期化するパターン
int[] intArrays = [int(10),20,30,40,50,60,70,80,90,100];
int[] intArrays = new int[](10);

// 変数宣言と初期化を分けるパターン
int[] intArrays;
intArrays = new int[](10);
特殊配列 (bytes / string)

Solidityは、次の2つの特別な配列を宣言できます。

  • バイト配列 (bytes)
  • 文字列配列 (string)

byte[]bytes は同じではありません。
byte[] はバイトデータの配列(1要素は32バイト)、bytes は任意の長さのバイナリデータと捉えれば良いです。

bytes localBytes = new bytes(0); // 初期データ長を指定して状態変数として宣言する例
localBytes = new bytes(10); // 配列の割り当て
localBytes = "4engineer.net" // 文字列を代入する
localBytes.push(0x32); // 値の追加
localBytes.length; // 長さの取得
localBytes.length = 4; // 長さを4バイトに設定する

バイト配列 (bytes)の中で、特に文字列だけに限定したものが 文字列配列 (string) となります。

文字列はImmutableで、インデックスでのアクセスや文字の追加はできません。
長さ属性 (length)も持ちません。長さを取得する場合は、bytesに変換後に取得する必要があります。

stringは、シングルクォーテーションまたはダブルクォーテーションで囲まれた文字によって構成されます。

string name = "Goichi"; // 文字列の宣言
bytes bytesName = bytes(name); // バイト配列への変換

Structs

構造体 を表します。構造体は参照型に分類されます。

構造体は、異なるデータ型の複数の変数を単一の型にグルーピングできます。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

// Defines a new type with two fields.
// Declaring a struct outside of a contract allows
// it to be shared by multiple contracts.
// Here, this is not really needed.
struct Funder {
    address addr;
    uint amount;
}

contract CrowdFunding {
    // Structs can also be defined inside contracts, which makes them
    // visible only there and in derived contracts.
    struct Campaign {
        address payable beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID is return variable
        // We cannot use "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"
        // because the right hand side creates a memory-struct "Campaign" that contains a mapping.
        Campaign storage c = campaigns[campaignID];
        c.beneficiary = beneficiary;
        c.fundingGoal = goal;
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // Creates a new temporary memory struct, initialised with the given values
        // and copies it over to storage.
        // Note that you can also use Funder(msg.sender, msg.value) to initialise.
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

Mappings

マッピング型 を表します。
ハッシュテーブルや辞書型に似ており、他の言語で使用されているキーバリューペアに相当します。

マッピングは、mappingキーワードとそれに続くキーとバリューの両方のデータ型に =>表記で区切って宣言します。

contract MappingExample {
    mapping (uint => string) map;

    function put(uint key, string memory value) public {
        map[key] = value;
    }

    function get(uint key) public view returns(string memory) {
        return map[key];
    }
}

まとめ

最後に、この記事の内容をまとめます。

要点まとめ

  • Solidityのファイル構成要素は、SPDX license identifier / pragma / import / comment / contract である。
  • Solidityのデータ型は大きく値型と参照型の2つに分かれる。
  • 値型のデータ型は、bool / int / uint / address / bytesN / enum である。
  • 参照型のデータ型は、配列、構造体、マッピングなどがある。
ゴイチ

Solidityの構文自体はJavaScriptやC言語に近いと言われていて、割と自然な構文なので比較的理解はしやすいと思います。

今後もSolidityの入門記事を投稿していく予定です。よろしければ他の記事も読んで下さい。

ゴイチ

最後まで読んでくれてありがとうございました!

参考サイト

参考書籍

この記事は役に立ちましたか?

  • この記事を書いた人
アバター画像

ゴイチ

ソフトウェアエンジニア歴20年。 C/C++, C#, Java, Kotlinが得意で、組込系・スマホ・大規模なWebサービスなど幅広いプログラミング経験があります。 現在は某SNSの会社でWebエンジニアをしています。

-Solidity
-, , ,