크립토 좀비 - 3

Lumi·2021년 11월 24일
0

크립토 좀비

목록 보기
3/7
post-thumbnail

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

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

}

🔨 추가 사항

일단 추가 기능을 위한 변수를 선언하였다.

  • level, readyTime

또한 cooldownTime이라는 지역변수를 선언하여 날짜를 부여 하였다.

이러한 추가된 변수는 좀비가 공격을 할수 있는 시간을 의미한다.

  • 후에 다루어 보자.

🔨 새롭게 알게 된 점

기본적으로 uint256이나 uint32이나 메모리는 똑같다.

  • 왜냐하면 솔리디티 자체에서 256의 크기로 메모리를 잡기 떄문에

하지만 struct안에 적어주는 값은 다르다.

  • 만약 struct안에 uint32를 적으면 256의 메모리 크기가 아니라 32의 메모리 크기를 가진다.
  • 왜냐하면 자동으로 압축을 해주기 떄문에

솔리디티에도 시간이 있다

now같은 경우에는 1970년 1월 1일부터 지금까지의 초를 반환한다.

참고로 솔리디티 시간 언어에는 seconds, minutes, hours, days, weeks, year
등이 있고 모두 초로 계산이 된다.

  • 1 minutes = 60초, 1 hours = 60*60초

또한 기본적으로 now는 uint256을 반환하기 떄문에 구조체에 맞춰서 uint32로 설정해 주는 것이다.

now + cooldownTime을 적용함으로써 하루가 지나면 공격이 가능하게 구현을 해놓은 것이다.

🔥 ownable.sol

contract Ownable {
  address public owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


  function Ownable() public {
    owner = msg.sender;
  }


  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }
  
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}

권한을 부여하기 위한 컨트랙트이다.

  • 마땅히 어려운 코드는 없어보이니 넘어가도록 하겠다.

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

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

}

🔨 추가 사항

일단 좀비에게 먹이로줄 데이터를 따로 받아서 사용을 하였다.

  • 왜냐하면 상수값으로 지정을 하게 되면 해당 주소에 모든것을 의지해야한다.

이런 경우에는 만약 데이터의 주소값이 변경되거나, 오류가 발생을 하게 되면 사용이 불가능해지는 단점이 있다.

그러기 떄문에 일단 setKittyContractAddress함수를 통해서 값을 받아오는 것이다.

이떄 onlyOwner을 사용을 하였는데 그 이유는 다른 악의적인 사용자가 이상한 데이터를 줄 수도 있기 떄문이다.

  • 이 함수는 반드시 외부의 값을 받아서 사용하기 떄문에 external을 추가해 주는 것이다.

🔥 zombiehelper.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]);
    uint counter = 0;
    for(let i=0; i<zombies.length; i++){
      if(zombieToOwner[i] == _owner){
        result[counter] = i;
        counter++
      }
    }
    return result;
  }

}

오늘은 거의 이곳을 작업하면서 학습을 하였다.

이 곳에서는 좀비의 능력을 작성하는 코드이다.

기본적으로 특정 레벨에 도달하였을떄만 함수를 작동할 것이기 때문에 조건을 걸어준다.

  • aboveLevel

특정 레벨에 도달하면 해당 함수를 실행 가능하고 반드시 사용자만 실행이 가능하다는 특징이 있다.

그후 함수의 기능에 따라 이름을 바꾸거나, 새로운 DNA를 설정 가능하다.

getZombiesByOwner

사용자가 가지고 있는 좀비를 반환하는 함수이다.

만약 전역으로 선언되어있는 배열을 활용하게 되면 메모리 소모가 심하다.

  • 메모리 소모가 심하다는 소리는 많은 가스비를 요구한다는 뜻이다.

왜냐하면 배열에는 다른 사용자들의 좀비도 들어있을 곳이고 특정 좀비를 뺴오면 해당 공간은 빈공간이 되어서 모든 배열을 이동해야 한다.

  • 만약 해당 좀비만의 값만 가져오고 따로 배열에서 삭제하지 않는다고 해도 모든 배열을 돌아야 한다.

그러기 떄문에 함수 내에서 따로 배열을 만드는 것이다.

이 과정은 비효율적인 과정이지만 메모리적으로는 효율적인 작업이다.

🔨 추가로 알게 된 사항

storage연산은 메모리 적으로 매우 비싸다.

storage연산은 블록체인 생태계에 영구적으로 기록이 되기 떄문에 생태계를 변경해야 하는 점에서 더 많은 비용이 든다.

그러기 떄문에 떄로는 비효율적이더라도 storage 보다는 호출될 떄마다 momory에 다시 만드는 것이 좋다.

  • 어떤 배열에서 내용을 찾기 위해 단순히 변수에 저장하는 것 대신에

🔥 느낀점

이번 글은 나도 개인적으로 집중을 제대로 하지 못해서 많이 전문성이 떨어지는 것같다...

  • 사실 빅쇼트라는 금융관련 영화가 너무 재미있다보니... 그 부분에 대해서 생각하다보니 많이 집중을 못한것 같다.

다음 과정도 천천히 집중해서 진행을 해볼 것이며

한번 다시 작성을 해보면서 복습하는 시간을 가져봐야 겠다.

profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글