Crypto Zombie - 5

Hong·2022년 12월 18일
0



🧟‍♂️

🏭 zombiefactory.sol
 - 🍞 zombiefeeding.sol
    - 👼 zombieHelper.sol
		- 💥 zombieAttack.sol
			- 🪙 erc721.sol
			- 🧑‍🔬 safemath.sol
				- 🧸 zombieOwnership.sol

🪙 erc721.sol
🧑‍🔬 safemath.sol
🚧 ownable.sol



Chapter 1

ERC-20 tokenERC-721 token의 차이점
크립토 좀비 게임을 만들기에 적합한 token은 ERC-721이다
각각의 좀비가 고유한 특성을 들고 있고 하나의 tokenId단위로 거래되기 때문이다




Chapter 2

overriding

솔리디티에서 overriding은 상속받은 contract의 메소드가 하위 contract에서 충분한 기능을 제공하지 않거나, 부족할 때 상위 contract로부터 상속받은 contract를 재정의 하는 것이다.

아래는 크립토 좀비 code는 아니고 override의 이해를 돕기위해 reference를 참고했다

// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;

contract Father{
    
    string public familyName = "Kim";
    string public givenName = "Jung";
    uint256 public money = 100; 
    
    constructor(string memory _givenName) public {
        givenName = _givenName;
    }
    

    function getFamilyName() view public  returns(string memory){
        return familyName;
    } 
    
    function getGivenName() view public  returns(string memory){
        return givenName;
    } 
  
  	//부모 컨트랙트에 virtual을 적어줘서 해당 함수가 overriding가능 함을 알려준다
    function getMoney() view  public virtual returns(uint256){
        return money;
    }
}

contract Son is Father("James"){
    uint256 public earning = 0;
  
    function work() public {
        earning += 100;
    }
  
  	//자식 컨트랙트에 override를 적어줘서 이 함수를 overriding할 것이라고 알려줌
    function getMoney() view  public override returns(uint256){
        return money+earning;
    }
}

부모 컨트랙트의 함수에 virtual(이 함수는 자식 컨트랙트에서 overriding 가능하다)
자식 컨트랙트의 함수에 override(이 함수를 overriding할거다)




Chapter 3

ERC-71balanceOf, ownerOf함수

  • balanceOf : address를 받아, 해당 address가 토큰을 얼마나 가지고 있는지 반환한다
  • ownerOf : 토큰 ID(우리의 경우에는 좀비 ID)를 받아, 이를 소유하고 있는 사람의 address를 반환한다.



Chapter 4

Refactoring(리팩토링)

  • ERC-721 표준에서 정의하는 함수 이름과
    내가 개인적으로 작성한 smart contract의 함수이름이 중복되면 무엇을 바꿔야 할까?

예를 들어, ERC-721 표준에서 정의하는 함수 중 ownerOf와 내가 크립토 좀비를 만들며 작성한 modifier의 이름 ownerOf가 이름이 중복된다고 해보자

이런 상황에서는 내가 개인적으로 작성한 함수의 이름을 바꾸는 것이 바람직하다
왜냐하면 ERC-721은 다른 token 네트워크들과도 원활하게 상호작용할 수 있어야하기 때문이다
만약 내가 배포하는 ERC-721 token의 함수이름을 고친다면 다른 네트워크와 기능적으로 충돌이 생길 것이다.




Chapter 5, 6

ERC-721의 2가지 transfer방법 (transfer, approve)

  • event, emit

🧸 zombieOwnership.sol

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  //크립토 좀비 게임에 맞는 transfer함수를 만들기 위해 _transfer에서 부가적인 기능을 추가했다(추상화)
  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;

    zombieToOwner[_tokenId] = _to;

    Transfer(_from, _to, _tokenId);
  }

  //_tokenId 보유자만 transfer함수를 통해 해당 zombie를 전송할 수 있도록 modifier onlyOwnerOf를 적용했다
  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

  function approve(address _to, uint256 _tokenId) public {

  }

  function takeOwnership(uint256 _tokenId) public {

  }
}

첫번째 전송방법 : transfer

function transfer(address _to, uint256 _tokenId) public;

첫 번째 방법은 토큰의 소유자가 전송 상대의 address, 전송하고자 하는 _tokenId와 함께 transfer 함수를 호출하는 것이다.


두번째 전송방법 : approve, takeOwnership

function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;

두 번째 방법은 토큰의 소유자가 먼저 위에서 본 정보들을 가지고 approve를 호출하는 것다. 그리고서 컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장한다. 보통 mapping (uint256 => address)를 써서 저장한다. 이후 누군가 takeOwnership을 호출하면, 해당 컨트랙트는 이 msg.sender가 소유자로부터 토큰을 받을 수 있게 허가를 받았는지 확인한다. 그리고 허가를 받았다면 해당 토큰을 그에게 전송한다.


🖊️ Solidity에서 Event

event 라는 것은 블록체인 네트워크의 블록에 특정값을 기록하는 것을 말한다.

