Truffleを使ってERC20の独自トークンを発行する

こんにちは。休みの日はDapps開発をしている 藤田拓弥 です。

今回はERC20の独自トークン作成からローカル環境にデプロイまでを行います。
Truffle(Ethereumのスマートコントラクト開発フレームワーク)を使うので、何それ?という方は下記の記事を参考にして環境構築をお願いします。

takuyafujita.hatenablog.com

それでは早速作っていきましょう。

Truffleでプロジェクトを作成する

今回は自分の名前(takuya)でトークンを発行しますので「takuya_token」という名前のディレクトリを作成します。

mkdir takuya_token

作成したディレクトリに移動します。

cd takuya_token

Truffleを初期化してプロジェクトに必要なファイル一式を作成します。

truffle init

OpenZepplinをインストールする

続いて独自トークンとなるコントラクトを作成しますが、その前にOpenZepplinをインストールします。
OpenZepplinはEthereum上で安全なコントラクトを実装するためのフレームワークです。
その中にERC20も含まれており、トークンに必要な機能が一通り揃っています。

まずは「package.json」を作成します。

npm init -f

続いてOpenZeppelinをインストールします。

npm install zeppelin-solidity

インストールすると以下の警告が出ました。

This package has been renamed to openzeppelin-solidity. Please update your dependency, or you will no longer receive updates.

OpenZeppelinのバージョンが上がり、パッケージ名が変わっているようです。

新:openzeppelin-solidity
旧:zeppelin-solidity

今回は旧の方を使用します。

コントラクトを実装する

プロジェクト(takuya_token)配下の「contracts」ファルダ内に、「TakuyaToken.sol」という名前でファイルを新規作成します。

「TakuyaToken.sol」の内容は下記の通りです。


マイグレーションファイルを作成する

デプロイを行うので、マイグレーションファイルを作成します。

プロジェクト(takuya_token)配下の「migrations」ファルダ内に、「2_deploy_takuya_token.js」という名前でファイルを新規作成します。

「2_deploy_takuya_token.js」の内容は下記の通りです。


テストコードを作成する

プロジェクト(takuya_token)配下の「test」ファルダ内に、「TestTakuyaToken.js」という名前でファイルを新規作成します。

「TestTakuyaToken.js」の内容は下記の通りです。


テストを実施する

今回はローカル環境を使用するので、以下のコマンドでEthereumブロックチェーンエミュレータを起動します。

truffle develop

truffle consoleに入りますので、そのままテストを実行します。

truffle(develop)> test

テスト結果が表示されます。想定通りの結果になればデプロイに進みます。

Compiling .\contracts\Migrations.sol...
Compiling .\contracts\TakuyaToken.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/StandardToken.sol...
Compiling zeppelin-solidity\contracts\math\SafeMath.sol...
Compiling zeppelin-solidity\contracts\token\ERC20\BasicToken.sol...
Compiling zeppelin-solidity\contracts\token\ERC20\ERC20.sol...
Compiling zeppelin-solidity\contracts\token\ERC20\ERC20Basic.sol...

  Contract: TakuyaToken
    √ should put 1000 TAKUYA in the first account

  1 passing (65ms)

デプロイする

以下のコマンドでローカル環境にデプロイを行います。

truffle(develop)> migrate

デプロイされた内容を確認する

以下のコマンドでデプロイしたTakuyaTokenのオブジェクトを変数(TakuyaToken)に格納します。

truffle(develop)> TakuyaToken = TakuyaToken.at(TakuyaToken.address)

コントラクトを作成する際に設定した情報を確認します。

トークンの名称

truffle(develop)> TakuyaToken.name()
'TAKUYA'

トークンの単位

truffle(develop)> TakuyaToken.symbol()
'TAKUYA'

トークンの総発行量

truffle(develop)> TakuyaToken.totalSupply()
BigNumber { s: 1, e: 21, c: [ 10000000 ] }

独自トークンを送金する

1つ目のアカウントはトークンの発行者なので作成した全てのトークンが入っていることを確認します。

