LoginSignup
1
1

More than 1 year has passed since last update.

【Ethereum】Docker環境でCryptoZombies

Last updated at Posted at 2020-04-05

今、Ethereum、Solidity に非常に興味がありますが、そのきっかけがCryptoZombiesを一度やってみたことにあるのは間違いありません。

ただ、整えられた環境で練習しているという感じで、そこからどう展開できるのかよく分かりませんでした。

そこで、自分の環境で作ってみることができるのか、試してみたいと思います。

Truffle Boxes

環境は、Truffle Boxes で作成します。
Truffle Boxes

といっても色々ある訳ですが、今回は、DRIZZLE-VUE-BOX を使用してみます。Vueももう少し鍛えたいので。
CryptoZombiesフォルダに、docker-compose.yml、Dockerfile を作成します。

docker-compose.yml
version: '3'

services:
  truffle:
    build: 
      context: ./trufflebox/
      dockerfile: Dockerfile
    volumes:
      - ./trufflebox:/usr/src/app
    command: sh -c "cd vapp && npm run serve"
    ports:
      - "8004:8080"
Dockerfile
FROM node:8-alpine  

RUN apk add --update alpine-sdk
RUN apk add --no-cache git python g++ make \
    && npm i -g --unsafe-perm=true --allow-root truffle 

WORKDIR /usr/src/app

この2ファイルを下記構成で作成。

CryptoZombies
├── docker-compose.yml
└── trufflebox
    └── Dockerfile

CryptoZombies フォルダで、ビルドしてイメージ作成。

$ docker-compose build
Building truffle
Step 1/4 : FROM node:8-alpine
 ---> 2b8fcdc6230a
Step 2/4 : RUN apk add --update alpine-sdk
 ---> Using cache
 ---> 761342077e72
Step 3/4 : RUN apk add --no-cache git python g++ make     && npm i -g --unsafe-perm=true --allow-root truffle
 ---> Using cache
 ---> 82200b1b0c8f
Step 4/4 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 4eb121f5853d
Successfully built 4eb121f5853d
Successfully tagged cryptozombies_truffle:latest

vue-box トリュフボックスをインストール。
大量にログが出てしまったので、一部省略します。

$ docker-compose run truffle truffle unbox vue-box

Creating network "cryptozombies_default" with the default driver
You can improve web3's performance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project
This directory is non-empty...
? Proceed anyway? (Y/n) 
Starting unbox...
=================

? Proceed anyway? Yes
✔ Preparing to download box
✔ Downloading
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN drizzle-vue-box@0.0.1 No description
npm WARN drizzle-vue-box@0.0.1 No repository field.

npm WARN deprecated mkdirp@0.5.4: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated core-js@2.6.11: core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.
npm WARN deprecated circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor.
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
In file included from /root/.cache/node-gyp/8.17.0/include/node/node.h:63,
                 from ../src/addon.cc:1:
/root/.cache/node-gyp/8.17.0/include/node/v8.h: In static member function 'static void v8::V8::SetAllowCodeGenerationFromStringsCallback(v8::DeprecatedAllowCodeGenerationFromStringsCallback)':
/root/.cache/node-gyp/8.17.0/include/node/v8.h:10304:19: warning: cast between incompatible function types from 'v8::DeprecatedAllowCodeGenerationFromStringsCallback' {aka 'bool (*)(v8::Local<v8::Context>)'} to 'v8::FreshNewAllowCodeGenerationFromStringsCallback' {aka 'bool (*)(v8::Local<v8::Context>, v8::Local<v8::String>)'} [-Wcast-function-type]
10304 |           callback));
      |                   ^