log 기록을 남기기 위해 event를 사용한다고 생각하면 될 것 같다(consolelog).
예를 들어, Transaction이라는 event가 있다고 가정하였을때, Transaction()을 실행시키면, 해당 송금 transaction에 대한 from(송신자), to(수신자), value(금액 or NFT tokenId)의 정보가 event로 출력이 되어서(emit으로 출력, emit안적으면 log안남음) 블록체인 네트워크 안에 기록이 된다.




Chapter 7, 8

ERC-721의 approve

ERC-721의 takeOwnership

🧸 zombieOwnership.sol

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  // zombieApprovals이라는 이름으로 approve된 좀비들을 저장할 mapping을 만들었다
  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

  // _tokenId보유자만 approve함수를 실행시킬 수 있어야 함으로 modifier onlyOwnerOf를 적용했다
  function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    // zombieApprovals mappring에 내가 보내고자하는 _tokenId를 수신자 _to에게 전달할 것을 저장한다
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }

  function takeOwnership(uint256 _tokenId) public {
    // 입력받은 좀비(_tokenId)가 zombieApprovals mapping에 msg.sender주소로 들어가 있는지 확인한다(takeOwnership 함수를 실행하는 사람이 _tokenId의 소유권이 있는지 확인한다)  
    require(zombieApprovals[_tokenId] = msg.sender);
    
    // 보내고자하는 좀비(_tokenId)의 소유권 주소가 어디인지 찾아서 owner 변수에 저장한다
    address owner = ownerOf(_tokenId);
	
    // 좀비(_tokenId)의 owner로부터 takeOwnership함수를 실행하는 실행자의 주소로 좀비(_tokenId)를 보낸다
    _transfer(owner, msg.sender, _tokenId);

  }
}

approve

approve / takeOwnership을 사용하는 전송은 2단계로 나뉜다는 것을 기억해야한다
소유자는 새로운 소유자의 address와 그에게 보내고 싶은 _tokenId를 사용하여 approve를 호출한다.
새로운 소유자가 _tokenId를 사용하여 takeOwnership 함수를 호출하면, 컨트랙트는 그가 승인된 자인지 확인하고 그에게 토큰을 전송한다.
이처럼 2번의 함수 호출이 발생하기 때문에, 우리는 함수 호출 사이에 누가 무엇에 대해 승인이 되었는지 저장할 데이터 구조가 필요할 것이다(mapping으로 구현함).


takeOwnership

마지막 함수인 takeOwnership에서는 msg.sender가 이 토큰(좀비)를 가질 수 있도록 승인되었는지 확인하고(zombieApprovals mapping 데이터 구조에 들어가 있는지 확인한다), 승인이 되었다면(zombieApprovals[_tokenId] = _to라면) _transfer를 호출해야 한다.




Chapter 9

SafeMath로 overflow, underflow 막기 (uint256에 대해서만)

🏭 zombiefactory.sol

pragma solidity ^0.4.19;

import "./ownable.sol";
// safeMath 라이브러리가 적힌 sol파일을 import
import "./safemath.sol";

contract ZombieFactory is Ownable {

  // SafeMath 라이브러리를 사용하기 위해 uint256에 대해 SafeMath method를 사용하겠다고 명시한다
  using SafeMath for uint256;
 
  
  ...
}

SafeMath 라이브러리는 4개의 함수를 가지고 있다

  • add, sub, mul, div

그리고 using SafeMath for uint256;을 적어줌으로써 uint256에서 다음과 같이 이 함수들에 접근할 수 있다.

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10



Chapter 10

assert와 require의 차이점

🧸 zombieOwnership.sol

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

...

  function _transfer(address _from, address _to, uint256 _tokenId) private {
      // ownerZombieCount[_to]++;에서 아래와 같이 교체
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
    // ownerZombieCount[_from]--;에서 아래와 같이 교체
    ownerZombieCount[_from] = ownerZombieCount[_from].sub(1);
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

...

}

SafeMath 라이브러리를 살펴보면 add, sub, mul, div 함수에 assert로 에러처리 해준 것을 확인할 수 있다.

assert는 조건을 만족하지 않으면 에러를 발생시킨다는 점에서 require와 비슷하다.

assert와 require의 차이점은,
require는 함수 실행이 실패하면 남은 가스를 사용자에게 되돌려 주지만,
assert는 그렇지 않다는 것이다.
즉 대부분 경우 코드에 require를 쓰는 것이 이로울 것이다.
때문에 assert는 일반적으로 코드가 심각하게 잘못 실행될 때 사용한다(uint 오버플로우처럼 말이다).

간단히 말해, SafeMath의 add, sub, mul, 그리고 div는 4개의 기본 수학 연산을 하는 함수이지만, 오버플로우나 언더플로우가 발생하면 에러를 발생시켜서 이를 방지한다.