truffle(develop)> TakuyaToken.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 21, c: [ 10000000 ] }

2つ目のアカウントはトークンの残高が0であることを確認します。

truffle(develop)> TakuyaToken.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }

1つ目のアカウントから2つ目のアカウントに 100 TAKUYA を送金します。

truffle(develop)> TakuyaToken.transfer(web3.eth.accounts[1], 100)

2つ目のアカウントの残高が100であることを確認します。

truffle(develop)> TakuyaToken.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 2, c: [ 100 ] }

これで独自トークンを発行し、送金できることが確認できました!

本当はRopstenにデプロイしたかったのですがエラーが出て解決に時間がかかりそうだったので断念しました。

次はRopstenへのデプロイに挑戦しようかな。

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

【CryptoZombiesとは】

Solidityでスマートコントラクトの構築を学習できるサイトです。日本語で丁寧に説明してくれるので、プログラミング未経験の人でも学習できます。

cryptozombies.io

【はじめに】

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

【学んだこと】

Web3.js

イーサリアムのノードにアクセスできるJavaScriptライブラリです。フロントエンド(Webページ)からスマートコントラクトの関数を呼び出す場合に使用します。

まずは、Web3.jsをダウンロードして、htmlのheadタグ内にscriptタグを追加します。

<script language="javascript" type="text/javascript" src="web3.min.js"></script>

Web3プロバイダとMetamask

続いて、Web3プロバイダの設定(どのノードを使用すれば良いか教えてくれる)が必要になります。また、ブロックチェーンに書き込みを行う場合は秘密鍵を使って署名するので、秘密鍵の安全な管理も必要になります。これら全てを自前で実装することも可能ですが、今はMetamaskを使用するのがデファクトスタンダードになっています。MetamaskがインストールされていればMetamaskで使用しているWeb3プロバイダをそのまま使用することができ、イーサリアムのアカウントと秘密鍵も安全に管理してくれます。
Metamaskのインストール方法や使い方は他の方々がブログで紹介しているので、そちらを参考にしてください。

window.addEventListener('load', function() {
  // MetamaskがJavaScriptのグローバルオブジェクト web3 にWeb3プロバイダを設定するので
  // web3が設定されているかを確認すればMetamaskのインストール状況を判断できます。
  if (typeof web3 !== 'undefined') {
    // Web3が設定されていればMetamaskで使用しているWeb3プロバイダを設定
    web3js = new Web3(web3.currentProvider);
  } else {
    // web3が設定されていなければMetamaskをインストールするメッセージを表示
  }
  // 処理開始
  startApp()
})

コントラクトアドレスとABI

Web3.jsを使用してスマートコントラクトにアクセスするには
・スマートコントラクトをデプロイする際に付与されるコントラクトアドレス
・スマートコントラクトをコンパイルする際に作成されるABI(Application Binary Interface)
 ※ABIは関数の定義(関数名や引数など)がJSON形式で記載されています。
が必要になります。

var myContract;
function startApp() {
  // デプロイする際に付与されたコントラクトアドレス
  var myContractAddress = "YOUR_CONTRACT_ADDRESS";
  // コンパイルする際に作成されたABI
  var myContractABI = "YOUR_CONTRACT_ABI";
  // コントラクトアドレスとABIを使用してコントラクトを初期化
  myContract = new web3js.eth.Contract(myContractABI, myContractAddress);
}

call

Web3.jsを使用してスマートコントラクトのview関数、pure関数を呼び出します。読み取り専用なのでブロックチェーンへのトランザクションは発生しません。

myContract.methods.myMethod(argument1, argument2, argument3).call()

send

Web3.jsを使用してスマートコントラクトのview関数、pure関数以外の関数を呼び出します。ブロックチェーンのデータを更新するのでトランザクションが発生します。当然、ガス(Gas)の支払いや署名が必要になりますが、Metamaskがインストールされていれば全てMetamaskが行ってくれます。

myContract.methods.myMethod(argument1, argument2, argument3).send()

アカウント取得

