Ctypto Zombieで始めるSolidity入門③
この記事は前回の続きです。
前回の記事はこちら↓
chanichiwasshoi.hateblo.jp
このシリーズではCrypto ZombieというSolidityの学習が出来るサイト(しかも無料で)の内容を自分なりにまとめます。
入門編はこれ一つで全部学べるみたいなので、みなさんもぜひやってみてください!
cryptozombies.io
コントラクトの不変性
Solidityが普通のプログラミング言語と違うのが、コントラクトを一度イーサリアム上にデプロイするとコードは永遠にブロックチェーン上に残り続け、編集も更新もできなくなる。
これをコントラクトの不変性という。
しかしこれではあまりに不便なので、大抵は最初にデプロイするプログラムに中身を編集できるようなプログラムを埋め込む。
例えば、他のコントラクトを実行するようなプログラムを作ったとき、参照するコントラクトのアドレスとABIを変数で持っておく。参照するコントラクトを変更したくなったらその変数を変更できるようにしておけば良い。
fucntion setContract(address _address) external { contract = SomeInterface(_address); }
このようなプログラムを作っておけばコントラクトを変更できる。
関数修飾子
先程のプログラムを見るとある欠陥が見つかる。それは、この関数がexternalであるためだれでもコントラクトを変更することが出来ることである。それを解決するための一つの方法に関数修飾子を使う方法がある。
関数修飾子とは関数実行時に実行される関数のようなものである。modifierを使って定義する。
uint owner = "19fa2891389b..."; modifier myModifier() { require(msg.sender == owner); _; } function hoge() external myModifier {}
このように書くことでhogeが実行される前にメッセージを送った人のコントラクトが、オーナー変数のコントラクトと等しいかどうか確認することが出来る。オーナー変数に予め自分のコントラクトを入れておけば、自分以外の人にプログラムを攻撃されてしまう心配がない。
引数
関数拡張子は普通の関数と同じように引数を受け取れる。
modifier olderThan(uint _age, uint _userId) { require (age[_userId] >= _age); _; } function driveCar(uint _userId) public olderThan(16, _userId) {}
時間を使う
Solidity内で時間を使いたい場合は以下のように出来る。
function updateTimestamp() public { lastUpdated = now; } function fiveMinutesHavePassed() public view returns (bool) { return (now >= (lastUpdated + 5 minutes)); }
Crypto Zombieで始めるSolidity入門②
この記事は前回の続きです。
前回の記事はこちら↓
chanichiwasshoi.hateblo.jp
このシリーズではCrypto ZombieというSolidityの学習が出来るサイト(しかも無料で)の内容を自分なりにまとめます。
入門編はこれ一つで全部学べるみたいなので、みなさんもぜひやってみてください!
cryptozombies.io
新たな型
今回、addressとmappingという新しい型が登場します。
addressはEthereumアカウントのアドレスを表す型で、42桁の16進数で表されます。
また、mappingとはキーバリューストアにデータを格納するときのキーとバリューの型を指定します。
// キーがuint型で、値がaddress型のデータ mapping (uint => string) userIdToName; // userIdToNameのキーが0の場所に値"user1"を入れる。 userIdToName[0] = "user1";
msg.sender
Solidityにはすべての関数内で利用できるグローバル変数が用意されており、msg.senderもその一つです。
msg.senderにはそのスマートコントラクトを呼出したユーザーのaddressが格納されています。
アドレス「0x6079da09E8bfDFb3032eE0aD6f35Eb3e9aa506B6」を持つ太郎さんがこのコントラクトを呼出したとき、msg.senderには「0x6079da09E8bfDFb3032eE0aD6f35Eb3e9aa506B6」という値が入ります。
mapping (address => uint) favoriteNumber; function setMyNumber(uint _myNumber) public { favoriteNumber[msg.sender] = _myNumber; } function whatIsMyNumber() public view returns (uint) { return favoriteNumber[msg.sender]; }
Require
requireを使うと、ある条件を満たしていないときにエラーを起こしてプログラムの実行を止めることが出来る。
今回はユーザー名を登録する関数を作り、そのアドレスにすでに他の名前が登録済みだった場合エラーを起こす処理をします。
mapping (address => string) userName; function setAddress(string _name) public { require(userName[msg.sender] == 0); userName[msg.sender] = _name; }
継承
Solidityのcontractには継承機能があります。
あるコントラクトが他のコントラクトを継承すると、継承したコントラクトでは継承元のコントラクトのpublic関数やpublicな値にアクセスすることができます。
contract Doge { function catchphrase() public returns (string) { return "So Wow CryptoDoge"; } } contract BabyDoge is Doge { function anotherCatchphrase() public returns (string) { return "Such Moon BabyDoge"; } }
Import
別ファイルからコントラクトや値を読み込みたい場合はimportを使います。
import "./someothercontract.sol"; contract newContract is SomeOtherContract { }
ストレージ vs メモリ
Solidityの変数はstorageかmemoryのどちらかの領域に保存されます。
storageはブロックチェーン上に永久に保存される場所であるのに対し、memoryは一時的に値を保存する場所です。
コンピュータで言うところのハードディスクとRAMのような役割です。
デフォルトでは状態変数はstorageに保存され、関数内で宣言される変数はmemoryに保存されますが、どちらを使うか指定することもできます。
contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uint _index) public { // storageの場合は値を直接書き換えられる。 Sandwich storage mySandwich = sandwiches[_index]; mySandwich.status = "Eaten!"; // memoryの場合はコピーがanotherSandwichに入る。 Sandwich memory anotherSandwich = sandwiches[_index + 1]; // コピーなので値を変更してもsandwichesには影響を与えない。 anotherSandwich.status = "Eaten!"; } }
別コントラクトとのやり取り
ブロックチェーン上の他人のコントラクトとやり取りするにはinterfaceを定義する必要がある。たとえば、以下のようなコントラクトがあったとする。
contract LuckyNumber { mapping(address => uint) numbers; function setNum(uint _num) public { numbers[msg.sender] = _num; } function getNum(address _myAddress) public view returns (uint) { return numbers[_myAddress]; } }
この中の関数を使うには以下のようなinterfaceを作成する。
contract NumberInterface { function getNum(address _myAddress) public view returns (uint); }
ただのcontractのように見えるが、関数の最後にセミコロンを使っています。コンパイラはこれを見てinterfaceだと理解します。
これを使用するには以下のようにすれば良いです。
contract MyContract { address NumberInterfaceAddress = 0xab38...; NumberInterface numberContract = NumberInterface(NumberInterfaceAddress); function someFunction() public { uint num = numberContract.getNum(msg.sender); } }
InternalとExternal
前回の記事で関数にはprivateとpublicがあると紹介したが、実は公開範囲の定義は他にもあと2つあります。
InternalとExternalです。
privateな関数は外部からも継承先からも使用できませんが、Internalな関数は外部からは使用できず、継承先で使用することが出来ます。
また、Externalな関数は自身のコントラクト内からは使用できず、外部のみから使用できます。
contract hoge { function hogeInter() internal {} function hogeExter() external {} hogeExter() // NG! } contract childHoge is hoge { hogeInter() // OK! } contract fuga { hoge.hogeInter() // NG! hoge.hogeExter() // OK! }
(※ hoge.hogeInterやhoge.hogeExterという表現は正しくありませんが、hogeコントラクトの関数を使用するよ〜っていう意味で使用しています。)
Crypto Zombieで始めるSolidity入門①
今回のシリーズではCrypto ZombieというSolidityの学習が出来るサイト(しかも無料で)の内容を自分なりにまとめます。
入門編はこれ一つで全部学べるみたいなので、みなさんもぜひやってみてください!
コントラクトの書き方
新しいコントラクトを作るときには以下のように書きます。
pragma solidity ^0.4.19; contract HelloWorld { }
pragmaというのはsolidityのバージョンを指定してプログラムのバグを起こさないために書くものです。
contractという記述のあとにコントラクト名を書くことで新たなコントラクトを作ることができます。
状態変数
コントラクト内の変数は状態変数と呼ばれます。
普通の変数との違いは、状態変数に記述された値はブロックチェーン上に保存されます。
そう、こんなに簡単にブロックチェーンに値を保存できるんです!
contract Example { // この部分がブロックチェーン上に記載される uint hoge = 100; }
uintというのは型のことで、符号なし整数(正の整数)を表しています。
演算
solidityでも普通のプログラミング言語と同じような方法で演算ができます。
注意してほしいのは、同じ型同士でしか演算ができません。これも普通の言語と同じですね。
1 + 2 // 足し算 1 - 2 // 引き算 1 * 2 // 掛け算 1 / 2 // 割り算 1 % 2 // 余り 1 ** 2 // 累乗
型キャスト
どうしても違う型同士で演算したり、状態変数の型を変更したい場合は型キャストを使う。
uint8 a = 5; uint b = 6; uint8 c = a * b; // => Error uint8 c = a * uint8(b); // => Success!
構造体
複雑なデータ型を扱うために構造体というものがあります。
構造体とは複数のプロパティを含む値の雛形のようなものです。
struct Person { uint age; string name; }
配列
配列は複数の値を持ちます。
一般的なプログラミング言語にも配列は存在しますが、solidityの配列の書き方は少し特殊かもしれません。
uint[2] fixedArray; // 2つの整数を持つ配列 string[5] stringArray; // 5つの文字列を持つ配列 uint[] dynamicArray; // 任意の数の整数を持つ配列
このように配列の長さを指定することが出来ます。
長さが決まっている配列を固定長配列。長さが決まっていないものを可変長配列といいます。
[]の中に何も入れないと可変長配列になります。
以下は配列に構造体を追加する例です。
struct Person { uint age; string name; } Person[] public peaple; // publicで指定された変数はすべての場所から見ることが出来る。 Person satoshi = Person(172, "Satoshi"); peaple.push(satoshi); // peaple => [{172, "Satoshi"}]
関数
関数は以下のようにして定義します。
function hoge(string _a, uint _b){} hoge("fuga", 0) // 呼び出し
引数は(型 名前)という形で指定します。
Public/Private関数
public関数とは誰でも呼び出しができる関数のことを指します。
デフォルトではすべての関数はpublicになっており、誰でもどこからでも実行可能です。
一方、private関数は自分のコントラクト内からのみ実行可能です。
public関数とprivate関数は以下のように指定します。
//private関数 function _hoge() private {} //public関数 funciton fuga() publick {}
private関数はpublic関数と見分けるために先頭にアンダースコア(_)をつけることが慣習となっています(付けなくてもプログラム的には問題ありません)。
戻り値
関数に戻り値がある場合は、その戻り値の型を指定してあげる必要があります。
funciton hoge() public returns (string) { return "fuga"; }
修飾子
先程の関数はコントラクト内のデータを読み込んだり、編集をしたりしない関数です。
本来値を読み込んだり編集したりしたくない関数の中でうっかりそれをやってしまわないように、関数にpureとviewという修飾子をつけることができます。
pureはデータの読み込みも編集もしない関数。viewはデータの読み込みはするが編集はしない関数であることを表します。
// pure関数 funciton hoge() public pure returns (string) { return "fuga"; } uint test = 1; // view関数 functon hoge2() public view returns (uint) { return test; }
Keccak256
keccak256とはSHA3に基づいて値をハッシュ化するsolidityの組み込み関数です。
好きな文字列を与えると、256ビットのハッシュ化された値を返します。
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256("aaaab"); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 keccak256("aaaac");
イベント
イベントはSolidityにしか無い特殊な操作です。
ブロックチェーンに何かが生じたことをコントラクトがフロントエンドに伝えることができます。
event IntegersAdded(uint x, uint y, uint result); function add(uint _x, uint _y) public { uint result = _x + _y; // 関数が呼ばれたことをアプリに伝えるためにイベントを発生させる: IntegersAdded(_x, _y, result); return result; }
このようにすることで、関数addが呼ばれたときに新しい値_x, _y, resultをフロントエンドが受け取ることができます!!すげぇ。
イベントが発生したことはweb3.jsというライブラリで受け取ることができます。
Remixを使ってみた!
Remixって?
solidityでのスマートコントラクト開発をめちゃんこ楽にしてくれるブラウザアプリケーションだよ。
必要な環境
- npmが使えること
- gethでプライベートネットワークに接続できること
早速使ってみる
まずはremixdというツールをnpmでインストール
$ npm install -g @remix-project/remixd
次にremixdのサーバーを起動。このサーバーがローカルマシンとremixのブラウザアプリをつなげてくれるよ。
$ remixd -s <absolute-path> --remix-ide https://remix.ethereum.org
その次は、remixのブラウザアプリにアクセス。
remix.ethereum.org
このリンクを押して、アクセスしてね!
そしたら、ローカルマシンのフォルダをremixから見れるようにする。
そして、SingleNumRegister.solという名前のファイルを作成し、以下のように記述。
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.7; contract SingleNumRegister { uint storedData; function set(uint x) public{ storedData = x; } function get() public view returns (uint retVal){ return storedData; } }
※コンパイラーのバージョンは適宜修正
ファイルを選択した状態でSOLIDITY COMPILERメニューを押し、コンパイルを実行する。
DEPLOY & RUN TRANSACTIONメニューを選択。
CONTRACTにSingleNumRegisterを選択してDeployをクリック。
DEPLOY & RUN TRANSACTIONメニューを下にスクロールしていくとDeployed Contractsというドロップダウンがあるのでそこをクリック。
すると、SetとGetというメニューが出てくるので、試しにSetに1を入力して送信。
getを押すとちゃんと1が出力されることが確認できた。
終わり!
Ethereum入門読んでみた⑥
この記事はEthereum入門読んでみた⑤の続きです。
前回の記事はこちら↓
chanichiwasshoi.hateblo.jp
こちらの記事を参照しています。
メインネットに接続する - Ethereum入門
メインネットへの接続
任意のディレクトリに移動したあと、以下のコマンドを実行してメインネットに接続 & コンソールの立ち上げ。
$ geth console 2> ./e01.log
ログはe01.logというファイルに出力されるため、適宜確認しながら実行してください。
メインネットへ接続してしばらく待つと他のノードに接続されます。いくつのノードと接続されているか確認するにはgethコンソールで以下のコマンドを実行します。
> net.peerCount 4
また、接続されているノードの詳細を確認するには以下のコマンドを実行します。
> admin.peers [{ Caps: 'eth/60, eth/61', ID: '99017abe7031b48a855b8e79fecb6c927cda88229354f21184d343941ae78ee261d0ccb9f9999f620f96bd729b2cb7c4e8cdf3218d71b016fe531ff439b81dcc', LocalAddress: '160.16.80.199:34057', Name: 'Geth/v1.0.2/linux/go1.4.2', RemoteAddress: '192.169.7.150:30303' }, (中略) { Caps: 'eth/60, eth/61', ID: '6f8c6cc4a878ed88e03c7a0ee01386e095fbc1bfd48352c7bec05142cc60795317b5b8c5e9afc9863a414541df4d5b1627a86418177857166171fd45497c75ab', LocalAddress: '160.16.80.199:41617', Name: 'Geth/v1.0.2/linux/go1.4.2', RemoteAddress: '83.77.31.183:30303' } ]
以上でEthereum入門シリーズを終わります。
最後まで読んでいただきありがとうございました!
この記事少しでも読んでいただいた人の助けになってくれると嬉しいです。
Ethereum入門読んでみた⑤
この記事はEthereum入門読んでみた④の続きです。
前回の記事はこちら↓
chanichiwasshoi.hateblo.jp
また、この記事は以下のサイトを参照しています。
スマートコントラクトを作成し実行する - Ethereum入門
(solcなどのツールのインストールは上記のサイトを見ながら各自で行ってください。)
環境
$ solc --version solc, the solidity compiler commandline interface Version: 0.8.13+commit.abaa5c0e.Darwin.appleclang
スマートコントラクトの作成から実行までの流れ
コントラクトコードの作成
今回作るスマートコントラクトは、ある特定の整数をブロックチェーン上で保管し、登録情報が参照されたときにその整数を返すというごくシンプルな設計です。
まずは、Ethereum入門読んでみた③ - プログラミングのゴミ箱でgethを実行したフォルダに移動し、「SingleNumRegister.sol」という名前のファイルを作成してください。そのファイルに以下のように記述。
pragma solidity ^0.4.0; contract SingleNumRegister { uint storedData; function set(uint x) public{ storedData = x; } function get() public constant returns (uint retVal){ return storedData; } }
エラーハンドリング
コンパイラーバージョンに関するエラー
先程のコードでは以下のようなエラーが出ると思います。
Source file requires different compiler version (current compiler is 0.8.13+commit.abaa5c0e.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
これは、インストールされているコンパイラのversionがコード内で定義されているversionと合っていないために起こるエラーです。エラー文には0.8.13のバージョンのコンパイラが使われていると書かれているため、プログラム内の「^0.4.0」という部分を「^0.8.13」に直します。
構文エラー
先程のエラーを解決すると、今度は新たに「constant」の部分でwarningが出ると思います。実はsolidity v0.4.16からはconstantという構文が非推奨になっています*2。これを解決するために「constant」を「view」に置き換えてください。constant、viewの意味については後ほど解説します。
ライセンスエラー
ここまで来れば致命的なエラーはなくなりましたが、まだ1行目に
SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
というようなwarningが残っていると思います。これはsolidity v0.6.8以降でコードのライセンス(著作権に関する取り決め)を特定しなければならなくなったために起こるものです。解決するにはコードの最初に以下のように記述します。
// SPDX-License-Identifier: UNLICENSED
これはライセンスをしていない。つまりこのコードに関しては著作権に関するいかなる縛りも設定しないということを示しています。コードのライセンスについては以下の記事などが参考になります。
公開ライセンスの話 - Qiita
コードの全容は以下のとおりです。
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; contract SingleNumRegister { uint storedData; function set(uint x) public{ storedData = x; } function get() public view returns (uint retVal){ return storedData; } }
コードの解説
コードの意味を一行づつ解説します。
1行目 => ライセンスを確認
2行目 => コンパイラバージョンの設定
3行目 => SingleNumRegisterという名前のコントラクトコードを定義
4行目 => storedDataという名前のuint型変数を宣言。
solidityには
- int
- uint
- bool
- Array
- struct
- address
- mapping
などの型が存在する。詳しくは以下を参照。
【Ethereum・Solidity】基本データ型まとめ(int / string / bool / Array / Struct / address / mapping) | daiki44の開発備忘録
5~7行目 => setという名前の関数を定義。引数には「x」という名前のuint型を使用。「storedData」に「x」をセット。
8 ~ 10行目 => getという名前の関数を定義。uint 型の変数を返す。viewというオプションを付けることでコントラクト内の変数を参照できるようになる(ここではstoredDataのこと)。
つまり、セット呼び出してstoredDataにデータを入れ、getを呼び出すことでそのデータを読み取れるというシンプルなコードだ。
コントラクトコードのコンパイル
以下のコマンドでコンパイル
$ solc --abi --bin SingleNumRegister.sol ======= SingleNumRegister.sol:SingleNumRegister ======= Binary: 6060604052341561000f ・・・ Contract JSON ABI [{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, ・・・
次に、gethコンソールを起動してコンパイルしたコードをEthereumブロックチェーンに登録。
> var bin = "0x6060604052341561000f ・・・" undefined > var abi = [{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, ・・・ undefined
binとabiにはコンパイル時に出力された値を使ってください。(binの値はダブルクオーテーションで囲い、先頭に0xという記述を追加してください。)
さらに、以下のコマンドを実行。
> var contract = eth.contract(abi) undefined > var myContract = contract.new({ from: eth.accounts[0], data: bin}) undefined
もしも、myContractの登録で「Error: authentication needed: password or unlock」というふうに怒られたら、以下のコマンドでアカウントのロックを解除。
> personal.unlockAccount(eth.accounts[0]) Unlock account 0xf90153bb8478ee8b0b3d3a7dbe92df8301b74607 Passphrase: // パスワードを入力
その後、もう一度myContractの定義。
myContractの内容を確認。
> myContract abi: [{ constant: false, inputs: [{...}], name: "set", outputs: [], payable: false, (省略)
このように表示されたら成功です。
※ いずれかのアカウントでマイニングをしていないとコントラクトの登録が完了しません。
Contractアカウントへのアクセス
今回作ったスマートコントラクトを利用してもらうためには以下の2つの情報を他のユーザーに伝える必要があります。
- Contractアドレス
- ContractのABI(Application Binary Interface)
実際にこれらの情報を使って先程作ったコントラクトコードを実行してみましょう。
var cnt = eth.contract(myContract.abi).at(myContract.address);
これで、コントラクトアドレスへの接続ができました。
次は、実際にコードを呼び出してみましょう。まずは値のsetからです。
> cnt.set.sendTransactoin(6,{from:eth.accounts[0]}) '0x979c4e413a647673632d74a6c8b7f5b25a3260f3fefa4abea2dc265d61215939'
これで、変数storedDataに6という値がセットされました。(アカウントのアンロックが求められた場合は先程の手順でもう一度アカウントのロックを解除してください)
最後に、storedDataの中身をget関数を呼び出して確認しましょう。
> cnt.get.call() 6
これで、スマートコントラクトの実行が出来るようになりました。お疲れ様!
*1:コントラクトアカウントについては過去記事を参考にしてください。Ethereum入門読んでみた② - プログラミングのゴミ箱
Ethereum入門読んでみた④
この記事はEthereum入門読んでみた③の続きです。
前回の記事はこちら ↓
chanichiwasshoi.hateblo.jp
参照しているサイトはこちらです。
etherを送金する - Ethereum入門
etherの送金
// 送金前にアカウントのロックを解除 > personal.unlockAccount(eth.accounts[1]) Unlock account 24afe6c0c64821349bc1bfa73110512b33fa18e1 Passphrase: //パスワードを入力 true > eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(5, "ether")}) '0xc86c2a5bdf651f54095eca87e487d4f68f12030dd559f0377e9e7bf1566b9b28' // トランザクション番号
トランザクションの完了にはいずれかのアカウントでマイニングを実行していなければなりません。
マイニングは以下のコードで実行できます。
// マイニングの開始 > miner.start() // マイニング中かどうか確認 > eth.minig true
トランザクション情報を調べる
> eth.getTransaction('0xc86c2a5bdf651f54095eca87e487d4f68f12030dd559f0377e9e7bf1566b9b28') // 先程のトランザクション番号を入力 { blockHash: '0xeef0f74bc51ecb9f3d64099fa4f3c1651af36a632380d41dd987e8e7064a5276', // ブロックのハッシュ値 blockNumber: 11076, // ブロックの番号 from: '0x868d840e872df5134a3be6f7b68e52cb680fe3ac', // 送金元アカウント gas: 90000, // トランザクションの処理時のgas最大値 gasPrice: '55928534329', // 1gasあたりの手数料(wei) hash: '0x5fd0bdcccb379a8b4034668464ad9a499a8a6b7801ed66ac23e4df3d67ec64a5', input: '0x', nonce: 0, to: '0x2efbdc840746c862b63077643e5b7dd8bebb8448', // 送金先アカウント transactionIndex: 0, value: '3000000000000000000' // 総金額(wei) }
まだトランザクションを含んだブロックがマイニングされていないときにはblockHashとblockNumberは空となる。