[Solidity] 크립토 좀비 레슨 5 학습 리뷰

드림보이즈·2023년 2월 24일
0

크립토좀비

목록 보기
5/6

슬슬 느껴져, 나도 솔리디티 전문가??

레슨 5 : ERC721 & SafeMath & 주석

챕터 1 : 이더리움 토큰

이더리움에서 토큰은 그저 공통 규약을 따르는 스마트 컨트랙트

모든 컨트랙트가 사용한느 표준 함수 집합을 구현하는 것. (transfer, balanceOf)

즉 토큰 = 컨트랙트, 그 안에서 누가 얼마나 가지는지, 전송하게 해주는 것 뿐

왜 ERC20을 써야 하나?

모든 ERC20 토큰이 똑같은 이름의 동일한 함수들을 공유하기에, 서로 상호작용 가능

하나의 토큰과 상호작용하는 어플을 만들면, 다른 모든 ERC20토큰을 추가할 수 있음 (거래소처럼)

ERC721

ERC20은 화폐처럼 사용하는 토큰에 제격, 그러나 좀비는 쪼갤 수 없고, 각자 고유한 특성이 있지

ERC721은 교체불가, 각각이 유일하고 분할 불가.

표준을 사용하면 거래/판매 같은 로직을 구현 안해도, 누군가 ERC721 자산을 거래할 수 있는 플랫폼 만들면 알아서 쓰일 거여

Q. ZombieOwnership에 ERC721을 새로 만들어 보자

pragma solidity ^0.4.19;

import "./zombieattack.sol";

contract ZombieOwnership is ZombieAttack {}

챕터 2 : ERC721 표준, 다중 상속

상속 여러 개 하는 법? 그냥 ,으로 구분해서 같이 써줘

Q. ZombieOwnership이 ZombieAttack과 ERC721을 상속한다고 선언하라

contract ZombieOwnership is ZombieAttack, ERC721 {

}

챕터 3 : balanceOf & ownerOf

balanceOf

function balanceOf(address _owner) public view returns (uint256 _balance);

그냥 address가 토큰 얼마나 가지고 있는지

ownerOf

function ownerOf(uint256 _tokenId) public view returns (address _owner);

토큰 ID의 주인이 누구인지

Q. 우린 이미 저거 balanceOf & ownerOf 했잖아, 기억나냐


  function balanceOf(address _owner) public view returns (uint256 _balance) {
    // 1. 여기서 `_owner`가 가진 좀비의 수를 반환하게.
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    // 2. 여기서 `_tokenId`의 소유자를 반환하게.
    return zombieToOwner[_tokenId];

챕터 4 : 리팩토링

우린 이미 ownerOf 라는 modifier를 만들고 쓰고 있었으니 충돌이 날거다.

뭘 바꿔줘야 할까 ? 당연히 표준은 냅둬야지, 다른 컨트랙트는 다 저거 쓰는 줄 알텐데.

modifier를 onlyOwnerof로 고치고, 이거 쓴 거 다 찾아서 이름 바꿔줘라.


챕터 5~6 : 전송 로직

ERC721에는 토큰을 전송할 때 2가지 방식이 있음

  • transfer(_to, _tokenId)로 주인이 전달하는 방식
  • approve(_to, _tokenId)로 주인이 해당 토큰을 가질 수 있도록 허가를 해주고, 허가 받아놓은 사람이 takeOwnership(_tokenId)호출하면 허가 받았는지 확인하고 토큰 가져감

누가 호출하냐 차이이지, 전송 로직은 같음

Q. 이 로직 만의 private 함수 , _transfer 만들어 추상화 하자. 어떻게 만들래?

function _transfer(address _from, address _to, uint256 _tokenId) private {
	ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    
    zombieToOwner[_tokenId] = _to;
    // 이벤트 실행
    Transfer(_from, _to, _tokenId);

    

Q. 근데 전송하는 게 주인이 아니라면? 안전장치 걸고, 이제 transfer 함수를 만들어, _transfer가 거의 다 해줄거야

function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    // 2. 여기서 함수를 정의하게.
    _transfer(msg.sender, _to, _tokenId);
  }
    

챕터 7 : ERC721 : Approve

approve / takeOwnership 전송은 2 step

  • 소유자가 approve를 호출하고 _to에 _tokenId를 허락함
  • 받는 놈이 takeOwnership을 호출하고 확인 후 전송

Q. approve를 하면 takeOwnership 전까지 저장을 해놔야 하니, 그걸 구현하고, approve를 완성하라

mapping (uint => address) zombieApprovals;

 // 2. 여기에 함수 제어자를 추가하게.
function approve(address _to, uint256 _tokenId) public onlyOwnerof( _tokenId) {
    // 3. 여기서 함수를 정의하게.
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }


챕터 8 : takeOwnership

Q. takeOwnership 함수를 구현하라


function takeOwnership(uint256 _tokenId) public {
    // 여기서 시작하게.
    require(zombieApprovals[_tokenId] == msg.sender);
    address owner = ownerOf(_tokenId);
    _transfer(owner, msg.sender, _tokenId);
  }

챕터 9 : 오버플로우 막기

컨트랙트 보안 강화 : 오버플로우, 언더플로우

uint256은 2^256 -1 까지 가겠지만, 만약 게임을 진행하다 저것보다 커진다면 ? 0이 될것이다(마치 23:59분에서 00:00처럼)

SafeMath

이런 문제를 막기위해 OpenZeppelin에서 라이브러리를 만들어줌

Library는 솔리디티에서 특별한 종류의 컨트랙트.

기본(native) 데이터 타입에 함수를 붙일 때 유용하게 사용됨

add / sub / mul / div

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3) // 5 + 3 = 8