(省略)
 9313 |                reinterpret_cast<Callback>(callback), type);
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/root/.cache/node-gyp/8.17.0/include/node/v8.h: In instantiation of 'void v8::PersistentBase<T>::SetWeak(P*, typename v8::WeakCallbackInfo<P>::Callback, v8::WeakCallbackType) [with P = Nan::ObjectWrap; T = v8::Object; typename v8::WeakCallbackInfo<P>::Callback = void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)]':
../../nan/nan_object_wrap.h:65:61:   required from here
/root/.cache/node-gyp/8.17.0/include/node/v8.h:9313:16: warning: cast between incompatible function types from 'v8::WeakCallbackInfo<Nan::ObjectWrap>::Callback' {aka 'void (*)(const v8::WeakCallbackInfo<Nan::ObjectWrap>&)'} to 'Callback' {aka 'void (*)(const v8::WeakCallbackInfo<void>&)'} [-Wcast-function-type]
You can improve the performance of scrypt by upgrading to Node.js version 10.5.0 or newer, or by installing the (deprecated) scrypt package in your project
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN notsup Unsupported engine for mkdirp@1.0.3: wanted: {"node":">=10"} (current: {"node":"8.17.0","npm":"6.13.4"})
npm WARN notsup Not compatible with your version of node/npm: mkdirp@1.0.3
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.2.7 (node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.12: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
(省略)
npm WARN enoent SKIPPING OPTIONAL DEPENDENCY: ENOENT: no such file or directory, rename '/usr/src/app/vapp/node_modules/fsevents/node_modules/yallist' -> '/usr/src/app/vapp/node_modules/fsevents/node_modules/.yallist.DELETE'

✔ cleaning up temporary files
✔ Setting up box

Unbox successful, sweet!

Commands:

  Compile contracts: truffle compile
  Migrate contracts: truffle migrate
  Test contracts:    truffle test

成功はしているので大丈夫なのだろうか。

ここでできた、truffle-config.js は次のようになっています。ネットワークの記述はないです。

truffle-config.js
const path = require('path')
module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
  contracts_build_directory: path.join(__dirname, "vapp/src/contracts"),

};

いったん、メッセージ、および、DRIZZLE-VUE-BOXのページにしたがって、migrateします。

$ docker-compose run truffle truffle migrate
You can improve web3's performance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project

Compiling your contracts...
===========================
> Compiling ./contracts/ComplexStorage.sol
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/SimpleStorage.sol
> Compiling ./contracts/TutorialToken.sol
> Compiling openzeppelin-solidity/contracts/GSN/Context.sol
> Compiling openzeppelin-solidity/contracts/math/SafeMath.sol
> Compiling openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
> Compiling openzeppelin-solidity/contracts/token/ERC20/IERC20.sol
> Artifacts written to /usr/src/app/vapp/src/contracts
> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang

> Something went wrong while attempting to connect to the network. Check your network configuration.

Could not connect to your Ethereum client with the following parameters:
    - host       > 127.0.0.1
    - port       > 7545
    - network_id > 5777
Please check that your Ethereum client:
    - is running
    - is accepting RPC connections (i.e., "--rpc" option is used in geth)
    - is accessible over the network
    - is properly configured in your Truffle configuration file (truffle-config.js)

Truffle v5.1.18 (core: 5.1.18)
Node v8.17.0

コンパイルは成功しましたが、やはりネットワークに接続できませんので、truffle-config.js を修正します。

truffle-config.js(変更)
const path = require('path')
module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
  contracts_build_directory: path.join(__dirname, "vapp/src/contracts"),
  networks: {
    development: {
      host: "10.200.10.1",
      port: 7545,
      network_id: "*" // Match any network id
    }
  }
};

※port7545に接続するあたりは、Dockerで構築するEthereum PET-SHOP TRUFFLE BOXES(その1)をご参照ください。

もう一度migration。

$ docker-compose run truffle truffle migrate
You can improve web3's performance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.



Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 0x6691b7


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x06542d04e9a633fdb9e67c0cefe89eca782af6dd2b0c8ad16702d3129a2e0465
   > Blocks: 0            Seconds: 0
   > contract address:    0xE0f904185d649bb178312968f8f304C46254b22A
   > block number:        1
   > block timestamp:     1585745130
   > account:             0xc55F3d6C444ca88f529F3413EDEd85a39e38609C
   > balance:             99.99623034
   > gas used:            188483
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00376966 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00376966 ETH


