CryptoZombie Chapter 2

CryptoZombie: Zombies Attack their victims
try it yourself

0. Source codes

  1. zombiefactory.sol
pragma solidity ^0.4.25;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

	//두 가지 매핑에 주인에 관한 정보 저장
    //새로운 좀비 생성을 알리는 이벤트 추가
    //ZombieFeeding에서 사용할 수 있도록 private->internal
    function _createZombie(string _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        emit NewZombie(id, _name, _dna);
    }

    function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

	//좀비가 하나도 없는 계정만 랜덤 생성할 수 있도록 require 추가
    //이렇게 생성된 초기 좀비는 유전자가 00으로 끝나게 설정됨
    function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}
  1. zombiefeeding.sol
pragma solidity ^0.4.25;
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 {

	// 키티 컨트렉트 인스턴스 생성
  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);

	// 좀비와 타깃(키티)의 유전자를 평균 낸 새로운 유전자로 좀비 생성
    // 키티 종인지 검증 후 유전자가 99로 끝나도록 함(키티 특징)
  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(abi.encodePacked(_species)) == keccak256(abi.encodePacked("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");
  }

}

1. Lesson 2 Overview

By feeding our zombies, we will create new zombies!

2. Mappings and Addresses

Mapping and addresses are datatypes needed to give zombies an owner.
An address is an unique ID of an account owned by a specific user. It can be used as an ownership of a zombie.
A mapping is a key-value storing system.Think of js objects.

mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;

3. Msg.sender

Msg.sender is a global variable provided by solidity. It is the address of the person or smart contract that called the current function.

Updated _createZombie:

function _createZombie(string memory _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        
        // map the new zombie to owner
        zombieToOwner[id] = msg.sender;
        // increase the zombie count of this owner
        ownerZombieCount[msg.sender]++;
        
        emit NewZombie(id, _name, _dna);
    }

4. Require

require the function will throw an error and stop executing if some condition is not true
createRandomZombie function will be modified with require so that the player can only make 1 zombie from scratch.
Once the code gets caught in require, the whole codes above gets cancelled.

function createRandomZombie(string memory _name) public {
        require(ownerZombieCount[msg.sender]==0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

5. Inheritance

Use inheritance to implement various function without fattening ZombieFactory too much.

contract ZombieFeeding is ZombieFactory {}

6. Import

We can now organize by splitting the files.

7. Storage vs Memory (Data location)

Storage refers to variables stored permanently on the blockchain. Memory variables are temporary, and are erased between external function calls to your contract. Think of it like your computer's hard disk vs RAM.

Usually, solidity will handle this automatically. State variables (variables declared outside of functions) are by default storage, while variables declared inside functions are memory and will disappear when the function call ends. With structs and arrays inside functions, though, it needs to be declared explicitly.

8. Zombie DNA

When a zombie feeds on some other lifeform, its DNA will combine with the other lifeform's DNA to create a new zombie.

The new zombie's DNA is the average between the feeding zombie's DNA and the target's DNA.

function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    
    // This line will cause compile error
    _createZombie("NoName", newDna);
  }

9. More on Function Visibility

_createZombie is a private function from ZombieFactory. Therefore function inside ZombieFeeding cannot access it.
internal is similar to private except it can be accessed via inheritance.
external is similar to public except it can only be called outside of that contract.

function _createZombie(string memory _name, uint _dna) internal {}

10. What Do Zombies Eat?

They eat CryptoKittens. Yes I know. How brutal.
interface allows interactions with contracts that we don't own. It resembles normal contracts, but it lacks some features, indicating that it is an interface. It lets our contract know the required info to call other contract's functions. Those functions must be public or external.

11. Using an Interface

12. Handling Multiple Return Values

You can handle multiple return values using (,,,). The code shows how you can pick out specific values.

pragma solidity >=0.5.0 <0.6.0;

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 {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna);
  }

}

13. Bonus: Kitty Genes

We can add a special feature that differenciate cat-zombies from regular ones.

If you recall from lesson 1, we're currently only using the first 12 digits of our 16 digit DNA to determine the zombie's appearance. So let's use the last 2 unused digits to handle "special" characteristics.

Last 2 digits of the DNA must be 99 if it is a cat-zombie.
if statements are the same in solidity as in javascript.

좋은 웹페이지 즐겨찾기