📖

Chapter3: Solidityの紹介 | Solidity Programming Essentialsを読む

2022/08/02に公開

イーサリアムの知識を整理するために2022年6月発売のSolidity Programming Essentials 2nd Editionを読み進める試みです。この記事ではChapter3「Solidityの紹介」を読み進めます。

Solidity Programming Essentials: A guide to building smart contracts and tokens using the widely used Solidity language, 2nd Edition (English Edition)

読書ログは以下のスクラップで逐次更新していきます。
https://zenn.dev/mah/scraps/ea8c79961ae8c8

Chapter 3:Solidityの紹介

Solidityの基本的な文法の紹介。全くSolidityを触ったことがない人は文法説明を読むよりCryptoZombiesを一通り触ることを推奨します。

  • Solidity and Solidity files
  • The structure of a contract
  • Data types in Solidity
  • ★Storage and memory data locations
  • Literals
  • Integers
  • Booleans
  • The byte data type
  • Arrays
  • The structure of an array
  • Enumeration
  • The address type
  • The mapping type

ざっと流し読みして★のところはおさえておきたいなと思ったので、それぞれ深掘りしていきます。

Storage and memory data locations

コントラクト内で宣言され使用される各変数にはデータロケーションがある。EVMでは以下の4つのデータロケーションを用意している。

  • Storage: コントラクト内の全ての関数で利用可能なグローバルであり恒久的なメモリ。ストレージに保存するとイーサリアムが全てのノードにデータを保存する。
  • Memory: コントラクト内の全ての関数で利用可能なローカルメモリ。関数が実行を完了したときに破棄される。
  • Calldata: 関数の引数を含む全ての実行データが格納される場所。変更不可能。
  • Stack: EVMはイーサリアム命令セットで作業するための変数と中間値をロードするためのスタックを維持する。スタックはEVM内で1024レベルの深さであり、これ以上のものを格納すると例外が発生する。

変数のデータ位置は以下の2つの要素に依存する。

  • 変数宣言の位置
  • 変数のデータ型

この2つの要素に基づき、変数のデータ位置を規定・決定するルールが存在する。

ルール1

ステート変数として宣言された変数は、常にStorageに格納される。

ルール2

関数パラメータとして宣言された変数はCalldataまたはMemoryに格納される。

ルール3

関数内で宣言された変数はデフォルトでMemoryに格納される。ただし以下の注意点がある。

  • 値型変数はMemoryに格納されるが、参照型変数の場合は明示的に宣言する必要がある。
  • 参照型変数はMemoryに格納することができる。参照型変数はArray(配列)、Struct(構造体)、String(文字列)。
  • Storageに格納される関数内で宣言された参照型変数は常にステート変数を指す必要がある。
  • 関数で宣言された値型変数はオーバーライドしてStorageに保存することはできない。
  • Mapping(マッピング)は常に状態変数として宣言される。つまり関数内でマッピングを宣言することはできない。またMemory型として宣言することもできない。しかし関数内のマッピングは状態変数として宣言されたマッピングを参照することができる。

ルール4

呼び出し元が関数パラメータに与える引数は、CalldataまたはMemoryに格納される。

ルール5

他のステート変数からステート変数に代入すると値が上書きされる。

uint stateVar1 = 20;
uint stateVar2 = 40;

function getVar() public returns(uint) {
  stateVar1 = stateVar2;
  stateVar2 = 50;
  return stateVar1; // 40が返る(stateVar2の参照ではない)
}

配列型のステート変数の場合でも値が上書きされる動きに変わりはない。

uint[2] stateArray1 = [uint(1), 2];
uint[2] stateArray2 = [uint(3), 4];

function getVar() public returns(uint) {
  stateArray1 = stateArray2;
  stateArray2[1] = 5;
  return stateArray1[1]; // 4が返る(stateArray2の値ではない)
}

ルール6

他のメモリ変数からストレージ変数への代入は常に新しいコピーを作成する。

uint[2] stateArray;

function getVar() public returns(uint) {
  uint[2] memory localArray = [uint(1), 2];
  stateArray = localArray;
  localArray[1] = 10;
  return stateArray[1]; // 2が返る(代入時点で新しいコピーが生成されている)
}

ルール7

他のステート変数からメモリ変数への代入は常に新しいコピーを作成する。

uint stateVar = 20;

function getVar() public returns(uint) {
  uint localVar = 40;
  localVar = stateVar;
  stateVar = 50;
  return localVar; // 20が返る(代入時点で新しいコピーが作成されている)
}

ルール8

参照型の他のメモリ変数からメモリ変数への代入は新しいコピーを作成しないが、値型の場合は新しいコピーを作成する。

function getVar() public returns(uint) {
  uint localVar1 = 40;
  uint localVar2 = 80;
  localVar1 = localVar2;
  localVar2 = 100;
  return localVar1; // 80が返る
}

参照型の場合はコピーが作成されない。

function getVar() public returns(uint) {
  uint[] memory someVar = new uint[](1)
  someVar[0] = 23;
  uint[] memory otherVar = someVar;
  someVar[0] = 45;
  return otherVar[0]; // 45が返る
}

Discussion