第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);
}
}
次の講座はこちら。

