Skip to content

Latest commit

 

History

History
284 lines (218 loc) · 11.1 KB

12-forloops.md

File metadata and controls

284 lines (218 loc) · 11.1 KB
title actions requireLogin material
For ループ
答え合わせ
ヒント
true
editor
language startingCode answer
sol
zombiehelper.sol zombiefeeding.sol zombiefactory.sol ownable.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]); // ここから開始せよ return result; } }
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"); } }
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); } }
/** * @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; } }
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; } }

前のチャプターでは、関数内で配列を構築する際に、単にstorageに保存するかわりにforループを使用して作りたい場合もあると言ったな。

その理由を説明しよう。

getZombiesByOwner関数を素直に実装しようとするなら、オーナーからゾンビ軍団へのmappingZombieFactoryコントラクトに持たせればいいだろう:

mapping (address => uint[]) public ownerToZombies

新しいゾンビを作る度に、ownerToZombies[owner].push(zombieId)を使ってオーナーのゾンビ配列に追加していくだけだ。するとgetZombiesByOwnerは非常にシンプルな関数になる:

function getZombiesByOwner(address _owner) external view returns (uint[]) {
  return ownerToZombies[_owner];
}

この方法の問題点

この方法は簡単だから、つい使いたくなってしまう。だがゾンビを誰かに譲る関数をあとで作成した時に問題が起こる(後のレッスンで教えるから、しばし待つのだ!)。

その関数には次の動作が必要になる:

  1. ゾンビを新しいオーナーのownerToZombies配列に追加する
  2. 元のオーナーのownerToZombies配列からゾンビを削除する
  3. 穴を埋めるために、元のオーナーの各ゾンビの配列の番号を変更する
  4. 配列のlengthを1 減らす。

ステップ 3は、ゾンビの位置を全てずらすことになるから、ガスコストは非常に高額になってしまう。もしオーナーがゾンビを20体持っていて、最初のゾンビを誰かにあげたとする。すると残りの19体の配列番号を書き直さなくてはならなくなるのだ。

storageに書き込むことはSolidityの操作で一番ガスコストがかかるものだから、このゾンビを誰かに譲る関数を実行するたびに、莫大なガスコストが発生してしまうのだ。さらに悪いことに、ユーザーがどれくらいのゾンビを所有していて、何番目のゾンビを譲るのかによってガスのコストが変わってしまうのだ。だからユーザーはどのくらいガスが必要なのか、実行するまでわからないのだ。

注:もちろん、譲ったゾンビの場所に、配列の最後のゾンビを移動して、穴埋めに使う方法はあります。しかしその場合、取引の度にゾンビ軍団の順番を変えなければならなくなります。

この場合、view関数は外部から呼び出した時にガスコストがかからないから、getZombiesByOwner内でforループを使ってそのオーナーのゾンビ軍団の配列を作ってしまえばいい。そうすればtransfer関数はstorage内の配列を並び替える必要がないため安く抑えられるし、直感的ではないにしろ全体のコストも抑えられる。

for ループを使う

SolidityのforループはJavaScriptと同じようなものだと考えていい。

偶数の数字を格納する配列の例を出すぞ:

function getEvens() pure external returns(uint[]) {
  uint[] memory evens = new uint[](5);
  // 新しい配列のインデックスをトラックする:
  uint counter = 0;
  // 1から10までループさせる:
  for (uint i = 1; i <= 10; i++) {
    // もし `i` が偶数なら...
    if (i % 2 == 0) {
      // 配列に格納する
      evens[counter] = i;
      // カウンタを増やして `evens`の空のインデックスにする:
      counter++;
    }
  }
  return evens;
}

この関数は[2, 4, 6, 8, 10]の配列を返す。

それではテストだ

getZombiesByOwnerを完成させよ。forループでDApp内の全てのゾンビをループさせ、オーナーが一致するかどうかを判定し、result 配列に格納して返却せよ。

  1. counterというuintを宣言し、0に設定せよ。この変数はresult配列のインデックスとして使用する。

  2. uint i = 0から始めて、i < zombies.lengthまでループするforループを宣言せよ。このループは配列内の全てのゾンビをイテレートする。

  3. forループ内にifステートメントを作成し、zombieToOwner[i]_ownerと一致するか判定せよ。2つのアドレスを比較することでチェックしているのだ。

  4. ifステートメント内部には以下を設定せよ:

    1. result配列内にゾンビのIDを追加せよ。result[counter]iと同等になるよう設定するだけでよい。
    2. counterを 1 増やせ。(forループの例を参考にするのだ)。

これでいい。_owner が所有する全てのゾンビが返るはずだ。しかもガスは一切不要だ。