第3回に引き続きSolidity入門基礎編を進めていきます。
少しづつ難易度も上がって来てますが、頑張って取り組みましょう。
前回のはこちら。
Solidity入門基礎編【3】 – CryptoZombiesで学習 –
初めての方はこちらから。
Solidity入門基礎編【1】 – CryptoZombiesで学習 –
始めるまえにこちらへアクセスしてください。
https://cryptozombies.io/jp/lesson/4/chapter/1/
目次
チャプター 1: Payable関数
チャプターの前に関数修飾詞についておさらい。
関数修飾詞とはざっくりいうと、関数にくっつけるとの関数の動きを制御できるというもの。
可視性修飾詞
public:外部から実行・参照可能、同じコントラクト内の関数、他のコントラクト内の関数から呼べる関数
external:外部から実行・参照可能、他のコントラクト内の関数からのみ呼べる関数(同じコントラクト内の関数からは呼べない)
internal:外部から実行・参照不可、他のコントラクト内の関数、同じコントラクト内の関数からのみ呼べる関数
private:外部から実行・参照不可、同じコントラクト内の関数からのみ呼べる関数(他のコントラクト内の関数からは呼べない)
状態修飾詞
view:読み取り専用、コントラクト内のデータを読み込む
pure:読み取り専用、コントラクト内のデータも読まない(関数内だけで完結)
カスタム修飾詞(って勝手に呼んでる)
modifier:自分でルールをつくれる(オーナーしか関数使えなくするとか、レベル制限を設けるとか)
そして今回追加で学ぶのが、payable修飾詞。
これはethを受け取るための関数修飾詞。solidity独特のものです。
目的:0.001 etherを支払うとゾンビのレベルを上げられる機能を実装
pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { // levelUpに必要なetherを定義 uint levelUpFee = 0.001 ether; modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } // levelUp関数を入力 どのゾンビのレベルを上げるのか情報が必要なためidをパラメータに設定 function levelUp(uint _zombieId) external payable { // msg.valueは支払額のこと // 支払額が上記で設定した0.001 etherだったら処理が通る require(msg.value == levelUpFee); // ゾンビのレベルを1上げる zombies[_zombieId].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; } }
チャプター 2: Withdraw関数
チャプター1ではコントラクトに対して送金をしました。
送金されたetherはコントラクトに貯まっている状態です。
それを引き出すための関数を学んでいきます。
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 { // ユーザーから頂いたethを全額ownerに送金する // this.balanceはこのコントラクト内にある残高 owner.transfer(this.balance); } // levelUpFeeをいつでも書き換えられるようにするための関数 function setLevelUpFee(uint _fee) external onlyOwner { levelUpFee = _fee; } function levelUp(uint _zombieId) external payable { require(msg.value == levelUpFee); zombies[_zombieId].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; } }
チャプター 3: ゾンビ・バトル
このチャプターはコントラクト作成の復習回。
pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { }
チャプター 4: 乱数
これはレッスン1でもやったと思いますが、改めて。
目的:乱数を設定して、バトルの勝敗を決める機能の実装
import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { // randNonceは1ずつ繰り上げていく数字 初期値として0を設定 uint randNonce = 0; // ランダムな数字を返す関数 function randMod(uint _modulus) internal returns(uint) { randNonce++; // now, msg.sender, randNonceの数字でハッシュ値を作り、_modulusで割った余りを返す return uint(keccak256(now, msg.sender, randNonce)) % _modulus; } }
チャプター 5: ゾンビが闘う
戦いの機能実装をします。
・自分のゾンビから一体を選び,攻撃する相手のゾンビを選ぶ
・攻撃するゾンビは勝率70%、守備するゾンビは30%の勝率
・全ゾンビ(攻撃するものも守備するものも)が、winCountとlossCountを持ち、これらはバトル結果次第で増えていく
・もし攻撃するゾンビが勝ったら、レベルアップして新たなゾンビを産む
・もし攻撃するゾンビが負けた場合は、何も起こらない(lossCountの増加を除く)
・勝っても負けても、攻撃するゾンビのクールダウン時間は開始される
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; } // 攻撃関数を宣言 引数には自分のゾンビと攻撃対象のゾンビIDを設定 function attack(uint _zombieId, uint _targetId) external { } }
チャプター 6: 共通ロジックのリファクタリング
requireを共通化していこうねというお話。
ゾンビを所有しているオーナーしか実行できないという制限を持つ関数が複数あるので、
共通化するためにmodifierでカスタム関数修飾詞をつくる。
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で共通のrequireを宣言 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); } // 関数定義にmodifier(ownerOf)を加える function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) { // 重複するrequire文は削除する 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"); } }
チャプター 7: さらなるリファクタリング
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; } // ownerOfを使うように修正 function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) { zombies[_zombieId].name = _newName; } // ownerOfを使うように修正 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; } }
チャプター 8: Attackへ戻ろう!
ゾンビの攻撃用機能の実装に戻る。
先ほど宣言したownerOfも使っていきます。
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; } // modifierをここに加える function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) { // Zombie構造体をつかって、自己所有のゾンビを設定する。 Zombie storage myZombie = zombies[_zombieId]; // Zombie構造体をつかって、敵のゾンビを設定する。 Zombie storage enemyZombie = zombies[_targetId]; uint rand = randMod(100); } }
チャプター 9: ゾンビの勝敗
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 { // 0勝0敗を追加 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); } }
チャプター 10: ゾンビの勝利
成績を更新する機能を実装していく。
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 ownerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; Zombie storage enemyZombie = zombies[_targetId]; uint rand = randMod(100); // 70より下2桁の数字が小さいか if(rand <= attackVictoryProbability) { // 勝ったら勝ち数を増やす myZombie.winCount++; // ゾンビのレベルを上げる myZombie.level++; // 相手の負け数を増やす enemyZombie.lossCount++; feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); } } }
チャプター 11: ゾンビの敗北
次は負けた場合の処理を追加。
目的:
・myZombieのlossCountを増やせ。
・enemyZombieのwinCountを増やせ。
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 ownerOf(_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); } }
次の講座はこちら。