ERC-721 Enumerable 인터페이스
'Enumerable'이란 '나열이 가능한'의 뜻으로 해당 인터페이스의 기능은 발행한 ERC-721 토큰의 전체 리스트를 읽고 필요한 토큰 데이터를 찾게 도와주는 것이다. 메타 데이터와 같이 선택적으로 구현하는 기능이다.
Enumerable이 구현되면 토큰 Index를 통해 특정 토큰 ID를 읽어오는 것이 가능하며, 특정 주소에서 소유한 ERC-721 토큰 역시 찾을 수 있다.
인터페이스에 선언된 함수
- totalSupply : 총 발행된 토큰 수량
- tokenByIndex : 토큰 리스트에서 특정 토큰 ID 조회 (토큰 Index 참조)
- tokenOfOwnerByIndex : 특정 주소 토큰 리스트에서 토큰 ID 조회 (토큰 Index 참조)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// [Optional] ERC721Enumerable 인터페이스
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md.
interface ERC721Enumerable {
// 총 발행량 반환
// -> 유요한 주소에 발행된 토큰 추적
// -> address != 0)
function totalSupply() external view returns (uint256);
// 토큰 ID 반환
// -> 토큰 Index로 조회하며, 총 발행량을 넘어가는 숫자는 조회할 수 없다. (totalSupply() > Index)
function tokenByIndex(uint256 _index) external view returns (uint256);
// 토큰 소유자(Owner)의 보유 토큰 ID 반환
// -> 조회하고자 하는 주소와 토큰 Index 필요하며, Index는 소유자의 총 자산 보유량보다 클 수 없다.
// -> balanceOf(address) > Index
// -> address != 0
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}
ERC-721 Enumerable 컨트렉트
Enumerable 인터페이스를 구현해 발행된 ERC-721 토큰 ID를 추적할 수 있다. 발행 시점에서 주어진 Index를 참조해 전체 토큰 리스트에서 토큰을 찾을 수 있고, 토큰 소유권이 특정 주소로 지정되었을 때 역시 해당 Index를 참조해 토큰을 추적할 수 있다.
부가적으로 tokenByIndex를 totalSupply와 함께 사용하면 모든 토큰을 나열할 수 있고, tokenOfOwnerByIndex를 balanceOf와 함께 사용하면 토큰 소유자의 모든 토큰 리스트를 불러올 수 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Import
import "./nf-token.sol"; // NFToken 컨트렉트
import "./ERC721Enumerable.sol"; // ERC721Enumerable 인터페이스
// NFTokenEnumerable 컨트렉트
contract NFTokenEnumerable is NFToken, ERC721Enumerable {
// [에러 코드] 유효하지 않은 Index (0xcert 프레임워크 기준 에러 코드)
string constant INVALID_INDEX = "005007";
// [리스트] 토큰 ID
uint256[] internal tokens;
// [매핑] 토큰 ID -> 토큰 Index
mapping(uint256 => uint256) internal idToIndex;
// [매핑] 토큰 소유 주소 -> 보유 토큰 리스트(토큰 ID)
mapping(address => uint256[]) internal ownerToIds;
// [매핑] 토큰 ID -> 보유 토큰 리스트의 Index
mapping(uint256 => uint256) internal idToOwnerIndex;
// ERC721Enumerable 인터페이스 검증
constructor() {
supportedInterfaces[0x780e9d63] = true; // ERC721Enumerable
}
// 총 토큰 발행량
function totalSupply() external override view returns (uint256) {
return tokens.length;
}
// 전체 토큰 목록에서 토큰 ID 조회 반환 (토큰 Index로 조회)
// -> Index < totalSupply()
function tokenByIndex(uint256 _index) external override view returns (uint256) {
require(_index < tokens.length, INVALID_INDEX); // 유효 Index 검사
return tokens[_index]; // 토큰 ID 반환
}
// 토큰 소유자가 보유한 토큰 ID 반환 (토큰 Index로 조회)
function tokenOfOwnerByIndex(address _owner, uint256 _index) external override view returns (uint256) {
require(_index < ownerToIds[_owner].length, INVALID_INDEX); // 유효 Index 검사
return ownerToIds[_owner][_index]; // 토큰 ID 반환
}
// [Internal] 토큰 발행
function _mint(address _to, uint256 _tokenId) internal override virtual {
super._mint(_to, _tokenId); // 토큰 발행
tokens.push(_tokenId); // 전체 토큰 목록에 발행 토큰 ID 추가
idToIndex[_tokenId] = tokens.length - 1; // 토큰 ID에 토큰 Index 매핑
}
// [Internal] 토큰 소각
// -> 전체 토큰 리스트 마지막 토큰을 소각하면 가스비 소모(waste)
function _burn(uint256 _tokenId) internal override virtual {
super._burn(_tokenId); // 토큰 소각
uint256 tokenIndex = idToIndex[_tokenId]; // 토큰 Index
uint256 lastTokenIndex = tokens.length - 1; // 리스트 마지막 토큰 Index
uint256 lastToken = tokens[lastTokenIndex]; // 리스트 마지막 발행 토큰 ID
tokens[tokenIndex] = lastToken; // 소각된 토큰 위치에 리스트 마지막 토큰 이동
tokens.pop(); // 리스트 마지막 값 삭제
idToIndex[lastToken] = tokenIndex; // 이동된 토큰에 맞는 Index 설정
idToIndex[_tokenId] = 0; // 소각된 토큰 Index 0 처리
}
// [Internal] 특정 주소(토큰 소유자)로부터 토큰 제거
function _removeNFToken(address _from, uint256 _tokenId) internal override virtual {
require(idToOwner[_tokenId] == _from, NOT_OWNER); // 토큰 소유자 확인
delete idToOwner[_tokenId]; // 토큰 ID에 매핑된 소유자 주소 삭제
uint256 tokenToRemoveIndex = idToOwnerIndex[_tokenId]; // 특정 주소(토큰 소유자) 토큰 목록에서 삭제할 토큰 Index
uint256 lastTokenIndex = ownerToIds[_from].length - 1; // 보유 토큰 리스트의 마지막 Index (리스트 length로 계산)
// 삭제하고자 하는 토큰의 Index가 마지막 Index가 아닐 경우
if (lastTokenIndex != tokenToRemoveIndex) {
uint256 lastToken = ownerToIds[_from][lastTokenIndex]; // 마지막 Index 토큰 ID 추출
ownerToIds[_from][tokenToRemoveIndex] = lastToken; // 삭제할 토큰 Index에 마지막 Index 토큰 ID 입력
idToOwnerIndex[lastToken] = tokenToRemoveIndex; // 마지막 토큰 자리에 삭제할 토큰 Index 입력
}
ownerToIds[_from].pop(); // 토큰 삭제
}
// 보유 토큰 추가
function _addNFToken(address _to, uint256 _tokenId) internal override virtual {
require(idToOwner[_tokenId] == address(0), NFT_ALREADY_EXISTS); // 주소 검증 (address != 0)
idToOwner[_tokenId] = _to; // 토큰에 매핑된 소유자 주소 입력
ownerToIds[_to].push(_tokenId); // 토큰 소유자 자산 목록에 토큰 ID 추가
idToOwnerIndex[_tokenId] = ownerToIds[_to].length - 1; // 토큰 소유자 자산 리스트 Index 입력
}
// 보유중인 토큰 수 반환
function _getOwnerNFTCount(address _owner) internal override virtual view returns (uint256) {
return ownerToIds[_owner].length;
}
}
'Blockchain > Ethereum' 카테고리의 다른 글
[Ethereum] 이더리움, 블록체인 프로그래밍 참고 도서 (0) | 2022.03.27 |
---|---|
[Ethereum] ERC-721 소스 분석(3) - 메타 데이터 (0) | 2022.03.25 |
[Ethereum] ERC-721 소스 분석(2) - 토큰 (0) | 2022.03.24 |
[Ethereum] ERC-721 소스 분석(1) - 인터페이스 (0) | 2022.03.23 |
[Ethereum] ERC-721 (0) | 2022.03.23 |
댓글