Course 5 : ERC721&크립토 수집품

학미새🐥·2022년 8월 28일
0

1. 이더리움상의 토큰

  • 이더리움의 토큰 : 몇몇 공통 규약을 따르는 스마트 컨트랙트
    • 다른 모든 토큰 컨트랙트가 사용하는 표준 함수 집합
    • ex) transfer(address _to, uint256 _value),
      balanceOf(address _owner),
      mapping(address => uint256) balances
  • 즉, 토큰은 누가 얼만큼의 토큰을 가지는지 기록하고, 토큰을 전송할 수 있도록 만들어진 스마트 컨트랙트의 하나.

❓ 이러한 ERC20과 같은 토큰을 사용해야 하는 이유?

  • ERC20 토큰들이 똑같은 이름의 동일한 함수 집합을 공유하기 때문에, 토큰들 간의 서로 상호작용이 가능하다.

  • 즉, 하나의 ERC20 토큰과 상호작용하는 애플리케이션이 있다면, 또다른 ERC20 토큰과도 상호작용할 수 있다.

  • 따라서, 커스텀 코드를 추가하지 않고도 나의 앱에 여러 토큰을 쉽게 추가할 수 있다.

  • ERC20 : 화폐처럼 사용되는 토큰에 적절

  • ERC721 : 크립토 수짐품에 적절한 토큰 표준

ERC721

  • 교체가 불가능하다. Non-fungible
  • 각 토큰이 유일하고, 분할이 불가하다.
  • 각 토큰은 하나의 전체 단위로만 거래할 수 있다.
  • 각 토큰은 유일한 ID를 가진다.
    -> 수집품에 적절!

이러한 ERC721 표준을 사용하면, 컨트랙트에서 사용자들이 쉽품을 거래/판매할 수 있도록하는 로직을 직접 구현하지 않아도 된다.

2. ERC721 표준, 다중 상속

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

토큰 컨트랙트 구현하기

  • 인터페이스를 솔리디티 파일로 복사해서 저장한다. erc721.sol
  • erc721.sol을 나의 컨트랙트 파일로 import 해온 뒤, 상속하는 컨트랙트를 생성한다.
  • 컨트랙트에서 각각의 함수를 오버라이딩하여 정의한다.
    🔊 솔리디티 컨트랙트는, 여러 컨트랙트를 상속받을 수 있다. (,만 찍으면 됨)

3. balanceOf & ownerOf

balanceOf

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

  • address를 받아 해당 address가 보유한 토큰 수 반환
    ex) 토큰 수 -> 좀비 수

owernOf

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

  • 토큰 ID를 받아, 소유자의 address를 반환

  • 기존 컨트랙트에서 위의 것들을 mapping으로 미리 정의했다면, 구현은 return문 하나만으로 가능함
    ex)
    기존 : mapping (uint => address) public zombieToOwner;
    구현 : return zombieToOwner[_tokenId];

4. 리팩토링

  • 제어자와 함수가 동일한 이름을 가질 시, 컴파일 에러가 발생한다.
    따라서 둘 중 하나의 이름을 바꿔줘야 한다. -> 이런 행위를 리팩토링이라고 한다.
  • ERC721 표준을 상속하여 구현할 경우, 해당 함수명을 함부로 바꿔서는 안된다. 표준은 다른이들과 함께 하는 약속이기 때문에!

5-6. ERC721 : 전송 로직

  • 한 사람이 다른 유저에게 소유권을 넘기는 것 구현하기
  • ERC721에서 토큰을 전송할 수 있는 두 방법 :
    1. function transfer(address _to, uint256 _tokenId) public;
      _to : 토큰을 전송할 상대 주소
      _tokenId : 전송할 토큰 ID
      로, transfer 함수 호출하기
    • function approve(address _to, uint256 _tokenId) public;
      _to : 토큰을 전송할 상대 주소
      _tokenId : 전송할 토큰 ID
      로, 토큰 소유자approve를 호출하여, 토큰 수신 허가자를 지정한다.
    • 컨트랙트에서는 토큰을 가질 수 있도록 누가 허가 받았는지 mapping으로 저장한다.
    • function takeOwnership(uint256 _tokenId) public;
      누군가 takeOwnership을 호출하면, 컨트랙트는 msg.sender가 토큰을 받기로 허가받은 주소인지 확인한 후, 토큰을 전송한다.

    즉, transfer는 토큰 송신자가 호출, takeOwnership은 토큰 수신자가 호출하는 함수.
    두 함수의 로직은 결국 동일하기 때문에, 구체화시킬 공통 함수를 private으로 따로 정의하는 것이 좋다.

7. ERC721 : Approve

