ERC-721과 NFT

Lumi·2021년 11월 25일
post-thumbnail

🔥 ERC-721

좀더 사용자 입장에서 익숙한 용어는 바로 NFT이다.

이더리움의 개선 제안의 721번쨰에서 하나하나 구분 가능한 토큰에 대한 토론이 이루어 졌다.

ERC-20같이 완전히 대체 가능한 토큰이 아니라 오로지 단 하나의 토큰으로만 존재하는 토큰을 만드는 제안이였고

이를 NFT라고 부르게 되었다.

🌪 NFT가 왜 필요할까

NFT가 존재하는 이유에 대해서 생각을 해보자.

나의 사견으로는 일단 현 시점에서 NFT는 너무 비싸다고 생각을 한다.

일단 NFT가 존재하는 이유는 간단하다. 유일한 자산이 필요하기 때문이다.

현실 사회에서 집이라는 존재를 생각해보자.

물론 임대라는 역할로써의 집도 있지만

임대라는 역할의 집을 수행하려면 누군가가 그 집의 주인이 되어야 한다.

NFT가 이 주인 역할을 수행하게 된다.

현실 사회에서는 이 주인이라는 역할을 증명하려면 많은 서류와 과정이 필요하게 된다.

하지만 NFT를 활용하면 누가 주인인지 생태계에 입증이 되고 매우 간단한 과정이라고 할 수가 있다.

  • 이로인해 현실사회에서 발생하는 비용, 시간을 거의 없다시피 할 수가 있다.

이외에도 게임에도 정말 다양한 방식으로 사용가능하다.

  • 예를들면 자신만을 위한 아이템, 수집품 등등

NFT가 큰 가치를 지니는 이유는 유일무이하다는 특징 떄문이다.

🌪 ERC-20 vs ERC-721

둘의 가장 큰 차이는 중복이 가능하냐 이다.

ERC-20토큰은 누구나 발행이 가능하며 주인이 없는 토큰이라고 할 수가 있다.

하지만 ERC-721토큰은 주인이 명확하게 주어지는 토큰이며 누구나 발행 할 수 있지만 그 데이터에 대한 값은 오로지 하나이다.

🔥 ERC-721 함수의 기능

ERC721토큰은 일단 ERC20토큰보다 복잡하다..

일단 기본적으로 OpenZeppelin을 이용하여 고유한 NFT를 만드는 코드를 살펴보자.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract GameItem is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() public ERC721("GameItem", "ITM") {}

    function awardItem(address player, string memory tokenURI) public returns (uint256){
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
     }
}

모든 플레이어 계정에서 awardItem을 통해서 고유한 아이템을 생성 가능하다.

🌪 ERC-721에 대한 필수 함수 및 인터페이스

https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#IERC721-balanceOf-address-

🔨 ApprovalForAll

이벤트 함수이다.

ApprovalForAll(address owner, address operator, bool approved)

컨트랙트 오너(owner)가 오퍼레이터에게 모든 자산을 관리할 수 있는 권한을 부여하거나 없애는 역할이다.

setApprovalForAll함수를 실행하면 이 이벤트가 실행이 된다.

  • setApprovalForAll(operator, _approved)

🔨 ownerOf(uint256 tokenId)

tokenId를 매개변수로 받으며 해당 값에 맞는 NFT소유자를 알려준다.

  • 주소를 리턴함으로써 소유자를 알려준다.
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
}

public으로 누구나 소유자가 누구인지 확인이 가능하며

만약 토큰의 수유자가 없다면 에러를 발생시킨다.

  • _owners에는 토큰의 소유자에 대한 주소값들이 저장되어 있다.

🔨 setApprovalForAll(address operator, bool _approved)

앞서 말했듯이 ApprovalForAll 이벤트를 실행시키는 함수이다.

관리자만이 함수를 실행 가능하며 오퍼레이터의 모든 자산을 관리할 수 있는 권한을 부여하거나 없애는 역할이다.

function setApprovalForAll(address operator, bool approved) public virtual override
{
        _setApprovalForAll(_msgSender(), operator, approved);
}

