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コントラクトの関数を使用するよ〜っていう意味で使用しています。)