2_deploy_contracts.js
=====================

   Deploying 'SimpleStorage'
   -------------------------
   > transaction hash:    0x4d4f1762f60a44c8d5b80c6620911504a991b264dd62fd655b8ccae8df823f21
   > Blocks: 0            Seconds: 0
   > contract address:    0x1B520b74895deE2c71512071863f56fC62CF3A8b
   > block number:        3
   > block timestamp:     1585745131
   > account:             0xc55F3d6C444ca88f529F3413EDEd85a39e38609C
   > balance:             99.99270882
   > gas used:            134075
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0026815 ETH


   Deploying 'TutorialToken'
   -------------------------
   > transaction hash:    0x7360523d1806ff66ff5e673f7b8bc445b2637a4f9789e11931b04299b53a9849
   > Blocks: 0            Seconds: 0
   > contract address:    0x7F5D4B8B3D379A2c60b76e813B392266a936B51b
   > block number:        4
   > block timestamp:     1585745131
   > account:             0xc55F3d6C444ca88f529F3413EDEd85a39e38609C
   > balance:             99.96507908
   > gas used:            1381487
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.02762974 ETH


   Deploying 'ComplexStorage'
   --------------------------
   > transaction hash:    0x819f1e8cc4ce76c3d5478aa6d39fd65fa731d2c322510138e5e3c21cd6815b9b
   > Blocks: 0            Seconds: 0
   > contract address:    0xF88b3D9805da39D094178f2ba0dCc38a0610d214
   > block number:        5
   > block timestamp:     1585745132
   > account:             0xc55F3d6C444ca88f529F3413EDEd85a39e38609C
   > balance:             99.93307126
   > gas used:            1600391
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.03200782 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.06231906 ETH


Summary
=======
> Total deployments:   4
> Final cost:          0.06608872 ETH

できました!

では、起動します。こちらも、大量のログは省略。

$ docker-compose up
(省略)
truffle_1  |  DONE  Compiled successfully in 52507ms12:53:10
truffle_1  | 
truffle_1  | <s> [webpack.Progress] 100% 
truffle_1  | 
truffle_1  | 
truffle_1  |   App running at:
truffle_1  |   - Local:   http://localhost:8080/ 
truffle_1  | 
truffle_1  |   It seems you are running Vue CLI inside a container.
truffle_1  |   Access the dev server via http://localhost:<your container's external mapped port>/
truffle_1  | 
truffle_1  |   Note that the development build is not optimized.
truffle_1  |   To create a production build, run npm run build.
truffle_1  | 

できました!

ブラウザで、
http://localhost:8004/
を開きます。画面が開きます!

スクリーンショット 2020-04-01 22.02.33.png

Truffle Boxes で、基礎となる環境ができました。

Lesson 1 ゾンビファクトリーの作成

CryptoZombies の、Lesson 1 ゾンビファクトリーの作成からやっていきます。

といっても、一度やっているので、一気に最後まで書き上げます。
trufflebox/contracts/Contract.sol ファイルを作成します。

Contract.sol
pragma solidity ^0.4.19;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        NewZombie(id, _name, _dna);
    } 

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

キーワードとしては、配列、構造体、関数、イベント、といったところです。

Lesson 2 ゾンビが人間を襲う

まずは、Contract.sol に、さらに機能を追加します。

Contract.sol
pragma solidity ^0.4.19;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

contract ZombieFeeding is ZombieFactory {

}

キーワードとしては、アドレス、Mappings(マッピング)、msg.sender、Require、継承、といったものです。

そして、ここで継承を使って、Contract を二つに分けましたので、ファイルも分けます。
Contract.sol は、名前をzombiefactory.sol とします。
さらに、zombiefeeding.sol を作成し、以下のようにします。

zombiefactory.sol
pragma solidity ^0.4.19;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}
zombiefeeding.sol
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);

  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    if (keccak256(_species) == keccak256("kitty")) {
      newDna = newDna - newDna % 100 + 99;
    }
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

}

