【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を使用してプライベートネットワークにデプロイする方法とプロジェクトの構成(フロント、バックエンド、デプロイに必要な設定など)が理解できました。新しい技術を勉強するのは楽しいですね。もっと時間が欲しい。

【TruffleでDapp開発】「truffle unbox pet-shop」を実行すると「Downloading...」で止まる

【はじめに】

イーサリアムを購入してからブロックチェーンに興味を持ち、休みの日はDapp開発の勉強をしています。技術情報を調べていると【Truffle】というイーサリアム開発のフレームワークに出会いました。早速触ってみましたが、インストールでエラーが発生したり環境構築で四苦八苦しています。

【ETHEREUM PET SHOP】

先回、何とかTruffleのインストールが完了したので、公式サイトにあるチュートリアルETHEREUM PET SHOP」をダウンロードしてみました。

【ダウンロード】

Truffleのインストールが完了していると、下記のコマンドで「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...」で止まり、先に進んでいる様子がありません。。。

【原因】

セキュリティソフトが原因でした。Webのトラフィックを監視する機能を無効にして再度、コマンドを実行すると「Unbox successful. Sweet!」が表示されました。気付いたら簡単な事ですが、なかなか気付けず2~3時間悩んでしまいました。まだまだ先が長い。

【TruffleでDapp開発】Truffleのコマンド「truffle version」でSyntaxError

【はじめに】

イーサリアムを購入してからブロックチェーンに興味を持ち、休みの日はDapp開発の勉強をしています。技術情報を調べていると【Truffle】というイーサリアム開発のフレームワークに出会いました。早速触ってみましたが、インストール時点でエラーが発生しましたので原因と対応方法をまとめておきます。

【Truffleのインストール】

nodeとnpmが使える環境で以下のコマンドを実行します。

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

【原因と対応方法】

原因と対応方法については、この記事が参考になります。

trueman-developer.blogspot.com

以下のいずれかの方法で対応可能であることが分かりましたので、nodeのバージョンをあげることにしました。Truffleのインストールなのでnodeのバージョンアップしか選択肢がありません。

  1. "use strict"(厳格モード)を使用する
  2. 実行時に起動オプション(--use_strict)をつける
  3. nodeのバージョンをあげる(6.x 以上)

【nodeのバージョンアップ】

上げる前のバージョン:v5.10.1
上げた後のバージョン:v10.8.0

nodeのバージョンは以下のコマンドで確認できます。

node -v

今回はnodeのバージョン管理ツール「nodist」を使いました。以下の記事が参考になります。

webbibouroku.com

手順1

github.com

上記サイトからインストーラーをダウンロードして、nodistのインストールを行います。インストール後は端末を再起動します。

手順2

再起動後、以下のコマンドでnodistのバージョンを確認します。

nodist -v

nodistのバージョンが表示すればインストール完了です。

0.8.8

手順3

nodeのバージョンを指定してインストールを行います。以下のコマンドでインストールできるバージョンが確認できます。

nodist dist

手順4

バージョンを指定してインストールを行います。今回はv10.8.0にしました。

nodist + 10.8.0

手順5

使用するnodeのバージョンに切り替えを行います。nodistはnodeのバージョン管理ツールなので簡単に切り替えができます。

nodist 10.8.0

手順6

正しく切り替わっているか確認します。以下のコマンドでnodeのバージョンを確認します。

node -v

変わっていない。。。

v5.10.1

nodist経由以外でnodeをインストールしている場合、アンインストールする必要がありました。ということで、v5.10.1のnodeをアンインストールします。windowsの設定>アプリと機能からnodeをアンインストールしました。

再度、nodeのバージョンを確認します。

node -v

バージョンがv10.8.0になりました!

v10.8.0

【Truffleのバージョン確認】

Truffleのバージョンを確認します。

truffle version

おぉ、SyntaxErrorではなくTruffleとSolidityのバージョンが表示されました。nodeのバージョンが古すぎたのが原因でしたね。これでTruffleのインストールは完了です。

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

【Solidity】OpenZeppelinのOwnableコントラクトとは

【はじめに】

この記事はSolidityを勉強する為にCryptoZombiesで学んだ事を自分用のメモとして書いています。

【CryptoZombiesとは】

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

cryptozombies.io

【OpenZeppelinのOwnableコントラクトとは】

Lesson3でOpenZeppelinのOwnableコントラクトが出てきました。
Solidityの勉強で重要と思いましたので記事にしておきます。

OpenZeppelinとは?

Ethereum上で安全なスマートコントラクトを実装するためのフレームワーク

github.com