요런식으로 쓸 수 있음.

Q. safeMath 쓸 준비를 해라 (import + using)

import "./safeMath.sol";

contraact !@$#$%{
using SafeMath for uint256;

}

챕터 10 : SafeMath 파트 2

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

라이브러리는 컨트랙트와 비슷하지만, using 키워드를 써서 다른 데이터 타입에 메서드를 사용할 수 있게 함

using SafeMath for uint;
// 우리는 이제 이 메소드들을 아무 uint에서나 쓸 수 있네.
uint test = 2;
test = test.mul(3); // test는 이제 6이 되네
test = test.add(5); // test는 이제 11이 되네

본래 mul, add의 인자는 2개지만, 메서드 형식으로 사용하면 test가 인자 1에 자동으로 들어감

assert를 사용하면서 에러 안나게 보장을 해주는 역할이지

assert는 require과 비슷하지만, require는 함수 실행이 실패하면 가스를 돌려주고, assert는 안 돌려줌

assert는 일반적으로 코드가 심각하게 잘못됬을 때 사용

Q. SafeMath를 적용시키자

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    // 1. SafeMath의 `add`로 교체하게.
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
    // 2. SafeMath의 `sub`로 교체하게.
    ownerZombieCount[_from] = ownerZombieCount[_from].div(1);
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

챕터 11 : safeMath 파트 3

일반적으로 수학 연산을 ++ 이런거 쓰지말고 앵간하면 safeMath를 쓰는게 좋다.

근데 만약 uint16,uint32에 적용하면???

메서드를 사용하면 uint256으로 바꿀 것이고, 그럼 우리가 세팅한 범위가 아무 쓸모 없어지겠지

그러니 얘네들 용으로 라이브러리를 따로 만들어야겠지 (다 똑같은데 타입만 다르게)

Q. 라이브러리 쓴다고 선언하고 고쳐라

  using SafeMath for uint256;
  using SafeMath32 for uint32;
  using SafeMath16 for uint16;
  // 1. using SafeMath32 for uint32를 선언하게.
  // 2. using SafeMath16 for uint16를 선언하게.
  
  
 ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);

챕터 12 : safeMath 파트 4

Q. zombieAttack에서 ++부분도 고치자


챕터 13 : 주석 (Comment)

컨트랙트의 모든 함수에서 예상되는 행동값을 주석으로 설명하는 것이 필요

다른 개발자들이 코드를 안 훑고 주석만 읽더라도 큰 맥락으로 이해를 할 수 있기 때문

솔리디티 커뮤니티의 표준은 natspec이라 불림

/// @title : 좀비 소유권 전송을 관리하는 컨트랙트
/// @author : Young Joo
/// @dev : OpenZeppelin의 ERC721 표준 초안 구현을 따른다

@notice는 사용자에게 컨트랙트, 함수가 무엇을 하는지

@dev는 개발자에게 추가적인 상세 정보를 설명

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

모든 함수에 대해 꼭 이 모든 태그들을 항상 써야만 하는 것은 아니다

모든 태그는 필수가 아니지만,

최소한 각각의 함수가 어떤 것을 하는지 설명하도록 @dev는 써라.


profile
10년 후 세계 최고 블록체인 개발자

0개의 댓글