キーワードとしては、Interface、複数の返り値、などです。

Lesson 3 Solidityの高度なコンセプト

ここで、OpenZeppelin が登場します。Ownable コントラクトを使うためです。
Ownable のコードは、ownable.sol にコピーして使うそうです。

ownable.sol
/**
 * @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.
   */
  function Ownable() 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 {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}

そして、ZombieFactory は、Ownable を継承し、onlyOwner関数修飾子 を使えるようにします。
さらに機能追加。キーワードは、ガス、時間計算。

zombiefactory.sol
pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
    uint cooldownTime = 1 days;

    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}
zombiefeeding.sol
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  KittyInterface kittyContract;

  function setKittyContractAddress(address _address) external onlyOwner {
    kittyContract = KittyInterface(_address);
  }

  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    if (keccak256(_species) == keccak256("kitty")) {
      newDna = newDna - newDna % 100 + 99;
    }
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

}

そして、さらに、zombiehelper.sol

zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}

キーワードとしては、関数修飾子、View 関数でガスを節約、Storageのコストは高い、memory、For ループ、といった内容。

Lesson 4 ゾンビのバトルシステム

バトルのためのロジックを書くために、zombieattack.sol を作成します(コントラクトの名前は、ZombieBattle なんですけどね)。
それに伴い、zombiehelper.sol に支払い関係の記述を追加し、zombiefeeding.sol には、呼び出し制限のためのmodifierを追加。zombiefactory.sol のZombie構造体にこれらの情報を保持するプロパティを追加。

zombiehelper.sol
pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}
zombiefeeding.sol
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory {

  KittyInterface kittyContract;

  modifier ownerOf(uint _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    _;
  }

  function setKittyContractAddress(address _address) external onlyOwner {
    kittyContract = KittyInterface(_address);
  }

  function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
  }

  function _isReady(Zombie storage _zombie) internal view returns (bool) {
      return (_zombie.readyTime <= now);
  }

  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    require(_isReady(myZombie));
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    if (keccak256(_species) == keccak256("kitty")) {
      newDna = newDna - newDna % 100 + 99;
    }
    _createZombie("NoName", newDna);
    _triggerCooldown(myZombie);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }
}
zombiefactory.sol
pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
    uint cooldownTime = 1 days;

    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
      uint16 winCount;
      uint16 lossCount;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    function _createZombie(string _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }

    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}
zombieattack.sol
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else {
      myZombie.lossCount++;
      enemyZombie.winCount++;
      _triggerCooldown(myZombie);
    }
  }
}

Lesson 5 ERC721とクリプト収集物

ERC721メソッド用のzombieownership.sol と、用意してくれている、erc721.sol を追加。
zombiefeeding.sol で定義していたownerOfという修飾詞名はERC721トークン規格と被るので同じ名前の修飾詞と関数を持つことはできないとエラーを出すそうです。
ここまでのところで一度コンパイルしてみます。

erc721.sol
contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

  function balanceOf(address _owner) public view returns (uint256 _balance);
  function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256 _tokenId) public;
  function approve(address _to, uint256 _tokenId) public;
  function takeOwnership(uint256 _tokenId) public;
}
zombieownership.sol
pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  function transfer(address _to, uint256 _tokenId) public {

  }

  function approve(address _to, uint256 _tokenId) public {

  }

  function takeOwnership(uint256 _tokenId) public {

  }
}

起動してあったdocker-compose up で起動中の環境を、Ctrl+Cで終了。バックグラウンド起動して、Docker環境のshellに入ります。

$ docker-compose up -d
$ docker-compose run truffle sh
/usr/src/app # truffle compile
You can improve web3's performance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project

Compiling your contracts...
===========================
> Compiling ./contracts/erc721.sol
> Compiling ./contracts/ownable.sol
> Compiling ./contracts/zombieattack.sol
> Compiling ./contracts/zombiefactory.sol
> Compiling ./contracts/zombiefeeding.sol
> Compiling ./contracts/zombiehelper.sol
> Compiling ./contracts/zombieownership.sol

