プログラミングのゴミ箱

日々の学習の中で知らなかったことについて、調べたことを解説します。

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";
  }
}

この例ではBabyDogeコントラクトからDogeコントラクトのcatchPhrase関数を呼び出せます。

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