CryptoZombie Chapter 5

빵 반죽·2021년 9월 24일
0

Solidity: CryptoZombies

목록 보기
5/6

CryptoZombie: ERC721 & Crypto-Collectibles
try it yourself

0. Source Codes

  1. erc721.sol
pragma solidity ^0.4.25;

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

  function balanceOf(address _owner) external view returns (uint256);
  function ownerOf(uint256 _tokenId) external view returns (address);
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
  function approve(address _approved, uint256 _tokenId) external payable;
}
  1. ownable.sol

  2. safemath.sol

  3. zombieattack.sol

  4. zombiefactory.sol

  5. zombiefeeding.sol

  6. zombiehelper.sol

  7. zombiownership.sol

pragma solidity ^0.4.25;

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) external view returns (uint256) {
    return ownerZombieCount[_owner];
  }

	// 좀비의 주인 계좌 리턴
  function ownerOf(uint256 _tokenId) external view returns (address) {
    return zombieToOwner[_tokenId];
  }

	// 좀비의 주인(msg.sender)가 다른 계좌로 좀비를 보냄
  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;
    emit Transfer(_from, _to, _tokenId);
  }

	// 좀비의 주인 또는 허락을 받은 계좌가 좀비를 보냄
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
      require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender);
      _transfer(_from, _to, _tokenId);
    }

	// 좀비 주인이 다른 계좌를 허락해줌 
  function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) {
      zombieApprovals[_tokenId] = _approved;
      emit Approval(msg.sender, _approved, _tokenId);
    }

}

1. Tokens on Ethereum

A token on Ethereum is basically just a smart contract that follows some common rules — namely it implements a standard set of functions that all other token contracts share, such as transferFrom(address _from, address _to, uint256 _tokenId) and balanceOf(address _owner).

Internally the smart contract usually has a mapping, mapping(address => uint256) balances, that keeps track of how much balance each address has.

So basically a token is just a contract that keeps track of who owns how much of that token, and some functions so those users can transfer their tokens to other addresses.

By using commonly used tokens in your app, it can interact more easily with same tokens.

There's another token standard that's a much better fit for crypto-collectibles like CryptoZombies — and they're called ERC721 tokens.
ERC721 tokens are not interchangeable since each one is assumed to be unique, and are not divisible. You can only trade them in whole units, and each one has a unique ID. So these are a perfect fit for making our zombies tradeable.

Let's make another file, zombieownership.sol with ZombieOwnership contract in it. Each zombie is a NFT token, meaning it is unique.

2. ERC721 Standard, Multiple Inheritance

This is what we will need to implement(erc721.sol):

pragma solidity >=0.5.0 <0.6.0;

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

  function balanceOf(address _owner) external view returns (uint256);
  function ownerOf(uint256 _tokenId) external view returns (address);
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
  function approve(address _approved, uint256 _tokenId) external payable;
}

ZombieOwnership will need to inherit this as well as ZombieAttack. The syntax is the following:

contract ZombieOwnership is ZombieAttack, ERC721 {}

3. balanceOf & ownerOf

balanceOf: takes an address and returns the number of tokens(zombies) it has.
ownerOf: takes a token ID(zombie ID) and returns the owner address.

We already organized these information using mapping before, so it should be simple.

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

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

4. Refactoring

We already used the name ownerOf for our modifier, so the code above will cause an compile error. Change it to onlyOwnerOf.

5. ERC721: Transfer Logic

There are 2 ways when transferring tokens:
1. The token's owner(_from) calls transferFrom and sends a specific token to receiver(_to).

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
  1. The token's owner calls approve and it stores the information about the receiver and the token. The owner or receiver can call transferFrom and it checks if the msg.sender is the owner or approved by the owner to take the token.
function approve(address _approved, uint256 _tokenId) external payable;

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

_transfer():

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

6. ERC721: Transfer Cont'd

transferFrom():

  mapping (uint => address) zombieApprovals;
  
  function transferFrom(address _from, address _to, uint256 _tokenId) external payable {
    require(zombieToOwner[_tokenId]==msg.sender || zombieApprovals[_tokenId]==msg.sender);
    _transfer(_from, _to, _tokenId);
  }

7, 8. ERC721: Approve

approve:

  function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId]=_approved;
    emit Approval(msg.sender, _approved, _tokenId);
  }

9. Preventing Overflows

Use SafeMath library to prevent overflows and underflows.

using SafeMath for uint256;

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

10. SafeMath Part 2

library SafeMath {

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

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

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

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

0개의 댓글

관련 채용 정보