/usr/src/app/contracts/zombieattack.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.5.16+commit.9c3226ce.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.19;
^----------------------^
,/usr/src/app/contracts/zombiefactory.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.5.16+commit.9c3226ce.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.19;
^----------------------^
,/usr/src/app/contracts/zombiefeeding.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.5.16+commit.9c3226ce.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.19;
^----------------------^
,/usr/src/app/contracts/zombiehelper.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.5.16+commit.9c3226ce.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.19;
^----------------------^
,/usr/src/app/contracts/zombieownership.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.5.16+commit.9c3226ce.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.19;
^----------------------^

Error: Truffle is currently using solc 0.5.16, but one or more of your contracts specify "pragma solidity ^0.4.19".
Please update your truffle config or pragma statement(s).
(See https://truffleframework.com/docs/truffle/reference/configuration#compiler-configuration for information on
configuring Truffle to use a specific solc compiler version.)

Compilation failed. See above.
Truffle v5.1.18 (core: 5.1.18)
Node v8.17.0

おっと。
全部サンプルに合わせて
pragma solidity >=0.4.21 <0.6.0;
に変更して再度。

/usr/src/app # truffle compile
You can improve web3's performance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project

Compiling your contracts...
===========================
> Compiling ./contracts/erc721.sol
> Compiling ./contracts/ownable.sol
> Compiling ./contracts/zombieattack.sol
> Compiling ./contracts/zombiefactory.sol
> Compiling ./contracts/zombiefeeding.sol
> Compiling ./contracts/zombiehelper.sol
> Compiling ./contracts/zombieownership.sol

> Compilation warnings encountered:

    /usr/src/app/contracts/erc721.sol:1:1: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.5.16;"
