どうも、次世代デジタル基盤開発事業部の土田です。
本投稿は 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 メモリ
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 ゾンビは何を食べるのか?
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
Data location must be "memory" or "calldata" for parameter in function, but none was given.solidity(6651)
下記でも発生していましたが、メッセージが若干変わっていますかね?今回はmemory
を指定します。以降、このエラーについては割愛します。
// 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
を明示的に指定する必要があります。以降、このエラーについても割愛します。
// Before if (keccak256(_species) == keccak256("kitty")) {
// After if (keccak256(bytes(_species)) == keccak256("kitty")) {
Chapter3 Solidityの高度なコンセプト
3-2 Ownable コントラクト
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
を明示的に指定します。以降、このエラーについても割愛します。
// Before
OwnershipTransferred(owner, newOwner);
// After emit OwnershipTransferred(owner, newOwner);
3-5 時間の単位
"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 ゾンビのクールダウン
"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 関数でガスを節約
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関数
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 乱数
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
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を作ってみて、公開までしてみたいなと思います 。
ありがとうございました。