토큰의 소유자를 바꿀 두번 째 방식은
1. 소유자가 approve호출로 토큰 소유 허가권을 주는 것,
2. 수신자가 takeOwnership으로 소유권을 가져오는 것
이렇게 두번의 함수 호출로 발생 되기 때문에,
그 사이에 허가 여부를 저장할 데이터 구조가 필요하다.
-> mapping (uint => address)를 사용하여 토큰id의 허가된 주소 address 매핑을 관리한다.

8. ERC721 : takeOwnership

takeOwnership의 로직은
1. msg.sender가 토큰을 소유할 허가권이 있는지 확인하고,
2. 있다면 _transfer를 호출한다.
여기서 _transfer는, 토큰 전송의 두가지 공통 로직을 별도로 구현해놓은 private 함수이다.

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

9. 오버플로우 막기

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

스마트 컨트랙트를 작성할 때 인지할 주요한 보안 기능
❓ 오버플로우란?
uint8 number = 255;일 때,
number++;을 해버리면,
number는 256이 아닌 0이 되어버린다.
-> number 변수가 8bit짜리 변수라서, 0~255까지만 담을 수 있기 때문이다.
이런 경우, overflow가 발생한 것이다.

❓ 언더플로우란?
uint8 number = 0;일 때,
number--;을 해버리면,
number는 -1이 아닌 255가 되어버린다.
-> uint는 부호가 없기 때문에, 음수가 될 수 없다.
이런 경우, underflow가 발생한 것이다.

SafeMath 사용하기

  • 오픈제플린에서 이러한 플로우 문제를 막기 위해 만든 라이브러리

    솔리디티의 라이브러리는 특별한 컨트랙트 역할을 한다!
    🔊기본(native) 데이터 타입에 함수를 붙일 수 있음

사용법 : using SafeMath for uint256 구문을 추가한다.

  • SafeMath 라이브러리의 4개 함수

    • add
    • sub
    • mul
    • div
      ex)
    using SafeMath for uint256;
    
    uint256 a = 5;
    uint256 b = a.add(3); // 5 + 3 = 8
    uint256 c = a.mul(2); // 5 * 2 = 10

🤍 라이브러리를 사용하는 법
1. 라이브러리 코드를 복붙한 .sol 파일을 생성한다.
2. 라이브러리를 사용할 파일에 해당 파일을 import 한다.
3. using SafeMath for uint256;을 선언한다.

10. SafeMath 파트2

library 키워드

  • 라이브러리는 contract가 아닌 library 키워드로 시작된다.
  • 특징 : using키워드를 통해 라이브러리의 메소드데이터 타입에 적용할 수 있음
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;
  }
}
  • 여기서 모두 인자를 두개로 갖지만, 사용할 uint의 값이 첫번째 인수로 자동 전달될 수 있음.
using SafeMath for uint;
// 우리는 이제 이 메소드들을 아무 uint에서나 쓸 수 있네.
uint test = 2;
test = test.mul(3); // test는 이제 6이 되네
test = test.add(5); // test는 이제 11이 되네

❓ 그렇다면 얘네가 어떻게 오버플로우를 막아주는가?
add 함수를 보면 assert(c >= a)가 있는데, 얘는 합한 값이 a보다 커야한다는 조건이다. 즉, 오버플로우가 발생하여 합을 했음에도 결과가 작아지는 것을 막는 방법이다.

assert?

  • require과 유사한 조건문이다. 조건 불만족 시, 에러가 발생한다.
  • 차이점 :
    require : 함수 실행이 실패하면 남은 가스를 유저에게 돌려줌.
    assert : 유저에게 안돌려줌. -> 코드가 잘못 실행되는 경우에 사용함. (like overflow)

즉! SafeMath의 네 종류의 함수는 사칙연산 시, 오버플로우/언더플로우가 발생하면 오류를 발생시켜준다.

11. SafeMath 파트 3

(새로 배운 내용 없음)

12. SafeMath 파트 4

(새로 배운 내용 없음)

13. 주석

  • 한줄 주석 : //
  • 여러 줄 주석 : /* */
  • 솔리디티 커뮤니티의 표준 주석 형식 : natspec
/// @title 기본적인 산수를 위한 컨트랙트
/// @author H4XF13LD MORRIS 💯💯😎💯💯
/// @notice 지금은 곱하기 함수만 추가한다.
contract Math {
  /// @notice 2개의 숫자를 곱한다.
  /// @param x 첫 번쨰 uint.
  /// @param y 두 번째 uint.
  /// @return z (x * y) 곱의 값
  /// @dev 이 함수는 현재 오버플로우를 확인하지 않는다.
  function multiply(uint x, uint y) returns (uint z) {
    // 이것은 일반적인 주석으로, natspec에 포함되지 않는다.
    z = x * y;
  }
}
  • @title, @author : 말그대로.
  • @notice : 컨트랙트/함수가 하는 일 설명.
  • @dev : 개발자에게 상세 정보 설명
  • @param, @return : 함수의 매개 변수와 반환값 설명
profile
뭐든 다해보려는 공대생입니다

0개의 댓글