function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
}

기본적인 setApprovalForAll함수는 실행 가능하다.

  • public이고 따로 제약이 없기 떄문에

하지만 그다음 바로 실행되는 _setApprovalForAll함수는 조건이 적혀 있기 떄문에 제약에 걸리게 된다.

  • 이러한 이유로 관리자만이 실행 가능하다.

이 함수는 관리자가 다른 주소에 권한을 주는 함수이며 검증후에 다음 코드로 넘어가게 된다.

🔨 getApproved(uint256 tokenId)

function getApproved(uint256 tokenId) public view virtual override returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
}

변수로 주어지는 tokenId에 매칭된 주소를 반환하는 함수이다.

  • _tokenApprovals에는 승인된 주소들 목록이 담겨 있다.

그러기 떄문에 승인된 주소들을 확인하는 함수라고 할 수가 있다.

  • tokenId가 존재하지 않다면 오류가 발생한다.

🔨 isApprovedForAll(address owner, address operator)

function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
}

오퍼레이터가 관리자의 모든 자산을 관리 할 수 있는 권한을 가지고 있는지 확인하는 함수이다.

  • bool값을 return한다.

🌪 ERC-165

ERC165는 ERC-721 스마트 컨트랙트를 생성할 떄 반드시 구현해야 하는 인터페이스를 검사하고, 언제 사용하는지 감지하는 역할이다.

함수 supportsInterface(bytes4 interfaceID) 만을 가지고 있으며 변수로 ERC-721의 인터페이스 ID를 입력해야만 ERC-721스마트 컨트랙트가 정상적으로 작동하게 된다.

pragma solidity ^0.4.20;

interface ERC165 {
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

다음은 ERC165 인터페이스 이다.

ERC-721에 작성되어 있는 인터페이스 id는 0x80ac58cd이다.

이 id는 기본적인 인터페이스를 몇번의 암호화 과정을 거쳐 축약되는 것이다.

  • 즉 id는 함수를 하나의 leaf로 두고 생성한 머클 루트로 볼 수가 있다.

쉽게 말해 keccak256을 통해서 모든 함수를 각각 암호화 하고 그 값을 축약한 id값이다.

🌪 IERC721Receiver

자산의 컨트랙트에서 safeTransfer를 지원하려는 모든 컨트랙트에 대한 인터페이스이다.

🔨 onERC721Received(address operator, address from, uint256 tokenId, bytes data)

NFT의 수신을 처리하는 이 컨트랙트는 safeTransfer후, 이 함수를 호출하게 된다.

  • 이 함수는 함수 선택자를 반환한다.
  • 만약 그렇지 않을 경우, 트랜잭션은 실패한다.
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }
    
function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4);

🌪 IERC721Metadata

인터페이스 이다.

🔨 tokenURI(uint256 tokenId)

이 함수는 tokenId를 입력받아 URI를 리턴한다.

  • 일반적으로 NFT에 포함될 이름, 설명, 이미지 URI를 포함하는 jSON파일의 형태로 저장한 URI를 반환한다.
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}

일단 tokenId가 있는지를 검사를 하고 함수 _baseURI를 실행하여 baseURI가 있는지를 검사한다.

있다면 tokenId를 합친 문자열을 리턴, 없다면 빈 문자열을 리턴한다.

🔨 baseURI()

기본 URI를 반환한다.

  • _setBaseURI으로 설정한 URI를 반환한다.

🌪 IERC721Enumerable

인터페이스 이다.

🔨 tokenByIndex(uint256 index)

컨트랙트에 의해 저장된 모든 토큰의 index를 기반으로 tokenId를 리턴한다.

  • totalSupply와 함께 사용하여 모든 토큰을 열거할 수 있다.

🔨 tokenOfOwnerByIndex(address owner, uint256 index)

토큰 목록의 인덱스를 기반으로 관리자가 소유한 토큰 ID를 리턴한다.

  • balanceOf와 함께 사용하여 관리자의 모든 토큰을 열거할 수 있다.
profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글