Web3プロバイダを設定したグローバルオブジェクト web3 からユーザーのアカウントを取得することができます。Metamaskを使用している場合、Metamaskでアクティブになっているユーザーのアカウントを取得します。

var userAccount = web3.eth.accounts[0]

ユーザーのアカウントが変更されているかチェックしたい場合は、setInterval(JavaScriptのループ関数)を使います。

var accountInterval = setInterval(function() {
  // アカウントが変更されているかチェック
  if (web3.eth.accounts[0] !== userAccount) {
    userAccount = web3.eth.accounts[0];
    // 変更後のアカウントで行いたい処理
  }
}, 100);

トランザクションの送信

トランザクションが大量に保留されていたり、設定したガス代が安すぎると数ブロック待つ必要があるため、非同期処理で記述する必要があります。

function createRandomZombie(name) {
  // しばらく時間がかかるので、ユーザーにトランザクションが送信されたことを知らせる
  $("#txStatus").text("Creating new zombie on the blockchain. This may take a while...");
  // トランザクションをコントラクトに送信する
  return cryptoZombies.methods.createRandomZombie(name)
  .send({ from: userAccount })
  // トランザクションがブロックに含まれるとreceiptが発行される
  .on("receipt", function(receipt) {
    // トランザクションがブロックチェーンに取り込まれたことを知らせる
    $("#txStatus").text("Successfully created " + name + "!");
    getZombiesByOwner(userAccount).then(displayZombies);
  })
  .on("error", function(error) {
    // トランザクションが失敗したことをユーザーに知らせる
    $("#txStatus").text(error);
  });
}

Wei

イーサ(イーサリアムの通貨)の最小単位
1 wei = 0.000000000000000001 ether

// payable関数を呼び出す際、etherではなくweiで指定する必要があるので
// toWeiを使用してetherをweiに変換する
cryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001", "ether") })

eventをトリガーにする

Web3.jsではスマートコントラクトのイベントをトリガーにすることができます。

// スマートコントラクト側(Solidity)
// eventを設定
event NewZombie(uint zombieId, string name, uint dna);

// フロントエンド側(javascript)
// eventをトリガーとして処理を行います。
cryptoZombies.events.NewZombie()
.on("data", function(event) {
  let zombie = event.returnValues;
  console.log("A new zombie was born!", zombie.zombieId, zombie.name, zombie.dna);
}).on("error", console.error);

indexedを使用すればイベントのフィルタリングができます。

// スマートコントラクト側(Solidity)
// indexedで引数を設定しておきます
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

// フロントエンド側(javascript)
// '_to'が'userAccount'と同じ場合のみ処理が行われます。
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
  let data = event.returnValues;
}).on("error", console.error);

【できたもの】

今回のLessonはフロントエンド(javascript)がメインだったので比較的分かりやすかったです。Metamaskのお面カワイイ!
f:id:takuyafujita:20181014194805j:plain

現在はLesson6で終了ですが、もうすぐseason2が登場しそうなので楽しみです!

Gethのコンパイル環境構築とコンパイル方法

こんにちは。休みの日はDapp開発の勉強をしている 藤田拓弥@ディズニー好きのSE です。

今日はイーサリアムが提供しているクライアントソフトのGethを触ってみようと思い、公式サイトにアクセスしましたが「Retrieving packages from release server...」のダイアログが表示され続け何もできませんでした。インストール用の実行ファイルもダウンロードできませんでした。(2018年9月30日時点)

f:id:takuyafujita:20180930160257j:plain

ブラウザを変えてみても状況が変わらなかったのでダウンロードは諦めて、Gitに公開されているGethのソースコードを自分でコンパイルする方法を試してみました。

試した環境はwindows10です。

Gethのコンパイル方法は以下のサイトを参考にしました。

