CryptoZombie 5단계

1. 이더리움 상의 토큰

내부상으로 스마트 컨트랙트는 보통
mapping(address => uint256) balances와 같은 매핑을 가지ㅗㄱ ㅣㅇ싸. 각각의 주소에 잔액이 얼마나 있는지 기록하는 것이3다.

1-1. 토큰

몇몇 공통 규약을 따르는 스마트 컨트랙트.
즉 다른 모든 토큰 컨트랙트가 사용하는 표준 함수 집합을 구현하는 것.

1-2. ERC20

하나의 ERC20토큰ㄷ과 상호작용할 수 있는 application 하나를 만들면 이 application이 다른 어떤 ERC20 토큰과도 상호작용이 가능하다.

2. ERC721표준, 다중 상속

2-1.ERC721표준

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

2-2. 토큰 컨트랙트 구현

토큰 컨트랙트를 구현할 때, 가장 ㅏ먼저 인터페이스를 솔리디티 파일로 따로 목사해 저장하고 import "./erc721.sol";을 써서 임포트를 해야한다. 그리고 해당 컨트랙트를 상속하는 우리의 컨트랙트 만들고 각각 함수를 오버라이딩하여 저장해야 한다.

2-3. 다중 상속

상속하고자 하는 다수의 컨트랙트를 쉼표(,)로 구분하여 사용한다.

contract SatoshiNakamoto is NickSzabo, HalFinney {
}

2-4. 정답

pragma solidity ^0.4.19;

import "./zombieattack.sol";

import "./erc721.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

}

3. balanceOf & ownerOf

우리는 mapping을 썼기 때문에 balanceOf, onwerOf를 return 문장으로 구현할 수 있다.

3-1. balanceOf

function balanceOf(address _owner) public view returns (uint256 _balance);
단순히 address를 받아서 해당 address가 얼마나 토큰을 가지고 있는지 반환한다.

크립토 좀비에서는 좀비들은 토큰들이 될 것이다.ㅣ

3-2. ownerOf

function ownerOf(uint256 _tokenId) public view returns (address _owner);
이 함수는 토큰 ID를 받아 이를 소유하고 있는 사람의 address를 반환한다.

3-3. balanceOf구현

  • zombiefactory.sol 을 보면 다음과 같은 코드가 있다.

    이걸 참고해서 다음과 같이 적는다.

3-4. ownerOf구현

3-5. 정답코드

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

  function transfer(address _to, uint256 _tokenId) public {

  }

  function approve(address _to, uint256 _tokenId) public {

  }

  function takeOwnership(uint256 _tokenId) public {

  }
}

4. 리팩토링

3단계에서 이미 ownerOf을 만들었는데 이미 zombiefeeding.sol에서 이와 같은 modifier이 이미 존재한다. 그렇다고 방금 만든 것을 바꾸기에는 ERC721표준을 사용하기 때문에 기존의 코드를 바꾸어야 한다.

4-1. 정답

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;

  // 1. 제어자의 이름을 `onlyOwnerOf`로 바꾸게.
  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);
  }

  // 2. 여기서도 제어자의 이름을 바꾸게.
  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");
  }
}

5. ERC721 전송 로직

5-1. ERC721 토큰 전송 방법

5-1-1. transfer

토큰을 보내는 사람이 호출
토큰의 소유자가 전송상대의 address와 전송하고자 하는 _tokenId와 함께 tranfer 함수 호출하기
function tranfer(address _to, uint256 _tokenId) public;

5-1-2.approve,takeOwnership

토큰을 받는 사람이 호출
소유자가 위에서 본 정보들을 가지고 approve를 호출한다. 그리고서 컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장한다.
mapping (uint256 => address)를 써서 한다.

이후 누군가가 takeOwnership을 호출하면 msg.sender가 소유자로부터 토큰을 받을 수 있늦지 확인하고, 허가를 받았다면 해당 토큰을 그에게 전송한다.

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

그렇기에 로직만의 프라이빗함수인 _transfer을 만들어 추상화하여 두 함수 모두에서 쓸 수 있도록 하는 방향이 좋다.

5-2. 정답 코드

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

  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 {

  }

  function approve(address _to, uint256 _tokenId) public {

  }

  function takeOwnership(uint256 _tokenId) public {

  }
}

6. ERC721 전송

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

  }