Ownableコントラクトとは?

OpenZeppelinの1つで、コントラクトの実行権限を限定することができる。
実行権限を管理しておかないと誰でも実行できる状態になり悪用される恐れがある。
Ownableコントラクトを継承すれば、コントラクトを作成した人しか実行できないようにすることが可能。

【Ownableコントラクト】

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;

  event OwnershipTransferred(
    address indexed previousOwner,
    address indexed newOwner
  );

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  constructor() public {
    owner = msg.sender;
  }

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

  /**
   * @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);
  }

  /**
   * @dev Transfers control of the contract to a newOwner.
   * @param _newOwner The address to transfer ownership to.
   */
  function _transferOwnership(address _newOwner) internal {
    require(_newOwner != address(0));
    emit OwnershipTransferred(owner, _newOwner);
    owner = _newOwner;
  }
}

constructor

Ownableコントラクトのコンストラク
コントラクト作成時に1度だけ実行されるので、作成者のアドレスをownerに格納することができる。

modifier onlyOwner

onlyOwnerを関数につけることで使用できる。
コントラクトを呼び出したアドレスと作成者のアドレスが同じでない場合、エラーになる。
つまり、作成者以外が呼び出しても実行できなくなる。

transferOwnership

ownerのアドレスを変更することで実行権限を移行することができる。

【まとめ】

OpenZeppelinはSolidityの開発で頻繁に使われているので、自分で作成するよりもセキュリティ面で安心して使用できます。
Ownable以外にも便利なコントラクトがありそうなので、もっと勉強しなければ。

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

【CryptoZombiesとは】

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

cryptozombies.io

【はじめに】

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

【学んだこと】

  • データ型のmappingとaddress
    • mappingはKVS(Key-Value Store)
    • addressはEthereumのアドレス
'mappingの書き方 Key => Value
mapping (address => uint) public accountBalance;
mapping (uint => string) userIdToName;
  • msg.sender

その関数を呼び出したユーザー(またはスマートコントラクト)のaddressを取得する。

mapping (address => uint) accountBalance;

function setBalance(uint _myBalance) public {
  accountBalance[msg.sender] = _myBalance;
}

function whatIsMyBalance() public view returns (uint) {
  return accountBalance[msg.sender];
}
  • Require

条件を満たさない場合、エラーを投げる。

'挨拶(_greeting)が"Hello"の場合、"Hello!"を返す。
'"Hello"以外の場合、エラーになる。
function sayHello(string _greeting) public returns (string) {
  require(keccak256(_greeting) == keccak256("Hello"));
  return "Hello!";
}
  • 継承
'BabyDogからtype()も実行可能
contract Dog {
  function type() public returns (string) {
    return "puppy";
  }
}

contract BabyDog is Dog {
  function age() public returns (string) {
    return "1 year old";
  }
}
  • Import

別ファイルに定義されているコントラクトをImportで呼び出し、
継承すれば使用可能になる。

import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}
  • 変数のStorageとMemory
    • Storageはブロックチェーン上に永久に格納される。
    • Memoryは関数呼び出し時に消去される。
  • InternalとExternal

関数宣言はprivateやpublicの宣言方法と同じ。
Internalはprivateと同じだが、このコントラクトから継承したコントラクトにもアクセスできる。
Externalはpublicと同じだが、コントラクトの外からだけ呼び出すことができる。
コントラクト内部の別の関数から呼び出すことはできない。

  function eat() internal {
  }

  function drink() external {
  }
  • interface

ブロックチェーン上の他人のコントラクトとやりとりする時に使用する。

'Ethereumのアドレスと電話番号を紐づけるコントラクト
contract PhoneNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}
'別のコントラクトからPhoneNumberのコントラクトを使用したい場合、Interfaceを定義する。
contract PhoneNumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

contract MyContract {
  address PhoneNumberInterfaceAddress = 0x......;
  PhoneNumberInterface phoneNumberContract = PhoneNumberInterface(PhoneNumberInterfaceAddress);

  function someFunction() public {
    uint num = phoneNumberContract.getNum(msg.sender);
  }
}
  • 複数の返り値

返り値は複数指定することができる。
特定の返り値のみ取得する場合は不要な返り値をブランクにする。

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  (a, b, c) = multipleReturns();
}

function getLastReturnValue() external {
  uint c;
  (,,c) = multipleReturns();
}
  • If

JavaScriptと同じ

【できたもの】

ゾンビにクリプトキティを食べさせると進化する。

f:id:takuyafujita:20180729182042j:plain
f:id:takuyafujita:20180729182220j:plain