Git:Geth公式のインストール手順(Windows

github.com

Qiita:gethをwindows10にインストールする

qiita.com

両方参考にしましたが、内容は同じです。

Gethのコンパイル環境構築

Go言語のインストール

公式サイトからwindows用のMSIファイルをダウンロードしてインストールします。

コマンドプロンプトでバージョンが表示されればOKです。

C:\>go version
go version go1.11 windows/amd64

chocolatey(パッケージ管理ソフト)のインストール

コマンドプロンプトまたはWindows PowerShellのどちらかを使用してインストールします。
インストールに必要な要件等は公式サイトを確認してください。

コマンドプロンプトを使用する場合

管理者として以下のコマンドを実行します。

@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
Windows PowerShellを使用する場合

管理者として以下のコマンドを実行します。

Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

コマンドプロンプトでバージョンが表示されればOKです。

C:\>choco --version
0.10.11

chocolateyを使用して必要なソフトをインストール

既にインストール済みの場合は対応不要です。インストール時にスクリプトを実行するか?と聞かれるので「y」を選んで進めます。オプション -y をつけると実行確認が表示されないので楽です。

C:\Windows\system32> choco install git
C:\Windows\system32> choco install golang
C:\Windows\system32> choco install mingw

インストール後にsuccessfullyが表示されていればOKです。

環境変数の設定

C:\Users\xxx> set "GOPATH=%USERPROFILE%"
C:\Users\xxx> set "Path=%USERPROFILE%\bin;%Path%"
C:\Users\xxx> setx GOPATH "%GOPATH%"
C:\Users\xxx> setx Path "%Path%"

Gethのコンパイル手順

githubからGethのソースをclone

C:\Users\xxx> mkdir src\github.com\ethereum
C:\Users\xxx> git clone https://github.com/ethereum/go-ethereum src\github.com\ethereum\go-ethereum

goで各種外部パッケージを取得

C:\Users\xxx> cd src\github.com\ethereum\go-ethereum
C:\Users\xxx> go get -u -v golang.org/x/net/context

goでGethの実行ファイルを作成

C:\Users\xxx\src\github.com\ethereum\go-ethereum> go install -v ./cmd/...

Gethが正常にインストールされたか確認

コマンドプロンプトで「geth help」を実行し、Gethの情報(NAME、USAGE、VERSIONなど)が表示されればOKです。

C:\>geth help

NAME:
   geth - the go-ethereum command line interface

   Copyright 2013-2018 The go-ethereum Authors

USAGE:
   geth [options] command [command options] [arguments...]

VERSION:
   1.8.17-unstable

【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

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

【CryptoZombiesとは】

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

cryptozombies.io

【はじめに】

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

【学んだこと】

payable修飾子

ブロックチェーン上にあるコントラクトを呼び出す際にETHを受け取ることができる。

contract OnlineStore {
  function buySomething() external payable {
    // 関数呼び出し時、0.001ETHが送られてきたか確認する
    require(msg.value == 0.001 ether);
    // 0.001ETHが送られてきた場合に実行する処理
    transferThing(msg.sender);
  }
}
// buySomething関数を呼び出す(web3.js)
// valueに0.001ETHを設定
OnlineStore.buySomething({from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001)})

CryptoZombiesより引用

withdraws関数とtransfer関数

コントラクトに送られたETHはコントラクトのアカウントに貯められる。貯まったETHをコントラクト以外で使用するにはコントラクトから引き出す処理(withdraws関数)が必要になる。transfer関数を使用すると指定したイーサリアムのアドレスに送金できる。

contract GetPaid is Ownable {
  function withdraws() external onlyOwner {
    // transfer関数でownerにETHを送る
    // this.balanceでコントラクトに貯まっているETHの残高を取得する
    owner.transfer(this.balance);
  }
}

CryptoZombiesより引用

乱数

ハッシュ関数のkeccak256で乱数を生成する。ただし、この方法で作成した乱数を使用しランダムな結果にしても脆弱性(※)が含まれる。
※自分のノードに向けてトランザクションを発行し、自分の都合の良い結果になるまで繰り返し行うことができる脆弱性。対策としてはoracle(イーサリアムブロックチェーン外の乱数関数にアクセスする)を使う。

uint once = 0;
uint random = uint(keccak256(once));
once++;
uint random = uint(keccak256(once));

else

JavaScriptと同じ

