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

【CryptoZombiesとは】

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

cryptozombies.io

【はじめに】

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

【学んだこと】

イミュータブル(immutable)

コントラクトをイーサリアム上にデプロイするとイミュータブル(※)になる。改竄できないのでセキュリティが高くなる反面、不具合があっても修正できない。修正する場合は別途、修正版のコントラクトを新規で作成する。

※作成後にその状態を変えることのできないオブジェクト

外部依存関係

外部のコントラクトを呼び出す場合は、コントラクトのアドレスをハードコーディングせず関数にする。関数にしておくと呼び出し先のコントラクトで不具合が発生しても後から変更できる。ハードコーディングしているとイミュータブルなので修正できない。

Ownableコントラクト

外部のコントラクトを呼び出す関数の場合、誰でも呼び出しできる状態(External)だとクラッキングされる。そんな時はOwnableを使い、権限を持った人しか呼び出しできないようにする。OpenZeppelinのOwnableコントラクトを使うのが一般的。

OpenZeppelinのOwnableコントラクト

OpenZeppelinはイーサリアム上で安全なスマートコントラクトを実装するためのフレームワーク。そのうちの1つにOwnableコントラクトがあり、コントラクトの実行権限を限定することができる。

詳細は下記にまとめています。

takuyafujita.hatenablog.com

関数修飾子

functionの代わりにmodifierを使う。

/**
  * @dev Throws if called by any account other than the owner.
  */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

下記の場合、transferOwnershipの処理が実行される前に関数修飾子のonlyOwnerが実行される。onlyOwnerの[_;]が実行されると呼び出し元のtransferOwnershipの処理が実行される。関数修飾子はrequireで処理前のチェックを行う使い方が一般的。

/**
  * @dev Allows the current owner to transfer control of the contract to a newOwner.
  * @param _newOwner The address to transfer ownership to.
  */
  function transferOwnership(address _newOwner) public onlyOwner {
    _transferOwnership(_newOwner);
  }

関数修飾子に引数を渡す

関数修飾子には関数同様、引数を渡すことが可能です。

mapping (uint => uint) public age;

// ユーザの年齢が一定の年齢を超えていること
modifier olderThan(uint _age, uint _userId) {
  require (age[_userId] >= _age);
  _;
}

// 基準となる年齢(20)とユーザIDを関数修飾子の引数として渡す
function drinking(uint _userId) public olderThan(20, _userId) {
  // 処理
}

ガス(Gas)

Solidityでは関数を実行する度にガス(Gas)と呼ばれる手数料が必要になる。必要なガスの量は関数のロジックで決まり、複雑なロジックになるほど多くのガスが必要になる。関数を実行する度にお金が必要になるのでコードの最適化(ガス代を抑える)が重要になる。

ガスが必要な理由

分散型のイーサリアムはいくつものノードが関数の実行結果が正しいことを検証しているが、無駄にネットワークに負荷をかける処理が多くなると検証時間も遅くなる。手数料を設定することでネットワークの負荷を軽減している。

ガスを節約する方法(struct構造)

structの中に複数のuintを宣言する場合、小さい単位で宣言すると変数がまとめられてストレージが小さくなりガス代が安くなる。下記の場合、NormalよりMinの方がガス代が安い。更に同じデータ型を隣同士にしておく([uint32 a]と[uint32 b])ことでもストレージを抑えることができ、ガス代も安くなる。
※struct以外でuintを宣言する場合は全て256Bitでストレージを確保するのでガス代が安くなる効果はない。

struct Normal {
  uint a;   // 256Bit
  uint b;   // 256Bit
  uint c;   // 256Bit
}

struct Min {
  uint32 a; // 32Bit
  uint32 b; // 32Bit
  uint c;   // 256Bit
}
ガスを節約する方法(View関数)

View関数はブロックチェーンの変更を行わないので外部から呼び出す場合のみガス代がいらない。

ガスを節約する方法(Memory)

Storageのデータを操作するとブロックチェーンに書き込まれ、全てのノードに取り込まれる必要があるのでガス代が高くなる。一時的に必要なデータであればStorageよりもMemoryを使用してガス代を抑える。Memoryの使い方は以下の通りです。

function getMemory() external pure returns(uint) {
  uint memory value = 10;
  return value;
}

時間

now変数は現在のunixタイムスタンプを返す。

seconds、minutes、hours、days、weeks、years

uintの秒数に変換されて使用できる。

1 minutes : 60
1 hours   : 3600 (60分×60秒)
1 days    : 86400(24時間×60分×60秒)
uint lastUpdated;

// lastUpdatedに5分加算する
function addFiveMin() public view returns (uint) {
  return lastUpdated + 5 minutes;
}

For

JavaScriptと同じ

【できたもの】

レベルが上がるとゾンビの名前を変更できる機能を追加したよ。

f:id:takuyafujita:20180902143837j:plain