Crypto Zombie - 4

Hong·2022년 12월 18일
0



🧟‍♂️

🏭 zombiefactory.sol
 - 🍞 zombiefeeding.sol
    - 👼 zombieHelper.sol
		- 💥 zombieAttack.sol



Chapter 1

함수에 payable 특성을 부과하면 해당 함수를 통해 contract가 ether를 받을 수 있도록 만들어준다

👼 zombieHelper.sol

    uint levelUpFee = 0.001 ether;

...

    function levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

payable함수의 트랜잭션을 봉투라고 생각하고 파라미터를 편지내용이라고 생각하면 좋다

  • 트랜잭션 : 봉투
  • 함수의 파라미터 : 편지 내용, value : 돈



Chapter 2

transfer함수를 통해 특정 address로 ether 송금하기

👼 zombieHelper.sol

  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

transfer 함수를 사용해서 이더를 특정 주소로 전달할 수 있다.
그리고 this.balance는 컨트랙트에 저장돼있는 전체 잔액을 말한다.




Chapter 3

import로 contract가져와서 새로 작성하는 contract에 상속하기

💥 zombieAttack.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
    
}



Chapter 4

keccak256을 이용한 난수생성

이 방법이 완전하고 안전한 방법의 난수 생성은 아니다 - 해결방법

💥 zombieAttack.sol

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {

  uint randNonce = 0;
  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    
    //여기가 난수 생성부분
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }
}

keccak256 해시함수의 파라미터로 now의 타임스탬프 값, msg.sender, 증가하는 randNonce(딱 한 번만 사용되는 숫자, 즉 똑같은 입력으로 두 번 이상 동일한 해시 함수를 실행할 수 없게 함)를 받고 있다.

그리고서 keccak을 사용하여 이 입력들을 임의의 해시 값으로 변환하고,
변환한 해시 값을 uint로 바꾼 후, % 100을 써서 마지막 2자리 숫자만 받도록 했다.

이를 통해 0과 99 사이의 완전한 난수를 얻을 수 있다.




Chapter 5

attack함수를 통해 나의 좀비가 다른 좀비에게 공격할 수 있는 시스템을 만든다

💥 zombieAttack.sol

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  
...

  function attack(uint _zombieId, uint _targetId) external {
    
  }
}

좀비 전투는 다음과 같이 진행될 것이다

  • 나의 좀비 중 하나를 고르고(_zombieId), 상대방의 좀비를 공격 대상으로 선택한다(_targetId).

  • 내가 공격하는 쪽의 좀비라면, 나는 70%의 승리 확률을 가진다. 방어하는 쪽의 좀비는 30%의 승리 확률을 가질 것이다.

  • 모든 좀비들(공격, 방어 모두)은 전투 결과에 따라 증가하는 winCount와 lossCount를 가질 것이다.

  • 공격하는 쪽의 좀비가 이기면, 좀비의 레벨이 오르고 새로운 좀비가 생긴다.

  • 좀비가 지면, 아무것도 일어나지 않는다(좀비의 lossCount가 증가하는 것 빼고).

  • 좀비가 이기든 지든, 공격하는 쪽 좀비의 재사용 대기시간이 활성화될 것이다.




Chapter 6

중복되는 require문은 modifier로 생성해서 함수에 적용시킨다 이렇게 바꾸면 코드의 길이를 줄일 수 있다

🍞 zombiefeeding.sol


...

contract ZombieFeeding is ZombieFactory {

  KittyInterface kittyContract;

  // 함수를 실행하는자가 _zombieId의 소유자인지 확인하고 싶은 과정을 여러번 쓰고 싶음으로 해당 require를 modifier로 만들어서 여러번 쓸 수 있도록 해준다 
  modifier ownerOf(uint _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    _;
  }

...

  // 함수 제어자에 ownerOf라는 modifier를 추가해줬다
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    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);
  }

...

함수를 실행하는자가 _zombieId의 소유자인지 확인하고 싶은 과정을 여러번 쓰고 싶음으로 해당 requiremodifier로 만들어서 여러번 쓸 수 있도록 해준다




Chapter 7

chapter 6에 이어서 changeName, changeDna함수에도 ownerOf modifier를 적용해줬다

👼 zombieHelper.sol

...

contract ZombieHelper is ZombieFeeding {
...

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

...



Chapter 8

chapter 6, 7에 이어서 attack함수에도 ownerOf modifier를 적용해줬다

💥 zombieAttack.sol

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);
  }
}

attack함수ownerOf modifier추가
(attack을 하고자하는 zombie가 내가 소유한 zombie임을 확인해야 하기 때문임)




Chapter 9

attack함수를 구현하기 위해(승, 패 기록할거임) Zombie구조체에 winCount, lossCount변수를 추가함

🏭 zombiefactory.sol

pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

...

    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
      // attack함수를 구현하기 위해 Zombie구조체에 winCount, lossCount변수를 추가함
      uint16 winCount;
      uint16 lossCount;
    }

...

    function _createZombie(string _name, uint _dna) internal {
        // 위의 Zombie struct의 구조가 변경되었기 때문에 zombie생성시 입력되는 값도 추가로 수정해줌(처음 생성된 zombie는 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);
    }

...

}

위의 Zombie struct의 구조가 변경되었기 때문에
zombie생성시 입력되는 값도 추가로 수정해줌(처음 생성된 zombie는 0승 0패를 가지고 있음)




Chapter 10~11

attack이 성공한다면(난수 rand의 값이 70보다 작다면, 70%확률로 승리) 발생하는 일들을 처리해준다

attack이 실패한다면(난수 rand의 값이 70보다 크다면, 30%확률로 패배) 발생하는 일들을 처리해준다

💥 zombieAttack.sol

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {

  ...

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    
    //attack이 성공한다면(난수 rand의 값이 70보다 작다면, 70%확률로 승리)
    //myZombie의 구조체 숫자를 알맞게 변경시켜주고 enemyZombie를 먹이로 준다
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
      
    //attack이 실패한다면(난수 rand의 값이 70보다 크다면, 30%확률로 패배)
    //myZombie의 구조체 숫자를 알맞게 변경시켜준다
    } else {
      myZombie.lossCount++;
      enemyZombie.winCount++;
    }

    //attack에 대한 행동이 끝났으니 cooldowntime을 부여한다
    _triggerCooldown(myZombie);
  }
}

attack이 성공한다면(난수 rand의 값이 70보다 작다면, 70%확률로 승리)
myZombie의 구조체 숫자를 알맞게 변경시켜주고 enemyZombie를 먹이로 준다

attack이 실패한다면(난수 rand의 값이 70보다 크다면, 30%확률로 패배)
myZombie의 구조체 숫자를 알맞게 변경시켜준다

attack에 대한 행동이 끝났으니 cooldowntime을 부여한다




🧟‍♂️ 전체코드

👼 zombieHelper.sol

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;
  }

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  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;
  }

}


🍞 zombiefeeding.sol

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 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);
  }

  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    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");
  }
}


🏭 zombiefactory.sol

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 {
        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);
    }

}


🚨 ownable.sol

/**
 * @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;
  }

}


💥 zombieAttack.sol

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);
  }
}
profile
Notorious

0개의 댓글