Solidity入門1 <Pragmas, Contracts, Types>
Solidityは、スマートコントラクトを実装するのに利用されるオブジェクト指向言語です.
Solidityの文法や基本的な機能についてみていきます.
Pragmas
Solidityのソースコードは、ファイルの先頭に Version Pragma を書く必要があります.
Version Pragmaは、コンパイラーのバージョンを指定します.
コンパイラーのバージョンが 0.6.0以上 かつ 0.7.0未満 であること
pragma solidity >=0.6.0 <0.7.0;
コンパイラーのバージョンが 0.6.2以上 かつ 0.7.0未満 であること
pragma solidity ^0.6.2;
Contracts
Contract は、オブジェクト指向言語におけるクラスに似た仕組みです.
オブジェクト指向言語と同様に継承の機能を持ち、他のContractを継承することができます.
Wallet
を継承した SimpleWallet
を定義する
contract Wallet { } contract SimpleWallet is Wallet { }
Contractの中には、State Variables、Functions、Function Modifiers, Events, Errors, Struct Types, Enum Typesを定義することができます.
Constructor
Solidityでは、Contract内にただ一つコンストラクタを宣言することができます.
コンストラクタを宣言しない場合、デフォルトのコンストラクタ(何もしないコンストラクタ)があるとみなせます.
コンストラクタはコントラクト生成(デプロイ)時に、実行されます.
Wallet
の生成時にコンストラクタで minimumBalance
に初期値をセットする
contract Wallet { uint public minimumBalance; constructor(uint _minimumBalance) public { minimumBalance = _minimumBalance; } }
SimpleWallet
から Wallet
のコンストラクタを呼び出す
contract SimpleWallet is Wallet { constructor(uint _minimumBalance) Wallet(_minimumBalance + 10) public {} }
Value Types
Solidityで使用するValue型をいくつか抜粋してみてみましょう.
Booleans
true
, false
のどちらかの値をとります.
キーワード bool
を使用して、宣言します.
bool approved;
Integers
符号付き整数(int
)と符号なし整数(uint
)の2種類があります.
キーワード int
, uint
の後に、数値のビット数を示す数を付けて宣言します.
この値は、8の倍数となる 8 ~ 256 の範囲で選ぶことができます.
int64 rate; uint128 balance; uint score;
数値のビット数を省略することもできます.
int
, uint
はそれぞれ int256
, uint256
のエイリアス(同じデータ型)です.
Addresses
Addressには、address
, address payable
の2種類があります.
どちらもEthereumのアドレスと同じ長さである20バイトの値を保持します.
Addressはメンバを持っており、アカウントの残高を取得できます.
- balance
address
のアカウントの残高を返す(単位: wei)
コントラクトの残高を取得する
contract Wallet { function getBalance() external view returns (uint) { return address(this).balance; } }
- call(bytes memory) returns (bool, bytes memory)
ペイロードを引数にCALL
を実行し、 処理の成否を表す boolean値 と データ を返す.
全てのガスを転送する(転送するガスの量を指定することも可能).
address payable
のみ、以下のメンバを持っています.
transfer(uint256 amount)
address
のアカウントに、引数で指定した ETH (単位: wei) を送金する.
固定量のガスを転送する.
コントラクトアカウントの残高が不足していた場合、処理が中断され、変更がリバートされる.send(uint256 amount) returns (bool)
address
のアカウントに、引数で指定した ETH (単位: wei) を送金する.
固定量のガスを転送する.
コントラクトアカウントの残高が不足していた場合、送金処理が拒否され、false
を返す(処理が中断されない).
call
, transfer
, send
を使用した送金の例
(コントラクトアカウントから to
のアカウントに、100wei を送金する)
※ call
を使用した送金が推奨されています.
contract SendEther { address payable to = payable(0x123); function sendViaCall() external payable { (bool sent, bytes memory data) = to.call{value: 100}(""); require(sent, "Failed to send Ether"); } function sendViaTransfer() external payable { to.transfer(100); } function sendViaSend() external payable { bool sent = to.send(100); require(sent, "Failed to send Ether"); } }
他にもメンバを保持しており、以下のリンクから確認できます.
Units and Globally Available Variables — Solidity 0.8.17 documentation
Strings
文字列型は、ダブルクウォートもしくはシングルクオートで文字列を囲って記述します.
また、文字列リテラルは、bytes型に割り当てることも可能です.
string memory text1 = 'abc'; string memory text2 = "def"; bytes32 stringLiteral = 'ghq';
Solidityでは、他の言語と異なり、文字列を比較する機能がサポートされていません.
そのため、2つの文字列を比較したいときは、以下の手順を踏みます.
abi.encodePacked
を使用して、bytes型に変換する- 1で取得した値に
keccak256
を使用して、ハッシュ値を取得する - 2で取得した値を比較する
文字列の一致を判定する処理
function compare(string memory a, string memory b) public pure returns(bool) { return keccak256(abi.encodePacked(a)) == keccak256(encodePacked(b)); }
Enums
Enumsを使用して、独自のデータ型を定義できます.
Enumsは一つ以上のメンバーを含みます.
enum Seasons
を定義して、その変数に Winter
を代入
enum Seasons { Spring, Summer, Autumn, Winter }; Seasons season; season = Seasons.Winter;
Enumsのデフォルト値は、一番の左のメンバーとなります.
Reference Types
参照型は、Value型と異なり、異なる変数から同じデータを参照できます.
意図せず他の変数の値も変更してしまう恐れがあるので、注意深く扱う必要があります.
参照型には、Arrays, Structs, Mappingsがあります.
参照型の変数を宣言するときは、データの格納場所(Data location)も一緒に指定する必要があります.
Data locationには、以下の3種類があります
memory
外部関数(External function)を実行してる間、データが保持されるstorage
コントラクトが存在する間、データが保持される
コントラクトの直下に定義する変数は、Data locationを省略可能で、その場合storage
となるcalldata
関数の引数を保持する (変更が不可能)
Arrays
配列には、固定長のものと可変長のものがあります.
あるデータ型 T
に対して、T[]
のように定義します.
要素数が3の uint
の配列を生成
uint[] memory numbers = mew uint[](3);
Data locationがmemoryの場合、配列の生成後に、動的に要素を追加することができません.
配列の操作
uint[] storage numbers = new uint[](); // 配列に要素を追加 (配列が可変長の場合のみ、呼び出し可能) numbers.push(1); // 配列の末尾の要素を削除 (配列が可変長の場合のみ、呼び出し可能) // 返り値はなし numbers.pop(); // 配列の要素数を取得 (配列が固定長の場合のみ、固定値を返す) numbers.length;
Structs
任意のデータ型のメンバからなる、新しいデータ構造を定義できます.
配列やMappingsのデータ型にすることも可能です.
struct Person { string name; uint age; address addr; } // Personの配列を宣言 Person[] people;
Mappings
Mappingsは、キーとバリューのペアからなるデータ型です.
JavaでのMap, PythonでのDictに相当するものです.
mapping(KeyType => ValueType)
という書式で、宣言します.
キーがaddress
、バリューがuint
のmappingを宣言
mapping(address => uint) balances; address addr = payable(0x123); // キー・バリューのペアを追加 balances[addr] = 123; // キーからバリューを取得 balances[addr];
Mappingsを宣言するときは、Data locationにstorageを指定する必要があります.
存在しないキーで、バリューを取得しようとした場合、バリューのデータ型の初期値が返り値になります.
Mappingsは、他の言語と同様に、要素数を取得したり、キー・バリューのペアをイテレートすることができません.
なので、別途配列を用意して、キーをその配列に格納するなどの工夫が必要となります.