오버플로우나 언더플로우를 막기 위해 코드에서 +, -, * 또는 /을 쓰는 곳을 찾아 add, sub, mul, div로 교체하면 overflow, underflow없이 연산이 가능하다(정확하게는 오버플로우 언더플로우가 일어나면 에러처리를 해준다).




Chapter 11

SafeMath로 overflow, underflow 막기 (uint32, uint16에 대해서)

🧑‍🔬 safemath.sol

pragma solidity ^0.4.18;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

/**
 * @title SafeMath32
 * @dev SafeMath library implemented for uint32
 */
library SafeMath32 {

  function mul(uint32 a, uint32 b) internal pure returns (uint32) {
    if (a == 0) {
      return 0;
    }
    uint32 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint32 a, uint32 b) internal pure returns (uint32) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint32 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint32 a, uint32 b) internal pure returns (uint32) {
    assert(b <= a);
    return a - b;
  }

  function add(uint32 a, uint32 b) internal pure returns (uint32) {
    uint32 c = a + b;
    assert(c >= a);
    return c;
  }
}

/**
 * @title SafeMath16
 * @dev SafeMath library implemented for uint16
 */
library SafeMath16 {

  function mul(uint16 a, uint16 b) internal pure returns (uint16) {
    if (a == 0) {
      return 0;
    }
    uint16 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint16 a, uint16 b) internal pure returns (uint16) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint16 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint16 a, uint16 b) internal pure returns (uint16) {
    assert(b <= a);
    return a - b;
  }

  function add(uint16 a, uint16 b) internal pure returns (uint16) {
    uint16 c = a + b;
    assert(c >= a);
    return c;
  }
}

Chapter 10에서 SafeMath를 이용해서 uint256 type의 숫자들에 대해 overflow, underflow를 막는 방법을 익혔지만
uint8 uint16 uint 32등의 숫자 type에 대해서 overflow, underflow를 막아야할 필요성을 느낄 때가 분명 있을 것이다.

아래는 SafeMath의 기본 라이브러리 중 add함수를 적어놓은 것이다

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}

만약 uint8.add를 호출한다면, 타입이 uint256로 변환된다.
그러니 2^8에서 오버플로우가 발생하지 않을 것이다. 256은 uint256에서 유효한 숫자이기 때문이다.
결국 우리는 다른 type의 숫자들에 대해 오버플로우, 언더플로우를 막기 위해 uint16과 uint32에서 2개의 라이브러리를 더 만들어야 한다(위에 적혀있다).
(SafeMath16과 SafeMath32라고 불렀음)




Chapter 12

chapter 10과 같은 작업(연산에 대해 SafeMath적용)을 파일 전역에 걸쳐 수정해줌




Chapter 13

solidity에서 주석달기 표준(natspec)

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

/// @title 좀비 소유권 전송을 관리하는 컨트랙트
/// @author notoriousHong
contract ZombieOwnership is ZombieAttack, ERC721 {
    /// @dev OpenZeppelin의 ERC721 표준 초안 구현을 따른다

...

}

natspec

솔리디티 커뮤니티에서 표준으로 쓰이는 주석형식은 natspec이라 불린다.
아래와 같이 생겼다.

/// @title 작성한 컨트랙트의 제목을 적는다.
/// @author notoriousHong
/// @notice 사용자에게 컨트랙트/함수가 무엇을 하는지 설명한다
contract Math {
  /// @notice 사용자에게 컨트랙트/함수가 무엇을 하는지 설명한다
  /// @param x 첫 번쨰 uint. 함수에서 어떤 매개 변수와 반환값을 가지는지 설명
  /// @param y 두 번째 uint. 함수에서 어떤 매개 변수와 반환값을 가지는지 설명
  /// @return z (x * y) 곱의 값. 함수에서 어떤 매개 변수와 반환값을 가지는지 설명
  /// @dev 개발자에게 추가적인 상세 정보를 설명하기 위해 사용한다.
  function multiply(uint x, uint y) returns (uint z) {
    // 이것은 일반적인 주석으로, natspec에 포함되지 않는다.
    z = x * y;
  }
}






🧟‍♂️ 전체코드

👼 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 levelUp(uint _zombieId) external payable {
    require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
  }

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

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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";
import "./safemath.sol";

contract ZombieFactory is Ownable {

  using SafeMath for uint256;

  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

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieAttack 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 onlyOwnerOf(_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);
    }
  }
}


🪙 erc721.sol

contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

  function balanceOf(address _owner) public view returns (uint256 _balance);
  function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256 _tokenId) public;
  function approve(address _to, uint256 _tokenId) public;
  function takeOwnership(uint256 _tokenId) public;
}


🧑‍🔬 safemath.sol

pragma solidity ^0.4.18;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}


🧸 zombieOwnership.sol

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  using SafeMath for uint256;

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
    ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

  function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }

  function takeOwnership(uint256 _tokenId) public {
    require(zombieApprovals[_tokenId] == msg.sender);
    address owner = ownerOf(_tokenId);
    _transfer(owner, msg.sender, _tokenId);
  }
}


profile
Notorious

0개의 댓글