본문 바로가기
프로그래밍/solidity

ERC721토큰

by 한코코 2022. 7. 18.

토큰이란?

이더리움에서 토큰이란 기본적으로 몇몇 공통 규약을 따르는 스마트 컨트랙트.

즉, 다른 모든 토큰 컨트랙트가 사용하는 표준 함수 집합을 구현하는것.

누가 얼마나 가지고 있는지 기록하는 하나의 컨트랙트.

 

 

 

 

ERC20 토큰을 사용하는 이유는?

ERC20 토큰들이 동일한 함수 집합을 공유하기 때문에 이 토큰들에 같은 방식으로 상호작용이 가능하기때문.

예) 내부적으로 스마트 컨트랙트는 각 주소에 잔액이 얼마나 있는지 기록하기 위해 다음과 같은 매핑을 가지고 있다.

mapping(address => uint256) balances

이런 식으로 같은 구조를 갖고있기 때문에 특정 함수를 사용해서 토큰을 주고받기가 가능하다.

거래소에서 새로운 토큰을 상장하면 그저 새로운 스마트 컨트랙트를 거래소에 추가하는것과 다르지 않다는 말.

 

 

 

 

ERC721 토큰을 사용하는 이유는?

다른 코인으로 대체가 가능한 ERC20 토큰과 달리 ERC721 토큰은 대체 불가능하다.

ERC20 토큰처럼 나눌 수도 없고 하나의 전체 단위로만 거래할 수 있다.

대체 불가능하기때문에 각각 유일한 ID를 가지고 있고, 수집품 같은 상품을 다룰때 적합하다.

 

 

 

 


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 다중상속

솔리디티에서는 다중상속이 가능하다.

import "./erc721.sol";
contract ZombieOwnership is ZombieAttack, ERC721 {}

 

 

 

 

ERC721 메소드 : balanceOf

address를 받아 해당 address가 토큰을 얼마나 가지고 있는지 반환한다.

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

 

 

 

 

ERC721 메소드 : ownerOf

토큰 ID를 받아서 이를 소유하고 있는 사람의 address를 반환한다.

사용하는 이유 : 토큰 소유자가 아닌 사람이 접근해서 임의로 다른 사람들에게 토큰을 전송하는것을 막기 위해.

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

 

 

 

ERC721 전송 로직 두가지

1)  토큰을 보내는 사람이 함수를 호출하는 로직

transfer(address _from, address _to, uint256 _tokenId) : _from( 계정 )에서  _to( 계정 )으로 _tokenId(  )만큼의 이더를 전송.

토큰의 소유자가 전송상대의 address, 전송하고자 하는 _tokenId와 함께 transfer 함수를 호출하는것

function transfer(address _to, uint256 _tokenId) public;

 

 

2)  토큰을 받는 사람이 함수를 호출하는 로직

approve(address _to, uint _tokenId) : _tokenId( 토큰 )를 _to( 계정 )으로 전송함

토큰의 소유자가 전송상대의 address, 전송하고자 하는 _tokenId와 함께 approve를 호출함.

컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장함

보통 mapping (uint256 => address)를 사용해서 허가정보를 저장함.

 

새로운 소유자(msg.sender)가 _tokenId를 사용해서 takeOwnership호출.

takeOwnership를 호출하면 해당 컨트랙트는 이 msg.sender가 소유자로부터 토큰을 받을 수 있게 허가를 받았는지 확인.

허가를 받았다면 토큰을 그에게 전송함.

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

 

 

 

 

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

uint8 number = 255;
number++;

8비트를 저장할 수 있는 uint8에 저장할 수 있는 가장 큰 수는 11111111 즉, 2의8승(2^8) 255다.

하지만 그보다 더 큰수를 넣으면 numbers는 11111111을 넘어선 00000000이 되므로 결국 0이 된다.

이게 오버플로우다.

 

언더플로우는 반대로 0에서 1을 빼면 그 전의 값인 255로 돌아가는 것을 말한다.

uint는 부호가 없어서 음수가 될 수 없기 때문이다.

시계가 12:59를 넘으면 00:00이 되는것을 생각하면 된다.

 

그래서 OpenZepplin에서는 이 문제를 막기 위해서 SafeMath 라이브러리를 만들어 놓았다.

 

 

 

 

 

SafeMath 라이브러리

라이브러리 : 솔리디티에서 특별한 종류의 컨트랙트를 가르킨다. native(기본) 데이터 타입에 함수를 붙일때도 사용된다.

assert 구문은 ()소괄호 안에 넣은 조건이 언제나 성립하도록 보장한다.

이 조건을 만족히키지 않으면 에러가 발생하는건 require과 비슷하지만,

실패시 require은 남은 가스비를 돌려주고, assert는 돌려주지 않는다.

그래서 assert 구문은 코드가 심각하게 잘못 실행될때 사용된다.

import "./safemath.sol";
using SafeMath for uint256;

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

 

add(+), sub(-), mul(*), div(/)로 사용된다.

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

 

오버플로우나 언더플로우를 막기 위한 라이브러리이므로 uint 크기에 따라 라이브러리를 다르게 적용시켜야한다.

using SafeMath for uint256;
using SafeMath32 for uint32;
using SafeMath16 for uint16;

 

 

 

솔리디티의 독특한 주석약속, natspec ///

모두 /// 뒤에 작성된다.

@title : 개인/인터페이스를 설명하는 태그

@auther : 저자의 이름

@notice : 사용자에게 컨트랙트/함수가 무엇을 하는지 설명

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

@param : Doxygen에서와 같이 매개변수를 문서화합니다(다음에는 매개변수 이름이 와야 함).

@return : 계약 문서의 반환 함수를 문서화함

@inheritdoc : 기본 기능에서 누락된 모든 태그를 복사함

@custom : 사용자 정의 태그

 

댓글