if (age < 30) {
  // 30歳未満
} else {
  // 30歳以上
}

【できたもの】

ゾンビ同士のバトル機能を追加
バトルに勝つとゾンビのレベルが上がる。更に新たなゾンビを生み出し、自分のゾンビ軍団に加わる。

f:id:takuyafujita:20180917041746j:plain
f:id:takuyafujita:20180917041804j:plain

【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

【TruffleでDapp開発】公式チュートリアル「ETHEREUM PET SHOP」で基礎を学ぶ

【はじめに】

イーサリアムを購入してからブロックチェーンに興味を持ち、休みの日はDapp開発の勉強をしています。技術情報を調べていると【Truffle】というイーサリアム開発のフレームワークに出会いました。Truffleの公式サイトに「ETHEREUM PET SHOP」というチュートリアルがあり、Dapp開発の基礎を学べるので早速触ってみました。チュートリアルと言ってもインストールでエラーが発生したり、環境構築で四苦八苦しましたので、苦労したところをブログに残しておこうと思います。

【参考にしたブログ】

Dapp開発初心者かつ英語も得意ではないので、こじりょー(@kojiryoinvestor)さんのブログを参考に進めました。大変分かり易かったです。ありがとうございます。

Dapps作成手順をイチから学べる!Truffleの「イーサリアム・ペットショップ」

【Truffleのインストール】

nodeとnpmが使える環境で以下のコマンドを実行します。
nodeはv6以上をインストールしておかないと、後々困るので注意してください。私は見落としていて時間を無駄にしてしまいました。。。

npm install -g truffle

【バージョン確認】

以下のコマンドを実行し、Truffleのバージョンが表示すれば正常にインストールされています。

truffle version

正しくインストールされている場合、TruffleとSolidityのバージョンが表示されます。

Truffle v4.1.13 (core: 4.1.13)
Solidity v0.4.24 (solc-js)

私の場合、ここでSyntaxErrorが発生しました。

SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode

解決方法はこちらにまとめています。

【Truffleのプロジェクト作成】

Truffleのプロジェクトを格納するディレクトリを作成し、そのディレクトリに移動します。

mkdir pet_shop
cd pet_shop

下記のコマンドで「ETHEREUM PET SHOP」のプロジェクト一式がダウンロードできます。

truffle unbox pet-shop

成功すると下記の様に「Unbox successful. Sweet!」が表示されます。

Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test
  Run dev server: npm run dev

私の場合、「Downloading...」で止まり、先に進んでいる様子がありませんでした。
解決方法はこちらにまとめています。

【スマートコントラクトの作成】

contractsのディレクトリ配下に「Adoption.sol」を新規作成します。ファイルの中身は次の様に記載します。

pragma solidity ^0.4.17;

contract Adoption {

  address[16] public adopters;

  // Adopting a pet
  function adopt(uint petId) public returns (uint) {
    require(petId >= 0 && petId <= 15);

    adopters[petId] = msg.sender;

    return petId;
  }

  // Retrieving the adopters
  function getAdopters() public view returns (address[16]) {
    return adopters;
  }

}

コンパイル

truffle compile

build/contractsのディレクトリ配下に「Adoption.json」、「Migrations.json」が作成されていればコンパイルが正常に完了しています。

Windowsの場合、コマンドプロンプトコンパイルすると、以下の様にtruffle.jsのエラーになります。公式にも記載さている通り、私はWindows PowerShellコンパイルしました。

truffle.jsのエラー

エラー:'module'は宣言されていません。
コード:800A1391
ソース:MicrosoftJScript 実行時エラー

マイグレーション

作成したスマートコントラクトをデプロイするためにマイグレーションファイルが必要です。migrationsのディレクトリ配下に「2_deploy_contracts.js」を新規作成します。ファイルの中身は次の様に記載します。

var Adoption = artifacts.require("Adoption");

module.exports = function(deploy) {
  deploy.deploy(Adoption);
}

【Ganacheを起動】