contract ERC721 {
^ (Relevant source part starts here and spans across multiple lines).
,/usr/src/app/contracts/ownable.sol:6:1: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.5.16;"
contract Ownable {
^ (Relevant source part starts here and spans across multiple lines).
,/usr/src/app/contracts/ownable.sol:15:3: Warning: This declaration shadows an existing declaration.
  function Ownable() public {
  ^ (Relevant source part starts here and spans across multiple lines).
/usr/src/app/contracts/ownable.sol:6:1: The shadowed declaration is here:
contract Ownable {
^ (Relevant source part starts here and spans across multiple lines).

/usr/src/app/contracts/ownable.sol:15:3: SyntaxError: Functions are not allowed to have the same name as the contract. If you intend this to be a constructor, use "constructor(...) { ... }" to define it.
  function Ownable() public {
  ^ (Relevant source part starts here and spans across multiple lines).
,/usr/src/app/contracts/zombiefactory.sol:27:28: TypeError: Data location must be "storage" or "memory" for parameter in function, but none was given.
    function _createZombie(string _name, uint _dna) internal {
                           ^----------^
,/usr/src/app/contracts/zombiefactory.sol:34:33: TypeError: Data location must be "storage" or "memory" for parameter in function, but none was given.
    function _generateRandomDna(string _str) private view returns (uint) {
                                ^---------^
,/usr/src/app/contracts/zombiefactory.sol:39:33: TypeError: Data location must be "memory" for parameter in function, but none was given.
    function createRandomZombie(string _name) public {
                                ^----------^

Compilation failed. See above.
Truffle v5.1.18 (core: 5.1.18)
Node v8.17.0

そもそも、wnable.sol がエラーになってしまっている。そうね、0.4からだと色々変わっているかもですね。最新だとかなり変わっているように見えるので、ほどほどっぽい(なんだそれ)こちらのを参考にさせていただきます。
https://takuyafujita.hatenablog.com/entry/2018/08/04/173529
また、erc721.sol と、ownable.sol にも、
pragma solidity >=0.4.21 <0.6.0;
は付けておきます。
さらに、0.5ではmemoryの記述が必要になっているということで、zombiefactory.sol、zombiefeeding.sol、zombiehelper.sol も修正しておきます。
その他、色々ありました!!(順番に書いているというよりは、最終ソースから消していった感じかな・・・ZombieAttackはやはりZombieAttackだよねとか)

などなど、エラーメッセージを見ながら対応していくと、こんなエラーが出て、これが言いたかったことだなと思います。

/usr/src/app # truffle compile
You can improve web3's performance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project

Compiling your contracts...
===========================
> Compiling ./contracts/erc721.sol
> Compiling ./contracts/ownable.sol
> Compiling ./contracts/zombieattack.sol
> Compiling ./contracts/zombiefactory.sol
> Compiling ./contracts/zombiefeeding.sol
> Compiling ./contracts/zombiehelper.sol
> Compiling ./contracts/zombieownership.sol

/usr/src/app/contracts/zombiefeeding.sol:24:3: DeclarationError: Identifier already declared.
  modifier ownerOf(uint _zombieId) {
  ^ (Relevant source part starts here and spans across multiple lines).
/usr/src/app/contracts/zombieownership.sol:12:3: The previous declaration is here:
  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
  ^ (Relevant source part starts here and spans across multiple lines).
,/usr/src/app/contracts/zombiefeeding.sol:24:3: TypeError: Override changes modifier to function.
  modifier ownerOf(uint _zombieId) {
  ^ (Relevant source part starts here and spans across multiple lines).
,/usr/src/app/contracts/zombiefactory.sol:31:9: TypeError: Event invocations have to be prefixed by "emit".
        NewZombie(id, _name, _dna);
        ^------------------------^
,/usr/src/app/contracts/zombiefactory.sol:35:36: TypeError: Invalid type for argument in function call. Invalid implicit conversion from string memory to bytes memory requested. This function requires a single bytes argument. Use abi.encodePacked(...) to obtain the pre-0.5.0 behaviour or abi.encode(...) to use ABI encoding.
        uint rand = uint(keccak256(_str));
                                   ^--^
,/usr/src/app/contracts/zombiefeeding.sol:46:19: TypeError: Invalid type for argument in function call. Invalid implicit conversion from string memory to bytes memory requested. This function requires a single bytes argument. Use abi.encodePacked(...) to obtain the pre-0.5.0 behaviour or abi.encode(...) to use ABI encoding.
    if (keccak256(_species) == keccak256("kitty")) {
                  ^------^
,/usr/src/app/contracts/zombiehelper.sol:15:20: TypeError: Member "balance" not found or not visible after argument-dependent lookup in contract ZombieHelper. Use "address(this).balance" to access this address member.
    owner.transfer(this.balance);
                   ^----------^

Compilation failed. See above.
Truffle v5.1.18 (core: 5.1.18)
Node v8.17.0

抜粋するとここですね。

/usr/src/app/contracts/zombiefeeding.sol:24:3: DeclarationError: Identifier already declared.
  modifier ownerOf(uint _zombieId) {
  ^ (Relevant source part starts here and spans across multiple lines).
/usr/src/app/contracts/zombieownership.sol:12:3: The previous declaration is here:
  function ownerOf(uint256 _tokenId) public view returns (address _owner) {

といういことで、zombiefeeding.sol も修正しましょうという流れですが、0.5対応の方が大変じゃないかという流れではあります。いったん、このエラーだけは解消しておきます。

ここまでのキーワードとしては、ERC20トークンとERC721トークン、多重継承、など。

さて、ここで、ソースを0.5対応板にしてしまったので、連続性が途絶えます。
今普通に公開されているのは、Lesson6 までですが、githubでは9まで行ってますし、英語だと13まで行っています。
https://github.com/loomnetwork/cryptozombie-lessons/tree/master/jp
https://github.com/loomnetwork/cryptozombie-lessons/tree/master/en

是非そこまで続けてみようと思いますし、そう考えると、動くか動かないかわからないものをただ書き続けるのも辛いので、ここで一度切って、ここからは、solidity0.5対応版として、改めて、続けていきたいと思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1