Hardhatで始めるDapps開発2

どうも、次世代デジタル基盤開発事業部の土田です。

本投稿は TECOTEC Advent Calendar 2022 の1日目の記事です。

去年もトップバッターでした。一年って早いものですね。さて、今回はいつまで経っても終わっていなかったCryptoZombieを終わらせようと思います。タスクの年末大掃除です!

CryptoZombieは下記です。 cryptozombies.io

これまでのシリーズは下記の通りです。
TruffleSuiteで始めるDapps開発 - テコテック開発者ブログ
TruffleSuiteで始めるDapps開発2 - テコテック開発者ブログ
TruffleSuiteで始めるDapps開発3 - テコテック開発者ブログ
Hardhatで始めるDapps開発 - テコテック開発者ブログ

タイトルの通り、前回セットアップしたHardhatを使っていきます。

はじめに

基本的に各チャプター通りに進めてみましたが、難しいポイントはあまりなかったと思います。(何故か翻訳が一部韓国語になっていたり、ヒントが見れなかったりとサイトに問題はありましたが・・・。)
前回からSolidityのバージョンを0.8.9にして進めていたので、0.8.9を使って進めた際にコンパイルエラーになったり、分かりづらく感じたりしたポイントを重点的に記録していこうと思います。

筆者環境

Windows10
WSL2(Windows Subsystem for Linux)
Ubuntu 20.04
MetaMask
Chrome
Hardhat 2.10.1
Solidity 0.8.9

Chapter2 ゾンビが人を襲う

Chapter2では、クリプトキティのアドレスやコードを使用する箇所があります。
cryptozombies.io

CryptoZombie側でgetKitty関数を用意してくれているものの、ローカル環境で開発したかったので、下記のように自前でKittyCore.solとして用意しました。 genesが取れれば以降のチャプターは問題なく進めることができるはずです。

pragma solidity ^0.8.9;

contract KittyCore {
    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
    ) {
        // https://api.cryptokitties.co/kitties/1
        isGestating = false;
        isReady = true;
        cooldownIndex = 0;
        nextActionAt = 0;
        siringWithId = 0;
        birthTime = 1511417999;
        matronId = 0;
        sireId = 0;
        generation = 0;
        genes = 626837621154801616088980922659877168609154386318304496692374110716999053;
    }
}

Chapter2-7 ストレージ vs メモリ

cryptozombies.io

Function state mutability can be restricted to view solidity(2018)

エラーの通り、viewを指定する必要があります。

// Before
function feedAndMultiply(uint _zombieId, uint _targetDna) public {
// After
function feedAndMultiply(uint _zombieId, uint _targetDna) public view {

Chapter2-10 ゾンビは何を食べるのか?

cryptozombies.io

Contract "KittyInterface" should be marked as abstract.solidity(3656)

abstractで対応しましたが、interfaceでも構いません。

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

Chapter2-13 ボーナス: Kitty Genes

cryptozombies.io

Data location must be "memory" or "calldata" for parameter in function, but none was given.solidity(6651)

下記でも発生していましたが、メッセージが若干変わっていますかね?今回はmemoryを指定します。以降、このエラーについては割愛します。

tec.tecotec.co.jp

// Before
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
// After
function feedAndMultiply(uint _zombieId, uint _targetDna, string memory _species) public {

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.solidity(7556)

下記でも発生していましたが、keccak256を使用する際にbytesを明示的に指定する必要があります。以降、このエラーについても割愛します。

tec.tecotec.co.jp

// Before
if (keccak256(_species) == keccak256("kitty")) {
// After
if (keccak256(bytes(_species)) == keccak256("kitty")) {

Chapter3 Solidityの高度なコンセプト

3-2 Ownable コントラクト

cryptozombies.io

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.solidity(5796)

こちらはサンプルで提供されているownable.solの修正になります。今回はコンストラクタにすることで対応しました。

// Before
function Ownable() public {
    owner = msg.sender;
}
// After
constructor () {
    owner = msg.sender;
}

Event invocations have to be prefixed by "emit".solidity(3132)

下記でも発生していましたが、emitを明示的に指定します。以降、このエラーについても割愛します。

tec.tecotec.co.jp

// Before
OwnershipTransferred(owner, newOwner);
// After
emit OwnershipTransferred(owner, newOwner);

3-5 時間の単位

cryptozombies.io

"now" has been deprecated. Use "block.timestamp" instead.solidity(7359)

解決方法がそのまま記載してありますが、nowが使えなくなったようです。 ethereum.stackexchange.com

// Before
zombies.push(Zombie(_name, _dna, 1, uint32(now+ cooldownTime)));
// After
zombies.push(Zombie(_name, _dna, 1, uint32(block.timestamp + cooldownTime)));

3-6 ゾンビのクールダウン

cryptozombies.io

"now" has been deprecated. Use "block.timestamp" instead.solidity(7359)

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

function _isReady(Zombie storage _zombie) internal view returns (bool) {
    return (_zombie.readyTime <= now);
}
// After
function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(block.timestamp + cooldownTime);
}

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

3-10 View 関数でガスを節約

cryptozombies.io

Data location must be "memory" or "calldata" for return parameter in function, but none was given.solidity(6651)

戻り値のほうにも指定する必要があります。

// Before
function getZombiesByOwner(address _owner) external view returns (uint[]) {
// After
function getZombiesByOwner(address _owner) external view returns (uint[] memory) {

Chapter4 ゾンビのバトルシステム

4-2 Withdraw関数

cryptozombies.io

Member "balance" not found or not visible after argument-dependent lookup in contract ZombieHelper. Use "address(this).balance" to access this address member.solidity(3125)

上記に対応すると下記も出てきます。

"send" and "transfer" are only available for objects of type "address payable", not "address".solidity(9862)

対応方法までしっかり書いてあって親切ですね!それぞれ対応します。

// Before
function withdraw() external onlyOwner {
  owner.transfer(this.balance);
}
// After
function withdraw() external onlyOwner {
    payable(owner).transfer(address(this).balance);
}

4-4 乱数

cryptozombies.io

Wrong argument count for function call: 3 arguments given but expected 1. 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.solidity(4323)

abi.encodePackedで対応します。nowも忘れずにblock.timestampにすること!

// Before
function randMod(uint _modulus) internal returns (uint) {
    randNonce++;
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}
// After
function randMod(uint _modulus) internal returns (uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(block.timestamp, msg.sender, randNonce)) % _modulus;
}

Chapter5 ERC721とクリプト収集物

5-3 balanceOf と ownerOf

Override changes modifier to function.solidity(1469)

こちらは5-4で解決します。(5-3に書いておいて欲しい・・・)

5-12 SafeMathパート4

cryptozombies.io

Member "add" not found or not visible after argument-dependent lookup in uint256.solidity(9582)

下記の通り、使用するsolファイル毎にそれぞれusingが必要です。こちらは公式回答に無かったり、サンプルコードにいつのまにか記述されていたりと分かりづらく感じたのでご紹介しておきます。

// After
contract ZombieBattle is ZombieHelper {
    using SafeMath for uint256;
    using SafeMath32 for uint32;
    using SafeMath16 for uint16;

おわり

私が躓いたり分かりづらいと感じたことはこれくらいでした。都度動作確認しながら進めていたので、基本的にはこれらの対応で問題なくChapter5まで終えることができるはずです。CryptoZombieにはまだまだチャプターがありますが、これでSolidity言語での開発は一通り終わりましたので、今回はここまでとします!

来年はなにか簡単なDappsを作ってみて、公開までしてみたいなと思います 。
ありがとうございました。

www.tecotec.co.jp