7. ERC721:Approve 구현

아까 approve/takeOwnership을 사용하려면 2단계로 나뉜다.

  1. 소유자가 새로운 address와 그에게 보내고싶은_tokenId를 사용해 approve 함수 호출
  2. 새로운 소유자가 _tokenId를 사용하여 takeOwnership 함수를 호출한다. 그러면 컨트랙트는 자신을 호출한 사람이 승인된 자인지 확인하고 맞으면 토큰을 전송해준다.

👉함수 호출 사이에 누가 무엇에 대해 승인이 되었는지 데이터구조를 짜서 저장해야 한다.

7-1.정답

pragma solidity ^0.4.19;

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

contract ZombieOwnership is ZombieAttack, ERC721 {

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

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

  function takeOwnership(uint256 _tokenId) public {

  }
}

8. ERC721:takeOwnership

8-1. takeOwnership

이 함수에서는 msg.sender가 토큰/좀비를 가질 수 있도록 승인되었는지 확인한다.
ㅡ그 후 승인이 되었으면 _transfer을 호출하기

8-2. 정답

pragma solidity ^0.4.19;

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

contract ZombieOwnership is ZombieAttack, ERC721 {

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

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

require에도 ;를 붙여야 한다.

9. overflow & underflow

9-1. overflow & underflow

uint8은 최대로 나타낼 수 있는 수가 255이다. 이를 이진수로 나타내면 11111111이다. 그런데 여기에 1을 더하면 0이 된다. 이는 23:59분일 때 1분을 더하면 00:00이 되는 원리와 같은 것이다 .

underflow는 uint8을 사용하는 0에 -1을 해주면 255와 같아지는 현상이다. (uint8에는 부호가 없어 음수가 될 수 없음)

9-2.SafeMath

OpenZeppelin에서는 이러한 문제를 막아주기 위해 SafeMath라는 라이브러리를 만들었다.

9-2-1. 함수 종류

  • add
  • sub
  • mul
  • div

9-2-2. 사용 예시

using SafeMath for uint256;

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

9-3. 정답


import "./safemath.sol";

contract ZombieFactory is Ownable {

  using SafeMath for uint256;

...

10. SafeMath 2

10-1. SafeMath 내부 코드

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

10-2. library

library는 using을 쓸 수 있다. 라이브러리의 메소드들을 다른 데이터타입에 적용할 수 있게 해준다.

using 라이브버리명 for 데이터타입

using SafeMath for uint;
uint test =2;
test = test.mul(3)//6
test = test.add(5)//11

여기서 mul 이나 add는 2개의 인수를 필요로 하는데 이는 using SafeMath for uint를 선언할 때 uint(test)는 자동으로 첫번째 인수로 전달이 된다.

10-2-2. assert와 require차이

assert: 함수 실행이 실패하면 남은 가스를 사용자에게 되돌려주지 않는다. 그렇기에 심각한 오버플로우같은 문제를 해결할 때 쓴다.
require: 함수 실행이 실패하면 남은 가스를 사용자에게 되돌려준다.

10-2-2. 스킬

오버플로우나 언더플로우를 막기 위해 +,-,*,/를 쓰는 곳을 add, sub,mul,div로 교체한다.

myUint++;👉myUint=myUint.add(1)

10-3. 해답

11. SAFEMATH3

11-1. 라이브러리 데이터 타입 다를때

라이브러리 파일에서 일일이 다 달아준다.

11-2. 해답

12. SAFEMATH4

12-1. 해답


13. 주석

13-1. 주석 다는 방법

13-1-1. @notice & @dev

@notice는 컨트랙트나 함수가 어떠한 역할을 하는지 설명한다.
@dev는 개발자에게 추가적인 상세 정보를 설명하기 위해 사용하는 항목이다. 적어도 함수가 어떤 역할을 하는지 설명하는 @dev는 꼭 사용하도록 한다.

13-1-2. @param & @return

@param과 @return은 함수에서 어떤 매개 변수와 반환값을 가지는지 설명하네.

13-2. 예시

13-3. 정답

이 URL를 공유해서 친구들이 자네의 전체 군대를 확인할 수 있도록 해보게:

https://share.cryptozombies.io/ko/lesson/5/share/H4XF13LD_MORRIS_💯💯😎💯💯

좋은 웹페이지 즐겨찾기