ERC-721 설계에 대해 소개드리기 앞서
간단하게 ERC-721이 무엇인지 설명하도록 하겠습니다.
우선 ERC-20, ERC-721에서 ERC는 Ethereum Request for Comment인데요
RFC(Request for Comment)는 주로 인터넷 기술에 대해 새로운 기술을 제시하고 비평을 받기 기다리는 문서입니다.
ERC는 인터넷 기술 대신에 새로운 이더리움 기술이 제시되고 비평을 받게 되며, 다른 사람들의 의견에 따라 표준이 될 수 있습니다.
이더리움 Github에 있는 ERC-20 공식 문서를 살펴보면
Abstract에서 토큰을 전송하고, 제삼자가 토큰을 전송할 수 있게 허락하는 기능에 대한
표준을 제공한다고 합니다.
그러면 공식 문서에서 ERC-721은 어떻게 설명하고 있을까요?
Simple Summary 부분을 보면 ERC-721은 증서라고 알려진 NFT 토큰에 대한 표준을 제공한다고 합니다.
그럼 NFT는 무엇일까요?
NFT는 Non fungible token으로 fungible이 ‘대체가 가능한’이란 뜻이라서
NFT토큰은 대체가 불가능한(유일한) 토큰이라고 생각하시면 됩니다.
ERC-721은 ERC-20처럼 토큰 그 자체보다는 게임에 주로 쓰입니다.
유명한 게임 크립토키티는 고양이를 수집하는 게임인데요, 고양이들의 생김새가 모두 다르고 보유하게 되는 고양이는 이 세상에서 하나밖에 없는 유일한 고양이입니다.
예시를 좀 더 소개하자면 영화 티켓도 ERC-721으로 만들 수 있습니다. 가령 영화 티켓을 사는 입장이라고 생각해봅시다. 친구랑 같이 영화를 보러 간다면 티켓이 두 장 필요할 것입니다.
티켓 모양은 같지만 두 티켓은 엄연히 다르고 유일한 티켓입니다. 왜냐하면 그 티켓은 영화가 시작하는 시간과 좌석번호가 다르기 때문이죠.
같은 시간에 동시에 같은 좌석에 앉아서 영화를 볼 경우는 없으니
영화티켓도 ERC-721 속성을 가지고 있다고 할 수 있습니다.
ERC-721의 설명은 이 정도로 마치고, ERC-721 토큰에 어떤 기능이 있는지 알아보겠습니다.
ERC-721 기반 토큰에 대해 알아보기 위해 EIP 공식문서를 직접 참고했습니다.
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
Openzeppelin에서는 ERC-721에서 구현해야 할 인터페이스들을 소스코드로 제공합니다.
이 인터페이스들을 상속하면 ERC-721 기반 토큰을 만들 수 있습니다.
공식 EIP 문서에는 주석으로 각 함수가 어떤 기능들을 수행해야 할지 명시해 놓았습니다.
각 함수들이 어떤 기능들을 하는지 주석을 해석해서 정리해 보았습니다.
우선 (a.)balanceOf는 address를 변수로 받고, balance을 리턴합니다.
ERC-20에도 같은 함수가 있는데 address가 보유하는 토큰의 총량을 보여줍니다.
하지만 ERC-721은 NFT 토큰이기 때문에 이와 다르게 NFT 토큰의 개수를 리턴하게 됩니다.
크립토키티를 예로 들면 A고양이, B고양이, C고양이를 보유하고 있을 때 (a.)balanceOf는 3을 리턴합니다.
참고로 A고양이, B고양이, C고양이는 대체 불가능(NFT)하기 때문에 A고양이 2마리, B고양이 5마리 이렇게 보유할 수가 없습니다.
(b.)OwnerOf는 token의 ID를 변수로 받고, address를 리턴합니다.
예를 들어 B고양이의 tokenID를 저 함수에 입력하면 B고양이를 보유하는 address “0x..” 이런 식으로 값을 리턴하는 것입니다.
앞에서 말한 것처럼 B고양이는 유일하기 때문에 B고양이를 보유하고 있는 address는 오직 하나밖에 없겠죠?
(c.)approve와 (c-1.)getApproved는 기능이 비슷해서 묶어봤습니다.
(c.)approve는 ERC-20의 approve와 동일합니다. 앞에서 언급했듯이 ERC-20은 토큰을 전송하는 것뿐만 아니라 승인받은 제삼자가 토큰을 전송하는 기능에 대한 표준을 제공합니다. 토큰을 대신 전송하는 사람을 Operator라고 하며, 토큰을 보유하고 있는 사람이 tokenID와 Operator의 address을 입력하면 Operator에게 해당 토큰 거래를 허용하게 되는 것입니다.
(c-1.)getApproved는 단순합니다.
해당 tokenID를 입력하면 그 토큰에 해당하는 Operator를 반환해줍니다.
(d.)setApprovalForAll 과 (d-1.)isApprovedForAll 은 (c.)와 (c-1.)을 한꺼번에 처리하는 것으로 기억하시면 되겠습니다.
(d.)setApprovalForAll 을 호출한 NFT 토큰 Owner는 자신이 보유한 모든 NFT토큰에 대해 Operator가 전송 권한을 갖게 할 수 있습니다. _approved 변수에 true를 입력하면 모든 토큰에 대한 전송 권한을 갖게, false를 입력하면 모든 토큰에 대한 전송 권한을 취소하게 됩니다.
(d-1.)isApprovedForAll은 (c-1.)처럼 해당 주소가 권한이 있는지 없는지 여부를 확인할 수 있습니다.
(e.)transferFrom 은 핵심이기 때문에 Openzeppelin에서 실제로 구현된 코드를 보여드리겠습니다.
Require(…)은 require 안에 있는 내용이 거짓이면 함수의 실행을 중단하게 됩니다(throw).
그래서 코드를 보면
첫 번째 require에서 해당 토큰을 보내려는 address가 토큰의 Owner이거나 Operator인지 확인하게 됩니다. 맞다면 문제없이 다음 함수들을 call하게 됩니다.
_clearApproval 은 토큰이 다른 address로 이전되기 때문에 기존에 토큰을 전송할 수 있는 권한을 가지고 있는 Operator의 권한을 없앱니다.
_removeTokenFrom 은 Owner의 토큰을 없애고
_addToken 은 새로운 Owner에게 토큰을 전달하게 됩니다.
(f.) safeTransferFrom 이 두 개 있는데 하나는 bytes 형의 인자를 더 받고 하나는 안 받습니다.
Openzeppelin 코드를 보면
후자의 경우 bytes형의 인자를 보낸 대신에 전자의 data 부분을 “” 인 공백을 보내는 것으로
쉽게 해결합니다…
(f.) byte가 있는 safeTransferFrom는 실제 유저의 주소(EOA)가 아닌 스마트컨트랙트 주소에 보낼 때 사용됩니다.
해당 스마트컨트랙트가 ERC-721토큰을 받을 수 있는 토큰인지 확인을 하는 것이죠.
그래서 (e.) transferFrom을 사용할 때, 실제 유저의 주소에 보내는 건 상관없지만, 스마트컨트랙트 주소에 ERC-721 토큰을 보낼 때, 소실될 수 있다고 경고를 하고 있죠…
또한 ERC-721 토큰을 받으려는 스마트컨트랙트 주소는 해당 인터페이스를 구현하는 것이 필수적이라고 명시되어있습니다.
그래서 Openzeppelin의 소스코드를 참조하면 해당 스마트컨트랙트 주소가 ERC-721을 받을 수 있는지 ERC-165를 통해서 체크합니다.
ERC-165를 간단하게 설명하자면 keccak256을 사용해 해당 코드가 ERC-20, ERC-721 표준을 따르는지 검사하기 위한 표준입니다. (Standard를 지키는 여부를 확인하는 Standard…)
ERC-721기반의 토큰을 처음부터 끝까지 설계하는 방법을 설명하려면
몇 개월을 연재해야 할 정도로 분량이 길어집니다ㅠ
그래서 상세하게 설명을 하진 않았지만
ERC-721 설계에 필요한 전반적인 구조를 보여드리려고 했습니다.
참고로 저는 실제 ERC-721 토큰을 만들기 위해
https://medium.com/coinmonks/jumping-into-solidity-the-erc721-standard-part-1-e25b67fc91f3
위 사이트에서 도움을 많이 받았습니다.
읽어주셔서 감사합니다.
황호진(Hojin Hwang) | 고려대학교 블록체인 학회 KUBL | zxoihojin@gmail.com
관심분야: 블록체인, 게임 개발
출처
[1] RFC
https://ko.wikipedia.org/wiki/RFC
[2] ERC-721 깃허브
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
[3] Openzeppelin ERC-721 구현
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol