【CryptoZombiesを触ってみた】Lesson5で学んだこと

【CryptoZombiesとは】

Solidityでスマートコントラクトの構築を学習できるサイト
日本語で丁寧に説明してくれるので、プログラミングやったことない人でも学習できる。

cryptozombies.io

【はじめに】

Solidityは未経験ですが、JavaScriptは触ったことがあります。
そんな人が自分用の学習メモとして書いています。

【学んだこと】

ERC20とERC721

ERC(Ethereum Request for Comments)はイーサリアムトークン規格。ERC20はトークンの送金、残高照会、アドレス毎の残高記録など、トークンを発行するのに必要な機能について定められている。ERC20で作成されたトークン同士は互換性があるので連携しやすい(実装が容易になる)。ERC721は同じトークンでも、それぞれがユニークであると仮定され異なる特徴を持たせることができる。ERC721であれば、同じトークン(ゾンビ)でもレベルが異なれば別物として扱うことができる。

ERCについてはこちらの説明が分かりやすいです。
dappsmarket.net

複数継承

Solidityではカンマ区切りで複数の継承ができる。

contract newContract is Contract1, Contract2 {
}

ERC721の実装

ERC721を継承し各関数をオーバーライドして使用する。

import "./erc721.sol";

// ERC721を継承
contract newContract is ERC721 {

  // balanceOf:addressを受け取り、そのaddressのトークン保有量を返す。
  function balanceOf(address _owner) public view returns (uint256 _balance);

  // ownerOf:トークンIDを受け取り、その所有者のaddressを返す。
  function ownerOf(uint256 _tokenId) public view returns (address _owner);

  // transfer:トークン所有者が送り先(address)、送るトークン(トークンID)を指定し所有権を移転する。
  // 処理の最後にTransferイベントを呼び出す必要がある。
  function transfer(address _to, uint256 _tokenId) public;

  // approve:トークン所有者が送り先(address)、送るトークン(トークンID)を指定し、コントラクトのmappingに記録する。
  // transferと異なり、approveを実行しただけで所有権の移転はしない。takeOwnershipを実行した際に移転する。
  // 処理の最後にApprovalイベントを呼び出す必要がある。
  function approve(address _to, uint256 _tokenId) public;

  // takeOwnership:トークンの受取人が呼び出す。approveで記録したmappingから受取人のアドレスを検索し、該当のトークンの所有権を移転する。
  function takeOwnership(uint256 _tokenId) public;

}

オーバーフローとアンダーフロー

オーバーフローとは四則演算の結果が許容範囲を上回ること。

uint8 number = 255;
// uint8の最大値である255を上回るのでオーバーフローが発生し、結果は0となる。
number++;

アンダーフローとは四則演算の結果が許容範囲を下回ること。

uint8 number = 0;
// uint8の最小値である0を下回るのでアンダーフローが発生し、結果は255となる。
number--;

SafeMath

SafeMathはOpenZeppelinのライブラリ。OpenZeppelinについてはこちらの記事でも紹介しています。

takuyafujita.hatenablog.com

SafeMathはadd(加算)、sub(減算)、mul(乗算)、div(除算)の4つの関数を持ち、オーバーフローやアンダーフローの脆弱性に対応している。SafeMathを使用して四則演算を行えばオーバーフローやアンダーフローが発生する演算を行う前にエラーで返してくれる。基本的に四則演算を行う場合はSafeMathを使用した方が安全。

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

SafeMathの使い方

// uint256にSafeMathの関数を追加する(使用できるようにする)。
using SafeMath for uint256;

// aがSafeMathの関数の1つ目の引数として渡される。
uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

※uint16やuint32の場合、ライブラリ内でuint256に変換されるので脆弱性に対応出来ていない。この場合、uint16やuint32用のSafeMathライブラリを作成する必要がある。

assert

偽(false)の場合、エラーを発生させる。関数呼び出しが失敗した場合、ユーザーにガスの残りを返却しない。

require

真(true)の場合、エラーを発生させる。関数呼び出しが失敗した場合、ユーザーにガスの残りを返却する。

コメント

JavaScriptに似ている

// 1行コメントの場合

/*
  複数行
  コメント
  の場合
*/

// natspec
/// @title contractのタイトル
/// @author contractの作成者
/// @notice contractが何を行うのか説明
contract Math {
  /// @notice 関数が何を行うのか説明
  /// @param x 1つ目の引数の説明
  /// @param y 2つ目の引数の説明
  /// @return z 戻り値の説明
  /// @dev 開発者向けの詳細な説明
  function multiply(uint x, uint y) returns (uint z) {
    z = x * y;
  }
}

【できたもの】

ゾンビが4体に増えた。ゾンビの名前が変更できるようになった。

f:id:takuyafujita:20180924194242j:plain