Solidityの基礎をまとめてみた
EVMを使ってスマートコントラクトを組む際、プログラミング言語としてSolidityの知識は必須になります。そこで、Solidityの基礎を書きました。
1 いちばんさいしょに
まずsolidityを使うには、PCへの環境構築を行います。
これは各自調べてください。
また、環境構築が不要なツールもありますので、環境構築が面倒な方はRemixを使うといいでしょう。https://remix.ethereum.org/
またSolidityの書式はまず以下になります。
pragma solidity >=0.4.0 <0.6.0;
contract コントラクト名 {
処理内容
}
0.4.0や0.6.0はSolidityのバーションを表します。
2 値と演算について
値とは、数値や文章のことを言います。また、文章の時は" "で括ります。
(例) 123、0.32、"あうい" 、"thie is nemlog"
またプログラミングは本来、計算するためにあるものです。なので、四則演算を行う事ができます。これは、数字同士だけでなく、テキストと演算することもできます。
int x = 1 + 4 - 2 × 3;
int y = 'abc' + 123;
3 変数について
変数の宣言
変数とはいわゆる、値を入れるための箱のようなものです。
その変数はどのようなタイプ(型)であるのかを宣言する必要があります。そのため、以下のように定義します。
型名 修飾子 変数名 ;
修飾子の一つにmemoryとstorageあり、 memoryはEVM内で完結される一時変数、storageはブロックチェーンに 保管される変数です。
★基本的な変数の型の例
・整数型:int (整数を定義)
・固定小数点型:flixed (固定小数点を定義)
・アドレス型:adress (イーサリアムのアドレスを定義)
また値を変数に保管する際、以下のように記述します。
型名 修飾子 変数名 = 値;
ここで、=(イコールと呼ぶ)の意味は右辺を左辺に代入するということです。
つまり、Aを変数、Bを値とすると、A=B は「BをAに代入する」という意味になります。
数学でのイコールとは意味が違いますので、注意してください。
具体的な変数の型の説明
ブーリアン型 - bool
trueまたはfalseを代入するための変数です。
bool a = true;
bool b = false;
整数型 - int、unit
整数を代入するための変数であり、符号付きを定義するintと符号なしを定義するunitがあります。また、int8からunit256までの8ビット刻みで宣言します。(intとかunit単体で利用するときは、int256およびunit256になります)
int a;
unit8 b;
文字列型
文字列型は任意の長さの文字列を保存することができ、stringと書きます。
string str;
str = "hello Ethereum !";
固定小数点型
固定小数点を定義します。これは、fixedMxNまたはufixedMxNと宣言され、Mは8〜256までのビット単位のサイズ、Nはそのポイント以降の少数の数(最大18)です。
MとNを省略した場合はそれぞれ、ufixed128x19 とfixed128x19を意味します。
fixed a;
ufixed32x8 b;
アドレス型 - adress
イーサリアムのアドレスを保管できる型です。
adress y = 0x1d009CBBc2c5f336226a122D8d7d9baF7508407E ;
4 配列について
配列
プログラミングコードを書く際、変数をいくつも定義すると、コードが複雑になることがあります。そのときに役に立つのが配列です。配列は、複数の値をまとめて保管できる変数です。ここで定義した変数を配列名といい、以下のように記述します。
型名[index] 修飾子 配列名
uint[10] array ;
ここで、indexは配列の要素を表します。配列にはindex番号が割り振られていて、先頭より0、1、2と割り振られています。その定義した配列より値を表示したい場合は、以下のように記述します。
配列名[インデックス番号];
固定バイト配列
任意の値をなんでも保管でき、bytesNと表します。またNはサイズを表し、1~32まで指定できます。
bytes8 num = 1;
動的バイト配列
要素数が未定の場合は、サイズに応じて変えられるものがあります。
それは、bytesまたはbytes[]と表されます。
bytes raw;
マッピング
マッピングとは値の入った変数に名前を付けて、「名前」と「中身に入っている値」をセットで管理する方法です。
その名前をkeyといい、中身に入っている値をvalueといいます。
mapping(key_type => value_type) 配列名
これはkey_Type(キーの型)をキーとしたvalue_Type(値の型)の配列を意味します。
//addressをキーとしたuintの配列
mapping(address => uint) balance;
balance[0x00001234abcd] = 100;
この記述例は、addressを指定することで対応するuint型の値が取得できる配列を意味し、アドレスからハッシュ値を得るために用いることができます。
5 構造体と列挙体
構造体
構造体とは、複数の型のデータを一つにまとめたものであり、構造体を構成する要素をメンバといいます。
構造体の書式は以下のようにして表します。
struct 構造体タグ{
型 メンバ名;
型 メンバ名;
…
};
ここで、この構造体に付けられる名前を構造体タグといいます。
(例文)
struct nemlog {
uint8 age;
string name;
}
構造体を利用する際は、以下のようにして変数を用意します。
構造体タグ 変数名 ;
構造体の中にある使いたい変数を利用する時は以下のように書きます。
変数名.メンバ名 ;
struct nemlog {
uint8 age;
string name;
}
nemlog YUTO;
YUTO.age = 32;
列挙体
列挙体とはいくつかの定数をひとまとめにしたものであり、書式は以下のようにして表します。
enum 列挙体タグ{
定数1 ;
定数2 ;
…
};
(例文)
enum xymcity { GoLeft, GoRight, GoStraight, SitStill }
列挙体を利用する際は、以下のようにして変数を用意します。
列挙体タグ 変数名 ;
列挙体の中にある使いたい定数を利用する時は以下のように書きます。
変数名 = 列挙体タグ.定数 ;
enum xymcity { GoLeft, GoRight, GoStraight, SitStill }
xymcity hatchet;
hatchet = xymcity.GoStraight;
6 オブジェクト指向
配列にはindex番号が割り振られていて、先頭より0、1、2と割り振られています。その定義した配列より値を表示したい場合は、以下のように記述します。
配列名[インデックス番号];
ここで、インデックス番号の代わりに文字を使った場合の配列をオブジェクトといいます。オブジェクトの中には複数の情報(属性)を持ちます。その情報を使いたい場合は以下のように記述します。
オブジェクト名.属性名
msgオブジェクト
トランザクションまたはメッセージに関する属性が含まれています。
msg.sender : 直近の呼び出し元のアドレスを返します。
msg.value : 送られたイーサの量を返し、単位はweiであります。
msg.gas : 実行環境にて残っているガスの量を返します。
msg.data : msg の完全なバイナリデータを返します。
txオブジェクト
トランザクション関連の情報にアクセスする手段を提供します。
tx.gasprice : 呼び出し元のトランザクションでのガス価格を返します。
tx.origin : トランザクションの呼び出しアドレスを返します
blockオブジェクト
現在のブロックに関する情報が含まれています。block.coinbase (address): 現在のブロックの coinbase(
block.coinbase : 現在のブロックの料金とブロック報酬の受取人のアドレス(coinbase)を返します。
block.difficulty : 現在のブロックの難易度(difficulty)を返します。
block.gaslimit : 現在のブロックに含まれる全てのトランザクションで消費できるガス量の最大値を返します。
block.number : 現在のブロック高(ブロックチェーンの番号)を返します。
block.timestamp : 現在のブロックのタイムスタンプを返します。
addressオブジェクト
address.balance : アドレスの残高を返します。単位はweiです。
address.transfer(amount) : アドレスに金額(単位:wei)を送金します。エラーの場合は例外を投げます。
address.send(amount):アドレスに金額(単位:wei)を送金します。エラーの場合はfalseを返します。
7 関数について
関数とは、ある処理をひとまとめにして書いたものであり、入力された値と出力された値のパイプ役となるものです。これはSolidityにあらかじめ組み込まれた関数(組み込み関数)と、自分で定義する関数(ユーザー定義関数)があります。
組み込み関数について
組み込み関数は
関数の名前 (parameter types) [returns (return types)]
のようにして書きます。ここでは、どんな関数の名前があるのか紹介します。
addmod: (x + y) % k を計算します。
mulmod: (x * y) % k を計算します。
keccak256 : SHA3(kec- cak256) を計算します。
sha3 : keccak256のエイリアス。
sha256 : sha256 を計算します。
ecrecover : 署名からメッセージの署名に利用されたアドレスを返します。
selfdestruct(address recipient): 現在のコントラクトを削除し、アカウントにあるイーサの残高を受信者アドレスに送金します。
this : 現在実行中のコントラクトアカウントのアドレスを返します。
ユーザー定義関数について
関数を定義する場合は、基本的に以下のようになります。
function FunctionName (parameter types) {public|external|internal|private} [view|pure|payable]
[returns (return types)]
ここで
FunctionName:関数の名前。名前は自由に設定できます。
parameter types:関数に渡す引数。引数には型を指定します。
アクセス修飾子(public, private, internal, external)について
public(パブリック関数) : publicで定義された関数や変数は、コントラクト内からの呼び出しやコントラクト外部からの呼び出しにも対応します。
external(外部関数):コントラクト外部からの呼び出しのみに対応します。
internal(内部関数) : コントラクト内部からの呼び出のみに対応します。
private(プライベート関数) : 内部関数と機能が似ているが、継承されたコントラクトからの呼び出しには対応していません。
まとめ
コントラクト外部 | コントラクト内部 | 継承先 | |
public | ○ | ○ | ○ |
external | ○ | × | × |
internal | × | ○ | ○ |
private | × | ○ | × |
※継承については後ほど記述します。
状態装飾子について
view : 関数が動作してもデータの保存や変更は一切行わず、データの閲覧のみを行うと宣言します。
pure : 関数が動作してもデータの保存や変更を行わないだけでなく、読み取りすらも行わないことを宣言します。
payable : 送金を受け取る事ができる機能
[returns (<return types>)] について
処理内容には、戻り値を設定することがあります。戻り値とは関数内で処理された結果として得られた値です。つまり、出力値です。<return type>に型を宣言した引数を書きます。
戻り値を設定することで、処理内容に書かれた処理を終わらせ、そして呼び出し先の関数が出力値として値となります。
ここでごはんに例えると、ごはんを炊く前の米が引数、炊飯器が関数、炊いた米が戻り値になります。
コンストラクトについて
スマートコントラクトを以下の書式で書くとします。
contract コントラクト名 {
function 関数名( ) {
}
}
ここで、「コントラクト名 = 関数名」となった時の関数をコンストラクト関数といいます。
この時、以下のように記述する事でコンストラクト関数である事を明確にでき、エラーを少なくできます。
contract コントラクト名 {
function 関数名( ) {
constructor() {
}
}
}
つまり、constructor( ){ }を加えるという事です。
関数装飾子
modifier修飾子を関数につけると、修飾された関数を実行する前に、modifierの処理を予め実行させることができます。
modifier修飾子をつけた関数を関数装飾子と言い、以下の書式で定義します。
modifier 関数名{
modifierの処理部分;
_;
}
ここで、_; では、処理部分全体が終了したことを意味するので、必ず必要です。
(例文)
modifier onlyOwner {
require(msg.sender == owner);
_;
}
ここで、関数装飾子onlyOwnerを利用してdestroy関数を利用してみます。
function destroy() public onlyOwner {
selfdestruct(owner);
}
これは、destroy関数はonlyOwnerに修飾されている事を意味し、destroy()を実行する前にonlyOwnerを実行することになります。
8 コントラクトの継承について
Solidityではあるコントラクト(親コントラクト)の機能や変数を別のコントラクト(子コントラクト)へ引き継ぐ事ができ、これを継承といいます。
継承を利用するには、isを用いて親コントラクトを指定し、以下のようにします。
contract 子コントラクト is 親コントラクト {
処理内容
}
また、あるコントラクトAをコントラクトBを通してコントラクトCへ継承する事を多重継承といい、以下のように書きます。
contract C is A , B {
処理内容
}
つまり、isの後にコンマで区切ります。また、AとBの順番は逆にしてはいけません。
(例文)
contract Owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
<p>contract Destructible is Owned {
function destroy() virtual public {</p>
<p>if (msg.sender == owner) selfdestruct(owner);
}
}
contract Named is Owned, Destructible {
constructor(bytes32 name) public {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd
43E2f23970); NameReg(config.lookup(1)).register(name);
}</p>
9 イベントについて
Eventとは、ブロックチェーンで何かが生じたときに、web上やアプリ上に伝えることができる仕組みを言います。
function add(uint _x, uint _y) public {
uint result = _x + _y;
return result;
}
この関数の実行結果をweb上に伝える事ができないので、eventを使ってweb上と連絡を取ります。
まずは、この関数と関係ないところでevent IntegersAddedを宣言します。引数をx,y,resultとします。
event IntegersAdded(uint x, uint y, uint result);
先ほどの関数に一行IntegersAddedイベントを追記する事で。関数が呼ばれたことをアプリに伝えるためにイベントを発生させる事ができます。
function add(uint _x, uint _y) public {
uint result = _x + _y;
IntegersAdded(_x, _y, result);//ここでeventを入れる
return result;
}
10 制御文について
制御文とは、プログラムを実際に流すための文章を言います。ここでは、代表的なものであるif文とfor文について紹介します。
(1) if文
if文は条件分岐をする際の構文であり、条件に応じて実行する処理を変更します。
この文は、以下のように記述します。
if(条件式){
条件式を満たしているときに、この処理を実行する
} else{
条件式を満たしていないとき、この処理を実行する}
この例文は、変数xが10以上だと、命令aが実行され、そうではない場合は命令bが実行されます。
ここで、条件式を満たしていない処理を実行しない場合は、else以下の文を省略することができます。
ここで、比較演算子について説明します。ここではAとBを値として考えます。
A==B | AとBは等しい |
A != B | AとBは等しくない |
A < B | AはB未満である |
A <= B | AはB以下である |
A > B | AはBより大きい |
A >= B | AはB以上である |
これらの比較演算子をif文の条件式に書きます。
(2) for文
for文は繰り返し処理をする際に用いられる構文であり、以下のようにして記述されます。
for(初期値 ; 繰り返し処理をする条件 ; 処理した後の処理){
繰り返し処理をする内容
}
説明すると、まず初期値の所で変数と繰り返し処理をする際の一番最初の値を設定します。それから「繰り返し処理をする条件」に当てはめ、条件式を満たした場合に「繰り返し処理をする内容」を実行し、その次に「処理した後の処理」に入ります。それから「繰り返し処理する条件」にあてはめ、その条件を満たしたら、また「繰り返し処理をする内容」を実行し、その次に「処理した後の処理」に入ります。それを繰り返し条件をする条件を満たさなくなるまで継続します。
つまり、
初期値→繰り返し処理をする条件→繰り返し処理をする内容→処理した後の処理→繰り返し処理をする条件→繰り返し処理をする内容→処理した後の処理・・・
とループをしていくということです。