開発用のブロックチェーン(プライベートネットワーク)が必要なので、ローカル環境にイーサリアムブロックチェーンを構築してくれるツール「Ganache」を使用します。Ganacheをダウンロードして起動します。

Ganacheはこちらからダウンロードできます。

起動するとテスト用のアドレスが割り振られます。

f:id:takuyafujita:20180819113840j:plain

【デプロイ】

Ganacheを起動した状態で以下のコマンドを実行します。

truffle migrate

次の様な出力が表示されます。

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x1d88cf62c4d2c04cd115f578496740c76198299db54e706f751f21b63425b37c
  Migrations: 0x15637172d8cf0057da00a7b55e8bf6843c44163a
Saving successful migration to network...
  ... 0x4a6862964a8c66850373e62d3cd8b364667d437f81f6b00c0b7f24a93c24679e
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying Adoption...
  ... 0x207859a3b740dc07200a2f0068adf25390044725ed4846d0b94efbd69f366104
  Adoption: 0xf979539e570d3d49bd2dc97741ab1df96339f132
Saving successful migration to network...
  ... 0xf3325f15f22d04ed92ca9b06bf802c9df13f5a182532980c508e8dd6d15e8be7
Saving artifacts...

Ganacheを見るとスマートコントラクトをデプロイする際のコストとして、ETHが消費されていることが分かります。

f:id:takuyafujita:20180819114005j:plain

これでプライベートネットワークにスマートコントラクトがデプロイされました。

【スマートコントラクトのテスト】

TruffleはJavaScriptやSolidityでテストコードが作成できます。チュートリアルでは、Solidityで書いています。testのディレクトリ配下に「TestAdoption.sol」を新規作成します。ファイルの中身は次の様に記載します。

pragma solidity ^0.4.17;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
  Adoption adoption = Adoption(DeployedAddresses.Adoption());

  // Testing the adopt() function
  function testUserCanAdoptPet() public {
    uint returnedId = adoption.adopt(8);

    uint expected = 8;

    Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
  }

  // Testing retrieval of a single pet's owner
  function testGetAdopterAddressByPetId() public {
    // Expected owner is this contract
    address expected = this;

    address adopter = adoption.adopters(8);

    Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
  }

  // Testing retrieval of all pet owners
  function testGetAdopterAddressByPetIdInArray() public {
    // Expected owner is this contract
    address expected = this;

    // Store adopters in memory rather than contract's storage
    address[16] memory adopters = adoption.getAdopters();

    Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
  }
}

以下のコマンドでテストを実行します。

truffle test

次の様なテスト結果が出力されます。

Using network 'development'.

Compiling .\contracts\Adoption.sol...
Compiling .\test\TestAdoption.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...

  TestAdoption
    √ testUserCanAdoptPet (55ms)
    √ testGetAdopterAddressByPetId (51ms)
    √ testGetAdopterAddressByPetIdInArray (73ms)

  3 passing (802ms)

すべてのテストが想定通りの結果になりましたので、テストは完了です。

ユーザインタフェースの作成】

バックエンド側の実装が完了しましたので、続いてフロント側を実装していきます。src/jsのディレクトリ配下にある「app.js」を開きます。必要な関数は全て準備されていますが中身がコメントになっています。コメント部分をチュートリアルに記載されているコードに置き換えていきます。置き換えるのは、initWeb3、initContract、markAdopted、handleAdoptの4箇所です。

以下、完成した「app.js」のコードになります。

App = {
  web3Provider: null,
  contracts: {},

  init: function() {
    // Load pets.
    $.getJSON('../pets.json', function(data) {
      var petsRow = $('#petsRow');
      var petTemplate = $('#petTemplate');

      for (i = 0; i < data.length; i ++) {
        petTemplate.find('.panel-title').text(data[i].name);
        petTemplate.find('img').attr('src', data[i].picture);
        petTemplate.find('.pet-breed').text(data[i].breed);
        petTemplate.find('.pet-age').text(data[i].age);
        petTemplate.find('.pet-location').text(data[i].location);
        petTemplate.find('.btn-adopt').attr('data-id', data[i].id);

        petsRow.append(petTemplate.html());
      }
    });

    return App.initWeb3();
  },

  initWeb3: function() {
    // Is there an injected web3 instance?
    if (typeof web3 !== 'undefined') {
      App.web3Provider = web3.currentProvider;
    } else {
      // If no injected web3 instance is detected, fall back to Ganache
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  },

  initContract: function() {
    $.getJSON('Adoption.json', function(data) {
      // Get the necessary contract artifact file and instantiate it with truffle-contract
      var AdoptionArtifact = data;
      App.contracts.Adoption = TruffleContract(AdoptionArtifact);

      // Set the provider for our contract
      App.contracts.Adoption.setProvider(App.web3Provider);

      // Use our contract to retrieve and mark the adopted pets
      return App.markAdopted();
    });

    return App.bindEvents();
  },

  bindEvents: function() {
    $(document).on('click', '.btn-adopt', App.handleAdopt);
  },

  markAdopted: function(adopters, account) {
    var adoptionInstance;

    App.contracts.Adoption.deployed().then(function(instance) {
      adoptionInstance = instance;

      return adoptionInstance.getAdopters.call();
    }).then(function(adopters) {
      for (i = 0; i < adopters.length; i++) {
        if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
          $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
        }
      }
    }).catch(function(err) {
      console.log(err.message);
    });
  },

  handleAdopt: function(event) {
    event.preventDefault();

    var petId = parseInt($(event.target).data('id'));

    var adoptionInstance;

    web3.eth.getAccounts(function(error, accounts) {
      if (error) {
        console.log(error);
      }

      var account = accounts[0];

      App.contracts.Adoption.deployed().then(function(instance) {
        adoptionInstance = instance;

        // Execute adopt as a transaction by sending account
        return adoptionInstance.adopt(petId, {from: account});
      }).then(function(result) {
        return App.markAdopted();
      }).catch(function(err) {
        console.log(err.message);
      });
    });
  }

};

$(function() {
  $(window).load(function() {
    App.init();
  });
});

【MetaMaskのインストールと設定】

ETHEREUM PET SHOPの実装は全て完了しましたが、取引する為にはMetaMaskが必要になります。

チュートリアルではGanacheに表示されるMnemonicを使用してMetaMaskのアカウントを作成していますが、メインネットでこのアカウントにETHを送信すると全て失います。

f:id:takuyafujita:20180819114029j:plain

私はMetaMaskのアカウントを別で作成していましたので、こじりょーさんのブログ(MetaMaskをプライベートネットに接続しようの箇所)を参考にして、Ganacheに表示されているアドレスをMetaMaskに取り込みました。この辺りは言葉の意味を理解できていないとセルフゴックス(操作ミスで仮想通貨を失う)の可能性があります。誰も責任を取ってくれませんので自己責任でお願いします。私の拙い文章では誤解される可能性がありますので詳細を省きます。

【ETHEREUM PET SHOPを表示】

以下のコマンドを実行すると、ETHEREUM PET SHOPがブラウザに表示されます。チュートリアルなので、lite-server(ローカルのWEBサーバ)の起動、Webサイトはsrcディレクトリ配下のファイルを使用、スマートコントラクトはbuild/contractsディレクトリ配下のファイルを使用するように設定済みでした。

npm run dev

遂にETHEREUM PET SHOPを表示することができました!

f:id:takuyafujita:20180819114044j:plain

早速、ペットを購入してみます。Adoptボタンをクリック!

f:id:takuyafujita:20180819114342j:plain

MetaMaskでETHを支払うと・・・

f:id:takuyafujita:20180819114350j:plain

Successに変わりました。正常に購入できました!

【まとめ】

環境構築に時間を費やしてしまいましたが、その後は止まることなく進めることができました。このチュートリアルを行うことで、TruffleやGanacheを使用してプライベートネットワークにデプロイする方法とプロジェクトの構成(フロント、バックエンド、デプロイに必要な設定など)が理解できました。新しい技術を勉強するのは楽しいですね。もっと時間が欲しい。